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.ioc.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.ioc.internal.util.CollectionFactory; 030import org.apache.tapestry5.ioc.services.ClassPropertyAdapter; 031import org.apache.tapestry5.ioc.services.PropertyAccess; 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 // TAP5-921 - Introspector misses interface methods not implemented in an abstract class 106 if (forClass.isInterface() || Modifier.isAbstract(forClass.getModifiers()) ) 107 addPropertiesFromExtendedInterfaces(forClass, descriptors); 108 109 addPropertiesFromScala(forClass, descriptors); 110 111 return new ClassPropertyAdapterImpl(forClass, descriptors); 112 } 113 catch (Throwable ex) 114 { 115 throw new RuntimeException(ex); 116 } 117 } 118 119 private <T> void addAll(List<T> list, T[] array) 120 { 121 list.addAll(Arrays.asList(array)); 122 } 123 124 private void addPropertiesFromExtendedInterfaces(Class forClass, List<PropertyDescriptor> descriptors) 125 throws IntrospectionException 126 { 127 LinkedList<Class> queue = CollectionFactory.newLinkedList(); 128 129 // Seed the queue 130 addAll(queue, forClass.getInterfaces()); 131 132 while (!queue.isEmpty()) 133 { 134 Class c = queue.removeFirst(); 135 136 BeanInfo info = Introspector.getBeanInfo(c); 137 138 // Duplicates occur and are filtered out in ClassPropertyAdapter which stores 139 // a property name to descriptor map. 140 addAll(descriptors, info.getPropertyDescriptors()); 141 addAll(queue, c.getInterfaces()); 142 } 143 } 144 145 private void addPropertiesFromScala(Class forClass, List<PropertyDescriptor> descriptors) 146 throws IntrospectionException 147 { 148 for (Method method : forClass.getMethods()) 149 { 150 addPropertyIfScalaGetterMethod(forClass, descriptors, method); 151 } 152 } 153 154 private void addPropertyIfScalaGetterMethod(Class forClass, List<PropertyDescriptor> descriptors, Method method) 155 throws IntrospectionException 156 { 157 if (!isScalaGetterMethod(method)) 158 return; 159 160 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(method.getName(), forClass, method.getName(), 161 null); 162 163 // found a getter, looking for the setter now 164 try 165 { 166 Method setterMethod = findScalaSetterMethod(forClass, method); 167 168 propertyDescriptor.setWriteMethod(setterMethod); 169 } 170 catch (NoSuchMethodException e) 171 { 172 // ignore 173 } 174 175 // check if the same property was already discovered with java bean accessors 176 177 addScalaPropertyIfNoJavaBeansProperty(descriptors, propertyDescriptor, method); 178 } 179 180 private void addScalaPropertyIfNoJavaBeansProperty(List<PropertyDescriptor> descriptors, 181 PropertyDescriptor propertyDescriptor, Method getterMethod) 182 { 183 boolean found = false; 184 185 for (PropertyDescriptor currentPropertyDescriptor : descriptors) 186 { 187 if (currentPropertyDescriptor.getName().equals(getterMethod.getName())) 188 { 189 found = true; 190 191 break; 192 } 193 } 194 195 if (!found) 196 descriptors.add(propertyDescriptor); 197 } 198 199 private Method findScalaSetterMethod(Class forClass, Method getterMethod) throws NoSuchMethodException 200 { 201 return forClass.getMethod(getterMethod.getName() + "_$eq", getterMethod.getReturnType()); 202 } 203 204 private boolean isScalaGetterMethod(Method method) 205 { 206 try 207 { 208 return Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 209 && !method.getReturnType().equals(Void.TYPE) 210 && method.getDeclaringClass().getDeclaredField(method.getName()) != null; 211 } 212 catch (NoSuchFieldException ex) 213 { 214 return false; 215 } 216 } 217}