protoj.lang.InstructionChain Maven / Gradle / Ivy
Show all versions of protoj-jdk6 Show documentation
/**
* Copyright 2009 Ashley Williams
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package protoj.lang;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import protoj.lang.internal.InstructionChainArgs;
import protoj.util.ArgRunnable;
/**
* Represents the list of instruction that are to be executed, that are usually
* parsed from the command line. However instructions may be programmatically
* added with the {@link #addInstruction(String, String)} method. The
* instructions are separated by spaces and consist of a name with possible
* options. Each instruction can also contain spaces when options are specified
* and so when this is the case, quotes be used around the entire instruction.
* The following example is an chain of two instructions:
*
*
* command1 "command2 -optA -optB"
*
*
* The first instruction consists of just a name whereas the second instruction
* consists of a name as well as two options.
*
* Most of the instructions correspond to commands with the same name from the
* commandStore that should be executed. However there are three special
* instructions that need specific handling:
*
* - {@link Instruction#INIT} instruction: provides initialization information
* for the protoj domain objects including the project root directory and script
* name used to start the application.
* - {@link Instruction#OPTS} instruction: provides system properties to the
* application
*
*
* @author Ashley Williams
*
*/
public final class InstructionChain {
/**
* The command instructions ready to be processed.
*/
private LinkedHashMap commandInstructions;
/**
* See {@link #getJvmArgs()}.
*/
private List jvmArgs;
/**
* See {@link #getRootDir()}.
*/
private File rootDir;
/**
* See {@link #getScriptName()}.
*/
private String scriptName;
/**
* See {@link #getInitInstruction()}.
*/
private Instruction initInstruction;
/**
* See {@link #getOptsInstruction()}.
*/
private Instruction optsInstruction;
/**
* See {@link #breakVisit()}.
*/
private boolean breakVisit;
/**
* Creates an instance based on command line arguments.
*
* @param args
*/
public InstructionChain(String[] args) {
new InstructionChainArgs(this, args).initChain();
}
/**
* Alternative constructor that doesn't rely on a main args array.
*
* @param rootDir
* @param scriptName
*/
public InstructionChain(File rootDir, String scriptName) {
init(rootDir, scriptName);
}
/**
* Common initialization method. Called by the constructors as well as by
* the InstructionChainArgs helper class.
*
* @param rootDir
* @param scriptName
*/
public void init(File rootDir, String scriptName) {
this.rootDir = rootDir.getCanonicalFile();
this.scriptName = scriptName;
this.commandInstructions = new LinkedHashMap();
this.jvmArgs = new ArrayList();
}
/**
* See {@link #addJvmArg(String)}.
*
* @return
*/
public List getJvmArgs() {
return Collections.unmodifiableList(jvmArgs);
}
/**
* Adds an additional jvm arg that will be applied to each command that is
* started in a new vm during the call to
* {@link DispatchFeature#startVm(String, String[], String, ArgRunnable)}.
* The specified jvmArg should be in -DX=Y format.
*
* @param jvmArg
*/
public void addJvmArg(String jvmArg) {
jvmArgs.add(jvmArg);
}
/**
* Adds another item to the configuration. A single item may contain not
* just the name but also the options, for example:
* "mycommand -arg1 -arg2"
. The first word in each instruction
* string when spaces are used as separators is assumed to be the name of
* the instruction.
*
* The special init and opts instructions are kept out of the
* commandInstructions property since they don't correspond to commands in
* the command store. Instead they receive special treatment and are
* assigned to the initInstruction and optsInstruction properties instead.
*
* If an instruction of the same name already exists it will be replaced.
* This limitation may be revisited in future.
*
* @param name
* @param options
* @return
*/
public Instruction addInstruction(String name, String options) {
Instruction instruction = new Instruction(name, options);
if (instruction.isInitInstruction()) {
initInstruction = instruction;
} else if (instruction.isOptsInstruction()) {
optsInstruction = instruction;
} else {
commandInstructions.put(name, instruction);
}
return instruction;
}
/**
* The init definition that should always be present.
*
* @return
*/
public Instruction getInitInstruction() {
return initInstruction;
}
/**
* The opts definition that may be null.
*
* @return
*/
public Instruction getOptsInstruction() {
return optsInstruction;
}
/**
* Removes the instruction with the given name and returns it. Null is
* returned if no matching instruction is found.
*
* @param name
* @return
*/
public Instruction remove(String name) {
return commandInstructions.remove(name);
}
/**
* Visits each of the instructions in turn, removing them as they are
* encountered. This is because the assumption is that the commands are
* being handled one at a time and don't want to risk them being rehandled.
* In order to stop the visit early, the visitor should call
* {@link #breakVisit()}. This will mean that it doesn't get passed any
* further instructions, but they are all still removed by the time this
* method returns.
*
* @param visitor
*/
public void visitAndRemove(ArgRunnable visitor) {
breakVisit = false;
ArrayList instructions = new ArrayList();
instructions.addAll(commandInstructions.values());
// iterate over a copy of the instruction values and remove the whole
// hashmap entry each time during the iteration
Iterator values = instructions.listIterator();
while (values.hasNext() && !breakVisit) {
Instruction instruction = values.next();
visitor.run(instruction);
values.remove();
remove(instruction.getName());
}
commandInstructions.clear();
}
/**
* See {@link #visitAndRemove(ArgRunnable)}.
*
*/
public void breakVisit() {
this.breakVisit = true;
}
/**
* Creates command line args suitable for passing in a main method,
* including all the command instructions in this chain. Delegates to
* {@link #createMainArgsAsArray(Collection)}. The command instructions are
* then removed since the assumption is that a new vm will be started to
* handle them instead. We don't want to handle them again in the current
* vm.
*
* @param skipFirst
* specify true if the first command instruction isn't required -
* useful when bootstrapping commands not including the current
* @return
*/
public String[] createArgsAndRemove(boolean skipFirst) {
Collection values;
if (skipFirst) {
ArrayList skippedList = new ArrayList();
skippedList.addAll(commandInstructions.values());
skippedList.remove(0);
values = skippedList;
} else {
values = commandInstructions.values();
}
String[] createMainArgs = createMainArgsAsArray(values);
// TODO can't always clear since may be inside visitAndRemove() iterator
// commandInstructions.clear();
return createMainArgs;
}
/**
* Creates command line args suitable for passing in a main method using the
* specified list of instruction commands. The resulting returned array
* contains the init instruction as the first element and the opts
* instruction, if any, as the last element.
*
* Note that if this is called by a visitor to
* {@link #visitAndRemove(ArgRunnable)} then the returned array will include
* the current instruction since it doesn't get removed until after the
* visit.
*
* @param commandInstructions
* @return
*/
private String[] createMainArgsAsArray(
Collection commandInstructions) {
ArrayList args = new ArrayList();
args.add(getInitInstruction().getText());
boolean containsDefinitions = commandInstructions.size() > 0;
if (containsDefinitions) {
for (Instruction definition : commandInstructions) {
args.add(definition.getText());
}
}
if (getOptsInstruction() != null) {
args.add(getOptsInstruction().getText());
}
return args.toArray(new String[] {});
}
/**
* Creates command line args suitable for passing in a main method,
* including just the specified instruction name and opts parameters.
* Delegates to {@link #createMainArgsAsArray(Collection)}.
*
* @param name
* @param opts
* @return
*/
public String[] createMainArgs(String name, String opts) {
Instruction command = new Instruction(name, opts);
ArrayList commands = new ArrayList();
commands.add(command);
return createMainArgsAsArray(commands);
}
/**
* The project root directory.
*
* @return
*/
public File getRootDir() {
return rootDir;
}
/**
* The name of the script that invokes this application.
*
* @return
*/
public String getScriptName() {
return scriptName;
}
}