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