/**
* $Id: ClassProperty.java 35 2008-11-05 17:19:05Z azeckoski $
* $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/ClassProperty.java $
* ClassFields.java - genericdao - May 5, 2008 2:16:35 PM - azeckoski
**************************************************************************
* Copyright (c) 2008 Aaron Zeckoski
* Licensed under the Apache License, Version 2
*
* A copy of the Apache License, Version 2 has been included in this
* distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Aaron Zeckoski (azeckoski@gmail.com) (aaronz@vt.edu) (aaron@caret.cam.ac.uk)
*/
package org.azeckoski.reflectutils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.azeckoski.reflectutils.annotations.ReflectTransient;
import org.azeckoski.reflectutils.map.ArrayOrderedMap;
import org.azeckoski.reflectutils.map.OrderedMap;
/**
* Simple class for holding the values we care about for a single field,
* if it uses getters and setters then they must be like this:<br/>
* If the fieldName is "thing" then: <br/>
* <b>getter:</b> Object getThing() and
* <b>setter:</b> void setThing(Object value)
* <br/>
* Warning: Note that the values in this object may be garbage collected at any time,
* do not store this object, it is meant for immediate short term use only
*/
public class ClassProperty {
private final String fieldName;
private Class<?> type;
private Field field;
private Method getter;
private Method setter;
protected boolean transientField = false;
protected boolean staticField = false;
protected boolean finalField = false;
protected boolean publicField = false;
protected int fieldModifiers = -1; // un-set value is -1
protected boolean mapped = false;
protected boolean indexed = false;
protected boolean arrayed = false;
private OrderedMap<Class<? extends Annotation>, Annotation> propertyAnnotations; // contains all annotations on this property
protected void addAnnotation(Annotation annotation) {
if (annotation != null) {
if (propertyAnnotations == null) {
propertyAnnotations = new ArrayOrderedMap<Class<? extends Annotation>, Annotation>();
}
Class<? extends Annotation> c = annotation.annotationType();
if (! propertyAnnotations.containsKey(c)) {
// only add an annotation in the first time it is encountered
propertyAnnotations.put(c, annotation);
}
// note that we compare the simple name to avoid issues with cross classloader types
if (ReflectTransient.class.getSimpleName().equals(c.getSimpleName())) {
transientField = true; // pretend to be transient
}
}
}
protected Collection<Annotation> getAnnotationsCollection() {
Collection<Annotation> ans = null;
if (propertyAnnotations == null
|| propertyAnnotations.isEmpty()) {
ans = new ArrayList<Annotation>();
} else {
ans = propertyAnnotations.values();
}
return ans;
}
/**
* @param annotationType the annotation type to look for on this field
* @return the annotation of this type for this field OR null if none found
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
T a = null;
if (propertyAnnotations != null) {
a = (T) propertyAnnotations.get(annotationType);
}
return a;
}
/**
* @return the annotations present on the property
*/
public Annotation[] getAnnotations() {
Collection<Annotation> c = getAnnotationsCollection();
return c.toArray(new Annotation[c.size()]);
}
public ClassProperty(String fieldName) {
this.fieldName = fieldName;
}
public ClassProperty(String fieldName, Method getter, Method setter) {
this.fieldName = fieldName;
if (getter != null) {
setGetter(getter);
}
if (setter != null) {
setSetter(setter);
}
}
public ClassProperty(String fieldName, Field field) {
this.fieldName = fieldName;
setField(field);
}
/**
* @return true if this field is complete (value can be set and retrieved via public methods/fields)
*/
public boolean isComplete() {
return (isPublicGettable() && isPublicSettable());
}
/**
* @return true if this field is partial (value can be set OR retrieved but not both)
*/
public boolean isPartial() {
return (! isComplete());
}
/**
* @return true if there is a getter and setter method available for this field (may not be public)
*/
public boolean isProperty() {
return (getGetter() != null && getSetter() != null);
}
/**
* @return true if the associated field object is set
*/
public boolean isField() {
return (getField() != null);
}
/**
* @return true if the associated field object is set and public
*/
public boolean isPublicField() {
return (publicField && isField());
}
/**
* @return true if the associated field object is static
*/
public boolean isStatic() {
return staticField;
}
/**
* @return true if this field value can be retrieved
*/
public boolean isGettable() {
return (isField() || getGetter() != null);
}
/**
* @return true if this field value can be retrieved and is public (either the getter or the field)
*/
public boolean isPublicGettable() {
return (isPublicField() || getGetter() != null);
}
/**
* @return true if this field value can be set
*/
public boolean isSettable() {
boolean settable = false;
if (! finalField) { // no setting finals
if (isField() || getSetter() != null) {
settable = true;
}
}
return settable;
}
/**
* @return true if this field value can be set and is public (either the setter or the field)
*/
public boolean isPublicSettable() {
boolean settable = false;
if (! finalField) { // no setting finals
if (isPublicField() || getSetter() != null) {
settable = true;
}
}
return settable;
}
/**
* @return the name of this field
*/
public String getFieldName() {
return fieldName;
}
public Method getGetter() {
return getter;
}
protected void setGetter(Method getter) {
this.getter = getter;
makeType(); // getter type always wins
}
public Method getSetter() {
return setter;
}
protected void setSetter(Method setter) {
this.setter = setter;
if (type == null) {
makeType();
}
}
public Field getField() {
return field;
}
protected void setField(Field field) {
this.field = field;
// only the field knows the true fieldModifiers
setModifiers(field);
if (type == null) {
makeType();
}
}
/**
* @return the type of this field
*/
public Class<?> getType() {
return type;
}
protected void setType(Class<?> type) {
this.type = type;
}
/**
* @return true if this field is final
*/
public boolean isFinal() {
return finalField;
}
/**
* @return true if this field is transient
*/
public boolean isTransient() {
return transientField;
}
/**
* @return true if this field is a map of some kind
*/
public boolean isMapped() {
return mapped;
}
/**
* @return true if this field is indexed (typically a list)
*/
public boolean isIndexed() {
return indexed;
}
/**
* @return true if this field is an array
*/
public boolean isArray() {
return arrayed;
}
/**
* @return the fieldModifiers code for this field (see {@link Modifier} to decode this)
*/
public int getModifiers() {
return fieldModifiers;
}
/**
* Sets the fieldModifiers based on this field,
* will not reset them once they are set except to clear them if a null field is set
*/
protected void setModifiers(Field field) {
if (field == null) {
fieldModifiers = -1;
transientField = false;
finalField = false;
publicField = false;
staticField = false;
} else {
if (fieldModifiers < 0) {
fieldModifiers = field.getModifiers();
transientField = Modifier.isTransient(fieldModifiers);
finalField = Modifier.isFinal(fieldModifiers);
publicField = Modifier.isPublic(fieldModifiers);
staticField = Modifier.isStatic(fieldModifiers);
}
}
}
private void makeType() {
if (getGetter() != null) {
Method m = getGetter();
setType( m.getReturnType() );
} else if (getSetter() != null) {
Method m = getSetter();
Class<?>[] params = m.getParameterTypes();
if (params != null) {
if (params.length == 1) {
setType( params[0] ); // normal setter
} else if (params.length == 2) {
setType( params[1] ); // indexed/mapped setter
}
}
} else {
Field f = getField();
setType( f.getType() );
}
indexed = false;
arrayed = false;
mapped = false;
Class<?> type = getType();
if (type != null) {
if (type.isArray()) {
indexed = true;
arrayed = true;
}
if (Map.class.isAssignableFrom(type)) {
mapped = true;
}
if (List.class.isAssignableFrom(type)) {
indexed = true;
}
}
}
@Override
public String toString() {
return fieldName + "(" + (type == null ? "" : type.getSimpleName()) + ")["
+(field == null?"-":"F")+(getter == null?"-":"G")+(setter == null?"-":"S")+":"
+(mapped?"M":"-")+(indexed?"I":"-")+(arrayed?"A":"-")+":"
+(finalField?"F":"-")+(transientField?"T":"-")+(publicField?"P":"-")+(staticField?"S":"-")+"]";
}
/**
* Slightly extended version with the extra indexed properties,
* indexed getters and setters must be setup like so:<br/>
* If the fieldName is "thing" then: <br/>
* <b>getter:</b> Object getThing(int index) and
* <b>setter:</b> void setThing(int index, Object value)
*/
public static class IndexedProperty extends ClassProperty {
private Method indexGetter;
private Method indexSetter;
public IndexedProperty(String fieldName) {
super(fieldName);
indexed = true;
}
public IndexedProperty(String fieldName, Method getter, Method setter) {
super(fieldName, getter, setter);
indexed = true;
}
public IndexedProperty(String fieldName, Method getter, Method setter, Method indexGetter,
Method indexSetter) {
super(fieldName, getter, setter);
setIndexGetter(indexGetter);
setIndexSetter(indexSetter);
indexed = true;
}
public Method getIndexGetter() {
return indexGetter;
}
protected void setIndexGetter(Method indexGetter) {
this.indexGetter = indexGetter;
}
public Method getIndexSetter() {
return indexSetter;
}
protected void setIndexSetter(Method indexSetter) {
this.indexSetter = indexSetter;
}
@Override
public boolean isGettable() {
boolean gettable = false;
if (getIndexGetter() != null || super.isGettable()) {
gettable = true;
}
return gettable;
}
@Override
public boolean isSettable() {
boolean settable = false;
if (getIndexSetter() != null || super.isSettable()) {
settable = true;
}
return settable;
}
}
/**
* Slightly extended version with the extra mapped properties,
* mapped getters and setters must be setup like so:<br/>
* If the fieldName is "thing" then: <br/>
* <b>getter:</b> Object getThing(String key) and
* <b>setter:</b> void setThing(String key, Object value)
* <br/>
* The keys MUST be Strings
*/
public static class MappedProperty extends ClassProperty {
private Method mapGetter;
private Method mapSetter;
public MappedProperty(String fieldName) {
super(fieldName);
mapped = true;
}
public MappedProperty(String fieldName, Method getter, Method setter) {
super(fieldName, getter, setter);
mapped = true;
}
public MappedProperty(String fieldName, Method getter, Method setter, Method mapGetter,
Method mapSetter) {
super(fieldName, getter, setter);
setMapGetter(mapGetter);
setMapSetter(mapSetter);
mapped = true;
}
public Method getMapGetter() {
return mapGetter;
}
protected void setMapGetter(Method mapGetter) {
this.mapGetter = mapGetter;
}
public Method getMapSetter() {
return mapSetter;
}
protected void setMapSetter(Method mapSetter) {
this.mapSetter = mapSetter;
}
@Override
public boolean isGettable() {
boolean gettable = false;
if (getMapGetter() != null || super.isGettable()) {
gettable = true;
}
return gettable;
}
@Override
public boolean isSettable() {
boolean settable = false;
if (getMapSetter() != null || super.isSettable()) {
settable = true;
}
return settable;
}
}
}