/*
 * 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.JAnnotationType;
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 <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
 * @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<Facet> 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 {
          String init = new StringBuilder(32).append("new ").append(className).append('(')
              .append(count).append(", \"").append(escapeValue(value)).append("\")").toString();
          jsc.append(init);
        }
        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<Facet> enumeration) {

    AnnotationBuilder[] annotationBuilders =
        state.getSGStateInfo().getSourceGenerator().getAnnotationBuilders();

    JEnum jEnum = (JEnum) state.getJClass();

    jEnum.removeInterface("java.io.Serializable");
    jEnum.removeAnnotation(new JAnnotationType("SuppressWarnings"));

    // add value field
    JField valueField = new JField(new JClass("java.lang.String"), "value");
    JModifiers modifiers = new JModifiers();
    modifiers.setFinal(true);
    modifiers.makePrivate();
    valueField.setModifiers(modifiers);
    jEnum.addField(valueField);

    // add enumConstants field
    JField enumConstantsField =
        new JField(new JClass("java.util.Map<java.lang.String, " + jEnum.getLocalName() + ">"),
            "enumConstants");
    modifiers = new JModifiers();
    modifiers.setFinal(true);
    modifiers.makePrivate();
    modifiers.setStatic(true);
    enumConstantsField.setModifiers(modifiers);
    enumConstantsField
        .setInitString("new java.util.HashMap<java.lang.String, " + jEnum.getLocalName() + ">()");
    jEnum.addField(enumConstantsField);

    // add static initialization of enumConstants field
    JSourceCode sourceCode = jEnum.getStaticInitializationCode();
    sourceCode.add("for (" + jEnum.getLocalName() + " c: " + jEnum.getLocalName() + ".values()) {");
    sourceCode.indent();
    sourceCode.indent();
    sourceCode.add(jEnum.getLocalName() + "." + enumConstantsField.getName() + ".put(c."
        + valueField.getName() + ", c);");
    sourceCode.unindent();
    sourceCode.add("}");

    addValueMethod(jEnum);
    addFromValueMethod(jEnum, enumConstantsField);
    addSetValueMethod(jEnum);
    addToStringMethod(jEnum);

    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),
                new String[] {"\"" + facet.getValue() + "\""});
      } else {
        enumConstant =
            new JEnumConstant("VALUE_" + enumCount, new String[] {"\"" + facet.getValue() + "\""});
      }

      // custom annotations
      for (AnnotationBuilder annotationBuilder : annotationBuilders) {
        annotationBuilder.addEnumConstantAnnotations(facet, enumConstant);
      }

      jEnum.addEnumConstant(enumConstant);
      enumCount++;
    }

    // custom annotations
    for (AnnotationBuilder annotationBuilder : annotationBuilders) {
      annotationBuilder.addEnumAnnotations(simpleType, jEnum);
    }
  }

  /**
   * Adds a {@link JMethod} instance for "value".
   * 
   * @param jEnum The {@link JEnum} instance to add this method to.
   */
  private void addValueMethod(JEnum jEnum) {
    JMethod valueMethod =
        new JMethod("value", new JClass("java.lang.String"), "the value of this constant");
    valueMethod.setSourceCode("return this.value;");
    jEnum.addMethod(valueMethod, false);
  }

  /**
   * Adds a {@link JMethod} instance for "toString".
   * 
   * @param jEnum The {@link JEnum} instance to add this method to.
   */
  private void addToStringMethod(JEnum jEnum) {
    JMethod toStringMethod =
        new JMethod("toString", new JClass("java.lang.String"), "the value of this constant");
    toStringMethod.setSourceCode("return this.value;");
    jEnum.addMethod(toStringMethod, false);
  }

  /**
   * Adds a {@link JMethod} instance for "setValue(String)".
   * 
   * @param jEnum The {@link JEnum} instance to add this method to.
   */
  private void addSetValueMethod(JEnum jEnum) {
    JMethod setValueMethod = new JMethod("setValue");
    setValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value"));
    jEnum.addMethod(setValueMethod, false);
  }

  /**
   * Adds a {@link JMethod} instance for "fromValue".
   * 
   * @param jEnum The {@link JEnum} instance to add this method to.
   */
  private void addFromValueMethod(JEnum jEnum, JField enumConstantsField) {
    JModifiers modifiers;
    JSourceCode sourceCode;
    JMethod fromValueMethod = new JMethod("fromValue", jEnum, "the constant for this value");
    fromValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value"));
    sourceCode = new JSourceCode();
    sourceCode.add(jEnum.getLocalName() + " c = " + jEnum.getLocalName() + "."
        + enumConstantsField.getName() + ".get(value);");
    sourceCode.add("if (c != null) {");
    sourceCode.indent();
    sourceCode.add("return c;");
    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);
  }

  /**
   * 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<Facet> 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<Object, Object> members"
          + " = new java.util.Hashtable<Object, Object>();");
    } 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<Object> 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:
   * 
   * <pre>
   *     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);
   *     }
   * </pre>
   * 
   * @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<Facet> 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:
   * <ul>
   * <li>if the value parses as a non-negative int, the string 'VALUE_' is prepended to it</li>
   * <li>if the value parses as a negative int, the string 'VALUE_NEG_' is prepended to it</li>
   * <li>the value is uppercased</li>
   * <li>the characters <code>[](){}<>'`"</code> are removed</li>
   * <li>the characters <code>|\/?~!@#$%^&*-+=:;.,</code> and any whitespace are replaced with
   * <code>_</code></li>
   * </ul>
   * 
   * @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 (EnumMember enumMember : enumMembers) {
      if (enumMember.getValue().equals(enumValue)) {
        customMemberName = enumMember.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 (char ch : chars) {
      switch (ch) {
        case '\\':
        case '\"':
        case '\'':
          sb.append('\\');
          break;
        default:
          break;
      }
      sb.append(ch);
    }
    return sb.toString();
  } // -- escapeValue

}
