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}