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