(no commit message)
[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 add(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 method) {
227             // TODO generalize to multiple parameters. 
228                 return new ParameterValues(
229                                 new String[] { method.getName() }, new Class[] { method
230                                                 .getParameterTypes()[0] });
231         }
232
233         private static final List<Method> getAllMethods(Class aClass) {
234                 return ReflectionUtils.getAllMethods(aClass);
235         }
236
237         /**
238          * Gets the required interfaces based on the configured setteres.
239          * 
240          * @return List of required interfaces.
241          */
242         public List<RequiredInterface> getRequiredInterfaces() {
243                 List<RequiredInterface> result = new ArrayList<RequiredInterface>();
244                 for (Method method : _setters.keySet()) {
245                         result.addAll(_setters.get(method).getRequiredInterfaces());
246                 }
247                 return result;
248         }
249
250         /**
251          * Invokes all configured setters with the appropriate values.
252          * 
253          * @param aScope
254          *            Scope within which invocation takes place.
255          * @param aObject
256          *            Object on which the invocation takes place.
257          */
258         public void inject(Scope aScope, Object aObject) {
259                 if (!_class.isInstance(aObject)) {
260                         throw new IllegalArgumentException("Object '" + aObject
261                                         + "' is not an instance of " + _class.getName());
262                 }
263                 for (Method method : _setters.keySet()) {
264                         ParameterValues values = _setters.get(method);
265
266                         try {
267                                 method.invoke(aObject, values.values(aScope));
268                         } catch (IllegalAccessException e) {
269                                 throw new SystemAssemblyException("Problem invoking " + method
270                                                 + " with " + values, e);
271                         } catch (InvocationTargetException e) {
272                                 throw new SystemAssemblyException("Problem invoking " + method
273                                                 + " with " + values, e);
274                         }
275                 }
276         }
277
278         /**
279          * Returns the parameter values for allowing detailed configuration of how
280          * parameter values are set.
281          * 
282          * @param aSetter
283          *            Setter name without the "set" prefix with the first character
284          *            converted to lower case.
285          * @return Parameter values.
286          */
287         public ParameterValues values(String aMethod) {
288                 for (Method method : _setters.keySet()) {
289                         if (method.getName().equals(aMethod)) {
290                                 return _setters.get(method);
291                         }
292                 }
293                 throw new IllegalArgumentException("No setter method '" + aMethod
294                                 + "' found");
295         }
296
297         public List<Method> getSetters() { 
298                 return new ArrayList<Method>(_setters.keySet());
299         }
300 }