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.direct;
017    
018    import java.lang.annotation.Annotation;
019    import java.lang.reflect.Field;
020    import java.lang.reflect.Type;
021    import java.util.Arrays;
022    import java.util.Collections;
023    import java.util.List;
024    import java.util.NoSuchElementException;
025    
026    import org.joda.beans.Bean;
027    import org.joda.beans.MetaBean;
028    import org.joda.beans.PropertyReadWrite;
029    import org.joda.beans.impl.BasicMetaProperty;
030    
031    /**
032     * A meta-property implementation designed for use by {@code DirectBean}.
033     * <p>
034     * This meta-property uses reflection to find the {@code Field} to obtain the annotations.
035     * 
036     * @param <P>  the type of the property content
037     * @author Stephen Colebourne
038     */
039    public final class DirectMetaProperty<P> extends BasicMetaProperty<P> {
040    
041        /** The meta-bean. */
042        private final MetaBean metaBean;
043        /** The property type. */
044        private final Class<P> propertyType;
045        /** The declaring type. */
046        private final Class<?> declaringType;
047        /** The field implementing the property. */
048        private final Field field;
049        /** The read-write type. */
050        private final PropertyReadWrite readWrite;
051    
052        /**
053         * Factory to create a read-write meta-property avoiding duplicate generics.
054         * 
055         * @param metaBean  the meta-bean, not null
056         * @param propertyName  the property name, not empty
057         * @param propertyType  the property type, not null
058         */
059        public static <P> DirectMetaProperty<P> ofReadWrite(
060                MetaBean metaBean, String propertyName, Class<?> declaringType, Class<P> propertyType) {
061            Field field = findField(metaBean, propertyName);
062            return new DirectMetaProperty<P>(metaBean, propertyName, declaringType, propertyType, PropertyReadWrite.READ_WRITE, field);
063        }
064    
065        /**
066         * Factory to create a read-write meta-property avoiding duplicate generics.
067         * 
068         * @param metaBean  the meta-bean, not null
069         * @param propertyName  the property name, not empty
070         * @param propertyType  the property type, not null
071         */
072        public static <P> DirectMetaProperty<P> ofReadOnly(
073                MetaBean metaBean, String propertyName, Class<?> declaringType, Class<P> propertyType) {
074            Field field = findField(metaBean, propertyName);
075            return new DirectMetaProperty<P>(metaBean, propertyName, declaringType, propertyType, PropertyReadWrite.READ_ONLY, field);
076        }
077    
078        /**
079         * Factory to create a read-write meta-property avoiding duplicate generics.
080         * 
081         * @param metaBean  the meta-bean, not null
082         * @param propertyName  the property name, not empty
083         * @param propertyType  the property type, not null
084         */
085        public static <P> DirectMetaProperty<P> ofWriteOnly(
086                MetaBean metaBean, String propertyName, Class<?> declaringType, Class<P> propertyType) {
087            Field field = findField(metaBean, propertyName);
088            return new DirectMetaProperty<P>(metaBean, propertyName, declaringType, propertyType, PropertyReadWrite.WRITE_ONLY, field);
089        }
090    
091        private static Field findField(MetaBean metaBean, String propertyName) {
092            Field field = null;
093            Class<?> cls = metaBean.beanType();
094            while (cls != DirectBean.class) {
095                try {
096                    field = cls.getDeclaredField(propertyName);
097                    break;
098                } catch (NoSuchFieldException ex) {
099                    try {
100                        field = cls.getDeclaredField("_" + propertyName);
101                        break;
102                    } catch (NoSuchFieldException ex2) {
103                        cls = cls.getSuperclass();
104                    }
105                }
106            }
107            return field;
108        }
109    
110        /**
111         * Constructor.
112         * 
113         * @param metaBean  the meta-bean, not null
114         * @param propertyName  the property name, not empty
115         * @param declaringType  the declaring type, not null
116         * @param propertyType  the property type, not null
117         * @param readWrite  the read-write type, not null
118         * @param field  the reflected field, not null
119         */
120        private DirectMetaProperty(MetaBean metaBean, String propertyName, Class<?> declaringType,
121                Class<P> propertyType, PropertyReadWrite readWrite, Field field) {
122            super(propertyName);
123            if (metaBean == null) {
124                throw new NullPointerException("MetaBean must not be null");
125            }
126            if (declaringType == null) {
127                throw new NullPointerException("Declaring type must not be null");
128            }
129            if (propertyType == null) {
130                throw new NullPointerException("Property type must not be null");
131            }
132            if (readWrite == null) {
133                throw new NullPointerException("PropertyReadWrite must not be null");
134            }
135            this.metaBean = metaBean;
136            this.propertyType = propertyType;
137            this.declaringType = declaringType;
138            this.readWrite = readWrite;
139            this.field = field;  // may be null
140        }
141    
142        //-----------------------------------------------------------------------
143        @Override
144        public MetaBean metaBean() {
145            return metaBean;
146        }
147    
148        @Override
149        public Class<?> declaringType() {
150            return declaringType;
151        }
152    
153        @Override
154        public Class<P> propertyType() {
155            return propertyType;
156        }
157    
158        @Override
159        public Type propertyGenericType() {
160            if (field == null) {
161                return propertyType;
162            }
163            return field.getGenericType();
164        }
165    
166        @Override
167        public PropertyReadWrite readWrite() {
168            return readWrite;
169        }
170    
171        @Override
172        public <A extends Annotation> A annotation(Class<A> annotationClass) {
173            if (field == null) {
174                throw new UnsupportedOperationException("Field not found for property: " + name());
175            }
176            A annotation = field.getAnnotation(annotationClass);
177            if (annotation == null) {
178                throw new NoSuchElementException("Unknown annotation: " + annotationClass.getName());
179            }
180            return annotation;
181        }
182    
183        @Override
184        public List<Annotation> annotations() {
185            if (field == null) {
186                return Collections.emptyList();
187            }
188            return Arrays.asList(field.getDeclaredAnnotations());
189        }
190    
191        //-----------------------------------------------------------------------
192        @SuppressWarnings("unchecked")
193        @Override
194        public P get(Bean bean) {
195            return (P) ((DirectBean) bean).propertyGet(name(), false);
196        }
197    
198        @Override
199        public void set(Bean bean, Object value) {
200            ((DirectBean) bean).propertySet(name(), value, false);
201        }
202    
203    }