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 }