Home | History | Annotate | Download | only in util
      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 class 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