/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Intalio, Inc. For written permission,
* please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
* nor may "Exolab" appear in their names without prior written
* permission of Intalio, Inc. Exolab is a registered
* trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
* (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
*
* This file was originally developed by Keith Visco during the
* course of employment at Intalio Inc.
* All portions of this file developed by Keith Visco after Jan 19 2005 are
* Copyright (C) 2005 Keith Visco. All Rights Reserved.
*
* $Id: $
*/
package org.exolab.castor.builder;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import org.castor.core.constants.cpa.JDOConstants;
import org.exolab.castor.builder.conflictresolution.ClassNameCRStrategy;
import org.exolab.castor.builder.conflictresolution.ClassNameCRStrategyRegistry;
import org.exolab.castor.builder.descriptors.DescriptorSourceFactory;
import org.exolab.castor.builder.descriptors.JDOClassDescriptorFactory;
import org.exolab.castor.builder.factory.MappingFileSourceFactory;
import org.exolab.castor.builder.info.ClassInfo;
import org.exolab.castor.builder.info.nature.JDOClassInfoNature;
import org.exolab.castor.builder.info.nature.XMLInfoNature;
import org.exolab.castor.builder.printing.JClassPrinter;
import org.exolab.castor.builder.printing.JClassPrinterFactoryRegistry;
import org.exolab.castor.mapping.xml.MappingRoot;
import org.exolab.castor.util.dialog.ConsoleDialog;
import org.exolab.javasource.JClass;
import org.exolab.javasource.JComment;
import org.exolab.javasource.JNaming;
/**
* Writes a single class (and any associated inner classes) to a file.
* @author <a href="mailto:kvisco@intalio.com">Keith Visco</a> - Main author.
* @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> - Contributions.
* @author <a href="mailto:nsgreen@thazar.com">Nathan Green</a> - Contributions.
* @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a> - Separated from SourceGenerator
* @version $Revision: 0000 $ $Date: $
*/
public final class SingleClassGenerator {
/**
* The default code header. Please leave "$" and "Id" separated with "+" so
* that the CVS server does not expand it here. */
private static final String DEFAULT_HEADER = "This class was automatically generated with \n"
+ "<a href=\"" + SourceGenerator.APP_URI + "\">"
+ SourceGenerator.APP_NAME + " "
+ SourceGenerator.VERSION
+ "</a>, using an XML Schema.\n$" + "Id" + "$";
/** Name of the CDR (Class Descriptor Resolver) file. */
private static final String CDR_FILE = ".castor.cdr";
/** True if the user should be prompted to overwrite when a file already exists. */
private boolean _promptForOverwrite = true;
/** Destination directory where all our output goes. */
private String _destDir;
/**
* Destination directory for all resource files (e.g. .castor.cdr files).
*/
private String _resourceDestinationDirectory;
/** The line separator to use for output. */
private String _lineSeparator = null;
/** A flag indicating whether or not to create descriptors for the generated classes. */
private boolean _createDescriptors = true;
/**
* A flag indicating whether or not to create JDO descriptors for
* the generated classes.
*/
private boolean _createJdoDescriptors = false;
/** The header at the top of each generated file. */
private final JComment _header;
/** Console dialog used to prompt the user when something is wrong. */
private final ConsoleDialog _dialog;
/**
* The DescriptorSourceFactory instance.
*/
private final DescriptorSourceFactory _descriptorSourceFactory;
/**
* The JDOClassDescriptorFactory instance.
*/
private JDOClassDescriptorFactory _jdoDescriptorSourceFactory;
/** The MappingFileSourceFactory instance. */
private final MappingFileSourceFactory _mappingSourceFactory;
/** The SourceGenerator instance that created us. */
private final SourceGenerator _sourceGenerator;
/**
* The class name conflict error handling strategy to use for
* resolving class name conflicts.
*/
private ClassNameCRStrategy _conflictStrategy;
/**
* The implementation of {@link JClassPrinter} to use for generating the
* Java classes and writing them to the file system.
*/
private JClassPrinter _jClassPrinter;
/**
* The registry for {@link ClassNameCRStrategy} implementations.
*/
private ClassNameCRStrategyRegistry _classNameConflictResolutionStrategyRegistry;
/**
* Creates an instance of this class.
* @param dialog A ConsoleDialog instance
* @param sourceGenerator A SourceGenerator instance
* @param conflictStrategyType Type of the {@link ClassNameCRStrategy} instance to be used.
* @param jClassPrinterType The string representation of the printer to be used,
*/
public SingleClassGenerator(final ConsoleDialog dialog,
final SourceGenerator sourceGenerator,
final String conflictStrategyType,
final String jClassPrinterType) {
this._dialog = dialog;
this._sourceGenerator = sourceGenerator;
this._header = new JComment(JComment.HEADER_STYLE);
this._descriptorSourceFactory = new DescriptorSourceFactory(_sourceGenerator);
this._jdoDescriptorSourceFactory = new JDOClassDescriptorFactory(_sourceGenerator);
this._mappingSourceFactory = new MappingFileSourceFactory(_sourceGenerator);
final String strategy = sourceGenerator.getProperty(
BuilderConfiguration.Property.NAME_CONFLICT_STRATEGIES, "");
this._classNameConflictResolutionStrategyRegistry
= new ClassNameCRStrategyRegistry(strategy);
createNameConflictStrategy(conflictStrategyType);
createJClassPrinter(jClassPrinterType);
}
/**
* Creates a JClassPrinter instance from the given string key.
* @param classPrinterType The string identifier if the printer,
*/
private void createJClassPrinter(final String classPrinterType) {
JClassPrinterFactoryRegistry registry = _sourceGenerator.getJClassPrinterFactoryRegistry();
this._jClassPrinter =
registry.getJClassPrinterFactory(classPrinterType).getJClassPrinter();
}
/**
* Sets the type of the {@link JClassPrinter} instance to be used for {@link JClass} writing.
* @param jclassPrinterType The string identifier if the printer,
*/
public void setJClassPrinterType(final String jclassPrinterType) {
this.createJClassPrinter(jclassPrinterType);
}
/**
* Sets the destination directory.
*
* @param destDir the destination directory.
*/
public void setDestDir(final String destDir) {
_destDir = destDir;
if (_resourceDestinationDirectory == null) {
_resourceDestinationDirectory = destDir;
}
}
/**
* Sets the destination directory for generated resources.
*
* @param destDir the destination directory.
*/
public void setResourceDestinationDirectory(final String destinationDirectory) {
_resourceDestinationDirectory = destinationDirectory;
}
/**
* Sets the line separator to use when printing the source code.
*
* @param lineSeparator
* the line separator to use when printing the source code. This
* method is useful if you are generating source on one platform,
* but will be compiling the source on a different platform.
* <B>Note:</B>This can be any string, so be careful. I
* recommend either using the default or using one of the
* following:
* <PRE>
* windows systems use: "\r\n"
* unix systems use: "\n"
* mac systems use: "\r"
* </PRE>
*/
public void setLineSeparator(final String lineSeparator) {
_lineSeparator = lineSeparator;
} //-- setLineSeparator
/**
* Sets whether or not to create ClassDescriptors for the generated classes.
* By default, descriptors are generated.
*
* @param createDescriptors
* a boolean, when true indicates to generated ClassDescriptors
*/
public void setDescriptorCreation(final boolean createDescriptors) {
_createDescriptors = createDescriptors;
} //-- setDescriptorCreation
/**
* Sets whether or not to create JDOClassDescriptors for the generated classes.
* By default, descriptors are generated.
*
* @param createJdoDescriptors if true, JDOClassDescriptors are generated.
*/
public void setJdoDescriptorCreation(final boolean createJdoDescriptors) {
_createJdoDescriptors = createJdoDescriptors;
}
/**
* Sets whether or not to prompt when we would otherwise overwrite an
* existing JClass. If set to false, then it is always OK to overwrite
* an existing class. If set to true, the user will be prompted.
*
* @param promptForOverwrite the new value
*/
public void setPromptForOverwrite(final boolean promptForOverwrite) {
this._promptForOverwrite = promptForOverwrite;
} //-- setPromptForOverwrite
/**
* Processes the JClass mapped by the provided key unless the JClass has
* already been processed.
*
* @param state SourceGenerator state
* @param classKeys Enumeration over a collection of keys to ClassInfos
*
* @return true if processing is allowed to continue, false if the
* SourceGenerator state is STOP_STATUS,
* @throws IOException If an already existing '.castor.cdr' file can not be
* loaded or found
*/
boolean processIfNotAlreadyProcessed(final Enumeration<?> classKeys,
final SGStateInfo state) throws IOException {
while (classKeys.hasMoreElements()) {
ClassInfo classInfo = state.resolve(classKeys.nextElement());
JClass jClass = classInfo.getJClass();
if (!state.processed(jClass)
&& (inCurrentSchema(state, classInfo)
|| _sourceGenerator.getGenerateImportedSchemas())) {
process(jClass, state);
if (state.getStatusCode() == SGStateInfo.STOP_STATUS) {
return false;
}
}
}
return true;
}
/**
* Indicates whether {@link ClassInfo} instance is defined within target namespace.
* @param state The Sourcegenerator state.
* @param classInfo The {@link ClassInfo} instance to be analyzed.
* @return True if it's within the targetNamespace
*/
private boolean inCurrentSchema(final SGStateInfo state, final ClassInfo classInfo) {
final String targetNamespace = state.getSchema().getTargetNamespace();
boolean inCurrentSchema = true;
if (targetNamespace != null) {
if (classInfo.hasNature(XMLInfoNature.class.getName())) {
XMLInfoNature xmlNature = new XMLInfoNature(classInfo);
inCurrentSchema = targetNamespace.equals(xmlNature.getNamespaceURI());
}
}
return inCurrentSchema;
}
/**
* Processes the given JClasses, one by one, stopping if the SourceGenerator
* state indicates STOP after processing one class.
*
* @param classes Array of classes to process
* @param state SourceGenerator state
* @return true if processing is allowed to continue, false if the
* SourceGenerator state is STOP_STATUS,
* @throws IOException If an already existing '.castor.cdr' file can not be
* loaded or found
*/
boolean process(final JClass[] classes, final SGStateInfo state) throws IOException {
for (int i = 0; i < classes.length; i++) {
process(classes[i], state);
if (state.getStatusCode() == SGStateInfo.STOP_STATUS) {
return false;
}
}
return true;
}
/**
* Processes the given JClass by checking for class name conflicts, and if
* there are none, making the class as processed and then printing the class
* and, if appropriate, its class descriptors.
* <p>
* If there is a class name conflict, at best the user stops the source
* generation and at worst the user continues, skipping this class.
*
* @param jClass the class to process
* @param state SourceGenerator state
* @return true if processing is allowed to continue, false if the
* SourceGenerator state is STOP_STATUS,
* @throws IOException If an already existing '.castor.cdr' file can not be
* loaded or found
*/
boolean process(final JClass jClass, final SGStateInfo state) throws IOException {
if (state.getStatusCode() == SGStateInfo.STOP_STATUS) {
return false;
}
if (state.processed(jClass)) {
return true;
}
//--Make sure this class's name doesn't conflict with a java.lang.* class
checkNameNotReserved(jClass.getName(), state);
ClassInfo classInfo = state.resolve(jClass);
//-- Have we already processed a class with this name?
JClass conflict = state.getProcessed(jClass.getName());
if (conflict != null && !state.getSuppressNonFatalWarnings()) {
SGStateInfo stateAfterResolution =
_conflictStrategy.dealWithClassNameConflict(state, classInfo, conflict);
return stateAfterResolution.getStatusCode() != SGStateInfo.STOP_STATUS;
}
//-- Mark the current class as processed
state.markAsProcessed(jClass);
//-- Print the class
if (checkAllowPrinting(jClass)) {
//hack for the moment
//to avoid the compiler complaining with java.util.Date
jClass.removeImport("org.exolab.castor.types.Date");
jClass.setHeader(_header);
if (_lineSeparator == null) {
_lineSeparator = System.getProperty("line.separator");
}
_jClassPrinter.printClass(jClass, _destDir, _lineSeparator, DEFAULT_HEADER);
}
//-- Process and print the class descriptors
if (classInfo != null) {
processClassDescriptor(jClass, state, classInfo);
if (classInfo.hasNature(JDOClassInfoNature.class.getName())) {
processJDOClassDescriptor(jClass, state, classInfo);
}
}
return state.getStatusCode() != SGStateInfo.STOP_STATUS;
} //-- processJClass
/**
* Processes the Class Descriptor for the provided JClass.
*
* @param jClass the classInfo to process
* @param state SourceGenerator state
* @param classInfo the XML Schema element declaration
* @throws IOException If an already existing '.castor.cdr' file can not be
* loaded or found
*/
private void processClassDescriptor(final JClass jClass, final SGStateInfo state,
final ClassInfo classInfo) throws IOException {
if (_createDescriptors) {
JClass desc = _descriptorSourceFactory.createSource(classInfo);
if (checkAllowPrinting(desc)) {
updateCDRFile(jClass, desc, state, CDR_FILE);
desc.setHeader(_header);
if (_lineSeparator == null) {
_lineSeparator = System.getProperty("line.separator");
}
_jClassPrinter.printClass(desc, _destDir, _lineSeparator, DEFAULT_HEADER);
}
} else {
// TODO cleanup mapping file integration (what does this TODO mean?)
// create a class mapping
String pkg = state.getPackageName();
if (pkg == null) {
pkg = "";
}
MappingRoot mapping = state.getMapping(pkg);
if (mapping == null) {
mapping = new MappingRoot();
state.setMapping(pkg, mapping);
}
mapping.addClassMapping(_mappingSourceFactory.createMapping(classInfo));
}
}
/**
* Process/generate JDOClassDescriptors for the given {@link ClassInfo}.
*
* @param jClass
* a structure to represent Java Source Files. See {@link JClass}
* for details.
* @param state
* the state of the SourceGenerator.
* @param classInfo
* the object holding all necessary information to generate the
* source code for the JDOClassDescriptor.
* @throws IOException
* If an already existing '.castor.cdr' file can not be loaded
* or found
*/
private void processJDOClassDescriptor(final JClass jClass, final SGStateInfo state,
final ClassInfo classInfo) throws IOException {
if (_createJdoDescriptors) {
JClass desc = _jdoDescriptorSourceFactory.createSource(classInfo);
if (checkAllowPrinting(desc)) {
updateCDRFile(jClass, desc, state, JDOConstants.PKG_CDR_LIST_FILE);
desc.setHeader(_header);
if (_lineSeparator == null) {
_lineSeparator = System.getProperty("line.separator");
}
_jClassPrinter.printClass(desc, _destDir, _lineSeparator, DEFAULT_HEADER);
}
}
}
/**
* Checks to see if we will write the provided JClass to disk. If we have
* been configured to not prompt for overwrite, then it is assumed and
* overwriting an existing file is always OK. If the file does not exist, it
* is always OK to write it. Only if we are configured to prompt for
* overwrite and the file already exists do we need to issue a dialog and
* get the user's permission.
*
* @param jClass a JClass to check to see if we can write
* @return true if we can write out the provided jClass
*/
private boolean checkAllowPrinting(final JClass jClass) {
if (!_promptForOverwrite) {
return true;
}
// Check whether there exists already a file with the same name;
// if not, it is OK to write (aka create) the (new) file
String filename = jClass.getFilename(_destDir);
File file = new File(filename);
if (!file.exists()) {
return true;
}
return _conflictStrategy.dealWithFileOverwrite(filename);
}
/**
* Checks the given name against various naming conflicts. If a conflict is
* found, then this method generates an appropriate error message and throws
* an IllegalArgumentException.
* @param elementName element name to check against lists of reserved names
* @param sInfo source generator state
*/
private void checkNameNotReserved(final String elementName, final SGStateInfo sInfo) {
if (elementName == null) {
return;
}
String nameToCompare = elementName.substring(0, 1).toUpperCase() + elementName.substring(1);
if (JNaming.isInJavaLang(nameToCompare)) {
String err = "'" + nameToCompare
+ "' conflicts with a class in java.lang.* and may cause a conflict during\n"
+ " compilation. If you get this complaint during compilation, you need to\n"
+ " use a mapping file or change the name of the schema element.";
sInfo.getDialog().notify(err);
}
if (JNaming.isReservedByCastor(nameToCompare)) {
String warn = "'" + nameToCompare + "' might conflict with a field name used"
+ " by Castor. If you get a complaint\nabout a duplicate name, you will"
+ " need to use a mapping file or change\nthe name of the conflicting"
+ " schema element.";
sInfo.getDialog().notify(warn);
}
final String withoutPackage = nameToCompare.substring(nameToCompare.lastIndexOf('.') + 1);
if (JNaming.isReservedByWindows(nameToCompare)
|| JNaming.isReservedByWindows(withoutPackage)) {
// FIXME We should fail under Windows and warn under other OSes
String warn = "'" + nameToCompare + "' is reserved by the Windows filesystem and"
+ " cannot be\nused as a class name. Windows will not allow you to create"
+ " a file with this\nname. You will have to use a binding file or change"
+ " the name of the conflicting\nschema element. For more information,"
+ " see\nhttp://msdn.microsoft.com/library/default.asp?"
+ "url=/library/en-us/fileio/fs/naming_a_file.asp";
sInfo.getDialog().notify(warn);
}
}
/**
* Updates the CDR (ClassDescriptorResolver) file with the
* classname->descriptor mapping.
*
* @param jClass JClass instance describing the entity class
* @param jDesc JClass instance describing is *Descriptor class
* @param sInfo state info
* @param cdrFileName the filename of the class descriptor resolver (cdr) file
* @throws IOException If an already existing '.castor.cdr' file can not be
* found or loaded
*/
private void updateCDRFile(final JClass jClass, final JClass jDesc,
final SGStateInfo sInfo, final String cdrFileName) throws IOException {
String entityFilename = jClass.getFilename(_resourceDestinationDirectory);
File file = new File(entityFilename);
File parentDirectory = file.getParentFile();
File cdrFile = new File(parentDirectory, cdrFileName);
String cdrFilename = cdrFile.getAbsolutePath();
Properties props = sInfo.getCDRFile(cdrFilename);
if (props == null) {
// check for existing .castor.xml file
props = new Properties();
if (cdrFile.exists()) {
FileInputStream fileStream = new FileInputStream(cdrFile);
props.load(fileStream);
fileStream.close();
}
sInfo.setCDRFile(cdrFilename, props);
}
props.setProperty(jClass.getName(), jDesc.getName());
} //-- updateCDRFile
/**
* Sets the desired {@link ClassNameCRStrategy} instance type to be used
* for name conflict resolution.
* @param nameConflictStrategy the desired {@link ClassNameCRStrategy} instance type
*/
public void setNameConflictStrategy(final String nameConflictStrategy) {
createNameConflictStrategy(nameConflictStrategy);
}
/**
* Creates a new {@link ClassNameCRStrategy} instance by calling the
* {@link ClassNameConflictResolutionStrategyFactory}.
* @param nameConflictStrategy The desired {@link ClassNameCRStrategy} type.
*/
private void createNameConflictStrategy(final String nameConflictStrategy) {
this._conflictStrategy = _classNameConflictResolutionStrategyRegistry
.getClassNameConflictResolutionStrategy(nameConflictStrategy);
this._conflictStrategy.setConsoleDialog(_dialog);
this._conflictStrategy.setSingleClassGenerator(this);
}
/**
* Returns the {@link SourceGenerator} instance that created this class.
* @return the {@link SourceGenerator} instance that created this class.
*/
public SourceGenerator getSourceGenerator() {
return _sourceGenerator;
}
}