    001    // Copyright 2007, 2008, 2010, 2011 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.internal.services;
    016
    017    import java.lang.reflect.Method;
    018    import java.lang.reflect.Modifier;
    019    import java.util.Collections;
    020    import java.util.List;
    021
    022    import org.apache.tapestry5.beaneditor.BeanModel;
    023    import org.apache.tapestry5.beaneditor.NonVisual;
    024    import org.apache.tapestry5.beaneditor.ReorderProperties;
    025    import org.apache.tapestry5.internal.beaneditor.BeanModelImpl;
    026    import org.apache.tapestry5.internal.beaneditor.BeanModelUtils;
    027    import org.apache.tapestry5.ioc.Location;
    028    import org.apache.tapestry5.ioc.Messages;
    029    import org.apache.tapestry5.ioc.ObjectLocator;
    030    import org.apache.tapestry5.ioc.annotations.Primary;
    031    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    032    import org.apache.tapestry5.ioc.services.ClassFactory;
    033    import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
    034    import org.apache.tapestry5.ioc.services.PropertyAccess;
    035    import org.apache.tapestry5.ioc.services.PropertyAdapter;
    036    import org.apache.tapestry5.ioc.services.TypeCoercer;
    037    import org.apache.tapestry5.services.BeanModelSource;
    038    import org.apache.tapestry5.services.ComponentLayer;
    039    import org.apache.tapestry5.services.DataTypeAnalyzer;
    040    import org.apache.tapestry5.services.PropertyConduitSource;
    041
    042    public class BeanModelSourceImpl implements BeanModelSource
    043    {
    044        private final TypeCoercer typeCoercer;
    045
    046        private final PropertyAccess propertyAccess;
    047
    048        private final PropertyConduitSource propertyConduitSource;
    049
    050        private final ClassFactory classFactory;
    051
    052        private final DataTypeAnalyzer dataTypeAnalyzer;
    053
    054        private final ObjectLocator locator;
    055
    056        private static class PropertyOrder implements Comparable<PropertyOrder>
    057        {
    058            final String propertyName;
    059
    060            final int classDepth;
    061
    062            final int sortKey;
    063
    064            public PropertyOrder(final String propertyName, int classDepth, int sortKey)
    065            {
    066                this.propertyName = propertyName;
    067                this.classDepth = classDepth;
    068                this.sortKey = sortKey;
    069            }
    070
    071            public int compareTo(PropertyOrder o)
    072            {
    073                int result = classDepth - o.classDepth;
    074
    075                if (result == 0)
    076                    result = sortKey - o.sortKey;
    077
    078                if (result == 0)
    079                    result = propertyName.compareTo(o.propertyName);
    080
    081                return result;
    082            }
    083        }
    084
    085        /**
    086         * @param classAdapter
    087         *            defines the bean that contains the properties
    088         * @param propertyNames
    089         *            the initial set of property names, which will be rebuilt in the correct order
    090         */
    091        private void orderProperties(ClassPropertyAdapter classAdapter, List<String> propertyNames)
    092        {
    093            List<PropertyOrder> properties = CollectionFactory.newList();
    094
    095            for (String name : propertyNames)
    096            {
    097                PropertyAdapter pa = classAdapter.getPropertyAdapter(name);
    098
    099                Method readMethod = pa.getReadMethod();
    100
    101                Location location = readMethod == null ? null : classFactory.getMethodLocation(readMethod);
    102
    103                int line = location == null ? -1 : location.getLine();
    104
    105                properties.add(new PropertyOrder(name, computeDepth(pa), line));
    106            }
    107
    108            Collections.sort(properties);
    109
    110            propertyNames.clear();
    111
    112            for (PropertyOrder po : properties)
    113            {
    114                propertyNames.add(po.propertyName);
    115            }
    116        }
    117
    118        private static int computeDepth(PropertyAdapter pa)
    119        {
    120            int depth = 0;
    121            Class c = pa.getDeclaringClass();
    122
    123            // When the method originates in an interface, the parent may be null, not Object.
    124
    125            while (c != null && c != Object.class)
    126            {
    127                depth++;
    128                c = c.getSuperclass();
    129            }
    130
    131            return depth;
    132        }
    133
    134        public BeanModelSourceImpl(TypeCoercer typeCoercer, PropertyAccess propertyAccess,
    135                PropertyConduitSource propertyConduitSource, @ComponentLayer
    136                ClassFactory classFactory, @Primary
    137                DataTypeAnalyzer dataTypeAnalyzer, ObjectLocator locator)
    138        {
    139            this.typeCoercer = typeCoercer;
    140            this.propertyAccess = propertyAccess;
    141            this.propertyConduitSource = propertyConduitSource;
    142            this.classFactory = classFactory;
    143            this.dataTypeAnalyzer = dataTypeAnalyzer;
    144            this.locator = locator;
    145        }
    146
    147        public <T> BeanModel<T> createDisplayModel(Class<T> beanClass, Messages messages)
    148        {
    149            return create(beanClass, false, messages);
    150        }
    151
    152        public <T> BeanModel<T> createEditModel(Class<T> beanClass, Messages messages)
    153        {
    154            return create(beanClass, true, messages);
    155        }
    156
    157        public <T> BeanModel<T> create(Class<T> beanClass, boolean filterReadOnlyProperties, Messages messages)
    158        {
    159            assert beanClass != null;
    160            assert messages != null;
    161            ClassPropertyAdapter adapter = propertyAccess.getAdapter(beanClass);
    162
    163            BeanModel<T> model = new BeanModelImpl<T>(beanClass, propertyConduitSource, typeCoercer, messages, locator);
    164
    165            for (final String propertyName : adapter.getPropertyNames())
    166            {
    167                PropertyAdapter pa = adapter.getPropertyAdapter(propertyName);
    168
    169                if (!pa.isRead())
    170                {
    171                    continue;
    172                }
    173
    174                if (isStaticFieldProperty(pa))
    175                {
    176                    continue;
    177                }
    178
    179                if (pa.getAnnotation(NonVisual.class) != null)
    180                {
    181                    continue;
    182                }
    183
    184                if (filterReadOnlyProperties && !pa.isUpdate())
    185                {
    186                    continue;
    187                }
    188
    189                final String dataType = dataTypeAnalyzer.identifyDataType(pa);
    190
    191                // If an unregistered type, then ignore the property.
    192
    193                if (dataType == null)
    194                {
    195                    continue;
    196                }
    197
    198                model.add(propertyName).dataType(dataType);
    199            }
    200
    201            // First, order the properties based on the location of the getter method
    202            // within the class.
    203
    204            List<String> propertyNames = model.getPropertyNames();
    205
    206            orderProperties(adapter, propertyNames);
    207
    208            model.reorder(propertyNames.toArray(new String[propertyNames.size()]));
    209
    210            // Next, check for an annotation with specific ordering information.
    211
    212            ReorderProperties reorderAnnotation = beanClass.getAnnotation(ReorderProperties.class);
    213
    214            if (reorderAnnotation != null)
    215            {
    216                BeanModelUtils.reorder(model, reorderAnnotation.value());
    217            }
    218
    219            return model;
    220        }
    221
    222        private boolean isStaticFieldProperty(PropertyAdapter adapter)
    223        {
    224            return adapter.isField() && Modifier.isStatic(adapter.getField().getModifiers());
    225        }
    226    }