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