View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
34   * All portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
35   * Visco. All Rights Reserved.
36   *
37   * $Id$
38   */
39  package org.exolab.castor.builder;
40  
41  import java.io.File;
42  import java.io.FileOutputStream;
43  import java.io.FileReader;
44  import java.io.FileWriter;
45  import java.io.IOException;
46  import java.io.Reader;
47  import java.util.Enumeration;
48  import java.util.Iterator;
49  import java.util.Properties;
50  import java.util.Vector;
51  
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  import org.castor.core.exceptions.CastorRuntimeException;
55  import org.castor.xml.BackwardCompatibilityContext;
56  import org.castor.xml.InternalContext;
57  import org.exolab.castor.builder.binding.BindingException;
58  import org.exolab.castor.builder.binding.BindingLoader;
59  import org.exolab.castor.builder.binding.ExtendedBinding;
60  import org.exolab.castor.builder.binding.XMLBindingComponent;
61  import org.exolab.castor.builder.binding.XPathHelper;
62  import org.exolab.castor.builder.binding.xml.PackageType;
63  import org.exolab.castor.builder.binding.xml.PackageTypeChoice;
64  import org.exolab.castor.builder.binding.xml.types.BindingType;
65  import org.exolab.castor.builder.conflict.strategy.ClassNameConflictResolver;
66  import org.exolab.castor.builder.conflict.strategy.TypeClassNameConflictResolver;
67  import org.exolab.castor.builder.conflict.strategy.XPATHClassNameConflictResolver;
68  import org.exolab.castor.builder.conflictresolution.WarningViaDialogClassNameCRStrategy;
69  import org.exolab.castor.builder.factory.FieldInfoFactory;
70  import org.exolab.castor.builder.factory.SourceFactory;
71  import org.exolab.castor.builder.info.ClassInfo;
72  import org.exolab.castor.builder.printing.JClassPrinter;
73  import org.exolab.castor.builder.printing.JClassPrinterFactory;
74  import org.exolab.castor.builder.printing.JClassPrinterFactoryRegistry;
75  import org.exolab.castor.mapping.xml.MappingRoot;
76  import org.exolab.castor.util.NestedIOException;
77  import org.exolab.castor.util.Version;
78  import org.exolab.castor.util.dialog.ConsoleDialog;
79  import org.exolab.castor.xml.Marshaller;
80  import org.exolab.castor.xml.ValidationException;
81  import org.exolab.castor.xml.XMLException;
82  import org.exolab.castor.xml.schema.AttributeDecl;
83  import org.exolab.castor.xml.schema.ComplexType;
84  import org.exolab.castor.xml.schema.ContentModelGroup;
85  import org.exolab.castor.xml.schema.ContentType;
86  import org.exolab.castor.xml.schema.ElementDecl;
87  import org.exolab.castor.xml.schema.Facet;
88  import org.exolab.castor.xml.schema.Group;
89  import org.exolab.castor.xml.schema.ModelGroup;
90  import org.exolab.castor.xml.schema.Particle;
91  import org.exolab.castor.xml.schema.Schema;
92  import org.exolab.castor.xml.schema.SchemaContext;
93  import org.exolab.castor.xml.schema.SchemaContextImpl;
94  import org.exolab.castor.xml.schema.SimpleContent;
95  import org.exolab.castor.xml.schema.SimpleType;
96  import org.exolab.castor.xml.schema.Structure;
97  import org.exolab.castor.xml.schema.XMLType;
98  import org.exolab.castor.xml.schema.reader.Sax2ComponentReader;
99  import org.exolab.castor.xml.schema.reader.SchemaUnmarshaller;
100 import org.exolab.javasource.JClass;
101 import org.xml.sax.InputSource;
102 import org.xml.sax.Parser;
103 import org.xml.sax.SAXParseException;
104 
105 /**
106  * A Java Source generation tool which uses XML Schema definitions to create an Object model.
107  *
108  * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a> - Main author.
109  * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> - Contributions.
110  * @author <a href="mailto:nsgreen@thazar.com">Nathan Green</a> - Contributions.
111  * @version $Revision$ $Date: 2006-03-30 14:58:45 -0700 (Thu, 30 Mar 2006) $
112  */
113 public class SourceGenerator extends BuilderConfiguration {
114 
115   /** Jakarta's common-logging logger. */
116   private static final Log LOG = LogFactory.getLog(SourceGenerator.class);
117 
118   // -------------/
119   // - Constants -/
120   // -------------/
121   /** The application name. */
122   static final String APP_NAME = "Castor";
123   /** The application description. */
124   static final String APP_DESC = "XML data binder for Java";
125   /** The application version. */
126   static final String VERSION = Version.VERSION;
127   /** The application URI. */
128   static final String APP_URI = "http://www.castor.org";
129   /** Warning message to remind users to create source code for imported schema. */
130   private static final String IMPORT_WARNING =
131       "Note: No code will be generated for the following *imported* schema: ";
132 
133   // ----------------------/
134   // - Instance Variables -/
135   // ----------------------/
136 
137   /** Castor internal context - mother of all. */
138   private final InternalContext _internalContext;
139   /** The XMLBindingComponent used to create Java classes from an XML Schema. */
140   private final XMLBindingComponent _bindingComponent;
141   /** Our object used to generate source for a single source file. */
142   private final SingleClassGenerator _singleClassGenerator;
143   /** The field info factory. */
144   private final FieldInfoFactory _infoFactory;
145   /** Allows us to ask the user questions. */
146   private final ConsoleDialog _dialog;
147   /** A vector that keeps track of all the schemas processed. */
148   private final Vector<Schema> _schemasProcessed = new Vector<Schema>(7);
149 
150   /** True if we should suppress non-fatal warnings. */
151   private boolean _suppressNonFatalWarnings = false;
152   /** Determines whether or not to print extra messages. */
153   private boolean _verbose = false;
154 
155   /**
156    * A flag indicating whether or not to create XML-specific class descriptors for the generated
157    * classes.
158    */
159   private boolean _createDescriptors = true;
160   /**
161    * A flag indicating whether or not to create JDO-specific class descriptors for the generated
162    * classes.
163    */
164   private boolean _createJdoDescriptors = false;
165 
166   /**
167    * A flag indicating whether or not to generate sources for imported XML Schemas.
168    */
169   private boolean _generateImported = false;
170   /** The source factory. */
171   private SourceFactory _sourceFactory = null;
172   /** A flag to indicate that the mapping file should be generated. */
173   private boolean _generateMapping = false;
174   /** The name of the mapping file to create used with the gen-mapping flag. */
175   private String _mappingFilename = "mapping.xml";
176   /**
177    * A flag indicating whether or not to generate XML marshalling framework specific methods.
178    */
179   private boolean _createMarshalMethods = true;
180   /**
181    * A flag indicating whether or not to implement CastorTestable (used by the Castor Testing
182    * Framework).
183    */
184   private boolean _testable = false;
185   /** A flag indicating that SAX1 should be used when generating the source. */
186   private boolean _sax1 = false;
187   /**
188    * A flag indicating that enumerated types should be constructed to perform case insensitive
189    * lookups based on the values.
190    */
191   private boolean _caseInsensitive = false;
192   /**
193    * A flag indicating, if true, that source generation should fail on the first error.
194    */
195   private boolean _failOnFirstError = false;
196   /** A GroupNaming helper class used to named anonymous groups. */
197   private GroupNaming _groupNaming = null;
198   /** Strategy for name conflict resolution. */
199   private String _nameConflictStrategy = WarningViaDialogClassNameCRStrategy.NAME;
200   /** JClass to XPATH registry, used for class name conflict resolution. */
201   private JClassRegistry _xmlInfoRegistry;
202 
203   /**
204    * Strategy implementation for resolving class name conflicts.
205    */
206   private ClassNameConflictResolver _conflictResolver = new XPATHClassNameConflictResolver();
207 
208   /**
209    * The default type of the {@link JClassPrinterFactory} to use for instantiating instances of
210    * {@link JClassPrinter} instances.
211    */
212   private String _jclassPrinterType = "standard";
213 
214   /**
215    * {@link JClassPrinterFactoryRegistry} instance to be used for obtaining instances of
216    * {@link JClassPrinterFactory} instances.
217    */
218   private JClassPrinterFactoryRegistry _jclassPrinterFactoryRegistry;
219 
220   protected SGStateInfo _sInfo;
221 
222   /**
223    * Creates a SourceGenerator using the default FieldInfo factory.
224    */
225   public SourceGenerator() {
226     this(null);
227   } // -- SourceGenerator
228 
229   /**
230    * Creates a SourceGenerator using the specific field info Factory.
231    *
232    * @param infoFactory the FieldInfoFactory to use.
233    */
234   public SourceGenerator(final FieldInfoFactory infoFactory) {
235     this(infoFactory, null);
236   }
237 
238   /**
239    * Creates a SourceGenerator using the specific field info Factory and the given Binding element.
240    *
241    * @param infoFactory the FieldInfoFactory to use.
242    * @param binding the binding element to use.
243    */
244   public SourceGenerator(final FieldInfoFactory infoFactory, final ExtendedBinding binding) {
245     super();
246 
247     _internalContext = new BackwardCompatibilityContext();
248 
249     setJavaNaming(_internalContext.getJavaNaming());
250     _dialog = new ConsoleDialog();
251     if (infoFactory == null) {
252       if (useOldFieldNaming()) {
253         _infoFactory = new FieldInfoFactory();
254       } else {
255         _infoFactory = new FieldInfoFactory(false);
256       }
257     } else {
258       _infoFactory = infoFactory;
259     }
260 
261     super.load();
262 
263     _groupNaming = new GroupNaming(getJavaNaming());
264 
265     _jclassPrinterFactoryRegistry = new JClassPrinterFactoryRegistry(this);
266     _singleClassGenerator =
267         new SingleClassGenerator(_dialog, this, _nameConflictStrategy, _jclassPrinterType);
268     _bindingComponent = new XMLBindingComponent(this, _groupNaming);
269     setBinding(binding);
270 
271     _conflictResolver.setSourceGenerator(this);
272     _xmlInfoRegistry = new JClassRegistry(_conflictResolver, getJavaNaming());
273   } // -- SourceGenerator
274 
275   /**
276    * Returns the selected {@link JClassPrinter} type, as defined by the list of
277    * {@link JClassPrinterFactory} instances enlisted in the Castor XML code generator property file.
278    * 
279    * @return the selected {@link JClassPrinter} type.
280    */
281   private String getJClassPrinterType() {
282     return _jclassPrinterType;
283   }
284 
285   /**
286    * Sets the filename of the mapping file.
287    * 
288    * @param filename filename of the mapping file
289    */
290   public final void setMappingFilename(final String filename) {
291     _mappingFilename = filename;
292   }
293 
294   /**
295    * Sets the strategy for handling name conflicts.
296    *
297    * @param nameConflictStrategy the name of the stretegy to use for handling name conflicts.
298    */
299   public final void setNameConflictStrategy(final String nameConflictStrategy) {
300     _nameConflictStrategy = nameConflictStrategy;
301     _singleClassGenerator.setNameConflictStrategy(nameConflictStrategy);
302   }
303 
304   /**
305    * Returns the version number of this SourceGenerator.
306    *
307    * @return the version number of this SourceGenerator
308    */
309   public static String getVersion() {
310     return VERSION;
311   } // -- getVersion
312 
313   /**
314    * Set to true if SAX1 should be used in the marshal method.
315    * 
316    * @param sax1 true if SAX1 should be used in the marshal method
317    */
318   public final void setSAX1(final boolean sax1) {
319     _sax1 = sax1;
320   }
321 
322   /**
323    * Set to true if enumerated type lookups should be performed in a case insensitive manner.
324    *
325    * @param caseInsensitive when true, enumerated type lookups will be performed in a case
326    *        insensitive manner.
327    */
328   public final void setCaseInsensitive(final boolean caseInsensitive) {
329     _caseInsensitive = caseInsensitive;
330   }
331 
332   /**
333    * If true, the source generator will fail on the first error encountered. Otherwise, the source
334    * generator will continue on certain errors.
335    *
336    * @param failOnFirstError if true, the source generator will fail on the first error encountered.
337    */
338   public final void setFailOnFirstError(final boolean failOnFirstError) {
339     _failOnFirstError = failOnFirstError;
340   }
341 
342   /**
343    * Sets whether or not to suppress non-fatal warnings encountered during source generation.
344    * 
345    * @param suppress true if non-fatal warnings should be suppressed.
346    */
347   public final void setSuppressNonFatalWarnings(final boolean suppress) {
348     _singleClassGenerator.setPromptForOverwrite(!suppress);
349     _suppressNonFatalWarnings = suppress;
350   } // -- setSuppressNonFatalWarnings
351 
352   /**
353    * Sets whether or not the source code generator prints additional messages during generating
354    * source code.
355    *
356    * @param verbose a boolean, when true indicates to print additional messages
357    */
358   public void setVerbose(final boolean verbose) {
359     _verbose = verbose;
360   } // -- setVerbose
361 
362   /**
363    * Sets the ClassNameConflictResolver instance to be used for automatic class name conflict
364    * resolution.
365    *
366    * @param resolverName The name of the resolver to be used for automatic class name conflict
367    *        resolution
368    */
369   public void setClassNameConflictResolver(final String resolverName) {
370     if (resolverName.equals("type")) {
371       _conflictResolver = new TypeClassNameConflictResolver();
372       _conflictResolver.setSourceGenerator(this);
373     } else if (resolverName.equals("xpath")) {
374       // leave default
375     } else {
376       throw new IllegalArgumentException("Invalid resolver type.");
377     }
378     _xmlInfoRegistry.setClassNameConflictResolver(_conflictResolver);
379   } // -- setClassNameConflictResolver
380 
381   /**
382    * Sets whether or not to create ClassDescriptors for the generated classes. By default,
383    * descriptors are generated.
384    *
385    * @param createDescriptors a boolean, when true indicates to generated ClassDescriptors
386    *
387    */
388   public final void setDescriptorCreation(final boolean createDescriptors) {
389     _createDescriptors = createDescriptors;
390     _singleClassGenerator.setDescriptorCreation(createDescriptors);
391   } // -- setDescriptorCreation
392 
393   /**
394    * Sets whether or not to create JDO-specific class descriptors for the generated classes. By
395    * default, JDO-specific class descriptors are NOT generated.
396    *
397    * @param createJdoDescriptors a boolean, when true indicates to generated JDO-specific class
398    *        descriptors
399    *
400    */
401   public final void setJdoDescriptorCreation(final boolean createJdoDescriptors) {
402     _createJdoDescriptors = createJdoDescriptors;
403     _singleClassGenerator.setJdoDescriptorCreation(createJdoDescriptors);
404   } // -- setDescriptorCreation
405 
406   /**
407    * Sets the destination directory.
408    *
409    * @param destDir the destination directory.
410    */
411   public final void setDestDir(final String destDir) {
412     _singleClassGenerator.setDestDir(destDir);
413   }
414 
415   /**
416    * Sets the destination directory for resources, e.g. '.castor.cdr' files.
417    *
418    * @param destDir the destination directory for resources.
419    */
420   public final void setResourceDestination(final String destination) {
421     _singleClassGenerator.setResourceDestinationDirectory(destination);
422   }
423 
424   /**
425    * Sets whether or not to create the XML marshaling framework specific methods (marshal,
426    * unmarshal, validate) in the generated classes. By default, these methods are generated.
427    *
428    * @param createMarshalMethods a boolean, when true indicates to generated the marshaling
429    *        framework methods
430    */
431   public final void setCreateMarshalMethods(final boolean createMarshalMethods) {
432     _createMarshalMethods = createMarshalMethods;
433   } // -- setCreateMarshalMethods
434 
435   /**
436    * Sets whether or not to generate Java sources for imported XML Schema. By default Java sources
437    * for imported XML schemas are not generated.
438    *
439    * @param generate true to generate the java classes for the imported XML Schema
440    */
441   public final void setGenerateImportedSchemas(final boolean generate) {
442     _generateImported = generate;
443   }
444 
445   /**
446    * Sets whether or not a mapping file should be generated, this is false by default. Note that
447    * this will only be used when generation of descriptors has been disabled.
448    *
449    * @param generateMapping a flag that indicates whether or not a mapping file should be generated.
450    */
451   public final void setGenerateMappingFile(final boolean generateMapping) {
452     _generateMapping = generateMapping;
453   } // -- setGenerateMappingFile
454 
455   /**
456    * Sets whether or not to implement CastorTestable.
457    *
458    * @param testable a boolean, when true indicates to implement CastorTestable
459    */
460   public final void setTestable(final boolean testable) {
461     _testable = testable;
462   } // -- setTestable
463 
464   /**
465    * Sets the binding to use with this instance of the SourceGenerator.
466    *
467    * @param binding the binding to use, null indicates that the default binding will be used.
468    */
469   public final void setBinding(final ExtendedBinding binding) {
470     if (binding != null) {
471       processNamespaces(binding.getPackage());
472     }
473     // --initialize the XMLBindingComponent
474     _bindingComponent.setBinding(binding);
475   } // -- setBinding
476 
477   /**
478    * Sets the binding to use given the path name of a Castor Binding File.
479    *
480    * @param fileName the file that represents a Binding
481    */
482   public final void setBinding(final String fileName) {
483     try {
484       ExtendedBinding binding = BindingLoader.createBinding(fileName);
485       setBinding(binding);
486     } catch (BindingException e) {
487       String err = "Unable to load a binding file due to the following:\n" + e.getMessage()
488           + "\nThe Source Generator will continue with no binding file.";
489       _dialog.notify(err);
490     }
491   }
492 
493   /**
494    * Sets the binding to use given an InputSource identifying a Castor Binding File.
495    *
496    * @param source an InputSource identifying a Castor Binding File.
497    */
498   public final void setBinding(final InputSource source) {
499     try {
500       ExtendedBinding binding = BindingLoader.createBinding(source);
501       setBinding(binding);
502     } catch (BindingException e) {
503       String err = "unable to load a binding file due to the following:\n" + e.getMessage()
504           + "\nThe Source Generator will continue with no binding file.";
505       _dialog.notify(err);
506     }
507   }
508 
509   /**
510    * Sets the line separator to use when printing the source code.
511    * <p>
512    * <B>Note:</B>This can be any string, so be careful. I recommend either using the default or
513    * using one of the following:
514    *
515    * <PRE>
516    * windows systems use: "\r\n"
517    * unix systems use: "\n"
518    * mac systems use: "\r"
519    * </PRE>
520    *
521    * @param lineSeparator the line separator to use when printing the source code. This method is
522    *        useful if you are generating source on one platform, but will be compiling the source on
523    *        a different platform.
524    */
525   public final void setLineSeparator(final String lineSeparator) {
526     _singleClassGenerator.setLineSeparator(lineSeparator);
527   } // -- setLineSeparator
528 
529   /**
530    * Tests the org.exolab.castor.builder.javaclassmapping property for the 'element' value.
531    *
532    * @return True if the Source Generator is mapping schema elements to Java classes.
533    */
534   public final boolean mappingSchemaElement2Java() {
535     if (_bindingComponent != null) {
536       ExtendedBinding binding = _bindingComponent.getBinding();
537       if (binding != null) {
538         BindingType type = binding.getDefaultBindingType();
539         if (type != null) {
540           return (type == BindingType.ELEMENT);
541         }
542       }
543     }
544     return super.mappingSchemaElement2Java();
545   } // -- mappingSchemaElement2Java
546 
547   /**
548    * Tests the org.exolab.castor.builder.javaclassmapping property for the 'type' value.
549    *
550    * @return True if the Source Generator is mapping schema types to Java classes.
551    */
552   public final boolean mappingSchemaType2Java() {
553     if (_bindingComponent != null) {
554       ExtendedBinding binding = _bindingComponent.getBinding();
555       if (binding != null) {
556         BindingType type = binding.getDefaultBindingType();
557         if (type != null) {
558           return (type == BindingType.TYPE);
559         }
560       }
561     }
562     return super.mappingSchemaType2Java();
563   } // -- mappingSchemaType2Java
564 
565   /**
566    * Creates Java Source code (Object model) for the given XML Schema. If the file exists, opens a
567    * FileReader and passes control to {@link #generateSource(InputSource, String)}.
568    *
569    * @param filename the full path to the XML Schema definition
570    * @param packageName the package for the generated source files
571    * @throws IOException if an IOException occurs writing the new source files
572    */
573   public final void generateSource(final String filename, final String packageName)
574       throws IOException {
575     final File schemaFile;
576     if (filename.startsWith("./")) {
577       schemaFile = new File(filename.substring(2));
578     } else {
579       schemaFile = new File(filename);
580     }
581 
582     FileReader reader = new FileReader(schemaFile);
583 
584     try {
585       InputSource source = new InputSource(reader);
586       source.setSystemId(toURIRepresentation(schemaFile.getAbsolutePath()));
587       generateSource(source, packageName);
588     } finally {
589       try {
590         reader.close();
591       } catch (java.io.IOException iox) {
592         // ignore
593       }
594     }
595   } // -- generateSource
596 
597   /**
598    * Creates Java Source code (Object model) for the given XML Schema. This method just passes
599    * control to {@link #generateSource(InputSource, String)}.
600    *
601    * @param reader the Reader with which to read the XML Schema definition. The caller should close
602    *        the reader, since thie method will not do so.
603    * @param packageName the package for the generated source files
604    * @throws IOException if an IOException occurs writing the new source files
605    */
606   public final void generateSource(final Reader reader, final String packageName)
607       throws IOException {
608     InputSource source = new InputSource(reader);
609     generateSource(source, packageName);
610   } // -- generateSource
611 
612   /**
613    * Creates Java Source code (Object model) for the given XML Schema. Parses the schema provided by
614    * the InputSource and then calls {@link #generateSource(Schema, String)} to actually generate the
615    * source.
616    *
617    * @param source - the InputSource representing the XML schema.
618    * @param packageName the package for the generated source files
619    * @throws IOException if an IOException occurs writing the new source files
620    */
621   public void generateSource(final InputSource source, final String packageName)
622       throws IOException {
623     // -- get default parser from Configuration
624     Parser parser = null;
625     try {
626       parser = _internalContext.getParser();
627     } catch (RuntimeException rte) {
628       // ignore
629     }
630 
631     if (parser == null) {
632       _dialog.notify("fatal error: unable to create SAX parser.");
633       return;
634     }
635 
636     SchemaContext schemaContext = new SchemaContextImpl();
637     SchemaUnmarshaller schemaUnmarshaller = null;
638     try {
639       schemaUnmarshaller = new SchemaUnmarshaller(schemaContext);
640     } catch (XMLException e) {
641       // --The default constructor cannot throw exception so this should never happen
642       // --just log the exception
643       e.printStackTrace();
644       System.exit(1);
645     }
646 
647     Sax2ComponentReader handler = new Sax2ComponentReader(schemaUnmarshaller);
648     parser.setDocumentHandler(handler);
649     parser.setErrorHandler(handler);
650 
651     try {
652       parser.parse(source);
653     } catch (java.io.IOException ioe) {
654       _dialog.notify("error reading XML Schema file");
655       if (_failOnFirstError) {
656         throw ioe;
657       }
658       return;
659     } catch (org.xml.sax.SAXException sx) {
660       Exception except = sx.getException();
661       if (except == null) {
662         except = sx;
663       }
664 
665       if (except instanceof SAXParseException) {
666         SAXParseException spe = (SAXParseException) except;
667         _dialog.notify("SAXParseException: " + spe);
668         _dialog.notify(" - occured at line ");
669         _dialog.notify(Integer.toString(spe.getLineNumber()));
670         _dialog.notify(", column ");
671         _dialog.notify(Integer.toString(spe.getColumnNumber()));
672       } else {
673         except.printStackTrace();
674       }
675       if (_failOnFirstError) {
676         String msg = "Source Generator: schema parser threw an Exception";
677         throw new CastorRuntimeException(msg, sx);
678       }
679       return;
680     }
681 
682     Schema schema = schemaUnmarshaller.getSchema();
683 
684     try {
685       schema.validate();
686     } catch (ValidationException vx) {
687       throw new NestedIOException(vx);
688     }
689 
690     generateSource(schema, packageName);
691   } // -- generateSource
692 
693   /**
694    * Creates Java Source code (Object model) for the given XML Schema. Convenience methods exist if
695    * you don't have a {@link org.exolab.castor.xml.schema.Schema} already parsed.
696    *
697    * @param schema the XML schema to generate the Java sources for.
698    * @param packageName the package for the generated source files.
699    * @throws IOException if this Exception occurs while generating source
700    * @see #generateSource(String, String) to provide the schema filename
701    * @see #generateSource(Reader, String) to provide a Reader for the schema
702    * @see #generateSource(InputSource, String) to provide an InputSource for the schema
703    */
704   public final void generateSource(final Schema schema, final String packageName)
705       throws IOException {
706     if (schema == null) {
707       throw new IllegalArgumentException("The argument 'schema' must not be null.");
708     }
709 
710     // --make sure the XML Schema is valid
711     try {
712       schema.validate();
713     } catch (ValidationException ve) {
714       String err =
715           "The schema: " + schema.getSchemaLocation() + " is not valid.\n" + ve.getMessage();
716       throw new IllegalArgumentException(err);
717     }
718 
719     // Now that we're ready to generate source and we know our configuration
720     // has been fully parsed, create our SourceFactory. (See CASTOR-1346.)
721     // We will reuse this SourceFactory if we are invoked multiple times.
722     if (_sourceFactory == null) {
723       _sourceFactory = new SourceFactory(this, _infoFactory, _groupNaming, this);
724       _sourceFactory.setCreateMarshalMethods(_createMarshalMethods);
725       _sourceFactory.setTestable(_testable);
726       _sourceFactory.setSAX1(_sax1);
727       _sourceFactory.setCaseInsensitive(_caseInsensitive);
728     }
729 
730     _sInfo = new SGStateInfo(schema, this);
731     _sInfo.setPackageName(packageName);
732     _sInfo.setDialog(_dialog);
733     _sInfo.setVerbose(_verbose);
734     _sInfo.setSuppressNonFatalWarnings(_suppressNonFatalWarnings);
735 
736     // --map the schemaLocation of the schema with the packageName defined
737     if (packageName != null) {
738       super.setLocationPackageMapping(schema.getSchemaLocation(), packageName);
739     }
740 
741     // --We start with a blank list of schemas processed
742     _schemasProcessed.clear();
743 
744     generateAllClassFiles(schema, _sInfo);
745 
746     // -- TODO Cleanup integration (what does this comment mean?)
747     if (!_createDescriptors && _generateMapping) {
748       generateMappingFile(packageName, _sInfo);
749     }
750 
751     // output statistical information from JClassRegistry in 'automatic'mode only
752     if (isAutomaticConflictResolution()) {
753       _xmlInfoRegistry.printStatistics(_bindingComponent);
754     }
755   } // -- generateSource
756 
757   // -------------------/
758   // - Private Methods -/
759   // -------------------/
760 
761   /**
762    * Generate all class files for the provided schema. Before processing the current schema, process
763    * the schemas imported by this one (unless our configuration says not to).
764    *
765    * @param schema the schema whose imported schemas to process
766    * @param sInfo source generator state information
767    * @throws IOException if this Exception occurs while processing an import schema
768    */
769   private void generateAllClassFiles(final Schema schema, final SGStateInfo sInfo)
770       throws IOException {
771     // Before processing the current schema, process its imported schemas
772     processImportedSchemas(schema, sInfo);
773 
774     // -- ** Generate code for all TOP-LEVEL structures **
775 
776     // -- register all global element names for name conflict resolution
777     for (ElementDecl element : schema.getElementDecls()) {
778       _xmlInfoRegistry.prebindGlobalElement(XPathHelper.getSchemaLocation(element));
779     }
780     for (ModelGroup modelGroup : schema.getModelGroups()) {
781       _xmlInfoRegistry.prebindGlobalElement(XPathHelper.getSchemaLocation(modelGroup));
782     }
783 
784     // -- handle all top-level element declarations
785     for (ElementDecl element : schema.getElementDecls()) {
786       createClasses(element, sInfo);
787     }
788 
789     // -- handle all top-level complextypes
790     for (ComplexType complexType : schema.getComplexTypes()) {
791       processComplexType(complexType, sInfo);
792     }
793 
794     // -- handle all top-level simpletypes
795     for (SimpleType simpleType : schema.getSimpleTypes()) {
796       processSimpleType(simpleType, sInfo);
797     }
798 
799     // -- handle all top-level groups
800     for (ModelGroup modelGroup : schema.getModelGroups()) {
801       createClasses(modelGroup, sInfo);
802     }
803 
804     // -- clean up any remaining JClasses which need printing
805     _singleClassGenerator.processIfNotAlreadyProcessed(sInfo.keys(), sInfo);
806 
807     // -- handle cdr files
808     for (Enumeration<String> cdrFiles = sInfo.getCDRFilenames(); cdrFiles.hasMoreElements();) {
809       String filename = cdrFiles.nextElement();
810       Properties props = sInfo.getCDRFile(filename);
811       final FileOutputStream fileOutputStream = new FileOutputStream(new File(filename));
812       props.store(fileOutputStream, null);
813       fileOutputStream.close();
814     }
815   } // -- createClasses
816 
817   /**
818    * Look at each schema imported by the given schema. Either warn that the invoker needs to
819    * separately generate source from that schema or process that schema, depending on settings.
820    * 
821    * @param schema the schema whose imported schemas to process
822    * @param sInfo source generator state information
823    * @throws IOException if this Exception occurs while processing an import schema
824    */
825   private void processImportedSchemas(final Schema schema, final SGStateInfo sInfo)
826       throws IOException {
827     Iterator<Schema> enumeration = schema.getImportedSchema().iterator();
828     while (enumeration.hasNext()) {
829       Schema importedSchema = enumeration.next();
830       if (!_generateImported) {
831         LOG.warn("Note: No code will be generated for the following *imported* schema: "
832             + importedSchema.getSchemaLocation() + "; if this is on intention, please "
833             + "do not forget to generate code for this schema as well. Alternatively,"
834             + "consider using the 'generateImportedSchemas' flag.");
835         continue;
836       }
837 
838       _schemasProcessed.add(schema);
839       if (!_schemasProcessed.contains(importedSchema)) {
840         SGStateInfo importedSInfo = new SGStateInfo(importedSchema, this);
841         String packageName = sInfo.getPackageName();
842         importedSInfo.setPackageName(packageName);
843 
844         // --map the schemaLocation of the imported schema with the packageName defined
845         if (packageName != null) {
846           setLocationPackageMapping(importedSchema.getSchemaLocation(), packageName);
847         }
848 
849         generateAllClassFiles(importedSchema, importedSInfo);
850 
851         // --'store' the imported JClass instances
852         sInfo.storeImportedSourcesByName(importedSInfo.getSourcesByName());
853         sInfo.storeImportedSourcesByName(importedSInfo.getImportedSourcesByName());
854         // --discard the SGStateInfo
855         importedSInfo = null;
856       }
857     }
858   }
859 
860   /**
861    * Generates the mapping file.
862    * 
863    * @param packageName Package name to be generated
864    * @param sInfo Source Generator current state
865    * @throws IOException if this Exception occurs while generating the mapping file
866    */
867   private void generateMappingFile(final String packageName, final SGStateInfo sInfo)
868       throws IOException {
869     String pkg = (packageName != null) ? packageName : "";
870     MappingRoot mapping = sInfo.getMapping(pkg);
871     if (mapping == null) {
872       return;
873     }
874 
875     FileWriter writer = new FileWriter(_mappingFilename);
876     try {
877       Marshaller marshaller = new Marshaller(writer);
878       marshaller.setSuppressNamespaces(true);
879       marshaller.marshal(mapping);
880     } catch (Exception ex) {
881       throw new NestedIOException(ex);
882     } finally {
883       writer.flush();
884       writer.close();
885     }
886   }
887 
888   /**
889    * Processes the given Element declaration and creates all necessary classes to support it.
890    *
891    * @param elementDecl the Element declaration to process
892    * @param sInfo our state information
893    * @throws IOException if this exception occurs while writing source files
894    */
895   private void createClasses(final ElementDecl elementDecl, final SGStateInfo sInfo)
896       throws IOException {
897     if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || elementDecl == null) {
898       return;
899     }
900 
901     // -- when mapping schema types, only interested in producing classes
902     // -- for elements with anonymous complex types
903     XMLType xmlType = elementDecl.getType();
904     if (mappingSchemaType2Java()) {
905       if (elementDecl.isReference() || ((xmlType != null) && (xmlType.getName() != null))) {
906         return;
907       }
908     }
909 
910     // --create component
911     _bindingComponent.setView(elementDecl);
912 
913     // -- already processed --> just return
914     ClassInfo cInfo = sInfo.resolve(elementDecl);
915     if (cInfo != null && cInfo.getJClass() != null) {
916       JClass jClass = cInfo.getJClass();
917       if (sInfo.processed(jClass)) {
918         return;
919       }
920       jClass = null;
921     }
922 
923     // -- No type definition
924     if (xmlType == null) {
925       if (sInfo.verbose()) {
926         String msg = "No type found for element: " + elementDecl.getName();
927         sInfo.getDialog().notify(msg);
928       }
929       return;
930     } else if (xmlType.isComplexType()) {
931       // -- ComplexType
932       JClass[] classes = _sourceFactory.createSourceCode(_bindingComponent, sInfo);
933       if (!_singleClassGenerator.process(classes, sInfo)) {
934         return;
935       }
936 
937       // only create classes for types that are not imported
938       if (xmlType.getSchema() == _bindingComponent.getSchema()) {
939         processComplexType((ComplexType) xmlType, sInfo);
940       }
941     } else if (xmlType.isSimpleType()) {
942       // -- SimpleType
943       processSimpleType((SimpleType) xmlType, sInfo);
944     } else {
945       // -- AnyType
946       // -- no processing needed for 'anyType'
947     }
948   } // -- createClasses
949 
950   /**
951    * Processes the given Group and creates all necessary classes to support it.
952    *
953    * @param group the Group to process
954    * @param sInfo our state information
955    * @throws IOException if this exception occurs while writing source files
956    */
957   private void createClasses(final Group group, final SGStateInfo sInfo) throws IOException {
958     if (group == null) {
959       return;
960     }
961 
962     // -- don't generate classes for empty groups
963     if (group.getParticleCount() == 0) {
964       if (group instanceof ModelGroup) {
965         ModelGroup mg = (ModelGroup) group;
966         if (mg.isReference()) {
967           mg = mg.getReference();
968           if (mg.getParticleCount() == 0) {
969             return;
970           }
971         }
972       } else {
973         return;
974       }
975     }
976 
977     _bindingComponent.setView(group);
978 
979     JClass[] classes = _sourceFactory.createSourceCode(_bindingComponent, sInfo);
980     processContentModel(group, sInfo);
981     _singleClassGenerator.process(classes, sInfo);
982   } // -- createClasses
983 
984   /**
985    * Processes the given ComplexType and creates all necessary classes to support it.
986    *
987    * @param complexType the ComplexType to process
988    * @param sInfo our state information
989    * @throws IOException if this exception occurs while writing source files
990    */
991   private void processComplexType(final ComplexType complexType, final SGStateInfo sInfo)
992       throws IOException {
993     if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || complexType == null) {
994       return;
995     }
996 
997     _bindingComponent.setView(complexType);
998 
999     ClassInfo classInfo = sInfo.resolve(complexType);
1000     if (classInfo == null) {
1001       // -- handle top-level complextypes
1002       if (complexType.isTopLevel()) {
1003         JClass[] classes = _sourceFactory.createSourceCode(_bindingComponent, sInfo);
1004         if (!_singleClassGenerator.process(classes, sInfo)) {
1005           return;
1006         }
1007       }
1008 
1009       // -- process AttributeDecl
1010       processAttributes(complexType, sInfo);
1011 
1012       // --process content type if necessary
1013       ContentType temp = complexType.getContentType();
1014       if (temp.getType() == ContentType.SIMPLE) {
1015         processSimpleType(((SimpleContent) temp).getSimpleType(), sInfo);
1016       }
1017 
1018       // -- process ContentModel
1019       processContentModel(complexType, sInfo);
1020     } else {
1021       JClass jClass = classInfo.getJClass();
1022       if (!sInfo.processed(jClass)) {
1023         // -- process AttributeDecl
1024         processAttributes(complexType, sInfo);
1025         // -- process ContentModel
1026         processContentModel(complexType, sInfo);
1027         _singleClassGenerator.process(jClass, sInfo);
1028       }
1029     }
1030   } // -- processComplexType
1031 
1032   /**
1033    * Processes the attribute declarations for the given complex type.
1034    *
1035    * @param complexType the ComplexType containing the attribute declarations to process.
1036    * @param sInfo the current source generator state information
1037    * @throws IOException if this Exception occurs while generating source files
1038    */
1039   private void processAttributes(final ComplexType complexType, final SGStateInfo sInfo)
1040       throws IOException {
1041     if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || complexType == null) {
1042       return;
1043     }
1044 
1045     Enumeration<AttributeDecl> enumeration = complexType.getAttributeDecls();
1046     while (enumeration.hasMoreElements()) {
1047       AttributeDecl attribute = enumeration.nextElement();
1048       processSimpleType(attribute.getSimpleType(), sInfo);
1049     }
1050   } // -- processAttributes
1051 
1052   /**
1053    * Processes the given ContentModelGroup.
1054    *
1055    * @param cmGroup the ContentModelGroup to process
1056    * @param sInfo the current source generator state information
1057    * @throws IOException if this Exception occurs while generating source files
1058    */
1059   private void processContentModel(final ContentModelGroup cmGroup, final SGStateInfo sInfo)
1060       throws IOException {
1061     if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || cmGroup == null) {
1062       return;
1063     }
1064 
1065     // Some special code to handle the fact that the enumerate method will simply skip
1066     // the first group is the number of particle is one
1067 
1068     Enumeration<Particle> enumeration = cmGroup.enumerate();
1069 
1070     while (enumeration.hasMoreElements()) {
1071       Structure struct = enumeration.nextElement();
1072       switch (struct.getStructureType()) {
1073         case Structure.ELEMENT:
1074           ElementDecl eDecl = (ElementDecl) struct;
1075           if (eDecl.isReference()) {
1076             continue;
1077           }
1078           createClasses(eDecl, sInfo);
1079           break;
1080         case Structure.GROUP:
1081           processContentModel((Group) struct, sInfo);
1082           // handle nested groups
1083           if (!((cmGroup instanceof ComplexType) || (cmGroup instanceof ModelGroup))) {
1084             createClasses((Group) struct, sInfo);
1085           }
1086           break;
1087         default:
1088           break;
1089       }
1090     }
1091   } // -- processContentModel
1092 
1093   /**
1094    * Handle simpleTypes.
1095    *
1096    * @param simpleType the SimpleType to be processed
1097    * @param sInfo the current source generator state information
1098    * @throws IOException if this Exception occurs while generating source files
1099    */
1100   private void processSimpleType(final SimpleType simpleType, final SGStateInfo sInfo)
1101       throws IOException {
1102     if (sInfo.getStatusCode() == SGStateInfo.STOP_STATUS || simpleType == null
1103         || simpleType.getSchema() != sInfo.getSchema()) {
1104       return;
1105     }
1106 
1107     // -- Right now the only time we actually generate source for a simpletype is
1108     // -- when it's an enumeration
1109     if (simpleType.hasFacet(Facet.ENUMERATION)) {
1110       ClassInfo classInfo = sInfo.resolve(simpleType);
1111       if (classInfo == null) {
1112         JClass jClass =
1113             _sourceFactory.createSourceCode(_bindingComponent.getBinding(), simpleType, sInfo);
1114         _singleClassGenerator.process(jClass, sInfo);
1115       } else {
1116         JClass jClass = classInfo.getJClass();
1117         _singleClassGenerator.process(jClass, sInfo);
1118       }
1119     }
1120   } // -- processSimpleType
1121 
1122   /**
1123    * Called by setBinding to fill in the mapping between namespaces and Java packages.
1124    *
1125    * @param packages the array of package element
1126    */
1127   private void processNamespaces(final PackageType[] packages) {
1128     if (packages.length == 0) {
1129       return;
1130     }
1131 
1132     for (PackageType temp : packages) {
1133       PackageTypeChoice choice = temp.getPackageTypeChoice();
1134       if (choice.getNamespace() != null) {
1135         super.setNamespacePackageMapping(choice.getNamespace(), temp.getName());
1136       } else if (choice.getSchemaLocation() != null) {
1137         // 1--Handle relative locations
1138         String tempLocation = choice.getSchemaLocation();
1139         String currentDir = System.getProperty("user.dir");
1140         currentDir = currentDir.replace('\\', '/');
1141         if (tempLocation.startsWith("./")) {
1142           tempLocation = tempLocation.substring(1);
1143           tempLocation = currentDir + tempLocation;
1144         } else if (tempLocation.startsWith("../")) {
1145           tempLocation = tempLocation.substring("../".length());
1146           int lastDir = currentDir.lastIndexOf('/');
1147           currentDir = currentDir.substring(0, lastDir + 1);
1148           tempLocation = currentDir + tempLocation;
1149         }
1150         super.setLocationPackageMapping(tempLocation, temp.getName());
1151         currentDir = null;
1152         tempLocation = null;
1153       }
1154     }
1155   }
1156 
1157   /**
1158    * Returns a string which is the URI of a file.
1159    * <ul>
1160    * <li>file:///DOSpath</li>
1161    * <li>file://UnixPath</li>
1162    * </ul>
1163    * No validation is done to check whether the file exists or not. This method will be no longer
1164    * used when the JDK URL.toString() is fixed.
1165    *
1166    * @param path The absolute path of the file.
1167    * @return A string representing the URI of the file.
1168    */
1169   public static String toURIRepresentation(final String path) {
1170     String result = path;
1171     if (!new File(result).isAbsolute()) {
1172       throw new IllegalArgumentException("The parameter must represent an absolute path.");
1173     }
1174 
1175     if (File.separatorChar != '/') {
1176       result = result.replace(File.separatorChar, '/');
1177     }
1178 
1179     if (result.startsWith("/")) {
1180       result = "file://" + result; /* Unix platform */
1181     } else {
1182       result = "file:///" + result; /* DOS platform */
1183     }
1184 
1185     return result;
1186   }
1187 
1188   /**
1189    * For backwards compability, when we are called as the main() routine, delegate the command-line
1190    * usage to the proper class.
1191    *
1192    * @param args our command line arguments.
1193    * @deprecated Please use {@link SourceGeneratorMain#main(String[])}
1194    */
1195   public static void main(final String[] args) {
1196     LOG.info("org.exolab.castor.builder.SourceGenerator.main() is deprecated. "
1197         + "Please use org.exolab.castor.builder.SourceGeneratorMain#main() instead.");
1198 
1199     SourceGeneratorMain.main(args);
1200   }
1201 
1202   /**
1203    * Returns the {@link JClassRegistry} instance associated with this source generator.
1204    * 
1205    * @return the {@link JClassRegistry} instance currently in use.
1206    */
1207   public JClassRegistry getXMLInfoRegistry() {
1208     return _xmlInfoRegistry;
1209   }
1210 
1211   /**
1212    * Sets the jclassPrinter type.
1213    * 
1214    * @param jClassPrinterType The string identifier of the printer to use.
1215    */
1216   public final void setJClassPrinterType(final String jClassPrinterType) {
1217     LOG.info("Setting JClass printing mode " + jClassPrinterType);
1218     _jclassPrinterType = jClassPrinterType;
1219     if (_singleClassGenerator != null) {
1220       _singleClassGenerator.setJClassPrinterType(jClassPrinterType);
1221     }
1222   }
1223 
1224   /**
1225    * Indicates whether classes should be created for imported XML schemas as well.
1226    * 
1227    * @return true if classes should be created for imported schemas.
1228    */
1229   public boolean getGenerateImportedSchemas() {
1230     return _generateImported;
1231   }
1232 
1233   /**
1234    * Returns the registry for {@link JClassPrinterFactory} instances.
1235    * 
1236    * @return the registry for {@link JClassPrinterFactory} instances.
1237    */
1238   public JClassPrinterFactoryRegistry getJClassPrinterFactoryRegistry() {
1239     return _jclassPrinterFactoryRegistry;
1240   }
1241 
1242 } // -- SourceGenerator