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