7c7adda30fb80d4a70bd6db98b6c011544460d94
[utils] / system / general / src / main / java / org / wamblee / system / adapters / SetterConfiguration.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.wamblee.system.adapters;
17
18 import java.lang.reflect.InvocationTargetException;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import org.wamblee.collections.CollectionFilter;
27 import org.wamblee.conditions.Condition;
28 import org.wamblee.reflection.ReflectionUtils;
29 import org.wamblee.system.core.RequiredInterface;
30 import org.wamblee.system.core.Scope;
31 import org.wamblee.system.core.SystemAssemblyException;
32
33 /**
34  * Represents the configuration for exposing the setters of a class as required
35  * interfaces.
36  * 
37  * @author Erik Brakkee
38  */
39 public class SetterConfiguration {
40     private Class clazz;
41
42     private boolean publicOnly;
43
44     private Map<Method, ParameterValues> setters;
45
46     /**
47      * Constructs the setter configuration. By default no setters are added.
48      * 
49      * @param aClass
50      *            Class which is being configured.
51      */
52     public SetterConfiguration(Class aClass) {
53         clazz = aClass;
54         publicOnly = true;
55         setters = new HashMap<Method, ParameterValues>();
56     }
57
58     /**
59      * Makes sure that all available setters are used.
60      * 
61      */
62     public SetterConfiguration initAllSetters() {
63         setters.clear();
64
65         for (Method method : getAllSetters(clazz, publicOnly)) {
66             setters.put(method, createParameterValues(method));
67         }
68
69         return this;
70     }
71
72     /**
73      * Called to set whether non-public setters are also used. By default only
74      * public setters are used. The currently selected setters remain chosen.
75      * 
76      * @param aIsNonPublic
77      *            Non public flag.
78      * 
79      */
80     public SetterConfiguration setNonPublic(boolean aIsNonPublic) {
81         publicOnly = !aIsNonPublic;
82
83         return this;
84     }
85
86     /**
87      * Removes all setters.
88      * 
89      * @return Reference to the current object to allow call chaining.
90      */
91     public SetterConfiguration clear() {
92         setters.clear();
93
94         return this;
95     }
96
97     /**
98      * Removes a setter from the set of methods.
99      * 
100      * @param aName
101      *            Name of the setter to remove.
102      * 
103      * @return Reference to the current object to allow call chaining.
104      * 
105      */
106     public SetterConfiguration remove(String aName) {
107         for (Method method : setters.keySet()) {
108             if (method.getName().equals(aName)) {
109                 setters.remove(method);
110
111                 return this;
112             }
113         }
114
115         throw new IllegalArgumentException(
116             "No method configured by the name of '" + aName + "'");
117     }
118
119     /**
120      * Removes the method from the set of methods.
121      * 
122      * @param aMethod
123      *            Method to remove.
124      * 
125      * @return
126      * 
127      */
128     public SetterConfiguration remove(Method aMethod) {
129         if (!aMethod.getDeclaringClass().isAssignableFrom(clazz)) {
130             throw new RuntimeException("Method " + aMethod +
131                 " not found in class " + clazz + " or its superclasses");
132         }
133
134         for (Method method : setters.keySet()) {
135             if (method.equals(aMethod)) {
136                 setters.remove(method);
137
138                 return this;
139             }
140         }
141
142         throw new IllegalArgumentException("Method '" + aMethod +
143             "' was not configured. ");
144     }
145
146     /**
147      * Adds a given setter name to the setters.
148      * 
149      * @param aName
150      *            Name of a setter method.
151      * 
152      * @return Reference to the current object to allow call chaining.
153      * 
154      */
155     public SetterConfiguration add(final String aName) {
156         List<Method> methods = new ArrayList<Method>();
157         CollectionFilter.filter(getAllSetters(clazz, publicOnly), methods,
158             new Condition<Method>() {
159                 @Override
160                 public boolean matches(Method aObject) {
161                     return aObject.getName().equals(aName);
162                 }
163             });
164
165         if (methods.size() == 0) {
166             throw new IllegalArgumentException("Method '" + aName +
167                 "' not found in " + clazz.getName());
168         }
169
170         // TODO is it possible to get more than one setter here in case the
171         // subclass overrides
172         // the baseclass method?
173         setters.put(methods.get(0), createParameterValues(methods.get(0)));
174
175         return this;
176     }
177
178     /**
179      * Adds a given setter identified by the type it accepts to the list of
180      * setters.N
181      * 
182      * @param aType
183      *            Type to look for. Note that this must be the exact type as
184      *            autoboxing and autounboxing is not used.
185      * 
186      * @return Reference to the current object to allow call chaining.
187      * 
188      * @throws IllegalArgumentException
189      *             In case no setter is found or multiple setters are found.
190      */
191     public SetterConfiguration addSetter(final Class aType) {
192         List<Method> result = new ArrayList<Method>();
193         CollectionFilter.filter(getAllSetters(clazz, publicOnly), result,
194             new Condition<Method>() {
195                 @Override
196                 public boolean matches(Method aObject) {
197                     Class type = aObject.getParameterTypes()[0];
198
199                     return type.equals(aType);
200                 }
201             });
202
203         if (result.size() == 0) {
204             throw new IllegalArgumentException("No setter found in class '" +
205                 clazz.getName() + "' that has a setter with argument type '" +
206                 aType.getName() + "'");
207         }
208
209         if (result.size() > 1) {
210             StringBuffer settersString = new StringBuffer();
211
212             for (Method method : result) {
213                 settersString.append((method.getName() + " "));
214             }
215
216             throw new IllegalArgumentException(
217                 "Multiple setters found in class '" + clazz.getName() +
218                     " that accept type '" + aType.getName() + "': " + settersString);
219         }
220
221         Method method = result.get(0);
222         setters.put(method, createParameterValues(method));
223
224         return this;
225     }
226
227     /**
228      * Gets all setters for the current class.
229      * 
230      * 
231      * @return List of all setters.
232      */
233     public static List<Method> getAllSetters(Class aClass, boolean aPublicOnly) {
234         List<Method> result = new ArrayList<Method>();
235
236         for (Method method : getAllMethods(aClass)) {
237             if (!aPublicOnly || Modifier.isPublic(method.getModifiers())) {
238                 if (method.getName().startsWith("set") &&
239                     (method.getParameterTypes().length == 1)) {
240                     method.setAccessible(true);
241                     result.add(method);
242                 }
243             }
244         }
245
246         return result;
247     }
248
249     private static ParameterValues createParameterValues(Method aMethod) {
250         Class[] paramTypes = aMethod.getParameterTypes();
251         String[] paramNames = new String[paramTypes.length];
252
253         for (int i = 0; i < paramTypes.length; i++) {
254             paramNames[i] = aMethod.getName() + "." + i;
255         }
256
257         return new ParameterValues(paramNames, paramTypes);
258     }
259
260     private static final List<Method> getAllMethods(Class aClass) {
261         return ReflectionUtils.getAllMethods(aClass);
262     }
263
264     /**
265      * Gets the required interfaces based on the configured setteres.
266      * 
267      * @return List of required interfaces.
268      */
269     public List<RequiredInterface> getRequiredInterfaces() {
270         List<RequiredInterface> result = new ArrayList<RequiredInterface>();
271
272         for (Method method : setters.keySet()) {
273             result.addAll(setters.get(method).getRequiredInterfaces());
274         }
275
276         return result;
277     }
278
279     /**
280      * Invokes all configured setters with the appropriate values.
281      * 
282      * @param aScope
283      *            Scope within which invocation takes place.
284      * @param aObject
285      *            Object on which the invocation takes place.
286      * 
287      */
288     public void inject(Scope aScope, Object aObject) {
289         if (!clazz.isInstance(aObject)) {
290             throw new IllegalArgumentException("Object '" + aObject +
291                 "' is not an instance of " + clazz.getName());
292         }
293
294         for (Method method : setters.keySet()) {
295             ParameterValues values = setters.get(method);
296
297             try {
298                 method.invoke(aObject, values.values(aScope));
299             } catch (IllegalAccessException e) {
300                 throw new SystemAssemblyException("Problem invoking " + method +
301                     " with " + values, e);
302             } catch (InvocationTargetException e) {
303                 throw new SystemAssemblyException("Problem invoking " + method +
304                     " with " + values, e);
305             }
306         }
307     }
308
309     /**
310      * Returns the parameter values for allowing detailed configuration of how
311      * parameter values are set.
312      * 
313      * @param aMethod
314      *            Setter name without the "set" prefix with the first character
315      *            converted to lower case.
316      * 
317      * @return Parameter values.
318      * 
319      */
320     public ParameterValues values(String aMethod) {
321         for (Method method : setters.keySet()) {
322             if (method.getName().equals(aMethod)) {
323                 return setters.get(method);
324             }
325         }
326
327         throw new IllegalArgumentException("No setter method '" + aMethod +
328             "' found");
329     }
330
331     public List<Method> getSetters() {
332         return new ArrayList<Method>(setters.keySet());
333     }
334 }