com.sun.tools.xjc.XJC2Task Maven / Gradle / Ivy
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.tools.xjc;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.writer.FilterCodeWriter;
import com.sun.tools.xjc.model.Model;
import com.sun.tools.xjc.reader.Util;
import com.sun.tools.xjc.util.ForkEntityResolver;
import com.sun.tools.xjc.api.SpecVersion;
import com.sun.xml.bind.v2.util.EditDistance;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.XMLCatalog;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
/**
* XJC task for Ant.
*
* See the accompanied document for the usage.
*
* @author
* Kohsuke Kawaguchi ([email protected])
*/
public class XJC2Task extends Task {
public XJC2Task() {
super();
classpath = new Path(null);
options.setSchemaLanguage(Language.XMLSCHEMA); // disable auto-guessing
}
public final Options options = new Options();
/** User-specified stack size. */
private long stackSize = -1;
/**
* False to continue the build even if the compilation fails.
*/
private boolean failonerror = true;
/**
* True if we will remove all the old output files before
* invoking XJC.
*/
private boolean removeOldOutput = false;
/**
* Files used to determine whether XJC should run or not.
*/
private final ArrayList dependsSet = new ArrayList();
private final ArrayList producesSet = new ArrayList();
/**
* Set to true once the <produces> element is used.
* This flag is used to issue a suggestion to users.
*/
private boolean producesSpecified = false;
/**
* Used to load additional user-specified classes.
*/
private final Path classpath;
/** Additional command line arguments. */
private final Commandline cmdLine = new Commandline();
/** for resolving entities such as dtds */
private XMLCatalog xmlCatalog = null;
/**
* Parses the schema attribute. This attribute will be used when
* there is only one schema.
*
* @param schema
* A file name (can be relative to base dir),
* or an URL (must be absolute).
*/
public void setSchema( String schema ) {
try {
options.addGrammar( getInputSource(new URL(schema)) );
} catch( MalformedURLException e ) {
File f = getProject().resolveFile(schema);
options.addGrammar(f);
dependsSet.add(f);
}
}
/** Nested <schema> element. */
public void addConfiguredSchema( FileSet fs ) {
for (InputSource value : toInputSources(fs))
options.addGrammar(value);
addIndividualFilesTo( fs, dependsSet );
}
/** Nested <classpath> element. */
public void setClasspath( Path cp ) {
classpath.createPath().append(cp);
}
/** Nested <classpath> element. */
public Path createClasspath() {
return classpath.createPath();
}
public void setClasspathRef(Reference r) {
classpath.createPath().setRefid(r);
}
/**
* Sets the schema language.
*/
public void setLanguage(String language) {
Language l = Language.valueOf(language.toUpperCase());
if(l==null) {
Language[] languages = Language.values();
String[] candidates = new String[languages.length];
for( int i=0; i element. */
public void addConfiguredBinding( FileSet fs ) {
for (InputSource is : toInputSources(fs))
options.addBindFile(is);
addIndividualFilesTo( fs, dependsSet );
}
/**
* Sets the package name of the generated code.
*/
public void setPackage( String pkg ) {
this.options.defaultPackage = pkg;
}
/**
* Adds a new catalog file.
*/
public void setCatalog( File catalog ) {
try {
this.options.addCatalog(catalog);
} catch (IOException e) {
throw new BuildException(e);
}
}
/**
* Mostly for our SQE teams and not to be advertized.
*/
public void setFailonerror(boolean value) {
failonerror = value;
}
/**
* Sets the stack size of the XJC invocation.
*
* @deprecated
* not much need for JAXB2, as we now use much less stack.
*/
public void setStackSize( String ss ) {
try {
stackSize = Long.parseLong(ss);
return;
} catch( NumberFormatException e ) {
;
}
if( ss.length()>2 ) {
String head = ss.substring(0,ss.length()-2);
String tail = ss.substring(ss.length()-2);
if( tail.equalsIgnoreCase("kb") ) {
try {
stackSize = Long.parseLong(head)*1024;
return;
} catch( NumberFormatException ee ) {
;
}
}
if( tail.equalsIgnoreCase("mb") ) {
try {
stackSize = Long.parseLong(head)*1024*1024;
return;
} catch( NumberFormatException ee ) {
;
}
}
}
throw new BuildException("Unrecognizable stack size: "+ss);
}
/**
* Add the catalog to our internal catalog
*
* @param xmlCatalog the XMLCatalog instance to use to look up DTDs
*/
public void addConfiguredXMLCatalog(XMLCatalog xmlCatalog) {
if(this.xmlCatalog==null) {
this.xmlCatalog = new XMLCatalog();
this.xmlCatalog.setProject(getProject());
}
this.xmlCatalog.addConfiguredXMLCatalog(xmlCatalog);
}
/**
* Controls whether files should be generated in read-only mode or not
*/
public void setReadonly( boolean flg ) {
this.options.readOnly = flg;
}
/**
* Controls whether the file header comment is generated or not.
*/
public void setHeader( boolean flg ) {
this.options.noFileHeader = !flg;
}
/**
* @see Options#runtime14
*/
public void setXexplicitAnnotation( boolean flg) {
this.options.runtime14 = flg;
}
/**
* Controls whether the compiler will run in the strict
* conformance mode (flg=false) or the extension mode (flg=true)
*/
public void setExtension( boolean flg ) {
if(flg)
this.options.compatibilityMode = Options.EXTENSION;
else
this.options.compatibilityMode = Options.STRICT;
}
/**
* Sets the target version of the compilation
*/
public void setTarget( String version ) {
options.target = SpecVersion.parse(version);
if(options.target==null)
throw new BuildException(version+" is not a valid version number. Perhaps you meant @destdir?");
}
/**
* Sets the directory to produce generated source files.
*/
public void setDestdir( File dir ) {
this.options.targetDir = dir;
}
/** Nested <depends> element. */
public void addConfiguredDepends( FileSet fs ) {
addIndividualFilesTo( fs, dependsSet );
}
/** Nested <produces> element. */
public void addConfiguredProduces( FileSet fs ) {
producesSpecified = true;
if( !fs.getDir(getProject()).exists() ) {
log(
fs.getDir(getProject()).getAbsolutePath()+" is not found and thus excluded from the dependency check",
Project.MSG_INFO );
} else
addIndividualFilesTo( fs, producesSet );
}
/** "removeOldOutput" attribute. */
public void setRemoveOldOutput( boolean roo ) {
this.removeOldOutput = roo;
}
public Commandline.Argument createArg() {
return cmdLine.createArgument();
}
/** Runs XJC. */
public void execute() throws BuildException {
log( "build id of XJC is " + Driver.getBuildID(), Project.MSG_VERBOSE );
classpath.setProject(getProject());
try {
if( stackSize==-1 )
doXJC(); // just invoke XJC
else {
try {
// launch XJC with a new thread so that we can set the stack size.
final Throwable[] e = new Throwable[1];
Thread t;
Runnable job = new Runnable() {
public void run() {
try {
doXJC();
} catch( Throwable be ) {
e[0] = be;
}
}
};
try {
// this method is available only on JDK1.4
Constructor c = Thread.class.getConstructor(
ThreadGroup.class,
Runnable.class,
String.class,
long.class);
t = (Thread)c.newInstance(
Thread.currentThread().getThreadGroup(),
job,
Thread.currentThread().getName()+":XJC",
stackSize);
} catch( Throwable err ) {
// if fail, fall back.
log( "Unable to set the stack size. Use JDK1.4 or above", Project.MSG_WARN );
doXJC();
return;
}
t.start();
t.join();
if( e[0] instanceof Error ) throw (Error)e[0];
if( e[0] instanceof RuntimeException ) throw (RuntimeException)e[0];
if( e[0] instanceof BuildException ) throw (BuildException)e[0];
if( e[0]!=null ) throw new BuildException(e[0]);
} catch( InterruptedException e ) {
throw new BuildException(e);
}
}
} catch( BuildException e ) {
log("failure in the XJC task. Use the Ant -verbose switch for more details");
if(failonerror)
throw e;
else {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
getProject().log(sw.toString(),Project.MSG_WARN);
// continue
}
}
}
private void doXJC() throws BuildException {
ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
// set the user-specified class loader so that XJC will use it.
Thread.currentThread().setContextClassLoader(new AntClassLoader(getProject(),classpath));
_doXJC();
} finally {
// restore the context class loader
Thread.currentThread().setContextClassLoader(old);
}
}
private void _doXJC() throws BuildException {
try {
// parse additional command line params
options.parseArguments(cmdLine.getArguments());
} catch( BadCommandLineException e ) {
throw new BuildException(e.getMessage(),e);
}
if(xmlCatalog!=null) {
if(options.entityResolver==null) {
options.entityResolver = xmlCatalog;
} else {
options.entityResolver = new ForkEntityResolver(options.entityResolver,xmlCatalog);
}
}
if( !producesSpecified ) {
log("Consider using / so that XJC won't do unnecessary compilation",Project.MSG_INFO);
}
// up to date check
long srcTime = computeTimestampFor(dependsSet,true);
long dstTime = computeTimestampFor(producesSet,false);
log("the last modified time of the inputs is "+srcTime, Project.MSG_VERBOSE);
log("the last modified time of the outputs is "+dstTime, Project.MSG_VERBOSE);
if( srcTime < dstTime ) {
log("files are up to date");
return;
}
InputSource[] grammars = options.getGrammars();
String msg = "Compiling "+grammars[0].getSystemId();
if( grammars.length>1 ) msg += " and others";
log( msg, Project.MSG_INFO );
if( removeOldOutput ) {
log( "removing old output files", Project.MSG_INFO );
for( File f : producesSet )
f.delete();
}
// TODO: I don't know if I should send output to stdout
ErrorReceiver errorReceiver = new ErrorReceiverImpl();
Model model = ModelLoader.load( options, new JCodeModel(), errorReceiver );
if(model==null)
throw new BuildException("unable to parse the schema. Error messages should have been provided");
try {
if(model.generateCode(options,errorReceiver)==null)
throw new BuildException("failed to compile a schema");
log( "Writing output to "+options.targetDir, Project.MSG_INFO );
model.codeModel.build( new AntProgressCodeWriter(options.createCodeWriter()));
} catch( IOException e ) {
throw new BuildException("unable to write files: "+e.getMessage(),e);
}
}
/**
* Determines the timestamp of the newest/oldest file in the given set.
*/
private long computeTimestampFor( List files, boolean findNewest ) {
long lastModified = findNewest?Long.MIN_VALUE:Long.MAX_VALUE;
for( File file : files ) {
log("Checking timestamp of "+file.toString(), Project.MSG_VERBOSE );
if( findNewest )
lastModified = Math.max( lastModified, file.lastModified() );
else
lastModified = Math.min( lastModified, file.lastModified() );
}
if( lastModified == Long.MIN_VALUE ) // no file was found
return Long.MAX_VALUE; // force re-run
if( lastModified == Long.MAX_VALUE ) // no file was found
return Long.MIN_VALUE; // force re-run
return lastModified;
}
/**
* Extracts {@link File} objects that the given {@link FileSet}
* represents and adds them all to the given {@link List}.
*/
private void addIndividualFilesTo( FileSet fs, List lst ) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
String[] includedFiles = ds.getIncludedFiles();
File baseDir = ds.getBasedir();
for (String value : includedFiles) {
lst.add(new File(baseDir, value));
}
}
/**
* Extracts files in the given {@link FileSet}.
*/
private InputSource[] toInputSources( FileSet fs ) {
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
String[] includedFiles = ds.getIncludedFiles();
File baseDir = ds.getBasedir();
ArrayList lst = new ArrayList();
for (String value : includedFiles) {
lst.add(getInputSource(new File(baseDir, value)));
}
return lst.toArray(new InputSource[lst.size()]);
}
/**
* Converts a File object to an InputSource.
*/
private InputSource getInputSource( File f ) {
try {
return new InputSource(f.toURL().toExternalForm());
} catch( MalformedURLException e ) {
return new InputSource(f.getPath());
}
}
/**
* Converts an URL to an InputSource.
*/
private InputSource getInputSource( URL url ) {
return Util.getInputSource(url.toExternalForm());
}
/**
* {@link CodeWriter} that produces progress messages
* as Ant verbose messages.
*/
private class AntProgressCodeWriter extends FilterCodeWriter {
public AntProgressCodeWriter( CodeWriter output ) {
super(output);
}
public OutputStream openBinary(JPackage pkg, String fileName) throws IOException {
if(pkg.isUnnamed())
log( "generating " + fileName, Project.MSG_VERBOSE );
else
log( "generating " +
pkg.name().replace('.',File.separatorChar)+
File.separatorChar+fileName, Project.MSG_VERBOSE );
return super.openBinary(pkg,fileName);
}
}
/**
* {@link ErrorReceiver} that produces messages
* as Ant messages.
*/
private class ErrorReceiverImpl extends ErrorReceiver {
public void warning(SAXParseException e) {
print(Project.MSG_WARN,Messages.WARNING_MSG,e);
}
public void error(SAXParseException e) {
print(Project.MSG_ERR,Messages.ERROR_MSG,e);
}
public void fatalError(SAXParseException e) {
print(Project.MSG_ERR,Messages.ERROR_MSG,e);
}
public void info(SAXParseException e) {
print(Project.MSG_VERBOSE,Messages.INFO_MSG,e);
}
private void print( int logLevel, String header, SAXParseException e ) {
log( Messages.format(header,e.getMessage()), logLevel );
log( getLocationString(e), logLevel );
log( "", logLevel );
}
}
}