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.FileInputStream;
43  import java.io.IOException;
44  import java.util.Enumeration;
45  import java.util.Properties;
46  
47  import org.castor.core.constants.cpa.JDOConstants;
48  import org.exolab.castor.builder.conflictresolution.ClassNameCRStrategy;
49  import org.exolab.castor.builder.conflictresolution.ClassNameCRStrategyRegistry;
50  import org.exolab.castor.builder.descriptors.DescriptorSourceFactory;
51  import org.exolab.castor.builder.descriptors.JDOClassDescriptorFactory;
52  import org.exolab.castor.builder.factory.MappingFileSourceFactory;
53  import org.exolab.castor.builder.info.ClassInfo;
54  import org.exolab.castor.builder.info.nature.JDOClassInfoNature;
55  import org.exolab.castor.builder.info.nature.XMLInfoNature;
56  import org.exolab.castor.builder.printing.JClassPrinter;
57  import org.exolab.castor.builder.printing.JClassPrinterFactoryRegistry;
58  import org.exolab.castor.mapping.xml.MappingRoot;
59  import org.exolab.castor.util.dialog.ConsoleDialog;
60  import org.exolab.javasource.JClass;
61  import org.exolab.javasource.JComment;
62  import org.exolab.javasource.JNaming;
63  
64  /**
65   * Writes a single class (and any associated inner classes) to a file.
66   * 
67   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a> - Main author.
68   * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> - Contributions.
69   * @author <a href="mailto:nsgreen@thazar.com">Nathan Green</a> - Contributions.
70   * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a> - Separated from SourceGenerator
71   * @version $Revision: 0000 $ $Date: $
72   */
73  public final class SingleClassGenerator {
74    /**
75     * The default code header. Please leave "$" and "Id" separated with "+" so that the CVS server
76     * does not expand it here.
77     */
78    private static final String DEFAULT_HEADER = "This class was automatically generated with \n"
79        + "<a href=\"" + SourceGenerator.APP_URI + "\">" + SourceGenerator.APP_NAME + " "
80        + SourceGenerator.VERSION + "</a>, using an XML Schema.\n$" + "Id" + "$";
81  
82    /** Name of the CDR (Class Descriptor Resolver) file. */
83    private static final String CDR_FILE = ".castor.cdr";
84    /** True if the user should be prompted to overwrite when a file already exists. */
85    private boolean _promptForOverwrite = true;
86    /** Destination directory where all our output goes. */
87    private String _destDir;
88    /**
89     * Destination directory for all resource files (e.g. .castor.cdr files).
90     */
91    private String _resourceDestinationDirectory;
92    /** The line separator to use for output. */
93    private String _lineSeparator = null;
94    /** A flag indicating whether or not to create descriptors for the generated classes. */
95    private boolean _createDescriptors = true;
96  
97    /**
98     * A flag indicating whether or not to create JDO descriptors for the generated classes.
99     */
100   private boolean _createJdoDescriptors = false;
101 
102   /** The header at the top of each generated file. */
103   private final JComment _header;
104   /** Console dialog used to prompt the user when something is wrong. */
105   private final ConsoleDialog _dialog;
106 
107   /**
108    * The DescriptorSourceFactory instance.
109    */
110   private final DescriptorSourceFactory _descriptorSourceFactory;
111 
112   /**
113    * The JDOClassDescriptorFactory instance.
114    */
115   private JDOClassDescriptorFactory _jdoDescriptorSourceFactory;
116 
117   /** The MappingFileSourceFactory instance. */
118   private final MappingFileSourceFactory _mappingSourceFactory;
119   /** The SourceGenerator instance that created us. */
120   private final SourceGenerator _sourceGenerator;
121 
122   /**
123    * The class name conflict error handling strategy to use for resolving class name conflicts.
124    */
125   private ClassNameCRStrategy _conflictStrategy;
126 
127   /**
128    * The implementation of {@link JClassPrinter} to use for generating the Java classes and writing
129    * them to the file system.
130    */
131   private JClassPrinter _jClassPrinter;
132 
133   /**
134    * The registry for {@link ClassNameCRStrategy} implementations.
135    */
136   private ClassNameCRStrategyRegistry _classNameConflictResolutionStrategyRegistry;
137 
138 
139   /**
140    * Creates an instance of this class.
141    * 
142    * @param dialog A ConsoleDialog instance
143    * @param sourceGenerator A SourceGenerator instance
144    * @param conflictStrategyType Type of the {@link ClassNameCRStrategy} instance to be used.
145    * @param jClassPrinterType The string representation of the printer to be used,
146    */
147   public SingleClassGenerator(final ConsoleDialog dialog, final SourceGenerator sourceGenerator,
148       final String conflictStrategyType, final String jClassPrinterType) {
149     this._dialog = dialog;
150     this._sourceGenerator = sourceGenerator;
151     this._header = new JComment(JComment.HEADER_STYLE);
152     this._descriptorSourceFactory = new DescriptorSourceFactory(_sourceGenerator);
153     this._jdoDescriptorSourceFactory = new JDOClassDescriptorFactory(_sourceGenerator);
154     this._mappingSourceFactory = new MappingFileSourceFactory(_sourceGenerator);
155 
156     final String strategy =
157         sourceGenerator.getProperty(BuilderConfiguration.Property.NAME_CONFLICT_STRATEGIES, "");
158     this._classNameConflictResolutionStrategyRegistry = new ClassNameCRStrategyRegistry(strategy);
159     createNameConflictStrategy(conflictStrategyType);
160     createJClassPrinter(jClassPrinterType);
161   }
162 
163   /**
164    * Creates a JClassPrinter instance from the given string key.
165    * 
166    * @param classPrinterType The string identifier if the printer,
167    */
168   private void createJClassPrinter(final String classPrinterType) {
169     JClassPrinterFactoryRegistry registry = _sourceGenerator.getJClassPrinterFactoryRegistry();
170     this._jClassPrinter = registry.getJClassPrinterFactory(classPrinterType).getJClassPrinter();
171   }
172 
173   /**
174    * Sets the type of the {@link JClassPrinter} instance to be used for {@link JClass} writing.
175    * 
176    * @param jclassPrinterType The string identifier if the printer,
177    */
178   public void setJClassPrinterType(final String jclassPrinterType) {
179     this.createJClassPrinter(jclassPrinterType);
180   }
181 
182   /**
183    * Sets the destination directory.
184    *
185    * @param destDir the destination directory.
186    */
187   public void setDestDir(final String destDir) {
188     _destDir = destDir;
189     if (_resourceDestinationDirectory == null) {
190       _resourceDestinationDirectory = destDir;
191     }
192   }
193 
194   /**
195    * Sets the destination directory for generated resources.
196    *
197    * @param destDir the destination directory.
198    */
199   public void setResourceDestinationDirectory(final String destinationDirectory) {
200     _resourceDestinationDirectory = destinationDirectory;
201   }
202 
203   /**
204    * Sets the line separator to use when printing the source code.
205    *
206    * @param lineSeparator the line separator to use when printing the source code. This method is
207    *        useful if you are generating source on one platform, but will be compiling the source on
208    *        a different platform. <B>Note:</B>This can be any string, so be careful. I recommend
209    *        either using the default or using one of the following:
210    * 
211    *        <PRE>
212    * windows systems use: "\r\n"
213    * unix systems use: "\n"
214    * mac systems use: "\r"
215    *        </PRE>
216    */
217   public void setLineSeparator(final String lineSeparator) {
218     _lineSeparator = lineSeparator;
219   } // -- setLineSeparator
220 
221   /**
222    * Sets whether or not to create ClassDescriptors for the generated classes. By default,
223    * descriptors are generated.
224    *
225    * @param createDescriptors a boolean, when true indicates to generated ClassDescriptors
226    */
227   public void setDescriptorCreation(final boolean createDescriptors) {
228     _createDescriptors = createDescriptors;
229   } // -- setDescriptorCreation
230 
231 
232   /**
233    * Sets whether or not to create JDOClassDescriptors for the generated classes. By default,
234    * descriptors are generated.
235    * 
236    * @param createJdoDescriptors if true, JDOClassDescriptors are generated.
237    */
238   public void setJdoDescriptorCreation(final boolean createJdoDescriptors) {
239     _createJdoDescriptors = createJdoDescriptors;
240   }
241 
242   /**
243    * Sets whether or not to prompt when we would otherwise overwrite an existing JClass. If set to
244    * false, then it is always OK to overwrite an existing class. If set to true, the user will be
245    * prompted.
246    *
247    * @param promptForOverwrite the new value
248    */
249   public void setPromptForOverwrite(final boolean promptForOverwrite) {
250     this._promptForOverwrite = promptForOverwrite;
251   } // -- setPromptForOverwrite
252 
253   /**
254    * Processes the JClass mapped by the provided key unless the JClass has already been processed.
255    *
256    * @param state SourceGenerator state
257    * @param classKeys Enumeration over a collection of keys to ClassInfos
258    *
259    * @return true if processing is allowed to continue, false if the SourceGenerator state is
260    *         STOP_STATUS,
261    * @throws IOException If an already existing '.castor.cdr' file can not be loaded or found
262    */
263   boolean processIfNotAlreadyProcessed(final Enumeration<?> classKeys, final SGStateInfo state)
264       throws IOException {
265     while (classKeys.hasMoreElements()) {
266       ClassInfo classInfo = state.resolve(classKeys.nextElement());
267       JClass jClass = classInfo.getJClass();
268       if (!state.processed(jClass)
269           && (inCurrentSchema(state, classInfo) || _sourceGenerator.getGenerateImportedSchemas())) {
270         process(jClass, state);
271         if (state.getStatusCode() == SGStateInfo.STOP_STATUS) {
272           return false;
273         }
274       }
275     }
276     return true;
277   }
278 
279   /**
280    * Indicates whether {@link ClassInfo} instance is defined within target namespace.
281    * 
282    * @param state The Sourcegenerator state.
283    * @param classInfo The {@link ClassInfo} instance to be analyzed.
284    * @return True if it's within the targetNamespace
285    */
286   private boolean inCurrentSchema(final SGStateInfo state, final ClassInfo classInfo) {
287     final String targetNamespace = state.getSchema().getTargetNamespace();
288     boolean inCurrentSchema = true;
289     if (targetNamespace != null) {
290       if (classInfo.hasNature(XMLInfoNature.class.getName())) {
291         XMLInfoNature xmlNature = new XMLInfoNature(classInfo);
292         inCurrentSchema = targetNamespace.equals(xmlNature.getNamespaceURI());
293       }
294     }
295     return inCurrentSchema;
296   }
297 
298   /**
299    * Processes the given JClasses, one by one, stopping if the SourceGenerator state indicates STOP
300    * after processing one class.
301    *
302    * @param classes Array of classes to process
303    * @param state SourceGenerator state
304    * @return true if processing is allowed to continue, false if the SourceGenerator state is
305    *         STOP_STATUS,
306    * @throws IOException If an already existing '.castor.cdr' file can not be loaded or found
307    */
308   boolean process(final JClass[] classes, final SGStateInfo state) throws IOException {
309     for (JClass jClass : classes) {
310       process(jClass, state);
311       if (state.getStatusCode() == SGStateInfo.STOP_STATUS) {
312         return false;
313       }
314     }
315     return true;
316   }
317 
318   /**
319    * Processes the given JClass by checking for class name conflicts, and if there are none, making
320    * the class as processed and then printing the class and, if appropriate, its class descriptors.
321    * <p>
322    * If there is a class name conflict, at best the user stops the source generation and at worst
323    * the user continues, skipping this class.
324    *
325    * @param jClass the class to process
326    * @param state SourceGenerator state
327    * @return true if processing is allowed to continue, false if the SourceGenerator state is
328    *         STOP_STATUS,
329    * @throws IOException If an already existing '.castor.cdr' file can not be loaded or found
330    */
331   boolean process(final JClass jClass, final SGStateInfo state) throws IOException {
332     if (state.getStatusCode() == SGStateInfo.STOP_STATUS) {
333       return false;
334     }
335 
336     if (state.processed(jClass)) {
337       return true;
338     }
339 
340     // --Make sure this class's name doesn't conflict with a java.lang.* class
341     checkNameNotReserved(jClass.getName(), state);
342 
343     ClassInfo classInfo = state.resolve(jClass);
344 
345     // -- Have we already processed a class with this name?
346     JClass conflict = state.getProcessed(jClass.getName());
347     if (conflict != null && !state.getSuppressNonFatalWarnings()) {
348       SGStateInfo stateAfterResolution =
349           _conflictStrategy.dealWithClassNameConflict(state, classInfo, conflict);
350       return stateAfterResolution.getStatusCode() != SGStateInfo.STOP_STATUS;
351     }
352 
353     // -- Mark the current class as processed
354     state.markAsProcessed(jClass);
355 
356     // -- Print the class
357     if (checkAllowPrinting(jClass)) {
358       // hack for the moment
359       // to avoid the compiler complaining with java.util.Date
360       jClass.removeImport("org.exolab.castor.types.Date");
361       jClass.setHeader(_header);
362       if (_lineSeparator == null) {
363         _lineSeparator = System.getProperty("line.separator");
364       }
365       _jClassPrinter.printClass(jClass, _destDir, _lineSeparator, DEFAULT_HEADER);
366     }
367 
368     // -- Process and print the class descriptors
369     if (classInfo != null) {
370       processClassDescriptor(jClass, state, classInfo);
371       if (classInfo.hasNature(JDOClassInfoNature.class.getName())) {
372         processJDOClassDescriptor(jClass, state, classInfo);
373       }
374     }
375 
376     return state.getStatusCode() != SGStateInfo.STOP_STATUS;
377   } // -- processJClass
378 
379   /**
380    * Processes the Class Descriptor for the provided JClass.
381    *
382    * @param jClass the classInfo to process
383    * @param state SourceGenerator state
384    * @param classInfo the XML Schema element declaration
385    * @throws IOException If an already existing '.castor.cdr' file can not be loaded or found
386    */
387   private void processClassDescriptor(final JClass jClass, final SGStateInfo state,
388       final ClassInfo classInfo) throws IOException {
389     if (_createDescriptors) {
390       JClass desc = _descriptorSourceFactory.createSource(classInfo);
391       if (checkAllowPrinting(desc)) {
392         updateCDRFile(jClass, desc, state, CDR_FILE);
393         desc.setHeader(_header);
394         if (_lineSeparator == null) {
395           _lineSeparator = System.getProperty("line.separator");
396         }
397         _jClassPrinter.printClass(desc, _destDir, _lineSeparator, DEFAULT_HEADER);
398       }
399     } else {
400       // TODO cleanup mapping file integration (what does this TODO mean?)
401       // create a class mapping
402       String pkg = state.getPackageName();
403       if (pkg == null) {
404         pkg = "";
405       }
406       MappingRoot mapping = state.getMapping(pkg);
407       if (mapping == null) {
408         mapping = new MappingRoot();
409         state.setMapping(pkg, mapping);
410       }
411       mapping.addClassMapping(_mappingSourceFactory.createMapping(classInfo));
412     }
413   }
414 
415   /**
416    * Process/generate JDOClassDescriptors for the given {@link ClassInfo}.
417    * 
418    * @param jClass a structure to represent Java Source Files. See {@link JClass} for details.
419    * @param state the state of the SourceGenerator.
420    * @param classInfo the object holding all necessary information to generate the source code for
421    *        the JDOClassDescriptor.
422    * @throws IOException If an already existing '.castor.cdr' file can not be loaded or found
423    */
424   private void processJDOClassDescriptor(final JClass jClass, final SGStateInfo state,
425       final ClassInfo classInfo) throws IOException {
426 
427     if (_createJdoDescriptors) {
428       JClass desc = _jdoDescriptorSourceFactory.createSource(classInfo);
429       if (checkAllowPrinting(desc)) {
430         updateCDRFile(jClass, desc, state, JDOConstants.PKG_CDR_LIST_FILE);
431         desc.setHeader(_header);
432         if (_lineSeparator == null) {
433           _lineSeparator = System.getProperty("line.separator");
434         }
435         _jClassPrinter.printClass(desc, _destDir, _lineSeparator, DEFAULT_HEADER);
436       }
437     }
438   }
439 
440   /**
441    * Checks to see if we will write the provided JClass to disk. If we have been configured to not
442    * prompt for overwrite, then it is assumed and overwriting an existing file is always OK. If the
443    * file does not exist, it is always OK to write it. Only if we are configured to prompt for
444    * overwrite and the file already exists do we need to issue a dialog and get the user's
445    * permission.
446    *
447    * @param jClass a JClass to check to see if we can write
448    * @return true if we can write out the provided jClass
449    */
450   private boolean checkAllowPrinting(final JClass jClass) {
451     if (!_promptForOverwrite) {
452       return true;
453     }
454 
455     // Check whether there exists already a file with the same name;
456     // if not, it is OK to write (aka create) the (new) file
457     String filename = jClass.getFilename(_destDir);
458     File file = new File(filename);
459 
460     if (!file.exists()) {
461       return true;
462     }
463 
464     return _conflictStrategy.dealWithFileOverwrite(filename);
465   }
466 
467   /**
468    * Checks the given name against various naming conflicts. If a conflict is found, then this
469    * method generates an appropriate error message and throws an IllegalArgumentException.
470    * 
471    * @param elementName element name to check against lists of reserved names
472    * @param sInfo source generator state
473    */
474   private void checkNameNotReserved(final String elementName, final SGStateInfo sInfo) {
475     if (elementName == null) {
476       return;
477     }
478 
479     String nameToCompare = elementName.substring(0, 1).toUpperCase() + elementName.substring(1);
480     if (JNaming.isInJavaLang(nameToCompare)) {
481       String err = "'" + nameToCompare
482           + "' conflicts with a class in java.lang.* and may cause a conflict during\n"
483           + " compilation. If you get this complaint during compilation, you need to\n"
484           + " use a mapping file or change the name of the schema element.";
485       sInfo.getDialog().notify(err);
486     }
487 
488     if (JNaming.isReservedByCastor(nameToCompare)) {
489       String warn = "'" + nameToCompare + "' might conflict with a field name used"
490           + " by Castor.  If you get a complaint\nabout a duplicate name, you will"
491           + " need to use a mapping file or change\nthe name of the conflicting"
492           + " schema element.";
493       sInfo.getDialog().notify(warn);
494     }
495 
496     final String withoutPackage = nameToCompare.substring(nameToCompare.lastIndexOf('.') + 1);
497     if (JNaming.isReservedByWindows(nameToCompare) || JNaming.isReservedByWindows(withoutPackage)) {
498       // FIXME We should fail under Windows and warn under other OSes
499       String warn = "'" + nameToCompare + "' is reserved by the Windows filesystem and"
500           + " cannot be\nused as a class name.  Windows will not allow you to create"
501           + " a file with this\nname.  You will have to use a binding file or change"
502           + " the name of the conflicting\nschema element.  For more information,"
503           + " see\nhttp://msdn.microsoft.com/library/default.asp?"
504           + "url=/library/en-us/fileio/fs/naming_a_file.asp";
505       sInfo.getDialog().notify(warn);
506     }
507   }
508 
509   /**
510    * Updates the CDR (ClassDescriptorResolver) file with the classname->descriptor mapping.
511    *
512    * @param jClass JClass instance describing the entity class
513    * @param jDesc JClass instance describing is *Descriptor class
514    * @param sInfo state info
515    * @param cdrFileName the filename of the class descriptor resolver (cdr) file
516    * @throws IOException If an already existing '.castor.cdr' file can not be found or loaded
517    */
518   private void updateCDRFile(final JClass jClass, final JClass jDesc, final SGStateInfo sInfo,
519       final String cdrFileName) throws IOException {
520     String entityFilename = jClass.getFilename(_resourceDestinationDirectory);
521     File file = new File(entityFilename);
522     File parentDirectory = file.getParentFile();
523     File cdrFile = new File(parentDirectory, cdrFileName);
524     String cdrFilename = cdrFile.getAbsolutePath();
525 
526     Properties props = sInfo.getCDRFile(cdrFilename);
527 
528     if (props == null) {
529       // check for existing .castor.xml file
530       props = new Properties();
531       if (cdrFile.exists()) {
532         try (FileInputStream fileStream = new FileInputStream(cdrFile)) {
533           props.load(fileStream);
534         }
535       }
536       sInfo.setCDRFile(cdrFilename, props);
537     }
538     props.setProperty(jClass.getName(), jDesc.getName());
539   } // -- updateCDRFile
540 
541   /**
542    * Sets the desired {@link ClassNameCRStrategy} instance type to be used for name conflict
543    * resolution.
544    * 
545    * @param nameConflictStrategy the desired {@link ClassNameCRStrategy} instance type
546    */
547   public void setNameConflictStrategy(final String nameConflictStrategy) {
548     createNameConflictStrategy(nameConflictStrategy);
549   }
550 
551   /**
552    * Creates a new {@link ClassNameCRStrategy} instance by calling the
553    * {@link ClassNameConflictResolutionStrategyFactory}.
554    * 
555    * @param nameConflictStrategy The desired {@link ClassNameCRStrategy} type.
556    */
557   private void createNameConflictStrategy(final String nameConflictStrategy) {
558     this._conflictStrategy = _classNameConflictResolutionStrategyRegistry
559         .getClassNameConflictResolutionStrategy(nameConflictStrategy);
560     this._conflictStrategy.setConsoleDialog(_dialog);
561     this._conflictStrategy.setSingleClassGenerator(this);
562   }
563 
564   /**
565    * Returns the {@link SourceGenerator} instance that created this class.
566    * 
567    * @return the {@link SourceGenerator} instance that created this class.
568    */
569   public SourceGenerator getSourceGenerator() {
570     return _sourceGenerator;
571   }
572 
573 }