blob: e1042d3fb6ff0fbddbd1567330119b9ac04107b0 [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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.JavaVersions;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.lib.util.collections.ComparatorChain;
import org.apache.openjpa.lib.xml.Commentable;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.ProxyManager;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;
/**
* Metadata for a managed class field.
*
* @author Abe White
*/
public class FieldMetaData
extends Extensions
implements ValueMetaData, MetaDataContext, MetaDataModes, Commentable {
private static final long serialVersionUID = -566180883009883198L;
/**
* Constant specifying that no null-value was given.
*/
public static final int NULL_UNSET = -1;
/**
* Constant specifying to use a datastore null to persist null values
* in object fields.
*/
public static final int NULL_NONE = 0;
/**
* Constant specifying to use a datastore default value to persist null
* values in object fields.
*/
public static final int NULL_DEFAULT = 1;
/**
* Constant specifying to throw an exception when attempting to persist
* null values in object fields.
*/
public static final int NULL_EXCEPTION = 2;
/**
* Constant specifying the management level of a field.
*/
public static final int MANAGE_PERSISTENT = 3;
/**
* Constant specifying the management level of a field.
*/
public static final int MANAGE_TRANSACTIONAL = 1;
/**
* Constant specifying the management level of a field.
*/
public static final int MANAGE_NONE = 0;
public static final int ONE_TO_ONE = 1;
public static final int ONE_TO_MANY = 2;
public static final int MANY_TO_ONE = 3;
public static final int MANY_TO_MANY = 4;
private static final Localizer _loc = Localizer.forPackage
(FieldMetaData.class);
private static final int DFG_FALSE = 1;
private static final int DFG_TRUE = 2;
private static final int DFG_EXPLICIT = 4;
private static final Method DEFAULT_METHOD;
static {
try {
DEFAULT_METHOD = Object.class.getMethod("wait", (Class[]) null);
} catch (Exception e) {
// shouldn't ever happen
throw new InternalException(e);
}
}
// name and type
private final ValueMetaData _val;
private final ValueMetaData _key;
private final ValueMetaData _elem;
private final ClassMetaData _owner;
private final String _name;
private Class<?> _dec = null;
private ClassMetaData _decMeta = null;
private String _fullName = null;
private String _embedFullName = null;
private int _resMode = MODE_NONE;
private String _mappedByIdValue = null;
private int _access = AccessCode.UNKNOWN;
// load/store info
private String[] _comments = null;
private int _listIndex = -1;
////////////////////////////////////////////////////////////////////
// Note: if you add additional state, make sure to add it to copy()
////////////////////////////////////////////////////////////////////
// misc info
private Class<?> _proxyClass = null;
private Object _initializer = null;
private boolean _transient = false;
private boolean _primKey = false;
private Boolean _version = null;
private int _nullValue = NULL_UNSET;
private int _manage = MANAGE_PERSISTENT;
private int _index = -1;
private int _decIndex = -1;
private int _pkIndex = -1;
private boolean _explicit = false;
private int _dfg = 0;
private Set<String> _fgSet = null;
private String[] _fgs = null;
private String _lfg = null;
private Boolean _lrs = null;
private Boolean _stream = null;
private String _extName = null;
private String _factName = null;
private String _extString = null;
private Map _extValues = Collections.EMPTY_MAP;
private Map _fieldValues = Collections.EMPTY_MAP;
private Boolean _enumField = null;
private Boolean _lobField = null;
private Boolean _serializableField = null;
private boolean _generated = false;
private boolean _useSchemaElement = true;
// Members aren't serializable. Use a proxy that can provide a Member
// to avoid writing the full Externalizable implementation.
private MemberProvider _backingMember = null;
// Members aren't serializable. Initializing _extMethod and _factMethod to
// DEFAULT_METHOD is sufficient to trigger lazy population of these fields.
private transient Method _extMethod = DEFAULT_METHOD;
private transient Member _factMethod = DEFAULT_METHOD;
// intermediate and impl data
private boolean _intermediate = true;
private Boolean _implData = Boolean.TRUE;
// value generation
private int _valStrategy = -1;
private int _upStrategy = -1;
private String _seqName = ClassMetaData.DEFAULT_STRING;
private SequenceMetaData _seqMeta = null;
// inverses
private String _mappedBy = null;
private FieldMetaData _mappedByMeta = null;
private FieldMetaData[] _inverses = null;
private String _inverse = ClassMetaData.DEFAULT_STRING;
// ordering on load
private Order[] _orders = null;
private String _orderDec = null;
// indicate if this field is used by other field as "order by" value
private boolean _usedInOrderBy = false;
private boolean _isElementCollection = false;
private int _associationType;
private boolean _persistentCollection = false;
private Boolean _delayCapable = null;
/**
* Constructor.
*
* @param name the field name
* @param type the field type
* @param owner the owning class metadata
*/
protected FieldMetaData(String name, Class<?> type, ClassMetaData owner) {
_name = name;
_owner = owner;
_dec = null;
_decMeta = null;
_val = owner.getRepository().newValueMetaData(this);
_key = owner.getRepository().newValueMetaData(this);
_elem = owner.getRepository().newValueMetaData(this);
setDeclaredType(type);
}
/**
* Supply the backing member object; this allows us to utilize
* parameterized type information if available.
* Sets the access style of this receiver based on whether the given
* member represents a field or getter method.
*/
public void backingMember(Member member) {
if (member == null)
return;
if (Modifier.isTransient(member.getModifiers()))
_transient = true;
_backingMember = new MemberProvider(member);
Class<?> type;
Class<?>[] types;
if (member instanceof Field) {
Field f = (Field) member;
type = f.getType();
types = JavaVersions.getParameterizedTypes(f);
setAccessType(AccessCode.FIELD);
} else {
Method meth = (Method) member;
type = meth.getReturnType();
types = JavaVersions.getParameterizedTypes(meth);
setAccessType(AccessCode.PROPERTY);
}
setDeclaredType(type);
if (Collection.class.isAssignableFrom(type)
&& _elem.getDeclaredType() == Object.class
&& types.length == 1) {
_elem.setDeclaredType(types[0]);
} else if (Map.class.isAssignableFrom(type)
&& types.length == 2) {
if (_key.getDeclaredType() == Object.class)
_key.setDeclaredType(types[0]);
if (_elem.getDeclaredType() == Object.class)
_elem.setDeclaredType(types[1]);
}
}
/**
* Return the backing member supplied in {@link #backingMember}.
*/
public Member getBackingMember() {
return (_backingMember == null) ? null : _backingMember.getMember();
}
/**
* The metadata repository.
*/
@Override
public MetaDataRepository getRepository() {
return _owner.getRepository();
}
/**
* The class that defines the metadata for this field.
*/
public ClassMetaData getDefiningMetaData() {
return _owner;
}
/**
* The declaring class.
*/
public Class<?> getDeclaringType() {
return (_dec == null) ? _owner.getDescribedType() : _dec;
}
/**
* The declaring class.
*/
public void setDeclaringType(Class<?> cls) {
_dec = cls;
_decMeta = null;
_fullName = null;
_embedFullName = null;
}
/**
* The declaring class.
*/
public ClassMetaData getDeclaringMetaData() {
if (_dec == null)
return _owner;
if (_decMeta == null)
_decMeta = getRepository().getMetaData(_dec,
_owner.getEnvClassLoader(), true);
return _decMeta;
}
/**
* The field name.
*/
public String getName() {
return _name;
}
/**
* The field name, qualified by the owning class.
* @deprecated Use getFullName(boolean) instead.
*/
@Deprecated
public String getFullName() {
return getFullName(false);
}
/**
* The field name, qualified by the owning class and optionally the
* embedding owner's name (if any).
*/
public String getFullName(boolean embedOwner) {
if (_fullName == null)
_fullName = getDeclaringType().getName() + "." + _name;
if (embedOwner && _embedFullName == null) {
if (_owner.getEmbeddingMetaData() == null)
_embedFullName = _fullName;
else
_embedFullName = _owner.getEmbeddingMetaData().
getFieldMetaData().getFullName(true) + "." + _fullName;
}
return (embedOwner) ? _embedFullName : _fullName;
}
/**
* The field name, qualified by the defining class.
*/
public String getRealName() {
// Added to support OPENJPA-704
return getDefiningMetaData().getDescribedType().getName() + "." + _name;
}
/**
* MetaData about the field value.
*/
public ValueMetaData getValue() {
return _val;
}
/**
* Metadata about the key value.
*/
public ValueMetaData getKey() {
return _key;
}
/**
* Metadata about the element value.
*/
public ValueMetaData getElement() {
return _elem;
}
/**
* Return whether this field is mapped to the datastore. By default,
* returns true for all persistent fields whose defining class is mapped.
*/
public boolean isMapped() {
return _manage == MANAGE_PERSISTENT && _owner.isMapped();
}
/**
* The type this field was initialized with, and therefore the
* type to use for proxies when loading data into this field.
*/
public Class<?> getProxyType() {
return (_proxyClass == null) ? getDeclaredType() : _proxyClass;
}
/**
* The type this field was initialized with, and therefore the
* type to use for proxies when loading data into this field.
*/
public void setProxyType(Class<?> type) {
_proxyClass = type;
}
/**
* The initializer used by the field, or null if none. This
* is additional information for initializing the field, such as
* a custom {@link Comparator} used by a {@link Set} or
* a {@link TimeZone} used by a {@link Calendar}.
*/
public Object getInitializer() {
return _initializer;
}
/**
* The initializer used by the field, or null if none. This
* is additional information for initializing the field, such as
* a custom {@link Comparator} used by a {@link Set} or
* a {@link TimeZone} used by a {@link Calendar}.
*/
public void setInitializer(Object initializer) {
_initializer = initializer;
}
/**
* Return whether this is a transient field.
*/
public boolean isTransient() {
return _transient;
}
/**
* Return whether this is a transient field.
*/
public void setTransient(boolean trans) {
_transient = trans;
}
/**
* The absolute index of this persistent/transactional field.
*/
public int getIndex() {
return _index;
}
/**
* The absolute index of this persistent/transactional field.
*/
public void setIndex(int index) {
_index = index;
}
/**
* The relative index of this persistent/transactional field.
*/
public int getDeclaredIndex() {
return _decIndex;
}
/**
* The relative index of this persistent/transactional field.
*/
public void setDeclaredIndex(int index) {
_decIndex = index;
}
/**
* The index in which this field was listed in the metadata. Defaults to
* <code>-1</code> if this field was not listed in the metadata.
*/
public int getListingIndex() {
return _listIndex;
}
/**
* The index in which this field was listed in the metadata. Defaults to
* <code>-1</code> if this field was not listed in the metadata.
*/
public void setListingIndex(int index) {
_listIndex = index;
}
/**
* The absolute primary key index for this field, or -1 if not a primary
* key. The first primary key field has index 0, the second index 1, etc.
*/
public int getPrimaryKeyIndex() {
return _pkIndex;
}
/**
* The absolute primary key index for this field, or -1 if not a primary
* key. The first primary key field has index 0, the second index 1, etc.
*/
public void setPrimaryKeyIndex(int index) {
_pkIndex = index;
}
/**
* Return the management level for the field. Will be one of:
* <ul>
* <li>{@link #MANAGE_PERSISTENT}: the field is persistent</li>
* <li>{@link #MANAGE_TRANSACTIONAL}: the field is transactional but not
* persistent</li>
* <li>{@link #MANAGE_NONE}: the field is not managed</li>
* </ul> Defaults to {@link #MANAGE_PERSISTENT}.
*/
public int getManagement() {
return _manage;
}
/**
* Return the management level for the field. Will be one of:
* <ul>
* <li>{@link #MANAGE_PERSISTENT}: the field is persistent</li>
* <li>{@link #MANAGE_TRANSACTIONAL}: the field is transactional but not
* persistent</li>
* <li>{@link #MANAGE_NONE}: the field is not managed</li>
* </ul>
* Defaults to {@link #MANAGE_PERSISTENT}.
*/
public void setManagement(int manage) {
if ((_manage == MANAGE_NONE) != (manage == MANAGE_NONE))
_owner.clearFieldCache();
_manage = manage;
}
/**
* Whether this is a primary key field.
*/
public boolean isPrimaryKey() {
return _primKey;
}
/**
* Whether this is a primary key field.
*/
public void setPrimaryKey(boolean primKey) {
_primKey = primKey;
}
/**
* For a primary key field, return the type of the corresponding object id
* class field.
*/
public int getObjectIdFieldTypeCode() {
ClassMetaData relmeta = getDeclaredTypeMetaData();
if (relmeta == null)
return getDeclaredTypeCode();
if (relmeta.getIdentityType() == ClassMetaData.ID_DATASTORE) {
boolean unwrap = getRepository().getMetaDataFactory().getDefaults().
isDataStoreObjectIdFieldUnwrapped();
return (unwrap) ? JavaTypes.LONG : JavaTypes.OBJECT;
}
if (relmeta.isOpenJPAIdentity())
return relmeta.getPrimaryKeyFields()[0].getObjectIdFieldTypeCode();
return JavaTypes.OBJECT;
}
/**
* For a primary key field, return the type of the corresponding object id
* class field.
*/
public Class<?> getObjectIdFieldType() {
ClassMetaData relmeta = getDeclaredTypeMetaData();
if (relmeta == null || getValue().isEmbedded())
return getDeclaredType();
switch (relmeta.getIdentityType()) {
case ClassMetaData.ID_DATASTORE:
boolean unwrap = getRepository().getMetaDataFactory().
getDefaults().isDataStoreObjectIdFieldUnwrapped();
return (unwrap) ? long.class : Object.class;
case ClassMetaData.ID_APPLICATION:
if (relmeta.isOpenJPAIdentity())
return relmeta.getPrimaryKeyFields()[0].
getObjectIdFieldType();
return (relmeta.getObjectIdType() == null) ? Object.class
: relmeta.getObjectIdType();
default:
return Object.class;
}
}
/**
* Whether this field holds optimistic version information.
*/
public boolean isVersion() {
return _version == Boolean.TRUE;
}
/**
* Whether this field holds optimistic version information.
*/
public void setVersion(boolean version) {
_version = (version) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Whether this field is in the default fetch group.
*/
public boolean isInDefaultFetchGroup() {
if (_dfg == 0) {
if (_manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion())
_dfg = DFG_FALSE;
else {
// field left as default; dfg setting depends on type
switch (getTypeCode()) {
case JavaTypes.OBJECT:
if (isSerializable() || isEnum())
_dfg = DFG_TRUE;
else
_dfg = DFG_FALSE;
break;
case JavaTypes.ARRAY:
if (isLobArray())
_dfg = DFG_TRUE;
else
_dfg = DFG_FALSE;
break;
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
case JavaTypes.PC:
case JavaTypes.PC_UNTYPED:
_dfg = DFG_FALSE;
break;
default:
_dfg = DFG_TRUE;
}
}
}
return (_dfg & DFG_TRUE) > 0;
}
private boolean isEnum() {
if (_enumField == null) {
Class<?> decl = getDeclaredType();
_enumField = Enum.class.isAssignableFrom(decl)
? Boolean.TRUE : Boolean.FALSE;
}
return _enumField;
}
private boolean isSerializable() {
if (_serializableField == null) {
Class<?> decl = getDeclaredType();
if (Serializable.class.isAssignableFrom(decl))
_serializableField = Boolean.TRUE;
else
_serializableField = Boolean.FALSE;
}
return _serializableField;
}
private boolean isLobArray() {
// check for byte[], Byte[], char[], Character[]
if (_lobField == null) {
Class<?> decl = getDeclaredType();
if (decl == byte[].class || decl == Byte[].class ||
decl == char[].class || decl == Character[].class)
_lobField = Boolean.TRUE;
else
_lobField = Boolean.FALSE;
}
return _lobField;
}
/**
* Whether this field is in the default fetch group.
*/
public void setInDefaultFetchGroup(boolean dfg) {
if (dfg)
_dfg = DFG_TRUE;
else
_dfg = DFG_FALSE;
_dfg |= DFG_EXPLICIT;
}
/**
* Whether the default fetch group setting is explicit.
*/
public boolean isDefaultFetchGroupExplicit() {
return (_dfg & DFG_EXPLICIT) > 0;
}
/**
* Whether the default fetch group setting is explicit. Allow setting
* for testing.
*/
public void setDefaultFetchGroupExplicit(boolean explicit) {
if (explicit)
_dfg |= DFG_EXPLICIT;
else
_dfg &= ~DFG_EXPLICIT;
}
/**
* Gets the name of the custom fetch groups those are associated to this
* receiver. This does not include the "default" and "all" fetch groups.
*
* @return the set of fetch group names, not including the default and
* all fetch groups.
*/
public String[] getCustomFetchGroups() {
if (_fgs == null) {
if (_fgSet == null || _manage != MANAGE_PERSISTENT
|| isPrimaryKey() || isVersion())
_fgs = new String[0];
else
_fgs = _fgSet.toArray(new String[_fgSet.size()]);
}
return _fgs;
}
/**
* The fetch group that is to be loaded when this receiver is loaded, or
* null if none set.
*/
public String getLoadFetchGroup () {
return _lfg;
}
/**
* The fetch group that is to be loaded when this receiver is loaded, or
* null if none set.
*/
public void setLoadFetchGroup (String lfg) {
if ("".equals(lfg))
lfg = null;
_lfg = lfg;
}
/**
* Whether this field is in the given fetch group.
*/
public boolean isInFetchGroup(String fg) {
if (_manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion())
return false;
if (FetchGroup.NAME_ALL.equals(fg))
return true;
if (FetchGroup.NAME_DEFAULT.equals(fg))
return isInDefaultFetchGroup();
return _fgSet != null && _fgSet.contains(fg);
}
/**
* Set whether this field is in the given fetch group.
*
* @param fg is the name of a fetch group that must be present in the
* class that declared this field or any of its persistent superclasses.
*/
public void setInFetchGroup(String fg, boolean in) {
if (StringUtil.isEmpty(fg))
throw new MetaDataException(_loc.get("empty-fg-name", this));
if (fg.equals(FetchGroup.NAME_ALL))
return;
if (fg.equals(FetchGroup.NAME_DEFAULT)) {
setInDefaultFetchGroup(in);
return;
}
if (_owner.getFetchGroup(fg) == null)
throw new MetaDataException(_loc.get("unknown-fg", fg, this));
if (in && _fgSet == null)
_fgSet = new HashSet<>();
if ((in && _fgSet.add(fg))
|| (!in && _fgSet != null && _fgSet.remove(fg)))
_fgs = null;
}
/**
* How the data store should treat null values for this field:
* <ul>
* <li>{@link #NULL_UNSET}: no value supplied</li>
* <li>{@link #NULL_NONE}: leave null values as null in the data store</li>
* <li>{@link #NULL_EXCEPTION}: throw an exception if this field is null
* at commit</li>
* <li>{@link #NULL_DEFAULT}: use the database default if this field is
* null at commit</li>
* </ul> Defaults to {@link #NULL_UNSET}.
*/
public int getNullValue() {
return _nullValue;
}
/**
* How the data store should treat null values for this field:
* <ul>
* <li>{@link #NULL_UNSET}: no value supplied</li>
* <li>{@link #NULL_NONE}: leave null values as null in the data store</li>
* <li>{@link #NULL_EXCEPTION}: throw an exception if this field is null
* at commit</li>
* <li>{@link #NULL_DEFAULT}: use the database default if this field is
* null at commit</li>
* </ul> Defaults to {@link #NULL_UNSET}.
*/
public void setNullValue(int nullValue) {
_nullValue = nullValue;
}
/**
* Whether this field is explicitly declared in the metadata.
*/
public boolean isExplicit() {
return _explicit;
}
/**
* Whether this field is explicitly declared in the metadata.
*/
public void setExplicit(boolean explicit) {
_explicit = explicit;
}
/**
* The field that this field shares a mapping with.
*/
public String getMappedBy() {
return _mappedBy;
}
/**
* The field that this field shares a mapping with.
*/
public void setMappedBy(String mapped) {
_mappedBy = mapped;
_mappedByMeta = null;
}
/**
* The field that this field shares a mapping with.
*/
public FieldMetaData getMappedByMetaData() {
if (_mappedBy != null && _mappedByMeta == null) {
ClassMetaData meta = null;
switch (getTypeCode()) {
case JavaTypes.PC:
meta = getTypeMetaData();
break;
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
meta = _elem.getTypeMetaData();
break;
}
FieldMetaData field = (meta == null) ? null
: getMappedByField(meta, _mappedBy);
if (field == null)
throw new MetaDataException(_loc.get("no-mapped-by", this,
_mappedBy));
if (field.getMappedBy() != null)
throw new MetaDataException(_loc.get("circ-mapped-by", this,
_mappedBy));
OpenJPAConfiguration conf = getRepository().getConfiguration();
boolean isAbstractMappingUniDirectional = getRepository().getMetaDataFactory().
getDefaults().isAbstractMappingUniDirectional(conf);
if (isAbstractMappingUniDirectional) {
if (field.getDeclaringMetaData().isAbstract())
throw new MetaDataException(_loc.get("no-mapped-by-in-mapped-super", field,
field.getDeclaringMetaData()));
if (this.getDeclaringMetaData().isAbstract())
throw new MetaDataException(_loc.get("no-mapped-by-in-mapped-super", this,
this.getDeclaringMetaData()));
}
_mappedByMeta = field;
}
return _mappedByMeta;
}
public FieldMetaData getMappedByField(ClassMetaData meta, String mappedBy) {
FieldMetaData field = meta.getField(mappedBy);
if (field != null)
return field;
int dotIdx = mappedBy.indexOf(".");
if ( dotIdx == -1)
return null;
String fieldName = mappedBy.substring(0, dotIdx);
FieldMetaData field1 = meta.getField(fieldName);
if (field1 == null)
return null;
ClassMetaData meta1 = field1.getEmbeddedMetaData();
if (meta1 == null)
return null;
String mappedBy1 = mappedBy.substring(dotIdx + 1);
return getMappedByField(meta1, mappedBy1);
}
/**
* Logical inverse field.
*/
public String getInverse() {
if (ClassMetaData.DEFAULT_STRING.equals(_inverse))
_inverse = null;
return _inverse;
}
/**
* Logical inverse field.
*/
public void setInverse(String inverse) {
_inverses = null;
_inverse = inverse;
}
/**
* Return all inverses of this field.
*/
public FieldMetaData[] getInverseMetaDatas() {
if (_inverses == null) {
// can't declare both an inverse owner and a logical inverse
String inv = getInverse();
if (_mappedBy != null && inv != null && !_mappedBy.equals(inv))
throw new MetaDataException(_loc.get("mapped-not-inverse",
this));
// get the metadata for the type on the other side of this relation
ClassMetaData meta = null;
switch (getTypeCode()) {
case JavaTypes.PC:
meta = getTypeMetaData();
break;
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
meta = _elem.getTypeMetaData();
break;
}
Collection<FieldMetaData> inverses = null;
if (meta != null) {
// add mapped by and named inverse, if any
FieldMetaData field = getMappedByMetaData();
if (field != null) {
// mapped by field isn't necessarily a pc type, but all
// inverses must be
if (field.getTypeCode() == JavaTypes.PC
|| field.getElement().getTypeCode() == JavaTypes.PC) {
inverses = new ArrayList<>(3);
inverses.add(field);
}
} else if (inv != null) {
field = meta.getField(inv);
if (field == null)
throw new MetaDataException(_loc.get("no-inverse",
this, inv));
inverses = new ArrayList<>(3);
inverses.add(field);
}
// scan rel type for fields that name this field as an inverse
FieldMetaData[] fields = meta.getFields();
Class<?> type = getDeclaringMetaData().getDescribedType();
for (FieldMetaData fieldMetaData : fields) {
// skip fields that aren't compatible with our owning class
switch (fieldMetaData.getTypeCode()) {
case JavaTypes.PC:
if (!type.isAssignableFrom(fieldMetaData.getType()))
continue;
break;
case JavaTypes.COLLECTION:
case JavaTypes.ARRAY:
if (!type.isAssignableFrom(fieldMetaData.
getElement().getType()))
continue;
break;
default:
continue;
}
// if the field declares us as its inverse and we haven't
// already added it (we might have if we also declared it
// as our inverse), add it now
if (_name.equals(fieldMetaData.getMappedBy())
|| _name.equals(fieldMetaData.getInverse())) {
if (inverses == null)
inverses = new ArrayList<>(3);
if (!inverses.contains(fieldMetaData))
inverses.add(fieldMetaData);
}
}
}
MetaDataRepository repos = getRepository();
if (inverses == null)
_inverses = repos.EMPTY_FIELDS;
else
_inverses = inverses.toArray
(repos.newFieldMetaDataArray(inverses.size()));
}
return _inverses;
}
/**
* The strategy to use for insert value generation.
* One of the constants from {@link ValueStrategies}.
*/
public int getValueStrategy() {
if (_valStrategy == -1)
_valStrategy = ValueStrategies.NONE;
return _valStrategy;
}
/**
* The strategy to use for insert value generation.
* One of the constants from {@link ValueStrategies}.
*/
public void setValueStrategy(int strategy) {
_valStrategy = strategy;
if (strategy != ValueStrategies.SEQUENCE)
setValueSequenceName(null);
}
/**
* The value sequence name, or null for none.
*/
public String getValueSequenceName() {
if (ClassMetaData.DEFAULT_STRING.equals(_seqName))
_seqName = null;
return _seqName;
}
/**
* The value sequence name, or null for none.
*/
public void setValueSequenceName(String seqName) {
_seqName = seqName;
_seqMeta = null;
if (seqName != null)
setValueStrategy(ValueStrategies.SEQUENCE);
}
/**
* Metadata for the value sequence.
*/
public SequenceMetaData getValueSequenceMetaData() {
if (_seqMeta == null && getValueSequenceName() != null)
_seqMeta = getRepository().getSequenceMetaData(_owner,
getValueSequenceName(), true);
return _seqMeta;
}
/**
* The strategy to use when updating the field.
*/
public int getUpdateStrategy() {
if (isVersion())
return UpdateStrategies.RESTRICT;
if (_upStrategy == -1)
_upStrategy = UpdateStrategies.NONE;
return _upStrategy;
}
/**
* Set the update strategy.
*/
public void setUpdateStrategy(int strategy) {
_upStrategy = strategy;
}
/**
* Whether this field is backed by a large result set.
*/
public boolean isLRS() {
return _lrs == Boolean.TRUE && _manage == MANAGE_PERSISTENT;
}
/**
* Whether this field is backed by a large result set.
*/
public void setLRS(boolean lrs) {
_lrs = (lrs) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Whether this field is backed by a stream.
*
* @since 1.1.0
*/
public boolean isStream() {
return _stream == Boolean.TRUE && _manage == MANAGE_PERSISTENT;
}
/**
* Whether this field is backed by a stream.
*
* @since 1.1.0
*/
public void setStream(boolean stream) {
_stream = (stream) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Whether this field uses intermediate data when loading/storing
* information through a {@link OpenJPAStateManager}. Defaults to true.
*
* @see OpenJPAStateManager#setIntermediate(int,Object)
*/
public boolean usesIntermediate() {
return _intermediate;
}
/**
* Whether this field uses intermediate data when loading/storing
* information through a {@link OpenJPAStateManager}. Defaults to true.
*
* @see OpenJPAStateManager#setIntermediate(int,Object)
*/
public void setUsesIntermediate(boolean intermediate) {
_intermediate = intermediate;
_owner.clearExtraFieldDataTable();
}
/**
* Whether this field uses impl data in conjunction with standard
* field data when acting on a {@link OpenJPAStateManager}.
* Defaults to {@link Boolean#TRUE} (non-cachable impl data).
*
* @return {@link Boolean#FALSE} if this field does not use impl data,
* {@link Boolean#TRUE} if this field uses non-cachable impl
* data, or <code>null</code> if this field uses impl data that
* should be cached across instances
* @see OpenJPAStateManager#setImplData(int,Object)
*/
public Boolean usesImplData() {
return _implData;
}
/**
* Whether this field uses impl data in conjunction with standard
* field data when acting on a {@link OpenJPAStateManager}.
*
* @see OpenJPAStateManager#setImplData(int,Object)
* @see #usesImplData
*/
public void setUsesImplData(Boolean implData) {
_implData = implData;
_owner.clearExtraFieldDataTable();
}
/**
* The orderings for this field to be applied on load, or empty array.
*/
public Order[] getOrders() {
if (_orders == null) {
if (_orderDec == null)
_orders = getRepository().EMPTY_ORDERS;
else {
String[] decs = StringUtil.split(_orderDec, ",", 0);
Order[] orders = getRepository().newOrderArray(decs.length);
int spc;
boolean asc;
for (int i = 0; i < decs.length; i++) {
decs[i] = decs[i].trim();
spc = decs[i].indexOf(' ');
if (spc == -1)
asc = true;
else {
asc = decs[i].substring(spc + 1).trim().
toLowerCase().startsWith("asc");
decs[i] = decs[i].substring(0, spc);
}
orders[i] = getRepository().newOrder(this, decs[i], asc);
//set "isUsedInOrderBy" to the field
ClassMetaData elemCls = getElement()
.getDeclaredTypeMetaData();
if (elemCls != null) {
FieldMetaData fmd = elemCls.getDeclaredField(decs[i]);
if (fmd != null)
fmd.setUsedInOrderBy(true);
}
}
_orders = orders;
}
}
return _orders;
}
/**
* The orderings for this field to be applied on load.
*/
public void setOrders(Order[] orders) {
_orderDec = null;
_orders = orders;
}
/**
* String declaring the orderings for this field to be applied on load,
* or null. The string is of the form:<br />
* <code>orderable[ asc|desc][, ...]</code><br />
* The orderable <code>#element</code> is used to denote the value of
* the field's elements.
*/
public String getOrderDeclaration() {
if (_orderDec == null && _orders != null) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < _orders.length; i++) {
if (i > 0)
buf.append(", ");
buf.append(_orders[i].getName()).append(" ");
buf.append((_orders[i].isAscending()) ? "asc" : "desc");
}
_orderDec = buf.toString();
}
return _orderDec;
}
/**
* String declaring the orderings for this field to be applied on load,
* or null. The string is of the form:<br />
* <code>orderable[ asc|desc][, ...]</code><br />
* The orderable <code>#element</code> is used to denote the value of
* the field's elements.
*/
public void setOrderDeclaration(String dec) {
_orderDec = StringUtil.trimToNull(dec);
_orders = null;
}
/**
* Order this field value when it is loaded.
*/
public Object order(Object val) {
if (val == null)
return null;
Order[] orders = getOrders();
if (orders.length == 0)
return val;
// create a comparator for the elements of the value
Comparator<?> comp;
if (orders.length == 1)
comp = orders[0].getComparator();
else {
List<Comparator<?>> comps = null;
Comparator<?> curComp;
for (int i = 0; i < orders.length; i++) {
curComp = orders[i].getComparator();
if (curComp != null) {
if (comps == null)
comps = new ArrayList<>(orders.length);
if (i != comps.size())
throw new MetaDataException(_loc.get
("mixed-inmem-ordering", this));
comps.add(curComp);
}
}
if (comps == null)
comp = null;
else
comp = new ComparatorChain(comps);
}
if (comp == null)
return val;
// sort
switch (getTypeCode()) {
case JavaTypes.ARRAY:
List l = JavaTypes.toList(val, _elem.getType(), true);
Collections.sort(l, (Comparator<? super Order>) comp);
return JavaTypes.toArray(l, _elem.getType());
case JavaTypes.COLLECTION:
if (val instanceof List)
Collections.sort((List) val, (Comparator<? super Order>) comp);
return val;
default:
throw new MetaDataException(_loc.get("cant-order", this));
}
}
/**
* Whether the field is externalized.
*/
public boolean isExternalized() {
return getExternalizerMethod() != null
|| getExternalValueMap() != null;
}
/**
* Convert the given field value to its external value through the
* provided externalizer, or return the value as-is if no externalizer.
*/
public Object getExternalValue(Object val, StoreContext ctx) {
Map extValues = getExternalValueMap();
if (extValues != null) {
Object foundVal = extValues.get(val);
if (foundVal == null) {
throw new UserException(_loc.get("bad-externalized-value",
new Object[] { val, extValues.keySet(), this }))
.setFatal(true).setFailedObject(val);
} else {
return foundVal;
}
}
Method externalizer = getExternalizerMethod();
if (externalizer == null)
return val;
// special case for queries: allow the given value to pass through
// as-is if it is already in externalized form
if (val != null && getType().isInstance(val)
&& (!getDeclaredType().isInstance(val)
|| getDeclaredType() == Object.class))
return val;
try {
// either invoke the static toExternal(val[, ctx]) method, or the
// non-static val.toExternal([ctx]) method
if (Modifier.isStatic(externalizer.getModifiers())) {
if (externalizer.getParameterTypes().length == 1)
return externalizer.invoke(null, new Object[]{ val });
return externalizer.invoke(null, new Object[]{ val, ctx });
}
if (val == null)
return null;
if (externalizer.getParameterTypes().length == 0)
return externalizer.invoke(val, (Object[]) null);
return externalizer.invoke(val, new Object[]{ ctx });
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new MetaDataException(_loc.get("externalizer-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
}
}
/**
* Return the result of passing the given external value through the
* factory to get the field value. If no factory is present,
* the given value is returned as-is.
*/
public Object getFieldValue(Object val, StoreContext ctx) {
Map fieldValues = getFieldValueMap();
if (fieldValues != null)
return fieldValues.get(val);
Member factory = getFactoryMethod();
if (factory == null)
return val;
try {
if (val == null && getNullValue() == NULL_DEFAULT)
return AccessController.doPrivileged(
J2DoPrivHelper.newInstanceAction(getDeclaredType()));
// invoke either the constructor for the field type,
// or the static type.toField(val[, ctx]) method
if (factory instanceof Constructor) {
if (val == null)
return null;
return ((Constructor) factory).newInstance
(new Object[]{ val });
}
Method meth = (Method) factory;
if (meth.getParameterTypes().length == 1)
return meth.invoke(null, new Object[]{ val });
return meth.invoke(null, new Object[]{ val, ctx });
} catch (Exception e) {
// unwrap cause
if (e instanceof InvocationTargetException) {
Throwable t = ((InvocationTargetException) e).
getTargetException();
if (t instanceof Error)
throw (Error) t;
e = (Exception) t;
// allow null values to cause NPEs and illegal arg exceptions
// without error
if (val == null && (e instanceof NullPointerException
|| e instanceof IllegalArgumentException))
return null;
}
if (e instanceof OpenJPAException)
throw (OpenJPAException) e;
if (e instanceof PrivilegedActionException)
e = ((PrivilegedActionException) e).getException();
throw new MetaDataException(_loc.get("factory-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
}
}
/**
* The name of this field's externalizer, or null if none.
*/
public String getExternalizer() {
return _extName;
}
/**
* The name of this field's externalizer, or null if none.
*/
public void setExternalizer(String externalizer) {
_extName = externalizer;
_extMethod = DEFAULT_METHOD;
}
/**
* The name of this field's factory, or null if none.
*/
public String getFactory() {
return _factName;
}
/**
* The name of this field's factory, or null if none.
*/
public void setFactory(String factory) {
_factName = factory;
_factMethod = DEFAULT_METHOD;
}
/**
* Properties string mapping field values to external values.
*/
public String getExternalValues() {
return _extString;
}
/**
* Properties string mapping field values to external values.
*/
public void setExternalValues(String values) {
_extString = values;
_extValues = null;
}
/**
* Return the mapping of field values to external values.
*/
public Map getExternalValueMap() {
parseExternalValues();
return _extValues;
}
/**
* Return the mapping of external values to field values.
*/
public Map getFieldValueMap() {
parseExternalValues();
return _fieldValues;
}
/**
* Parse external values into maps.
*/
private void parseExternalValues() {
if (_extValues != Collections.EMPTY_MAP
&& _fieldValues != Collections.EMPTY_MAP)
return;
if (_extString == null) {
_extValues = null;
_fieldValues = null;
return;
}
// parse string into options; this takes care of proper trimming etc
Options values = Configurations.parseProperties(_extString);
if (values.isEmpty())
throw new MetaDataException(_loc.get("no-external-values", this,
_extString));
Map extValues = new HashMap((int) (values.size() * 1.33 + 1));
Map fieldValues = new HashMap((int) (values.size() * 1.33 + 1));
Map.Entry entry;
Object extValue, fieldValue;
for (Map.Entry<Object, Object> objectObjectEntry : values.entrySet()) {
entry = (Map.Entry) objectObjectEntry;
fieldValue = transform((String) entry.getKey(),
getDeclaredTypeCode());
extValue = transform((String) entry.getValue(), getTypeCode());
extValues.put(fieldValue, extValue);
fieldValues.put(extValue, fieldValue);
}
_extValues = extValues;
_fieldValues = fieldValues;
}
/**
* Return the string value converted to the given type code. The string
* must be non-null and trimmed.
*/
private Object transform(String val, int typeCode) {
if ("null".equals(val))
return null;
switch (typeCode) {
case JavaTypes.BOOLEAN:
case JavaTypes.BOOLEAN_OBJ:
return Boolean.valueOf(val);
case JavaTypes.BYTE:
case JavaTypes.BYTE_OBJ:
return Byte.valueOf(val);
case JavaTypes.INT:
case JavaTypes.INT_OBJ:
return Integer.valueOf(val);
case JavaTypes.LONG:
case JavaTypes.LONG_OBJ:
return Long.valueOf(val);
case JavaTypes.SHORT:
case JavaTypes.SHORT_OBJ:
return Short.valueOf(val);
case JavaTypes.DOUBLE:
case JavaTypes.DOUBLE_OBJ:
return Double.valueOf(val);
case JavaTypes.FLOAT:
case JavaTypes.FLOAT_OBJ:
return Float.valueOf(val);
case JavaTypes.CHAR:
case JavaTypes.CHAR_OBJ:
return val.charAt(0);
case JavaTypes.STRING:
return val;
case JavaTypes.ENUM:
return Enum.valueOf((Class<? extends Enum>)getDeclaredType(), val);
}
throw new MetaDataException(_loc.get("bad-external-type", this));
}
/**
* The externalizer method.
*/
public Method getExternalizerMethod() {
if (_manage != MANAGE_PERSISTENT)
return null;
if (_extMethod == DEFAULT_METHOD) {
if (_extName != null) {
_extMethod = findMethod(_extName);
if (_extMethod == null)
throw new MetaDataException(_loc.get("bad-externalizer",
this, _extName));
} else
_extMethod = null;
}
return _extMethod;
}
/**
* The factory method or constructor.
*/
public Member getFactoryMethod() {
if (_manage != MANAGE_PERSISTENT)
return null;
if (_factMethod == DEFAULT_METHOD) {
if (getExternalizerMethod() == null)
_factMethod = null;
else {
try {
if (_factName == null)
_factMethod = getDeclaredType().getConstructor
(new Class[]{ getType() });
else
_factMethod = findMethodByNameAndType(_factName, getType());
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
}
if (!(_factMethod instanceof Constructor)
&& !(_factMethod instanceof Method))
throw new MetaDataException(_loc.get("bad-factory", this));
}
}
return _factMethod;
}
/**
* Find the method for the specified name. Possible forms are:
* <ul>
* <li>toExternalString</li>
* <li>MyFactoryClass.toExternalString</li>
* <li>com.company.MyFactoryClass.toExternalString</li>
* </ul>
*
* @param method the name of the method to locate
* @return the method for invocation
*/
private Method findMethod(String method) {
return findMethodByNameAndType(method, null);
}
/**
* Find the method for the specified name and type. Possible forms are:
* <ul>
* <li>toExternalString</li>
* <li>MyFactoryClass.toExternalString</li>
* <li>com.company.MyFactoryClass.toExternalString</li>
* </ul>
*
* @param method the name of the method to locate
* @param type The type of the parameter which will pass the object from the database.
* @return the method for invocation
*/
private Method findMethodByNameAndType(String method, Class<?> type) {
if (StringUtil.isEmpty(method))
return null;
// get class name and get package name divide on the last '.', so the
// names don't apply in this case, but the methods do what we want
String methodName = ClassUtil.getClassName(method);
String clsName = ClassUtil.getPackageName(method);
Class<?> cls = null;
Class<?> owner = _owner.getDescribedType();
if (clsName.length() == 0)
cls = getDeclaredType();
else if (clsName.equals(owner.getName())
|| clsName.equals(ClassUtil.getClassName(owner)))
cls = owner;
else
cls = JavaTypes.classForName(clsName, this);
// find the named method
Method[] methods = cls.getMethods();
Class<?>[] params;
for (Method value : methods) {
if (value.getName().equals(methodName)) {
params = value.getParameterTypes();
// static factory methods require one argument or one argument
// plus a context; non-static methods require zero arguments or
// just a context
if (Modifier.isStatic(value.getModifiers())
&& (params.length == 1 || (params.length == 2
&& isStoreContextParameter(params[1]))))
if (type == null) {
return value;
}
else if (isConvertibleToByMethodInvocationConversion(type, params[0])) {
return value;
}
if (!Modifier.isStatic(value.getModifiers())
&& (params.length == 0 || (params.length == 1
&& isStoreContextParameter(params[0]))))
return value;
}
}
return null;
}
/**
* Test if the {@code sourceType} is convertible to the {@code destType}.
* Convertible follows the rules in Java Language Specification, 3rd Ed, s5.3 and means that:
* <ul>
* <li>{@code sourceType} and {@code destType} are the same type (identity conversion)</li>
* <li>For primitive types: that {@code sourceType} can be widened into {@code destType}
* or that {@code sourceType} can be boxed into a class assignable to {@code destType}.</li>
* <li>For non-primitive types: that the {@code sourceType} can be unboxed into a primitive
* that is the same as, or can be widened into,
* {@code destType} or {@code sourceType} can be assigned to {@code destType}.</li>
*
* @return True iff the conditions above are true.
*/
private boolean isConvertibleToByMethodInvocationConversion(Class<?> sourceType, Class<?> destType) {
// Note that class.isAssignableFrom is a widening reference conversion test
if (sourceType.isPrimitive()) {
return isConvertibleToByIdentityPrimitiveConversion(sourceType, destType)
|| isConvertibleToByWideningPrimitive(sourceType, destType)
|| destType.isAssignableFrom(box(sourceType));
} else {
// Note that unbox will return null if the sourceType is not a wrapper.
// The identity primitive conversion and widening primitive handle this.
return isConvertibleToByIdentityPrimitiveConversion(unbox(sourceType), destType)
|| isConvertibleToByWideningPrimitive(unbox(sourceType), destType)
|| destType.isAssignableFrom(sourceType);
}
}
/**
* @return The results of unboxing {@code sourceType} following Java Language Specification, 3rd Ed, s5.1.8
*/
private Class<?> unbox(Class<?> sourceType) {
if (sourceType == java.lang.Boolean.class) {
return java.lang.Boolean.TYPE;
} else if (sourceType == java.lang.Byte.class) {
return java.lang.Byte.TYPE;
} else if (sourceType == java.lang.Short.class) {
return java.lang.Short.TYPE;
} else if (sourceType == java.lang.Character.class) {
return java.lang.Character.TYPE;
} else if (sourceType == java.lang.Integer.class) {
return java.lang.Integer.TYPE;
} else if (sourceType == java.lang.Long.class) {
return java.lang.Long.TYPE;
} else if (sourceType == java.lang.Float.class) {
return java.lang.Float.TYPE;
} else if (sourceType == java.lang.Double.class) {
return java.lang.Double.TYPE;
} else {
return null;
}
}
/**
* @return The results of unboxing {@code sourceType} following Java Language Specification, 3rd Ed, s5.1.7
*/
private Class<?> box(Class<?> sourceType) {
if (sourceType.isPrimitive()) {
if (sourceType == java.lang.Boolean.TYPE) {
return java.lang.Boolean.class;
} else if (sourceType == java.lang.Byte.TYPE) {
return java.lang.Byte.class;
} else if (sourceType == java.lang.Short.TYPE) {
return java.lang.Short.class;
} else if (sourceType == java.lang.Character.TYPE) {
return java.lang.Character.class;
} else if (sourceType == java.lang.Integer.TYPE) {
return java.lang.Integer.class;
} else if (sourceType == java.lang.Long.TYPE) {
return java.lang.Long.class;
} else if (sourceType == java.lang.Float.TYPE) {
return java.lang.Float.class;
} else if (sourceType == java.lang.Double.TYPE) {
return java.lang.Double.class;
}
return null; // Should never be reached because all primitives are accounted for above.
} else {
throw new IllegalArgumentException("Cannot box a type that is not a primitive.");
}
}
/**
* @return true if {@code sourceType} can be converted by a widening primitive conversion
* following Java Language Specification, 3rd Ed, s5.1.2
*/
private boolean isConvertibleToByWideningPrimitive(Class<?> sourceType, Class<?> destType) {
// Widening conversion following Java Language Specification, s5.1.2.
if (sourceType == java.lang.Byte.TYPE) {
return destType == java.lang.Short.TYPE ||
destType == java.lang.Integer.TYPE ||
destType == java.lang.Long.TYPE ||
destType == java.lang.Float.TYPE ||
destType == java.lang.Double.TYPE;
} else if (sourceType == java.lang.Short.TYPE) {
return destType == java.lang.Integer.TYPE ||
destType == java.lang.Long.TYPE ||
destType == java.lang.Float.TYPE ||
destType == java.lang.Double.TYPE;
} else if (sourceType == java.lang.Character.TYPE) {
return destType == java.lang.Integer.TYPE ||
destType == java.lang.Long.TYPE ||
destType == java.lang.Float.TYPE ||
destType == java.lang.Double.TYPE;
} else if (sourceType == java.lang.Integer.TYPE) {
return destType == java.lang.Long.TYPE ||
destType == java.lang.Float.TYPE ||
destType == java.lang.Double.TYPE;
} else if (sourceType == java.lang.Long.TYPE) {
return destType == java.lang.Float.TYPE ||
destType == java.lang.Double.TYPE;
} else if (sourceType == java.lang.Float.TYPE) {
return destType == java.lang.Double.TYPE;
}
return false;
}
/**
* Returns true iff the sourceType is a primitive that can be converted to
* destType using an identity conversion - i.e. sourceType and destType are the same type.
* following Java Language Specification, 3rd Ed, s5.1.1
*/
private boolean isConvertibleToByIdentityPrimitiveConversion(Class<?> sourceType, Class<?> destType) {
return sourceType != null && sourceType.isPrimitive() && sourceType == destType;
}
/**
* Return true if the given type is a store context type; we can't
* use the standard <code>isAssignableFrom</code> because of classloader
* oddness.
*/
private static boolean isStoreContextParameter(Class<?> type) {
return StoreContext.class.getName().equals(type.getName());
}
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (!(other instanceof FieldMetaData))
return false;
return getFullName(true).equals(((FieldMetaData) other).
getFullName(true));
}
@Override
public int hashCode() {
return getFullName(true).hashCode();
}
public int compareTo(Object other) {
if (other == null)
return 1;
return getFullName(true).compareTo(((FieldMetaData) other).
getFullName(true));
}
@Override
public String toString() {
return getFullName(true);
}
////////////////////////
// Resolve and validate
////////////////////////
/**
* Resolve mode for this field.
*/
@Override
public int getResolve() {
return _resMode;
}
/**
* Resolve mode for this field.
*/
@Override
public void setResolve(int mode) {
_resMode = mode;
}
/**
* Resolve mode for this field.
*/
@Override
public void setResolve(int mode, boolean on) {
if (mode == MODE_NONE)
_resMode = mode;
else if (on)
_resMode |= mode;
else
_resMode &= ~mode;
}
/**
* Resolve and validate metadata. Return true if already resolved.
*/
@Override
public boolean resolve(int mode) {
if ((_resMode & mode) == mode)
return true;
int cur = _resMode;
_resMode |= mode;
Log log = getRepository().getLog();
if (log.isTraceEnabled())
log.trace(_loc.get("resolve-field", _owner + "@"
+ System.identityHashCode(_owner) + "." + _name));
// we only perform actions for metadata mode
if ((mode & MODE_META) == 0 || (cur & MODE_META) != 0)
return false;
Method externalizer = getExternalizerMethod();
if (externalizer != null)
setType(externalizer.getReturnType());
// only pass on metadata resolve mode so that metadata is always
// resolved before any other resolve modes our subclasses pass along
_val.resolve(MODE_META);
_key.resolve(MODE_META);
_elem.resolve(MODE_META);
MetaDataRepository repos = getRepository();
int validate = repos.getValidate();
if ((validate & MetaDataRepository.VALIDATE_META) != 0
&& (!ImplHelper.isManagedType(repos.getConfiguration(),
_owner.getDescribedType())
|| (validate & MetaDataRepository.VALIDATE_UNENHANCED) == 0)) {
validateLRS();
if ((validate & MetaDataRepository.VALIDATE_RUNTIME) == 0)
validateSupportedType();
validateValue();
validateExtensionKeys();
}
return false;
}
/**
* Validate that this field can be used for LRS.
*/
private void validateLRS() {
if (!isLRS())
return;
// can't use lrs for arrays
if (getTypeCode() == JavaTypes.ARRAY)
throw new MetaDataException(_loc.get("bad-lrs-array", this));
// can't use lrs for externalized vals
if (getExternalizerMethod() != null)
throw new MetaDataException(_loc.get("bad-lrs-extern", this));
// can't use lrs for concrete types
if (getType() != Collection.class && getType() != Map.class
&& getType() != Set.class)
throw new MetaDataException(_loc.get("bad-lrs-concrete", this));
}
/**
* Validate that this field is supported by the runtime.
*/
private void validateSupportedType() {
// log warnings about things we don't handle
OpenJPAConfiguration conf = getRepository().getConfiguration();
Collection<String> opts = conf.supportedOptions();
Log log = conf.getLog(OpenJPAConfiguration.LOG_METADATA);
switch (getTypeCode()) {
case JavaTypes.PC:
if (isEmbedded() && !opts.contains(
OpenJPAConfiguration.OPTION_EMBEDDED_RELATION)) {
setEmbedded(false);
if (log.isWarnEnabled())
log.warn(_loc.get("cant-embed", this));
} else
if (isEmbedded() && getDeclaredTypeCode() != JavaTypes.PC) {
setEmbedded(false);
if (log.isWarnEnabled())
log.warn(_loc.get("cant-embed-extern", this));
}
break;
case JavaTypes.COLLECTION:
if (!opts.contains(OpenJPAConfiguration.OPTION_TYPE_COLLECTION))
throw new UnsupportedException(
_loc.get("type-not-supported",
"Collection", this));
if (_elem.isEmbeddedPC() && !opts.contains(
OpenJPAConfiguration.OPTION_EMBEDDED_COLLECTION_RELATION)){
_elem.setEmbedded(false);
if (log.isWarnEnabled())
log.warn(_loc.get("cant-embed-element", this));
}
break;
case JavaTypes.ARRAY:
if (!opts.contains(OpenJPAConfiguration.OPTION_TYPE_ARRAY))
throw new UnsupportedException(
_loc.get("type-not-supported",
"Array", this));
if (_elem.isEmbeddedPC() && !opts.contains(
OpenJPAConfiguration.OPTION_EMBEDDED_COLLECTION_RELATION)) {
_elem.setEmbedded(false);
if (log.isWarnEnabled())
log.warn(_loc.get("cant-embed-element", this));
}
break;
case JavaTypes.MAP:
if (!opts.contains(OpenJPAConfiguration.OPTION_TYPE_MAP))
throw new UnsupportedException(
_loc.get("type-not-supported",
"Map", this));
if (_elem.isEmbeddedPC() && !opts.contains(
OpenJPAConfiguration.OPTION_EMBEDDED_MAP_RELATION)) {
_elem.setEmbedded(false);
if (log.isWarnEnabled())
log.warn(_loc.get("cant-embed-element", this));
}
if (_key.isEmbeddedPC() && !opts.contains(
OpenJPAConfiguration.OPTION_EMBEDDED_MAP_RELATION)) {
_key.setEmbedded(false);
if (log.isWarnEnabled())
log.warn(_loc.get("cant-embed-key", this));
}
break;
}
}
/**
* Validate our value strategy.
*/
private void validateValue() {
if (getExternalizerMethod() != null && getExternalValueMap() != null)
throw new MetaDataException(_loc.get("extern-externvalues", this));
if (getValueStrategy() == ValueStrategies.SEQUENCE
&& getValueSequenceName() == null)
throw new MetaDataException(_loc.get("no-seq-name", this));
ValueStrategies.assertSupported(getValueStrategy(), this,
"value strategy");
}
/**
* Copy state from the given field to this one. Do not copy mapping
* information.
*/
public void copy(FieldMetaData field) {
super.copy(field);
_intermediate = field.usesIntermediate();
_implData = field.usesImplData();
// copy field-level info; use get methods to force resolution of
// lazy data
_proxyClass = field.getProxyType();
_initializer = field.getInitializer();
_transient = field.isTransient();
_nullValue = field.getNullValue();
_manage = field.getManagement();
_explicit = field.isExplicit();
_extName = field.getExternalizer();
_extMethod = DEFAULT_METHOD;
_factName = field.getFactory();
_factMethod = DEFAULT_METHOD;
_extString = field.getExternalValues();
_extValues = Collections.EMPTY_MAP;
_fieldValues = Collections.EMPTY_MAP;
_primKey = field.isPrimaryKey();
_backingMember = field._backingMember;
_enumField = field._enumField;
_lobField = field._lobField;
_serializableField = field._serializableField;
_generated = field._generated;
_mappedByIdValue = field._mappedByIdValue;
_isElementCollection = field._isElementCollection;
_access = field._access;
_orderDec = field._orderDec;
_useSchemaElement = field._useSchemaElement;
// embedded fields can't be versions
if (_owner.getEmbeddingMetaData() == null && _version == null)
_version = (field.isVersion()) ? Boolean.TRUE : Boolean.FALSE;
// only copy this data if not already set explicitly in this instance
if (_dfg == 0) {
_dfg = (field.isInDefaultFetchGroup()) ? DFG_TRUE : DFG_FALSE;
if (field.isDefaultFetchGroupExplicit())
_dfg |= DFG_EXPLICIT;
}
if (_fgSet == null && field._fgSet != null)
_fgSet = new HashSet(field._fgSet);
if (_lfg == null)
_lfg = field.getLoadFetchGroup();
if (_lrs == null)
_lrs = (field.isLRS()) ? Boolean.TRUE : Boolean.FALSE;
if (_valStrategy == -1)
_valStrategy = field.getValueStrategy();
if (_upStrategy == -1)
_upStrategy = field.getUpdateStrategy();
if (ClassMetaData.DEFAULT_STRING.equals(_seqName)) {
_seqName = field.getValueSequenceName();
_seqMeta = null;
}
if (ClassMetaData.DEFAULT_STRING.equals(_inverse))
_inverse = field.getInverse();
// copy value metadata
_val.copy(field);
_key.copy(field.getKey());
_elem.copy(field.getElement());
}
@Override
protected void addExtensionKeys(Collection exts) {
getRepository().getMetaDataFactory().addFieldExtensionKeys(exts);
}
///////////////
// Commentable
///////////////
@Override
public String[] getComments() {
return (_comments == null) ? EMPTY_COMMENTS : _comments;
}
@Override
public void setComments(String[] comments) {
_comments = comments;
}
////////////////////////////////
// ValueMetaData implementation
////////////////////////////////
@Override
public FieldMetaData getFieldMetaData() {
return this;
}
@Override
public Class getType() {
return _val.getType();
}
@Override
public void setType(Class type) {
_val.setType(type);
if (type.isArray())
_elem.setType(type.getComponentType());
else if (type == Properties.class) {
_key.setType(String.class);
_elem.setType(String.class);
}
}
@Override
public int getTypeCode() {
return _val.getTypeCode();
}
@Override
public void setTypeCode(int code) {
_val.setTypeCode(code);
}
@Override
public boolean isTypePC() {
return _val.isTypePC();
}
@Override
public ClassMetaData getTypeMetaData() {
return _val.getTypeMetaData();
}
@Override
public Class getDeclaredType() {
return _val.getDeclaredType();
}
@Override
public void setDeclaredType(Class type) {
_val.setDeclaredType(type);
if (type.isArray())
_elem.setDeclaredType(type.getComponentType());
else if (type == Properties.class) {
_key.setDeclaredType(String.class);
_elem.setDeclaredType(String.class);
}
}
@Override
public int getDeclaredTypeCode() {
return _val.getDeclaredTypeCode();
}
@Override
public void setDeclaredTypeCode(int type) {
_val.setDeclaredTypeCode(type);
}
@Override
public boolean isDeclaredTypePC() {
return _val.isDeclaredTypePC();
}
@Override
public ClassMetaData getDeclaredTypeMetaData() {
return _val.getDeclaredTypeMetaData();
}
@Override
public boolean isEmbedded() {
return _val.isEmbedded();
}
@Override
public void setEmbedded(boolean embedded) {
_val.setEmbedded(embedded);
}
@Override
public boolean isEmbeddedPC() {
return _val.isEmbeddedPC();
}
@Override
public ClassMetaData getEmbeddedMetaData() {
return _val.getEmbeddedMetaData();
}
@Override
public ClassMetaData addEmbeddedMetaData(int access) {
return _val.addEmbeddedMetaData(access);
}
@Override
public ClassMetaData addEmbeddedMetaData() {
return _val.addEmbeddedMetaData();
}
@Override
public int getCascadeDelete() {
return _val.getCascadeDelete();
}
@Override
public void setCascadeDelete(int delete) {
_val.setCascadeDelete(delete);
}
@Override
public int getCascadePersist() {
return _val.getCascadePersist();
}
@Override
public void setCascadePersist(int persist) {
_val.setCascadePersist(persist);
}
@Override
public void setCascadePersist(int cascade, boolean checkPUDefault) {
_val.setCascadePersist(cascade, checkPUDefault);
}
@Override
public int getCascadeAttach() {
return _val.getCascadeAttach();
}
@Override
public void setCascadeAttach(int attach) {
_val.setCascadeAttach(attach);
}
@Override
public int getCascadeDetach() {
return _val.getCascadeDetach();
}
@Override
public void setCascadeDetach(int detach) {
_val.setCascadeDetach(detach);
}
@Override
public int getCascadeRefresh() {
return _val.getCascadeRefresh();
}
@Override
public void setCascadeRefresh(int refresh) {
_val.setCascadeRefresh(refresh);
}
@Override
public boolean isSerialized() {
return _val.isSerialized();
}
@Override
public void setSerialized(boolean serialized) {
_val.setSerialized(serialized);
}
@Override
public String getValueMappedBy() {
return _val.getValueMappedBy();
}
@Override
public void setValueMappedBy(String mapped) {
_val.setValueMappedBy(mapped);
}
@Override
public FieldMetaData getValueMappedByMetaData () {
return _val.getValueMappedByMetaData ();
}
@Override
public Class<?> getTypeOverride () {
return _val.getTypeOverride ();
}
@Override
public void setTypeOverride(Class type) {
_val.setTypeOverride (type);
}
@Override
public void copy (ValueMetaData vmd) {
_val.copy (vmd);
}
/**
* Check if this field is used by other field as "order by" value.
*
* @since 1.1.0
*/
public boolean isUsedInOrderBy() {
return _usedInOrderBy;
}
/**
* Whether this field is used by other field as "order by" value .
*
* @since 1.1.0
*/
public void setUsedInOrderBy(boolean isUsed) {
_usedInOrderBy = isUsed;
}
/**
* Serializable wrapper around a {@link Method} or {@link Field}. For
* space considerations, this does not support {@link Constructor}s.
*/
public static class MemberProvider
implements Externalizable {
private transient Member _member;
public MemberProvider() {
// for externalization
}
MemberProvider(Member member) {
if (member instanceof Constructor)
throw new IllegalArgumentException();
_member = member;
}
public Member getMember() {
return _member;
}
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
boolean isField = in.readBoolean();
Class<?> cls = (Class<?>) in.readObject();
String memberName = (String) in.readObject();
try {
if (isField)
_member = AccessController.doPrivileged(
J2DoPrivHelper.getDeclaredFieldAction(
cls, memberName));
else {
Class<?>[] parameterTypes = (Class[]) in.readObject();
_member = AccessController.doPrivileged(
J2DoPrivHelper.getDeclaredMethodAction(
cls, memberName, parameterTypes));
}
} catch (SecurityException e) {
IOException ioe = new IOException(e.getMessage(), e);
throw ioe;
} catch (PrivilegedActionException pae) {
IOException ioe = new IOException(
pae.getException().getMessage(), pae);
throw ioe;
}
}
@Override
public void writeExternal(ObjectOutput out)
throws IOException {
boolean isField = _member instanceof Field;
out.writeBoolean(isField);
out.writeObject(_member.getDeclaringClass());
out.writeObject(_member.getName());
if (!isField)
out.writeObject(((Method) _member).getParameterTypes());
}
}
public boolean isValueGenerated() {
return _generated;
}
public void setValueGenerated(boolean generated) {
this._generated = generated;
}
public boolean isElementCollection() {
return _isElementCollection;
}
public void setElementCollection(boolean isElementCollection) {
this._isElementCollection = isElementCollection;
}
public String getMappedByIdValue() {
return _mappedByIdValue;
}
public void setMappedByIdValue(String mappedByIdValue) {
this._mappedByIdValue = mappedByIdValue;
}
public boolean isMappedById() {
return (_mappedByIdValue != null);
}
/**
* Gets the access type used by this field. If no access type is set for
* this field then return the access type used by the declaring class.
*/
public int getAccessType() {
if (AccessCode.isUnknown(_access)) {
int fCode = AccessCode.toFieldCode(getDeclaringMetaData()
.getAccessType());
return fCode;
}
return _access;
}
/**
* Sets access type of this field. The access code is verified for validity
* as well as against the access style used by the declaring class.
*/
public void setAccessType(int fCode) {
ClassMetaData owner = getDeclaringMetaData();
owner.mergeFieldAccess(this, fCode);
_access = fCode;
}
public int getAssociationType() {
return _associationType;
}
public void setAssociationType(int type) {
_associationType = type;
}
public boolean isPersistentCollection() {
return _persistentCollection;
}
public void setPersistentCollection(boolean persistentCollection) {
_persistentCollection = persistentCollection;
}
private Class<?> _relationType = Unknown.class;
public Class<?> getRelationType() {
if (_relationType == Unknown.class) {
if (isDeclaredTypePC())
_relationType = getDeclaredType();
else if (getElement().isDeclaredTypePC())
_relationType = getElement().getDeclaredType();
else if (getKey().isDeclaredTypePC())
_relationType = getKey().getDeclaredType();
else
_relationType = null;
}
return _relationType;
}
private class Unknown{}
public boolean isDelayCapable() {
if (_delayCapable != null) {
return _delayCapable;
}
if (getTypeCode() != JavaTypes.COLLECTION || isLRS()) {
_delayCapable = Boolean.FALSE;
return _delayCapable;
} else {
// Verify the proxy manager is configured to handle delay loading
ProxyManager pm = getRepository().getConfiguration().getProxyManagerInstance();
if (pm != null) {
_delayCapable = pm.getDelayCollectionLoading();
} else {
_delayCapable = Boolean.FALSE;
}
}
return _delayCapable;
}
public void setDelayCapable(Boolean delayCapable) {
_delayCapable = delayCapable;
}
/**
* Whether to include schema name in generated files
*/
public boolean getUseSchemaElement() {
return _useSchemaElement;
}
/**
* Whether to include schema name in generated files
*/
public void setUseSchemaElement(boolean _useSchemaElement) {
this._useSchemaElement = _useSchemaElement;
}
public String getSetterName() {
String setterName = "set" + StringUtil.capitalize(_name);
if (_name.length() > 1 && Character.isLowerCase(_name.charAt(0)) && Character.isUpperCase(_name.charAt(1))) {
// We have the special case where the first char is lower, and the
// following char is capital. We need to support using the
// setaStart() (correct) and setAStart() (incorrect -- old way)
Class<?> type = getDeclaringMetaData().getDescribedType();
setterName = "set" + _name;
try {
type.getDeclaredMethod(setterName, getType());
return setterName;
} catch (Exception e) {
}
setterName = "set" + StringUtil.capitalize(_name);
try {
type.getDeclaredMethod(setterName, getType());
} catch (Exception e) {
}
}
return setterName;
}
}