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.beanmodel.internal.services; 016 017import java.lang.reflect.Method; 018import java.lang.reflect.Modifier; 019import java.util.Collections; 020import java.util.List; 021 022import org.apache.tapestry5.beaneditor.NonVisual; 023import org.apache.tapestry5.beaneditor.ReorderProperties; 024import org.apache.tapestry5.beanmodel.BeanModel; 025import org.apache.tapestry5.beanmodel.BeanModelUtils; 026import org.apache.tapestry5.beanmodel.internal.beanmodel.BeanModelImpl; 027import org.apache.tapestry5.beanmodel.services.BeanModelSource; 028import org.apache.tapestry5.beanmodel.services.PropertyConduitSource; 029import org.apache.tapestry5.commons.Location; 030import org.apache.tapestry5.commons.Messages; 031import org.apache.tapestry5.commons.ObjectLocator; 032import org.apache.tapestry5.commons.services.ClassPropertyAdapter; 033import org.apache.tapestry5.commons.services.DataTypeAnalyzer; 034import org.apache.tapestry5.commons.services.PlasticProxyFactory; 035import org.apache.tapestry5.commons.services.PropertyAccess; 036import org.apache.tapestry5.commons.services.PropertyAdapter; 037import org.apache.tapestry5.commons.services.TypeCoercer; 038import org.apache.tapestry5.commons.util.CollectionFactory; 039import org.apache.tapestry5.ioc.annotations.ComponentLayer; 040import org.apache.tapestry5.ioc.annotations.Primary; 041 042public 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 PlasticProxyFactory proxyFactory; 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 defines the bean that contains the properties 087 * @param propertyNames the initial set of property names, which will be rebuilt in the correct order 088 */ 089 private void orderProperties(ClassPropertyAdapter classAdapter, List<String> propertyNames) 090 { 091 List<PropertyOrder> properties = CollectionFactory.newList(); 092 093 for (String name : propertyNames) 094 { 095 PropertyAdapter pa = classAdapter.getPropertyAdapter(name); 096 097 Method readMethod = pa.getReadMethod(); 098 099 Location location = readMethod == null ? null : proxyFactory.getMethodLocation(readMethod); 100 101 int line = location == null ? -1 : location.getLine(); 102 103 properties.add(new PropertyOrder(name, computeDepth(pa), line)); 104 } 105 106 Collections.sort(properties); 107 108 propertyNames.clear(); 109 110 for (PropertyOrder po : properties) 111 { 112 propertyNames.add(po.propertyName); 113 } 114 } 115 116 private static int computeDepth(PropertyAdapter pa) 117 { 118 int depth = 0; 119 Class c = pa.getDeclaringClass(); 120 121 // When the method originates in an interface, the parent may be null, not Object. 122 123 while (c != null && c != Object.class) 124 { 125 depth++; 126 c = c.getSuperclass(); 127 } 128 129 return depth; 130 } 131 132 public BeanModelSourceImpl(TypeCoercer typeCoercer, PropertyAccess propertyAccess, 133 PropertyConduitSource propertyConduitSource, 134 @ComponentLayer 135 PlasticProxyFactory proxyFactory, 136 @Primary 137 DataTypeAnalyzer dataTypeAnalyzer, ObjectLocator locator) 138 { 139 this.typeCoercer = typeCoercer; 140 this.propertyAccess = propertyAccess; 141 this.propertyConduitSource = propertyConduitSource; 142 this.proxyFactory = proxyFactory; 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}