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