/* * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain copyright * statements and notices. Redistributions must also contain a * copy of this document. * * 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. The name "Exolab" must not be used to endorse or promote * products derived from this Software without prior written * permission of Intalio, Inc. For written permission, * please contact info@exolab.org. * * 4. Products derived from this Software may not be called "Exolab" * nor may "Exolab" appear in their names without prior written * permission of Intalio, Inc. Exolab is a registered * trademark of Intalio, Inc. * * 5. Due credit should be given to the Exolab Project * (http://www.exolab.org/). * * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Copyright 2004 (C) Intalio, Inc. All Rights Reserved. * * $Id: CastorSourceGenTask.java 6410 2006-11-17 12:06:24Z wguttmn $ */ package org.castor.anttask; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; import java.util.Vector; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.taskdefs.MatchingTask; import org.apache.tools.ant.types.FileSet; import org.castor.xml.BackwardCompatibilityContext; import org.castor.xml.InternalContext; import org.exolab.castor.builder.SourceGenerator; import org.exolab.castor.builder.binding.ExtendedBinding; import org.exolab.castor.builder.factory.FieldInfoFactory; import org.exolab.castor.xml.XMLException; import org.exolab.castor.xml.schema.Schema; import org.exolab.castor.xml.schema.SchemaContext; import org.exolab.castor.xml.schema.SchemaContextImpl; import org.exolab.castor.xml.schema.reader.Sax2ComponentReader; import org.exolab.castor.xml.schema.reader.SchemaUnmarshaller; import org.exolab.javasource.JClass; import org.xml.sax.InputSource; import org.xml.sax.Parser; import org.xml.sax.SAXException; /** * An <a href="http://ant.apache.org/">Ant</a> task to call the Castor * Source Generator. It can be passed a file, a directory, a Fileset or all * three. * * @author <a href="mailto:joel.farquhar@montage-dmc.com">Joel Farquhar</a> * @author <a href="mailto:ferret AT frii DOT com">Bruce Snyder</a> * @author <a href="mailto:wguttmn AT codehaus DOT org">Werner Guttmann</a> * @version $Revision: 6543 $ $Date: 2005-03-05 06:42:06 -0700 (Sat, 05 Mar 2005) $ */ public final class CastorCodeGenTask extends MatchingTask { /** Message indicating that the method setJdoDescriptorCreation cannot be invoked. */ private static final String PROBLEM_SETTING_JDO_DESC = "Problem calling SourceGenerator.setJdoDescriptorCreation: "; /** Msg indicating class descriptor generation has been disabled. */ private static final String DISABLE_DESCRIPTORS_MSG = "Disabling generation of Class descriptors"; /** Msg indicating marshaling framework code will not be generated. */ private static final String DISABLE_MARSHAL_MSG = "Disabling generation of Marshaling framework methods (marshal, unmarshal, validate)."; /** Msg indicating that castor-testable code will be generated. */ private static final String CASTOR_TESTABLE_MSG = "The generated classes will implement org.exolab.castor.tests.CastorTestable"; /** Error message -- invalid line seperator. */ private static final String INVALID_LINESEP_MSG = "Invalid value for lineseparator, must be win, unix, or mac."; /** Error message -- no schemas to run code generator on. */ private static final String NO_SCHEMA_MSG = "At least one of the file, url or dir attributes, or a fileset element, must be set."; /** Castor XML context - the mother of all. */ private InternalContext _internalContext; /** If processing one schema file, this lists the file. */ private File _schemaFile = null; /** If processing one schema file from an URL, this lists the file. */ private String _schemaURL = null; /** If processing all schemas in a directory, this lists the directory. */ private File _schemaDir = null; /** If processing a fileset, this lists the fileset. */ private Vector < FileSet > _schemaFilesets = new Vector < FileSet > (); // Begin Source Generator parameters /** The package that generated code will belong to. */ private String _srcpackage; /** The directory that code will be generated into. */ private String _todir; /** The directory that resources will be generated into, if specified. */ private String _resourcesDirectory; /** Binding file for the code generator. */ private String _bindingfile; /** Information about how to generate collections. */ private String _types; /** Line seperator to use for generated code. */ private String _lineseparator; /** If true, the code generator will be verbose. */ private boolean _verbose; /** If true, non-fatal warnings will be suppressed. Also, existing source * files will be silently overwritten. */ private boolean _warnings = true; /** If true, class descriptors will not be generated. */ private boolean _nodesc; /** If true, marshaling code will not be generated. */ private boolean _nomarshal; /** * If true, a mapping file will be generated (additionally). */ private boolean _generateMapping; /** If true, Castor's CTF testable framework code will be generated. */ private boolean _testable; /** Whether to generate code for imported schemas, too. */ private boolean _generateImportedSchemas; /** Whether to generate SAX-1 compliant code. */ private boolean _sax1; /** Whether enumerated type lookup should be performed in a case insensitive manner. */ private boolean _caseInsensitive; /** CastorBuilderProperties file. */ private String _properties; /** The name conflict strategy to use. */ private String _nameConflictStrategy = "warnViaConsoleDialog"; /** Name of the automatic clas name conflict strategy to use. */ private String _automaticConflictStrategy = "xpath"; /** Whether to generate JDO descriptors. */ private boolean _generateJdoDescriptors; /** * Mode for printing JClass instances. */ private String _jclassPrinterType = "standard"; /** SourceGenerator instance. */ private SourceGenerator _sgen; /** * No-arg constructor. */ public CastorCodeGenTask() { super(); _internalContext = new BackwardCompatibilityContext(); } /** * Sets the individual schema that will have code generated for it. * * @param file One schema file. */ public void setFile(final File file) { _schemaFile = file; } /** * Sets an URL for one individual schema that will have code generated for it. * * @param schemaURL URL for one schema file. */ public void setSchemaURL(final String schemaURL) { _schemaURL = schemaURL; } /** * Sets the directory such that all schemas in this directory will have code * generated for them. * * @param dir The directory containing schemas to process. */ public void setDir(final File dir) { _schemaDir = dir; } /** * Adds a fileset to process that contains schemas to process. * * @param set An individual file set containing schemas. */ public void addFileset(final FileSet set) { _schemaFilesets.addElement(set); } /** * Sets the package that generated code will belong to. * * @param pack The package that generated code will belong to. */ public void setPackage(final String pack) { _srcpackage = pack; } /** * Sets the directory into which code will be generated. * * @param dest The directory into which code will be generated. */ public void setTodir(final String dest) { _todir = dest; } /** * Sets the directory into which resources will be generated. If not * specified, resources will be generated in the same location as * code. * * @param dest The directory into which resources will be generated. */ public void setResourcesDirectory(final String destination) { _resourcesDirectory = destination; } /** * Sets the binding file to be used for code generation. * * @param bindingfile The binding file to be used for code generation. */ public void setBindingfile(final String bindingfile) { _bindingfile = bindingfile; } /** * Sets the line seperator to use for code generation. * * @param ls The line seperator to use for code generation. */ public void setLineseparator(final String ls) { _lineseparator = ls; } /** * Sets the type factory for code generation. * * @param tf The type factory to use for code generation. */ public void setTypes(final String tf) { _types = (tf.equals("j2")) ? "arraylist" : tf; } /** * Sets whether or not code generation gives extra information about its work. * * @param b If true, the code generator will be verbose. */ public void setVerbose(final boolean b) { _verbose = b; } /** * Sets the name conflict strategy to use. * * @param nameConflictStrategy The name conflict strategy to use */ public void setNameConflictStrategy(final String nameConflictStrategy) { _nameConflictStrategy = nameConflictStrategy; } /** * Sets the name conflict strategy to use. * * @param automaticConflictStrategy The automatic class name conflict strategy to use */ public void setAutomaticConflictStrategy(final String automaticConflictStrategy) { _automaticConflictStrategy = automaticConflictStrategy; } /** * Sets whether or not non-fatal warnings should be suppressed. * * @param b If true, non-fatal warnings will be suppressed. This additionally * means that existing source files will be silently overwritten. */ public void setWarnings(final boolean b) { _warnings = b; } /** * Sets whether or not class descriptors are generated. * * @param b If true, class descriptors are generated. */ public void setNodesc(final boolean b) { _nodesc = b; } /** * Sets whether or not marshaling methods are generated. * * @param b If true, marshaling methods are generated. */ public void setNomarshal(final boolean b) { _nomarshal = b; } /** * Sets whether CTF framework code is generated. * * @param b If true, the generated code will be instrumented for the CTF. */ public void setTestable(final boolean b) { _testable = b; } /** * Controls whether to generate code for imported schemas as well. * * @param generateImportedSchemas True if code should be generated for imported schemas. */ public void setGenerateImportedSchemas(final boolean generateImportedSchemas) { _generateImportedSchemas = generateImportedSchemas; } /** * Controls whether to generate JDO-specific class descriptors. * * @param generateJdoDescriptors True if JDP class descriptors should be generated */ public void setGenerateJdoDescriptors(final boolean generateJdoDescriptors) { _generateJdoDescriptors = generateJdoDescriptors; } /** * Controls whether to generate SAX-1 compliant code. * * @param sax1 True if SAX-1 compliant code should be generated. */ public void setSAX1(final boolean sax1) { _sax1 = sax1; } /** * Controls whether enumerated type lookup should be performed in a case * insensitive manner. * * @param caseInsensitive True if enumerated type lookup should be performed in a case * insensitive manner */ public void setCaseInsensitive(final boolean caseInsensitive) { _caseInsensitive = caseInsensitive; } /** * Sets the file to use for castor builder properties. * * @param properties The properties to use. */ public void setProperties(final String properties) { _properties = properties; } /** * Sets the mode for printing {@link JClass} instances. * @param jclassPrinterType The mode for printing {@link JClass} instances. */ public void setJClassPrinterType(final String jclassPrinterType) { _jclassPrinterType = jclassPrinterType; } /** * Controls whether a mapping file should (additionally) be generated. * @param generateMapping True if a mapping file should be generated. */ public void setGenerateMapping(final boolean generateMapping) { _generateMapping = generateMapping; } /** * Configured the code generator. If anything goes wrong during configuration of the * Ant task a BuildException will be thrown. */ private void config() { // Create Source Generator with appropriate type factory if (_types != null) { FieldInfoFactory factory; try { factory = new FieldInfoFactory(_types); _sgen = new CastorSourceGeneratorWrapper(factory); } catch (Exception e) { try { factory = (FieldInfoFactory) Class.forName(_types).newInstance(); _sgen = new CastorSourceGeneratorWrapper(factory); } catch (Exception e2) { throw new BuildException("Invalid types \"" + _types + "\": " + e.getMessage()); } } } else { // default _sgen = new CastorSourceGeneratorWrapper(); } // Set Line Separator String lineSep = System.getProperty("line.separator"); if (_lineseparator != null) { if ("win".equals(_lineseparator)) { log("Using Windows style line separation."); lineSep = "\r\n"; } else if ("unix".equals(_lineseparator)) { log("Using UNIX style line separation."); lineSep = "\n"; } else if ("mac".equals(_lineseparator)) { log("Using Macintosh style line separation."); lineSep = "\r"; } else { throw new BuildException(INVALID_LINESEP_MSG); } } _sgen.setLineSeparator(lineSep); _sgen.setDestDir(_todir); // TODO: use reflection to invoke this if (_resourcesDirectory != null && _resourcesDirectory.length() > 0) { _sgen.setResourceDestination(_resourcesDirectory); } if (_bindingfile != null) { _sgen.setBinding(_bindingfile); } _sgen.setVerbose(_verbose); _sgen.setSuppressNonFatalWarnings(!_warnings); _sgen.setDescriptorCreation(!_nodesc); if (_nodesc) { log(DISABLE_DESCRIPTORS_MSG); } _sgen.setCreateMarshalMethods(!_nomarshal); if (_nomarshal) { log(DISABLE_MARSHAL_MSG); } _sgen.setGenerateImportedSchemas(_generateImportedSchemas); _sgen.setSAX1(_sax1); _sgen.setCaseInsensitive(_caseInsensitive); _sgen.setNameConflictStrategy(_nameConflictStrategy); _sgen.setClassNameConflictResolver(_automaticConflictStrategy); _sgen.setJClassPrinterType(_jclassPrinterType); _sgen.setGenerateMappingFile(_generateMapping); if (_generateJdoDescriptors) { callSetterMethodUsingReflection(_sgen, "setJdoDescriptorCreation", boolean.class, new Boolean(_generateJdoDescriptors)); } _sgen.setTestable(_testable); if (this._testable) { log(CASTOR_TESTABLE_MSG); } // Set Builder Properties; if (_properties != null) { String filePath = new File(_properties).getAbsolutePath(); Properties customProperties = new Properties(); try { customProperties.load(new FileInputStream(filePath)); } catch (FileNotFoundException e) { throw new BuildException("Properties file \"" + filePath + "\" not found"); } catch (IOException e) { throw new BuildException("Can't read properties file \"" + filePath + "\": " + e); } _sgen.setDefaultProperties(customProperties); } } /** * Runs source generation. If anything goes wrong during source generation a * BuildException will be thrown. * * @param filePath an individual Schema to generate code for. */ private void processFile(final String filePath) { log("Processing " + filePath); try { _sgen.generateSource(filePath, _srcpackage); } catch (FileNotFoundException e) { String message = "XML Schema file \"" + _schemaFile.getAbsolutePath() + "\" not found."; log(message); throw new BuildException(message); } catch (Exception iox) { throw new BuildException(iox); } } /** * Runs source generation on a XML schema instance pointed to by an URL. If anything * goes wrong during source generation a BuildException will be thrown. * * @param schemaURL An URL to an individual XML schema to generate code for. */ private void processURL(final String schemaURL) { log("Processing " + schemaURL); try { InputSource inputSource = new InputSource(schemaURL); _sgen.generateSource(inputSource, _srcpackage); } catch (FileNotFoundException e) { String message = "XML Schema file \"" + _schemaURL + "\" not found."; log(message); throw new BuildException(message); } catch (Exception iox) { throw new BuildException(iox); } } /** * Public execute method -- entry point for the Ant task. Loops over all * schema that need code generated and creates needed code generators, then * executes them. If anything goes wrong during execution of the Ant task a * BuildException will be thrown. * * @see org.apache.tools.ant.Task#execute() */ public void execute() { // Must have something to run the source generator on if (_schemaFile == null && _schemaDir == null && _schemaFilesets.size() == 0 && _schemaURL == null) { throw new BuildException(NO_SCHEMA_MSG); } try { config(); // Run source generator on file if (_schemaFile != null) { processFile(_schemaFile.getAbsolutePath()); } // Run source generator on all files in directory if (_schemaDir != null && _schemaDir.exists() && _schemaDir.isDirectory()) { DirectoryScanner ds = this.getDirectoryScanner(_schemaDir); String[] files = ds.getIncludedFiles(); for (int i = 0; i < files.length; i++) { String filePath = _schemaDir.getAbsolutePath() + File.separator + files[i]; processFile(filePath); } } // Run source generator on all files in FileSet for (int i = 0; i < _schemaFilesets.size(); i++) { FileSet fs = (FileSet) _schemaFilesets.elementAt(i); DirectoryScanner ds = fs.getDirectoryScanner(getProject()); File subdir = fs.getDir(getProject()); String[] files = ds.getIncludedFiles(); for (int j = 0; j < files.length; j++) { String filePath = subdir.getAbsolutePath() + File.separator + files[j]; processFile(filePath); } } // Run source generator on URL for XML schema if (_schemaURL != null) { processURL(_schemaURL); } } finally { _sgen = null; } } /** * Override Castor's SourceGenerator to inject exception handling. * Code based on castor-0.9.5.3-xml.jar. */ private final class CastorSourceGeneratorWrapper extends SourceGenerator { /** * No-arg constructor. */ public CastorSourceGeneratorWrapper() { super(); } /** * Constructs a source generator with the provided FieldInfoFactory. * * @param fieldInfoFactory A FieldInfoFactory to use for collections. */ public CastorSourceGeneratorWrapper(final FieldInfoFactory fieldInfoFactory) { super(fieldInfoFactory); } /** * Constructs a source generator with the provided FieldInfoFactory and * binding file. * * @param fieldInfoFactory a FieldInfoFactory to use for collections. * @param extendedBinding binding information for the code generator. */ public CastorSourceGeneratorWrapper( final FieldInfoFactory fieldInfoFactory, final ExtendedBinding extendedBinding) { super(fieldInfoFactory, extendedBinding); } /** * Generate source. If anything goes wrong during generation of source a * BuildException will be thrown. * * @param source an individual schema to process. * @param packageName the package name for generated code. * * @see org.exolab.castor.builder.SourceGenerator * #generateSource(org.xml.sax.InputSource, java.lang.String) */ public void generateSource(final InputSource source, final String packageName) { Parser parser = null; try { parser = _internalContext.getParser(); } catch (RuntimeException e) { throw new BuildException("Unable to create SAX parser.", e); } if (parser == null) { throw new BuildException("Unable to create SAX parser."); } SchemaContext schemaContext = new SchemaContextImpl(); SchemaUnmarshaller schemaUnmarshaller = null; try { schemaUnmarshaller = new SchemaUnmarshaller(schemaContext); } catch (XMLException e) { throw new BuildException("Unable to create schema unmarshaller.", e); } Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller); parser.setDocumentHandler(handler); parser.setErrorHandler(handler); try { parser.parse(source); } catch (IOException e) { String msg = "Can't read input file " + source.getSystemId() + ".\n" + e; throw new BuildException(msg, e); } catch (SAXException e) { String msg = "Can't parse input file " + source.getSystemId() + ".\n" + e; throw new BuildException(msg, e); } Schema schema = schemaUnmarshaller.getSchema(); try { generateSource(schema, packageName); } catch (Exception iox) { throw new BuildException(iox); } } } /** * Helper method to invoke a setter method on {@link SourceGenerator} that might not * be available due to a version issue. * * @param sgen {@link SourceGenerator} instance * @param methodName Name of the method * @param parameterType Type of the method parameter. * @param parameterValue Actual parameter value to be used during method invocation. * @throws BuildException If the method cannot be invoked. */ private void callSetterMethodUsingReflection(final SourceGenerator sgen, final String methodName, final Class parameterType, final Object parameterValue) throws BuildException { try { Method method = sgen.getClass().getMethod(methodName, new Class[] {parameterType}); method.invoke(sgen, new Object[] {parameterValue}); } catch (NoSuchMethodException e) { // unable to find method to configure JDO descriptor creation. } catch (IllegalArgumentException e) { throw new BuildException(PROBLEM_SETTING_JDO_DESC, e); } catch (IllegalAccessException e) { throw new BuildException(PROBLEM_SETTING_JDO_DESC, e); } catch (InvocationTargetException e) { throw new BuildException(PROBLEM_SETTING_JDO_DESC, e); } } }