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    }