001    /*
002     * Created on Nov 23, 2009
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     *
014     * Copyright @2009 the original author or authors.
015     */
016    package org.fest.reflect.beanproperty;
017    
018    import static org.fest.util.Strings.concat;
019    import static org.fest.util.Strings.quote;
020    
021    import java.beans.*;
022    
023    import org.fest.reflect.exception.ReflectionError;
024    import org.fest.reflect.field.StaticFieldName;
025    import org.fest.reflect.field.StaticFieldType;
026    import org.fest.reflect.reference.TypeRef;
027    
028    /**
029     * Understands the use of instrospection to access a property from a JavaBean.
030     * <p>
031     * The following is an example of proper usage of this class:
032     * <pre>
033     *   // Retrieves the value of the property "name"
034     *   String name = {@link org.fest.reflect.core.Reflection#property(String) property}("name").{@link PropertyName#ofType(Class) ofType}(String.class).{@link PropertyType#in(Object) in}(person).{@link Invoker#get() get}();
035     *
036     *   // Sets the value of the property "name" to "Yoda"
037     *   {@link org.fest.reflect.core.Reflection#property(String) property}("name").{@link PropertyName#ofType(Class) ofType}(String.class).{@link PropertyType#in(Object) in}(person).{@link Invoker#set(Object) set}("Yoda");
038     *
039     *   // Retrieves the value of the static property "count"
040     *   int count = {@link org.fest.reflect.core.Reflection#staticField(String) staticField}("count").{@link StaticFieldName#ofType(Class) ofType}(int.class).{@link StaticFieldType#in(Class) in}(Person.class).{@link Invoker#get() get}();
041     *
042     *   // Sets the value of the static property "count" to 3
043     *   {@link org.fest.reflect.core.Reflection#staticField(String) property}("count").{@link StaticFieldName#ofType(Class) ofType}(int.class).{@link StaticFieldType#in(Class) in}(Person.class).{@link Invoker#set(Object) set}(3);
044     * </pre>
045     * </p>
046     *
047     * @param <T> the declared type for the property to access.
048     *
049     * @author Alex Ruiz
050     *
051     * @since 1.2
052     */
053    public final class Invoker<T> {
054    
055      static <T> Invoker<T> newInvoker(String name, TypeRef<T> expectedType, Object target) {
056        return createInvoker(name, expectedType.rawType(), target);
057      }
058    
059      static <T> Invoker<T> newInvoker(String name, Class<T> expectedType, Object target) {
060        return createInvoker(name, expectedType, target);
061      }
062    
063      private static <T> Invoker<T> createInvoker(String name, Class<?> expectedType, Object target) {
064        PropertyDescriptor descriptor = descriptorForProperty(name, target);
065        verifyCorrectType(name, target, expectedType, descriptor);
066        return new Invoker<T>(name, target, descriptor);
067      }
068    
069      private static PropertyDescriptor descriptorForProperty(String propertyName, Object target) {
070        BeanInfo beanInfo = null;
071        Class<?> type = target.getClass();
072        try {
073          beanInfo = Introspector.getBeanInfo(type, Object.class);
074        } catch (Exception e) {
075          throw new ReflectionError(concat("Unable to get BeanInfo for type ", type.getName()), e);
076        }
077        for (PropertyDescriptor d : beanInfo.getPropertyDescriptors())
078          if (propertyName.equals(d.getName())) return d;
079        throw new ReflectionError(concat("Unable to find property ", quote(propertyName), " in " , type.getName()));
080      }
081    
082      static void verifyCorrectType(String name, Object target, Class<?> expectedType, PropertyDescriptor descriptor) {
083        Class<?> actualType = descriptor.getPropertyType();
084        if (!expectedType.isAssignableFrom(actualType)) throw incorrectPropertyType(name, target, actualType, expectedType);
085      }
086    
087      private static ReflectionError incorrectPropertyType(String name, Object target, Class<?> actual, Class<?> expected) {
088        String typeName = target.getClass().getName();
089        String msg = concat("The type of the property ", quote(name), " in ", typeName, " should be <",
090            expected.getName(), "> but was <", actual.getName(), ">");
091        throw new ReflectionError(msg);
092      }
093    
094      private final String propertyName;
095      private final Object target;
096      private final PropertyDescriptor descriptor;
097    
098      private Invoker(String propertyName, Object target, PropertyDescriptor descriptor) {
099        this.propertyName = propertyName;
100        this.target = target;
101        this.descriptor = descriptor;
102      }
103    
104      /**
105       * Sets a value in the property managed by this class.
106       * @param value the value to set.
107       * @throws ReflectionError if the given value cannot be set.
108       */
109      public void set(T value) {
110        try {
111          descriptor.getWriteMethod().invoke(target, value);
112        } catch (Exception e) {
113          throw new ReflectionError(concat("Unable to update the value in property ", quote(propertyName)), e);
114        }
115      }
116    
117      /**
118       * Returns the value of the property managed by this class.
119       * @return the value of the property managed by this class.
120       * @throws ReflectionError if the value of the property cannot be retrieved.
121       */
122      @SuppressWarnings("unchecked")
123      public T get() {
124        try {
125          return (T) descriptor.getReadMethod().invoke(target);
126        } catch (Exception e) {
127          throw new ReflectionError(concat("Unable to obtain the value in property " + quote(propertyName)), e);
128        }
129      }
130    
131      /**
132       * Returns the "real" property managed by this class.
133       * @return the "real" property managed by this class.
134       */
135      public PropertyDescriptor info() {
136        return descriptor;
137      }
138    }