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