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