blob: 79af122270ef08f211c821e1442b2b69aec99753 [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.configurable;
2
3import java.io.*;
4import java.lang.reflect.*;
5import java.lang.reflect.Proxy;
6import java.net.*;
7import java.util.*;
8import java.util.regex.*;
9
10public class Configurable<T> {
11
12 public static <T> T createConfigurable(Class<T> c, Map< ? , ? > properties) {
13 Object o = Proxy.newProxyInstance(c.getClassLoader(),
14 new Class< ? >[] {c},
15 new ConfigurableHandler(properties, c.getClassLoader()));
16 return c.cast(o);
17 }
18
19 public static <T> T createConfigurable(Class<T> c,
20 Dictionary< ? , ? > properties) {
21 Map<Object, Object> alt = new HashMap<Object, Object>();
22 for (Enumeration< ? > e = properties.keys(); e.hasMoreElements();) {
23 Object key = e.nextElement();
24 alt.put(key, properties.get(key));
25 }
26 return createConfigurable(c, alt);
27 }
28
29 static class ConfigurableHandler implements InvocationHandler {
30 final Map< ? , ? > properties;
31 final ClassLoader loader;
32
33 ConfigurableHandler(Map< ? , ? > properties, ClassLoader loader) {
34 this.properties = properties;
35 this.loader = loader;
36 }
37
38 public Object invoke(Object proxy, Method method, Object[] args)
39 throws Throwable {
40 Config ad = method.getAnnotation(Config.class);
41 String id = Configurable.mangleMethodName(method.getName());
42
43 if (ad != null && !ad.id().equals(Config.NULL))
44 id = ad.id();
45
46 Object o = properties.get(id);
47 if (o == null && args != null && args.length == 1)
48 o = args[0];
49
50 if (o == null) {
51 if (ad != null) {
52 if (ad.required())
53 throw new IllegalStateException(
54 "Attribute is required but not set "
55 + method.getName());
56
57 o = ad.deflt();
58 if (o.equals(Config.NULL))
59 o = null;
60 }
61 }
62 if (o == null) {
63 Class< ? > rt = method.getReturnType();
64 if (rt == boolean.class || rt == Boolean.class)
65 return false;
66 if (method.getReturnType().isPrimitive()
67 || Number.class
68 .isAssignableFrom(method.getReturnType())) {
69
70 o = "0";
71 }
72 else
73 return null;
74 }
75
76 if (args != null && args.length == 1) {
77 String s = (String) convert(String.class, o);
78
79 // Allow a base to be specified for File and URL
80 if (method.getReturnType() == File.class
81 && args[0].getClass() == File.class) {
82 return new File((File) args[0], s);
83 }
84 else
85 if (method.getReturnType() == URL.class
86 && args[0].getClass() == File.class) {
87 return new URL(((File) args[0]).toURI().toURL(), s);
88 }
89 else
90 if (method.getReturnType() == URL.class
91 && args[0].getClass() == URL.class) {
92 return new URL((URL) args[0], s);
93 }
94 }
95 return convert(method.getGenericReturnType(), o);
96 }
97
98 @SuppressWarnings({"unchecked", "rawtypes"})
99 public Object convert(Type type, Object o) throws Exception {
100
101 // TODO type variables
102 // TODO wildcards
103
104 if (type instanceof ParameterizedType) {
105 ParameterizedType pType = (ParameterizedType) type;
106 return convert(pType, o);
107 }
108
109 if (type instanceof GenericArrayType) {
110 GenericArrayType gType = (GenericArrayType) type;
111 return convertArray(gType.getGenericComponentType(), o);
112 }
113
114 Class< ? > resultType = (Class< ? >) type;
115
116 if (resultType.isArray()) {
117 return convertArray(resultType.getComponentType(), o);
118 }
119
120 Class< ? > actualType = o.getClass();
121 if (actualType.isAssignableFrom(resultType))
122 return o;
123
124 if (resultType == boolean.class || resultType == Boolean.class) {
125 if (actualType == boolean.class || actualType == Boolean.class)
126 return o;
127
128 if (Number.class.isAssignableFrom(actualType)) {
129 double b = ((Number) o).doubleValue();
130 if (b == 0)
131 return false;
132 return true;
133 }
134 return true;
135
136 }
137 else
138 if (resultType == byte.class || resultType == Byte.class) {
139 if (Number.class.isAssignableFrom(actualType))
140 return ((Number) o).byteValue();
141 resultType = Byte.class;
142 }
143 else
144 if (resultType == char.class) {
145 resultType = Character.class;
146 }
147 else
148 if (resultType == short.class) {
149 if (Number.class.isAssignableFrom(actualType))
150 return ((Number) o).shortValue();
151 resultType = Short.class;
152 }
153 else
154 if (resultType == int.class) {
155 if (Number.class.isAssignableFrom(actualType))
156 return ((Number) o).intValue();
157 resultType = Integer.class;
158 }
159 else
160 if (resultType == long.class) {
161 if (Number.class
162 .isAssignableFrom(actualType))
163 return ((Number) o).longValue();
164 resultType = Long.class;
165 }
166 else
167 if (resultType == float.class) {
168 if (Number.class
169 .isAssignableFrom(actualType))
170 return ((Number) o).floatValue();
171 resultType = Float.class;
172 }
173 else
174 if (resultType == double.class) {
175 if (Number.class
176 .isAssignableFrom(actualType))
177 return ((Number) o)
178 .doubleValue();
179 resultType = Double.class;
180 }
181
182 if (resultType.isPrimitive())
183 throw new IllegalArgumentException("Unknown primitive: "
184 + resultType);
185
186 if (Number.class.isAssignableFrom(resultType)
187 && actualType == Boolean.class) {
188 Boolean b = (Boolean) o;
189 o = b ? "1" : "0";
190 }
191 else
192 if (actualType == String.class) {
193 String input = (String) o;
194 if (Enum.class.isAssignableFrom(resultType)) {
195 return Enum.valueOf((Class<Enum>) resultType, input);
196 }
197 if (resultType == Class.class && loader != null) {
198 return loader.loadClass(input);
199 }
200 if (resultType == Pattern.class) {
201 return Pattern.compile(input);
202 }
203 }
204
205 try {
206 Constructor< ? > c = resultType.getConstructor(String.class);
207 return c.newInstance(o.toString());
208 }
209 catch (Throwable t) {
210 // handled on next line
211 }
212 throw new IllegalArgumentException("No conversion to " + resultType
213 + " from " + actualType + " value " + o);
214 }
215
216 private Object convert(ParameterizedType pType, Object o)
217 throws InstantiationException, IllegalAccessException,
218 Exception {
219 Class< ? > resultType = (Class< ? >) pType.getRawType();
220 if (Collection.class.isAssignableFrom(resultType)) {
221 Collection< ? > input = toCollection(o);
222 if (resultType.isInterface()) {
223 if (resultType == Collection.class
224 || resultType == List.class)
225 resultType = ArrayList.class;
226 else
227 if (resultType == Set.class
228 || resultType == SortedSet.class)
229 resultType = TreeSet.class;
230 else
231 if (resultType == Queue.class /*
232 * || resultType ==
233 * Deque.class
234 */)
235 resultType = LinkedList.class;
236 else
237 if (resultType == Queue.class /*
238 * || resultType ==
239 * Deque.class
240 */)
241 resultType = LinkedList.class;
242 else
243 throw new IllegalArgumentException(
244 "Unknown interface for a collection, no concrete class found: "
245 + resultType);
246 }
247
248 @SuppressWarnings("unchecked")
249 Collection<Object> result = (Collection<Object>) resultType
250 .newInstance();
251 Type componentType = pType.getActualTypeArguments()[0];
252
253 for (Object i : input) {
254 result.add(convert(componentType, i));
255 }
256 return result;
257 }
258 else
259 if (pType.getRawType() == Class.class) {
260 return loader.loadClass(o.toString());
261 }
262 if (Map.class.isAssignableFrom(resultType)) {
263 Map< ? , ? > input = toMap(o);
264 if (resultType.isInterface()) {
265 if (resultType == SortedMap.class)
266 resultType = TreeMap.class;
267 else
268 if (resultType == Map.class)
269 resultType = LinkedHashMap.class;
270 else
271 throw new IllegalArgumentException(
272 "Unknown interface for a collection, no concrete class found: "
273 + resultType);
274 }
275 @SuppressWarnings("unchecked")
276 Map<Object, Object> result = (Map<Object, Object>) resultType
277 .newInstance();
278 Type keyType = pType.getActualTypeArguments()[0];
279 Type valueType = pType.getActualTypeArguments()[1];
280
281 for (Map.Entry< ? , ? > entry : input.entrySet()) {
282 result.put(convert(keyType, entry.getKey()),
283 convert(valueType, entry.getValue()));
284 }
285 return result;
286 }
287 throw new IllegalArgumentException(
288 "cannot convert to "
289 + pType
290 + " because it uses generics and is not a Collection or a map");
291 }
292
293 Object convertArray(Type componentType, Object o) throws Exception {
294 Collection< ? > input = toCollection(o);
295 Class< ? > componentClass = getRawClass(componentType);
296 Object array = Array.newInstance(componentClass, input.size());
297
298 int i = 0;
299 for (Object next : input) {
300 Array.set(array, i++, convert(componentType, next));
301 }
302 return array;
303 }
304
305 private Class< ? > getRawClass(Type type) {
306 if (type instanceof Class)
307 return (Class< ? >) type;
308
309 if (type instanceof ParameterizedType)
310 return (Class< ? >) ((ParameterizedType) type).getRawType();
311
312 throw new IllegalArgumentException(
313 "For the raw type, type must be ParamaterizedType or Class but is "
314 + type);
315 }
316
317 private Collection< ? > toCollection(Object o) {
318 if (o instanceof Collection)
319 return (Collection< ? >) o;
320
321 if (o.getClass().isArray()) {
322 if (o.getClass().getComponentType().isPrimitive()) {
323 int length = Array.getLength(o);
324 List<Object> result = new ArrayList<Object>(length);
325 for (int i = 0; i < length; i++) {
326 result.add(Array.get(o, i));
327 }
328 return result;
329 }
330 return Arrays.asList((Object[]) o);
331 }
332
333 if (o instanceof String) {
334 String s = (String) o;
335 if (s.indexOf('|') > 0)
336 return Arrays.asList(s.split("\\|"));
337 }
338 return Arrays.asList(o);
339 }
340
341 private Map< ? , ? > toMap(Object o) {
342 if (o instanceof Map)
343 return (Map< ? , ? >) o;
344
345 throw new IllegalArgumentException("Cannot convert " + o
346 + " to a map as requested");
347 }
348
349 }
350
351 public static String mangleMethodName(String id) {
352 StringBuilder sb = new StringBuilder(id);
353 for (int i = 0; i < sb.length(); i++) {
354 char c = sb.charAt(i);
355 boolean twice = i < sb.length() - 1 && sb.charAt(i + 1) == c;
356 if (c == '$' || c == '_') {
357 if (twice)
358 sb.deleteCharAt(i + 1);
359 else
360 if (c == '$')
361 sb.deleteCharAt(i--); // Remove dollars
362 else
363 sb.setCharAt(i, '.'); // Make _ into .
364 }
365 }
366 return sb.toString();
367 }
368}