/* Standalone application that generates code Copyright (c) 2002-2014 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.copernicus.kernel; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import ptolemy.actor.CompositeActor; import ptolemy.actor.gui.ConfigurationApplication; import ptolemy.cg.kernel.generic.CodeGeneratorUtilities; import ptolemy.data.StringToken; import ptolemy.data.expr.Parameter; import ptolemy.data.expr.StringParameter; import ptolemy.data.expr.Variable; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.attributes.VersionAttribute; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.NamedObj; import ptolemy.kernel.util.Settable; import ptolemy.moml.MoMLParser; import ptolemy.moml.filter.BackwardCompatibility; import ptolemy.moml.filter.RemoveGraphicalClasses; import ptolemy.util.ClassUtilities; import ptolemy.util.MessageHandler; import ptolemy.util.StringUtilities; /////////////////////////////////////////////////////////////////// //// Copernicus /** A Standalone application that generates code using the Ptolemy II code generation system. This class acts a wrapper for the copernicus.*.Main classes by providing defaults arguments for the various backends. The generatorAttribute Parameter names a MoML file that contains definitions for other Parameters and Variables that control the compilation and execution of the model The default compilation arguments are read in from a file named compileCommandTemplate.in, variables are substituted and the compile command executed and then default arguments are read in from a file named runCommandTemplate.in.
For example:
java -classpath $PTII ptolemy.copernicus.kernel.Copernicus foo.xmlwill read in the $PTII/ptolemy/copernicus/java/compileCommandTemplate.in, substitute in the appropriate variables and then generate code for foo.xml
The default code generator is the deep java code generator in $PTII/ptolemy/copernicus/java.
The argument that names the xml file containing the model to generate
code for should be a relative pathname. The xml file argument is
converted into a URL internally.
If no xml file argument is specified,
then code is generated for
$PTII/ptolemy/domains/sdf/demo/OrthogonalCom/OrthogonalCom.xml
Generating code is fairly complex, so there are many other parameters that can be set as the other arguments.
The general format is
-VariableName VariableValue
, for example:
-codeGenerator "shallow"
For example:
java -classpath $PTII ptolemy.copernicus.kernel.GenerateCode -codeGenerator "shallow" foo.xml
The initial parameters, their values and any documentation can be printed with
java -classpath $PTII ptolemy.copernicus.kernel.GenerateCode -helpIf you have rebuilt Ptolemy II from sources, and have a shell such as bash available, then you can use
$PTII/bin/copernicus
as a shortcut. For example
$PTII/bin/copernicus -codeGenerator "shallow" foo.xml
The details of how this class works can be found in the
{@link GeneratorAttribute} documentation.
@author Christopher Hylands
@version $Id: Copernicus.java 70694 2014-11-21 17:25:55Z cxh $
@since Ptolemy II 2.0
@Pt.ProposedRating Red (cxh)
@Pt.AcceptedRating Red (cxh)
*/
public class Copernicus {
/** Parse the specified command-line arguments and then execute
* any specified commands.
* @param args The command-line arguments.
* @exception Exception If command line arguments have problems.
*/
public Copernicus(String[] args) throws Exception {
// Parse the command-line arguments
_parseArgs(args);
if (_modelPath == null) {
throw new RuntimeException("No model found in command line "
+ "arguments.\nRun 'copernicus -help' for information "
+ "on command line arguments.");
}
// Parse the model.
CompositeEntity toplevel = readInModel(_modelPath);
_generatorAttribute = (GeneratorAttribute) toplevel
.getAttribute(GENERATOR_NAME);
if (_generatorAttribute == null) {
_generatorAttribute = new GeneratorAttribute(toplevel,
GENERATOR_NAME);
_generatorAttribute.initialize();
// Parse the file named by the modelPath Parameter and update
// parameters. FIXME: Is this necessary?
// _generatorAttribute.sanityCheckAndUpdateParameters(_modelPath);
}
// Remove the generatorAttribute from the model so we don't
// generate code for it.
_generatorAttribute.setContainer(null);
// Save command-line parameters in the generator attribute.
_saveParsedArgs();
// Sanity check the parameters and update any parameters that
// were changed by the command-line parameters.
// For example, if the user changes ptIIUserDirectory, then
// we need to change ptIIUserDirectoryAsURL.
_generatorAttribute.sanityCheckAndUpdateParameters(_modelPath);
// See if we are redirecting the output.
StringParameter output = (StringParameter) _generatorAttribute
.getAttribute("output");
if (output != null && output.getToken() != null) {
String fileName = output.stringValue();
if (!fileName.equals("")) {
File outputFile = new File(fileName);
OutputStream outputStream = new FileOutputStream(outputFile);
System.setOut(new PrintStream(outputStream));
}
}
if (_verbose) {
System.out.println(_generatorAttribute.toString());
}
// Pass the model off to the real working function.
compileAndRun(toplevel, _generatorAttribute);
}
///////////////////////////////////////////////////////////////////
//// public methods ////
/** Return the command to run the generated code.
* The generatorAttribute argument contains a
* the runCommandTemplateFile parameter that refers
* to a template file that contains the command to run the generated
* code after the parameters from generatorAttribute
* are substituted in.
*
* @param generatorAttribute The GeneratorAttribute that contains
* the Parameters that determine the command to run.
* @return The command to run the generated code.
*/
public static String commandToRun(GeneratorAttribute generatorAttribute)
throws Exception {
String runCommandTemplateFile = generatorAttribute
.getParameter("runCommandTemplateFile");
return substitute(runCommandTemplateFile, generatorAttribute);
}
/** Possibly create the generated code and run it.
* What actually happens depends on the values of the compile
* and run parameters in generatorAttribute
* @param generatorAttribute The GeneratorAttribute that contains
* the parameters that determine the commands to create and run
* the generated code.
*/
public static void compileAndRun(CompositeEntity model,
GeneratorAttribute generatorAttribute) throws Exception {
// Save the _generatorAttribute in a temporary file and then
// add an attribute to _generatorAttribute that lists the
// location of the temporary file.
// This is a bit of a hack, but it is necessary if we want
// to be able to use the the key/value pairs that are in
// _generatorAttribute later in MakefileWriter.
// At first glance, it would appear that we could just read
// the GeneratorAttribute from the model, but one problem is
// that we filter out the GeneratorAttribute in KernelMain.
// Another problem is that this class reads in the model and
// then modifies the GeneratorAttribute according to the
// values of the command line arguments and other values, but
// we never update the model with this data.
String generatorAttributeFileName = exportMoMLToTemporaryFile(generatorAttribute);
// We add the filename as an attribute so that we can use its
// value to substitute.
// We substitute forward slashes for backward slashes because
// having backward slashes in attributes causes TokenMgrErrors
// while reading in a model.
new Parameter(generatorAttribute, "generatorAttributeFileName",
new StringToken(StringUtilities.substitute(
generatorAttributeFileName, "\\", "/")));
int exitValue = 1;
String compile = generatorAttribute.getParameter("compile");
if (compile.equals("true")) {
// NOTE Is this still needed?
// Temporary hack because cloning doesn't properly clone
// type constraints. In some ways, it would make sense to
// do this in readInModel(), where we already have a
// MoMLParser object, but we want to be sure the type
// constraints are cloned if we are passed in a model
// directly without running readInModel().
// CompositeActor modelClass = null;
// try {
// modelClass = (CompositeActor)
// _parser.searchForClass(_momlClassName,
// model.getMoMLInfo().source);
// } catch (XmlException xml) {
// throw new
// IllegalActionException("Failed to find class '"
// + _momlClassName + "' in '"
// + model.getMoMLInfo().source
// + "': " + xml);
// }
// if (modelClass != null) {
// model = modelClass;
// }
// Instantiate the right code generator.
String codeGeneratorClassName = generatorAttribute
.getParameter("codeGeneratorClassName");
System.out
.println("codeGeneratorClass = " + codeGeneratorClassName);
Class codeGeneratorClass = Class.forName(codeGeneratorClassName);
KernelMain codeGenerator = (KernelMain) codeGeneratorClass
.newInstance();
// Compile the model.
codeGenerator.compile(model.getName(), model, generatorAttribute);
}
String run = generatorAttribute.getParameter("run");
if (run.equals("true")) {
String command = commandToRun(generatorAttribute);
exitValue = executeCommand(command);
if (exitValue != 0) {
throw new Exception("Problem executing command. "
+ "Return value was: " + exitValue + ". Command was:\n"
+ command);
}
}
}
/** Execute a command in a subshell, and print out the results
* in standard error and standard out. Lines that begin with
* an octothorpe '#' are ignored. Substrings that start and end with
* a double quote are considered to be a single argument.
*
* @param command The command to execute.
* @return the exit status of the process, which is usually
* 0 if the process executed normally.
*/
public static int executeCommand(String command) throws Exception {
if (command == null || command.length() == 0) {
System.out.println("Warning, null or 0 length command string "
+ "passed to Copernicus.executeCommand()");
return 0;
}
String[] commands = StringUtilities.tokenizeForExec(command);
if (commands.length == 0) {
System.out.println("Warning, command was parsed to 0 tokens, "
+ "perhaps the command string was empty or "
+ "consisted only of comments?\n" + "command string was '"
+ command + "'");
return 0;
}
System.out.println("About to execute:\n ");
for (int i = 0; i < commands.length - 1; i++) {
System.out.println(" \"" + commands[i] + "\" \\");
}
if (commands.length > 0) {
System.out.println(" \"" + commands[commands.length - 1]
+ "\"");
}
System.out.flush();
// 0 indicates normal execution
int processReturnCode = 1;
try {
// This code is similar to tcl.lang.ExecCmd, so if you
// make changes here, please take a look at ExecCmd and
// see if it needs updating.
Process process = Runtime.getRuntime().exec(commands);
// Set up a Thread to read in any error messages
_StreamReaderThread errorGobbler = new _StreamReaderThread(
process.getErrorStream(), System.err);
// Set up a Thread to read in any output messages
_StreamReaderThread outputGobbler = new _StreamReaderThread(
process.getInputStream(), System.out);
// Start up the Threads
errorGobbler.start();
outputGobbler.start();
try {
processReturnCode = process.waitFor();
errorGobbler.join();
outputGobbler.join();
synchronized (_lock) {
process = null;
}
} catch (InterruptedException interrupted) {
System.out.println("InterruptedException: " + interrupted);
throw interrupted;
}
System.out.println("All Done.");
} catch (final IOException io) {
System.out.flush();
System.err.println("IOException: " + io);
}
return processReturnCode;
}
/** Export the MoML of the namedObj argument to a temporary file.
* The file is deleted when the Java virtual machine terminates.
* @param namedObj The NamedObj to export
* @return The name of the temporary file that was created
* @exception Exception If the temporary file cannot be created.
* @see java.io.File#createTempFile(java.lang.String, java.lang.String, java.io.File)
*/
public static String exportMoMLToTemporaryFile(NamedObj namedObj)
throws Exception {
File temporaryFile = File.createTempFile("ptCopernicus", ".xml");
temporaryFile.deleteOnExit();
FileWriter writer = null;
try {
writer = new FileWriter(temporaryFile);
String header = "
The MoML class name is processed as follows: *
ptolemy.domains.sdf.demo.OrthogonalCom.OrthogonalCom
* and inserted into a MoML fragment:
*
* <entity name="ToplevelModel" class=" + momlClassName + "/>
*
* and then passed to MoMLParser.parse().
*
@ParameterName@
in inputFileName, and
* substitute in the value of the Parameter and return the results.
*
* @param inputFileName The name of the file to read from.
* @param namedObj The NamedObj that contains Parameters to
* be searched for in inputFileName.
* @return The contents of inputFileName after doing the substitutions
* @deprecated See {@link ptolemy.cg.kernel.generic.CodeGeneratorUtilities#substitute(String, NamedObj)}
*/
@Deprecated
public static String substitute(String inputFileName, NamedObj namedObj)
throws FileNotFoundException, IOException {
return CodeGeneratorUtilities.substitute(inputFileName, namedObj);
}
/** Read in the contents of inputFile, and replace each matching
* String key found in substituteMap with the corresponding
* String value and write the results to outputFileName.
* @param inputFile A BufferedReader that refers to the file to be
* read in.
* @param substituteMap The Map of String keys like "@codeBase@"
* and String values like "../../..".
* @param outputFileName The name of the file to write to.
* @see #substitute(String, Map, String)
* @deprecated See {@link ptolemy.cg.kernel.generic.CodeGeneratorUtilities#substitute(BufferedReader, Map, String)}
*/
@Deprecated
public static void substitute(BufferedReader inputFile, Map substituteMap,
String outputFileName) throws FileNotFoundException, IOException {
CodeGeneratorUtilities.substitute(inputFile, substituteMap,
outputFileName);
}
/** Read in the contents of inputFileName, and replace each
* matching String key found in substituteMap with the
* corresponding String value and write the results to
* outputFileName.
* @param inputFileName The name of the file to read from.
* @param substituteMap The Map of String keys like "@codeBase@"
* and String values like "../../..".
* @param outputFileName The name of the file to write to.
* @see #substitute(BufferedReader, Map, String)
* @deprecated See {@link ptolemy.cg.kernel.generic.CodeGeneratorUtilities#substitute(String, Map, String)}
*/
@Deprecated
public static void substitute(String inputFileName, Map substituteMap,
String outputFileName) throws FileNotFoundException, IOException {
CodeGeneratorUtilities.substitute(inputFileName, substituteMap,
outputFileName);
}
///////////////////////////////////////////////////////////////////
//// public variables ////
/** The name of the GeneratorAttribute */
public static final String GENERATOR_NAME = "_generator";
///////////////////////////////////////////////////////////////////
//// protected methods ////
/** Parse a command-line argument.
* @return True if the argument is understood, false otherwise.
* @exception Exception If something goes wrong.
*/
protected boolean _parseArg(String arg) throws Exception {
if (arg.equals("-help")) {
System.out.println(_usage());
System.out.println(_help());
StringUtilities.exit(0);
} else if (arg.equals("-test")) {
_test = true;
} else if (arg.equals("-verbose")) {
_verbose = true;
} else if (arg.equals("-version")) {
System.out
.println("Version "
+ VersionAttribute.CURRENT_VERSION.getExpression()
+ ", Build $Id: Copernicus.java 70694 2014-11-21 17:25:55Z cxh $");
StringUtilities.exit(0);
} else if (arg.equals("")) {
// Ignore blank argument.
} else if (!arg.startsWith("-")) {
// Assume the argument is a file name or URL.
_modelPath = arg;
} else {
// Argument not recognized.
return false;
}
return true;
}
/** Parse the command-line arguments.
* @exception Exception If an argument is not understood or triggers
* an error.
*/
protected void _parseArgs(String[] args) throws Exception {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (_parseArg(arg) == false) {
if (arg.trim().startsWith("-")) {
if (i >= args.length - 1) {
throw new IllegalActionException("Cannot set "
+ "parameter " + arg + " when no value is "
+ "given.");
}
// Save in case this is a parameter name and value.
_parameterNames.add(arg.substring(1));
_parameterValues.add(args[i + 1]);
i++;
} else {
// Unrecognized option.
throw new IllegalActionException("Unrecognized option: "
+ arg);
}
}
}
}
/** Save arguments that were parsed in the generatorAttribute of
* the model.
*/
protected void _saveParsedArgs() throws Exception {
_generatorAttribute.updateModelAttributes(_modelPath);
// Check saved options to see whether any is setting an attribute.
Iterator names = _parameterNames.iterator();
Iterator values = _parameterValues.iterator();
while (names.hasNext() && values.hasNext()) {
String name = (String) names.next();
String value = (String) values.next();
boolean match = false;
Attribute attribute = _generatorAttribute.getAttribute(name);
if (attribute instanceof Settable) {
match = true;
((Settable) attribute).setExpression(value);
if (attribute instanceof Variable) {
// Force evaluation so that listeners
// are notified.
((Variable) attribute).getToken();
}
}
if (!match) {
// Unrecognized option.
throw new IllegalActionException("Unrecognized option: "
+ "No parameter exists with name \"" + name
+ "\". Try editing the model and removing the "
+ "GeneratorAttribute, perhaps "
+ "$PTII/ptolemy/copernicus/kernel/Generator.xml "
+ "has changed?");
}
}
}
///////////////////////////////////////////////////////////////////
//// private methods ////
/** Return a string containing all the Parameters */
private static String _help() {
NamedObj namedObj = new NamedObj();
try {
GeneratorAttribute generatorAttribute = new GeneratorAttribute(
namedObj, "_helpGeneratorAttribute");
return generatorAttribute.toString();
} catch (Exception ex) {
return ex.toString();
}
}
/** Return a string containing the usage */
private String _usage() {
StringBuffer usage = new StringBuffer(StringUtilities.usageString(
_commandTemplate, _commandOptions, _commandFlags));
try {
NamedObj namedObj = new NamedObj();
usage.append("\n\nThe following attributes of the code generator can\n"
+ "be set. For example '-codeGenerator java' means\n"
+ "to use the java code generator\n\n");
_generatorAttribute = new GeneratorAttribute(namedObj,
GENERATOR_NAME);
_generatorAttribute.initialize();
// Parse the file named by the modelPath Parameter and update
// parameters
_generatorAttribute.sanityCheckAndUpdateParameters(null);
usage.append(_generatorAttribute.toString());
} catch (Exception ex) {
usage.append("Problem evaluating default arguments: " + ex);
}
return usage.toString();
}
///////////////////////////////////////////////////////////////////
//// protected variables ////
/** The command-line options that are either present or not. */
protected String[] _commandFlags = {
"-help: Print this help information\n",
"-test: Smoke test the code generator by killing after 2 seconds.\n",
"-verbose: Show verbose execution information.\n",
"-version: Show version information.\n", };
/** The command-line options that take arguments. */
protected String[][] _commandOptions = { { "-