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