JavaTypeConverter.java

package org.andromda.utils;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.StringUtils;

/**
 * A utility object used by the code generator when it needs to convert an object
 * from one data type to another.
 *
 * @author Joel Kozikowski
 * @author Bob Fields
 */
public class JavaTypeConverter
{
    private List<String> javaTypeConversionIgnoreList = new ArrayList<String>();

    /**
     * Specifies a list of one or more fully qualified java types that should be ignored
     * whenever a type conversion is done.  See Spring namespace property "javaTypeConversionIgnoreList"
     * @param commaSeparatedIgnoreList comma separated list of types to be ignored
     */
    public void setJavaTypeConversionIgnoreList(String commaSeparatedIgnoreList)
    {
        javaTypeConversionIgnoreList = new ArrayList<String>();
        if (commaSeparatedIgnoreList != null)
        {
            final String[] typeList = commaSeparatedIgnoreList.split("\\s*,\\s*");
            javaTypeConversionIgnoreList.addAll(Arrays.asList(typeList));
        }
    }

    private static class ConversionEntry
    {
        // - not private to increase performance of
        //   inner class access
        final String sourceType;
        final String targetType;
        final String conversionPattern;

        public ConversionEntry(
            final String sourceType,
            final String targetType,
            final String conversionPattern)
        {
            this.sourceType = sourceType;
            this.targetType = targetType;
            this.conversionPattern = conversionPattern;
        }
    }

    private static ConversionEntry[] conversionTable =
        {
            new ConversionEntry("int", "java.lang.Integer", "new java.lang.Integer({0})"),
            new ConversionEntry("int", "java.lang.String", "java.lang.String.valueOf({0})"),
            new ConversionEntry("int", "long", "(long){0}"),
            new ConversionEntry("int", "double", "(double){0}"),
            new ConversionEntry("int", "float", "(float){0}"),
            new ConversionEntry("int", "boolean", "({0} != 0)"),
            new ConversionEntry("java.lang.Integer", "int", "({0} == null ? 0 : {0}.intValue())"),
            new ConversionEntry("java.lang.Integer", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.lang.String", "int", "({0} == null ? 0 : Integer.valueOf({0}).intValue())"),
            new ConversionEntry("java.lang.String", "java.lang.Integer", "({0} == null ? null : Integer.valueOf({0}))"),

            new ConversionEntry("long", "java.lang.Long", "new java.lang.Long({0})"),
            new ConversionEntry("long", "java.lang.String", "java.lang.String.valueOf({0})"),
            new ConversionEntry("long", "int", "(int){0}"),
            new ConversionEntry("long", "double", "(double){0}"),
            new ConversionEntry("long", "float", "(float){0}"),
            new ConversionEntry("long", "boolean", "({0} != 0)"),
            new ConversionEntry("java.lang.Long", "long", "({0} == null ? 0 : {0}.longValue())"),
            new ConversionEntry("java.lang.Long", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.lang.String", "long", "({0} == null ? 0 : Long.valueOf({0}).longValue())"),
            new ConversionEntry("java.lang.String", "java.lang.Long", "({0} == null ? null : Long.valueOf({0}))"),

            new ConversionEntry("double", "java.lang.Double", "new java.lang.Double({0})"),
            new ConversionEntry("double", "java.lang.String", "java.lang.String.valueOf({0})"),
            new ConversionEntry("double", "int", "(int){0}"),
            new ConversionEntry("double", "long", "(long){0}"),
            new ConversionEntry("double", "float", "(float){0}"),
            new ConversionEntry("double", "boolean", "({0} != 0.0)"),
            new ConversionEntry("java.lang.Double", "double", "({0} == null ? 0.0 : {0}.doubleValue())"),
            new ConversionEntry("java.lang.Double", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.lang.String", "double", "({0} == null ? 0.0 : Double.valueOf({0}).doubleValue())"),
            new ConversionEntry("java.lang.String", "java.lang.Double", "({0} == null ? null : Double.valueOf({0}))"),

            new ConversionEntry("float", "java.lang.Float", "new java.lang.Float({0})"),
            new ConversionEntry("float", "java.lang.String", "java.lang.String.valueOf({0})"),
            new ConversionEntry("float", "int", "(int){0}"),
            new ConversionEntry("float", "long", "(long){0}"),
            new ConversionEntry("float", "double", "(double){0}"),
            new ConversionEntry("float", "boolean", "({0} != 0.0f)"),
            new ConversionEntry("java.lang.Float", "float", "({0} == null ? 0.0f : {0}.floatValue())"),
            new ConversionEntry("java.lang.Float", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.lang.String", "float", "({0} == null ? 0.0f : Float.valueOf({0}).floatValue())"),
            new ConversionEntry("java.lang.String", "java.lang.Float", "({0} == null ? null : Float.valueOf({0}))"),

            new ConversionEntry("int", "java.math.BigInteger", "java.math.BigInteger.valueOf((long){0})"),
            new ConversionEntry("java.math.BigInteger", "int", "({0} == null ? 0 : {0}.intValue())"),
            new ConversionEntry("long", "java.math.BigInteger", "java.math.BigInteger.valueOf({0})"),
            new ConversionEntry("java.math.BigInteger", "long", "({0} == null ? 0 : {0}.longValue())"),
            new ConversionEntry("java.math.BigInteger", "float", "({0} == null ? 0.0f : {0}.floatValue())"),
            new ConversionEntry("float", "java.math.BigInteger", "new java.math.BigDecimal((double){0}).toBigInteger()"),
            new ConversionEntry("java.math.BigInteger", "double", "({0} == null ? 0.0 : {0}.doubleValue())"),
            new ConversionEntry("double", "java.math.BigInteger", "new java.math.BigDecimal({0}).toBigInteger()"),
            new ConversionEntry("java.lang.String", "java.math.BigInteger", "new java.math.BigInteger({0})"),
            new ConversionEntry("java.math.BigInteger", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.math.BigDecimal", "java.math.BigInteger", "({0} == null ? null : {0}.toBigInteger())"),
            new ConversionEntry("java.lang.Integer", "java.math.BigInteger", "({0} == null ? null : java.math.BigInteger.valueOf((long){0}.intValue()))"),
            new ConversionEntry("java.math.BigInteger", "java.lang.Integer", "({0} == null ? null : new java.lang.Integer({0}.intValue()))"),
            new ConversionEntry("java.lang.Long", "java.math.BigInteger", "({0} == null ? null : java.math.BigInteger.valueOf({0}.longValue()))"),
            new ConversionEntry("java.math.BigInteger", "java.lang.Long", "({0} == null ? null : new java.lang.Long({0}.longValue()))"),
            new ConversionEntry("java.lang.Float", "java.math.BigInteger", "({0} == null ? null : new java.math.BigDecimal((double){0}.floatValue()).toBigInteger())"),
            new ConversionEntry("java.lang.Double", "java.math.BigInteger", "({0} == null ? null : new java.math.BigDecimal({0}.doubleValue()).toBigInteger())"),

            new ConversionEntry("int", "java.math.BigDecimal", "java.math.BigDecimal.valueOf((long){0})"),
            new ConversionEntry("java.math.BigDecimal", "int", "({0} == null ? 0 : {0}.intValue())"),
            new ConversionEntry("long", "java.math.BigDecimal", "java.math.BigDecimal.valueOf({0})"),
            new ConversionEntry("java.math.BigDecimal", "long", "({0} == null ? 0 : {0}.longValue())"),
            new ConversionEntry("java.math.BigDecimal", "float", "({0} == null ? 0.0f : {0}.floatValue())"),
            new ConversionEntry("float", "java.math.BigDecimal", "new java.math.BigDecimal((double){0})"),
            new ConversionEntry("java.math.BigDecimal", "double", "({0} == null ? 0.0 : {0}.doubleValue())"),
            new ConversionEntry("double", "java.math.BigDecimal", "new java.math.BigDecimal({0})"),
            new ConversionEntry("java.lang.String", "java.math.BigDecimal", "new java.math.BigDecimal({0})"),
            new ConversionEntry("java.math.BigDecimal", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.math.BigInteger", "java.math.BigDecimal", "new java.math.BigDecimal({0})"),
            new ConversionEntry("java.lang.Integer", "java.math.BigDecimal", "({0} == null ? null : java.math.BigDecimal.valueOf((long){0}.intValue()))"),
            new ConversionEntry("java.math.BigDecimal", "java.lang.Integer", "({0} == null ? null : new java.lang.Integer({0}.intValue()))"),
            new ConversionEntry("java.lang.Long", "java.math.BigDecimal", "({0} == null ? null : java.math.BigDecimal.valueOf({0}.longValue()))"),
            new ConversionEntry("java.math.BigDecimal", "java.lang.Long", "({0} == null ? null : new java.lang.Long({0}.longValue()))"),
            new ConversionEntry("java.lang.Float", "java.math.BigDecimal", "({0} == null ? null : new java.math.BigDecimal((double){0}.floatValue()))"),
            new ConversionEntry("java.lang.Double", "java.math.BigDecimal", "({0} == null ? null : new java.math.BigDecimal({0}.doubleValue()))"),

            new ConversionEntry("boolean", "java.lang.Boolean", "new java.lang.Boolean({0})"),
            new ConversionEntry("boolean", "java.lang.String", "java.lang.String.valueOf({0})"),
            new ConversionEntry("boolean", "int", "({0} ? 1 : 0)"),
            new ConversionEntry("boolean", "long", "({0} ? 1 : 0)"),
            new ConversionEntry("boolean", "double", "({0} ? 1.0 : 0.0)"),
            new ConversionEntry("boolean", "float", "({0} ? 1.0f : 0.0f)"),
            new ConversionEntry("java.lang.Boolean", "boolean", "({0} == null ? false : {0}.booleanValue())"),
            new ConversionEntry("java.lang.Boolean", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.lang.String", "boolean", "({0} == null ? false : Boolean.valueOf({0}).booleanValue())"),
            new ConversionEntry("java.lang.String", "java.lang.Boolean", "({0} == null ? null : Boolean.valueOf({0}))"),

            new ConversionEntry("string", "java.lang.String", "{0}"),
            new ConversionEntry("java.lang.String", "string", "{0}"),

            new ConversionEntry("java.util.Date", "java.lang.String",
                "({0} == null ? null : new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm:ssZ\").format({0}))"),
            new ConversionEntry("java.sql.Timestamp", "java.lang.String",
                "({0} == null ? null : new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm:ssZ\").format({0}))"),
            new ConversionEntry("java.sql.Time", "java.lang.String",
                    "({0} == null ? null : new java.text.SimpleDateFormat(\"HH:mm:ssZ\").format({0}))"),
            new ConversionEntry("java.sql.Date", "java.lang.String",
                "({0} == null ? null : new java.text.SimpleDateFormat(\"yyyy-MM-dd\").format({0}))"),
            new ConversionEntry("java.lang.String", "java.util.Date",
                "({0} == null ? null : new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm:ssZ\").parse({0}))"),
            new ConversionEntry("java.lang.String", "java.sql.Timestamp",
                "({0} == null ? null : new java.sql.Timestamp(new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm:ssZ\").parse({0}).getTime()))"),
            new ConversionEntry("java.lang.String", "java.sql.Time",
                "({0} == null ? null : new java.sql.Time(new java.text.SimpleDateFormat(\"HH:mm:ssZ\").parse({0}).getTime()))"),
            new ConversionEntry("java.lang.String", "java.sql.Date",
                "({0} == null ? null : new java.sql.Date(new java.text.SimpleDateFormat(\"yyyy-MM-dd\").parse({0}).getTime()))"),
            new ConversionEntry("java.sql.Timestamp", "java.util.Date", "{0}"),
            new ConversionEntry("java.sql.Timestamp", "java.sql.Time", "({0} == null ? null : new java.sql.Time({0}.getTime()))"),
            new ConversionEntry("java.sql.Timestamp", "java.sql.Date", "({0} == null ? null : new java.sql.Date({0}.getTime()))"),
            new ConversionEntry("java.sql.Time", "java.util.Date", "{0}"),
            new ConversionEntry("java.sql.Time", "java.sql.Timestamp", "({0} == null ? null : new java.sql.Timestamp({0}.getTime()))"),
            new ConversionEntry("java.sql.Date", "java.util.Date", "{0}"),
            new ConversionEntry("java.sql.Date", "java.sql.Timestamp", "({0} == null ? null : new java.sql.Timestamp({0}.getTime()))"),
            new ConversionEntry("java.util.Date", "java.sql.Timestamp", "({0} == null ? null : new java.sql.Timestamp({0}.getTime()))"),
            new ConversionEntry("java.util.Date", "java.sql.Time", "({0} == null ? null : new java.sql.Time({0}.getTime()))"),
            new ConversionEntry("java.util.Date", "java.sql.Date", "({0} == null ? null : new java.sql.Date({0}.getTime()))"),
            new ConversionEntry("java.util.Date", "long", "({0} == null ? 0 : {0}.getTime())"),
            new ConversionEntry("java.sql.Timestamp", "long", "({0} == null ? 0 : {0}.getTime())"),
            new ConversionEntry("java.sql.Time", "long", "({0} == null ? 0 : {0}.getTime())"),
            new ConversionEntry("java.sql.Date", "long", "({0} == null ? 0 : {0}.getTime())"),
            new ConversionEntry("long", "java.sql.Date", "new java.sql.Date({0})"),
            new ConversionEntry("long", "java.sql.Time", "new java.sql.Time({0})"),
            new ConversionEntry("long", "java.sql.Timestamp", "new java.sql.Timestamp({0})"),
            new ConversionEntry("long", "java.util.Date", "new java.util.Date({0})"),

            new ConversionEntry("java.lang.Character", "java.lang.String", "({0} == null ? null : {0}.toString())"),
            new ConversionEntry("java.lang.String", "java.lang.Character", "({0} == null || {0}.length() == 0 ? null : java.lang.Character.valueOf({0}.charAt(0)))"),

            new ConversionEntry("java.lang.Object", "java.lang.String", "{0} == null ? null : {0}.toString()"),
            new ConversionEntry("java.lang.Object", "java.lang.Integer", "{0} == null ? null : {0}.toString()"),
            new ConversionEntry("java.lang.Object", "java.lang.Long", "{0} == null ? null : {0}.toString()"),
            new ConversionEntry("java.lang.Object", "java.lang.Float", "{0} == null ? null : {0}.toString()"),
            new ConversionEntry("java.lang.Object", "java.lang.Double", "{0} == null ? null : {0}.toString()"),
            new ConversionEntry("java.lang.Object", "java.lang.Character", "({0} == null || {0}.toString().length() == 0 ? null : java.lang.Character.valueOf({0}.toString().charAr(0)))"),
            new ConversionEntry("java.lang.String", "java.lang.Object", "{0}"),
            new ConversionEntry("java.lang.Integer", "java.lang.Object", "{0}"),
            new ConversionEntry("java.lang.Long", "java.lang.Object", "{0}"),
            new ConversionEntry("java.lang.Float", "java.lang.Object", "{0}"),
            new ConversionEntry("java.lang.Double", "java.lang.Object", "{0}"),
            new ConversionEntry("java.lang.Character", "java.lang.Object", "{0}"),
            new ConversionEntry("java.lang.Character", "char", "({0} == null ? 0 : {0}.charValue())"),
            new ConversionEntry("char", "java.lang.Character", "java.lang.Character.valueOf({0})"),
        };

    /**
     * Attempts to code a type conversion from the specified source value to the target
     * type that can be used on the right hand side of an assignment. If such a conversion
     * exists, the Java code fragment to do that will be returned. If no such conversion exists,
     * null is returned.
     * @param sourceType The fully qualified data type of the source value
     * @param sourceValue The actual source value (usually a variable name)
     * @param targetType The fully qualified data type of the target value
     * @return The converted value, or null if no conversion can be done.
     */
    public String typeConvert(
        final String sourceType,
        final String sourceValue,
        final String targetType)
    {
        String convertedValue;

        if (StringUtils.isBlank(sourceType) || StringUtils.isBlank(targetType) ||
            javaTypeConversionIgnoreList.contains(sourceType) || javaTypeConversionIgnoreList.contains(targetType))
        {
            convertedValue = null;
        }
        else if (sourceType.equals(targetType))
        {
            convertedValue = sourceValue;
        }
        else
        {
            convertedValue = null;

            for (int i = 0; i < conversionTable.length && convertedValue == null; i++)
            {
                if (conversionTable[i].sourceType.equals(sourceType) &&
                    conversionTable[i].targetType.equals(targetType))
                {
                    convertedValue =
                        MessageFormat.format(
                            conversionTable[i].conversionPattern,
                                sourceValue);
                } // if
            } // for

            if (convertedValue == null && sourceType.startsWith("java.lang.") && !sourceType.endsWith("[]"))
            {
                // If source is a primitive wrapper, try to convert
                // to the base primitive first...
                String primitiveSource = sourceType.substring(10).toLowerCase();
                if ("integer".equals(primitiveSource))
                {
                    primitiveSource = "int";
                }
                else if ("character".equals(primitiveSource)) {
                    primitiveSource = "char";
                }

                String interimValue = typeConvert(
                        sourceType,
                        sourceValue,
                        primitiveSource);

                if (interimValue != null)
                {
                    convertedValue = typeConvert(
                            primitiveSource,
                            interimValue,
                            targetType);
                }
            }

            if (convertedValue == null && targetType.startsWith("java.lang.") && !targetType.endsWith("[]"))
            {
                // One last try - if target is a primitive wrapper, try to convert
                // to the base primitive first...
                String primitiveTarget = targetType.substring(10).toLowerCase();
                if ("integer".equals(primitiveTarget))
                {
                    primitiveTarget = "int";
                }
                else if ("character".equals(primitiveTarget))
                {
                    primitiveTarget = "char";
                }

                String interimValue = typeConvert(
                        sourceType,
                        sourceValue,
                        primitiveTarget);
                if (interimValue != null)
                {
                    convertedValue = typeConvert(
                            primitiveTarget,
                            interimValue,
                            targetType);
                }
            }
        }

        return convertedValue;
    }

    private static final List<String> javaLangList = new ArrayList<String>();
    /**
     * Creates a java.lang. fully qualified name from the given <code>name</code>,
     * inserting 'java.lang.' in front if needed. Many EJB3 and Hibernate configuration files
     * require java.lang. in the type name, where Java code does not (gives compiler warning).
     * This allows UML Primitive types to be modeled and mapped without java.lang. implementation.
     *
     * @param name the name of the model element.
     * @return the java.lang. fully qualified name, if needed.
     */
    public static String getJavaLangTypeName(String name)
    {
        if (StringUtils.isBlank(name))
        {
            return name;
        }
        synchronized (javaLangList)
        {
            if (javaLangList.isEmpty())
            {
                javaLangList.add("String");
                javaLangList.add("Boolean");
                javaLangList.add("Integer");
                javaLangList.add("Long");
                javaLangList.add("Double");
                javaLangList.add("Object");
                javaLangList.add("Short");
                javaLangList.add("Character");
                javaLangList.add("Byte");
                javaLangList.add("Float");
                javaLangList.add("Number");
                javaLangList.add("Math");
                javaLangList.add("Class");
                javaLangList.add("Exception");
                javaLangList.add("Enum");
            }
        }
        // If type has no package and is not the same as lowercase (i.e. primitive type)...
        if (!(name.indexOf('.')>-1) && !(name.equals(name.toLowerCase())))
        {
            if (javaLangList.contains(name)
                || (name.endsWith("[]") && javaLangList.contains(name.substring(0, name.length()-2))))
            {
                name = "java.lang." + name;
            }
        }
        return name;
    }
}