com.j256.ormlite.field.FieldType Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ormlite-core Show documentation
Show all versions of ormlite-core Show documentation
Lightweight Object Relational Model (ORM) for persisting objects to SQL databases.
package com.j256.ormlite.field;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Map;
import com.j256.ormlite.dao.BaseDaoImpl;
import com.j256.ormlite.dao.BaseForeignCollection;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.dao.EagerForeignCollection;
import com.j256.ormlite.dao.ForeignCollection;
import com.j256.ormlite.dao.LazyForeignCollection;
import com.j256.ormlite.dao.ObjectCache;
import com.j256.ormlite.db.DatabaseType;
import com.j256.ormlite.field.types.VoidType;
import com.j256.ormlite.logger.Log.Level;
import com.j256.ormlite.logger.Logger;
import com.j256.ormlite.logger.LoggerFactory;
import com.j256.ormlite.misc.SqlExceptionUtil;
import com.j256.ormlite.stmt.mapped.MappedQueryForFieldEq;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.DatabaseResults;
import com.j256.ormlite.table.DatabaseTableConfig;
import com.j256.ormlite.table.TableInfo;
/**
* Per field information configured from the {@link DatabaseField} annotation and the associated {@link Field} in the
* class. Use the {@link #createFieldType} static method to instantiate the class.
*
* @author graywatson
*/
public class FieldType {
/** default suffix added to fields that are id fields of foreign objects */
public static final String FOREIGN_ID_FIELD_SUFFIX = "_id";
/*
* Default values.
*
* NOTE: These don't get any values so the compiler assigns them to the default values for the type. Ahhhh. Smart.
*/
private static boolean DEFAULT_VALUE_BOOLEAN;
private static byte DEFAULT_VALUE_BYTE;
private static char DEFAULT_VALUE_CHAR;
private static short DEFAULT_VALUE_SHORT;
private static int DEFAULT_VALUE_INT;
private static long DEFAULT_VALUE_LONG;
private static float DEFAULT_VALUE_FLOAT;
private static double DEFAULT_VALUE_DOUBLE;
private final String tableName;
private final Field field;
private final String columnName;
private final DatabaseFieldConfig fieldConfig;
private final boolean isId;
private final boolean isGeneratedId;
private final String generatedIdSequence;
private final Method fieldGetMethod;
private final Method fieldSetMethod;
private final Class parentClass;
private DataPersister dataPersister;
private Object defaultValue;
private Object dataTypeConfigObj;
private FieldConverter fieldConverter;
private FieldType foreignIdField;
private FieldType foreignRefField;
private FieldType foreignFieldType;
private Dao foreignDao;
private MappedQueryForFieldEq mappedQueryForForeignField;
/**
* ThreadLocal counters to detect initialization loops. Notice that there is _not_ an initValue() method on purpose.
* We don't want to create these if we don't have to.
*/
private static final ThreadLocal threadLevelCounters = new ThreadLocal();
private static final Logger logger = LoggerFactory.getLogger(FieldType.class);
/**
* You should use {@link FieldType#createFieldType} to instantiate one of these field if you have a {@link Field}.
*/
public FieldType(DatabaseType databaseType, String tableName, Field field, DatabaseFieldConfig fieldConfig,
Class parentClass) throws SQLException {
this.tableName = tableName;
this.field = field;
this.parentClass = parentClass;
// post process our config settings
fieldConfig.postProcess();
Class clazz = field.getType();
DataPersister dataPersister;
if (fieldConfig.getDataPersister() == null) {
Class persisterClass = fieldConfig.getPersisterClass();
if (persisterClass == null || persisterClass == VoidType.class) {
dataPersister = DataPersisterManager.lookupForField(field);
} else {
Method method;
try {
method = persisterClass.getDeclaredMethod("getSingleton");
} catch (Exception e) {
throw SqlExceptionUtil
.create("Could not find getSingleton static method on class " + persisterClass, e);
}
Object result;
try {
result = method.invoke(null);
} catch (InvocationTargetException e) {
throw SqlExceptionUtil.create("Could not run getSingleton method on class " + persisterClass,
e.getTargetException());
} catch (Exception e) {
throw SqlExceptionUtil.create("Could not run getSingleton method on class " + persisterClass, e);
}
if (result == null) {
throw new SQLException(
"Static getSingleton method should not return null on class " + persisterClass);
}
try {
dataPersister = (DataPersister) result;
} catch (Exception e) {
throw SqlExceptionUtil
.create("Could not cast result of static getSingleton method to DataPersister from class "
+ persisterClass, e);
}
}
} else {
dataPersister = fieldConfig.getDataPersister();
if (!dataPersister.isValidForField(field)) {
StringBuilder sb = new StringBuilder();
sb.append("Field class ").append(clazz.getName());
sb.append(" for field ").append(this);
sb.append(" is not valid for type ").append(dataPersister);
Class primaryClass = dataPersister.getPrimaryClass();
if (primaryClass != null) {
sb.append(", maybe should be ").append(primaryClass);
}
throw new IllegalArgumentException(sb.toString());
}
}
String foreignColumnName = fieldConfig.getForeignColumnName();
String defaultFieldName = field.getName();
if (fieldConfig.isForeign() || fieldConfig.isForeignAutoRefresh() || foreignColumnName != null) {
if (dataPersister != null && dataPersister.isPrimitive()) {
throw new IllegalArgumentException(
"Field " + this + " is a primitive class " + clazz + " but marked as foreign");
}
if (foreignColumnName == null) {
defaultFieldName = defaultFieldName + FOREIGN_ID_FIELD_SUFFIX;
} else {
defaultFieldName = defaultFieldName + "_" + foreignColumnName;
}
if (ForeignCollection.class.isAssignableFrom(clazz)) {
throw new SQLException("Field '" + field.getName() + "' in class " + clazz + "' should use the @"
+ ForeignCollectionField.class.getSimpleName() + " annotation not foreign=true");
}
} else if (fieldConfig.isForeignCollection()) {
if (clazz != Collection.class && !ForeignCollection.class.isAssignableFrom(clazz)) {
throw new SQLException("Field class for '" + field.getName() + "' must be of class "
+ ForeignCollection.class.getSimpleName() + " or Collection.");
}
Type type = field.getGenericType();
if (!(type instanceof ParameterizedType)) {
throw new SQLException("Field class for '" + field.getName() + "' must be a parameterized Collection.");
}
Type[] genericArguments = ((ParameterizedType) type).getActualTypeArguments();
if (genericArguments.length == 0) {
// i doubt this will ever be reached
throw new SQLException("Field class for '" + field.getName()
+ "' must be a parameterized Collection with at least 1 type.");
}
} else if (dataPersister == null && (!fieldConfig.isForeignCollection())) {
if (byte[].class.isAssignableFrom(clazz)) {
throw new SQLException("ORMLite does not know how to store " + clazz + " for field '" + field.getName()
+ "'. byte[] fields must specify dataType=DataType.BYTE_ARRAY or SERIALIZABLE");
} else if (Serializable.class.isAssignableFrom(clazz)) {
throw new SQLException("ORMLite does not know how to store " + clazz + " for field '" + field.getName()
+ "'. Use another class, custom persister, or to serialize it use "
+ "dataType=DataType.SERIALIZABLE");
} else {
throw new IllegalArgumentException("ORMLite does not know how to store " + clazz + " for field "
+ field.getName() + ". Use another class or a custom persister.");
}
}
if (fieldConfig.getColumnName() == null) {
this.columnName = defaultFieldName;
} else {
this.columnName = fieldConfig.getColumnName();
}
this.fieldConfig = fieldConfig;
if (fieldConfig.isId()) {
if (fieldConfig.isGeneratedId() || fieldConfig.getGeneratedIdSequence() != null) {
throw new IllegalArgumentException(
"Must specify one of id, generatedId, and generatedIdSequence with " + field.getName());
}
this.isId = true;
this.isGeneratedId = false;
this.generatedIdSequence = null;
} else if (fieldConfig.isGeneratedId()) {
if (fieldConfig.getGeneratedIdSequence() != null) {
throw new IllegalArgumentException(
"Must specify one of id, generatedId, and generatedIdSequence with " + field.getName());
}
this.isId = true;
this.isGeneratedId = true;
if (databaseType.isIdSequenceNeeded()) {
this.generatedIdSequence = databaseType.generateIdSequenceName(tableName, this);
} else {
this.generatedIdSequence = null;
}
} else if (fieldConfig.getGeneratedIdSequence() != null) {
this.isId = true;
this.isGeneratedId = true;
String seqName = fieldConfig.getGeneratedIdSequence();
if (databaseType.isEntityNamesMustBeUpCase()) {
seqName = databaseType.upCaseEntityName(seqName);
}
this.generatedIdSequence = seqName;
} else {
this.isId = false;
this.isGeneratedId = false;
this.generatedIdSequence = null;
}
if (this.isId && (fieldConfig.isForeign() || fieldConfig.isForeignAutoRefresh())) {
throw new IllegalArgumentException("Id field " + field.getName() + " cannot also be a foreign object");
}
if (fieldConfig.isUseGetSet()) {
this.fieldGetMethod = DatabaseFieldConfig.findGetMethod(field, databaseType, true);
this.fieldSetMethod = DatabaseFieldConfig.findSetMethod(field, databaseType, true);
} else {
if (!field.isAccessible()) {
try {
this.field.setAccessible(true);
} catch (SecurityException e) {
throw new IllegalArgumentException("Could not open access to field " + field.getName()
+ ". You may have to set useGetSet=true to fix.");
}
}
this.fieldGetMethod = null;
this.fieldSetMethod = null;
}
if (fieldConfig.isAllowGeneratedIdInsert() && !fieldConfig.isGeneratedId()) {
throw new IllegalArgumentException(
"Field " + field.getName() + " must be a generated-id if allowGeneratedIdInsert = true");
}
if (fieldConfig.isForeignAutoRefresh() && !fieldConfig.isForeign()) {
throw new IllegalArgumentException(
"Field " + field.getName() + " must have foreign = true if foreignAutoRefresh = true");
}
if (fieldConfig.isForeignAutoCreate() && !fieldConfig.isForeign()) {
throw new IllegalArgumentException(
"Field " + field.getName() + " must have foreign = true if foreignAutoCreate = true");
}
if (fieldConfig.getForeignColumnName() != null && !fieldConfig.isForeign()) {
throw new IllegalArgumentException(
"Field " + field.getName() + " must have foreign = true if foreignColumnName is set");
}
if (fieldConfig.isVersion() && (dataPersister == null || !dataPersister.isValidForVersion())) {
throw new IllegalArgumentException(
"Field " + field.getName() + " is not a valid type to be a version field");
}
assignDataType(databaseType, dataPersister);
}
/**
* Because we go recursive in a lot of situations if we construct DAOs inside of the FieldType constructor, we have
* to do this 2nd pass initialization so we can better use the DAO caches.
*
* @see BaseDaoImpl#initialize()
*/
public void configDaoInformation(ConnectionSource connectionSource, Class parentClass)
throws SQLException {
Class fieldClass = field.getType();
DatabaseType databaseType = connectionSource.getDatabaseType();
final FieldType foreignIdField;
final FieldType foreignRefField;
final FieldType foreignFieldType;
final Dao foreignDao;
final MappedQueryForFieldEq mappedQueryForForeignField;
String foreignColumnName = fieldConfig.getForeignColumnName();
if (fieldConfig.isForeignAutoRefresh() || foreignColumnName != null) {
DatabaseTableConfig tableConfig = fieldConfig.getForeignTableConfig();
TableInfo foreignTableInfo;
if (tableConfig == null) {
// NOTE: the cast is necessary for maven
@SuppressWarnings("unchecked")
Dao castDao = (Dao) DaoManager.createDao(connectionSource, fieldClass);
foreignDao = castDao;
foreignTableInfo = foreignDao.getTableInfo();
} else {
tableConfig.extractFieldTypes(databaseType);
// NOTE: the cast is necessary for maven
@SuppressWarnings("unchecked")
Dao castDao = (Dao) DaoManager.createDao(connectionSource, tableConfig);
foreignDao = castDao;
foreignTableInfo = castDao.getTableInfo();
}
foreignIdField = foreignTableInfo.getIdField();
if (foreignIdField == null) {
throw new IllegalArgumentException("Foreign field " + fieldClass + " does not have id field");
}
if (foreignColumnName == null) {
foreignRefField = foreignIdField;
} else {
foreignRefField = foreignTableInfo.getFieldTypeByColumnName(foreignColumnName);
if (foreignRefField == null) {
throw new IllegalArgumentException(
"Foreign field " + fieldClass + " does not have field named '" + foreignColumnName + "'");
}
}
MappedQueryForFieldEq castMappedQueryForForeignField =
MappedQueryForFieldEq.build(foreignDao, foreignTableInfo, foreignRefField);
mappedQueryForForeignField = castMappedQueryForForeignField;
foreignFieldType = null;
} else if (fieldConfig.isForeign()) {
if (this.dataPersister != null && this.dataPersister.isPrimitive()) {
throw new IllegalArgumentException(
"Field " + this + " is a primitive class " + fieldClass + " but marked as foreign");
}
DatabaseTableConfig tableConfig = fieldConfig.getForeignTableConfig();
if (tableConfig != null) {
tableConfig.extractFieldTypes(databaseType);
// NOTE: the cast is necessary for maven
@SuppressWarnings("unchecked")
Dao castDao = (Dao) DaoManager.createDao(connectionSource, tableConfig);
foreignDao = castDao;
} else {
/*
* Initially we were only doing this just for BaseDaoEnabled.class and isForeignAutoCreate(). But we
* need it also for foreign fields because the alternative was to use reflection. Chances are if it is
* foreign we're going to need the DAO in the future anyway so we might as well create it. This also
* allows us to make use of any table configs.
*/
// NOTE: the cast is necessary for maven
@SuppressWarnings("unchecked")
Dao castDao = (Dao) DaoManager.createDao(connectionSource, fieldClass);
foreignDao = castDao;
}
TableInfo foreignTableInfo = foreignDao.getTableInfo();
foreignIdField = foreignTableInfo.getIdField();
if (foreignIdField == null) {
throw new IllegalArgumentException("Foreign field " + fieldClass + " does not have id field");
}
foreignRefField = foreignIdField;
if (isForeignAutoCreate() && !foreignIdField.isGeneratedId()) {
throw new IllegalArgumentException(
"Field " + field.getName() + ", if foreignAutoCreate = true then class "
+ fieldClass.getSimpleName() + " must have id field with generatedId = true");
}
foreignFieldType = null;
mappedQueryForForeignField = null;
} else if (fieldConfig.isForeignCollection()) {
if (fieldClass != Collection.class && !ForeignCollection.class.isAssignableFrom(fieldClass)) {
throw new SQLException("Field class for '" + field.getName() + "' must be of class "
+ ForeignCollection.class.getSimpleName() + " or Collection.");
}
Type type = field.getGenericType();
if (!(type instanceof ParameterizedType)) {
throw new SQLException("Field class for '" + field.getName() + "' must be a parameterized Collection.");
}
Type[] genericArguments = ((ParameterizedType) type).getActualTypeArguments();
if (genericArguments.length == 0) {
// i doubt this will ever be reached
throw new SQLException("Field class for '" + field.getName()
+ "' must be a parameterized Collection with at least 1 type.");
}
// If argument is a type variable we need to get arguments from superclass
if (genericArguments[0] instanceof TypeVariable) {
genericArguments = ((ParameterizedType) parentClass.getGenericSuperclass()).getActualTypeArguments();
}
if (!(genericArguments[0] instanceof Class)) {
throw new SQLException("Field class for '" + field.getName()
+ "' must be a parameterized Collection whose generic argument is an entity class not: "
+ genericArguments[0]);
}
@SuppressWarnings("unchecked")
Class collectionClazz = (Class) genericArguments[0];
@SuppressWarnings("unchecked")
DatabaseTableConfig tableConfig = (DatabaseTableConfig) fieldConfig.getForeignTableConfig();
Dao foundDao;
if (tableConfig == null) {
Dao castDao = DaoManager.createDao(connectionSource, collectionClazz);
foundDao = castDao;
} else {
Dao castDao = DaoManager.createDao(connectionSource, tableConfig);
foundDao = castDao;
}
foreignDao = foundDao;
foreignFieldType = findForeignFieldType(collectionClazz, parentClass, foundDao);
foreignIdField = null;
foreignRefField = null;
mappedQueryForForeignField = null;
} else {
foreignIdField = null;
foreignRefField = null;
foreignFieldType = null;
foreignDao = null;
mappedQueryForForeignField = null;
}
this.mappedQueryForForeignField = mappedQueryForForeignField;
this.foreignFieldType = foreignFieldType;
this.foreignDao = foreignDao;
this.foreignIdField = foreignIdField;
this.foreignRefField = foreignRefField;
// we have to do this because if we have a foreign field then our id type might have gone to an _id primitive
if (this.foreignRefField != null) {
assignDataType(databaseType, this.foreignRefField.getDataPersister());
}
}
public Field getField() {
return field;
}
public String getTableName() {
return tableName;
}
public String getFieldName() {
return field.getName();
}
/**
* Return the class of the field associated with this field type.
*/
public Class getType() {
return field.getType();
}
/**
* Return the generic type of the field associated with this field type.
*/
public Type getGenericType() {
return field.getGenericType();
}
public String getColumnName() {
return columnName;
}
public DataPersister getDataPersister() {
return dataPersister;
}
public Object getDataTypeConfigObj() {
return dataTypeConfigObj;
}
public SqlType getSqlType() {
return fieldConverter.getSqlType();
}
/**
* Return the default value as parsed from the {@link DatabaseFieldConfig#getDefaultValue()} or null if no default
* value.
*/
public Object getDefaultValue() {
return defaultValue;
}
public int getWidth() {
return fieldConfig.getWidth();
}
public boolean isCanBeNull() {
return fieldConfig.isCanBeNull();
}
/**
* Return whether the field is an id field. It is an id if {@link DatabaseField#id},
* {@link DatabaseField#generatedId}, OR {@link DatabaseField#generatedIdSequence} are enabled.
*/
public boolean isId() {
return isId;
}
/**
* Return whether the field is a generated-id field. This is true if {@link DatabaseField#generatedId} OR
* {@link DatabaseField#generatedIdSequence} are enabled.
*/
public boolean isGeneratedId() {
return isGeneratedId;
}
/**
* Return whether the field is a generated-id-sequence field. This is true if
* {@link DatabaseField#generatedIdSequence} is specified OR if {@link DatabaseField#generatedId} is enabled and the
* {@link DatabaseType#isIdSequenceNeeded} is enabled. If the latter is true then the sequence name will be
* auto-generated.
*/
public boolean isGeneratedIdSequence() {
return generatedIdSequence != null;
}
/**
* Return the generated-id-sequence associated with the field or null if {@link #isGeneratedIdSequence} is false.
*/
public String getGeneratedIdSequence() {
return generatedIdSequence;
}
public boolean isForeign() {
return fieldConfig.isForeign();
}
/**
* Assign to the data object the val corresponding to the fieldType.
*/
public void assignField(ConnectionSource connectionSource, Object data, Object val, boolean parentObject,
ObjectCache objectCache) throws SQLException {
if (logger.isLevelEnabled(Level.TRACE)) {
logger.trace("assiging from data {}, val {}: {}", (data == null ? "null" : data.getClass()),
(val == null ? "null" : val.getClass()), val);
}
// if this is a foreign object then val is the foreign object's id val
if (foreignRefField != null && val != null) {
// get the current field value which is the foreign-id
Object foreignRef = extractJavaFieldValue(data);
/*
* See if we don't need to create a new foreign object. If we are refreshing and the id field has not
* changed then there is no need to create a new foreign object and maybe lose previously refreshed field
* information.
*/
if (foreignRef != null && foreignRef.equals(val)) {
return;
}
// awhitlock: raised as OrmLite issue: bug #122
Object cachedVal;
ObjectCache foreignCache = foreignDao.getObjectCache();
if (foreignCache == null) {
cachedVal = null;
} else {
cachedVal = foreignCache.get(getType(), val);
}
if (cachedVal != null) {
val = cachedVal;
} else if (!parentObject) {
// the value we are to assign to our field is now the foreign object itself
val = createForeignObject(connectionSource, val, objectCache);
}
}
if (fieldSetMethod == null) {
try {
field.set(data, val);
} catch (IllegalArgumentException e) {
if (val == null) {
throw SqlExceptionUtil.create("Could not assign object '" + val + "' to field " + this, e);
} else {
throw SqlExceptionUtil.create(
"Could not assign object '" + val + "' of type " + val.getClass() + " to field " + this, e);
}
} catch (IllegalAccessException e) {
throw SqlExceptionUtil.create(
"Could not assign object '" + val + "' of type " + val.getClass() + "' to field " + this, e);
}
} else {
try {
fieldSetMethod.invoke(data, val);
} catch (Exception e) {
throw SqlExceptionUtil
.create("Could not call " + fieldSetMethod + " on object with '" + val + "' for " + this, e);
}
}
}
/**
* Assign an ID value to this field.
*/
public Object assignIdValue(ConnectionSource connectionSource, Object data, Number val, ObjectCache objectCache)
throws SQLException {
Object idVal = dataPersister.convertIdNumber(val);
if (idVal == null) {
throw new SQLException("Invalid class " + dataPersister + " for sequence-id " + this);
} else {
assignField(connectionSource, data, idVal, false, objectCache);
return idVal;
}
}
/**
* Return the value from the field in the object that is defined by this FieldType.
*/
public FV extractRawJavaFieldValue(Object object) throws SQLException {
Object val;
if (fieldGetMethod == null) {
try {
// field object may not be a T yet
val = field.get(object);
} catch (Exception e) {
throw SqlExceptionUtil.create("Could not get field value for " + this, e);
}
} else {
try {
val = fieldGetMethod.invoke(object);
} catch (Exception e) {
throw SqlExceptionUtil.create("Could not call " + fieldGetMethod + " for " + this, e);
}
}
@SuppressWarnings("unchecked")
FV converted = (FV) val;
return converted;
}
/**
* Return the value from the field in the object that is defined by this FieldType. If the field is a foreign object
* then the ID of the field is returned instead.
*/
public Object extractJavaFieldValue(Object object) throws SQLException {
Object val = extractRawJavaFieldValue(object);
// if this is a foreign object then we want its reference field
if (foreignRefField != null && val != null) {
val = foreignRefField.extractRawJavaFieldValue(val);
}
return val;
}
/**
* Extract a field from an object and convert to something suitable to be passed to SQL as an argument.
*/
public Object extractJavaFieldToSqlArgValue(Object object) throws SQLException {
return convertJavaFieldToSqlArgValue(extractJavaFieldValue(object));
}
/**
* Convert a field value to something suitable to be stored in the database.
*/
public Object convertJavaFieldToSqlArgValue(Object fieldVal) throws SQLException {
/*
* Limitation here. Some people may want to override the null with their own value in the converter but we
* currently don't allow that. Specifying a default value I guess is a better mechanism.
*/
if (fieldVal == null) {
return null;
} else {
return fieldConverter.javaToSqlArg(this, fieldVal);
}
}
/**
* Convert a string value into the appropriate Java field value.
*/
public Object convertStringToJavaField(String value, int columnPos) throws SQLException {
if (value == null) {
return null;
} else {
return fieldConverter.resultStringToJava(this, value, columnPos);
}
}
/**
* Move the SQL value to the next one for version processing.
*/
public Object moveToNextValue(Object val) throws SQLException {
if (dataPersister == null) {
return null;
} else {
return dataPersister.moveToNextValue(val);
}
}
/**
* Return the id of the associated foreign object or null if none.
*/
public FieldType getForeignIdField() {
return foreignIdField;
}
/**
* Return the field associated with the foreign object or null if none.
*/
public FieldType getForeignRefField() {
return foreignRefField;
}
/**
* Call through to {@link DataPersister#isEscapedValue()}
*/
public boolean isEscapedValue() {
return dataPersister.isEscapedValue();
}
public Enum getUnknownEnumVal() {
return fieldConfig.getUnknownEnumValue();
}
/**
* Return the format of the field.
*/
public String getFormat() {
return fieldConfig.getFormat();
}
public boolean isUnique() {
return fieldConfig.isUnique();
}
public boolean isUniqueCombo() {
return fieldConfig.isUniqueCombo();
}
public String getIndexName() {
return fieldConfig.getIndexName(tableName);
}
public String getUniqueIndexName() {
return fieldConfig.getUniqueIndexName(tableName);
}
/**
* Call through to {@link DataPersister#isEscapedDefaultValue()}
*/
public boolean isEscapedDefaultValue() {
return dataPersister.isEscapedDefaultValue();
}
/**
* Call through to {@link DataPersister#isComparable()}
*/
public boolean isComparable() throws SQLException {
if (fieldConfig.isForeignCollection()) {
return false;
}
/*
* We've seen dataPersister being null here in some strange cases. Why? It may because someone is searching on
* an improper field. Or maybe a table-config does not match the Java object?
*/
if (dataPersister == null) {
throw new SQLException("Internal error. Data-persister is not configured for field. "
+ "Please post _full_ exception with associated data objects to mailing list: " + this);
} else {
return dataPersister.isComparable();
}
}
/**
* Call through to {@link DataPersister#isArgumentHolderRequired()}
*/
public boolean isArgumentHolderRequired() {
return dataPersister.isArgumentHolderRequired();
}
/**
* Call through to {@link DatabaseFieldConfig#isForeignCollection()}
*/
public boolean isForeignCollection() {
return fieldConfig.isForeignCollection();
}
/**
* Build and return a foreign collection based on the field settings that matches the id argument. This can return
* null in certain circumstances.
*
* @param parent
* The parent object that we will set on each item in the collection.
* @param id
* The id of the foreign object we will look for. This can be null if we are creating an empty
* collection.
*/
public BaseForeignCollection buildForeignCollection(Object parent, FID id) throws SQLException {
// this can happen if we have a foreign-auto-refresh scenario
if (foreignFieldType == null) {
return null;
}
@SuppressWarnings("unchecked")
Dao castDao = (Dao) foreignDao;
if (!fieldConfig.isForeignCollectionEager()) {
// we know this won't go recursive so no need for the counters
return new LazyForeignCollection(castDao, parent, id, foreignFieldType,
fieldConfig.getForeignCollectionOrderColumnName(), fieldConfig.isForeignCollectionOrderAscending());
}
// try not to create level counter objects unless we have to
LevelCounters levelCounters = threadLevelCounters.get();
if (levelCounters == null) {
if (fieldConfig.getForeignCollectionMaxEagerLevel() == 0) {
// then return a lazy collection instead
return new LazyForeignCollection(castDao, parent, id, foreignFieldType,
fieldConfig.getForeignCollectionOrderColumnName(),
fieldConfig.isForeignCollectionOrderAscending());
}
levelCounters = new LevelCounters();
threadLevelCounters.set(levelCounters);
}
if (levelCounters.foreignCollectionLevel == 0) {
levelCounters.foreignCollectionLevelMax = fieldConfig.getForeignCollectionMaxEagerLevel();
}
// are we over our level limit?
if (levelCounters.foreignCollectionLevel >= levelCounters.foreignCollectionLevelMax) {
// then return a lazy collection instead
return new LazyForeignCollection(castDao, parent, id, foreignFieldType,
fieldConfig.getForeignCollectionOrderColumnName(), fieldConfig.isForeignCollectionOrderAscending());
}
levelCounters.foreignCollectionLevel++;
try {
return new EagerForeignCollection(castDao, parent, id, foreignFieldType,
fieldConfig.getForeignCollectionOrderColumnName(), fieldConfig.isForeignCollectionOrderAscending());
} finally {
levelCounters.foreignCollectionLevel--;
}
}
/**
* Get the result object from the results. A call through to {@link FieldConverter#resultToJava}.
*/
public T resultToJava(DatabaseResults results, Map columnPositions) throws SQLException {
Integer dbColumnPos = columnPositions.get(columnName);
if (dbColumnPos == null) {
dbColumnPos = results.findColumn(columnName);
columnPositions.put(columnName, dbColumnPos);
}
/*
* Subtle problem here. If the field is a foreign-field and/or a primitive and the value was null then we get 0
* from results.getInt() which mirrors the ResultSet. We have to specifically test to see if we have a null
* result with results.wasNull(...) afterwards so we return a null value to not create the sub-object or turn a
* Integer field into 0 instead of null.
*
* But this presents a limitation. Sometimes people want to convert a result field value that is stored in the
* database as null into a non-null value when it comes out of the database. There is no way to do that because
* of the results.wasNull(...) checks after the fact then override the non-null value from the converter.
*/
@SuppressWarnings("unchecked")
T converted = (T) fieldConverter.resultToJava(this, results, dbColumnPos);
if (fieldConfig.isForeign()) {
/*
* If your foreign field is a primitive and the value was null then this would return 0 from
* results.getInt(). We have to specifically test to see if we have a foreign field so if it is null we
* return a null value to not create the sub-object.
*/
if (results.wasNull(dbColumnPos)) {
return null;
}
} else if (dataPersister.isPrimitive()) {
if (fieldConfig.isThrowIfNull() && results.wasNull(dbColumnPos)) {
throw new SQLException(
"Results value for primitive field '" + field.getName() + "' was an invalid null value");
}
} else if (!fieldConverter.isStreamType() && results.wasNull(dbColumnPos)) {
// we can't check if we have a null if this is a stream type
return null;
}
return converted;
}
/**
* Call through to {@link DataPersister#isSelfGeneratedId()}
*/
public boolean isSelfGeneratedId() {
return dataPersister.isSelfGeneratedId();
}
/**
* Call through to {@link DatabaseFieldConfig#isAllowGeneratedIdInsert()}
*/
public boolean isAllowGeneratedIdInsert() {
return fieldConfig.isAllowGeneratedIdInsert();
}
/**
* Call through to {@link DatabaseFieldConfig#getColumnDefinition()} and
* {@link DatabaseFieldConfig#getFullColumnDefinition()}.
*/
public String getColumnDefinition() {
String full = fieldConfig.getFullColumnDefinition();
if (full == null) {
return fieldConfig.getColumnDefinition();
} else {
return full;
}
}
/**
* Call through to {@link DatabaseFieldConfig#isForeignAutoCreate()}
*/
public boolean isForeignAutoCreate() {
return fieldConfig.isForeignAutoCreate();
}
/**
* Call through to {@link DatabaseFieldConfig#isVersion()}
*/
public boolean isVersion() {
return fieldConfig.isVersion();
}
/**
* Call through to {@link DataPersister#generateId()}
*/
public Object generateId() {
return dataPersister.generateId();
}
/**
* Call through to {@link DatabaseFieldConfig#isReadOnly()}
*/
public boolean isReadOnly() {
return fieldConfig.isReadOnly();
}
/**
* Return the value of field in the data argument if it is not the default value for the class. If it is the default
* then null is returned.
*/
public FV getFieldValueIfNotDefault(Object object) throws SQLException {
@SuppressWarnings("unchecked")
FV fieldValue = (FV) extractJavaFieldValue(object);
if (isFieldValueDefault(fieldValue)) {
return null;
} else {
return fieldValue;
}
}
/**
* Return whether or not the data object has a default value passed for this field of this type.
*/
public boolean isObjectsFieldValueDefault(Object object) throws SQLException {
Object fieldValue = extractJavaFieldValue(object);
return isFieldValueDefault(fieldValue);
}
/**
* Return whether or not the field value passed in is the default value for the type of the field. Null will return
* true.
*/
public Object getJavaDefaultValueDefault() {
if (field.getType() == boolean.class) {
return DEFAULT_VALUE_BOOLEAN;
} else if (field.getType() == byte.class || field.getType() == Byte.class) {
return DEFAULT_VALUE_BYTE;
} else if (field.getType() == char.class || field.getType() == Character.class) {
return DEFAULT_VALUE_CHAR;
} else if (field.getType() == short.class || field.getType() == Short.class) {
return DEFAULT_VALUE_SHORT;
} else if (field.getType() == int.class || field.getType() == Integer.class) {
return DEFAULT_VALUE_INT;
} else if (field.getType() == long.class || field.getType() == Long.class) {
return DEFAULT_VALUE_LONG;
} else if (field.getType() == float.class || field.getType() == Float.class) {
return DEFAULT_VALUE_FLOAT;
} else if (field.getType() == double.class || field.getType() == Double.class) {
return DEFAULT_VALUE_DOUBLE;
} else {
return null;
}
}
/**
* Pass the foreign data argument to the foreign {@link Dao#create(Object)} method.
*/
public int createWithForeignDao(T foreignData) throws SQLException {
@SuppressWarnings("unchecked")
Dao castDao = (Dao) foreignDao;
return castDao.create(foreignData);
}
/**
* @deprecated Use {@link #createFieldType(DatabaseType, String, Field, Class)}.
*/
@Deprecated
public static FieldType createFieldType(ConnectionSource connectionSource, String tableName, Field field,
Class parentClass) throws SQLException {
return createFieldType(connectionSource.getDatabaseType(), tableName, field, parentClass);
}
/**
* Return An instantiated {@link FieldType} or null if the field does not have a {@link DatabaseField} annotation.
*/
public static FieldType createFieldType(DatabaseType databaseType, String tableName, Field field,
Class parentClass) throws SQLException {
DatabaseFieldConfig fieldConfig = DatabaseFieldConfig.fromField(databaseType, tableName, field);
if (fieldConfig == null) {
return null;
} else {
return new FieldType(databaseType, tableName, field, fieldConfig, parentClass);
}
}
@Override
public boolean equals(Object arg) {
if (arg == null || arg.getClass() != this.getClass()) {
return false;
}
FieldType other = (FieldType) arg;
return field.equals(other.field)
&& (parentClass == null ? other.parentClass == null : parentClass.equals(other.parentClass));
}
@Override
public int hashCode() {
return field.hashCode();
}
@Override
public String toString() {
return getClass().getSimpleName() + ":name=" + field.getName() + ",class="
+ field.getDeclaringClass().getSimpleName();
}
private Object createForeignObject(ConnectionSource connectionSource, Object val, ObjectCache objectCache)
throws SQLException {
// try to stop the level counters objects from being created
LevelCounters levelCounters = threadLevelCounters.get();
if (levelCounters == null) {
// only create a shell if we are not in auto-refresh mode
if (!fieldConfig.isForeignAutoRefresh()) {
return createForeignShell(connectionSource, val, objectCache);
}
levelCounters = new LevelCounters();
threadLevelCounters.set(levelCounters);
}
// we record the current auto-refresh level which will be used along the way
if (levelCounters.autoRefreshLevel == 0) {
// if we aren't in an auto-refresh loop then don't _start_ an new loop if auto-refresh is not configured
if (!fieldConfig.isForeignAutoRefresh()) {
return createForeignShell(connectionSource, val, objectCache);
}
levelCounters.autoRefreshLevelMax = fieldConfig.getMaxForeignAutoRefreshLevel();
}
// if we have recursed the proper number of times, return a shell with just the id set
if (levelCounters.autoRefreshLevel >= levelCounters.autoRefreshLevelMax) {
return createForeignShell(connectionSource, val, objectCache);
}
/*
* We may not have a mapped query for id because we aren't auto-refreshing ourselves. But a parent class may be
* auto-refreshing us with a level > 1 so we may need to build our query-for-id optimization on the fly here.
*/
if (mappedQueryForForeignField == null) {
@SuppressWarnings("unchecked")
Dao