001// Copyright 2006, 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 015package org.apache.tapestry5.beanmodel.internal.services; 016 017import java.beans.BeanInfo; 018import java.beans.IntrospectionException; 019import java.beans.Introspector; 020import java.beans.PropertyDescriptor; 021import java.lang.annotation.Annotation; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.util.Arrays; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.tapestry5.commons.services.ClassPropertyAdapter; 030import org.apache.tapestry5.commons.services.PropertyAccess; 031import org.apache.tapestry5.commons.util.CollectionFactory; 032 033@SuppressWarnings("unchecked") 034public class PropertyAccessImpl implements PropertyAccess 035{ 036 private final Map<Class, ClassPropertyAdapter> adapters = CollectionFactory.newConcurrentMap(); 037 038 @Override 039 public Object get(Object instance, String propertyName) 040 { 041 return getAdapter(instance).get(instance, propertyName); 042 } 043 044 @Override 045 public void set(Object instance, String propertyName, Object value) 046 { 047 getAdapter(instance).set(instance, propertyName, value); 048 } 049 050 @Override 051 public Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass) { 052 return getAdapter(instance).getAnnotation(instance, propertyName, annotationClass); 053 } 054 055 056 /** 057 * Clears the cache of adapters and asks the {@link Introspector} to clear its cache. 058 */ 059 @Override 060 public synchronized void clearCache() 061 { 062 adapters.clear(); 063 064 Introspector.flushCaches(); 065 } 066 067 @Override 068 public ClassPropertyAdapter getAdapter(Object instance) 069 { 070 return getAdapter(instance.getClass()); 071 } 072 073 @Override 074 public ClassPropertyAdapter getAdapter(Class forClass) 075 { 076 ClassPropertyAdapter result = adapters.get(forClass); 077 078 if (result == null) 079 { 080 result = buildAdapter(forClass); 081 adapters.put(forClass, result); 082 } 083 084 return result; 085 } 086 087 /** 088 * Builds a new adapter and updates the _adapters cache. This not only guards access to the adapter cache, but also 089 * serializes access to the Java Beans Introspector, which is not thread safe. In addition, handles the case where 090 * the class in question is an interface, accumulating properties inherited from super-classes. 091 */ 092 private synchronized ClassPropertyAdapter buildAdapter(Class forClass) 093 { 094 // In some race conditions, we may hit this method for the same class multiple times. 095 // We just let it happen, replacing the old ClassPropertyAdapter with a new one. 096 097 try 098 { 099 BeanInfo info = Introspector.getBeanInfo(forClass); 100 101 List<PropertyDescriptor> descriptors = CollectionFactory.newList(); 102 103 addAll(descriptors, info.getPropertyDescriptors()); 104 105 // Introspector misses: 106 // - interface methods not implemented in an abstract class (TAP5-921) 107 // - default methods (TAP5-2449) 108 addPropertiesFromExtendedInterfaces(forClass, descriptors); 109 110 addPropertiesFromScala(forClass, descriptors); 111 112 113 return new ClassPropertyAdapterImpl(forClass, descriptors); 114 } 115 catch (Throwable ex) 116 { 117 throw new RuntimeException(ex); 118 } 119 } 120 121 private static <T> void addAll(List<T> list, T[] array) 122 { 123 if (array.length > 0){ 124 list.addAll(Arrays.asList(array)); 125 } 126 } 127 128 private static <T> void addInterfacesRecursively(List<Class> list, Class<?> clazz) 129 { 130 Class<?>[] interfaces = clazz.getInterfaces(); 131 for (int i = 0; i < interfaces.length; i++) { 132 Class<?> iface = interfaces[i]; 133 addInterfacesRecursively(list, iface); 134 list.add(iface); 135 } 136 } 137 138 private static void addPropertiesFromExtendedInterfaces(Class forClass, List<PropertyDescriptor> descriptors) 139 throws IntrospectionException 140 { 141 142 Class[] interfaces = forClass.getInterfaces(); 143 if (interfaces.length == 0){ 144 return; 145 } 146 LinkedList<Class> queue = CollectionFactory.newLinkedList(); 147 // Seed the queue 148 addInterfacesRecursively(queue, forClass); 149 150 while (!queue.isEmpty()) 151 { 152 Class c = queue.removeFirst(); 153 154 BeanInfo info = Introspector.getBeanInfo(c); 155 156 // Duplicates occur and are filtered out in ClassPropertyAdapter which stores 157 // a property name to descriptor map. 158 addAll(descriptors, info.getPropertyDescriptors()); 159 } 160 } 161 162 private void addPropertiesFromScala(Class forClass, List<PropertyDescriptor> descriptors) 163 throws IntrospectionException 164 { 165 for (Method method : forClass.getMethods()) 166 { 167 addPropertyIfScalaGetterMethod(forClass, descriptors, method); 168 } 169 } 170 171 private void addPropertyIfScalaGetterMethod(Class forClass, List<PropertyDescriptor> descriptors, Method method) 172 throws IntrospectionException 173 { 174 if (!isScalaGetterMethod(method)) 175 return; 176 177 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(method.getName(), forClass, method.getName(), 178 null); 179 180 // found a getter, looking for the setter now 181 try 182 { 183 Method setterMethod = findScalaSetterMethod(forClass, method); 184 185 propertyDescriptor.setWriteMethod(setterMethod); 186 } 187 catch (NoSuchMethodException e) 188 { 189 // ignore 190 } 191 192 // check if the same property was already discovered with java bean accessors 193 194 addScalaPropertyIfNoJavaBeansProperty(descriptors, propertyDescriptor, method); 195 } 196 197 private void addScalaPropertyIfNoJavaBeansProperty(List<PropertyDescriptor> descriptors, 198 PropertyDescriptor propertyDescriptor, Method getterMethod) 199 { 200 boolean found = false; 201 202 for (PropertyDescriptor currentPropertyDescriptor : descriptors) 203 { 204 if (currentPropertyDescriptor.getName().equals(getterMethod.getName())) 205 { 206 found = true; 207 208 break; 209 } 210 } 211 212 if (!found) 213 descriptors.add(propertyDescriptor); 214 } 215 216 private Method findScalaSetterMethod(Class forClass, Method getterMethod) throws NoSuchMethodException 217 { 218 return forClass.getMethod(getterMethod.getName() + "_$eq", getterMethod.getReturnType()); 219 } 220 221 private boolean isScalaGetterMethod(Method method) 222 { 223 try 224 { 225 return Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 226 && !method.getReturnType().equals(Void.TYPE) 227 && method.getDeclaringClass().getDeclaredField(method.getName()) != null; 228 } 229 catch (NoSuchFieldException ex) 230 { 231 return false; 232 } 233 } 234}