| 1 | /* |
|---|
| 2 | * $Id$ |
|---|
| 3 | * |
|---|
| 4 | * Copyright 2006, The jCoderZ.org Project. All rights reserved. |
|---|
| 5 | * |
|---|
| 6 | * Redistribution and use in source and binary forms, with or without |
|---|
| 7 | * modification, are permitted provided that the following conditions are |
|---|
| 8 | * met: |
|---|
| 9 | * |
|---|
| 10 | * * Redistributions of source code must retain the above copyright |
|---|
| 11 | * notice, this list of conditions and the following disclaimer. |
|---|
| 12 | * * Redistributions in binary form must reproduce the above |
|---|
| 13 | * copyright notice, this list of conditions and the following |
|---|
| 14 | * disclaimer in the documentation and/or other materials |
|---|
| 15 | * provided with the distribution. |
|---|
| 16 | * * Neither the name of the jCoderZ.org Project nor the names of |
|---|
| 17 | * its contributors may be used to endorse or promote products |
|---|
| 18 | * derived from this software without specific prior written |
|---|
| 19 | * permission. |
|---|
| 20 | * |
|---|
| 21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND |
|---|
| 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|---|
| 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|---|
| 24 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS |
|---|
| 25 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|---|
| 26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|---|
| 27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
|---|
| 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
|---|
| 29 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
|---|
| 30 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
|---|
| 31 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 32 | */ |
|---|
| 33 | package org.jcoderz.phoenix.cmpgen2; |
|---|
| 34 | |
|---|
| 35 | import java.math.BigDecimal; |
|---|
| 36 | import java.sql.Date; |
|---|
| 37 | import java.sql.Timestamp; |
|---|
| 38 | import java.util.Arrays; |
|---|
| 39 | import java.util.HashSet; |
|---|
| 40 | import java.util.List; |
|---|
| 41 | import java.util.Set; |
|---|
| 42 | |
|---|
| 43 | import org.jcoderz.commons.util.Constants; |
|---|
| 44 | import org.jcoderz.phoenix.sqlparser.ColumnSpec; |
|---|
| 45 | import org.jcoderz.phoenix.sqlparser.NumericAttribute; |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | /** |
|---|
| 49 | * @author Albrecht Messner |
|---|
| 50 | */ |
|---|
| 51 | public final class TypeMapping |
|---|
| 52 | { |
|---|
| 53 | private static final String[] JAVA_PRIMITIVE_TYPES = { |
|---|
| 54 | Byte.TYPE.getName(), |
|---|
| 55 | Short.TYPE.getName(), |
|---|
| 56 | Integer.TYPE.getName(), |
|---|
| 57 | Long.TYPE.getName(), |
|---|
| 58 | Float.TYPE.getName(), |
|---|
| 59 | Double.TYPE.getName(), |
|---|
| 60 | Character.TYPE.getName(), |
|---|
| 61 | Boolean.TYPE.getName(), |
|---|
| 62 | "byte[]" |
|---|
| 63 | }; |
|---|
| 64 | |
|---|
| 65 | private static final String[] PRIMITIVE_TYPE_WRAPPERS = { |
|---|
| 66 | Byte.class.getName(), |
|---|
| 67 | Short.class.getName(), |
|---|
| 68 | Integer.class.getName(), |
|---|
| 69 | Long.class.getName(), |
|---|
| 70 | Float.class.getName(), |
|---|
| 71 | Double.class.getName(), |
|---|
| 72 | Character.class.getName(), |
|---|
| 73 | Boolean.class.getName(), |
|---|
| 74 | "byte[]" |
|---|
| 75 | }; |
|---|
| 76 | |
|---|
| 77 | // these get mapped to java.lang.String |
|---|
| 78 | private static final String[] STRING_TYPES = { |
|---|
| 79 | "CHAR", |
|---|
| 80 | "CHAR2", |
|---|
| 81 | "NCHAR", |
|---|
| 82 | "NCHAR2", |
|---|
| 83 | "VARCHAR", |
|---|
| 84 | "VARCHAR2", |
|---|
| 85 | "NVARCHAR", |
|---|
| 86 | "NVARCHAR2" |
|---|
| 87 | }; |
|---|
| 88 | |
|---|
| 89 | private static final String[] TIMESTAMP_TYPES = { |
|---|
| 90 | "TIMESTAMP" |
|---|
| 91 | }; |
|---|
| 92 | |
|---|
| 93 | private static final String[] DATE_TYPES = { |
|---|
| 94 | "DATE" |
|---|
| 95 | }; |
|---|
| 96 | |
|---|
| 97 | // these get mapped to numeric types |
|---|
| 98 | private static final String[] NUMERIC_TYPES = { |
|---|
| 99 | "NUMBER", |
|---|
| 100 | "NUMERIC", |
|---|
| 101 | "DECIMAL", |
|---|
| 102 | "INTEGER", |
|---|
| 103 | "INT", |
|---|
| 104 | "FLOAT", |
|---|
| 105 | "REAL" |
|---|
| 106 | }; |
|---|
| 107 | |
|---|
| 108 | private static final String[] FLOAT_TYPES = { |
|---|
| 109 | "FLOAT", |
|---|
| 110 | "REAL" |
|---|
| 111 | }; |
|---|
| 112 | |
|---|
| 113 | private static final int INTEGER_PRECISION_LIMIT = 10; |
|---|
| 114 | private static final int LONG_PRECISION_LIMIT = 19; |
|---|
| 115 | |
|---|
| 116 | |
|---|
| 117 | // put all arrays into hash sets for faster lookup |
|---|
| 118 | private static final Set STRING_TYPE_SET = new HashSet(); |
|---|
| 119 | private static final Set NUMERIC_TYPE_SET = new HashSet(); |
|---|
| 120 | private static final Set TIMESTAMP_TYPE_SET = new HashSet(); |
|---|
| 121 | private static final Set DATE_TYPE_SET = new HashSet(); |
|---|
| 122 | private static final Set FLOAT_TYPE_SET = new HashSet(); |
|---|
| 123 | private static final Set JAVA_PRIMITIVE_TYPE_SET = new HashSet(); |
|---|
| 124 | |
|---|
| 125 | static |
|---|
| 126 | { |
|---|
| 127 | STRING_TYPE_SET.addAll(Arrays.asList(STRING_TYPES)); |
|---|
| 128 | NUMERIC_TYPE_SET.addAll(Arrays.asList(NUMERIC_TYPES)); |
|---|
| 129 | TIMESTAMP_TYPE_SET.addAll(Arrays.asList(TIMESTAMP_TYPES)); |
|---|
| 130 | DATE_TYPE_SET.addAll(Arrays.asList(DATE_TYPES)); |
|---|
| 131 | FLOAT_TYPE_SET.addAll(Arrays.asList(FLOAT_TYPES)); |
|---|
| 132 | JAVA_PRIMITIVE_TYPE_SET.addAll(Arrays.asList(JAVA_PRIMITIVE_TYPES)); |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | /** |
|---|
| 136 | * private constructor to avoid instantiation |
|---|
| 137 | */ |
|---|
| 138 | private TypeMapping () |
|---|
| 139 | { |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | /** |
|---|
| 143 | * Finds the appropriate type mapping from a given column spec. |
|---|
| 144 | * |
|---|
| 145 | * Note that the type returned here is the "simple" type as it is stored |
|---|
| 146 | * in the database, not the "complex" type. |
|---|
| 147 | * |
|---|
| 148 | * @param column the column specification |
|---|
| 149 | * @param fullyQualified whether the type should contain the package |
|---|
| 150 | * name or not |
|---|
| 151 | * @return the simple java type used to store the column in the db |
|---|
| 152 | * @throws CmpGeneratorException if no type mapping can be found |
|---|
| 153 | */ |
|---|
| 154 | public static String getJavaType ( |
|---|
| 155 | ColumnSpec column, |
|---|
| 156 | boolean fullyQualified) |
|---|
| 157 | throws CmpGeneratorException |
|---|
| 158 | { |
|---|
| 159 | String javaType = column.getJavaType(); |
|---|
| 160 | if (javaType == null) |
|---|
| 161 | { |
|---|
| 162 | // this gets a mapping for character data types |
|---|
| 163 | javaType = getTypeMapping(column.getColumnType()); |
|---|
| 164 | |
|---|
| 165 | // no character type, try to find a numeric type |
|---|
| 166 | if (javaType == null) |
|---|
| 167 | { |
|---|
| 168 | final List sqlTypeAttributes = column.getDatatypeAttributes(); |
|---|
| 169 | int att1 = 0, att2 = 0; |
|---|
| 170 | if (sqlTypeAttributes.size() > 0) |
|---|
| 171 | { |
|---|
| 172 | att1 = ((NumericAttribute) sqlTypeAttributes.get(0)).getNumber(); |
|---|
| 173 | if (sqlTypeAttributes.size() > 1) |
|---|
| 174 | { |
|---|
| 175 | att2 = ((NumericAttribute) sqlTypeAttributes.get(1)) |
|---|
| 176 | .getNumber(); |
|---|
| 177 | } |
|---|
| 178 | } |
|---|
| 179 | javaType = |
|---|
| 180 | getNumberTypeMapping( |
|---|
| 181 | column.getColumnType(), |
|---|
| 182 | att1, |
|---|
| 183 | att2); |
|---|
| 184 | } |
|---|
| 185 | } |
|---|
| 186 | else |
|---|
| 187 | { |
|---|
| 188 | // a java type has been specified |
|---|
| 189 | final String loadMethod = column.getLoadMethod(); |
|---|
| 190 | if (loadMethod != null) |
|---|
| 191 | { |
|---|
| 192 | // hey! it's a complex type |
|---|
| 193 | // the "simple" java type can be found in the signature of the |
|---|
| 194 | // load method |
|---|
| 195 | final int openParen = loadMethod.indexOf('('); |
|---|
| 196 | final int closeParen = loadMethod.indexOf(')'); |
|---|
| 197 | if (openParen == -1 |
|---|
| 198 | || closeParen == -1 |
|---|
| 199 | || closeParen <= openParen) |
|---|
| 200 | { |
|---|
| 201 | throw new CmpGeneratorException( |
|---|
| 202 | loadMethod + " is an invalid load method signature"); |
|---|
| 203 | } |
|---|
| 204 | javaType = loadMethod.substring(openParen + 1, closeParen).trim(); |
|---|
| 205 | if (javaType.length() <= 0) |
|---|
| 206 | { |
|---|
| 207 | throw new CmpGeneratorException( |
|---|
| 208 | loadMethod + " is an invalid load method signature"); |
|---|
| 209 | } |
|---|
| 210 | } |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | if (javaType == null) |
|---|
| 214 | { |
|---|
| 215 | throw new CmpGeneratorException("No type mapping found for column " |
|---|
| 216 | + column); |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | // ok, now we've got a java type. if it is a primitive type |
|---|
| 220 | // and the column is nullable, then we must use the wrapper object |
|---|
| 221 | // instead |
|---|
| 222 | if (isPrimitiveType(javaType) && !column.isNotNull()) |
|---|
| 223 | { |
|---|
| 224 | javaType = primitiveToObject(javaType); |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | // check if we should return the type fully qualified |
|---|
| 228 | if (!fullyQualified) |
|---|
| 229 | { |
|---|
| 230 | javaType = unqualifyType(javaType); |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | return javaType; |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | /** |
|---|
| 237 | * Returns everything after the last dot in a type name. |
|---|
| 238 | * @param typeName a java type name |
|---|
| 239 | * @return the unqualified type name, or the argument if |
|---|
| 240 | * it was not a qualified java type name |
|---|
| 241 | */ |
|---|
| 242 | public static String unqualifyType (String typeName) |
|---|
| 243 | { |
|---|
| 244 | final int dotIndex = typeName.lastIndexOf('.'); |
|---|
| 245 | return typeName.substring(dotIndex + 1); |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | /** |
|---|
| 249 | * Returns the fully qualified java type to which an SQL type is mapped. |
|---|
| 250 | * |
|---|
| 251 | * @param sqlType the name of the sql type |
|---|
| 252 | * @return the FQ type name of the java type, or null if this method can not |
|---|
| 253 | * find a type mapping |
|---|
| 254 | */ |
|---|
| 255 | public static String getTypeMapping (String sqlType) |
|---|
| 256 | { |
|---|
| 257 | String javaType; |
|---|
| 258 | |
|---|
| 259 | final String s = sqlType.toUpperCase(Constants.SYSTEM_LOCALE); |
|---|
| 260 | if (STRING_TYPE_SET.contains(s)) |
|---|
| 261 | { |
|---|
| 262 | javaType = String.class.getName(); |
|---|
| 263 | } |
|---|
| 264 | else if (TIMESTAMP_TYPE_SET.contains(s)) |
|---|
| 265 | { |
|---|
| 266 | javaType = Timestamp.class.getName(); |
|---|
| 267 | } |
|---|
| 268 | else if (DATE_TYPE_SET.contains(s)) |
|---|
| 269 | { |
|---|
| 270 | javaType = Date.class.getName(); |
|---|
| 271 | } |
|---|
| 272 | else if (NUMERIC_TYPE_SET.contains(s)) |
|---|
| 273 | { |
|---|
| 274 | javaType = null; |
|---|
| 275 | } |
|---|
| 276 | else |
|---|
| 277 | { |
|---|
| 278 | javaType = Object.class.getName(); |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | return javaType; |
|---|
| 282 | } |
|---|
| 283 | |
|---|
| 284 | /** |
|---|
| 285 | * Returns a type mapping for numeric types. |
|---|
| 286 | * |
|---|
| 287 | * @param sqlType the name of the sql type |
|---|
| 288 | * @param precision the precision of the sql type |
|---|
| 289 | * @param scale the scale of the sql type, or 0 if no scale given |
|---|
| 290 | * @return the name of the appropriate java type |
|---|
| 291 | */ |
|---|
| 292 | public static String getNumberTypeMapping ( |
|---|
| 293 | String sqlType, |
|---|
| 294 | int precision, |
|---|
| 295 | int scale) |
|---|
| 296 | { |
|---|
| 297 | String javaType; |
|---|
| 298 | final String s = sqlType.toUpperCase(Constants.SYSTEM_LOCALE); |
|---|
| 299 | |
|---|
| 300 | if (precision < 0 || scale < 0) |
|---|
| 301 | { |
|---|
| 302 | throw new IllegalArgumentException( |
|---|
| 303 | "Scale and precision must be non-negative"); |
|---|
| 304 | } |
|---|
| 305 | |
|---|
| 306 | if (NUMERIC_TYPE_SET.contains(s)) |
|---|
| 307 | { |
|---|
| 308 | if (scale > 0 || FLOAT_TYPE_SET.contains(s)) |
|---|
| 309 | { |
|---|
| 310 | return BigDecimal.class.getName(); |
|---|
| 311 | } |
|---|
| 312 | else |
|---|
| 313 | { |
|---|
| 314 | if (precision < INTEGER_PRECISION_LIMIT) |
|---|
| 315 | { |
|---|
| 316 | javaType = Integer.TYPE.getName(); |
|---|
| 317 | } |
|---|
| 318 | else if (precision < LONG_PRECISION_LIMIT) |
|---|
| 319 | { |
|---|
| 320 | javaType = Long.TYPE.getName(); |
|---|
| 321 | } |
|---|
| 322 | else |
|---|
| 323 | { |
|---|
| 324 | javaType = BigDecimal.class.getName(); |
|---|
| 325 | } |
|---|
| 326 | } |
|---|
| 327 | } |
|---|
| 328 | else |
|---|
| 329 | { |
|---|
| 330 | throw new IllegalArgumentException(s + " is not a numeric SQL type"); |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | return javaType; |
|---|
| 334 | } |
|---|
| 335 | |
|---|
| 336 | /** |
|---|
| 337 | * Determines whether a given type is primitive. |
|---|
| 338 | * |
|---|
| 339 | * @param type the name of a java type |
|---|
| 340 | * @return true if the type is primitive, false otherwise |
|---|
| 341 | */ |
|---|
| 342 | public static boolean isPrimitiveType (String type) |
|---|
| 343 | { |
|---|
| 344 | boolean result = false; |
|---|
| 345 | if (JAVA_PRIMITIVE_TYPE_SET.contains(type)) |
|---|
| 346 | { |
|---|
| 347 | result = true; |
|---|
| 348 | } |
|---|
| 349 | return result; |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | /** |
|---|
| 353 | * Maps a java primitive type to its corresponding wrapper object. |
|---|
| 354 | * @param primitiveType the name of a primitive type |
|---|
| 355 | * @return the corresponding wrapper object |
|---|
| 356 | * @throws IllegalArgumentException if primitiveType is not a primitive type |
|---|
| 357 | */ |
|---|
| 358 | public static String primitiveToObject (String primitiveType) |
|---|
| 359 | { |
|---|
| 360 | if (!isPrimitiveType(primitiveType)) |
|---|
| 361 | { |
|---|
| 362 | throw new IllegalArgumentException( |
|---|
| 363 | "Can't map " |
|---|
| 364 | + primitiveType |
|---|
| 365 | + " to its Object wrapper because it is not a primitive type"); |
|---|
| 366 | } |
|---|
| 367 | String objectWrapperName = null; |
|---|
| 368 | for (int i = 0; i < JAVA_PRIMITIVE_TYPES.length; i++) |
|---|
| 369 | { |
|---|
| 370 | if (JAVA_PRIMITIVE_TYPES[i].equals(primitiveType)) |
|---|
| 371 | { |
|---|
| 372 | objectWrapperName = PRIMITIVE_TYPE_WRAPPERS[i]; |
|---|
| 373 | break; |
|---|
| 374 | } |
|---|
| 375 | } |
|---|
| 376 | return objectWrapperName; |
|---|
| 377 | } |
|---|
| 378 | |
|---|
| 379 | } |
|---|