All Downloads are FREE. Search and download functionalities are using the official Maven repository.

protoj.lang.Command Maven / Gradle / Ivy

The newest version!
/**
 * 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import joptsimple.OptionParser;
import joptsimple.OptionSet;
import protoj.lang.internal.InformationException;
import protoj.lang.internal.ProjectReporter;
import protoj.util.ArgRunnable;
import protoj.util.JavaTask;

/**
 * Represents a unit of work to be carried out by the project. A command
 * consists of two parts:
 * 
    *
  1. bootstrap: the first part is actually the information used to create a * new vm and is called the bootstrap. This is so that various vm parameters can * be configured such as the maximum amount of memory, because it isn't possible * to reconfigure an already running vm.
  2. *
  3. body: the second part is the actual code that should be executed, called * the body.
  4. *
* To determine whether the command will bootstrap or whether its body will be * executed, the {@link #execute(Instruction)} method checks for the existence * of the {@link #BODY} command line options. If it isn't present the command * will bootstrap to a new vm, but with the BODY option added. If the BODY * option does exist then the body will be executed. * * @author Ashley Williams * */ public final class Command { private static final String BODY = "body"; /** * See {@link #getAliases()}. */ private ArrayList aliases = new ArrayList(); /** * The instance used to create a new vm. */ private Runnable bootstrap; /** * The functionality associated with this command. */ private Runnable body; /** * The parent aggregate for this command. */ private final CommandStore store; /** * See {@link #getParser()}. */ private OptionParser parser; /** * See {@link #getOptions()}. */ private OptionSet options; /** * See {@link #getMemory()}. */ private String memory; /** * The command line snippet that is being used to invoke this command. */ private Instruction instruction; /** * See {@link #initBootstrapCurrentVm()}. */ private boolean bootstrapCurrentVm; /** * See {@link #getStartVmConfig()}. */ private ArgRunnable startVmConfig; /** * See {@link #initHelpResource(String)}. */ private String helpResource; /** * See {@link #initHelpString(String)} */ private String helpString; /** * Creates a command with an associated body that will execute in another * instance of the current vm. * * @param store * the owning instance * @param name * the name of this command as called at the command line * @param memory * how much memory this command should be lauched in * @param body * the plugged in command funtionality */ public Command(CommandStore store, String name, String memory, Runnable body) { this.store = store; this.memory = memory; this.bootstrap = createBootstrap(); this.parser = createOptionParser(); this.body = body; this.bootstrapCurrentVm = false; initAliases(name); } /** * Useful for commands that only have very short descriptions that may not * require resource files. * * @param helpString */ public void initHelpString(String helpString) { this.helpString = helpString; } /** * Provide the path to the resource that contains the help for this command. * That resource can contain velocity markup. * * @param helpResource */ public void initHelpResource(String helpResource) { this.helpResource = helpResource; } /** * See {@link #getParser()}. * * @return */ private OptionParser createOptionParser() { OptionParser parser = new OptionParser(); parser.accepts(BODY); return parser; } /** * Constructor support that creates the vm wrapper used to delegate the * command body functionality. * * @return */ private Runnable createBootstrap() { Runnable bootstrap = new Runnable() { public void run() { startVm(); } }; return bootstrap; } /** * This method must not be inlined into the anonymous inner class as the * advice in ShellConfig.aj requires this join-point. * * @param memory */ private void startVm() { StandardProject parent = store.getParent(); InstructionChain chain = parent.getInstructionChain(); String optsWithBody = (instruction.getOpts() + " -" + BODY).trim(); DispatchFeature dispatchFeature = parent.getDispatchFeature(); String mainClass; if (bootstrapCurrentVm) { mainClass = dispatchFeature.getCurrentMainClass(); } else { mainClass = dispatchFeature.getMainClass(); } String[] args = chain.createMainArgs(getName(), optsWithBody); dispatchFeature.startVm(mainClass, args, memory, getStartVmConfig()); } /** * See {@link #setStartVmConfig(ArgRunnable)}. * * @param startVmConfig */ public void setStartVmConfig(ArgRunnable startVmConfig) { this.startVmConfig = startVmConfig; } /** * The instance used to provide additional configuration before the vm is * launched inside the {@link #startVm()} method. * * @return */ public ArgRunnable getStartVmConfig() { return startVmConfig; } /** * Optional method, specify alternate names for this command. * * @param aliases */ public void initAliases(String... aliases) { this.aliases.addAll(Arrays.asList(aliases)); } /** * There is almost no reason to use this method since by default most * commands should bootstrap to the project main. This was created for the * compile task that must bootstrap to the currently running vm since the * project main classes may not have been compiled. */ public void initBootstrapCurrentVm() { this.bootstrapCurrentVm = true; } /** * Executes this command using the given specified instruction that usually * originates from the command line. The --body option is used to determine * what should happen when this method is called: *
    *
  • if the --body option is not specified then a new vm is started
  • *
  • if the --body option is specified then the command body is executed.
  • *
* * @param instruction */ public void execute(Instruction instruction) { this.instruction = instruction; this.options = parser.parse(instruction.getOpts().split(" ")); boolean executeBody = options.has(BODY); if (executeBody) { ProjectReporter reporter = store.getParent().getDispatchFeature() .getReporter(); reporter.beginCommand(getName()); body.run(); } else { bootstrap.run(); } } /** * The maximum amount of memory that the forked vm will be able to claim. * * @return */ public String getMemory() { return memory; } /** * See {@link #getMemory()}. * * @param memory */ public void setMemory(String memory) { this.memory = memory; } /** * The unique name of this command. Also appears in the list of aliases as * the very first element. * * @return */ public String getName() { return aliases.get(0); } /** * A read-only list of all the names this command is known by. * * @return */ public List getAliases() { return Collections.unmodifiableList(aliases); } /** * A description useful for help text. This is obtained by loading the * helpResource resource name as specified in the constructor, on the * classpath. The resources can contain velocity markup. If the resource * name hasn't been initialized then the helpString is checked. If that * isn't found then a default string is used. * * @return */ public String getDescription() { String description; if (helpResource != null) { ResourceFeature feature = store.getParent().getResourceFeature(); description = feature.filterResourceToString(helpResource); } else if (helpString != null) { description = helpString; } else { description = "no description provided"; } return description; } /** * Use in order to configure the command line options for this command. The * returned parser can be used as in the following example that expects a * mandatory file path to be passed via the -f option: * *
	 * OptionSpec<File> fileOpt = parser.accepts("f").withRequiredArg().ofType(
	 * 		File.class);
	 * 
*

* The returned parser is already configured to look for the "--body" option * that when specified indicates the command body is to be executed. When * not specified, a new vm is to be created. * * @return */ public OptionParser getParser() { return parser; } /** * Reports the aliases available, useful for help text. An example return * value would be: "foo (f, -foo, --foo)" * * @return */ public String getAliasText() { StringBuilder builder = new StringBuilder(); String name = getName(); builder.append(name); if (aliases.size() > 1) { builder.append(" ("); for (String alias : aliases) { if (!alias.equals(name)) { builder.append(alias); builder.append(", "); } } builder.delete(builder.length() - 2, builder.length()); builder.append(")"); } return builder.toString(); } /** * The helper responsible for parsing the command line arguments. * * @return */ public OptionSet getOptions() { return options; } /** * Convenience method that checks whether or not the given alias is known to * this command. * * @param alias * @return */ public boolean containsAlias(String alias) { return aliases.contains(alias); } /** * Includes both the command name and arguments, if any, that make up the * full definition of the command. * * @return */ public String getInstructionText() { return instruction.getText(); } /** * A reference to the owning store. * * @return */ public CommandStore getStore() { return store; } /** * Builds a message suitable for displaying in response to a help command. * * @return */ public String getHelpText() { StringBuilder builder = new StringBuilder(); builder.append("Name: "); builder.append(getAliasText()); builder.append("\n\n"); builder.append(getDescription()); builder.append("\n"); return builder.toString(); } /** * Throws an InformationException with standardized error information. * Useful for calling from a command that has badly specified options. The * help description is included in the detailed message along with an * additional hint that gives more information as to what the problem is. * * @param hint * may be null or empty if no hint is available */ public void throwBadOptionsException(String hint) { StringBuilder builder = new StringBuilder(); builder .append("The following command contained unrecognized options (ignore any --body switch):\n\n \""); builder.append(getInstructionText()); boolean isHintSpecified = hint != null && hint.length() != 0; if (isHintSpecified) { builder.append("\" - "); builder.append(hint); } builder.append("\n\nPlease review the command help as follows:\n"); builder.append(getHelpText()); throw new InformationException(builder.toString()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy