1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.util; 17 18 import java.lang.reflect.Field; 19 import java.lang.reflect.InvocationTargetException; 20 import java.lang.reflect.Method; 21 22 /** 23 * Internal class to automatically generate a Property for a given class/name pair, given the 24 * specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)} 25 */ 26 @android.ravenwood.annotation.RavenwoodKeepWholeClass 27 class ReflectiveProperty<T, V> extends Property<T, V> { 28 29 private static final String PREFIX_GET = "get"; 30 private static final String PREFIX_IS = "is"; 31 private static final String PREFIX_SET = "set"; 32 private Method mSetter; 33 private Method mGetter; 34 private Field mField; 35 36 /** 37 * For given property name 'name', look for getName/isName method or 'name' field. 38 * Also look for setName method (optional - could be readonly). Failing method getters and 39 * field results in throwing NoSuchPropertyException. 40 * 41 * @param propertyHolder The class on which the methods or field are found 42 * @param name The name of the property, where this name is capitalized and appended to 43 * "get" and "is to search for the appropriate methods. If the get/is methods are not found, 44 * the constructor will search for a field with that exact name. 45 */ ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name)46 public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) { 47 // TODO: cache reflection info for each new class/name pair 48 super(valueType, name); 49 char firstLetter = Character.toUpperCase(name.charAt(0)); 50 String theRest = name.substring(1); 51 String capitalizedName = firstLetter + theRest; 52 String getterName = PREFIX_GET + capitalizedName; 53 try { 54 mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null); 55 } catch (NoSuchMethodException e) { 56 // getName() not available - try isName() instead 57 getterName = PREFIX_IS + capitalizedName; 58 try { 59 mGetter = propertyHolder.getMethod(getterName, (Class<?>[])null); 60 } catch (NoSuchMethodException e1) { 61 // Try public field instead 62 try { 63 mField = propertyHolder.getField(name); 64 Class fieldType = mField.getType(); 65 if (!typesMatch(valueType, fieldType)) { 66 throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " + 67 "does not match Property type (" + valueType + ")"); 68 } 69 return; 70 } catch (NoSuchFieldException e2) { 71 // no way to access property - throw appropriate exception 72 throw new NoSuchPropertyException("No accessor method or field found for" 73 + " property with name " + name); 74 } 75 } 76 } 77 Class getterType = mGetter.getReturnType(); 78 // Check to make sure our getter type matches our valueType 79 if (!typesMatch(valueType, getterType)) { 80 throw new NoSuchPropertyException("Underlying type (" + getterType + ") " + 81 "does not match Property type (" + valueType + ")"); 82 } 83 String setterName = PREFIX_SET + capitalizedName; 84 try { 85 mSetter = propertyHolder.getMethod(setterName, getterType); 86 } catch (NoSuchMethodException ignored) { 87 // Okay to not have a setter - just a readonly property 88 } 89 } 90 91 /** 92 * Utility method to check whether the type of the underlying field/method on the target 93 * object matches the type of the Property. The extra checks for primitive types are because 94 * generics will force the Property type to be a class, whereas the type of the underlying 95 * method/field will probably be a primitive type instead. Accept float as matching Float, 96 * etc. 97 */ typesMatch(Class<V> valueType, Class getterType)98 private boolean typesMatch(Class<V> valueType, Class getterType) { 99 if (getterType != valueType) { 100 if (getterType.isPrimitive()) { 101 return (getterType == float.class && valueType == Float.class) || 102 (getterType == int.class && valueType == Integer.class) || 103 (getterType == boolean.class && valueType == Boolean.class) || 104 (getterType == long.class && valueType == Long.class) || 105 (getterType == double.class && valueType == Double.class) || 106 (getterType == short.class && valueType == Short.class) || 107 (getterType == byte.class && valueType == Byte.class) || 108 (getterType == char.class && valueType == Character.class); 109 } 110 return false; 111 } 112 return true; 113 } 114 115 @Override set(T object, V value)116 public void set(T object, V value) { 117 if (mSetter != null) { 118 try { 119 mSetter.invoke(object, value); 120 } catch (IllegalAccessException e) { 121 throw new AssertionError(); 122 } catch (InvocationTargetException e) { 123 throw new RuntimeException(e.getCause()); 124 } 125 } else if (mField != null) { 126 try { 127 mField.set(object, value); 128 } catch (IllegalAccessException e) { 129 throw new AssertionError(); 130 } 131 } else { 132 throw new UnsupportedOperationException("Property " + getName() +" is read-only"); 133 } 134 } 135 136 @Override get(T object)137 public V get(T object) { 138 if (mGetter != null) { 139 try { 140 return (V) mGetter.invoke(object, (Object[])null); 141 } catch (IllegalAccessException e) { 142 throw new AssertionError(); 143 } catch (InvocationTargetException e) { 144 throw new RuntimeException(e.getCause()); 145 } 146 } else if (mField != null) { 147 try { 148 return (V) mField.get(object); 149 } catch (IllegalAccessException e) { 150 throw new AssertionError(); 151 } 152 } 153 // Should not get here: there should always be a non-null getter or field 154 throw new AssertionError(); 155 } 156 157 /** 158 * Returns false if there is no setter or public field underlying this Property. 159 */ 160 @Override isReadOnly()161 public boolean isReadOnly() { 162 return (mSetter == null && mField == null); 163 } 164 } 165