/*
Copyright 1996-2008 Ariba, Inc.
Licensed 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.
$Id: //ariba/platform/util/core/ariba/util/fieldvalue/FieldValue_Object.java#5 $
*/
package ariba.util.fieldvalue;
import ariba.util.core.Fmt;
import ariba.util.core.MultiKeyHashtable;
/**
The FieldValue_Object class extension is the default implementation of the
FieldValue interface. This default implementation is based
on java.lang.reflection primitives and will work for essentailly
all classes. Of course, certain subclasses may desire to alter
the default behavior and, as such, should subclass this class and
override the appropriate methods. For example, a Map
implementation may want to treat the fieldName as a key for get/put
rather than as the name of an instance variable. Some other subclass
may desire to mix and match the two approaches. In any case, these
special cases should be implemented in subclasses of FieldValue_Object.
*/
public class FieldValue_Object extends FieldValue
{
private static final FieldValueAccessor NotFoundAccessor = new NotFoundFieldValueAccessor();
protected final MultiKeyHashtable[] _accessorsHashtable = {
new MultiKeyHashtable(2, 8 , true),
new MultiKeyHashtable(2, 8, true),
};
/**
Creates and returns a new FieldValueAccessor (by default,
a ReflectionFieldValueAccessor) for the given target and fieldName.
No caching is done by this method. This method is designed to be
overridden by subclasses of FieldValue_Object which want to define
their own specialized accessors. Note that the target is passed
rather than its class so that accessors can be created at a finer
granularity than class. Certain meta-data driven classes require
this flexibility.
@param target the object for which the accessor will be created
@param fieldName the name of the field for which the accessor will be created
@param type the type of accessor will be created (either FieldValue.Setter
or FieldValue.Getter)
@return a new FieldValueAccessor (ReflectionFieldValueAccessor by default)
*/
public FieldValueAccessor createAccessor (Object target, String fieldName, int type)
{
FieldValueAccessor accessor = null;
if (fieldName.equals("this")) {
fieldName = "getThis";
}
if (type == Setter) {
accessor = FieldValueAccessorUtil.newReflectionSetter(target.getClass(), fieldName);
}
else {
accessor = FieldValueAccessorUtil.newReflectionGetter(target.getClass(), fieldName);
}
return accessor;
}
/**
Maintains a cache of FieldValueAccessor's for the instance by fieldName.
In general, all instances of a given class will share the same
FieldValueAccessor for a given fieldName. However, certain meta-data
driven classes may need to introspect upon the instance before returning
the proper accessor.
@param target the object for which the accessor will be looked up
@param fieldName the name of the field for the accessor
@param type the type of accessor will be created (either FieldValue.Setter
or FieldValue.Getter)
@return the cached FieldValueAccessor (ReflectionFieldValueAccessor by default)
*/
public FieldValueAccessor getAccessor (Object target, String fieldName, int type)
{
FieldValueAccessor accessor = null;
Class targetObjectClass = target.getClass();
MultiKeyHashtable accessorsHashtable = _accessorsHashtable[type];
synchronized (accessorsHashtable) {
fieldName = fieldName.intern();
accessor = (FieldValueAccessor)accessorsHashtable.get(targetObjectClass,
fieldName);
if (accessor == NotFoundAccessor) {
accessor = null;
}
else if (accessor == null) {
accessor = createAccessor(target, fieldName, type);
accessorsHashtable.put(targetObjectClass, fieldName,
(accessor != null) ? accessor : NotFoundAccessor);
}
}
return accessor;
}
/**
Sets the value on the reveiver using the fieldName indicated by
fieldPath -- only the first node of the fieldPath is considered if it is
a multi-node path.
@param target the object on which the value will be set for the field identiifed by fieldPath
@param fieldPath the fieldPath node (which contains a candidate accessor) to be
used to set the value on target.
@param value the value to set on the target
*/
public void setFieldValuePrimitive (Object target, FieldPath fieldPath, Object value)
{
FieldValueSetter setter = fieldPath._previousSetter;
boolean isAccessorApplicable = (target.getClass() == setter.forClass())
&& setter.isApplicable(target);
if (!isAccessorApplicable) {
setter = (FieldValueSetter)getAccessor(target, fieldPath._fieldName, Setter);
if (setter == null) {
String message = Fmt.S(
"Unable to locate setter method or " +
"field for: \"%s\" on target class: \"%s\"",
fieldPath._fieldName, target.getClass().getName());
throw new FieldValueException(message);
}
fieldPath._previousSetter = setter;
}
setter.setValue(target, value);
}
/**
Gets the value from the reveiver using the fieldName indicated by
fieldPath -- only the first node of the fieldPath is considered if
it is a multi-node path.
@param target the object from which to get the value of the field identified by fieldPath
@param fieldPath the fieldPath node which identifes the field to get.
@return the value obtained from the target using the fieldPath
*/
public Object getFieldValuePrimitive (Object target, FieldPath fieldPath)
{
FieldValueGetter getter = fieldPath._previousGetter;
boolean isAccessorApplicable = (target.getClass() == getter.forClass())
&& getter.isApplicable(target);
if (!isAccessorApplicable) {
getter = (FieldValueGetter)getAccessor(target, fieldPath._fieldName, Getter);
if (getter == null) {
String message = Fmt.S(
"Unable to locate getter method or " +
"field for: \"%s\" on target class: \"%s\"",
fieldPath._fieldName, target.getClass().getName());
throw new FieldValueException(message);
}
fieldPath._previousGetter = getter;
}
return getter.getValue(target);
}
/**
Recursively calls getFieldValuePrimitive() with the head of the fieldPath
list up to the last fieldPath node and then calls setFieldValuePrimitive().
Each time the recursion iterates, the receiver is the value of the
previous getFieldValuePrimitive().
@param target the object on which to start the recursion for setting
the value using the fieldPath
@param fieldPath the linked list of fieldPath nodes used to navigate from
target to the final object in the chain, upon which the value is set
@param value the value which is set on the final object in the chain
*/
public void setFieldValue (Object target,
FieldPath fieldPath, Object value)
{
FieldPath fieldPathCdr = fieldPath._nextFieldPath;
if (fieldPathCdr == null) {
setFieldValuePrimitive(target, fieldPath, value);
}
else {
Object nextTargetObject = getFieldValuePrimitive(target, fieldPath);
if (nextTargetObject != null) {
fieldPathCdr.setFieldValue(nextTargetObject, value);
}
}
}
/**
Recursively calls getFieldValuePrimitive() with the head of the fieldPath list.
Each time the recursion iterates, the receiver is the value of the previous
getFieldValuePrimitive().
@param target the first object from which to start the recursion for getting
the value identified by the fieldPath.
@param fieldPath the linked list of fieldPath nodes that identifes the value to get
@return the value obtained from the last object in the chain
*/
public Object getFieldValue (Object target, FieldPath fieldPath)
{
Object value = getFieldValuePrimitive(target, fieldPath);
FieldPath fieldPathCdr = fieldPath._nextFieldPath;
if (fieldPathCdr != null && value != null) {
value = fieldPathCdr.getFieldValue(value);
}
return value;
}
public void populateFieldInfo (Class targetClass, FieldInfo.Collection collection)
{
Class superCls = targetClass.getSuperclass();
if (superCls != null) populateFieldInfo(superCls, collection);
if (collection.includeFields()) {
FieldValueAccessorUtil.popuplateFromFields(targetClass, collection);
}
FieldValueAccessorUtil.popuplateFromMethods(targetClass, collection);
}
/*
private boolean fieldEqualsExtensionMethod (Field field, Method extensionMethod)
{
String fieldName = field.getName();
String methodName = extensionMethod.getName();
return FieldValueAccessorUtil.matchForSetter(fieldName, methodName) ||
FieldValueAccessorUtil.matchForGetter(fieldName, methodName);
}
protected void checkForCollisions (Class otherClass)
{
Method[] extensionMethods = getClass().getMethods();
Field[] otherFields = otherClass.getDeclaredFields();
for (int index = extensionMethods.length - 1; index > -1; index--) {
Method currentExtensionMethod = extensionMethods[index];
for (int otherIndex = otherFields.length - 1; otherIndex > - 1; otherIndex--) {
Field otherField = otherFields[otherIndex];
if (fieldEqualsExtensionMethod(otherField, currentExtensionMethod)) {
String message = Fmt.S(
"ClassExtension/Field collision between: " +
"%s/%s and %s/%s.",
getClass(), currentExtensionMethod, otherClass, otherField);
throw new RuntimeException(message);
}
}
}
super.checkForCollisions(otherClass);
}
*/
}
/**
A dummy/marker class used to maintain a negative cache for accessors which are not
found.
*/
class NotFoundFieldValueAccessor extends BaseAccessor
{
public NotFoundFieldValueAccessor ()
{
super(Object.class, "NotFound");
}
public Object getValue (Object target){return null;}
public void setValue (Object target, Object value){}
}