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.lang.reflect.Field;
019    import java.lang.reflect.Modifier;
020    import java.util.Collections;
021    import java.util.HashMap;
022    import java.util.Map;
023    import java.util.NoSuchElementException;
024    
025    import org.joda.beans.Bean;
026    import org.joda.beans.BeanBuilder;
027    import org.joda.beans.MetaBean;
028    import org.joda.beans.MetaProperty;
029    import org.joda.beans.PropertyMap;
030    import org.joda.beans.impl.BasicBeanBuilder;
031    import org.joda.beans.impl.BasicPropertyMap;
032    
033    /**
034     * A standard meta-bean implementation.
035     * <p>
036     * This is the standard implementation of a meta-bean.
037     * It requires that the bean implements {@code Bean} and has a no-arguments constructor.
038     * 
039     * @author Stephen Colebourne
040     */
041    public final class ReflectiveMetaBean implements MetaBean {
042    
043        /** The bean type. */
044        private final Class<? extends Bean> beanType;
045        /** The meta-property instances of the bean. */
046        private final Map<String, MetaProperty<?>> metaPropertyMap;
047    
048        /**
049         * Factory to create a meta-bean avoiding duplicate generics.
050         * 
051         * @param <B>  the type of the bean
052         * @param beanClass  the bean class, not null
053         */
054        public static <B extends Bean> ReflectiveMetaBean of(Class<B> beanClass) {
055            return new ReflectiveMetaBean(beanClass);
056        }
057    
058        /**
059         * Constructor.
060         * 
061         * @param beanType  the bean type, not null
062         */
063        @SuppressWarnings("unchecked")
064        private ReflectiveMetaBean(Class<? extends Bean> beanType) {
065            if (beanType == null) {
066                throw new NullPointerException("Bean class must not be null");
067            }
068            this.beanType = beanType;
069            Map<String, MetaProperty<?>> map = new HashMap<String, MetaProperty<?>>();
070            Field[] fields = beanType.getDeclaredFields();
071            for (Field field : fields) {
072                if (MetaProperty.class.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers())) {
073                    field.setAccessible(true);
074                    MetaProperty<Object> mp;
075                    try {
076                        mp = (MetaProperty<Object>) field.get(null);
077                        if (mp instanceof ReflectiveMetaProperty) {
078                            ((ReflectiveMetaProperty<Object>) mp).setMetaBean(this);
079                        }
080                    } catch (IllegalArgumentException ex) {
081                        throw new UnsupportedOperationException("MetaProperty cannot be created: " + field.getName(), ex);
082                    } catch (IllegalAccessException ex) {
083                        throw new UnsupportedOperationException("MetaProperty cannot be created: " + field.getName(), ex);
084                    }
085                    if (mp == null) {
086                        throw new UnsupportedOperationException("MetaProperty cannot be created: " + field.getName() + ": Value must not be null");
087                    }
088                    map.put(mp.name(), mp);
089                }
090            }
091            
092            this.metaPropertyMap = Collections.unmodifiableMap(map);
093        }
094    
095        //-----------------------------------------------------------------------
096        @Override
097        public BeanBuilder<Bean> builder() {
098            try {
099                Bean bean = beanType.newInstance();
100                return new BasicBeanBuilder<Bean>(bean);
101            } catch (InstantiationException ex) {
102                throw new UnsupportedOperationException("Bean cannot be created: " + beanName(), ex);
103            } catch (IllegalAccessException ex) {
104                throw new UnsupportedOperationException("Bean cannot be created: " + beanName(), ex);
105            }
106        }
107    
108        @Override
109        public PropertyMap createPropertyMap(Bean bean) {
110            return BasicPropertyMap.of(bean);
111        }
112    
113        //-----------------------------------------------------------------------
114        @Override
115        public String beanName() {
116            return beanType.getName();
117        }
118    
119        @Override
120        public Class<? extends Bean> beanType() {
121            return beanType;
122        }
123    
124        //-----------------------------------------------------------------------
125        @Override
126        public int metaPropertyCount() {
127            return metaPropertyMap.size();
128        }
129    
130        @Override
131        public boolean metaPropertyExists(String propertyName) {
132            return metaPropertyMap.containsKey(propertyName);
133        }
134    
135        @SuppressWarnings("unchecked")
136        @Override
137        public <R> MetaProperty<R> metaProperty(String propertyName) {
138            MetaProperty<?> metaProperty = metaPropertyMap.get(propertyName);
139            if (metaProperty == null) {
140                throw new NoSuchElementException("Property not found: " + propertyName);
141            }
142            return (MetaProperty<R>) metaProperty;
143        }
144    
145        @Override
146        public Iterable<MetaProperty<?>> metaPropertyIterable() {
147            return metaPropertyMap.values();
148        }
149    
150        @Override
151        public Map<String, MetaProperty<?>> metaPropertyMap() {
152            return metaPropertyMap;
153        }
154    
155        //-----------------------------------------------------------------------
156        @Override
157        public boolean equals(Object obj) {
158            if (obj instanceof ReflectiveMetaBean) {
159                ReflectiveMetaBean other = (ReflectiveMetaBean) obj;
160                return this.beanType.equals(other.beanType);
161            }
162            return false;
163        }
164    
165        @Override
166        public int hashCode() {
167            return beanType.hashCode() + 3;
168        }
169    
170        /**
171         * Returns a string that summarises the meta-bean.
172         * 
173         * @return a summary string, not null
174         */
175        @Override
176        public String toString() {
177            return "MetaBean:" + beanName();
178        }
179    
180    }