/* * 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 { 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<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 (int i = 0; i < annotationBuilders.length; i++) { AnnotationBuilder annotationBuilder = annotationBuilders[i]; annotationBuilder.addEnumConstantAnnotations(facet, enumConstant); } jEnum.addEnumConstant(enumConstant); enumCount++; } // custom annotations for (int i = 0; i < annotationBuilders.length; i++) { AnnotationBuilder annotationBuilder = annotationBuilders[i]; 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 (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 }