165 lines
6.9 KiB
Java
165 lines
6.9 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2011 The Android Open Source Project
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
package android.util;
|
||
|
|
||
|
import java.lang.reflect.Field;
|
||
|
import java.lang.reflect.InvocationTargetException;
|
||
|
import java.lang.reflect.Method;
|
||
|
|
||
|
/**
|
||
|
* Internal class to automatically generate a Property for a given class/name pair, given the
|
||
|
* specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)}
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
class ReflectiveProperty<T, V> extends Property<T, V> {
|
||
|
|
||
|
private static final String PREFIX_GET = "get";
|
||
|
private static final String PREFIX_IS = "is";
|
||
|
private static final String PREFIX_SET = "set";
|
||
|
private Method mSetter;
|
||
|
private Method mGetter;
|
||
|
private Field mField;
|
||
|
|
||
|
/**
|
||
|
* For given property name 'name', look for getName/isName method or 'name' field.
|
||
|
* Also look for setName method (optional - could be readonly). Failing method getters and
|
||
|
* field results in throwing NoSuchPropertyException.
|
||
|
*
|
||
|
* @param propertyHolder The class on which the methods or field are found
|
||
|
* @param name The name of the property, where this name is capitalized and appended to
|
||
|
* "get" and "is to search for the appropriate methods. If the get/is methods are not found,
|
||
|
* the constructor will search for a field with that exact name.
|
||
|
*/
|
||
|
public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) {
|
||
|
// TODO: cache reflection info for each new class/name pair
|
||
|
super(valueType, name);
|
||
|
char firstLetter = Character.toUpperCase(name.charAt(0));
|
||
|
String theRest = name.substring(1);
|
||
|
String capitalizedName = firstLetter + theRest;
|
||
|
String getterName = PREFIX_GET + capitalizedName;
|
||
|
try {
|
||
|
mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null);
|
||
|
} catch (NoSuchMethodException e) {
|
||
|
// getName() not available - try isName() instead
|
||
|
getterName = PREFIX_IS + capitalizedName;
|
||
|
try {
|
||
|
mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null);
|
||
|
} catch (NoSuchMethodException e1) {
|
||
|
// Try public field instead
|
||
|
try {
|
||
|
mField = propertyHolder.getField(name);
|
||
|
Class fieldType = mField.getType();
|
||
|
if (!typesMatch(valueType, fieldType)) {
|
||
|
throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " +
|
||
|
"does not match Property type (" + valueType + ")");
|
||
|
}
|
||
|
return;
|
||
|
} catch (NoSuchFieldException e2) {
|
||
|
// no way to access property - throw appropriate exception
|
||
|
throw new NoSuchPropertyException("No accessor method or field found for"
|
||
|
+ " property with name " + name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Class getterType = mGetter.getReturnType();
|
||
|
// Check to make sure our getter type matches our valueType
|
||
|
if (!typesMatch(valueType, getterType)) {
|
||
|
throw new NoSuchPropertyException("Underlying type (" + getterType + ") " +
|
||
|
"does not match Property type (" + valueType + ")");
|
||
|
}
|
||
|
String setterName = PREFIX_SET + capitalizedName;
|
||
|
try {
|
||
|
mSetter = propertyHolder.getMethod(setterName, getterType);
|
||
|
} catch (NoSuchMethodException ignored) {
|
||
|
// Okay to not have a setter - just a readonly property
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Utility method to check whether the type of the underlying field/method on the target
|
||
|
* object matches the type of the Property. The extra checks for primitive types are because
|
||
|
* generics will force the Property type to be a class, whereas the type of the underlying
|
||
|
* method/field will probably be a primitive type instead. Accept float as matching Float,
|
||
|
* etc.
|
||
|
*/
|
||
|
private boolean typesMatch(Class<V> valueType, Class getterType) {
|
||
|
if (getterType != valueType) {
|
||
|
if (getterType.isPrimitive()) {
|
||
|
return (getterType == float.class && valueType == Float.class) ||
|
||
|
(getterType == int.class && valueType == Integer.class) ||
|
||
|
(getterType == boolean.class && valueType == Boolean.class) ||
|
||
|
(getterType == long.class && valueType == Long.class) ||
|
||
|
(getterType == double.class && valueType == Double.class) ||
|
||
|
(getterType == short.class && valueType == Short.class) ||
|
||
|
(getterType == byte.class && valueType == Byte.class) ||
|
||
|
(getterType == char.class && valueType == Character.class);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void set(T object, V value) {
|
||
|
if (mSetter != null) {
|
||
|
try {
|
||
|
mSetter.invoke(object, value);
|
||
|
} catch (IllegalAccessException e) {
|
||
|
throw new AssertionError();
|
||
|
} catch (InvocationTargetException e) {
|
||
|
throw new RuntimeException(e.getCause());
|
||
|
}
|
||
|
} else if (mField != null) {
|
||
|
try {
|
||
|
mField.set(object, value);
|
||
|
} catch (IllegalAccessException e) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
} else {
|
||
|
throw new UnsupportedOperationException("Property " + getName() +" is read-only");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public V get(T object) {
|
||
|
if (mGetter != null) {
|
||
|
try {
|
||
|
return (V) mGetter.invoke(object, (Object[])null);
|
||
|
} catch (IllegalAccessException e) {
|
||
|
throw new AssertionError();
|
||
|
} catch (InvocationTargetException e) {
|
||
|
throw new RuntimeException(e.getCause());
|
||
|
}
|
||
|
} else if (mField != null) {
|
||
|
try {
|
||
|
return (V) mField.get(object);
|
||
|
} catch (IllegalAccessException e) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
// Should not get here: there should always be a non-null getter or field
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns false if there is no setter or public field underlying this Property.
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean isReadOnly() {
|
||
|
return (mSetter == null && mField == null);
|
||
|
}
|
||
|
}
|