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 */ 016package org.joda.beans.impl.reflection; 017 018import java.beans.IntrospectionException; 019import java.beans.PropertyDescriptor; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Type; 024import java.util.Arrays; 025import java.util.List; 026 027import org.joda.beans.Bean; 028import org.joda.beans.MetaBean; 029import org.joda.beans.Property; 030import org.joda.beans.PropertyReadWrite; 031import org.joda.beans.impl.BasicMetaProperty; 032import 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 */ 046public 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 <P> the property type 063 * @param beanType the bean type, not null 064 * @param propertyName the property name, not empty 065 * @return the property, not null 066 */ 067 public static <P> ReflectiveMetaProperty<P> of(Class<? extends Bean> beanType, String propertyName) { 068 return new ReflectiveMetaProperty<P>(beanType, propertyName); 069 } 070 071 /** 072 * Constructor using {@code PropertyDescriptor} to find the get and set methods. 073 * 074 * @param beanType the bean type, not null 075 * @param propertyName the property name, not empty 076 */ 077 @SuppressWarnings("unchecked") 078 private ReflectiveMetaProperty(Class<? extends Bean> beanType, String propertyName) { 079 super(propertyName); 080 PropertyDescriptor descriptor; 081 try { 082 descriptor = new PropertyDescriptor(propertyName, beanType); 083 } catch (IntrospectionException ex) { 084 throw new NoSuchFieldError("Invalid property: " + propertyName + ": " + ex.getMessage()); 085 } 086 Method readMethod = descriptor.getReadMethod(); 087 Method writeMethod = descriptor.getWriteMethod(); 088 if (readMethod == null && writeMethod == null) { 089 throw new NoSuchFieldError("Invalid property: " + propertyName + ": Both read and write methods are missing"); 090 } 091 this.declaringType = (readMethod != null ? readMethod.getDeclaringClass() : writeMethod.getDeclaringClass()); 092 this.propertyType = (Class<P>) descriptor.getPropertyType(); 093 this.readMethod = readMethod; 094 this.writeMethod = writeMethod; 095 } 096 097 /** 098 * Sets the meta-bean, necessary due to ordering restrictions during loading. 099 * @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}