View Javadoc

1   /*
2    *  Copyright 2001-2013 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.beans.impl.reflection;
17  
18  import java.beans.IntrospectionException;
19  import java.beans.PropertyDescriptor;
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Type;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.joda.beans.Bean;
28  import org.joda.beans.MetaBean;
29  import org.joda.beans.Property;
30  import org.joda.beans.PropertyReadWrite;
31  import org.joda.beans.impl.BasicMetaProperty;
32  import org.joda.beans.impl.BasicProperty;
33  
34  /**
35   * A meta-property implemented using a {@code PropertyDescriptor}.
36   * <p>
37   * The property descriptor class is part of the JDK JavaBean standard.
38   * It provides access to get and set a property on a bean.
39   * <p>
40   * Instances of this class should be declared as a static constant on the bean,
41   * one for each property, followed by a {@code ReflectiveMetaBean} declaration.
42   * 
43   * @param <P>  the type of the property content
44   * @author Stephen Colebourne
45   */
46  public final class ReflectiveMetaProperty<P> extends BasicMetaProperty<P> {
47  
48      /** The meta-bean. */
49      private volatile MetaBean metaBean;
50      /** The declaring type. */
51      private final Class<?> declaringType;
52      /** The type of the property. */
53      private final Class<P> propertyType;
54      /** The read method. */
55      private final Method readMethod;
56      /** The write method. */
57      private final Method writeMethod;
58  
59      /**
60       * Factory to create a meta-property avoiding duplicate generics.
61       * 
62       * @param <P>  the property type
63       * @param beanType  the bean type, not null
64       * @param propertyName  the property name, not empty
65       * @return the property, not null
66       */
67      public static <P> ReflectiveMetaProperty<P> of(Class<? extends Bean> beanType, String propertyName) {
68          return new ReflectiveMetaProperty<P>(beanType, propertyName);
69      }
70  
71      /**
72       * Constructor using {@code PropertyDescriptor} to find the get and set methods.
73       * 
74       * @param beanType  the bean type, not null
75       * @param propertyName  the property name, not empty
76       */
77      @SuppressWarnings("unchecked")
78      private ReflectiveMetaProperty(Class<? extends Bean> beanType, String propertyName) {
79          super(propertyName);
80          PropertyDescriptor descriptor;
81          try {
82              descriptor = new PropertyDescriptor(propertyName, beanType);
83          } catch (IntrospectionException ex) {
84              throw new NoSuchFieldError("Invalid property: " + propertyName + ": " + ex.getMessage());
85          }
86          Method readMethod = descriptor.getReadMethod();
87          Method writeMethod = descriptor.getWriteMethod();
88          if (readMethod == null && writeMethod == null) {
89              throw new NoSuchFieldError("Invalid property: " + propertyName + ": Both read and write methods are missing");
90          }
91          this.declaringType = (readMethod != null ? readMethod.getDeclaringClass() : writeMethod.getDeclaringClass());
92          this.propertyType = (Class<P>) descriptor.getPropertyType();
93          this.readMethod = readMethod;
94          this.writeMethod = writeMethod;
95      }
96  
97      /**
98       * Sets the meta-bean, necessary due to ordering restrictions during loading.
99       * @param metaBean  the meta-bean, not null
100      */
101     void setMetaBean(MetaBean metaBean) {
102         this.metaBean = metaBean;
103     }
104 
105     //-----------------------------------------------------------------------
106     @Override
107     public Property<P> createProperty(Bean bean) {
108         return BasicProperty.of(bean, this);
109     }
110 
111     @Override
112     public MetaBean metaBean() {
113         return metaBean;
114     }
115 
116     @Override
117     public Class<?> declaringType() {
118         return declaringType;
119     }
120 
121     @Override
122     public Class<P> propertyType() {
123         return propertyType;
124     }
125 
126     @Override
127     public Type propertyGenericType() {
128         if (readMethod != null) {
129             return readMethod.getGenericReturnType();
130         }
131         return writeMethod.getGenericParameterTypes()[0];
132     }
133 
134     @Override
135     public PropertyReadWrite readWrite() {
136         return (readMethod == null ? PropertyReadWrite.WRITE_ONLY :
137                 (writeMethod == null ? PropertyReadWrite.READ_ONLY : PropertyReadWrite.READ_WRITE));
138     }
139 
140     @Override
141     public List<Annotation> annotations() {
142         if (readMethod != null) {
143             return Arrays.asList(readMethod.getDeclaredAnnotations());
144         }
145         return Arrays.asList(writeMethod.getDeclaredAnnotations());
146     }
147 
148     //-----------------------------------------------------------------------
149     @Override
150     @SuppressWarnings("unchecked")
151     public P get(Bean bean) {
152         if (readWrite().isReadable() == false) {
153             throw new UnsupportedOperationException("Property cannot be read: " + name());
154         }
155         try {
156             return (P) readMethod.invoke(bean, (Object[]) null);
157         } catch (IllegalArgumentException ex) {
158             throw new UnsupportedOperationException("Property cannot be read: " + name(), ex);
159         } catch (IllegalAccessException ex) {
160             throw new UnsupportedOperationException("Property cannot be read: " + name(), ex);
161         } catch (InvocationTargetException ex) {
162             if (ex.getCause() instanceof RuntimeException) {
163                 throw (RuntimeException) ex.getCause();
164             }
165             throw new RuntimeException(ex);
166         }
167     }
168 
169     @Override
170     public void set(Bean bean, Object value) {
171         if (readWrite().isWritable() == false) {
172             throw new UnsupportedOperationException("Property cannot be written: " + name());
173         }
174         try {
175             writeMethod.invoke(bean, value);
176         } catch (IllegalArgumentException ex) {
177             if (value == null && writeMethod.getParameterTypes()[0].isPrimitive()) {
178                 throw new NullPointerException("Property cannot be written: " + name() + ": Cannot store null in primitive");
179             }
180             if (propertyType.isInstance(value) == false) {
181                 throw new ClassCastException("Property cannot be written: " + name() + ": Invalid type: " + value.getClass().getName());
182             }
183             throw new UnsupportedOperationException("Property cannot be written: " + name(), ex);
184         } catch (IllegalAccessException ex) {
185             throw new UnsupportedOperationException("Property cannot be written: " + name(), ex);
186         } catch (InvocationTargetException ex) {
187             if (ex.getCause() instanceof RuntimeException) {
188                 throw (RuntimeException) ex.getCause();
189             }
190             throw new RuntimeException(ex);
191         }
192     }
193 
194 }