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