    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
    015package org.apache.tapestry5.beanmodel.internal.services;
    016
    017import java.lang.reflect.Method;
    018import java.lang.reflect.Modifier;
    019import java.util.Collections;
    020import java.util.List;
    021
    022import org.apache.tapestry5.beaneditor.NonVisual;
    023import org.apache.tapestry5.beaneditor.ReorderProperties;
    024import org.apache.tapestry5.beanmodel.BeanModel;
    025import org.apache.tapestry5.beanmodel.BeanModelUtils;
    026import org.apache.tapestry5.beanmodel.internal.beanmodel.BeanModelImpl;
    027import org.apache.tapestry5.beanmodel.services.BeanModelSource;
    028import org.apache.tapestry5.beanmodel.services.PropertyConduitSource;
    029import org.apache.tapestry5.commons.Location;
    030import org.apache.tapestry5.commons.Messages;
    031import org.apache.tapestry5.commons.ObjectLocator;
    032import org.apache.tapestry5.commons.services.ClassPropertyAdapter;
    033import org.apache.tapestry5.commons.services.DataTypeAnalyzer;
    034import org.apache.tapestry5.commons.services.PlasticProxyFactory;
    035import org.apache.tapestry5.commons.services.PropertyAccess;
    036import org.apache.tapestry5.commons.services.PropertyAdapter;
    037import org.apache.tapestry5.commons.services.TypeCoercer;
    038import org.apache.tapestry5.commons.util.CollectionFactory;
    039import org.apache.tapestry5.ioc.annotations.ComponentLayer;
    040import org.apache.tapestry5.ioc.annotations.Primary;
    041
    042public 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 PlasticProxyFactory proxyFactory;
    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  defines the bean that contains the properties
    087     * @param propertyNames the initial set of property names, which will be rebuilt in the correct order
    088     */
    089    private void orderProperties(ClassPropertyAdapter classAdapter, List<String> propertyNames)
    090    {
    091        List<PropertyOrder> properties = CollectionFactory.newList();
    092
    093        for (String name : propertyNames)
    094        {
    095            PropertyAdapter pa = classAdapter.getPropertyAdapter(name);
    096
    097            Method readMethod = pa.getReadMethod();
    098
    099            Location location = readMethod == null ? null : proxyFactory.getMethodLocation(readMethod);
    100
    101            int line = location == null ? -1 : location.getLine();
    102
    103            properties.add(new PropertyOrder(name, computeDepth(pa), line));
    104        }
    105
    106        Collections.sort(properties);
    107
    108        propertyNames.clear();
    109
    110        for (PropertyOrder po : properties)
    111        {
    112            propertyNames.add(po.propertyName);
    113        }
    114    }
    115
    116    private static int computeDepth(PropertyAdapter pa)
    117    {
    118        int depth = 0;
    119        Class c = pa.getDeclaringClass();
    120
    121        // When the method originates in an interface, the parent may be null, not Object.
    122
    123        while (c != null && c != Object.class)
    124        {
    125            depth++;
    126            c = c.getSuperclass();
    127        }
    128
    129        return depth;
    130    }
    131
    132    public BeanModelSourceImpl(TypeCoercer typeCoercer, PropertyAccess propertyAccess,
    133                               PropertyConduitSource propertyConduitSource,
    134                               @ComponentLayer
    135                               PlasticProxyFactory proxyFactory,
    136                               @Primary
    137                               DataTypeAnalyzer dataTypeAnalyzer, ObjectLocator locator)
    138    {
    139        this.typeCoercer = typeCoercer;
    140        this.propertyAccess = propertyAccess;
    141        this.propertyConduitSource = propertyConduitSource;
    142        this.proxyFactory = proxyFactory;
    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}