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