/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. 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 the jCoderZ.org Project 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 REGENTS 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 REGENTS AND 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.jcoderz.phoenix.cmpgen2; import java.math.BigDecimal; import java.sql.Date; import java.sql.Timestamp; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.jcoderz.commons.util.Constants; import org.jcoderz.phoenix.sqlparser.ColumnSpec; import org.jcoderz.phoenix.sqlparser.NumericAttribute; /** * @author Albrecht Messner */ public final class TypeMapping { private static final String[] JAVA_PRIMITIVE_TYPES = { Byte.TYPE.getName(), Short.TYPE.getName(), Integer.TYPE.getName(), Long.TYPE.getName(), Float.TYPE.getName(), Double.TYPE.getName(), Character.TYPE.getName(), Boolean.TYPE.getName(), "byte[]" }; private static final String[] PRIMITIVE_TYPE_WRAPPERS = { Byte.class.getName(), Short.class.getName(), Integer.class.getName(), Long.class.getName(), Float.class.getName(), Double.class.getName(), Character.class.getName(), Boolean.class.getName(), "byte[]" }; // these get mapped to java.lang.String private static final String[] STRING_TYPES = { "CHAR", "CHAR2", "NCHAR", "NCHAR2", "VARCHAR", "VARCHAR2", "NVARCHAR", "NVARCHAR2" }; private static final String[] TIMESTAMP_TYPES = { "TIMESTAMP" }; private static final String[] DATE_TYPES = { "DATE" }; // these get mapped to numeric types private static final String[] NUMERIC_TYPES = { "NUMBER", "NUMERIC", "DECIMAL", "INTEGER", "INT", "FLOAT", "REAL" }; private static final String[] FLOAT_TYPES = { "FLOAT", "REAL" }; private static final int INTEGER_PRECISION_LIMIT = 10; private static final int LONG_PRECISION_LIMIT = 19; // put all arrays into hash sets for faster lookup private static final Set STRING_TYPE_SET = new HashSet(); private static final Set NUMERIC_TYPE_SET = new HashSet(); private static final Set TIMESTAMP_TYPE_SET = new HashSet(); private static final Set DATE_TYPE_SET = new HashSet(); private static final Set FLOAT_TYPE_SET = new HashSet(); private static final Set JAVA_PRIMITIVE_TYPE_SET = new HashSet(); static { STRING_TYPE_SET.addAll(Arrays.asList(STRING_TYPES)); NUMERIC_TYPE_SET.addAll(Arrays.asList(NUMERIC_TYPES)); TIMESTAMP_TYPE_SET.addAll(Arrays.asList(TIMESTAMP_TYPES)); DATE_TYPE_SET.addAll(Arrays.asList(DATE_TYPES)); FLOAT_TYPE_SET.addAll(Arrays.asList(FLOAT_TYPES)); JAVA_PRIMITIVE_TYPE_SET.addAll(Arrays.asList(JAVA_PRIMITIVE_TYPES)); } /** * private constructor to avoid instantiation */ private TypeMapping () { } /** * Finds the appropriate type mapping from a given column spec. * * Note that the type returned here is the "simple" type as it is stored * in the database, not the "complex" type. * * @param column the column specification * @param fullyQualified whether the type should contain the package * name or not * @return the simple java type used to store the column in the db * @throws CmpGeneratorException if no type mapping can be found */ public static String getJavaType ( ColumnSpec column, boolean fullyQualified) throws CmpGeneratorException { String javaType = column.getJavaType(); if (javaType == null) { // this gets a mapping for character data types javaType = getTypeMapping(column.getColumnType()); // no character type, try to find a numeric type if (javaType == null) { final List sqlTypeAttributes = column.getDatatypeAttributes(); int att1 = 0, att2 = 0; if (sqlTypeAttributes.size() > 0) { att1 = ((NumericAttribute) sqlTypeAttributes.get(0)).getNumber(); if (sqlTypeAttributes.size() > 1) { att2 = ((NumericAttribute) sqlTypeAttributes.get(1)) .getNumber(); } } javaType = getNumberTypeMapping( column.getColumnType(), att1, att2); } } else { // a java type has been specified final String loadMethod = column.getLoadMethod(); if (loadMethod != null) { // hey! it's a complex type // the "simple" java type can be found in the signature of the // load method final int openParen = loadMethod.indexOf('('); final int closeParen = loadMethod.indexOf(')'); if (openParen == -1 || closeParen == -1 || closeParen <= openParen) { throw new CmpGeneratorException( loadMethod + " is an invalid load method signature"); } javaType = loadMethod.substring(openParen + 1, closeParen).trim(); if (javaType.length() <= 0) { throw new CmpGeneratorException( loadMethod + " is an invalid load method signature"); } } } if (javaType == null) { throw new CmpGeneratorException("No type mapping found for column " + column); } // ok, now we've got a java type. if it is a primitive type // and the column is nullable, then we must use the wrapper object // instead if (isPrimitiveType(javaType) && !column.isNotNull()) { javaType = primitiveToObject(javaType); } // check if we should return the type fully qualified if (!fullyQualified) { javaType = unqualifyType(javaType); } return javaType; } /** * Returns everything after the last dot in a type name. * @param typeName a java type name * @return the unqualified type name, or the argument if * it was not a qualified java type name */ public static String unqualifyType (String typeName) { final int dotIndex = typeName.lastIndexOf('.'); return typeName.substring(dotIndex + 1); } /** * Returns the fully qualified java type to which an SQL type is mapped. * * @param sqlType the name of the sql type * @return the FQ type name of the java type, or null if this method can not * find a type mapping */ public static String getTypeMapping (String sqlType) { String javaType; final String s = sqlType.toUpperCase(Constants.SYSTEM_LOCALE); if (STRING_TYPE_SET.contains(s)) { javaType = String.class.getName(); } else if (TIMESTAMP_TYPE_SET.contains(s)) { javaType = Timestamp.class.getName(); } else if (DATE_TYPE_SET.contains(s)) { javaType = Date.class.getName(); } else if (NUMERIC_TYPE_SET.contains(s)) { javaType = null; } else { javaType = Object.class.getName(); } return javaType; } /** * Returns a type mapping for numeric types. * * @param sqlType the name of the sql type * @param precision the precision of the sql type * @param scale the scale of the sql type, or 0 if no scale given * @return the name of the appropriate java type */ public static String getNumberTypeMapping ( String sqlType, int precision, int scale) { String javaType; final String s = sqlType.toUpperCase(Constants.SYSTEM_LOCALE); if (precision < 0 || scale < 0) { throw new IllegalArgumentException( "Scale and precision must be non-negative"); } if (NUMERIC_TYPE_SET.contains(s)) { if (scale > 0 || FLOAT_TYPE_SET.contains(s)) { return BigDecimal.class.getName(); } else { if (precision < INTEGER_PRECISION_LIMIT) { javaType = Integer.TYPE.getName(); } else if (precision < LONG_PRECISION_LIMIT) { javaType = Long.TYPE.getName(); } else { javaType = BigDecimal.class.getName(); } } } else { throw new IllegalArgumentException(s + " is not a numeric SQL type"); } return javaType; } /** * Determines whether a given type is primitive. * * @param type the name of a java type * @return true if the type is primitive, false otherwise */ public static boolean isPrimitiveType (String type) { boolean result = false; if (JAVA_PRIMITIVE_TYPE_SET.contains(type)) { result = true; } return result; } /** * Maps a java primitive type to its corresponding wrapper object. * @param primitiveType the name of a primitive type * @return the corresponding wrapper object * @throws IllegalArgumentException if primitiveType is not a primitive type */ public static String primitiveToObject (String primitiveType) { if (!isPrimitiveType(primitiveType)) { throw new IllegalArgumentException( "Can't map " + primitiveType + " to its Object wrapper because it is not a primitive type"); } String objectWrapperName = null; for (int i = 0; i < JAVA_PRIMITIVE_TYPES.length; i++) { if (JAVA_PRIMITIVE_TYPES[i].equals(primitiveType)) { objectWrapperName = PRIMITIVE_TYPE_WRAPPERS[i]; break; } } return objectWrapperName; } }