001 /*
002 * Copyright 2001-2013 Stephen Colebourne
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.joda.beans.impl.reflection;
017
018 import java.beans.IntrospectionException;
019 import java.beans.PropertyDescriptor;
020 import java.lang.annotation.Annotation;
021 import java.lang.reflect.InvocationTargetException;
022 import java.lang.reflect.Method;
023 import java.lang.reflect.Type;
024 import java.util.Arrays;
025 import java.util.List;
026
027 import org.joda.beans.Bean;
028 import org.joda.beans.MetaBean;
029 import org.joda.beans.Property;
030 import org.joda.beans.PropertyReadWrite;
031 import org.joda.beans.impl.BasicMetaProperty;
032 import org.joda.beans.impl.BasicProperty;
033
034 /**
035 * A meta-property implemented using a {@code PropertyDescriptor}.
036 * <p>
037 * The property descriptor class is part of the JDK JavaBean standard.
038 * It provides access to get and set a property on a bean.
039 * <p>
040 * Instances of this class should be declared as a static constant on the bean,
041 * one for each property, followed by a {@code ReflectiveMetaBean} declaration.
042 *
043 * @param <P> the type of the property content
044 * @author Stephen Colebourne
045 */
046 public final class ReflectiveMetaProperty<P> extends BasicMetaProperty<P> {
047
048 /** The meta-bean. */
049 private volatile MetaBean metaBean;
050 /** The declaring type. */
051 private final Class<?> declaringType;
052 /** The type of the property. */
053 private final Class<P> propertyType;
054 /** The read method. */
055 private final Method readMethod;
056 /** The write method. */
057 private final Method writeMethod;
058
059 /**
060 * Factory to create a meta-property avoiding duplicate generics.
061 *
062 * @param beanType the bean type, not null
063 * @param propertyName the property name, not empty
064 */
065 public static <P> ReflectiveMetaProperty<P> of(Class<? extends Bean> beanType, String propertyName) {
066 return new ReflectiveMetaProperty<P>(beanType, propertyName);
067 }
068
069 /**
070 * Constructor using {@code PropertyDescriptor} to find the get and set methods.
071 *
072 * @param beanType the bean type, not null
073 * @param propertyName the property name, not empty
074 */
075 @SuppressWarnings("unchecked")
076 private ReflectiveMetaProperty(Class<? extends Bean> beanType, String propertyName) {
077 super(propertyName);
078 PropertyDescriptor descriptor;
079 try {
080 descriptor = new PropertyDescriptor(propertyName, beanType);
081 } catch (IntrospectionException ex) {
082 throw new NoSuchFieldError("Invalid property: " + propertyName + ": " + ex.getMessage());
083 }
084 Method readMethod = descriptor.getReadMethod();
085 Method writeMethod = descriptor.getWriteMethod();
086 if (readMethod == null && writeMethod == null) {
087 throw new NoSuchFieldError("Invalid property: " + propertyName + ": Both read and write methods are missing");
088 }
089 this.declaringType = (readMethod != null ? readMethod.getDeclaringClass() : writeMethod.getDeclaringClass());
090 this.propertyType = (Class<P>) descriptor.getPropertyType();
091 this.readMethod = readMethod;
092 this.writeMethod = writeMethod;
093 }
094
095 /**
096 * Sets the meta-bean, necessary due to ordering restrictions during loading.
097 * @param metaBean the meta-bean, not null
098 */
099 void setMetaBean(MetaBean metaBean) {
100 this.metaBean = metaBean;
101 }
102
103 //-----------------------------------------------------------------------
104 @Override
105 public Property<P> createProperty(Bean bean) {
106 return BasicProperty.of(bean, this);
107 }
108
109 @Override
110 public MetaBean metaBean() {
111 return metaBean;
112 }
113
114 @Override
115 public Class<?> declaringType() {
116 return declaringType;
117 }
118
119 @Override
120 public Class<P> propertyType() {
121 return propertyType;
122 }
123
124 @Override
125 public Type propertyGenericType() {
126 if (readMethod != null) {
127 return readMethod.getGenericReturnType();
128 }
129 return writeMethod.getGenericParameterTypes()[0];
130 }
131
132 @Override
133 public PropertyReadWrite readWrite() {
134 return (readMethod == null ? PropertyReadWrite.WRITE_ONLY :
135 (writeMethod == null ? PropertyReadWrite.READ_ONLY : PropertyReadWrite.READ_WRITE));
136 }
137
138 @Override
139 public List<Annotation> annotations() {
140 if (readMethod != null) {
141 return Arrays.asList(readMethod.getDeclaredAnnotations());
142 }
143 return Arrays.asList(writeMethod.getDeclaredAnnotations());
144 }
145
146 //-----------------------------------------------------------------------
147 @Override
148 @SuppressWarnings("unchecked")
149 public P get(Bean bean) {
150 if (readWrite().isReadable() == false) {
151 throw new UnsupportedOperationException("Property cannot be read: " + name());
152 }
153 try {
154 return (P) readMethod.invoke(bean, (Object[]) null);
155 } catch (IllegalArgumentException ex) {
156 throw new UnsupportedOperationException("Property cannot be read: " + name(), ex);
157 } catch (IllegalAccessException ex) {
158 throw new UnsupportedOperationException("Property cannot be read: " + name(), ex);
159 } catch (InvocationTargetException ex) {
160 if (ex.getCause() instanceof RuntimeException) {
161 throw (RuntimeException) ex.getCause();
162 }
163 throw new RuntimeException(ex);
164 }
165 }
166
167 @Override
168 public void set(Bean bean, Object value) {
169 if (readWrite().isWritable() == false) {
170 throw new UnsupportedOperationException("Property cannot be written: " + name());
171 }
172 try {
173 writeMethod.invoke(bean, value);
174 } catch (IllegalArgumentException ex) {
175 if (value == null && writeMethod.getParameterTypes()[0].isPrimitive()) {
176 throw new NullPointerException("Property cannot be written: " + name() + ": Cannot store null in primitive");
177 }
178 if (propertyType.isInstance(value) == false) {
179 throw new ClassCastException("Property cannot be written: " + name() + ": Invalid type: " + value.getClass().getName());
180 }
181 throw new UnsupportedOperationException("Property cannot be written: " + name(), ex);
182 } catch (IllegalAccessException ex) {
183 throw new UnsupportedOperationException("Property cannot be written: " + name(), ex);
184 } catch (InvocationTargetException ex) {
185 if (ex.getCause() instanceof RuntimeException) {
186 throw (RuntimeException) ex.getCause();
187 }
188 throw new RuntimeException(ex);
189 }
190 }
191
192 }