setterconfiguration now by default does not add all the setters.
[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 (without the "set" prefix).
104          * @return Reference to the current object to allow call chaining.
105          */
106         public SetterConfiguration remove(String aName) {
107                 final String name = createSetterName(aName);
108                 Map<Method, ParameterValues> setters = new HashMap<Method, ParameterValues>();
109                 for (Method method : _setters.keySet()) {
110                         if (method.getName().equals(name)) {
111                                 _setters.remove(method);
112                                 return this;
113                         }
114                 }
115                 throw new IllegalArgumentException(
116                                 "No setter configured by the name of '" + aName + "'");
117         }
118
119         /**
120          * Creates the name of a setter based on the name of the setter without the
121          * "set" prefix.
122          * 
123          * @param aName
124          *            Setter name.
125          * @return Setter name.
126          */
127         private String createSetterName(String aName) {
128                 return "set" + aName.substring(0, 1).toUpperCase() + aName.substring(1);
129         }
130
131         /**
132          * Adds a given setter name to the setters.
133          * 
134          * @param aName
135          * @return Reference to the current object to allow call chaining.
136          */
137         public SetterConfiguration add(String aName) {
138                 final String name = createSetterName(aName);
139                 int oldlen = _setters.size();
140                 List<Method> methods = new ArrayList<Method>();
141                 CollectionFilter.filter(getAllSetters(_class, _publicOnly), methods,
142                                 new Condition<Method>() {
143                                         @Override
144                                         public boolean matches(Method aObject) {
145                                                 return aObject.getName().equals(name);
146                                         }
147
148                                 });
149                 if (methods.size() == 0 ) {
150                         throw new IllegalArgumentException("No setter found for '" + aName
151                                         + "' in " + _class.getName());
152                 }
153                 // TODO is it possible to get more than one setter here in case the subclass overrides
154                 // the baseclass method? 
155                 _setters.put(methods.get(0), createParameterValues(methods.get(0)));
156                 return this;
157         }
158
159         /**
160          * Adds a given setter identified by the type it accepts to the list of
161          * setters.N
162          * 
163          * @param aType
164          *            Type to look for. Note that this must be the exact type as
165          *            autoboxing and autounboxing is not used.
166          * @return Reference to the current object to allow call chaining.
167          * @throws IllegalArgumentException
168          *             In case no setter is found or multiple setters are found.
169          */
170         public SetterConfiguration add(final Class aType) {
171                 List<Method> result = new ArrayList<Method>();
172                 CollectionFilter.filter(getAllSetters(_class, _publicOnly), result,
173                                 new Condition<Method>() {
174                                         @Override
175                                         public boolean matches(Method aObject) {
176                                                 Class type = aObject.getParameterTypes()[0];
177                                                 return type.equals(aType);
178                                         }
179
180                                 });
181                 if (result.size() == 0) {
182                         throw new IllegalArgumentException("No setter found in class '"
183                                         + _class.getName()
184                                         + "' that has a setter with argument type '"
185                                         + aType.getName() + "'");
186                 }
187                 if (result.size() > 1) {
188                         String setters = "";
189                         for (Method method : result) {
190                                 setters += method.getName() + " ";
191                         }
192                         throw new IllegalArgumentException(
193                                         "Multiple setters found in class '" + _class.getName()
194                                                         + " that accept type '" + aType.getName() + "': "
195                                                         + setters);
196                 }
197                 Method method = result.get(0);
198                 _setters.put(method, createParameterValues(method));
199                 return this;
200         }
201
202         /**
203          * Gets all setters for the current class.
204          * 
205          * @return List of all setters.
206          */
207         public static List<Method> getAllSetters(Class aClass,
208                         boolean aPublicOnly) {
209                 List<Method> result = new ArrayList<Method>();
210                 for (Method method : getAllMethods(aClass)) {
211                         if (!aPublicOnly || Modifier.isPublic(method.getModifiers())) {
212                                 if (method.getName().startsWith("set")
213                                                 && method.getParameterTypes().length == 1) {
214                                         method.setAccessible(true);
215                                         result.add(method);
216                                 }
217                         }
218                 }
219                 return result;
220         }
221
222         private static ParameterValues createParameterValues(Method method) {
223                 return new ParameterValues(
224                                 new String[] { getSetterName(method) }, new Class[] { method
225                                                 .getParameterTypes()[0] });
226         }
227
228         private static final List<Method> getAllMethods(Class aClass) {
229                 return ReflectionUtils.getAllMethods(aClass);
230         }
231
232         /**
233          * Gets the required interfaces based on the configured setteres.
234          * 
235          * @return List of required interfaces.
236          */
237         public List<RequiredInterface> getRequiredInterfaces() {
238                 List<RequiredInterface> result = new ArrayList<RequiredInterface>();
239                 for (Method method : _setters.keySet()) {
240                         result.addAll(_setters.get(method).getRequiredInterfaces());
241                 }
242                 return result;
243         }
244
245         /**
246          * Invokes all configured setters with the appropriate values.
247          * 
248          * @param aScope
249          *            Scope within which invocation takes place.
250          * @param aObject
251          *            Object on which the invocation takes place.
252          */
253         public void inject(Scope aScope, Object aObject) {
254                 if (!_class.isInstance(aObject)) {
255                         throw new IllegalArgumentException("Object '" + aObject
256                                         + "' is not an instance of " + _class.getName());
257                 }
258                 for (Method method : _setters.keySet()) {
259                         ParameterValues values = _setters.get(method);
260
261                         try {
262                                 method.invoke(aObject, values.values(aScope));
263                         } catch (IllegalAccessException e) {
264                                 throw new SystemAssemblyException("Problem invoking " + method
265                                                 + " with " + values, e);
266                         } catch (InvocationTargetException e) {
267                                 throw new SystemAssemblyException("Problem invoking " + method
268                                                 + " with " + values, e);
269                         }
270                 }
271         }
272
273         /**
274          * Returns the parameter values for allowing detailed configuration of how
275          * parameter values are set.
276          * 
277          * @param aSetter
278          *            Setter name without the "set" prefix with the first character
279          *            converted to lower case.
280          * @return Parameter values.
281          */
282         public ParameterValues values(String aMethod) {
283                 String name = createSetterName(aMethod);
284                 for (Method method : _setters.keySet()) {
285                         if (method.getName().equals(name)) {
286                                 return _setters.get(method);
287                         }
288                 }
289                 throw new IllegalArgumentException("No setter method '" + name
290                                 + "' found");
291         }
292
293         /**
294          * Gets the setter name for a given setter method. This is the name of the
295          * setter without the "set" prefix and with the first character converted to
296          * lowercase.
297          * 
298          * @param aMethod
299          *            Method.
300          * @return Setter name.
301          */
302         private static String getSetterName(Method aMethod) {
303                 String result = aMethod.getName().substring(3);
304                 return result.substring(0, 1).toLowerCase() + result.substring(1);
305         }
306
307         public List<Method> getSetters() { 
308                 return new ArrayList<Method>(_setters.keySet());
309         }
310 }