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 }