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