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