001/*
002 * To change this template, choose Tools | Templates
003 * and open the template in the editor.
004 */
005package org.anarres.qemu.qapi.common;
006
007import com.fasterxml.jackson.databind.JsonNode;
008import com.fasterxml.jackson.databind.ObjectMapper;
009import java.io.BufferedReader;
010import java.io.Closeable;
011import java.io.EOFException;
012import java.io.IOException;
013import java.io.InputStreamReader;
014import java.io.OutputStreamWriter;
015import java.io.Writer;
016import java.net.InetAddress;
017import java.net.InetSocketAddress;
018import java.net.Socket;
019import java.nio.charset.Charset;
020import javax.annotation.Nonnegative;
021import javax.annotation.Nonnull;
022import org.anarres.qemu.qapi.api.VersionInfo;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * A connection to a QEmu process.
028 *
029 * @author shevek
030 */
031public class QApiConnection implements Closeable {
032
033    private static final Logger LOG = LoggerFactory.getLogger(QApiConnection.class);
034    private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
035    private final ObjectMapper mapper = new ObjectMapper();
036    private final Socket socket;
037    private final Writer output;
038    private final BufferedReader input;
039    private final QApiGreeting greeting;
040
041    public QApiConnection(@Nonnull Socket socket) throws IOException {
042        this.socket = socket;
043        this.output = new OutputStreamWriter(socket.getOutputStream(), ISO_8859_1);
044        this.input = new BufferedReader(new InputStreamReader(socket.getInputStream(), ISO_8859_1));
045        this.greeting = read(QApiGreeting.class);
046        /* QmpCapabilitiesCommand.Response capabilities = */ invoke(new QmpCapabilitiesCommand());
047        // assert !capabilities.isError();
048    }
049
050    public QApiConnection(@Nonnull InetAddress address, @Nonnegative int port) throws IOException {
051        this(new Socket(address, port));
052    }
053
054    public QApiConnection(@Nonnull String address, @Nonnegative int port) throws IOException {
055        this(InetAddress.getByName(address), port);
056    }
057
058    public QApiConnection(@Nonnull InetSocketAddress address) throws IOException {
059        this(address.getAddress(), address.getPort());
060    }
061
062    @Nonnull
063    public QApiGreeting getGreeting() {
064        return greeting;
065    }
066
067    @Nonnull
068    public VersionInfo getQEmuVersion() {
069        return getGreeting().QMP.version;
070    }
071
072    @Nonnull
073    private <T> T read(@Nonnull Class<T> type) throws IOException {
074        for (;;) {
075            String line = input.readLine();
076            if (line == null)
077                throw new EOFException();
078            if (LOG.isDebugEnabled())
079                LOG.debug("<<<" + line);
080            JsonNode tree = mapper.readTree(line);
081            if (tree.get("event") != null)  // Could parse a QApiEvent.
082                continue;
083            return mapper.treeToValue(tree, type);
084        }
085        // return mapper.readValue(line, type);
086    }
087
088    @Nonnull
089    public <Response extends QApiResponse<?>> Response invoke(@Nonnull QApiCommand<?, Response> command) throws IOException {
090        String line = mapper.writeValueAsString(command);
091        if (LOG.isDebugEnabled())
092            LOG.debug(">>>" + line);
093        output.write(line);
094        output.write('\n');
095        output.flush();
096        Class<Response> returnType = command.getReturnType();
097        return read(returnType);
098    }
099
100    @Nonnull
101    public <Result, Response extends QApiResponse<Result>> Result call(@Nonnull QApiCommand<?, Response> command) throws IOException, QApiException {
102        return invoke(command).getResult();
103    }
104
105    @Override
106    public void close() throws IOException {
107        socket.close();
108    }
109}