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