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