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