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 }