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 }