Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/builder.properties =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/builder.properties (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/builder.properties (revision 0) @@ -0,0 +1 @@ +org.exolab.castor.builder.javaVersion=5.0 Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input-capital-a.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input-capital-a.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input-capital-a.xml (revision 0) @@ -0,0 +1,5 @@ + + + A + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input_wrong.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input_wrong.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input_wrong.xml (revision 0) @@ -0,0 +1,6 @@ + + + A + FIVE + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/test.xsd =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/test.xsd (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/test.xsd (revision 0) @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/TestDescriptor.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/TestDescriptor.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/TestDescriptor.xml (revision 0) @@ -0,0 +1,24 @@ + + + Test java5style enums + Matthias Epheser + + Tests the generation and unmarshalling of java5 enums with a string value property + + basic capability + 1.5 + + test.xsd + builder.properties + Root + + Test Source Generation + input.xml + + + Test invalid input file + input_wrong.xml + true + + + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5WithClashingConstants/input.xml (revision 0) @@ -0,0 +1,6 @@ + + + A + 5 + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/output.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/output.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/output.xml (revision 0) @@ -0,0 +1,5 @@ + + + Medium + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/builder.properties =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/builder.properties (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/builder.properties (revision 0) @@ -0,0 +1 @@ +org.exolab.castor.builder.javaVersion=5.0 Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/test.xsd =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/test.xsd (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/test.xsd (revision 0) @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/TestDescriptor.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/TestDescriptor.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/TestDescriptor.xml (revision 0) @@ -0,0 +1,20 @@ + + + Test java5style enums with default value + Matthias Epheser + + Tests the generation and unmarshalling of java5 enums with a default value + + basic capability + 1.5 + + test.xsd + builder.properties + Root + + Test Source Generation + input.xml + output.xml + + + Index: xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/input.xml =================================================================== --- xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/input.xml (revision 0) +++ xmlctf/tests/MasterTestSuite/sourcegenerator/Enums/java5_defaultWithClashes/input.xml (revision 0) @@ -0,0 +1,4 @@ + + + Index: codegen/src/main/java/org/exolab/castor/builder/factory/EnumerationFactory.java =================================================================== --- codegen/src/main/java/org/exolab/castor/builder/factory/EnumerationFactory.java (revision 8018) +++ codegen/src/main/java/org/exolab/castor/builder/factory/EnumerationFactory.java (working copy) @@ -1,792 +1,808 @@ -/* - * Copyright 2005 Werner Guttmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.exolab.castor.builder.factory; - -import java.util.Enumeration; - -import org.exolab.castor.builder.AnnotationBuilder; -import org.exolab.castor.builder.BuilderConfiguration; -import org.exolab.castor.builder.FactoryState; -import org.exolab.castor.builder.GroupNaming; -import org.exolab.castor.builder.SGTypes; -import org.exolab.castor.builder.SourceGenerator; -import org.exolab.castor.builder.TypeConversion; -import org.exolab.castor.builder.binding.ExtendedBinding; -import org.exolab.castor.builder.binding.XMLBindingComponent; -import org.exolab.castor.builder.binding.xml.EnumBindingType; -import org.exolab.castor.builder.binding.xml.EnumMember; -import org.exolab.castor.builder.types.XSString; -import org.exolab.castor.builder.types.XSType; -import org.exolab.castor.xml.schema.Facet; -import org.exolab.castor.xml.schema.SimpleType; -import org.exolab.javasource.JArrayType; -import org.exolab.javasource.JClass; -import org.exolab.javasource.JConstructor; -import org.exolab.javasource.JDocComment; -import org.exolab.javasource.JEnum; -import org.exolab.javasource.JEnumConstant; -import org.exolab.javasource.JField; -import org.exolab.javasource.JMethod; -import org.exolab.javasource.JModifiers; -import org.exolab.javasource.JParameter; -import org.exolab.javasource.JSourceCode; -import org.exolab.javasource.JType; - -/** - * This class creates the Java sources for XML Schema components that define - * an enumeration. - * - * @author Werner Guttmann - * @version $Revision: 6287 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ - */ -public final class EnumerationFactory extends BaseFactory { - - /** - * The TypeConversion instance to use for mapping SimpleTypes into XSTypes. - */ - private TypeConversion _typeConversion; - - /** - * A flag indicating that enumerated types should be constructed to perform - * case insensitive lookups based on the values. - */ - private boolean _caseInsensitive = false; - - /** - * Current (hence maximum) suffix for init methods, used to avoid - * the static initializer limits of a JVM. - */ - private int _maxSuffix = 0; - - /** - * Maximum number of enumeration-based constants within a class file. - */ - private int _maxEnumerationsPerClass; - - /** - * Creates a new EnumerationFactory for the builder configuration given. - * @param config the current BuilderConfiguration instance. - * @param groupNaming The group naming scheme to be used. - * @param sourceGenerator the calling source generator. - */ - public EnumerationFactory(final BuilderConfiguration config, - final GroupNaming groupNaming, - final SourceGenerator sourceGenerator) { - super(config, null, groupNaming, sourceGenerator); - _typeConversion = new TypeConversion(getConfig()); - - // TODO[WG]: add code to read in max. value from builder property file - _maxEnumerationsPerClass = config.getMaximumNumberOfConstants(); - } //-- SourceFactory - - /** - * Creates all the necessary enumeration code for a given SimpleType. - * - * @param binding Extended binding instance - * @param simpleType the SimpleType we are processing an enumeration for - * @param state our current state - * @see #processEnumerationAsBaseType - */ - void processEnumerationAsNewObject(final ExtendedBinding binding, - final SimpleType simpleType, final FactoryState state) { - // reset _maxSuffix value to 0 - _maxSuffix = 0; - boolean generateConstantDefinitions = true; - int numberOfEnumerationFacets = simpleType.getNumberOfFacets(Facet.ENUMERATION); - if (numberOfEnumerationFacets > _maxEnumerationsPerClass) { - generateConstantDefinitions = false; - } - - Enumeration enumeration = simpleType.getFacets(Facet.ENUMERATION); - - XMLBindingComponent component = new XMLBindingComponent(getConfig(), getGroupNaming()); - if (binding != null) { - component.setBinding(binding); - component.setView(simpleType); - } - - //-- select naming for types and instances - boolean useValuesAsName = true; - useValuesAsName = selectNamingScheme(component, enumeration, useValuesAsName); - - enumeration = simpleType.getFacets(Facet.ENUMERATION); - - JClass jClass = state.getJClass(); - String className = jClass.getLocalName(); - - if (component.getJavaClassName() != null) { - className = component.getJavaClassName(); - } - - // the java 5 way -> create an enum - if (state.getJClass() instanceof JEnum) { - createJava5Enum(simpleType, state, component, - useValuesAsName, enumeration); - return; - } - - JField field = null; - JField fHash = new JField( - SGTypes.createHashtable(getConfig().useJava50()), "_memberTable"); - fHash.setInitString("init()"); - fHash.getModifiers().setStatic(true); - - JSourceCode jsc = null; - - //-- modify constructor - JConstructor constructor = jClass.getConstructor(0); - constructor.getModifiers().makePrivate(); - constructor.addParameter(new JParameter(JType.INT, "type")); - constructor.addParameter(new JParameter(SGTypes.STRING, "value")); - jsc = constructor.getSourceCode(); - jsc.add("this.type = type;"); - jsc.add("this.stringValue = value;"); - - createValueOfMethod(jClass, className); - createEnumerateMethod(jClass, className); - createToStringMethod(jClass, className); - createInitMethod(jClass); - createReadResolveMethod(jClass); - - //-- Loop through "enumeration" facets - int count = 0; - - while (enumeration.hasMoreElements()) { - Facet facet = (Facet) enumeration.nextElement(); - - String value = facet.getValue(); - - String typeName = null; - String objName = null; - - if (useValuesAsName) { - objName = translateEnumValueToIdentifier(component.getEnumBinding(), facet); - } else { - objName = "VALUE_" + count; - } - - //-- create typeName - //-- Note: this could cause name conflicts - typeName = objName + "_TYPE"; - - //-- Inheritence/Duplicate name cleanup - boolean addInitializerCode = true; - if (jClass.getField(objName) != null) { - //-- either inheritence, duplicate name, or error. - //-- if inheritence or duplicate name, always take - //-- the later definition. Do same if error, for now. - jClass.removeField(objName); - jClass.removeField(typeName); - addInitializerCode = false; - } - - if (generateConstantDefinitions) { - //-- handle int type - field = new JField(JType.INT, typeName); - field.setComment("The " + value + " type"); - JModifiers modifiers = field.getModifiers(); - modifiers.setFinal(true); - modifiers.setStatic(true); - modifiers.makePublic(); - field.setInitString(Integer.toString(count)); - jClass.addField(field); - - //-- handle Class type - field = new JField(jClass, objName); - field.setComment("The instance of the " + value + " type"); - - modifiers = field.getModifiers(); - modifiers.setFinal(true); - modifiers.setStatic(true); - modifiers.makePublic(); - - StringBuilder init = new StringBuilder(32); - init.append("new "); - init.append(className); - init.append("("); - init.append(typeName); - init.append(", \""); - init.append(escapeValue(value)); - init.append("\")"); - - field.setInitString(init.toString()); - jClass.addField(field); - - } - //-- initializer method - - if (addInitializerCode) { - jsc = getSourceCodeForInitMethod(jClass); - jsc.add("members.put(\""); - jsc.append(escapeValue(value)); - if (_caseInsensitive) { - jsc.append("\".toLowerCase(), "); - } else { - jsc.append("\", "); - } - if (generateConstantDefinitions) { - jsc.append(objName); - } else { - StringBuilder init = new StringBuilder(32); - init.append("new "); - init.append(className); - init.append("("); - init.append(Integer.toString(count)); - init.append(", \""); - init.append(escapeValue(value)); - init.append("\")"); - jsc.append(init.toString()); - } - jsc.append(");"); - } - - ++count; - } - - //-- finish init method - final JMethod method = jClass.getMethod(this.getInitMethodName(_maxSuffix), 0); - method.getSourceCode().add("return members;"); - - //-- add memberTable to the class, we can only add this after all the types, - //-- or we'll create source code that will generate null pointer exceptions, - //-- because calling init() will try to add null values to the hashtable. - jClass.addField(fHash); - - //-- add internal type - field = new JField(JType.INT, "type"); - field.getModifiers().setFinal(true); - jClass.addField(field); - - //-- add internal stringValue - field = new JField(SGTypes.STRING, "stringValue"); - field.setInitString("null"); - jClass.addField(field); - - createGetTypeMethod(jClass, className); - } //-- processEnumerationAsNewObject - - private void createJava5Enum(final SimpleType simpleType, - final FactoryState state, final XMLBindingComponent component, - final boolean useValuesAsName, final Enumeration enumeration) { - - AnnotationBuilder[] annotationBuilders = - state.getSGStateInfo().getSourceGenerator().getAnnotationBuilders(); - - JEnum jEnum = (JEnum) state.getJClass(); - - // add value field - JField jField = new JField(new JClass("java.lang.String"), "value"); - JModifiers modifiers = new JModifiers(); - modifiers.setFinal(true); - modifiers.makePrivate(); - jField.setModifiers(modifiers); - jEnum.addField(jField); - - JMethod valueMethod = new JMethod("value", new JClass("java.lang.String"), - "the value of this constant"); - valueMethod.setSourceCode("return this.value;"); - jEnum.addMethod(valueMethod, false); - - JMethod fromValueMethod = new JMethod("fromValue", jEnum, "the constant for this value"); - fromValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value")); - JSourceCode sourceCode = new JSourceCode(); - sourceCode.add("for (" + jEnum.getLocalName() + " c: " - + jEnum.getLocalName() + ".values()) {"); - sourceCode.indent(); - sourceCode.add("if (c.value.equals(value)) {"); - sourceCode.indent(); - sourceCode.add("return c;"); - sourceCode.unindent(); - sourceCode.add("}"); - sourceCode.unindent(); - sourceCode.add("}"); - sourceCode.add("throw new IllegalArgumentException(value);"); - fromValueMethod.setSourceCode(sourceCode); - modifiers = new JModifiers(); - modifiers.setStatic(true); - fromValueMethod.setModifiers(modifiers); - jEnum.addMethod(fromValueMethod, false); - - JMethod setValueMethod = new JMethod("setValue"); - setValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value")); - jEnum.addMethod(setValueMethod, false); - - JMethod toStringMethod = new JMethod("toString", - new JClass("java.lang.String"), "the value of this constant"); - toStringMethod.setSourceCode("return this.value;"); - jEnum.addMethod(toStringMethod, false); - - JConstructor constructor = jEnum.createConstructor(); - constructor.addParameter(new JParameter(new JClass("java.lang.String"), "value")); - constructor.setSourceCode("this.value = value;"); - modifiers = new JModifiers(); - modifiers.makePrivate(); - constructor.setModifiers(modifiers); - jEnum.addConstructor(constructor); - - int enumCount = 0; - while (enumeration.hasMoreElements()) { - Facet facet = (Facet) enumeration.nextElement(); - JEnumConstant enumConstant; - if (useValuesAsName) { - enumConstant = new JEnumConstant( - translateEnumValueToIdentifier(component.getEnumBinding(), facet), - new String[]{"\"" + facet.getValue() + "\""}); - } else { - enumConstant = new JEnumConstant( - "VALUE_" + enumCount, new String[]{"\"" - + facet.getValue() + "\""}); - } - - // custom annotations - for (int i = 0; i < annotationBuilders.length; i++) { - AnnotationBuilder annotationBuilder = annotationBuilders[i]; - annotationBuilder.addEnumConstantAnnotations(facet, enumConstant); - } - - jEnum.addConstant(enumConstant); - enumCount++; - } - - // custom annotations - for (int i = 0; i < annotationBuilders.length; i++) { - AnnotationBuilder annotationBuilder = annotationBuilders[i]; - annotationBuilder.addEnumAnnotations(simpleType, jEnum); - } - } - - - - /** - * Returns the JSourceCode instance for the current init() method, dealing with - * static initializer limits of the JVM by creating new init() methods - * as needed. - * @param jClass The JClass instance for which an init method needs to be added - * @return the JSourceCode instance for the current init() method - */ - private JSourceCode getSourceCodeForInitMethod(final JClass jClass) { - final JMethod currentInitMethod = jClass.getMethod(getInitMethodName(_maxSuffix), 0); - if (currentInitMethod.getSourceCode().size() > _maxEnumerationsPerClass) { - ++_maxSuffix; - JMethod mInit = createInitMethod(jClass); - currentInitMethod.getSourceCode().add("members.putAll(" + mInit.getName() + "());"); - currentInitMethod.getSourceCode().add("return members;"); - - return mInit.getSourceCode(); - } - return currentInitMethod.getSourceCode(); - } - - /** - * Returns the method name for an init method. - * @param index index of the init method. - * @return the method name for an init method. - */ - private String getInitMethodName(final int index) { - if (index == 0) { - return "init"; - } - - return "init" + index; - } - - private boolean selectNamingScheme(final XMLBindingComponent component, - final Enumeration enumeration, final boolean useValuesAsName) { - boolean duplicateTranslation = false; - short numberOfTranslationToSpecialCharacter = 0; - - while (enumeration.hasMoreElements()) { - Facet facet = enumeration.nextElement(); - String possibleId = translateEnumValueToIdentifier(component.getEnumBinding(), facet); - if (possibleId.equals("_")) { - numberOfTranslationToSpecialCharacter++; - if (numberOfTranslationToSpecialCharacter > 1) { - duplicateTranslation = true; - } - } - - if (!getJavaNaming().isValidJavaIdentifier(possibleId)) { - return false; - } - } - - if (duplicateTranslation) { - return false; - } - return useValuesAsName; - } - - /** - * Creates 'getType()' method for this enumeration class. - * @param jClass The enumeration class to create this method for. - * @param className The name of the class. - */ - private void createGetTypeMethod(final JClass jClass, final String className) { - JMethod mGetType = new JMethod("getType", JType.INT, "the type of this " + className); - mGetType.getSourceCode().add("return this.type;"); - JDocComment jdc = mGetType.getJDocComment(); - jdc.appendComment("Returns the type of this " + className); - jClass.addMethod(mGetType); - } - - /** - * Creates 'readResolve(Object)' method for this enumeration class. - * @param jClass The enumeration class to create this method for. - */ - private void createReadResolveMethod(final JClass jClass) { - JDocComment jdc; - JSourceCode jsc; - JMethod mReadResolve = new JMethod("readResolve", SGTypes.OBJECT, - "this deserialized object"); - mReadResolve.getModifiers().makePrivate(); - jClass.addMethod(mReadResolve); - jdc = mReadResolve.getJDocComment(); - jdc.appendComment(" will be called during deserialization to replace "); - jdc.appendComment("the deserialized object with the correct constant "); - jdc.appendComment("instance."); - jsc = mReadResolve.getSourceCode(); - jsc.add("return valueOf(this.stringValue);"); - } - - /** - * Creates 'init()' method for this enumeration class. - * @param jClass The enumeration class to create this method for. - * @return an 'init()' method for this enumeration class. - */ - private JMethod createInitMethod(final JClass jClass) { - final String initMethodName = getInitMethodName(_maxSuffix); - JMethod mInit = new JMethod(initMethodName, - SGTypes.createHashtable(getConfig().useJava50()), - "the initialized Hashtable for the member table"); - jClass.addMethod(mInit); - mInit.getModifiers().makePrivate(); - mInit.getModifiers().setStatic(true); - if (getConfig().useJava50()) { - mInit.getSourceCode().add("java.util.Hashtable members" - + " = new java.util.Hashtable();"); - } else { - mInit.getSourceCode().add("java.util.Hashtable members = new java.util.Hashtable();"); - } - return mInit; - } - - /** - * Creates 'toString()' method for this enumeration class. - * @param jClass The enumeration class to create this method for. - * @param className The name of the class. - */ - private void createToStringMethod(final JClass jClass, final String className) { - JMethod mToString = new JMethod("toString", SGTypes.STRING, - "the String representation of this " + className); - jClass.addMethod(mToString); - JDocComment jdc = mToString.getJDocComment(); - jdc.appendComment("Returns the String representation of this "); - jdc.appendComment(className); - mToString.getSourceCode().add("return this.stringValue;"); - } - - /** - * Creates 'enumerate()' method for this enumeration class. - * @param jClass The enumeration class to create this method for. - * @param className The name of the class. - */ - private void createEnumerateMethod(final JClass jClass, final String className) { - // TODO for the time being return Enumeration for Java 5.0; change - JMethod mEnumerate = new JMethod("enumerate", - SGTypes.createEnumeration(SGTypes.OBJECT, getConfig().useJava50(), true), - "an Enumeration over all possible instances of " + className); - mEnumerate.getModifiers().setStatic(true); - jClass.addMethod(mEnumerate); - JDocComment jdc = mEnumerate.getJDocComment(); - jdc.appendComment("Returns an enumeration of all possible instances of "); - jdc.appendComment(className); - mEnumerate.getSourceCode().add("return _memberTable.elements();"); - } - - /** - * Creates 'valueOf(String)' method for this enumeration class. - * @param jClass The enumeration class to create this method for. - * @param className The name of the class. - */ - private void createValueOfMethod(final JClass jClass, final String className) { - JMethod mValueOf = new JMethod( - "valueOf", jClass, "the " + className + " value of parameter 'string'"); - mValueOf.addParameter(new JParameter(SGTypes.STRING, "string")); - mValueOf.getModifiers().setStatic(true); - jClass.addMethod(mValueOf); - - JDocComment jdc = mValueOf.getJDocComment(); - jdc.appendComment("Returns a new " + className); - jdc.appendComment(" based on the given String value."); - - JSourceCode jsc = mValueOf.getSourceCode(); - jsc.add("java.lang.Object obj = null;\n" - + "if (string != null) {\n" - + " obj = _memberTable.get(string{1});\n" - + "}\n" - + "if (obj == null) {\n" - + " String err = \"'\" + string + \"' is not a valid {0}\";\n" - + " throw new IllegalArgumentException(err);\n" - + "}\n" - + "return ({0}) obj;", className, (_caseInsensitive ? ".toLowerCase()" : "")); - - } - - /** - * Creates all the necessary enumeration code from the given SimpleType. - * Enumerations are handled by creating an Object like the following: - * - * - * public class {name} { - * // list of values - * {type}[] values = { - * ... - * }; - * - * // Returns true if the given value is part - * // of this enumeration - * public boolean contains({type} value); - * - * // Returns the {type} value whose String value - * // is equal to the given String - * public {type} valueOf(String strValue); - * } - * - * @param binding Extended binding instance - * @param simpleType the SimpleType we are processing an enumeration for - * @param state our current state - */ - void processEnumerationAsBaseType(final ExtendedBinding binding, - final SimpleType simpleType, final FactoryState state) { - SimpleType base = (SimpleType) simpleType.getBaseType(); - XSType baseType = null; - - if (base == null) { - baseType = new XSString(); - } else { - baseType = _typeConversion.convertType(base, getConfig().useJava50()); - } - - Enumeration enumeration = simpleType.getFacets(Facet.ENUMERATION); - - JClass jClass = state.getJClass(); - String className = jClass.getLocalName(); - - JField fValues = null; - JDocComment jdc = null; - JSourceCode jsc = null; - - //-- modify constructor - JConstructor constructor = jClass.getConstructor(0); - constructor.getModifiers().makePrivate(); - - fValues = new JField(new JArrayType( - baseType.getJType(), getConfig().useJava50()), "values"); - - //-- Loop through "enumeration" facets - //-- and create the default values for the type. - int count = 0; - - StringBuilder values = new StringBuilder("{\n"); - - while (enumeration.hasMoreElements()) { - Facet facet = (Facet) enumeration.nextElement(); - String value = facet.getValue(); - - //-- Should we make sure the value is valid before proceeding?? - - //-- we need to move this code to XSType so that we don't have to do - //-- special code here for each type - - if (count > 0) { - values.append(",\n"); - } - - //-- indent for fun - values.append(" "); - - if (baseType.getType() == XSType.STRING_TYPE) { - values.append('\"'); - //-- escape value - values.append(escapeValue(value)); - values.append('\"'); - } else { - values.append(value); - } - - ++count; - } - - values.append("\n}"); - - fValues.setInitString(values.toString()); - jClass.addField(fValues); - - //-- #valueOf method - JMethod method = new JMethod("valueOf", jClass, - "the String value of the provided " + baseType.getJType()); - method.addParameter(new JParameter(SGTypes.STRING, "string")); - method.getModifiers().setStatic(true); - jClass.addMethod(method); - jdc = method.getJDocComment(); - jdc.appendComment("Returns the " + baseType.getJType()); - jdc.appendComment(" based on the given String value."); - jsc = method.getSourceCode(); - - jsc.add("for (int i = 0; i < values.length; i++) {"); - jsc.add("}"); - jsc.add("throw new IllegalArgumentException(\""); - jsc.append("Invalid value for "); - jsc.append(className); - jsc.append(": \" + string + \".\");"); - } //-- processEnumerationAsBaseType - - /** - * Attempts to translate a simpleType enumeration value into a legal java - * identifier. Translation is through a couple of simple rules: - * - * if the value parses as a non-negative int, the string 'VALUE_' is - * prepended to it - * if the value parses as a negative int, the string 'VALUE_NEG_' is - * prepended to it - * the value is uppercased - * the characters [](){}<>'`" are removed - * the characters |\/?~!@#$%^&*-+=:;., and any - * whitespace are replaced with _ - * - * @param enumBinding if not null, a possible custom binding for this enum - * @param facet the facet whose enum value is being translated. - * @return the identifier for the enum value - * - * @author rhett-sutphin@uiowa.edu - */ - private String translateEnumValueToIdentifier(final EnumBindingType enumBinding, - final Facet facet) { - String enumValue = facet.getValue(); - - try { - String enumerationValue = null; - int intVal = Integer.parseInt(facet.getValue()); - - String customMemberName = null; - if (enumBinding != null) { - customMemberName = - getCustomMemberName(enumBinding, String.valueOf(intVal)); - } - - if (customMemberName != null) { - enumerationValue = customMemberName; - } else { - if (intVal >= 0) { - enumerationValue = "VALUE_" + intVal; - } else { - enumerationValue = "VALUE_NEG_" + Math.abs(intVal); - } - } - - - return enumerationValue; - } catch (NumberFormatException e) { - // just keep going - } - - StringBuilder sb = new StringBuilder(32); - String customMemberName = null; - - if (enumBinding != null) { - customMemberName = getCustomMemberName(enumBinding, enumValue); - } - - if (customMemberName != null) { - sb.append(customMemberName); - } else { - sb.append(enumValue.toUpperCase()); - int i = 0; - while (i < sb.length()) { - char c = sb.charAt(i); - if ("[](){}<>'`\"".indexOf(c) >= 0) { - sb.deleteCharAt(i); - i--; - } else if (Character.isWhitespace(c) || "\\/?~!@#$%^&*-+=:;.,".indexOf(c) >= 0) { - sb.setCharAt(i, '_'); - } - i++; - } - } - return sb.toString(); - } - - /** - * Returns a custom member name (if defined). - * @param enumBinding The {@link EnumBindingType} instance - * @param enumValue The enumeration value. - * @return the custom member name - */ - private String getCustomMemberName(final EnumBindingType enumBinding, - final String enumValue) { - // check whether there's a custom binding for the member name - String customMemberName = null; - EnumMember[] enumMembers = enumBinding.getEnumMember(); - for (int i = 0; i < enumMembers.length; i++) { - if (enumMembers[i].getValue().equals(enumValue)) { - customMemberName = enumMembers[i].getJavaName(); - } - } - return customMemberName; - } - - /** - * Set to true if enumerated type lookups should be performed in a case - * insensitive manner. - * - * @param caseInsensitive when true - */ - public void setCaseInsensitive(final boolean caseInsensitive) { - _caseInsensitive = caseInsensitive; - } - - /** - * Escapes special characters in the given String so that it can be printed - * correctly. - * - * @param str the String to escape - * @return the escaped String, or null if the given String was null. - */ - private static String escapeValue(final String str) { - if (str == null) { - return str; - } - - StringBuilder sb = new StringBuilder(); - char[] chars = str.toCharArray(); - - for (int i = 0; i < chars.length; i++) { - char ch = chars[i]; - switch (ch) { - case '\\': - case '\"': - case '\'': - sb.append('\\'); - break; - default: - break; - } - sb.append(ch); - } - return sb.toString(); - } //-- escapeValue - -} +/* + * Copyright 2005 Werner Guttmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.exolab.castor.builder.factory; + +import java.util.Enumeration; +import java.util.HashSet; + +import org.exolab.castor.builder.AnnotationBuilder; +import org.exolab.castor.builder.BuilderConfiguration; +import org.exolab.castor.builder.FactoryState; +import org.exolab.castor.builder.GroupNaming; +import org.exolab.castor.builder.SGTypes; +import org.exolab.castor.builder.SourceGenerator; +import org.exolab.castor.builder.TypeConversion; +import org.exolab.castor.builder.binding.ExtendedBinding; +import org.exolab.castor.builder.binding.XMLBindingComponent; +import org.exolab.castor.builder.binding.xml.EnumBindingType; +import org.exolab.castor.builder.binding.xml.EnumMember; +import org.exolab.castor.builder.types.XSString; +import org.exolab.castor.builder.types.XSType; +import org.exolab.castor.xml.schema.Facet; +import org.exolab.castor.xml.schema.SimpleType; +import org.exolab.javasource.JArrayType; +import org.exolab.javasource.JClass; +import org.exolab.javasource.JConstructor; +import org.exolab.javasource.JDocComment; +import org.exolab.javasource.JEnum; +import org.exolab.javasource.JEnumConstant; +import org.exolab.javasource.JField; +import org.exolab.javasource.JMethod; +import org.exolab.javasource.JModifiers; +import org.exolab.javasource.JParameter; +import org.exolab.javasource.JSourceCode; +import org.exolab.javasource.JType; + +/** + * This class creates the Java sources for XML Schema components that define + * an enumeration. + * + * @author Werner Guttmann + * @version $Revision: 6287 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ + */ +public final class EnumerationFactory extends BaseFactory { + + /** + * The TypeConversion instance to use for mapping SimpleTypes into XSTypes. + */ + private TypeConversion _typeConversion; + + /** + * A flag indicating that enumerated types should be constructed to perform + * case insensitive lookups based on the values. + */ + private boolean _caseInsensitive = false; + + /** + * Current (hence maximum) suffix for init methods, used to avoid + * the static initializer limits of a JVM. + */ + private int _maxSuffix = 0; + + /** + * Maximum number of enumeration-based constants within a class file. + */ + private int _maxEnumerationsPerClass; + + /** + * Describes whether the generated symbols should not be uppercased + */ + private boolean shouldNotUpperCase = false; + + /** + * Creates a new EnumerationFactory for the builder configuration given. + * @param config the current BuilderConfiguration instance. + * @param groupNaming The group naming scheme to be used. + * @param sourceGenerator the calling source generator. + */ + public EnumerationFactory(final BuilderConfiguration config, + final GroupNaming groupNaming, + final SourceGenerator sourceGenerator) { + super(config, null, groupNaming, sourceGenerator); + _typeConversion = new TypeConversion(getConfig()); + + // TODO[WG]: add code to read in max. value from builder property file + _maxEnumerationsPerClass = config.getMaximumNumberOfConstants(); + } //-- SourceFactory + + /** + * Creates all the necessary enumeration code for a given SimpleType. + * + * @param binding Extended binding instance + * @param simpleType the SimpleType we are processing an enumeration for + * @param state our current state + * @see #processEnumerationAsBaseType + */ + void processEnumerationAsNewObject(final ExtendedBinding binding, + final SimpleType simpleType, final FactoryState state) { + // reset _maxSuffix value to 0 + _maxSuffix = 0; + boolean generateConstantDefinitions = true; + int numberOfEnumerationFacets = simpleType.getNumberOfFacets(Facet.ENUMERATION); + if (numberOfEnumerationFacets > _maxEnumerationsPerClass) { + generateConstantDefinitions = false; + } + + Enumeration enumeration = simpleType.getFacets(Facet.ENUMERATION); + + XMLBindingComponent component = new XMLBindingComponent(getConfig(), getGroupNaming()); + if (binding != null) { + component.setBinding(binding); + component.setView(simpleType); + } + + //-- select naming for types and instances + boolean useValuesAsName = true; + useValuesAsName = selectNamingScheme(component, enumeration, useValuesAsName); + + enumeration = simpleType.getFacets(Facet.ENUMERATION); + + JClass jClass = state.getJClass(); + String className = jClass.getLocalName(); + + if (component.getJavaClassName() != null) { + className = component.getJavaClassName(); + } + + // the java 5 way -> create an enum + if (state.getJClass() instanceof JEnum) { + createJava5Enum(simpleType, state, component, + useValuesAsName, enumeration); + return; + } + + JField field = null; + JField fHash = new JField( + SGTypes.createHashtable(getConfig().useJava50()), "_memberTable"); + fHash.setInitString("init()"); + fHash.getModifiers().setStatic(true); + + JSourceCode jsc = null; + + //-- modify constructor + JConstructor constructor = jClass.getConstructor(0); + constructor.getModifiers().makePrivate(); + constructor.addParameter(new JParameter(JType.INT, "type")); + constructor.addParameter(new JParameter(SGTypes.STRING, "value")); + jsc = constructor.getSourceCode(); + jsc.add("this.type = type;"); + jsc.add("this.stringValue = value;"); + + createValueOfMethod(jClass, className); + createEnumerateMethod(jClass, className); + createToStringMethod(jClass, className); + createInitMethod(jClass); + createReadResolveMethod(jClass); + + //-- Loop through "enumeration" facets + int count = 0; + + while (enumeration.hasMoreElements()) { + Facet facet = enumeration.nextElement(); + + String value = facet.getValue(); + + String typeName = null; + String objName = null; + + if (useValuesAsName) { + objName = translateEnumValueToIdentifier(component.getEnumBinding(), facet, true); + } else { + objName = "VALUE_" + count; + } + + //-- create typeName + //-- Note: this could cause name conflicts + typeName = objName + "_TYPE"; + + //-- Inheritence/Duplicate name cleanup + boolean addInitializerCode = true; + if (jClass.getField(objName) != null) { + //-- either inheritence, duplicate name, or error. + //-- if inheritence or duplicate name, always take + //-- the later definition. Do same if error, for now. + jClass.removeField(objName); + jClass.removeField(typeName); + addInitializerCode = false; + } + + if (generateConstantDefinitions) { + //-- handle int type + field = new JField(JType.INT, typeName); + field.setComment("The " + value + " type"); + JModifiers modifiers = field.getModifiers(); + modifiers.setFinal(true); + modifiers.setStatic(true); + modifiers.makePublic(); + field.setInitString(Integer.toString(count)); + jClass.addField(field); + + //-- handle Class type + field = new JField(jClass, objName); + field.setComment("The instance of the " + value + " type"); + + modifiers = field.getModifiers(); + modifiers.setFinal(true); + modifiers.setStatic(true); + modifiers.makePublic(); + + StringBuilder init = new StringBuilder(32); + init.append("new "); + init.append(className); + init.append("("); + init.append(typeName); + init.append(", \""); + init.append(escapeValue(value)); + init.append("\")"); + + field.setInitString(init.toString()); + jClass.addField(field); + + } + //-- initializer method + + if (addInitializerCode) { + jsc = getSourceCodeForInitMethod(jClass); + jsc.add("members.put(\""); + jsc.append(escapeValue(value)); + if (_caseInsensitive) { + jsc.append("\".toLowerCase(), "); + } else { + jsc.append("\", "); + } + if (generateConstantDefinitions) { + jsc.append(objName); + } else { + StringBuilder init = new StringBuilder(32); + init.append("new "); + init.append(className); + init.append("("); + init.append(Integer.toString(count)); + init.append(", \""); + init.append(escapeValue(value)); + init.append("\")"); + jsc.append(init.toString()); + } + jsc.append(");"); + } + + ++count; + } + + //-- finish init method + final JMethod method = jClass.getMethod(this.getInitMethodName(_maxSuffix), 0); + method.getSourceCode().add("return members;"); + + //-- add memberTable to the class, we can only add this after all the types, + //-- or we'll create source code that will generate null pointer exceptions, + //-- because calling init() will try to add null values to the hashtable. + jClass.addField(fHash); + + //-- add internal type + field = new JField(JType.INT, "type"); + field.getModifiers().setFinal(true); + jClass.addField(field); + + //-- add internal stringValue + field = new JField(SGTypes.STRING, "stringValue"); + field.setInitString("null"); + jClass.addField(field); + + createGetTypeMethod(jClass, className); + } //-- processEnumerationAsNewObject + + private void createJava5Enum(final SimpleType simpleType, + final FactoryState state, final XMLBindingComponent component, + final boolean useValuesAsName, final Enumeration enumeration) { + + AnnotationBuilder[] annotationBuilders = + state.getSGStateInfo().getSourceGenerator().getAnnotationBuilders(); + + JEnum jEnum = (JEnum) state.getJClass(); + + // add value field + JField jField = new JField(new JClass("java.lang.String"), "value"); + JModifiers modifiers = new JModifiers(); + modifiers.setFinal(true); + modifiers.makePrivate(); + jField.setModifiers(modifiers); + jEnum.addField(jField); + + JMethod valueMethod = new JMethod("value", new JClass("java.lang.String"), + "the value of this constant"); + valueMethod.setSourceCode("return this.value;"); + jEnum.addMethod(valueMethod, false); + + JMethod fromValueMethod = new JMethod("fromValue", jEnum, "the constant for this value"); + fromValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value")); + JSourceCode sourceCode = new JSourceCode(); + sourceCode.add("for (" + jEnum.getLocalName() + " c: " + + jEnum.getLocalName() + ".values()) {"); + sourceCode.indent(); + sourceCode.add("if (c.value.equals(value)) {"); + sourceCode.indent(); + sourceCode.add("return c;"); + sourceCode.unindent(); + sourceCode.add("}"); + sourceCode.unindent(); + sourceCode.add("}"); + sourceCode.add("throw new IllegalArgumentException(value);"); + fromValueMethod.setSourceCode(sourceCode); + modifiers = new JModifiers(); + modifiers.setStatic(true); + fromValueMethod.setModifiers(modifiers); + jEnum.addMethod(fromValueMethod, false); + + JMethod setValueMethod = new JMethod("setValue"); + setValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value")); + jEnum.addMethod(setValueMethod, false); + + JMethod toStringMethod = new JMethod("toString", + new JClass("java.lang.String"), "the value of this constant"); + toStringMethod.setSourceCode("return this.value;"); + jEnum.addMethod(toStringMethod, false); + + JConstructor constructor = jEnum.createConstructor(); + constructor.addParameter(new JParameter(new JClass("java.lang.String"), "value")); + constructor.setSourceCode("this.value = value;"); + modifiers = new JModifiers(); + modifiers.makePrivate(); + constructor.setModifiers(modifiers); + jEnum.addConstructor(constructor); + + int enumCount = 0; + while (enumeration.hasMoreElements()) { + Facet facet = enumeration.nextElement(); + JEnumConstant enumConstant; + if (useValuesAsName) { + enumConstant = new JEnumConstant( + translateEnumValueToIdentifier(component.getEnumBinding(), facet, true), + new String[]{"\"" + facet.getValue() + "\""}); + } else { + enumConstant = new JEnumConstant( + "VALUE_" + enumCount, new String[]{"\"" + + facet.getValue() + "\""}); + } + + // custom annotations + for (int i = 0; i < annotationBuilders.length; i++) { + AnnotationBuilder annotationBuilder = annotationBuilders[i]; + annotationBuilder.addEnumConstantAnnotations(facet, enumConstant); + } + + jEnum.addConstant(enumConstant); + enumCount++; + } + + // custom annotations + for (int i = 0; i < annotationBuilders.length; i++) { + AnnotationBuilder annotationBuilder = annotationBuilders[i]; + annotationBuilder.addEnumAnnotations(simpleType, jEnum); + } + } + + + + /** + * Returns the JSourceCode instance for the current init() method, dealing with + * static initializer limits of the JVM by creating new init() methods + * as needed. + * @param jClass The JClass instance for which an init method needs to be added + * @return the JSourceCode instance for the current init() method + */ + private JSourceCode getSourceCodeForInitMethod(final JClass jClass) { + final JMethod currentInitMethod = jClass.getMethod(getInitMethodName(_maxSuffix), 0); + if (currentInitMethod.getSourceCode().size() > _maxEnumerationsPerClass) { + ++_maxSuffix; + JMethod mInit = createInitMethod(jClass); + currentInitMethod.getSourceCode().add("members.putAll(" + mInit.getName() + "());"); + currentInitMethod.getSourceCode().add("return members;"); + + return mInit.getSourceCode(); + } + return currentInitMethod.getSourceCode(); + } + + /** + * Returns the method name for an init method. + * @param index index of the init method. + * @return the method name for an init method. + */ + private String getInitMethodName(final int index) { + if (index == 0) { + return "init"; + } + + return "init" + index; + } + + private boolean selectNamingScheme(final XMLBindingComponent component, + final Enumeration enumeration, final boolean useValuesAsName) { + int numSymbols = 0; + HashSet upperCaseSymbols = new HashSet(); + HashSet symbols = new HashSet(); + + while (enumeration.hasMoreElements()) { + Facet facet = enumeration.nextElement(); + String possibleId = translateEnumValueToIdentifier(component.getEnumBinding(), facet, true); + upperCaseSymbols.add(possibleId); + symbols.add(translateEnumValueToIdentifier(component.getEnumBinding(), facet, false)); + numSymbols++; + + if (!getJavaNaming().isValidJavaIdentifier(possibleId)) { + return false; + } + } + + // if the two sets differ in size, then we have a case clash here. + // i.e. a duplicate naming that can be resolved by keeping the case. + if (symbols.size() != upperCaseSymbols.size()) { + shouldNotUpperCase=true; + } + + // if the size of the set of symbols does not equal our + // counted symbols, we have a duplicate naming. + if (symbols.size() != numSymbols) { + return false; + } + return useValuesAsName; + } + + /** + * Creates 'getType()' method for this enumeration class. + * @param jClass The enumeration class to create this method for. + * @param className The name of the class. + */ + private void createGetTypeMethod(final JClass jClass, final String className) { + JMethod mGetType = new JMethod("getType", JType.INT, "the type of this " + className); + mGetType.getSourceCode().add("return this.type;"); + JDocComment jdc = mGetType.getJDocComment(); + jdc.appendComment("Returns the type of this " + className); + jClass.addMethod(mGetType); + } + + /** + * Creates 'readResolve(Object)' method for this enumeration class. + * @param jClass The enumeration class to create this method for. + */ + private void createReadResolveMethod(final JClass jClass) { + JDocComment jdc; + JSourceCode jsc; + JMethod mReadResolve = new JMethod("readResolve", SGTypes.OBJECT, + "this deserialized object"); + mReadResolve.getModifiers().makePrivate(); + jClass.addMethod(mReadResolve); + jdc = mReadResolve.getJDocComment(); + jdc.appendComment(" will be called during deserialization to replace "); + jdc.appendComment("the deserialized object with the correct constant "); + jdc.appendComment("instance."); + jsc = mReadResolve.getSourceCode(); + jsc.add("return valueOf(this.stringValue);"); + } + + /** + * Creates 'init()' method for this enumeration class. + * @param jClass The enumeration class to create this method for. + * @return an 'init()' method for this enumeration class. + */ + private JMethod createInitMethod(final JClass jClass) { + final String initMethodName = getInitMethodName(_maxSuffix); + JMethod mInit = new JMethod(initMethodName, + SGTypes.createHashtable(getConfig().useJava50()), + "the initialized Hashtable for the member table"); + jClass.addMethod(mInit); + mInit.getModifiers().makePrivate(); + mInit.getModifiers().setStatic(true); + if (getConfig().useJava50()) { + mInit.getSourceCode().add("java.util.Hashtable members" + + " = new java.util.Hashtable();"); + } else { + mInit.getSourceCode().add("java.util.Hashtable members = new java.util.Hashtable();"); + } + return mInit; + } + + /** + * Creates 'toString()' method for this enumeration class. + * @param jClass The enumeration class to create this method for. + * @param className The name of the class. + */ + private void createToStringMethod(final JClass jClass, final String className) { + JMethod mToString = new JMethod("toString", SGTypes.STRING, + "the String representation of this " + className); + jClass.addMethod(mToString); + JDocComment jdc = mToString.getJDocComment(); + jdc.appendComment("Returns the String representation of this "); + jdc.appendComment(className); + mToString.getSourceCode().add("return this.stringValue;"); + } + + /** + * Creates 'enumerate()' method for this enumeration class. + * @param jClass The enumeration class to create this method for. + * @param className The name of the class. + */ + private void createEnumerateMethod(final JClass jClass, final String className) { + // TODO for the time being return Enumeration for Java 5.0; change + JMethod mEnumerate = new JMethod("enumerate", + SGTypes.createEnumeration(SGTypes.OBJECT, getConfig().useJava50(), true), + "an Enumeration over all possible instances of " + className); + mEnumerate.getModifiers().setStatic(true); + jClass.addMethod(mEnumerate); + JDocComment jdc = mEnumerate.getJDocComment(); + jdc.appendComment("Returns an enumeration of all possible instances of "); + jdc.appendComment(className); + mEnumerate.getSourceCode().add("return _memberTable.elements();"); + } + + /** + * Creates 'valueOf(String)' method for this enumeration class. + * @param jClass The enumeration class to create this method for. + * @param className The name of the class. + */ + private void createValueOfMethod(final JClass jClass, final String className) { + JMethod mValueOf = new JMethod( + "valueOf", jClass, "the " + className + " value of parameter 'string'"); + mValueOf.addParameter(new JParameter(SGTypes.STRING, "string")); + mValueOf.getModifiers().setStatic(true); + jClass.addMethod(mValueOf); + + JDocComment jdc = mValueOf.getJDocComment(); + jdc.appendComment("Returns a new " + className); + jdc.appendComment(" based on the given String value."); + + JSourceCode jsc = mValueOf.getSourceCode(); + jsc.add("java.lang.Object obj = null;\n" + + "if (string != null) {\n" + + " obj = _memberTable.get(string{1});\n" + + "}\n" + + "if (obj == null) {\n" + + " String err = \"'\" + string + \"' is not a valid {0}\";\n" + + " throw new IllegalArgumentException(err);\n" + + "}\n" + + "return ({0}) obj;", className, (_caseInsensitive ? ".toLowerCase()" : "")); + + } + + /** + * Creates all the necessary enumeration code from the given SimpleType. + * Enumerations are handled by creating an Object like the following: + * + * + * public class {name} { + * // list of values + * {type}[] values = { + * ... + * }; + * + * // Returns true if the given value is part + * // of this enumeration + * public boolean contains({type} value); + * + * // Returns the {type} value whose String value + * // is equal to the given String + * public {type} valueOf(String strValue); + * } + * + * @param binding Extended binding instance + * @param simpleType the SimpleType we are processing an enumeration for + * @param state our current state + */ + void processEnumerationAsBaseType(final ExtendedBinding binding, + final SimpleType simpleType, final FactoryState state) { + SimpleType base = (SimpleType) simpleType.getBaseType(); + XSType baseType = null; + + if (base == null) { + baseType = new XSString(); + } else { + baseType = _typeConversion.convertType(base, getConfig().useJava50()); + } + + Enumeration enumeration = simpleType.getFacets(Facet.ENUMERATION); + + JClass jClass = state.getJClass(); + String className = jClass.getLocalName(); + + JField fValues = null; + JDocComment jdc = null; + JSourceCode jsc = null; + + //-- modify constructor + JConstructor constructor = jClass.getConstructor(0); + constructor.getModifiers().makePrivate(); + + fValues = new JField(new JArrayType( + baseType.getJType(), getConfig().useJava50()), "values"); + + //-- Loop through "enumeration" facets + //-- and create the default values for the type. + int count = 0; + + StringBuilder values = new StringBuilder("{\n"); + + while (enumeration.hasMoreElements()) { + Facet facet = enumeration.nextElement(); + String value = facet.getValue(); + + //-- Should we make sure the value is valid before proceeding?? + + //-- we need to move this code to XSType so that we don't have to do + //-- special code here for each type + + if (count > 0) { + values.append(",\n"); + } + + //-- indent for fun + values.append(" "); + + if (baseType.getType() == XSType.STRING_TYPE) { + values.append('\"'); + //-- escape value + values.append(escapeValue(value)); + values.append('\"'); + } else { + values.append(value); + } + + ++count; + } + + values.append("\n}"); + + fValues.setInitString(values.toString()); + jClass.addField(fValues); + + //-- #valueOf method + JMethod method = new JMethod("valueOf", jClass, + "the String value of the provided " + baseType.getJType()); + method.addParameter(new JParameter(SGTypes.STRING, "string")); + method.getModifiers().setStatic(true); + jClass.addMethod(method); + jdc = method.getJDocComment(); + jdc.appendComment("Returns the " + baseType.getJType()); + jdc.appendComment(" based on the given String value."); + jsc = method.getSourceCode(); + + jsc.add("for (int i = 0; i < values.length; i++) {"); + jsc.add("}"); + jsc.add("throw new IllegalArgumentException(\""); + jsc.append("Invalid value for "); + jsc.append(className); + jsc.append(": \" + string + \".\");"); + } //-- processEnumerationAsBaseType + + /** + * Attempts to translate a simpleType enumeration value into a legal java + * identifier. Translation is through a couple of simple rules: + * + * if the value parses as a non-negative int, the string 'VALUE_' is + * prepended to it + * if the value parses as a negative int, the string 'VALUE_NEG_' is + * prepended to it + * the value is uppercased + * the characters [](){}<>'`" are removed + * the characters |\/?~!@#$%^&*-+=:;., and any + * whitespace are replaced with _ + * + * @param enumBinding if not null, a possible custom binding for this enum + * @param facet the facet whose enum value is being translated. + * @param upperCase controls whether the generated symbol gets uppercased. Overridden by this.shouldNotUpperCase. + * @return the identifier for the enum value + * + * @author rhett-sutphin@uiowa.edu + */ + private String translateEnumValueToIdentifier(final EnumBindingType enumBinding, + final Facet facet, boolean upperCase) { + String enumValue = facet.getValue(); + + try { + String enumerationValue = null; + int intVal = Integer.parseInt(facet.getValue()); + + String customMemberName = null; + if (enumBinding != null) { + customMemberName = + getCustomMemberName(enumBinding, String.valueOf(intVal)); + } + + if (customMemberName != null) { + enumerationValue = customMemberName; + } else { + if (intVal >= 0) { + enumerationValue = "VALUE_" + intVal; + } else { + enumerationValue = "VALUE_NEG_" + Math.abs(intVal); + } + } + + + return enumerationValue; + } catch (NumberFormatException e) { + // just keep going + } + + StringBuilder sb = new StringBuilder(32); + String customMemberName = null; + + if (enumBinding != null) { + customMemberName = getCustomMemberName(enumBinding, enumValue); + } + + if (customMemberName != null) { + sb.append(customMemberName); + } else { + if (upperCase && !shouldNotUpperCase) + sb.append(enumValue.toUpperCase()); + else + sb.append(enumValue); + int i = 0; + while (i < sb.length()) { + char c = sb.charAt(i); + if ("[](){}<>'`\"".indexOf(c) >= 0) { + sb.deleteCharAt(i); + i--; + } else if (Character.isWhitespace(c) || "\\/?~!@#$%^&*-+=:;.,".indexOf(c) >= 0) { + sb.setCharAt(i, '_'); + } + i++; + } + } + return sb.toString(); + } + + /** + * Returns a custom member name (if defined). + * @param enumBinding The {@link EnumBindingType} instance + * @param enumValue The enumeration value. + * @return the custom member name + */ + private String getCustomMemberName(final EnumBindingType enumBinding, + final String enumValue) { + // check whether there's a custom binding for the member name + String customMemberName = null; + EnumMember[] enumMembers = enumBinding.getEnumMember(); + for (int i = 0; i < enumMembers.length; i++) { + if (enumMembers[i].getValue().equals(enumValue)) { + customMemberName = enumMembers[i].getJavaName(); + } + } + return customMemberName; + } + + /** + * Set to true if enumerated type lookups should be performed in a case + * insensitive manner. + * + * @param caseInsensitive when true + */ + public void setCaseInsensitive(final boolean caseInsensitive) { + _caseInsensitive = caseInsensitive; + } + + /** + * Escapes special characters in the given String so that it can be printed + * correctly. + * + * @param str the String to escape + * @return the escaped String, or null if the given String was null. + */ + private static String escapeValue(final String str) { + if (str == null) { + return str; + } + + StringBuilder sb = new StringBuilder(); + char[] chars = str.toCharArray(); + + for (int i = 0; i < chars.length; i++) { + char ch = chars[i]; + switch (ch) { + case '\\': + case '\"': + case '\'': + sb.append('\\'); + break; + default: + break; + } + sb.append(ch); + } + return sb.toString(); + } //-- escapeValue + +}
- * public class {name} { - * // list of values - * {type}[] values = { - * ... - * }; - * - * // Returns true if the given value is part - * // of this enumeration - * public boolean contains({type} value); - * - * // Returns the {type} value whose String value - * // is equal to the given String - * public {type} valueOf(String strValue); - * } - *
[](){}<>'`"
|\/?~!@#$%^&*-+=:;.,
_
+ * public class {name} { + * // list of values + * {type}[] values = { + * ... + * }; + * + * // Returns true if the given value is part + * // of this enumeration + * public boolean contains({type} value); + * + * // Returns the {type} value whose String value + * // is equal to the given String + * public {type} valueOf(String strValue); + * } + *