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