/* * Copyright (c) 2006-2008, Dennis M. Sosnoski All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. 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. Neither the name of * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 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 THE COPYRIGHT OWNER OR 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. */ package org.jibx.schema.codegen; import java.util.ArrayList; import java.util.Arrays; import org.apache.log4j.Logger; import org.eclipse.jdt.core.dom.InfixExpression.Operator; import org.jibx.binding.model.BindingHolder; import org.jibx.binding.model.FormatElement; import org.jibx.schema.elements.FacetElement; import org.jibx.schema.elements.FilteredSegmentList; import org.jibx.schema.elements.SchemaBase; import org.jibx.schema.elements.SimpleRestrictionElement; import org.jibx.schema.elements.FacetElement.Enumeration; /** * Information for an enumeration class to be included in code generated from schema. * * @author Dennis M. Sosnoski */ public class EnumerationClassHolder extends ClassHolder { /** Instance method to get text value. */ public static final String INSTANCEVALUE_METHOD = "value"; /** Static conversion method name, with exception if value not matched. */ public static final String CONVERTFORCE_METHOD = "fromValue"; /** Static conversion method name, with null return if value not matched. */ public static final String CONVERTIF_METHOD = "convert"; /** Logger for class. */ private static final Logger s_logger = Logger.getLogger(EnumerationClassHolder.class.getName()); /** Enumeration group defining the class. */ private Wrapper m_classGroup; /** Binding definition element for this class. */ private FormatElement m_bindingFormat; /** * Constructor. * * @param name class name * @param base base class name * @param pack package information * @param nconv name converter * @param inner use inner classes for substructures */ public EnumerationClassHolder(String name, String base, PackageHolder pack, NameConverter nconv, boolean inner) { super(name, base, pack, nconv, inner); } /** * Constructor for creating a child inner class definition. * * @param name class name * @param context parent class */ protected EnumerationClassHolder(String name, ClassHolder context) { super(name, context); } /** * Get the binding component linked to this class. * * @return binding definition element (<mapping> or <structure>) */ public FormatElement getBinding() { return m_bindingFormat; } /** * Set the binding component linked to this class. * * @param format binding definition element */ public void setBinding(FormatElement format) { m_bindingFormat = format; } /** * Convert an item structure to a class representation. This may include creating child classes, where necessary. * * @param group */ public void createStructure(GroupItem group) { if (group.isEnumeration()) { // set the basic configuration information setNamespace(group.getSchemaComponent().getSchema().getEffectiveNamespace()); m_classGroup = new Wrapper(group, null); // just add String as an import addImport("java.lang.String", false); // import the serializable interface if needed if (IMPLEMENT_SERIALIZABLE) { addImport("java.io.Serializable", false); } } else { throw new IllegalArgumentException("Internal error - group is not an enumeration"); } } /** * Generate this class. * * @param builder class source file builder * @param holder binding holder */ public void generate(SourceBuilder builder, BindingHolder holder) { // setup the class builder Item item = m_classGroup.getItem(); String basename = getSuperClass() == null ? null : getSuperClass().getFullName(); ClassBuilder clasbuilder; String name = getName(); if (m_outerClass == null) { clasbuilder = builder.newMainClass(name, basename, USE_JAVA5_ENUM); } else { clasbuilder = builder.newInnerClass(name, basename, m_outerClass.getBuilder(), USE_JAVA5_ENUM); } // handle the common initialization setBuilder(clasbuilder); initClass(m_classGroup, holder); // create a field to hold the string value String fieldname = m_nameConverter.toFieldName(INSTANCEVALUE_METHOD); clasbuilder.addField(fieldname, "java.lang.String").setPrivateFinal(); // add private constructor with field assignment MethodBuilder constr = clasbuilder.addConstructor(name); constr.createBlock().addAssignVariableToField(INSTANCEVALUE_METHOD, fieldname); constr.setPrivate(); constr.addParameter(INSTANCEVALUE_METHOD, "java.lang.String"); // get the list of facets including enumerations String fullname = getFullName(); if (s_logger.isInfoEnabled()) { s_logger.info("Generating enumeration class " + fullname); } GroupItem group = (GroupItem)item; SimpleRestrictionElement restrict = (SimpleRestrictionElement)group.getFirstChild().getSchemaComponent(); FilteredSegmentList facets = restrict.getFacetsList(); if (USE_JAVA5_ENUM) { // add an enumeration constant for each value defined for (int i = 0; i < facets.size(); i++) { FacetElement facet = (FacetElement)facets.get(i); if (facet.type() == SchemaBase.ENUMERATION_TYPE) { // get value for this enumeration element FacetElement.Enumeration enumelem = (Enumeration)facet; String value = enumelem.getValue(); // define the constant String constname = m_nameSet.add(m_nameConverter.toConstantName(value)); clasbuilder.addConstant(constname, value); } } // add value method returning string value MethodBuilder tostring = clasbuilder.addMethod(INSTANCEVALUE_METHOD, "java.lang.String"); tostring.setPublic(); tostring.createBlock().addReturnNamed(fieldname); // add static convert method returning instance for text value, or null if no match MethodBuilder convert = clasbuilder.addMethod(CONVERTIF_METHOD, fullname); convert.setPublicStatic(); convert.addParameter(INSTANCEVALUE_METHOD, "java.lang.String"); BlockBuilder body = convert.createBlock(); // create the return block when value is matched BlockBuilder retblock = clasbuilder.newBlock(); retblock.addReturnNamed("inst"); // create the method calls to compare current instance text with value InvocationBuilder valueexpr = clasbuilder.createNormalMethodCall("inst", INSTANCEVALUE_METHOD); InvocationBuilder equalsexpr = clasbuilder.createExpressionMethodCall(valueexpr, "equals"); equalsexpr.addVariableOperand(INSTANCEVALUE_METHOD); // embed the complete if statement in body block of for loop BlockBuilder forblock = clasbuilder.newBlock(); forblock.addIfStatement(equalsexpr, retblock); InvocationBuilder loopexpr = clasbuilder.createLocalStaticMethodCall("values"); body.addSugaredForStatement("inst", fullname, loopexpr, forblock); // finish with null return for match not found body.addReturnNull(); } else { // create a static instance of class for each enumeration value ArrayList enumpairs = new ArrayList(); for (int i = 0; i < facets.size(); i++) { FacetElement facet = (FacetElement)facets.get(i); if (facet.type() == SchemaBase.ENUMERATION_TYPE) { // get value for this enumeration element FacetElement.Enumeration enumelem = (Enumeration)facet; String value = enumelem.getValue(); // create a field to hold the corresponding instance of class String constname = m_nameSet.add(m_nameConverter.toConstantName(value)); FieldBuilder field = clasbuilder.addField(constname, name); field.setPublicStaticFinal(); field.setInitializer(clasbuilder.newInstanceFromString(name, value)); // track both the value and the corresponding field name enumpairs.add(new StringPair(value, constname)); } } // sort the pairs by value text StringPair[] pairs = (StringPair[])enumpairs.toArray(new StringPair[enumpairs.size()]); Arrays.sort(pairs); // create array of sorted text values String valuesname = m_nameConverter.toStaticFieldName("values"); NewArrayBuilder array = clasbuilder.newArrayBuilder("java.lang.String"); for (int i = 0; i < pairs.length; i++) { array.addStringLiteralOperand(pairs[i].getKey()); } FieldBuilder field = clasbuilder.addField(valuesname, "java.lang.String[]"); field.setInitializer(array); field.setPrivateStaticFinal(); // create matching array of instances corresponding to sorted values String instsname = m_nameConverter.toStaticFieldName("instances"); array = clasbuilder.newArrayBuilder(fullname); for (int i = 0; i < pairs.length; i++) { array.addVariableOperand(pairs[i].getValue()); } field = clasbuilder.addField(instsname, fullname + "[]"); field.setInitializer(array); field.setPrivateStaticFinal(); // add toString method returning string value MethodBuilder tostring = clasbuilder.addMethod(INSTANCEVALUE_METHOD, "java.lang.String"); tostring.setPublic(); tostring.createBlock().addReturnNamed(fieldname); // add static convert method returning instance for text value, or null if no match MethodBuilder convert = clasbuilder.addMethod(CONVERTIF_METHOD, fullname); convert.setPublicStatic(); convert.addParameter(INSTANCEVALUE_METHOD, "java.lang.String"); BlockBuilder body = convert.createBlock(); // build code to handle search for text value InvocationBuilder invoke = clasbuilder.createStaticMethodCall("java.util.Arrays", "binarySearch"); invoke.addVariableOperand(valuesname); invoke.addVariableOperand(INSTANCEVALUE_METHOD); body.addLocalVariableDeclaration("int", "index", invoke); // create the return block when value is matched BlockBuilder retblock = clasbuilder.newBlock(); retblock.addReturnExpression(clasbuilder.buildArrayIndexAccess(instsname, "index")); // create the null return block when value is not matched BlockBuilder nullblock = clasbuilder.newBlock(); nullblock.addReturnNull(); // finish with the if statement that decides which to execute InfixExpressionBuilder test = clasbuilder.buildNameOp("index", Operator.GREATER_EQUALS); test.addNumberLiteralOperand("0"); body.addIfElseStatement(test, retblock, nullblock); } // add static valueOf method returning instance for text value MethodBuilder valueof = clasbuilder.addMethod(CONVERTFORCE_METHOD, fullname); valueof.setPublicStatic(); valueof.addParameter("text", "java.lang.String"); BlockBuilder body = valueof.createBlock(); m_bindingFormat.setDeserializerName(getBindingName() + ".fromValue"); // build code to call convert method InvocationBuilder invoke = clasbuilder.createLocalStaticMethodCall(CONVERTIF_METHOD); invoke.addVariableOperand("text"); body.addLocalVariableDeclaration(fullname, INSTANCEVALUE_METHOD, invoke); // create the return block when value is found BlockBuilder retblock = clasbuilder.newBlock(); retblock.addReturnNamed(INSTANCEVALUE_METHOD); // create the exception thrown when value is not found BlockBuilder throwblock = clasbuilder.newBlock(); InfixExpressionBuilder strcat = clasbuilder.buildStringConcatenation("Value '"); strcat.addVariableOperand("text"); strcat.addStringLiteralOperand("' is not allowed"); throwblock.addThrowException("IllegalArgumentException", strcat); // finish with the if statement that decides which to execute InfixExpressionBuilder test = clasbuilder.buildNameOp(INSTANCEVALUE_METHOD, Operator.EQUALS); test.addNullOperand(); body.addIfElseStatement(test, throwblock, retblock); // finish with subclass generation (which might be needed, if enumeration uses inlined type) generateInner(builder, holder); } }