blob: 6832240f385baa3f57d5b3c6a9ab1c767a64d135 [file] [log] [blame]
Chet Haaseb39f0512011-05-24 14:36:40 -07001/*
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 {
Chet Haaseaccb54c2011-06-09 08:38:12 -0700131 throw new UnsupportedOperationException("Property " + getName() +" is read-only");
Chet Haaseb39f0512011-05-24 14:36:40 -0700132 }
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}