JdbcTypeFinder.java

package org.andromda.jdbcmetadata;

import java.lang.reflect.Field;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import org.andromda.core.mapping.Mappings;
import org.andromda.metafacades.uml.TypeMappings;
import org.apache.log4j.Logger;

/**
 * <p>
 * Provides the ability to find a <code>java.sql.Types</code> field name based
 * on an int value. Maps from a JDBC SQL datatype to a UML datatype, taking into
 * account the nullability, size, precision, and scale.
 * </p>
 *
 * @author Bob Fields
 */
public class JdbcTypeFinder
{
    private static final Logger LOGGER = Logger.getLogger(JdbcTypeFinder.class);
    private static final Map<Object, String> jdbcTypes = new HashMap<Object, String>();

    /**
     * Stores the schema types to model type mappings.
     */
    private Mappings typeMappings = null;
    private Mappings wrapperMappings = null;

    /**
     * Sets the <code>mappingsUri</code> which is the URI to the sql types to
     * model type mappings.
     *
     * @param typeMappingsUri The typeMappings to set.
     * @param wrapperMappingsUri The URI for the wrapper mappings xml file
     */
    public void setTypeMappings(String typeMappingsUri, String wrapperMappingsUri)
    {
        try
        {
            this.typeMappings = Mappings.getInstance(typeMappingsUri);
            this.wrapperMappings = Mappings.getInstance(wrapperMappingsUri);
        }
        catch (final Throwable throwable)
        {
            throw new RuntimeException(throwable);
        }
    }

    /**
     * Sets the <code>mappingsUri</code> which is the URI to the sql types to
     * model type mappings.
     * @param jdbcType The SQL datatype from the DBMS
     * @param nullable If the SQL column is nullable (return wrapper types)
     * @param size Number of digits or characters in the datatype, 1 to 38 for numeric
     * @param scale Number of decimal places in the datatype -84 to +127
     * @param identifier If the column is part of the table primary key
     * @param unique If unique, umlDatatype will be nullable for findBy queries
     * @return umlType String
     */
    public String getUmlType(String jdbcType, boolean nullable, int size, Integer scale,
        boolean identifier, boolean unique)
    {
        String umlType = jdbcType;
        if (isNumeric(jdbcType) && scale != null)
        {
            // Nothing to the right of the decimal point
            if (scale.intValue() < 1)
            {
                // Change from decimal to integer type based on size
                if (size < 4)
                {
                    jdbcType = "TINYINT"; // byte
                }
                else if (size < 6)
                {
                    jdbcType = "SMALLINT"; // short
                }
                else if (size < 11)
                {
                    jdbcType = "INT"; // int
                }
                else //if (size < 20)
                {
                    jdbcType = "BIGINT"; // long
                }
                // else keep existing type, i.e. DECIMAL = BigDecimal
            }
            else if (scale.intValue()==2)
            {
                // Two decimal places - assume Money datatype
                jdbcType = "MONEY"; // long
            }
        }
        // Get back Char instead of String datatype
        else if (jdbcType.startsWith("CHAR"))
        {
            if (size == 1)
            {
                jdbcType += "(1)";
            }
        }
        if (this.typeMappings.containsFrom(jdbcType))
        {
            umlType = this.typeMappings.getTo(jdbcType);
        }
        // So that it can be used in findBy criteria queries with nullable values
        if (nullable || identifier || unique)
        {
            umlType = this.wrapperMappings.getTo(umlType);
        }
        LOGGER.info("getUmlType umlType=" + umlType + " jdbcType=" + jdbcType + " nullable=" + nullable + " size=" + size + " scale=" + scale
                + " identifier=" + identifier + " unique=" + unique);
        return umlType;
    }

    /**
     * @param jdbcType
     * @return true if numeric
     */
    public static boolean isNumeric(String jdbcType)
    {
        boolean numeric = false;
        if (jdbcType.startsWith("NUM") || jdbcType.startsWith("TINYINT") ||
            jdbcType.startsWith("INT") || jdbcType.startsWith("SMALLINT") ||
            jdbcType.startsWith("MEDIUMINT") || jdbcType.startsWith("SIGNED") ||
            jdbcType.startsWith("BIGINT") || jdbcType.startsWith("IDENTITY") ||
            jdbcType.startsWith("DEC") || jdbcType.startsWith("DOUBLE") ||
            jdbcType.startsWith("FLOAT") || jdbcType.startsWith("REAL")
            )
        {
            numeric = true;
        }
        return numeric;
    }

    /**
     * Gets the mappings from primitive types to wrapper types. Some languages
     * have primitives (i.e., Java) and some languages don't, so therefore this
     * property is optional.
     * @param mappingsUri
     *
     * @return the wrapper mappings
     */
    protected TypeMappings getWrapperMappings(String mappingsUri)
    {
        TypeMappings mappings = TypeMappings.getInstance(mappingsUri);
        /*final String propertyName = "wrapperMappingsUri";
        final Object property = this.getConfiguredProperty(propertyName);
        TypeMappings mappings = null;
        if (property instanceof String)
        {
            final String uri = (String)property;
            try
            {
                mappings = TypeMappings.getInstance(uri);
                this.setProperty(
                    propertyName,
                    mappings);
            }
            catch (final Exception ex)
            {
                final String errMsg = "Error getting '" + propertyName + "' --> '" + uri + '\'';
                ClassifierFacadeLogicImpl.LOGGER.error(
                    errMsg, ex);

                // don't throw the exception
            }
        }
        else
        {
            mappings = (TypeMappings)property;
        }*/
        return mappings;
    }

    /**
     * Initialize the <code>jdbcTypes</code> Map.
     */
    static
    {
        try
        {
            Field[] fields = Types.class.getFields();
            int fieldsNum = fields.length;
            Field field;
            for (int ctr = 0; ctr < fieldsNum; ctr++)
            {
                field = fields[ctr];
                jdbcTypes.put(
                    field.get(null),
                    field.getName());
            }
        }
        catch (Throwable th)
        {
            throw new RuntimeException(th);
        }
    }

    /**
     * Finds the name of the <code>jdbcType</code> passed in, or null if there
     * is no type in the java.sql.Types class matching the given
     * <code>jdbcType</code>.
     *
     * @param jdbcType the JDBC type to find.
     * @return the JDBC type name.
     */
    public static String find(int jdbcType)
    {
        return jdbcTypes.get(Integer.valueOf(jdbcType));
    }
}