Project: cow
/*
 * Copyright (C) 2010 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package com.actionbarsherlock.internal.nineoldandroids.animation; 
 
//import android.util.FloatProperty; 
//import android.util.IntProperty; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.util.HashMap; 
import java.util.concurrent.locks.ReentrantReadWriteLock; 
 
import android.util.Log; 
 
/**
 * This class holds information about a property and the values that that property 
 * should take on during an animation. PropertyValuesHolder objects can be used to create 
 * animations with ValueAnimator or ObjectAnimator that operate on several different properties 
 * in parallel. 
 */
 
public class PropertyValuesHolder implements Cloneable { 
 
    /**
     * The name of the property associated with the values. This need not be a real property, 
     * unless this object is being used with ObjectAnimator. But this is the name by which 
     * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator. 
     */
 
    String mPropertyName; 
 
    /**
     * @hide 
     */
 
    //protected Property mProperty; 
 
    /**
     * The setter function, if needed. ObjectAnimator hands off this functionality to 
     * PropertyValuesHolder, since it holds all of the per-property information. This 
     * property is automatically 
     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. 
     */
 
    Method mSetter = null
 
    /**
     * The getter function, if needed. ObjectAnimator hands off this functionality to 
     * PropertyValuesHolder, since it holds all of the per-property information. This 
     * property is automatically 
     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator. 
     * The getter is only derived and used if one of the values is null. 
     */
 
    private Method mGetter = null
 
    /**
     * The type of values supplied. This information is used both in deriving the setter/getter 
     * functions and in deriving the type of TypeEvaluator. 
     */
 
    Class mValueType; 
 
    /**
     * The set of keyframes (time/value pairs) that define this animation. 
     */
 
    KeyframeSet mKeyframeSet = null
 
 
    // type evaluators for the primitive types handled by this implementation 
    private static final TypeEvaluator sIntEvaluator = new IntEvaluator(); 
    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator(); 
 
    // We try several different types when searching for appropriate setter/getter functions. 
    // The caller may have supplied values in a type that does not match the setter/getter 
    // functions (such as the integers 0 and 1 to represent floating point values for alpha). 
    // Also, the use of generics in constructors means that we end up with the Object versions 
    // of primitive types (Float vs. float). But most likely, the setter/getter functions 
    // will take primitive types instead. 
    // So we supply an ordered array of other types to try before giving up. 
    private static Class[] FLOAT_VARIANTS = {float.class, Float.classdouble.classint.class
            Double.class, Integer.class}; 
    private static Class[] INTEGER_VARIANTS = {int.class, Integer.classfloat.classdouble.class
            Float.class, Double.class}; 
    private static Class[] DOUBLE_VARIANTS = {double.class, Double.classfloat.classint.class
            Float.class, Integer.class}; 
 
    // These maps hold all property entries for a particular class. This map 
    // is used to speed up property/setter/getter lookups for a given class/property 
    // combination. No need to use reflection on the combination more than once. 
    private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap = 
            new HashMap<Class, HashMap<String, Method>>(); 
    private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap = 
            new HashMap<Class, HashMap<String, Method>>(); 
 
    // This lock is used to ensure that only one thread is accessing the property maps 
    // at a time. 
    final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock(); 
 
    // Used to pass single value to varargs parameter in setter invocation 
    final Object[] mTmpValueArray = new Object[1]; 
 
    /**
     * The type evaluator used to calculate the animated values. This evaluator is determined 
     * automatically based on the type of the start/end objects passed into the constructor, 
     * but the system only knows about the primitive types int and float. Any other 
     * type will need to set the evaluator to a custom evaluator for that type. 
     */
 
    private TypeEvaluator mEvaluator; 
 
    /**
     * The value most recently calculated by calculateValue(). This is set during 
     * that function and might be retrieved later either by ValueAnimator.animatedValue() or 
     * by the property-setting logic in ObjectAnimator.animatedValue(). 
     */
 
    private Object mAnimatedValue; 
 
    /**
     * Internal utility constructor, used by the factory methods to set the property name. 
     * @param propertyName The name of the property for this holder. 
     */
 
    private PropertyValuesHolder(String propertyName) { 
        mPropertyName = propertyName; 
    } 
 
    /**
     * Internal utility constructor, used by the factory methods to set the property. 
     * @param property The property for this holder. 
     */
 
    //private PropertyValuesHolder(Property property) { 
    //    mProperty = property; 
    //    if (property != null) { 
    //        mPropertyName = property.getName(); 
    //    } 
    //} 
 
    /**
     * Constructs and returns a PropertyValuesHolder with a given property name and 
     * set of int values. 
     * @param propertyName The name of the property being animated. 
     * @param values The values that the named property will animate between. 
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object. 
     */
 
    public static PropertyValuesHolder ofInt(String propertyName, int... values) { 
        return new IntPropertyValuesHolder(propertyName, values); 
    } 
 
    /**
     * Constructs and returns a PropertyValuesHolder with a given property and 
     * set of int values. 
     * @param property The property being animated. Should not be null. 
     * @param values The values that the property will animate between. 
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object. 
     */
 
    //public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) { 
    //    return new IntPropertyValuesHolder(property, values); 
    //} 
 
    /**
     * Constructs and returns a PropertyValuesHolder with a given property name and 
     * set of float values. 
     * @param propertyName The name of the property being animated. 
     * @param values The values that the named property will animate between. 
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object. 
     */
 
    public static PropertyValuesHolder ofFloat(String propertyName, float... values) { 
        return new FloatPropertyValuesHolder(propertyName, values); 
    } 
 
    /**
     * Constructs and returns a PropertyValuesHolder with a given property and 
     * set of float values. 
     * @param property The property being animated. Should not be null. 
     * @param values The values that the property will animate between. 
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object. 
     */
 
    //public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) { 
    //    return new FloatPropertyValuesHolder(property, values); 
    //} 
 
    /**
     * Constructs and returns a PropertyValuesHolder with a given property name and 
     * set of Object values. This variant also takes a TypeEvaluator because the system 
     * cannot automatically interpolate between objects of unknown type. 
     * 
     * @param propertyName The name of the property being animated. 
     * @param evaluator A TypeEvaluator that will be called on each animation frame to 
     * provide the necessary interpolation between the Object values to derive the animated 
     * value. 
     * @param values The values that the named property will animate between. 
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object. 
     */
 
    public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator, 
            Object... values) { 
        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); 
        pvh.setObjectValues(values); 
        pvh.setEvaluator(evaluator); 
        return pvh; 
    } 
 
    /**
     * Constructs and returns a PropertyValuesHolder with a given property and 
     * set of Object values. This variant also takes a TypeEvaluator because the system 
     * cannot automatically interpolate between objects of unknown type. 
     * 
     * @param property The property being animated. Should not be null. 
     * @param evaluator A TypeEvaluator that will be called on each animation frame to 
     * provide the necessary interpolation between the Object values to derive the animated 
     * value. 
     * @param values The values that the property will animate between. 
     * @return PropertyValuesHolder The constructed PropertyValuesHolder object. 
     */
 
    //public static <V> PropertyValuesHolder ofObject(Property property, 
    //        TypeEvaluator<V> evaluator, V... values) { 
    //    PropertyValuesHolder pvh = new PropertyValuesHolder(property); 
    //    pvh.setObjectValues(values); 
    //    pvh.setEvaluator(evaluator); 
    //    return pvh; 
    //} 
 
    /**
     * Constructs and returns a PropertyValuesHolder object with the specified property name and set 
     * of values. These values can be of any type, but the type should be consistent so that 
     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches 
     * the common type. 
     * <p>If there is only one value, it is assumed to be the end value of an animation, 
     * and an initial value will be derived, if possible, by calling a getter function 
     * on the object. Also, if any value is null, the value will be filled in when the animation 
     * starts in the same way. This mechanism of automatically getting null values only works 
     * if the PropertyValuesHolder object is used in conjunction 
     * {@link ObjectAnimator}, and with a getter function 
     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has 
     * no way of determining what the value should be. 
     * @param propertyName The name of the property associated with this set of values. This 
     * can be the actual property name to be used when using a ObjectAnimator object, or 
     * just a name used to get animated values, such as if this object is used with an 
     * ValueAnimator object. 
     * @param values The set of values to animate between. 
     */
 
    public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) { 
        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); 
        if (keyframeSet instanceof IntKeyframeSet) { 
            return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet); 
        } else if (keyframeSet instanceof FloatKeyframeSet) { 
            return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet); 
        } 
        else { 
            PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName); 
            pvh.mKeyframeSet = keyframeSet; 
            pvh.mValueType = ((Keyframe)values[0]).getType(); 
            return pvh; 
        } 
    } 
 
    /**
     * Constructs and returns a PropertyValuesHolder object with the specified property and set 
     * of values. These values can be of any type, but the type should be consistent so that 
     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches 
     * the common type. 
     * <p>If there is only one value, it is assumed to be the end value of an animation, 
     * and an initial value will be derived, if possible, by calling the property's 
     * {@link android.util.Property#get(Object)} function. 
     * Also, if any value is null, the value will be filled in when the animation 
     * starts in the same way. This mechanism of automatically getting null values only works 
     * if the PropertyValuesHolder object is used in conjunction with 
     * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has 
     * no way of determining what the value should be. 
     * @param property The property associated with this set of values. Should not be null. 
     * @param values The set of values to animate between. 
     */
 
    //public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) { 
    //    KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values); 
    //    if (keyframeSet instanceof IntKeyframeSet) { 
    //        return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet); 
    //    } else if (keyframeSet instanceof FloatKeyframeSet) { 
    //        return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet); 
    //    } 
    //    else { 
    //        PropertyValuesHolder pvh = new PropertyValuesHolder(property); 
    //        pvh.mKeyframeSet = keyframeSet; 
    //        pvh.mValueType = ((Keyframe)values[0]).getType(); 
    //        return pvh; 
    //    } 
    //} 
 
    /**
     * Set the animated values for this object to this set of ints. 
     * If there is only one value, it is assumed to be the end value of an animation, 
     * and an initial value will be derived, if possible, by calling a getter function 
     * on the object. Also, if any value is null, the value will be filled in when the animation 
     * starts in the same way. This mechanism of automatically getting null values only works 
     * if the PropertyValuesHolder object is used in conjunction 
     * {@link ObjectAnimator}, and with a getter function 
     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has 
     * no way of determining what the value should be. 
     * 
     * @param values One or more values that the animation will animate between. 
     */
 
    public void setIntValues(int... values) { 
        mValueType = int.class
        mKeyframeSet = KeyframeSet.ofInt(values); 
    } 
 
    /**
     * Set the animated values for this object to this set of floats. 
     * If there is only one value, it is assumed to be the end value of an animation, 
     * and an initial value will be derived, if possible, by calling a getter function 
     * on the object. Also, if any value is null, the value will be filled in when the animation 
     * starts in the same way. This mechanism of automatically getting null values only works 
     * if the PropertyValuesHolder object is used in conjunction 
     * {@link ObjectAnimator}, and with a getter function 
     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has 
     * no way of determining what the value should be. 
     * 
     * @param values One or more values that the animation will animate between. 
     */
 
    public void setFloatValues(float... values) { 
        mValueType = float.class
        mKeyframeSet = KeyframeSet.ofFloat(values); 
    } 
 
    /**
     * Set the animated values for this object to this set of Keyframes. 
     * 
     * @param values One or more values that the animation will animate between. 
     */
 
    public void setKeyframes(Keyframe... values) { 
        int numKeyframes = values.length; 
        Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)]; 
        mValueType = ((Keyframe)values[0]).getType(); 
        for (int i = 0; i < numKeyframes; ++i) { 
            keyframes[i] = (Keyframe)values[i]; 
        } 
        mKeyframeSet = new KeyframeSet(keyframes); 
    } 
 
    /**
     * Set the animated values for this object to this set of Objects. 
     * If there is only one value, it is assumed to be the end value of an animation, 
     * and an initial value will be derived, if possible, by calling a getter function 
     * on the object. Also, if any value is null, the value will be filled in when the animation 
     * starts in the same way. This mechanism of automatically getting null values only works 
     * if the PropertyValuesHolder object is used in conjunction 
     * {@link ObjectAnimator}, and with a getter function 
     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has 
     * no way of determining what the value should be. 
     * 
     * @param values One or more values that the animation will animate between. 
     */
 
    public void setObjectValues(Object... values) { 
        mValueType = values[0].getClass(); 
        mKeyframeSet = KeyframeSet.ofObject(values); 
    } 
 
    /**
     * Determine the setter or getter function using the JavaBeans convention of setFoo or 
     * getFoo for a property named 'foo'. This function figures out what the name of the 
     * function should be and uses reflection to find the Method with that name on the 
     * target object. 
     * 
     * @param targetClass The class to search for the method 
     * @param prefix "set" or "get", depending on whether we need a setter or getter. 
     * @param valueType The type of the parameter (in the case of a setter). This type 
     * is derived from the values set on this PropertyValuesHolder. This type is used as 
     * a first guess at the parameter type, but we check for methods with several different 
     * types to avoid problems with slight mis-matches between supplied values and actual 
     * value types used on the setter. 
     * @return Method the method associated with mPropertyName. 
     */
 
    private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) { 
        // TODO: faster implementation... 
        Method returnVal = null
        String methodName = getMethodName(prefix, mPropertyName); 
        Class args[] = null
        if (valueType == null) { 
            try { 
                returnVal = targetClass.getMethod(methodName, args); 
            } catch (NoSuchMethodException e) { 
                Log.e("PropertyValuesHolder", targetClass.getSimpleName() + " - " + 
                        "Couldn't find no-arg method for property " + mPropertyName + ": " + e); 
            } 
        } else { 
            args = new Class[1]; 
            Class typeVariants[]; 
            if (mValueType.equals(Float.class)) { 
                typeVariants = FLOAT_VARIANTS; 
            } else if (mValueType.equals(Integer.class)) { 
                typeVariants = INTEGER_VARIANTS; 
            } else if (mValueType.equals(Double.class)) { 
                typeVariants = DOUBLE_VARIANTS; 
            } else { 
                typeVariants = new Class[1]; 
                typeVariants[0] = mValueType; 
            } 
            for (Class typeVariant : typeVariants) { 
                args[0] = typeVariant; 
                try { 
                    returnVal = targetClass.getMethod(methodName, args); 
                    // change the value type to suit 
                    mValueType = typeVariant; 
                    return returnVal; 
                } catch (NoSuchMethodException e) { 
                    // Swallow the error and keep trying other variants 
                } 
            } 
            // If we got here, then no appropriate function was found 
            Log.e("PropertyValuesHolder"
                    "Couldn't find " + prefix + "ter property " + mPropertyName + 
                            " for " + targetClass.getSimpleName() + 
                            " with value type "+ mValueType); 
        } 
 
        return returnVal; 
    } 
 
 
    /**
     * Returns the setter or getter requested. This utility function checks whether the 
     * requested method exists in the propertyMapMap cache. If not, it calls another 
     * utility function to request the Method from the targetClass directly. 
     * @param targetClass The Class on which the requested method should exist. 
     * @param propertyMapMap The cache of setters/getters derived so far. 
     * @param prefix "set" or "get", for the setter or getter. 
     * @param valueType The type of parameter passed into the method (null for getter). 
     * @return Method the method associated with mPropertyName. 
     */
 
    private Method setupSetterOrGetter(Class targetClass, 
            HashMap<Class, HashMap<String, Method>> propertyMapMap, 
            String prefix, Class valueType) { 
        Method setterOrGetter = null
        try { 
            // Have to lock property map prior to reading it, to guard against 
            // another thread putting something in there after we've checked it 
            // but before we've added an entry to it 
            mPropertyMapLock.writeLock().lock(); 
            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass); 
            if (propertyMap != null) { 
                setterOrGetter = propertyMap.get(mPropertyName); 
            } 
            if (setterOrGetter == null) { 
                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType); 
                if (propertyMap == null) { 
                    propertyMap = new HashMap<String, Method>(); 
                    propertyMapMap.put(targetClass, propertyMap); 
                } 
                propertyMap.put(mPropertyName, setterOrGetter); 
            } 
        } finally { 
            mPropertyMapLock.writeLock().unlock(); 
        } 
        return setterOrGetter; 
    } 
 
    /**
     * Utility function to get the setter from targetClass 
     * @param targetClass The Class on which the requested method should exist. 
     */
 
    void setupSetter(Class targetClass) { 
        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType); 
    } 
 
    /**
     * Utility function to get the getter from targetClass 
     */
 
    private void setupGetter(Class targetClass) { 
        mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get"null); 
    } 
 
    /**
     * Internal function (called from ObjectAnimator) to set up the setter and getter 
     * prior to running the animation. If the setter has not been manually set for this 
     * object, it will be derived automatically given the property name, target object, and 
     * types of values supplied. If no getter has been set, it will be supplied iff any of the 
     * supplied values was null. If there is a null value, then the getter (supplied or derived) 
     * will be called to set those null values to the current value of the property 
     * on the target object. 
     * @param target The object on which the setter (and possibly getter) exist. 
     */
 
    void setupSetterAndGetter(Object target) { 
        //if (mProperty != null) { 
        //    // check to make sure that mProperty is on the class of target 
        //    try { 
        //        Object testValue = mProperty.get(target); 
        //        for (Keyframe kf : mKeyframeSet.mKeyframes) { 
        //            if (!kf.hasValue()) { 
        //                kf.setValue(mProperty.get(target)); 
        //            } 
        //        } 
        //        return; 
        //    } catch (ClassCastException e) { 
        //        Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() + 
        //                ") on target object " + target + ". Trying reflection instead"); 
        //        mProperty = null; 
        //    } 
        //} 
        Class targetClass = target.getClass(); 
        if (mSetter == null) { 
            setupSetter(targetClass); 
        } 
        for (Keyframe kf : mKeyframeSet.mKeyframes) { 
            if (!kf.hasValue()) { 
                if (mGetter == null) { 
                    setupGetter(targetClass); 
                } 
                try { 
                    kf.setValue(mGetter.invoke(target)); 
                } catch (InvocationTargetException e) { 
                    Log.e("PropertyValuesHolder", e.toString()); 
                } catch (IllegalAccessException e) { 
                    Log.e("PropertyValuesHolder", e.toString()); 
                } 
            } 
        } 
    } 
 
    /**
     * Utility function to set the value stored in a particular Keyframe. The value used is 
     * whatever the value is for the property name specified in the keyframe on the target object. 
     * 
     * @param target The target object from which the current value should be extracted. 
     * @param kf The keyframe which holds the property name and value. 
     */
 
    private void setupValue(Object target, Keyframe kf) { 
        //if (mProperty != null) { 
        //    kf.setValue(mProperty.get(target)); 
        //} 
        try { 
            if (mGetter == null) { 
                Class targetClass = target.getClass(); 
                setupGetter(targetClass); 
            } 
            kf.setValue(mGetter.invoke(target)); 
        } catch (InvocationTargetException e) { 
            Log.e("PropertyValuesHolder", e.toString()); 
        } catch (IllegalAccessException e) { 
            Log.e("PropertyValuesHolder", e.toString()); 
        } 
    } 
 
    /**
     * This function is called by ObjectAnimator when setting the start values for an animation. 
     * The start values are set according to the current values in the target object. The 
     * property whose value is extracted is whatever is specified by the propertyName of this 
     * PropertyValuesHolder object. 
     * 
     * @param target The object which holds the start values that should be set. 
     */
 
    void setupStartValue(Object target) { 
        setupValue(target, mKeyframeSet.mKeyframes.get(0)); 
    } 
 
    /**
     * This function is called by ObjectAnimator when setting the end values for an animation. 
     * The end values are set according to the current values in the target object. The 
     * property whose value is extracted is whatever is specified by the propertyName of this 
     * PropertyValuesHolder object. 
     * 
     * @param target The object which holds the start values that should be set. 
     */
 
    void setupEndValue(Object target) { 
        setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1)); 
    } 
 
    @Override 
    public PropertyValuesHolder clone() { 
        try { 
            PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone(); 
            newPVH.mPropertyName = mPropertyName; 
            //newPVH.mProperty = mProperty; 
            newPVH.mKeyframeSet = mKeyframeSet.clone(); 
            newPVH.mEvaluator = mEvaluator; 
            return newPVH; 
        } catch (CloneNotSupportedException e) { 
            // won't reach here 
            return null
        } 
    } 
 
    /**
     * Internal function to set the value on the target object, using the setter set up 
     * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator 
     * to handle turning the value calculated by ValueAnimator into a value set on the object 
     * according to the name of the property. 
     * @param target The target object on which the value is set 
     */
 
    void setAnimatedValue(Object target) { 
        //if (mProperty != null) { 
        //    mProperty.set(target, getAnimatedValue()); 
        //} 
        if (mSetter != null) { 
            try { 
                mTmpValueArray[0] = getAnimatedValue(); 
                mSetter.invoke(target, mTmpValueArray); 
            } catch (InvocationTargetException e) { 
                Log.e("PropertyValuesHolder", e.toString()); 
            } catch (IllegalAccessException e) { 
                Log.e("PropertyValuesHolder", e.toString()); 
            } 
        } 
    } 
 
    /**
     * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used 
     * to calculate animated values. 
     */
 
    void init() { 
        if (mEvaluator == null) { 
            // We already handle int and float automatically, but not their Object 
            // equivalents 
            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : 
                    (mValueType == Float.class) ? sFloatEvaluator : 
                    null
        } 
        if (mEvaluator != null) { 
            // KeyframeSet knows how to evaluate the common types - only give it a custom 
            // evaluator if one has been set on this class 
            mKeyframeSet.setEvaluator(mEvaluator); 
        } 
    } 
 
    /**
     * The TypeEvaluator will the automatically determined based on the type of values 
     * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so 
     * desired. This may be important in cases where either the type of the values supplied 
     * do not match the way that they should be interpolated between, or if the values 
     * are of a custom type or one not currently understood by the animation system. Currently, 
     * only values of type float and int (and their Object equivalents: Float 
     * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator. 
     * @param evaluator 
     */
 
    public void setEvaluator(TypeEvaluator evaluator) { 
        mEvaluator = evaluator; 
        mKeyframeSet.setEvaluator(evaluator); 
    } 
 
    /**
     * Function used to calculate the value according to the evaluator set up for 
     * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue(). 
     * 
     * @param fraction The elapsed, interpolated fraction of the animation. 
     */
 
    void calculateValue(float fraction) { 
        mAnimatedValue = mKeyframeSet.getValue(fraction); 
    } 
 
    /**
     * Sets the name of the property that will be animated. This name is used to derive 
     * a setter function that will be called to set animated values. 
     * For example, a property name of <code>foo</code> will result 
     * in a call to the function <code>setFoo()</code> on the target object. If either 
     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will 
     * also be derived and called. 
     * 
     * <p>Note that the setter function derived from this property name 
     * must take the same parameter type as the 
     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to 
     * the setter function will fail.</p> 
     * 
     * @param propertyName The name of the property being animated. 
     */
 
    public void setPropertyName(String propertyName) { 
        mPropertyName = propertyName; 
    } 
 
    /**
     * Sets the property that will be animated. 
     * 
     * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property 
     * must exist on the target object specified in that ObjectAnimator.</p> 
     * 
     * @param property The property being animated. 
     */
 
    //public void setProperty(Property property) { 
    //    mProperty = property; 
    //} 
 
    /**
     * Gets the name of the property that will be animated. This name will be used to derive 
     * a setter function that will be called to set animated values. 
     * For example, a property name of <code>foo</code> will result 
     * in a call to the function <code>setFoo()</code> on the target object. If either 
     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will 
     * also be derived and called. 
     */
 
    public String getPropertyName() { 
        return mPropertyName; 
    } 
 
    /**
     * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value 
     * most recently calculated in calculateValue(). 
     * @return 
     */
 
    Object getAnimatedValue() { 
        return mAnimatedValue; 
    } 
 
    @Override 
    public String toString() { 
        return mPropertyName + ": " + mKeyframeSet.toString(); 
    } 
 
    /**
     * Utility method to derive a setter/getter method name from a property name, where the 
     * prefix is typically "set" or "get" and the first letter of the property name is 
     * capitalized. 
     * 
     * @param prefix The precursor to the method name, before the property name begins, typically 
     * "set" or "get". 
     * @param propertyName The name of the property that represents the bulk of the method name 
     * after the prefix. The first letter of this word will be capitalized in the resulting 
     * method name. 
     * @return String the property name converted to a method name according to the conventions 
     * specified above. 
     */
 
    static String getMethodName(String prefix, String propertyName) { 
        if (propertyName == null || propertyName.length() == 0) { 
            // shouldn't get here 
            return prefix; 
        } 
        char firstLetter = Character.toUpperCase(propertyName.charAt(0)); 
        String theRest = propertyName.substring(1); 
        return prefix + firstLetter + theRest; 
    } 
 
    static class IntPropertyValuesHolder extends PropertyValuesHolder { 
 
        // Cache JNI functions to avoid looking them up twice 
        //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = 
        //        new HashMap<Class, HashMap<String, Integer>>(); 
        //int mJniSetter; 
        //private IntProperty mIntProperty; 
 
        IntKeyframeSet mIntKeyframeSet; 
        int mIntAnimatedValue; 
 
        public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) { 
            super(propertyName); 
            mValueType = int.class
            mKeyframeSet = keyframeSet; 
            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; 
        } 
 
        //public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) { 
        //    super(property); 
        //    mValueType = int.class; 
        //    mKeyframeSet = keyframeSet; 
        //    mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; 
        //    if (property instanceof  IntProperty) { 
        //        mIntProperty = (IntProperty) mProperty; 
        //    } 
        //} 
 
        public IntPropertyValuesHolder(String propertyName, int... values) { 
            super(propertyName); 
            setIntValues(values); 
        } 
 
        //public IntPropertyValuesHolder(Property property, int... values) { 
        //    super(property); 
        //    setIntValues(values); 
        //    if (property instanceof  IntProperty) { 
        //        mIntProperty = (IntProperty) mProperty; 
        //    } 
        //} 
 
        @Override 
        public void setIntValues(int... values) { 
            super.setIntValues(values); 
            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; 
        } 
 
        @Override 
        void calculateValue(float fraction) { 
            mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction); 
        } 
 
        @Override 
        Object getAnimatedValue() { 
            return mIntAnimatedValue; 
        } 
 
        @Override 
        public IntPropertyValuesHolder clone() { 
            IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone(); 
            newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet; 
            return newPVH; 
        } 
 
        /**
         * Internal function to set the value on the target object, using the setter set up 
         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator 
         * to handle turning the value calculated by ValueAnimator into a value set on the object 
         * according to the name of the property. 
         * @param target The target object on which the value is set 
         */
 
        @Override 
        void setAnimatedValue(Object target) { 
            //if (mIntProperty != null) { 
            //    mIntProperty.setValue(target, mIntAnimatedValue); 
            //    return; 
            //} 
            //if (mProperty != null) { 
            //    mProperty.set(target, mIntAnimatedValue); 
            //    return; 
            //} 
            //if (mJniSetter != 0) { 
            //    nCallIntMethod(target, mJniSetter, mIntAnimatedValue); 
            //    return; 
            //} 
            if (mSetter != null) { 
                try { 
                    mTmpValueArray[0] = mIntAnimatedValue; 
                    mSetter.invoke(target, mTmpValueArray); 
                } catch (InvocationTargetException e) { 
                    Log.e("PropertyValuesHolder", e.toString()); 
                } catch (IllegalAccessException e) { 
                    Log.e("PropertyValuesHolder", e.toString()); 
                } 
            } 
        } 
 
        @Override 
        void setupSetter(Class targetClass) { 
            //if (mProperty != null) { 
            //    return; 
            //} 
            // Check new static hashmap<propName, int> for setter method 
            //try { 
            //    mPropertyMapLock.writeLock().lock(); 
            //    HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); 
            //    if (propertyMap != null) { 
            //        Integer mJniSetterInteger = propertyMap.get(mPropertyName); 
            //        if (mJniSetterInteger != null) { 
            //            mJniSetter = mJniSetterInteger; 
            //        } 
            //    } 
            //    if (mJniSetter == 0) { 
            //        String methodName = getMethodName("set", mPropertyName); 
            //        mJniSetter = nGetIntMethod(targetClass, methodName); 
            //        if (mJniSetter != 0) { 
            //            if (propertyMap == null) { 
            //                propertyMap = new HashMap<String, Integer>(); 
            //                sJNISetterPropertyMap.put(targetClass, propertyMap); 
            //            } 
            //            propertyMap.put(mPropertyName, mJniSetter); 
            //        } 
            //    } 
            //} catch (NoSuchMethodError e) { 
            //    Log.d("PropertyValuesHolder", 
            //            "Can't find native method using JNI, use reflection" + e); 
            //} finally { 
            //    mPropertyMapLock.writeLock().unlock(); 
            //} 
            //if (mJniSetter == 0) { 
                // Couldn't find method through fast JNI approach - just use reflection 
                super.setupSetter(targetClass); 
            //} 
        } 
    } 
 
    static class FloatPropertyValuesHolder extends PropertyValuesHolder { 
 
        // Cache JNI functions to avoid looking them up twice 
        //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = 
        //        new HashMap<Class, HashMap<String, Integer>>(); 
        //int mJniSetter; 
        //private FloatProperty mFloatProperty; 
 
        FloatKeyframeSet mFloatKeyframeSet; 
        float mFloatAnimatedValue; 
 
        public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) { 
            super(propertyName); 
            mValueType = float.class
            mKeyframeSet = keyframeSet; 
            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; 
        } 
 
        //public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) { 
        //    super(property); 
        //    mValueType = float.class; 
        //    mKeyframeSet = keyframeSet; 
        //    mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; 
        //    if (property instanceof FloatProperty) { 
        //        mFloatProperty = (FloatProperty) mProperty; 
        //    } 
        //} 
 
        public FloatPropertyValuesHolder(String propertyName, float... values) { 
            super(propertyName); 
            setFloatValues(values); 
        } 
 
        //public FloatPropertyValuesHolder(Property property, float... values) { 
        //    super(property); 
        //    setFloatValues(values); 
        //    if (property instanceof  FloatProperty) { 
        //        mFloatProperty = (FloatProperty) mProperty; 
        //    } 
        //} 
 
        @Override 
        public void setFloatValues(float... values) { 
            super.setFloatValues(values); 
            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet; 
        } 
 
        @Override 
        void calculateValue(float fraction) { 
            mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction); 
        } 
 
        @Override 
        Object getAnimatedValue() { 
            return mFloatAnimatedValue; 
        } 
 
        @Override 
        public FloatPropertyValuesHolder clone() { 
            FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone(); 
            newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet; 
            return newPVH; 
        } 
 
        /**
         * Internal function to set the value on the target object, using the setter set up 
         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator 
         * to handle turning the value calculated by ValueAnimator into a value set on the object 
         * according to the name of the property. 
         * @param target The target object on which the value is set 
         */
 
        @Override 
        void setAnimatedValue(Object target) { 
            //if (mFloatProperty != null) { 
            //    mFloatProperty.setValue(target, mFloatAnimatedValue); 
            //    return; 
            //} 
            //if (mProperty != null) { 
            //    mProperty.set(target, mFloatAnimatedValue); 
            //    return; 
            //} 
            //if (mJniSetter != 0) { 
            //    nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue); 
            //    return; 
            //} 
            if (mSetter != null) { 
                try { 
                    mTmpValueArray[0] = mFloatAnimatedValue; 
                    mSetter.invoke(target, mTmpValueArray); 
                } catch (InvocationTargetException e) { 
                    Log.e("PropertyValuesHolder", e.toString()); 
                } catch (IllegalAccessException e) { 
                    Log.e("PropertyValuesHolder", e.toString()); 
                } 
            } 
        } 
 
        @Override 
        void setupSetter(Class targetClass) { 
            //if (mProperty != null) { 
            //    return; 
            //} 
            // Check new static hashmap<propName, int> for setter method 
            //try { 
            //    mPropertyMapLock.writeLock().lock(); 
            //    HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass); 
            //    if (propertyMap != null) { 
            //        Integer mJniSetterInteger = propertyMap.get(mPropertyName); 
            //        if (mJniSetterInteger != null) { 
            //            mJniSetter = mJniSetterInteger; 
            //        } 
            //    } 
            //    if (mJniSetter == 0) { 
            //        String methodName = getMethodName("set", mPropertyName); 
            //        mJniSetter = nGetFloatMethod(targetClass, methodName); 
            //        if (mJniSetter != 0) { 
            //            if (propertyMap == null) { 
            //                propertyMap = new HashMap<String, Integer>(); 
            //                sJNISetterPropertyMap.put(targetClass, propertyMap); 
            //            } 
            //            propertyMap.put(mPropertyName, mJniSetter); 
            //        } 
            //    } 
            //} catch (NoSuchMethodError e) { 
            //    Log.d("PropertyValuesHolder", 
            //            "Can't find native method using JNI, use reflection" + e); 
            //} finally { 
            //    mPropertyMapLock.writeLock().unlock(); 
            //} 
            //if (mJniSetter == 0) { 
                // Couldn't find method through fast JNI approach - just use reflection 
                super.setupSetter(targetClass); 
            //} 
        } 
 
    } 
 
    //native static private int nGetIntMethod(Class targetClass, String methodName); 
    //native static private int nGetFloatMethod(Class targetClass, String methodName); 
    //native static private void nCallIntMethod(Object target, int methodID, int arg); 
    //native static private void nCallFloatMethod(Object target, int methodID, float arg); 
}