(no commit message)
[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() + "': " +
219                     settersString);
220         }
221
222         Method method = result.get(0);
223         setters.put(method, createParameterValues(method));
224
225         return this;
226     }
227
228     /**
229      * Gets all setters for the current class.
230      * 
231      * 
232      * @return List of all setters.
233      */
234     public static List<Method> getAllSetters(Class aClass, boolean aPublicOnly) {
235         List<Method> result = new ArrayList<Method>();
236
237         for (Method method : getAllMethods(aClass)) {
238             if (!aPublicOnly || Modifier.isPublic(method.getModifiers())) {
239                 if (method.getName().startsWith("set") &&
240                     (method.getParameterTypes().length == 1)) {
241                     method.setAccessible(true);
242                     result.add(method);
243                 }
244             }
245         }
246
247         return result;
248     }
249
250     private static ParameterValues createParameterValues(Method aMethod) {
251         Class[] paramTypes = aMethod.getParameterTypes();
252         String[] paramNames = new String[paramTypes.length];
253
254         for (int i = 0; i < paramTypes.length; i++) {
255             paramNames[i] = aMethod.getName() + "." + i;
256         }
257
258         return new ParameterValues(paramNames, paramTypes);
259     }
260
261     private static final List<Method> getAllMethods(Class aClass) {
262         return ReflectionUtils.getAllMethods(aClass);
263     }
264
265     /**
266      * Gets the required interfaces based on the configured setteres.
267      * 
268      * @return List of required interfaces.
269      */
270     public List<RequiredInterface> getRequiredInterfaces() {
271         List<RequiredInterface> result = new ArrayList<RequiredInterface>();
272
273         for (Method method : setters.keySet()) {
274             result.addAll(setters.get(method).getRequiredInterfaces());
275         }
276
277         return result;
278     }
279
280     /**
281      * Invokes all configured setters with the appropriate values.
282      * 
283      * @param aScope
284      *            Scope within which invocation takes place.
285      * @param aObject
286      *            Object on which the invocation takes place.
287      * 
288      */
289     public void inject(Scope aScope, Object aObject) {
290         if (!clazz.isInstance(aObject)) {
291             throw new IllegalArgumentException("Object '" + aObject +
292                 "' is not an instance of " + clazz.getName());
293         }
294
295         for (Method method : setters.keySet()) {
296             ParameterValues values = setters.get(method);
297
298             try {
299                 method.invoke(aObject, values.values(aScope));
300             } catch (IllegalAccessException e) {
301                 throw new SystemAssemblyException("Problem invoking " + method +
302                     " with " + values, e);
303             } catch (InvocationTargetException e) {
304                 throw new SystemAssemblyException("Problem invoking " + method +
305                     " with " + values, e);
306             }
307         }
308     }
309
310     /**
311      * Returns the parameter values for allowing detailed configuration of how
312      * parameter values are set.
313      * 
314      * @param aMethod
315      *            Setter name without the "set" prefix with the first character
316      *            converted to lower case.
317      * 
318      * @return Parameter values.
319      * 
320      */
321     public ParameterValues values(String aMethod) {
322         for (Method method : setters.keySet()) {
323             if (method.getName().equals(aMethod)) {
324                 return setters.get(method);
325             }
326         }
327
328         throw new IllegalArgumentException("No setter method '" + aMethod +
329             "' found");
330     }
331
332     public List<Method> getSetters() {
333         return new ArrayList<Method>(setters.keySet());
334     }
335 }