blob: 72902e2ce9b756d62b25ee62f453fe6a1231fd3e [file] [log] [blame]
Stuart McCullochf3173222012-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) {
Stuart McCulloch4482c702012-06-15 13:27:53 +000013 Object o = Proxy.newProxyInstance(c.getClassLoader(), new Class< ? >[] {
14 c
15 }, new ConfigurableHandler(properties, c.getClassLoader()));
Stuart McCullochf3173222012-06-07 21:57:32 +000016 return c.cast(o);
17 }
18
Stuart McCulloch4482c702012-06-15 13:27:53 +000019 public static <T> T createConfigurable(Class<T> c, Dictionary< ? , ? > properties) {
20 Map<Object,Object> alt = new HashMap<Object,Object>();
Stuart McCullochf3173222012-06-07 21:57:32 +000021 for (Enumeration< ? > e = properties.keys(); e.hasMoreElements();) {
22 Object key = e.nextElement();
23 alt.put(key, properties.get(key));
24 }
25 return createConfigurable(c, alt);
26 }
27
28 static class ConfigurableHandler implements InvocationHandler {
29 final Map< ? , ? > properties;
30 final ClassLoader loader;
31
32 ConfigurableHandler(Map< ? , ? > properties, ClassLoader loader) {
33 this.properties = properties;
34 this.loader = loader;
35 }
36
Stuart McCulloch4482c702012-06-15 13:27:53 +000037 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Stuart McCullochf3173222012-06-07 21:57:32 +000038 Config ad = method.getAnnotation(Config.class);
39 String id = Configurable.mangleMethodName(method.getName());
40
41 if (ad != null && !ad.id().equals(Config.NULL))
42 id = ad.id();
43
44 Object o = properties.get(id);
45 if (o == null && args != null && args.length == 1)
46 o = args[0];
47
48 if (o == null) {
49 if (ad != null) {
50 if (ad.required())
Stuart McCulloch4482c702012-06-15 13:27:53 +000051 throw new IllegalStateException("Attribute is required but not set " + method.getName());
Stuart McCullochf3173222012-06-07 21:57:32 +000052
53 o = ad.deflt();
54 if (o.equals(Config.NULL))
55 o = null;
56 }
57 }
58 if (o == null) {
59 Class< ? > rt = method.getReturnType();
60 if (rt == boolean.class || rt == Boolean.class)
61 return false;
Stuart McCulloch4482c702012-06-15 13:27:53 +000062 if (method.getReturnType().isPrimitive() || Number.class.isAssignableFrom(method.getReturnType())) {
Stuart McCullochf3173222012-06-07 21:57:32 +000063
64 o = "0";
Stuart McCulloch4482c702012-06-15 13:27:53 +000065 } else
Stuart McCullochf3173222012-06-07 21:57:32 +000066 return null;
67 }
68
69 if (args != null && args.length == 1) {
70 String s = (String) convert(String.class, o);
71
72 // Allow a base to be specified for File and URL
Stuart McCulloch4482c702012-06-15 13:27:53 +000073 if (method.getReturnType() == File.class && args[0].getClass() == File.class) {
Stuart McCullochf3173222012-06-07 21:57:32 +000074 return new File((File) args[0], s);
Stuart McCulloch4482c702012-06-15 13:27:53 +000075 } else if (method.getReturnType() == URL.class && args[0].getClass() == File.class) {
76 return new URL(((File) args[0]).toURI().toURL(), s);
77 } else if (method.getReturnType() == URL.class && args[0].getClass() == URL.class) {
78 return new URL((URL) args[0], s);
Stuart McCullochf3173222012-06-07 21:57:32 +000079 }
Stuart McCullochf3173222012-06-07 21:57:32 +000080 }
81 return convert(method.getGenericReturnType(), o);
82 }
83
Stuart McCulloch4482c702012-06-15 13:27:53 +000084 @SuppressWarnings({
85 "unchecked", "rawtypes"
86 })
Stuart McCullochf3173222012-06-07 21:57:32 +000087 public Object convert(Type type, Object o) throws Exception {
88
89 // TODO type variables
90 // TODO wildcards
91
92 if (type instanceof ParameterizedType) {
93 ParameterizedType pType = (ParameterizedType) type;
94 return convert(pType, o);
95 }
96
97 if (type instanceof GenericArrayType) {
98 GenericArrayType gType = (GenericArrayType) type;
99 return convertArray(gType.getGenericComponentType(), o);
100 }
101
102 Class< ? > resultType = (Class< ? >) type;
103
104 if (resultType.isArray()) {
105 return convertArray(resultType.getComponentType(), o);
106 }
107
108 Class< ? > actualType = o.getClass();
109 if (actualType.isAssignableFrom(resultType))
110 return o;
111
112 if (resultType == boolean.class || resultType == Boolean.class) {
113 if (actualType == boolean.class || actualType == Boolean.class)
114 return o;
115
116 if (Number.class.isAssignableFrom(actualType)) {
117 double b = ((Number) o).doubleValue();
118 if (b == 0)
119 return false;
120 return true;
121 }
122 return true;
123
Stuart McCulloch4482c702012-06-15 13:27:53 +0000124 } else if (resultType == byte.class || resultType == Byte.class) {
125 if (Number.class.isAssignableFrom(actualType))
126 return ((Number) o).byteValue();
127 resultType = Byte.class;
128 } else if (resultType == char.class) {
129 resultType = Character.class;
130 } else if (resultType == short.class) {
131 if (Number.class.isAssignableFrom(actualType))
132 return ((Number) o).shortValue();
133 resultType = Short.class;
134 } else if (resultType == int.class) {
135 if (Number.class.isAssignableFrom(actualType))
136 return ((Number) o).intValue();
137 resultType = Integer.class;
138 } else if (resultType == long.class) {
139 if (Number.class.isAssignableFrom(actualType))
140 return ((Number) o).longValue();
141 resultType = Long.class;
142 } else if (resultType == float.class) {
143 if (Number.class.isAssignableFrom(actualType))
144 return ((Number) o).floatValue();
145 resultType = Float.class;
146 } else if (resultType == double.class) {
147 if (Number.class.isAssignableFrom(actualType))
148 return ((Number) o).doubleValue();
149 resultType = Double.class;
Stuart McCullochf3173222012-06-07 21:57:32 +0000150 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000151
152 if (resultType.isPrimitive())
Stuart McCulloch4482c702012-06-15 13:27:53 +0000153 throw new IllegalArgumentException("Unknown primitive: " + resultType);
Stuart McCullochf3173222012-06-07 21:57:32 +0000154
Stuart McCulloch4482c702012-06-15 13:27:53 +0000155 if (Number.class.isAssignableFrom(resultType) && actualType == Boolean.class) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000156 Boolean b = (Boolean) o;
157 o = b ? "1" : "0";
Stuart McCulloch4482c702012-06-15 13:27:53 +0000158 } else if (actualType == String.class) {
159 String input = (String) o;
160 if (Enum.class.isAssignableFrom(resultType)) {
161 return Enum.valueOf((Class<Enum>) resultType, input);
Stuart McCullochf3173222012-06-07 21:57:32 +0000162 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000163 if (resultType == Class.class && loader != null) {
164 return loader.loadClass(input);
165 }
166 if (resultType == Pattern.class) {
167 return Pattern.compile(input);
168 }
169 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000170
171 try {
172 Constructor< ? > c = resultType.getConstructor(String.class);
173 return c.newInstance(o.toString());
174 }
175 catch (Throwable t) {
176 // handled on next line
177 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000178 throw new IllegalArgumentException("No conversion to " + resultType + " from " + actualType + " value " + o);
Stuart McCullochf3173222012-06-07 21:57:32 +0000179 }
180
Stuart McCulloch4482c702012-06-15 13:27:53 +0000181 private Object convert(ParameterizedType pType, Object o) throws InstantiationException,
182 IllegalAccessException, Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000183 Class< ? > resultType = (Class< ? >) pType.getRawType();
184 if (Collection.class.isAssignableFrom(resultType)) {
185 Collection< ? > input = toCollection(o);
186 if (resultType.isInterface()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000187 if (resultType == Collection.class || resultType == List.class)
Stuart McCullochf3173222012-06-07 21:57:32 +0000188 resultType = ArrayList.class;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000189 else if (resultType == Set.class || resultType == SortedSet.class)
190 resultType = TreeSet.class;
191 else if (resultType == Queue.class /*
Stuart McCullochf3173222012-06-07 21:57:32 +0000192 * || resultType ==
193 * Deque.class
194 */)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000195 resultType = LinkedList.class;
196 else if (resultType == Queue.class /*
197 * || resultType ==
198 * Deque.class
199 */)
200 resultType = LinkedList.class;
201 else
202 throw new IllegalArgumentException(
203 "Unknown interface for a collection, no concrete class found: " + resultType);
Stuart McCullochf3173222012-06-07 21:57:32 +0000204 }
205
Stuart McCulloch4482c702012-06-15 13:27:53 +0000206 Collection<Object> result = (Collection<Object>) resultType.newInstance();
Stuart McCullochf3173222012-06-07 21:57:32 +0000207 Type componentType = pType.getActualTypeArguments()[0];
208
209 for (Object i : input) {
210 result.add(convert(componentType, i));
211 }
212 return result;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000213 } else if (pType.getRawType() == Class.class) {
214 return loader.loadClass(o.toString());
Stuart McCullochf3173222012-06-07 21:57:32 +0000215 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000216 if (Map.class.isAssignableFrom(resultType)) {
217 Map< ? , ? > input = toMap(o);
218 if (resultType.isInterface()) {
219 if (resultType == SortedMap.class)
220 resultType = TreeMap.class;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000221 else if (resultType == Map.class)
222 resultType = LinkedHashMap.class;
Stuart McCullochf3173222012-06-07 21:57:32 +0000223 else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000224 throw new IllegalArgumentException(
225 "Unknown interface for a collection, no concrete class found: " + resultType);
Stuart McCullochf3173222012-06-07 21:57:32 +0000226 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000227 Map<Object,Object> result = (Map<Object,Object>) resultType.newInstance();
Stuart McCullochf3173222012-06-07 21:57:32 +0000228 Type keyType = pType.getActualTypeArguments()[0];
229 Type valueType = pType.getActualTypeArguments()[1];
230
231 for (Map.Entry< ? , ? > entry : input.entrySet()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000232 result.put(convert(keyType, entry.getKey()), convert(valueType, entry.getValue()));
Stuart McCullochf3173222012-06-07 21:57:32 +0000233 }
234 return result;
235 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000236 throw new IllegalArgumentException("cannot convert to " + pType
237 + " because it uses generics and is not a Collection or a map");
Stuart McCullochf3173222012-06-07 21:57:32 +0000238 }
239
240 Object convertArray(Type componentType, Object o) throws Exception {
241 Collection< ? > input = toCollection(o);
242 Class< ? > componentClass = getRawClass(componentType);
243 Object array = Array.newInstance(componentClass, input.size());
244
245 int i = 0;
246 for (Object next : input) {
247 Array.set(array, i++, convert(componentType, next));
248 }
249 return array;
250 }
251
252 private Class< ? > getRawClass(Type type) {
253 if (type instanceof Class)
254 return (Class< ? >) type;
255
256 if (type instanceof ParameterizedType)
257 return (Class< ? >) ((ParameterizedType) type).getRawType();
258
Stuart McCulloch4482c702012-06-15 13:27:53 +0000259 throw new IllegalArgumentException("For the raw type, type must be ParamaterizedType or Class but is "
260 + type);
Stuart McCullochf3173222012-06-07 21:57:32 +0000261 }
262
263 private Collection< ? > toCollection(Object o) {
264 if (o instanceof Collection)
265 return (Collection< ? >) o;
266
267 if (o.getClass().isArray()) {
268 if (o.getClass().getComponentType().isPrimitive()) {
269 int length = Array.getLength(o);
270 List<Object> result = new ArrayList<Object>(length);
271 for (int i = 0; i < length; i++) {
272 result.add(Array.get(o, i));
273 }
274 return result;
275 }
276 return Arrays.asList((Object[]) o);
277 }
278
279 if (o instanceof String) {
280 String s = (String) o;
281 if (s.indexOf('|') > 0)
282 return Arrays.asList(s.split("\\|"));
283 }
284 return Arrays.asList(o);
285 }
286
287 private Map< ? , ? > toMap(Object o) {
288 if (o instanceof Map)
289 return (Map< ? , ? >) o;
290
Stuart McCulloch4482c702012-06-15 13:27:53 +0000291 throw new IllegalArgumentException("Cannot convert " + o + " to a map as requested");
Stuart McCullochf3173222012-06-07 21:57:32 +0000292 }
293
294 }
295
296 public static String mangleMethodName(String id) {
297 StringBuilder sb = new StringBuilder(id);
298 for (int i = 0; i < sb.length(); i++) {
299 char c = sb.charAt(i);
300 boolean twice = i < sb.length() - 1 && sb.charAt(i + 1) == c;
301 if (c == '$' || c == '_') {
302 if (twice)
303 sb.deleteCharAt(i + 1);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000304 else if (c == '$')
305 sb.deleteCharAt(i--); // Remove dollars
Stuart McCullochf3173222012-06-07 21:57:32 +0000306 else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000307 sb.setCharAt(i, '.'); // Make _ into .
Stuart McCullochf3173222012-06-07 21:57:32 +0000308 }
309 }
310 return sb.toString();
311 }
312}