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