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