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