d5256cc88315a7757249291b87c2a06cbaee2720
[utils] / system / general / src / main / java / org / wamblee / system / adapters / ConstructorConfiguration.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
22 import org.wamblee.system.core.RequiredInterface;
23 import org.wamblee.system.core.Scope;
24 import org.wamblee.system.core.SystemAssemblyException;
25
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.Modifier;
28
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32
33
34 /**
35  * Class that allows configuration of the constructor to use.   In
36  * particular, it provides:
37  *  <ul>
38  *      <li>Selection of a constructor using explicit selection  {@link
39  *      #select(Class...)} or using the most greedy constructor  {@link
40  *      #greedy()}.</li>
41  *      <li>Selection of methods to invoke to inject other objects into
42  *      the object.</li>
43  *      <li>Selection of fields to set.</li>
44  *  </ul>
45  */
46 public class ConstructorConfiguration {
47     /**
48      * DOCUMENT ME!
49      */
50     private Class clazz;
51
52     /**
53      * DOCUMENT ME!
54      */
55     private Constructor<?> constructor;
56
57     /**
58      * DOCUMENT ME!
59      */
60     private ParameterValues values;
61
62     /**
63      * DOCUMENT ME!
64      */
65     private boolean publicOnly;
66
67 /**
68          * Constructs the configuration. By default the public constructor with the 
69          * most arguments will be used.   
70          * @param aClass Class to construct. 
71          */
72     public ConstructorConfiguration(Class aClass) {
73         clazz           = aClass;
74         constructor     = null;
75         publicOnly      = true;
76     }
77
78     /**
79      * Sets whether or no non public constructors are also considered.
80      * Reset the choice of a constructor to its default.
81      *
82      * @param aNonPublic
83      *
84      * @return
85      */
86     public ConstructorConfiguration setNonPublic(boolean aNonPublic) {
87         publicOnly      = !aNonPublic;
88         constructor     = null;
89         values          = null;
90
91         return this;
92     }
93
94     /**
95      * Selects an explicit constructor.
96      *
97      * @return Return the injector to allow call chaining.
98      *
99      * @throws SystemAssemblyException DOCUMENT ME!
100      */
101     public ConstructorConfiguration select(Class... aTypes) {
102         try {
103             constructor = clazz.getDeclaredConstructor(aTypes);
104         } catch (Exception e) {
105             throw new SystemAssemblyException(e.getMessage(), e);
106         }
107
108         resetValues();
109
110         return this;
111     }
112
113     /**
114      * Selects the greediest constructor.
115      *
116      * @return The injector to allow call chaining.
117      *
118      * @throws SystemAssemblyException if the greediest constructor cannot be
119      *         uniquely  identified.
120      */
121     public ConstructorConfiguration greedy() {
122         Constructor<?>[] declared = clazz.getDeclaredConstructors();
123
124         if (declared.length == 0) {
125             throw new SystemAssemblyException("Class '" + clazz
126                 + " is an interface, primitive type, or array");
127         }
128
129         int                  max     = -1;
130         List<Constructor<?>> checked = new ArrayList<Constructor<?>>();
131         CollectionFilter.filter(Arrays.asList(declared), checked,
132             new Condition<Constructor<?>>() {
133                 @Override
134                 public boolean matches(Constructor<?> aObject) {
135                     if (!publicOnly) {
136                         return true;
137                     } else {
138                         return Modifier.isPublic(aObject.getModifiers());
139                     }
140                 }
141             });
142
143         for (Constructor ctor : checked) {
144             if (ctor.getParameterTypes().length > max) {
145                 max = ctor.getParameterTypes().length;
146             }
147         }
148
149         final int            max2    = max;
150         List<Constructor<?>> ctors   = checked;
151         List<Constructor<?>> longest = new ArrayList<Constructor<?>>();
152         CollectionFilter.filter(ctors, longest,
153             new Condition<Constructor<?>>() {
154                 @Override
155                 public boolean matches(Constructor<?> aObject) {
156                     return aObject.getParameterTypes().length == max2;
157                 }
158             });
159
160         if (longest.size() > 1) {
161             throw new SystemAssemblyException(
162                 "Greediest constructor cannot be uniquely determined");
163         }
164
165         constructor = longest.get(0);
166         resetValues();
167
168         return this;
169     }
170
171     public ParameterValues getParameters() {
172         getConstructor(); // initialize constructor if needed.
173
174         return values;
175     }
176
177     /**
178      * Resets the values.
179      */
180     private void resetValues() {
181         constructor.setAccessible(true);
182         values = new ParameterValues(constructor.getParameterTypes());
183     }
184
185     /**
186      * Creates the object in the given scope.
187      *
188      * @param aScope Scope containing required interfaces for this object.
189      *
190      * @return object.
191      *
192      * @throws SystemAssemblyException DOCUMENT ME!
193      */
194     public Object create(Scope aScope) {
195         Object[] valueArray = values.values(aScope);
196
197         try {
198             return getConstructor().newInstance(valueArray);
199         } catch (Exception e) {
200             throw new SystemAssemblyException("Could not construct object "
201                 + getConstructor() + " " + Arrays.asList(valueArray), e);
202         }
203     }
204
205     public List<RequiredInterface> getRequiredInterfaces() {
206         getConstructor(); // initialize constructor if needed.
207
208         return values.getRequiredInterfaces();
209     }
210
211     private Constructor getConstructor() {
212         if (constructor == null) {
213             greedy();
214         }
215
216         return constructor;
217     }
218 }