/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2002
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.tools.idlgen;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jacorb.idl.AcsAdapterForOldJacorb;
import org.jacorb.idl.AcsInterfacePrinter;
import org.jacorb.idl.AcsJacorbParser;
import org.jacorb.idl.AcsStructPrinter;
import org.jacorb.idl.AcsTypedefPrinter;
import org.jacorb.idl.AliasTypeSpec;
import org.jacorb.idl.Interface;
import org.jacorb.idl.StructType;
import alma.tools.idlgen.comphelpgen.ComponentHelperGeneratorProxy;
/**
* TODO: Update for JacORB!!
*
* Main class of the ACS IDL compiler that produces interfaces, structs, and holders
* for use with XML binding classes.
* The generated code will be similar but different from the output of a regular CORBA IDL compiler.
* IDL interfaces, structs, etc. that contain a field or parameter of type <code>XmlEntityStruct</code>
* will be compiled into source code that contains a corresponding XML binding class.
* <p>
* In collaboration with the other classes from this package, this class
* performs the following steps for each IDL file specified:
* <ol>
* <li>parses the IDL file (using an {@link org.openorb.compiler.parser.IdlParser}). The intermediate
* result is a tree of {@link org.openorb.compiler.object.IdlObject} nodes.</li>
* <li>identifies IDL <code>typedef</code>s for the type <code>XmlEntityStruct</code>,
* e.g. the definition of the type <code>SchedBlock</code>.
* (see {@link IdlTreeManipulator#findXmlTypedefNodes})</li>
* <li>marks the nodes in the parse tree
* that are affected by the XML entity types
* (using {@link IdlTreeManipulator#findXmlEntityNodes}).
* This ensures that later we don't generate code that would be functionally identical
* to the code generated by a standard IDL compiler.<br>
* Note that the identification of parse tree nodes works upward-recursive: for example, if an IDL
* struct contains another struct which has a member of type <code>SchedBlock</code>, then
* both IDL structs will get marked.</li>
* <li>translates the relevant nodes of the manipulated IDL parse tree into Java code
* (using {@link JavaGenerator#translateData}).
* <br>
* Note that the logic of mapping IDL to Java and the code generation itself are not separated.</li>
* <li>optionally runs the code generator for component helper class templates,
* see {@link alma.tools.idlgen.comphelpgen.ComponentHelperGeneratorProxy} and
* {@link alma.acs.tools.comphelpergen.CompHelperGenerator}.<br>
* Information about (component) interfaces and Java packages is transmitted as an XML string
* that conforms to the schema <code>HelperInfo</code> in the package
* <code>alma.acs.tools.comphelpergen</code>.</li>
* </ol>
* <p>
* <b>Parameters and properties</b>:
* <ul>
* <li>Takes all command line arguments that are valid for {@link org.openorb.compiler.IdlCompiler},
* although some of them don't apply and will be ignored.<br>
* Interesting for tracing is <code>-verbose</code>.</li>
* <li>The property <code>ACS.idl2jbind</code> specifies the mappings from typedef'd
* <code>XmlEntityStruct</code>s to Java binding classes.<br>
* For example, the value
* "<code>SchedBlock=alma.xmljbind.test.schedblock.SchedBlock;
* ObsProject=alma.xmljbind.test.obsproject.ObsProject</code>"
* instructs the compiler to use the Java class
* <code>alma.xmljbind.test.schedblock.SchedBlock</code>
* whereever an <code>XmlEntityStruct</code> typedef'd to <code>SchedBlock</code>
* is found.</li>
* <li>Two properties control the optional invocation of the code generator for
* component helper classes:
* <ul>
* <li><code>alma.acs.tools.comphelpergen.doGenerate</code>: if <code>true</code>,
* the ACS IDL compiler will run the generator after compiling an IDL file.
* See {@link alma.tools.idlgen.comphelpgen.ComponentHelperGeneratorProxy}.</li>
* <li><code>alma.acs.tools.comphelpergen.outRootDir</code> specifies the directory under which
* the component helper classes will be generated according to their Java packages.</li>
* </ul></li>
* </ul>
* In order to keep things simple, the implementation uses the OpenORB IDL compiler
* (<code>org.openorb.compiler.IdlCompiler</code> and related classes)
* whereever this is possible.
* No OpenORB code is modified; this goal results in some less
* intuitive handling of parse tree nodes, since new behavior could not be attached to the
* existing classes. On the other hand, we won't have to insert our patches into any new version
* of OpenORB that we might want to use in the future.
* <p>
* A half-inspired attempt has been made to pave the road for code generation for languages other than Java.
* The tree manipulations could also be used by other code generators.
* For Java we reused OpenORB's implementation of the IDL2Java mapping logic
* (see {@link JavaGenerator}), but similar generators for other languages would be much more complex.
* <p>
* Remark on the proprietary code generation strategy used:
* Since OpenORB does not separate IDL2Java mapping logic from Java code generation, we can't
* use any standard code generation framework without reimplementing the IDL2Java logic ourselves.
* The choice was to accept being different from other ACS code generators,
* with the advantage of not writing lots of new code.
*
* @author hsommer
*/
public class XmlIdlCompiler
{
public static final String PROP_DO_GENERATE_COMP_HELPERS = "alma.acs.tools.comphelpergen.doGenerate";
public static final String PROP_COMP_HELPERS_OUTDIR = "alma.acs.tools.comphelpergen.outRootDir";
/**
* See {@link #translateFromOpenORB(String[])} about missing verbosity setting coming from the Makefile
*/
private boolean verbose = false;
private final AcsXmlNamingExpert namingExpert;
/**
* Constructor for XmlIdlCompiler.
*/
public XmlIdlCompiler() {
namingExpert = new AcsXmlNamingExpert();
}
/**
* This operation is used to generate code for an IDL file that has been parsed already.
* @throws Exception
*/
public void generateCode(JacorbVisitor jacorbVisitor) throws Exception {
// -- options that are outside the OpenORB command line option handling
String idl2jbindMapping = System.getProperty("ACS.idl2jbind");
namingExpert.setIdlStruct2JavaBindingClassMappings(idl2jbindMapping);
boolean doGenerateCompHelpers = Boolean.getBoolean(PROP_DO_GENERATE_COMP_HELPERS);
String compHelperOutDir = System.getProperty(PROP_COMP_HELPERS_OUTDIR);
if (verbose) {
// Print the xml affected nodes
System.out.println("\nXML typedefs found: ");
for (AliasTypeSpec type : jacorbVisitor.xmlTypedefs) {
System.out.println("\t"+type.name());
}
System.out.println("\nXML-aware Structs found: ");
for (StructType str : jacorbVisitor.xmlAwareStructs) {
System.out.println("\t"+str.name());
}
System.out.println("\nXML-aware IFs found: ");
for (Interface interfce : jacorbVisitor.xmlAwareIFs) {
System.out.println("\t"+interfce.name());
}
System.out.println("\nXmlIdl options (non-JacORB):" +
"\n idl2jbindMapping = " + idl2jbindMapping +
"\n doGenerateCompHelpers = " + doGenerateCompHelpers +
"\n compHelperOutDir = " + compHelperOutDir);
}
if (doGenerateCompHelpers && compHelperOutDir == null) {
System.err.println("\nCan't generate component helper class(es) because " +
"no output directory is given in alma.acs.tools.comphelpergen.outRootDir");
doGenerateCompHelpers = false;
}
// -- init component helper generator with interfaces and Java packages --
ComponentHelperGeneratorProxy compHelpGen = null;
if (doGenerateCompHelpers) {
compHelpGen = new ComponentHelperGeneratorProxy(compHelperOutDir, verbose);
try
{
compHelpGen.setOriginalParseTree(jacorbVisitor.acsCompIFs);
}
catch (RuntimeException e)
{
System.err.println("failed to parse Java packages:");
System.err.println(e.toString());
System.err.println("Will disable component helper generation.");
compHelpGen = null;
doGenerateCompHelpers = false;
}
}
else if (verbose) {
System.out.println("no code generation for component helper classes was selected...");
}
// -- Start to generate Java code, the main purpose of all this --
if (verbose) {
System.out.println("\n**** Java code generation ****");
}
// -- Rename nodes affected by XML.
// -- We do this before starting the code generation to get also nested structs and inherited AcsJ interfaces right.
for (Interface interfce : jacorbVisitor.xmlAwareIFs) {
interfce.set_name(interfce.name() + "J");
}
// Generate code for each xml typedef
for (AliasTypeSpec alias : jacorbVisitor.xmlTypedefs) {
AcsTypedefPrinter printer = new AcsTypedefPrinter(alias, namingExpert);
printer.printHolderClass();
}
// Generate code for each xml struct
for (StructType struct : jacorbVisitor.xmlAwareStructs) {
AcsStructPrinter printer = new AcsStructPrinter(struct,
jacorbVisitor.xmlTypedefs, jacorbVisitor.xmlAwareStructs, jacorbVisitor.xmlAwareIFs, namingExpert);
printer.printStructClass();
printer.printHolderClass();
}
// Generate code for each interface
for (Interface interfce : jacorbVisitor.xmlAwareIFs) {
AcsInterfacePrinter printer = new AcsInterfacePrinter(interfce,
jacorbVisitor.xmlTypedefs, jacorbVisitor.xmlAwareStructs, jacorbVisitor.xmlAwareIFs, namingExpert);
printer.printAcsJInterface();
}
if (verbose) {
System.out.println("\n**** done with Java code generation ****");
}
// -- component helper code generation --
if (compHelpGen != null) {
System.out.println("ALMA IDL compiler done, now invoking component helper code generator...");
compHelpGen.generateComponentHelperCode();
}
}
/**
* Utility method that creates a File object for code generation.
* @throws IOException
*/
public static File createFile(String jpackage, String className) throws IOException {
String fname = className + ".java";
String path = AcsAdapterForOldJacorb.getParserOutDir() + File.separatorChar + jpackage.replace('.', File.separatorChar );
File dir = new File(path);
if (!dir.exists()) {
if (!dir.mkdirs()) {
throw new IOException("Unable to create " + path);
}
}
File f = new File(dir, fname);
return f;
}
/**
* The main method gets called from ACS/LGPL/Kit/acs/include/acsMakefileCore.mk
* with the following JVM properties and application arguments:
* <ul>
* <li><code>-d <outputDir></code> with the root of the directory tree for the generated output.
* <li>JVM property <code>ACS.idl2jbind</code> that contains the ';'-separated bindings of XmlEntityStruct typedefs
* to XML java binding classes as obtained from the Makefile directive <code>XML_IDL</code>; <br>
* Example value: "ObsProposal=alma.xmljbind.test.obsproposal.ObsProposal; SchedBlock=alma.xmljbind.test.schedblock.SchedBlock"
* <li>Optional JVM properties <code>alma.acs.tools.comphelpergen.doGenerate<code> and <code>alma.acs.tools.comphelpergen.outRootDir</code>
* <li>The command line also contains <code>$(verboseDef)</code> but it seems to be always empty.
* </ul>
* It runs the JacORB IDL parser with the custom ACS plugin and generates code for the nodes involved in
* XML translation.
* @param externalArgs -d <outdir> -I<myIdlIncludePath1> -I<myIdlIncludePath2> <myIdlFile>
*/
public static void main(String[] args) {
try {
XmlIdlCompiler inst = new XmlIdlCompiler();
String[] jarcorbArgs = inst.processAndPruneAcsArgs(args);
JacorbVisitor jacorbVisitor = new JacorbVisitor();
AcsJacorbParser parser = new AcsJacorbParser();
parser.init(jarcorbArgs, jacorbVisitor);
// Run the jacorb IDL parser with the ACS plugin
System.out.println("ACS IDL compilation for " + parser.idlFile);
parser.parse();
// XML manipulation and code generation
inst.generateCode(jacorbVisitor);
}
catch (Throwable thr) {
System.err.println("ACS IDL compilation failed!");
thr.printStackTrace(System.err);
System.exit(1);
}
}
/**
* Processes those arg from the given argument list that target ACS.
* The returned list of args is meant to be passed to JacORB.
*/
private String[] processAndPruneAcsArgs(String[] args) {
List<String> argListIn = Arrays.asList(args);
List<String> argListOut = new ArrayList<String>();
for (String arg : argListIn) {
if (arg.equals("-verbose")) {
// verbosity for ACS code
verbose = true;
// verbosity for jacorb code
int jacorbDebugLevel = 1; // 0...4
argListOut.add("-W");
argListOut.add(Integer.toString(jacorbDebugLevel));
}
else {
argListOut.add(arg);
}
}
return argListOut.toArray(new String[argListOut.size()]);
}
}