/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2017 Oracle and/or its affiliates. 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_1_1.html * or packager/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 packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [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 org.apache.tools.ant.taskdefs.MatchingTask; 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.*; import com.sun.codemodel.CodeWriter; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JPackage; import com.sun.codemodel.writer.FilterCodeWriter; import com.sun.istack.tools.DefaultAuthenticator; 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.taskdefs.Execute; import org.apache.tools.ant.taskdefs.LogStreamHandler; import org.apache.tools.ant.types.*; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; /** * @author Yan GAO (gaoyan.gao@oracle.com) */ public class XJCBase extends MatchingTask { public XJCBase() { super(); classpath = new Path(null); options.setSchemaLanguage(Language.XMLSCHEMA); // disable auto-guessing } private Path modulepath = null; public void setModulepath(Path mp) { this.modulepath = mp; } public Path getModulepath() { return this.modulepath; } private Path upgrademodulepath = null; public void setUpgrademodulepath(Path ump) { this.upgrademodulepath = ump; } public Path getUpgrademodulepath() { return this.upgrademodulepath; } private String addmodules = null; public void setAddmodules(String ams) { this.addmodules = ams; } public String getAddmodules() { return this.addmodules; } private String limitmodules = null; public void setLimitmodules(String lms) { this.limitmodules = lms; } public String getLimitmodules() { return this.limitmodules; } private String addreads = null; public void setAddreads(String ars) { this.addreads = ars; } public String getAddreads() { return this.addreads; } private String addexports = null; public void setAddexports(String aes) { this.addexports = aes; } public String getAddexports() { return this.addexports; } private String patchmodule = null; public void setPatchmodule(String pms) { this.patchmodule = pms; } public String getPatchmodule() { return this.patchmodule; } private String addopens = null; public void setAddopens(String aos) { this.addopens = aos; } public String getAddopens() { return this.addopens; } 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; private final ArrayList<File> bindingFiles = new ArrayList<>(); private final ArrayList<File> schemaFiles = new ArrayList<>(); /** * 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<File> dependsSet = new ArrayList<File>(); private final ArrayList<File> producesSet = new ArrayList<File>(); /** * Set to true once the {@code <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; /* *********************** -fork option ************************ */ private boolean fork = false; private final CommandlineJava cmd = new CommandlineJava(); CommandlineJava getCommandline() { return cmd; } /** * Gets the "fork" flag. * * @return true if execution should be done in forked JVM, false otherwise. */ public boolean getFork() { return fork; } /** * Sets the "fork" flag. * * @param fork true to run execution in a forked JVM. */ public void setFork(boolean fork) { this.fork = fork; } /** * 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) { File f; try { f = new File(schema); options.addGrammar(getInputSource(new URL(schema))); } catch (MalformedURLException e) { f = getProject().resolveFile(schema); options.addGrammar(f); dependsSet.add(f); } schemaFiles.add(f); } /** * Nested {@code <schema>} element. */ public void addConfiguredSchema(FileSet fs) { for (InputSource value : toInputSources(fs)) options.addGrammar(value); addIndividualFilesTo(fs, dependsSet); addIndividualFilesTo(fs, schemaFiles); } /** * Nested {@code <classpath>} element. */ public void setClasspath(Path cp) { classpath.createPath().append(cp); } /** * Nested {@code <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 < candidates.length; i++) candidates[i] = languages[i].name(); throw new BuildException("Unrecognized language: " + language + ". Did you mean " + EditDistance.findNearest(language.toUpperCase(), candidates) + " ?"); } options.setSchemaLanguage(l); } /** * External binding file. */ public void setBinding(String binding) { try { File f = new File(binding); bindingFiles.add(f); options.addBindFile(getInputSource(new URL(binding))); } catch (MalformedURLException e) { File f = getProject().resolveFile(binding); options.addBindFile(f); dependsSet.add(f); } } /** * Nested {@code <binding>} element. */ public void addConfiguredBinding(FileSet fs) { for (InputSource is : toInputSources(fs)) options.addBindFile(is); addIndividualFilesTo(fs, dependsSet); addIndividualFilesTo(fs, bindingFiles); } /** * Sets the package name of the generated code. */ public void setPackage(String pkg) { this.options.defaultPackage = pkg; } public String getPackage() { return this.options.defaultPackage; } private File catalog; /** * Adds a new catalog file. */ public void setCatalog(File catalog) { try { this.options.addCatalog(catalog); this.catalog = catalog; } catch (Exception e) { throw new BuildException(e); } } public File getCatalog() { return this.catalog; } /** * 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) { // ignore } 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) { // ignore } } if (tail.equalsIgnoreCase("mb")) { try { stackSize = Long.parseLong(head) * 1024 * 1024; return; } catch (NumberFormatException ee) { // ignore } } } 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; } public boolean getReadOnly() { return this.options.readOnly; } /** * Controls whether the file header comment is generated or not. */ public void setHeader(boolean flg) { this.options.noFileHeader = !flg; } public boolean getHeader() { return this.options.noFileHeader; } /** * @see Options#runtime14 */ public void setXexplicitAnnotation(boolean flg) { this.options.runtime14 = flg; } private boolean extension = false; /** * Controls whether the compiler will run in the strict * conformance mode (flg=false) or the extension mode (flg=true) */ public void setExtension(boolean flg) { extension = flg; if (flg) this.options.compatibilityMode = Options.EXTENSION; else this.options.compatibilityMode = Options.STRICT; } public boolean getExtension() { return extension; } private String specTarget; /** * 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?"); specTarget = options.target.getVersion(); } public String getSpecTarget() { return this.specTarget; } public boolean getVerbose() { return this.options.verbose; } /** * Sets the directory to produce generated source files. */ public void setDestdir(File dir) { this.options.targetDir = dir; } public File getDestdir() { return this.options.targetDir; } public void setEncoding(String encoding) { this.options.encoding = encoding; } public String getEncoding() { return this.options.encoding; } /** * Nested {@code <depends>} element. */ public void addConfiguredDepends(FileSet fs) { addIndividualFilesTo(fs, dependsSet); } /** * Nested {@code <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 boolean getRemoveOldOutput() { return this.removeOldOutput; } public Commandline.Argument createArg() { return cmdLine.createArgument(); } public Commandline.Argument createJvmarg() { return cmd.createVmArgument(); } /** * Set up command line to invoke. * * @return ready to run command line */ protected CommandlineJava setupCommand() { // d option if (null != getDestdir() && !getDestdir().getName().equals("")) { getCommandline().createArgument().setValue("-d"); getCommandline().createArgument().setFile(getDestdir()); } //p option if (null != getPackage() && !getPackage().equals("")) { getCommandline().createArgument().setValue("-p"); getCommandline().createArgument().setValue(getPackage()); } // extension flag if (getExtension()) { getCommandline().createArgument().setValue("-extension"); } // encoding option if (getEncoding() != null) { getCommandline().createArgument().setValue("-encoding"); getCommandline().createArgument().setValue(getEncoding()); } // readOnly option if (getReadOnly()) { getCommandline().createArgument().setValue("-readOnly"); } // no-header option if (getHeader()) { getCommandline().createArgument().setValue("-no-header"); } if (getRemoveOldOutput()) { getCommandline().createArgument().setValue("-removeOldOutput"); } if (getSpecTarget() != null) { getCommandline().createArgument().setValue("-target"); getCommandline().createArgument().setValue(getSpecTarget()); } // verbose option if (getVerbose()) { getCommandline().createArgument().setValue("-verbose"); } //catalog if ((getCatalog() != null) && (getCatalog().getName().length() > 0)) { getCommandline().createArgument().setValue("-catalog"); getCommandline().createArgument().setFile(getCatalog()); } for (String a : cmdLine.getArguments()) { getCommandline().createArgument().setValue(a); } addFilesToCommandLine(schemaFiles, null); addFilesToCommandLine(bindingFiles, "-b"); return getCommandline(); } void addFilesToCommandLine(ArrayList<File> files, String option) { if (!files.isEmpty()) { for (File file : files) { if (option != null && option.length() > 0) { getCommandline().createArgument().setValue(option); } boolean isLink = false; try { isLink = !file.getCanonicalPath().equals(file.getAbsolutePath()) && !(file.getAbsolutePath().contains("~1") && file.getCanonicalPath().indexOf(' ') >= 0); } catch (IOException e) { // do nothing } if (isLink) { getCommandline().createArgument().setValue(file.toURI().toString()); } else getCommandline().createArgument().setFile(file); } } } void setupForkCommand(String className) { ClassLoader loader = this.getClass().getClassLoader(); while (loader != null && !(loader instanceof AntClassLoader)) { loader = loader.getParent(); } String antcp = loader != null //taskedef cp ? ((AntClassLoader) loader).getClasspath() //system classloader, ie. env CLASSPATH=... : System.getProperty("java.class.path"); // try to find tools.jar and add it to the cp // so the behaviour on all JDKs is the same // (avoid creating MaskingClassLoader on non-Mac JDKs) File jreHome = new File(System.getProperty("java.home")); File toolsJar = new File(jreHome.getParent(), "lib/tools.jar"); if (toolsJar.exists()) { antcp += File.pathSeparatorChar + toolsJar.getAbsolutePath(); } getCommandline().createClasspath(getProject()).append(new Path(getProject(), antcp)); getCommandline().setClassname(className); } /** * Runs XJC. */ @Override public void execute() throws BuildException { log("build id of XJC is " + Driver.getBuildID(), Project.MSG_VERBOSE); classpath.setProject(getProject()); // 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; } boolean ok = false; try { if (getFork()) { setupCommand(); setupForkCommand("com.sun.tools.xjc.XJCFacade"); int status = run(getCommandline().getCommandline()); ok = (status == 0); } else { if (getCommandline().getVmCommand().size() > 1) { log("JVM args ignored when same JVM is used.", Project.MSG_WARN); } 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); } } } if (!ok) { log("Command invoked: " + "xjc" + getCommandline().toString()); throw new BuildException("xjc" + " failed", getLocation()); } } 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 } } } /** * Executes the given class name with the given arguments in a separate VM. * * @param command arguments. * @return return value from the executed process. */ private int run(String[] command) throws BuildException { Execute exe; LogStreamHandler logstr = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN); exe = new Execute(logstr); exe.setAntRun(getProject()); exe.setCommandline(command); try { int rc = exe.execute(); if (exe.killedProcess()) { log("Timeout: killed the sub-process", Project.MSG_WARN); } return rc; } catch (IOException e) { throw new BuildException(e, getLocation()); } } private void doXJC() throws BuildException { ClassLoader old = SecureLoader.getContextClassLoader(); AntClassLoader acl = null; try { if (classpath != null) { for (String pathElement : classpath.list()) { try { options.classpaths.add(new File(pathElement).toURI().toURL()); } catch (MalformedURLException ex) { log("Classpath for XJC task not setup properly: " + pathElement); } } } // set the user-specified class loader so that XJC will use it. // We have to specify parent classLoader because in other case AntClassLoader class classLoader will be set as parent // and we will lose current classLoader. SecureLoader.setContextClassLoader( acl = new AntClassLoader(this.getClass().getClassLoader(), getProject(), classpath, false)); _doXJC(); } finally { // restore the context class loader SecureLoader.setContextClassLoader(old); // close AntClassLoader if (acl != null) { acl.cleanup(); } if (options.proxyAuth != null) { DefaultAuthenticator.reset(); } } } private void _doXJC() throws BuildException { try { // parse additional command line params options.parseArguments(cmdLine.getArguments()); // options.parseArguments(jvmarg.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 <depends>/<produces> so that XJC won't do unnecessary compilation", Project.MSG_INFO); } 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 XJCBase.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 XJCBase.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<File> 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<File> 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<InputSource> lst = new ArrayList<InputSource>(); 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.toURI().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); } @Override public OutputStream openBinary(JPackage pkg, String fileName) throws IOException { if (pkg == null || 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); } } }