blob: c0b9b8a3fb67f2412bbf4ec74a25158e7bed35b4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.openjpa.meta;
import java.io.InputStream;
import java.io.Reader;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.lib.meta.CFMetaDataParser;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.MetaDataException;
/**
* Type constants for managed fields.
*
* @author Abe White
*/
public class JavaTypes {
public static final int BOOLEAN = 0;
public static final int BYTE = 1;
public static final int CHAR = 2;
public static final int DOUBLE = 3;
public static final int FLOAT = 4;
public static final int INT = 5;
public static final int LONG = 6;
public static final int SHORT = 7;
// keep OBJECT as first non-primitive type code; other code relies on it
public static final int OBJECT = 8;
public static final int STRING = 9;
public static final int NUMBER = 10;
public static final int ARRAY = 11;
public static final int COLLECTION = 12;
public static final int MAP = 13;
public static final int DATE = 14;
public static final int PC = 15;
public static final int BOOLEAN_OBJ = 16;
public static final int BYTE_OBJ = 17;
public static final int CHAR_OBJ = 18;
public static final int DOUBLE_OBJ = 19;
public static final int FLOAT_OBJ = 20;
public static final int INT_OBJ = 21;
public static final int LONG_OBJ = 22;
public static final int SHORT_OBJ = 23;
public static final int BIGDECIMAL = 24;
public static final int BIGINTEGER = 25;
public static final int LOCALE = 26;
public static final int PC_UNTYPED = 27;
public static final int CALENDAR = 28;
public static final int OID = 29;
public static final int INPUT_STREAM = 30;
public static final int INPUT_READER = 31;
public static final int ENUM = 32;
// Java8 time API
public static final int LOCAL_DATE = 33;
public static final int LOCAL_TIME = 34;
public static final int LOCAL_DATETIME = 35;
public static final int OFFSET_TIME = 36;
public static final int OFFSET_DATETIME = 37;
private static final Localizer _loc = Localizer.forPackage(JavaTypes.class);
private static final Map<Class<?>, Integer> _typeCodes = new HashMap<>();
static {
_typeCodes.put(String.class, STRING);
_typeCodes.put(Boolean.class, BOOLEAN_OBJ);
_typeCodes.put(Byte.class, BYTE_OBJ);
_typeCodes.put(Character.class, CHAR_OBJ);
_typeCodes.put(Double.class, DOUBLE_OBJ);
_typeCodes.put(Float.class, FLOAT_OBJ);
_typeCodes.put(Integer.class, INT_OBJ);
_typeCodes.put(Long.class, LONG_OBJ);
_typeCodes.put(Short.class, SHORT_OBJ);
_typeCodes.put(Date.class, DATE);
_typeCodes.put(java.sql.Date.class, DATE);
_typeCodes.put(java.sql.Timestamp.class, DATE);
_typeCodes.put(java.sql.Time.class, DATE);
_typeCodes.put(BigInteger.class, BIGINTEGER);
_typeCodes.put(BigDecimal.class, BIGDECIMAL);
_typeCodes.put(Number.class, NUMBER);
_typeCodes.put(Locale.class, LOCALE);
_typeCodes.put(Object.class, OBJECT);
_typeCodes.put(PersistenceCapable.class, PC_UNTYPED);
_typeCodes.put(Properties.class, MAP);
_typeCodes.put(Calendar.class, CALENDAR);
// Java8 time API
_typeCodes.put(LocalDate.class, LOCAL_DATE);
_typeCodes.put(LocalTime.class, LOCAL_TIME);
_typeCodes.put(LocalDateTime.class, LOCAL_DATETIME);
_typeCodes.put(OffsetTime.class, OFFSET_TIME);
_typeCodes.put(OffsetDateTime.class, OFFSET_DATETIME);
}
/**
* Return the field metadata type code for the given class. First class
* objects are not recognized in this method.
*/
public static int getTypeCode(Class<?> type) {
if (type == null)
return OBJECT;
if (type.isPrimitive()) {
switch (type.getName().charAt(0)) {
case 'b':
return (type == boolean.class) ? BOOLEAN : BYTE;
case 'c':
return CHAR;
case 'd':
return DOUBLE;
case 'f':
return FLOAT;
case 'i':
return INT;
case 'l':
return LONG;
case 's':
return SHORT;
}
}
Integer code = _typeCodes.get(type);
if (code != null) {
return code;
}
// have to do this first to catch custom collection and map types;
// on resolve we figure out if these custom types are
// persistence-capable
if (Collection.class.isAssignableFrom(type))
return COLLECTION;
if (Map.class.isAssignableFrom(type))
return MAP;
if (type.isArray())
return ARRAY;
if (Calendar.class.isAssignableFrom(type))
return CALENDAR;
if (type.isInterface()) {
if (Serializable.class.isAssignableFrom(type))
return OBJECT;
return PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class))
return INPUT_READER;
if (type.isAssignableFrom (InputStream.class))
return INPUT_STREAM;
if (Enum.class.isAssignableFrom(type))
return ENUM;
return OBJECT;
}
/**
* Check the given name against the same set of standard packages used
* when parsing metadata.
*/
public static Class<?> classForName(String name, ClassMetaData context) {
return classForName(name, context, null);
}
/**
* Check the given name against the same set of standard packages used
* when parsing metadata.
*/
public static Class<?> classForName(String name, ClassMetaData context, ClassLoader loader) {
return classForName(name, context, context.getDescribedType(), null,
loader);
}
/**
* Check the given name against the same set of standard packages used
* when parsing metadata.
*
* @param mustExist Whether the supplied loader <b>must</b> be able to load the class. If true no attempt to use a
* different classloader will be made. If false the ClassResolver from the configuration will be used.
*/
public static Class<?> classForName(String name, ClassMetaData context, ClassLoader loader, boolean mustExist) {
return classForName(name, context, context.getDescribedType(), null, loader, mustExist);
}
/**
* Check the given name against the same set of standard packages used
* when parsing metadata.
*/
public static Class<?> classForName(String name, ValueMetaData context) {
return classForName(name, context, null);
}
/**
* Check the given name against the same set of standard packages used
* when parsing metadata.
*/
public static Class<?> classForName(String name, ValueMetaData context, ClassLoader loader) {
return classForName(name,
context.getFieldMetaData().getDefiningMetaData(),
context.getFieldMetaData().getDeclaringType(), context, loader);
}
/**
* Try to load a class using the provided loader. Optionally tries the
* configuration's ClassResolver if the supplied loader cannot find the class.
*
* @param name Name of the class to load.
* @param loader ClassLoader to use. If null, the configuration's ClassResolver will be used.
* @param mustExist Whether the supplied loader <b>must</b> be able to load the class. If true no attempt to use a
* different classloader will be made. If false the ClassResolver from the configuration will be used.
*/
public static Class<?> classForName(String name, ValueMetaData context,
ClassLoader loader, boolean mustExist) {
return classForName(name,
context.getFieldMetaData().getDefiningMetaData(),
context.getFieldMetaData().getDeclaringType(), context, loader, mustExist);
}
/**
* OJ-758: Delegates to the final classForName. This is needed
* to maintain the existing code path prior to OJ-758.
*/
private static Class<?> classForName(String name, ClassMetaData meta,
Class<?> dec, ValueMetaData vmd, ClassLoader loader) {
return classForName(name, meta, dec, vmd, loader, true);
}
/**
* Check the given name against the same set of standard packages used
* when parsing metadata.
*/
private static Class<?> classForName(String name, ClassMetaData meta, Class<?> dec, ValueMetaData vmd,
ClassLoader loader, boolean mustExist) {
// special case for PersistenceCapable and Object
if ("PersistenceCapable".equals(name)
|| "javax.jdo.PersistenceCapable".equals(name)) // backwards compatibility
return PersistenceCapable.class;
if ("Object".equals(name))
return Object.class;
MetaDataRepository rep = meta.getRepository();
boolean runtime = (rep.getValidate() & MetaDataRepository.VALIDATE_RUNTIME) != 0;
if (loader == null)
loader = rep.getConfiguration().getClassResolverInstance().
getClassLoader(dec, meta.getEnvClassLoader());
// try the owner's package
String pkg = ClassUtil.getPackageName(dec);
Class<?> cls = CFMetaDataParser.classForName(name, pkg, runtime, loader);
if (cls == null && vmd != null) {
// try against this value type's package too
pkg = ClassUtil.getPackageName(vmd.getDeclaredType());
cls = CFMetaDataParser.classForName(name, pkg, runtime, loader);
}
//OJ-758 start: If the class is still null, as a last/final attempt to
//load the class, check with the ClassResolver to get a loader
//and use it to attempt to load the class.
if (cls == null && !mustExist){
loader = rep.getConfiguration().getClassResolverInstance().
getClassLoader(dec, meta.getEnvClassLoader());
cls = CFMetaDataParser.classForName(name, pkg, runtime, loader);
}
//OJ-758 end
if (cls == null)
throw new MetaDataException(_loc.get("bad-class", name,
(vmd == null) ? (Object) meta : (Object) vmd));
return cls;
}
/**
* Convert the given object to the given type if possible. If the type is
* a numeric primitive, this method only guarantees that the return value
* is a {@link Number}. If no known conversion or the value is null,
* returns the original value.
*/
public static Object convert(Object val, int typeCode) {
if (val == null)
return null;
switch (typeCode) {
case BIGDECIMAL:
if (val instanceof BigDecimal)
return val;
if (val instanceof Number)
return new BigDecimal(((Number) val).doubleValue());
if (val instanceof String)
return new BigDecimal(val.toString());
return val;
case BIGINTEGER:
if (val instanceof BigInteger)
return val;
if (val instanceof Number || val instanceof String)
return new BigInteger(val.toString());
return val;
case BOOLEAN:
case BOOLEAN_OBJ:
if (val instanceof String)
return Boolean.valueOf(val.toString());
return val;
case BYTE_OBJ:
if (val instanceof Byte)
return val;
if (val instanceof Number)
return ((Number) val).byteValue();
// no break
case BYTE:
if (val instanceof String)
return Byte.valueOf((String)val);
return val;
case CHAR:
case CHAR_OBJ:
if (val instanceof Character)
return val;
if (val instanceof String)
return val.toString().charAt(0);
if (val instanceof Number)
return (char) ((Number) val).intValue();
return val;
case DATE:
if (val instanceof String)
return new Date(val.toString());
return val;
case DOUBLE_OBJ:
if (val instanceof Double)
return val;
if (val instanceof Number)
return ((Number) val).doubleValue();
// no break
case DOUBLE:
if (val instanceof String)
return Double.valueOf(val.toString());
return val;
case FLOAT_OBJ:
if (val instanceof Float)
return val;
if (val instanceof Number)
return ((Number) val).floatValue();
// no break
case FLOAT:
if (val instanceof String)
return Float.valueOf(val.toString());
return val;
case INT_OBJ:
if (val instanceof Integer)
return val;
if (val instanceof Number)
return ((Number) val).intValue();
// no break
case INT:
if (val instanceof String)
return Integer.valueOf(val.toString());
return val;
case LONG_OBJ:
if (val instanceof Long)
return val;
if (val instanceof Number)
return ((Number) val).longValue();
// no break
case LONG:
if (val instanceof String)
return Long.valueOf(val.toString());
return val;
case NUMBER:
if (val instanceof Number)
return val;
if (val instanceof String)
return new BigDecimal(val.toString());
return val;
case SHORT_OBJ:
if (val instanceof Short)
return val;
if (val instanceof Number)
return ((Number) val).shortValue();
// no break
case SHORT:
if (val instanceof String)
return Short.valueOf(val.toString());
return val;
case STRING:
return val.toString();
default:
return val;
}
}
/**
* Return true if the (possibly unresolved) field or its elements might be
* persistence capable objects.
*/
public static boolean maybePC(FieldMetaData field) {
switch (field.getDeclaredTypeCode()) {
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
return maybePC(field.getElement());
case JavaTypes.MAP:
return maybePC(field.getKey()) || maybePC(field.getElement());
default:
return maybePC((ValueMetaData) field);
}
}
/**
* Return true if the (possibly unresolved) value might be a first class
* object.
*/
public static boolean maybePC(ValueMetaData val) {
return maybePC(val.getDeclaredTypeCode(), val.getDeclaredType());
}
/**
* Return true if the given unresolved typecode/type pair may represent a
* persistent object.
*/
static boolean maybePC(int typeCode, Class<?> type) {
if (type == null)
return false;
switch (typeCode) {
case JavaTypes.OBJECT:
case JavaTypes.PC:
case JavaTypes.PC_UNTYPED:
return true;
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
return !type.getName().startsWith("java.util.");
default:
return false;
}
}
/**
* Helper method to return the given array value as a collection.
*/
@SuppressWarnings("unchecked")
public static <T> List<T> toList(Object val, Class<T> elem, boolean mutable) {
if (val == null)
return null;
List<T> l;
if (!elem.isPrimitive()) {
// if an object array, use built-in list function
l = Arrays.asList((T[]) val);
if (mutable)
l = new ArrayList<>(l);
} else {
// convert to list of wrapper objects
int length = Array.getLength(val);
l = new ArrayList<>(length);
for (int i = 0; i < length; i++)
l.add((T)Array.get(val, i));
}
return l;
}
/**
* Helper method to return the given collection as an array.
*/
public static Object toArray(Collection<?> coll, Class<?> elem) {
if (coll == null)
return null;
Object array = Array.newInstance(elem, coll.size());
int idx = 0;
for (Iterator<?> itr = coll.iterator(); itr.hasNext(); idx++)
Array.set(array, idx, itr.next ());
return array;
}
/**
* Determine whether or not the provided Object value is the default for the provided typeCode.
*
* For example: If o = Integer(0) and typeCode = JavaTypes.INT, this method will return true.
*/
public static boolean isPrimitiveDefault(Object o, int typeCode) {
switch (typeCode) {
case BOOLEAN:
return ((Boolean) o).equals(Boolean.FALSE) ? true : false;
case BYTE:
return ((Byte) o) == 0 ? true : false;
case SHORT:
return ((Short) o) == 0 ? true : false;
case INT:
return ((Integer) o) == 0 ? true : false;
case LONG:
return ((Long) o) == 0L ? true : false;
case FLOAT:
return ((Float) o) == 0.0F ? true : false;
case CHAR:
return ((Character) o) == '\u0000' ? true : false;
case DOUBLE:
return ((Double) o) == 0.0d ? true : false;
}
return false;
}
}