001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036: package com.sun.tools.xjc;
037:
038: import java.io.File;
039: import java.io.IOException;
040: import java.io.OutputStream;
041: import java.io.PrintWriter;
042: import java.io.StringWriter;
043: import java.lang.reflect.Constructor;
044: import java.net.MalformedURLException;
045: import java.net.URL;
046: import java.util.ArrayList;
047: import java.util.List;
048:
049: import com.sun.codemodel.CodeWriter;
050: import com.sun.codemodel.JCodeModel;
051: import com.sun.codemodel.JPackage;
052: import com.sun.codemodel.writer.FilterCodeWriter;
053: import com.sun.tools.xjc.model.Model;
054: import com.sun.tools.xjc.reader.Util;
055: import com.sun.tools.xjc.util.ForkEntityResolver;
056: import com.sun.tools.xjc.api.SpecVersion;
057: import com.sun.xml.bind.v2.util.EditDistance;
058:
059: import org.apache.tools.ant.AntClassLoader;
060: import org.apache.tools.ant.BuildException;
061: import org.apache.tools.ant.DirectoryScanner;
062: import org.apache.tools.ant.Project;
063: import org.apache.tools.ant.Task;
064: import org.apache.tools.ant.types.Commandline;
065: import org.apache.tools.ant.types.FileSet;
066: import org.apache.tools.ant.types.Path;
067: import org.apache.tools.ant.types.Reference;
068: import org.apache.tools.ant.types.XMLCatalog;
069: import org.xml.sax.InputSource;
070: import org.xml.sax.SAXParseException;
071:
072: /**
073: * XJC task for Ant.
074: *
075: * See the accompanied document for the usage.
076: *
077: * @author
078: * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
079: */
080: public class XJC2Task extends Task {
081: public XJC2Task() {
082: super ();
083: classpath = new Path(null);
084: options.setSchemaLanguage(Language.XMLSCHEMA); // disable auto-guessing
085: }
086:
087: public final Options options = new Options();
088:
089: /** User-specified stack size. */
090: private long stackSize = -1;
091:
092: /**
093: * False to continue the build even if the compilation fails.
094: */
095: private boolean failonerror = true;
096:
097: /**
098: * True if we will remove all the old output files before
099: * invoking XJC.
100: */
101: private boolean removeOldOutput = false;
102:
103: /**
104: * Files used to determine whether XJC should run or not.
105: */
106: private final ArrayList<File> dependsSet = new ArrayList<File>();
107: private final ArrayList<File> producesSet = new ArrayList<File>();
108:
109: /**
110: * Set to true once the <produces> element is used.
111: * This flag is used to issue a suggestion to users.
112: */
113: private boolean producesSpecified = false;
114:
115: /**
116: * Used to load additional user-specified classes.
117: */
118: private final Path classpath;
119:
120: /** Additional command line arguments. */
121: private final Commandline cmdLine = new Commandline();
122:
123: /** for resolving entities such as dtds */
124: private XMLCatalog xmlCatalog = null;
125:
126: /**
127: * Parses the schema attribute. This attribute will be used when
128: * there is only one schema.
129: *
130: * @param schema
131: * A file name (can be relative to base dir),
132: * or an URL (must be absolute).
133: */
134: public void setSchema(String schema) {
135: try {
136: options.addGrammar(getInputSource(new URL(schema)));
137: } catch (MalformedURLException e) {
138: File f = getProject().resolveFile(schema);
139: options.addGrammar(f);
140: dependsSet.add(f);
141: }
142: }
143:
144: /** Nested <schema> element. */
145: public void addConfiguredSchema(FileSet fs) {
146: for (InputSource value : toInputSources(fs))
147: options.addGrammar(value);
148:
149: addIndividualFilesTo(fs, dependsSet);
150: }
151:
152: /** Nested <classpath> element. */
153: public void setClasspath(Path cp) {
154: classpath.createPath().append(cp);
155: }
156:
157: /** Nested <classpath> element. */
158: public Path createClasspath() {
159: return classpath.createPath();
160: }
161:
162: public void setClasspathRef(Reference r) {
163: classpath.createPath().setRefid(r);
164: }
165:
166: /**
167: * Sets the schema language.
168: */
169: public void setLanguage(String language) {
170: Language l = Language.valueOf(language.toUpperCase());
171: if (l == null) {
172: Language[] languages = Language.values();
173: String[] candidates = new String[languages.length];
174: for (int i = 0; i < candidates.length; i++)
175: candidates[i] = languages[i].name();
176:
177: throw new BuildException("Unrecognized language: "
178: + language
179: + ". Did you mean "
180: + EditDistance.findNearest(language.toUpperCase(),
181: candidates) + " ?");
182: }
183: options.setSchemaLanguage(l);
184: }
185:
186: /**
187: * External binding file.
188: */
189: public void setBinding(String binding) {
190: try {
191: options.addBindFile(getInputSource(new URL(binding)));
192: } catch (MalformedURLException e) {
193: File f = getProject().resolveFile(binding);
194: options.addBindFile(f);
195: dependsSet.add(f);
196: }
197: }
198:
199: /** Nested <binding> element. */
200: public void addConfiguredBinding(FileSet fs) {
201: for (InputSource is : toInputSources(fs))
202: options.addBindFile(is);
203:
204: addIndividualFilesTo(fs, dependsSet);
205: }
206:
207: /**
208: * Sets the package name of the generated code.
209: */
210: public void setPackage(String pkg) {
211: this .options.defaultPackage = pkg;
212: }
213:
214: /**
215: * Adds a new catalog file.
216: */
217: public void setCatalog(File catalog) {
218: try {
219: this .options.addCatalog(catalog);
220: } catch (IOException e) {
221: throw new BuildException(e);
222: }
223: }
224:
225: /**
226: * Mostly for our SQE teams and not to be advertized.
227: */
228: public void setFailonerror(boolean value) {
229: failonerror = value;
230: }
231:
232: /**
233: * Sets the stack size of the XJC invocation.
234: *
235: * @deprecated
236: * not much need for JAXB2, as we now use much less stack.
237: */
238: public void setStackSize(String ss) {
239: try {
240: stackSize = Long.parseLong(ss);
241: return;
242: } catch (NumberFormatException e) {
243: ;
244: }
245:
246: if (ss.length() > 2) {
247: String head = ss.substring(0, ss.length() - 2);
248: String tail = ss.substring(ss.length() - 2);
249:
250: if (tail.equalsIgnoreCase("kb")) {
251: try {
252: stackSize = Long.parseLong(head) * 1024;
253: return;
254: } catch (NumberFormatException ee) {
255: ;
256: }
257: }
258: if (tail.equalsIgnoreCase("mb")) {
259: try {
260: stackSize = Long.parseLong(head) * 1024 * 1024;
261: return;
262: } catch (NumberFormatException ee) {
263: ;
264: }
265: }
266: }
267:
268: throw new BuildException("Unrecognizable stack size: " + ss);
269: }
270:
271: /**
272: * Add the catalog to our internal catalog
273: *
274: * @param xmlCatalog the XMLCatalog instance to use to look up DTDs
275: */
276: public void addConfiguredXMLCatalog(XMLCatalog xmlCatalog) {
277: if (this .xmlCatalog == null) {
278: this .xmlCatalog = new XMLCatalog();
279: this .xmlCatalog.setProject(getProject());
280: }
281: this .xmlCatalog.addConfiguredXMLCatalog(xmlCatalog);
282: }
283:
284: /**
285: * Controls whether files should be generated in read-only mode or not
286: */
287: public void setReadonly(boolean flg) {
288: this .options.readOnly = flg;
289: }
290:
291: /**
292: * Controls whether the file header comment is generated or not.
293: */
294: public void setHeader(boolean flg) {
295: this .options.noFileHeader = !flg;
296: }
297:
298: /**
299: * @see Options#runtime14
300: */
301: public void setXexplicitAnnotation(boolean flg) {
302: this .options.runtime14 = flg;
303: }
304:
305: /**
306: * Controls whether the compiler will run in the strict
307: * conformance mode (flg=false) or the extension mode (flg=true)
308: */
309: public void setExtension(boolean flg) {
310: if (flg)
311: this .options.compatibilityMode = Options.EXTENSION;
312: else
313: this .options.compatibilityMode = Options.STRICT;
314: }
315:
316: /**
317: * Sets the target version of the compilation
318: */
319: public void setTarget(String version) {
320: options.target = SpecVersion.parse(version);
321: if (options.target == null)
322: throw new BuildException(
323: version
324: + " is not a valid version number. Perhaps you meant @destdir?");
325: }
326:
327: /**
328: * Sets the directory to produce generated source files.
329: */
330: public void setDestdir(File dir) {
331: this .options.targetDir = dir;
332: }
333:
334: /** Nested <depends> element. */
335: public void addConfiguredDepends(FileSet fs) {
336: addIndividualFilesTo(fs, dependsSet);
337: }
338:
339: /** Nested <produces> element. */
340: public void addConfiguredProduces(FileSet fs) {
341: producesSpecified = true;
342: if (!fs.getDir(getProject()).exists()) {
343: log(
344: fs.getDir(getProject()).getAbsolutePath()
345: + " is not found and thus excluded from the dependency check",
346: Project.MSG_INFO);
347: } else
348: addIndividualFilesTo(fs, producesSet);
349: }
350:
351: /** "removeOldOutput" attribute. */
352: public void setRemoveOldOutput(boolean roo) {
353: this .removeOldOutput = roo;
354: }
355:
356: public Commandline.Argument createArg() {
357: return cmdLine.createArgument();
358: }
359:
360: /** Runs XJC. */
361: public void execute() throws BuildException {
362:
363: log("build id of XJC is " + Driver.getBuildID(),
364: Project.MSG_VERBOSE);
365:
366: classpath.setProject(getProject());
367:
368: try {
369: if (stackSize == -1)
370: doXJC(); // just invoke XJC
371: else {
372: try {
373: // launch XJC with a new thread so that we can set the stack size.
374: final Throwable[] e = new Throwable[1];
375:
376: Thread t;
377: Runnable job = new Runnable() {
378: public void run() {
379: try {
380: doXJC();
381: } catch (Throwable be) {
382: e[0] = be;
383: }
384: }
385: };
386:
387: try {
388: // this method is available only on JDK1.4
389: Constructor c = Thread.class.getConstructor(
390: ThreadGroup.class, Runnable.class,
391: String.class, long.class);
392: t = (Thread) c.newInstance(Thread
393: .currentThread().getThreadGroup(), job,
394: Thread.currentThread().getName()
395: + ":XJC", stackSize);
396: } catch (Throwable err) {
397: // if fail, fall back.
398: log(
399: "Unable to set the stack size. Use JDK1.4 or above",
400: Project.MSG_WARN);
401: doXJC();
402: return;
403: }
404:
405: t.start();
406: t.join();
407: if (e[0] instanceof Error)
408: throw (Error) e[0];
409: if (e[0] instanceof RuntimeException)
410: throw (RuntimeException) e[0];
411: if (e[0] instanceof BuildException)
412: throw (BuildException) e[0];
413: if (e[0] != null)
414: throw new BuildException(e[0]);
415: } catch (InterruptedException e) {
416: throw new BuildException(e);
417: }
418: }
419: } catch (BuildException e) {
420: log("failure in the XJC task. Use the Ant -verbose switch for more details");
421: if (failonerror)
422: throw e;
423: else {
424: StringWriter sw = new StringWriter();
425: e.printStackTrace(new PrintWriter(sw));
426: getProject().log(sw.toString(), Project.MSG_WARN);
427: // continue
428: }
429: }
430: }
431:
432: private void doXJC() throws BuildException {
433: ClassLoader old = Thread.currentThread()
434: .getContextClassLoader();
435: try {
436: // set the user-specified class loader so that XJC will use it.
437: Thread.currentThread().setContextClassLoader(
438: new AntClassLoader(getProject(), classpath));
439: _doXJC();
440: } finally {
441: // restore the context class loader
442: Thread.currentThread().setContextClassLoader(old);
443: }
444: }
445:
446: private void _doXJC() throws BuildException {
447: try {
448: // parse additional command line params
449: options.parseArguments(cmdLine.getArguments());
450: } catch (BadCommandLineException e) {
451: throw new BuildException(e.getMessage(), e);
452: }
453:
454: if (xmlCatalog != null) {
455: if (options.entityResolver == null) {
456: options.entityResolver = xmlCatalog;
457: } else {
458: options.entityResolver = new ForkEntityResolver(
459: options.entityResolver, xmlCatalog);
460: }
461: }
462:
463: if (!producesSpecified) {
464: log(
465: "Consider using <depends>/<produces> so that XJC won't do unnecessary compilation",
466: Project.MSG_INFO);
467: }
468:
469: // up to date check
470: long srcTime = computeTimestampFor(dependsSet, true);
471: long dstTime = computeTimestampFor(producesSet, false);
472: log("the last modified time of the inputs is " + srcTime,
473: Project.MSG_VERBOSE);
474: log("the last modified time of the outputs is " + dstTime,
475: Project.MSG_VERBOSE);
476:
477: if (srcTime < dstTime) {
478: log("files are up to date");
479: return;
480: }
481:
482: InputSource[] grammars = options.getGrammars();
483:
484: String msg = "Compiling " + grammars[0].getSystemId();
485: if (grammars.length > 1)
486: msg += " and others";
487: log(msg, Project.MSG_INFO);
488:
489: if (removeOldOutput) {
490: log("removing old output files", Project.MSG_INFO);
491: for (File f : producesSet)
492: f.delete();
493: }
494:
495: // TODO: I don't know if I should send output to stdout
496: ErrorReceiver errorReceiver = new ErrorReceiverImpl();
497:
498: Model model = ModelLoader.load(options, new JCodeModel(),
499: errorReceiver);
500:
501: if (model == null)
502: throw new BuildException(
503: "unable to parse the schema. Error messages should have been provided");
504:
505: try {
506:
507: if (model.generateCode(options, errorReceiver) == null)
508: throw new BuildException("failed to compile a schema");
509:
510: log("Writing output to " + options.targetDir,
511: Project.MSG_INFO);
512:
513: model.codeModel.build(new AntProgressCodeWriter(options
514: .createCodeWriter()));
515: } catch (IOException e) {
516: throw new BuildException("unable to write files: "
517: + e.getMessage(), e);
518: }
519: }
520:
521: /**
522: * Determines the timestamp of the newest/oldest file in the given set.
523: */
524: private long computeTimestampFor(List<File> files,
525: boolean findNewest) {
526:
527: long lastModified = findNewest ? Long.MIN_VALUE
528: : Long.MAX_VALUE;
529:
530: for (File file : files) {
531: log("Checking timestamp of " + file.toString(),
532: Project.MSG_VERBOSE);
533:
534: if (findNewest)
535: lastModified = Math.max(lastModified, file
536: .lastModified());
537: else
538: lastModified = Math.min(lastModified, file
539: .lastModified());
540: }
541:
542: if (lastModified == Long.MIN_VALUE) // no file was found
543: return Long.MAX_VALUE; // force re-run
544:
545: if (lastModified == Long.MAX_VALUE) // no file was found
546: return Long.MIN_VALUE; // force re-run
547:
548: return lastModified;
549: }
550:
551: /**
552: * Extracts {@link File} objects that the given {@link FileSet}
553: * represents and adds them all to the given {@link List}.
554: */
555: private void addIndividualFilesTo(FileSet fs, List<File> lst) {
556: DirectoryScanner ds = fs.getDirectoryScanner(getProject());
557: String[] includedFiles = ds.getIncludedFiles();
558: File baseDir = ds.getBasedir();
559:
560: for (String value : includedFiles) {
561: lst.add(new File(baseDir, value));
562: }
563: }
564:
565: /**
566: * Extracts files in the given {@link FileSet}.
567: */
568: private InputSource[] toInputSources(FileSet fs) {
569: DirectoryScanner ds = fs.getDirectoryScanner(getProject());
570: String[] includedFiles = ds.getIncludedFiles();
571: File baseDir = ds.getBasedir();
572:
573: ArrayList<InputSource> lst = new ArrayList<InputSource>();
574:
575: for (String value : includedFiles) {
576: lst.add(getInputSource(new File(baseDir, value)));
577: }
578:
579: return lst.toArray(new InputSource[lst.size()]);
580: }
581:
582: /**
583: * Converts a File object to an InputSource.
584: */
585: private InputSource getInputSource(File f) {
586: try {
587: return new InputSource(f.toURL().toExternalForm());
588: } catch (MalformedURLException e) {
589: return new InputSource(f.getPath());
590: }
591: }
592:
593: /**
594: * Converts an URL to an InputSource.
595: */
596: private InputSource getInputSource(URL url) {
597: return Util.getInputSource(url.toExternalForm());
598: }
599:
600: /**
601: * {@link CodeWriter} that produces progress messages
602: * as Ant verbose messages.
603: */
604: private class AntProgressCodeWriter extends FilterCodeWriter {
605: public AntProgressCodeWriter(CodeWriter output) {
606: super (output);
607: }
608:
609: public OutputStream openBinary(JPackage pkg, String fileName)
610: throws IOException {
611: if (pkg.isUnnamed())
612: log("generating " + fileName, Project.MSG_VERBOSE);
613: else
614: log("generating "
615: + pkg.name().replace('.', File.separatorChar)
616: + File.separatorChar + fileName,
617: Project.MSG_VERBOSE);
618:
619: return super .openBinary(pkg, fileName);
620: }
621: }
622:
623: /**
624: * {@link ErrorReceiver} that produces messages
625: * as Ant messages.
626: */
627: private class ErrorReceiverImpl extends ErrorReceiver {
628:
629: public void warning(SAXParseException e) {
630: print(Project.MSG_WARN, Messages.WARNING_MSG, e);
631: }
632:
633: public void error(SAXParseException e) {
634: print(Project.MSG_ERR, Messages.ERROR_MSG, e);
635: }
636:
637: public void fatalError(SAXParseException e) {
638: print(Project.MSG_ERR, Messages.ERROR_MSG, e);
639: }
640:
641: public void info(SAXParseException e) {
642: print(Project.MSG_VERBOSE, Messages.INFO_MSG, e);
643: }
644:
645: private void print(int logLevel, String header,
646: SAXParseException e) {
647: log(Messages.format(header, e.getMessage()), logLevel);
648: log(getLocationString(e), logLevel);
649: log("", logLevel);
650: }
651: }
652: }
|