001/*
002 * To change this template, choose Tools | Templates
003 * and open the template in the editor.
004 */
005package org.anarres.qemu.exec;
006
007import java.io.IOException;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.List;
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013import org.anarres.qemu.exec.recipe.QEmuRecipe;
014import org.anarres.qemu.exec.util.QEmuCommandLineUtils;
015import org.anarres.qemu.exec.util.QEmuIdAllocator;
016import org.anarres.qemu.exec.util.QEmuOptionsList;
017
018/**
019 * An execution command line for a QEmu process.
020 *
021 * A QEmuCommandLine is a tree of {@link QEmuOption}, not a list
022 * (see {@link QEmuOptionsList}).
023 *
024 * Disk and bus IDs within a QEmuOption may be allocated using the embedded
025 * {@link QEmuIdAllocator} in order to guarantee uniqueness.
026 *
027 * For convenience, composites of commonly used options are provided as
028 * {@link QEmuRecipe}s.
029 *
030 * @author shevek
031 */
032public class QEmuCommandLine {
033
034    private final QEmuIdAllocator allocator;
035    @Nonnull
036    private QEmuArchitecture architecture;
037    @CheckForNull
038    private String commandName;
039    private final List<QEmuOption> options = new ArrayList<QEmuOption>();
040
041    private QEmuCommandLine(@Nonnull QEmuArchitecture architecture, @Nonnull QEmuIdAllocator allocator) {
042        this.allocator = allocator;
043        this.architecture = architecture;
044    }
045
046    public QEmuCommandLine(@Nonnull QEmuArchitecture architecture) {
047        this(architecture, new QEmuIdAllocator());
048    }
049
050    public QEmuCommandLine(@Nonnull QEmuCommandLine prototype) {
051        this(prototype.getArchitecture(), new QEmuIdAllocator(prototype.getAllocator()));
052        addOptions(prototype.getOptions());
053    }
054
055    @Nonnull
056    public QEmuArchitecture getArchitecture() {
057        return architecture;
058    }
059
060    public void setArchitecture(@Nonnull QEmuArchitecture architecture) {
061        this.architecture = architecture;
062    }
063
064    @Nonnull
065    public QEmuIdAllocator getAllocator() {
066        return allocator;
067    }
068
069    /** Returns all the options in this command line. */
070    @Nonnull
071    public List<? extends QEmuOption> getOptions() {
072        return options;
073    }
074
075    /**
076     * Returns the name (possibly with path) of the binary used to execute qemu.
077     *
078     * This is derived from {@link #getArchitecture()} if not explicitly
079     * set using {@link #setCommandName(java.lang.String)}.
080     */
081    @Nonnull
082    public String getCommandName() {
083        String c = commandName;
084        if (c != null)
085            return c;
086        return getArchitecture().getCommand();
087    }
088
089    public void setCommandName(@Nonnull String commandName) {
090        this.commandName = commandName;
091    }
092
093    private static <T extends QEmuOption> void getOptions(@Nonnull List<? super T> out, @Nonnull Iterable<? extends QEmuOption> in, @Nonnull Class<T> type) {
094        for (QEmuOption option : in) {
095            if (type.isInstance(option))
096                out.add(type.cast(option));
097            if (option instanceof QEmuOptionsList)
098                getOptions(out, (QEmuOptionsList) option, type);
099        }
100    }
101
102    /**
103     * Returns all options of the specified type in this command line.
104     */
105    @Nonnull
106    public <T extends QEmuOption> List<? extends T> getOptions(@Nonnull Class<T> type) {
107        List<T> out = new ArrayList<T>();
108        getOptions(out, getOptions(), type);
109        return out;
110    }
111
112    /**
113     * Returns the first option of the specified type in this command line.
114     *
115     * If there are no options of type <code>type</code>, null is returned.
116     * If there is more than one option of type <code>type</code>, the subsequent
117     * options are ignored.
118     */
119    @CheckForNull
120    public <T extends QEmuOption> T getOption(@Nonnull Class<T> type) {
121        List<? extends T> options = getOptions(type);
122        if (options.isEmpty())
123            return null;
124        return options.iterator().next();
125    }
126
127    public void addOptions(@Nonnull Iterable<? extends QEmuOption> options) {
128        for (QEmuOption option : options)
129            if (option != null)
130                this.options.add(option);
131    }
132
133    public void addOptions(@Nonnull QEmuOption... options) {
134        addOptions(Arrays.asList(options));
135    }
136
137    @Nonnull
138    public List<String> toCommandWords() {
139        List<String> line = new ArrayList<String>();
140        line.add(getCommandName());
141        for (QEmuOption option : options)
142            option.appendTo(line);
143        return line;
144    }
145
146    /**
147     * Executes this QEmuCommandLine and returns a new {@link Process}.
148     */
149    @Nonnull
150    public Process exec() throws IOException {
151        List<String> commandWords = toCommandWords();
152        ProcessBuilder builder = new ProcessBuilder(commandWords);
153        // TODO: Use Redirect to send the I/O somewhere useful.
154        QEmuCommandLineUtils.redirectIO(builder);
155        return builder.start();
156    }
157
158    @Override
159    public String toString() {
160        StringBuilder buf = new StringBuilder();
161        for (String word : toCommandWords()) {
162            if (buf.length() > 0)
163                buf.append(' ');
164            buf.append(word);
165        }
166        return buf.toString();
167    }
168}