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 }