blob: 9eae98a711d90708300b981709fa19b50a40c12e [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.lib.converter;
2
3import java.lang.reflect.*;
4import java.util.*;
5import java.util.concurrent.*;
6import java.util.regex.*;
7
8import aQute.lib.base64.*;
9
10/**
11 * General Java type converter from an object to any type. Supports number
12 * conversion
13 *
14 * @author aqute
Stuart McCullochbb014372012-06-07 21:57:32 +000015 */
Stuart McCulloch2286f232012-06-15 13:27:53 +000016@SuppressWarnings({
17 "unchecked", "rawtypes"
18})
Stuart McCulloch285034f2012-06-12 12:41:16 +000019public class Converter {
20 public interface Hook {
21 Object convert(Type dest, Object o) throws Exception;
22 }
23
24 boolean fatal = true;
Stuart McCullochec47fe72012-09-19 12:56:05 +000025 Map<Type,Hook> hooks;
26 List<Hook> allHooks;
Stuart McCullochbb014372012-06-07 21:57:32 +000027
28 public <T> T convert(Class<T> type, Object o) throws Exception {
Stuart McCulloch285034f2012-06-12 12:41:16 +000029 // Is it a compatible type?
30 if (type.isAssignableFrom(o.getClass()))
31 return (T) o;
Stuart McCullochbb014372012-06-07 21:57:32 +000032 return (T) convert((Type) type, o);
33 }
34
Stuart McCulloch2286f232012-06-15 13:27:53 +000035 public <T> T convert(TypeReference<T> type, Object o) throws Exception {
Stuart McCulloch81d48de2012-06-29 19:23:09 +000036 return (T) convert(type.getType(), o);
Stuart McCulloch2286f232012-06-15 13:27:53 +000037 }
Stuart McCullochbb014372012-06-07 21:57:32 +000038
Stuart McCulloch81d48de2012-06-29 19:23:09 +000039 public Object convert(Type type, Object o) throws Exception {
40 Class resultType = getRawClass(type);
41 if (o == null) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000042 if (resultType.isPrimitive() || Number.class.isAssignableFrom(resultType))
43 return convert(type, 0);
Stuart McCulloch81d48de2012-06-29 19:23:09 +000044
45 return null; // compatible with any
46 }
47
Stuart McCullochec47fe72012-09-19 12:56:05 +000048 if (allHooks != null) {
49 for (Hook hook : allHooks) {
50 Object r = hook.convert(type, o);
51 if (r != null)
52 return r;
53 }
54 }
55
56 if (hooks != null) {
57 Hook hook = hooks.get(type);
58 if (hook != null) {
59 Object value = hook.convert(type, o);
60 if (value != null)
61 return value;
62 }
Stuart McCulloch285034f2012-06-12 12:41:16 +000063 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000064
Stuart McCulloch285034f2012-06-12 12:41:16 +000065 Class< ? > actualType = o.getClass();
Stuart McCullochbb014372012-06-07 21:57:32 +000066
67 // We can always make a string
68
69 if (resultType == String.class) {
70 if (actualType.isArray()) {
71 if (actualType == char[].class)
72 return new String((char[]) o);
73 if (actualType == byte[].class)
74 return Base64.encodeBase64((byte[]) o);
75 int l = Array.getLength(o);
76 StringBuilder sb = new StringBuilder("[");
77 String del = "";
78 for (int i = 0; i < l; i++) {
79 sb.append(del);
80 del = ",";
81 sb.append(convert(String.class, Array.get(o, i)));
82 }
83 sb.append("]");
84 return sb.toString();
85 }
86 return o.toString();
87 }
88
89 if (Collection.class.isAssignableFrom(resultType))
90 return collection(type, resultType, o);
91
92 if (Map.class.isAssignableFrom(resultType))
93 return map(type, resultType, o);
94
95 if (type instanceof GenericArrayType) {
96 GenericArrayType gType = (GenericArrayType) type;
97 return array(gType.getGenericComponentType(), o);
98 }
99
100 if (resultType.isArray()) {
101 if (actualType == String.class) {
102 String s = (String) o;
103 if (byte[].class == resultType)
104 return Base64.decodeBase64(s);
105
106 if (char[].class == resultType)
107 return s.toCharArray();
108 }
109 if (byte[].class == resultType) {
110 // Sometimes classes implement toByteArray
111 try {
112 Method m = actualType.getMethod("toByteArray");
113 if (m.getReturnType() == byte[].class)
114 return m.invoke(o);
115
Stuart McCulloch285034f2012-06-12 12:41:16 +0000116 }
117 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000118 // Ignore
119 }
120 }
121
122 return array(resultType.getComponentType(), o);
123 }
124
Stuart McCulloch285034f2012-06-12 12:41:16 +0000125 if (resultType.isAssignableFrom(o.getClass()))
126 return o;
127
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000128 if (Map.class.isAssignableFrom(actualType) && resultType.isInterface()) {
129 return proxy(resultType, (Map) o);
130 }
131
Stuart McCullochbb014372012-06-07 21:57:32 +0000132 // Simple type coercion
133
134 if (resultType == boolean.class || resultType == Boolean.class) {
135 if (actualType == boolean.class || actualType == Boolean.class)
136 return o;
137 Number n = number(o);
138 if (n != null)
139 return n.longValue() == 0 ? false : true;
140
141 resultType = Boolean.class;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000142 } else if (resultType == byte.class || resultType == Byte.class) {
143 Number n = number(o);
144 if (n != null)
145 return n.byteValue();
146 resultType = Byte.class;
147 } else if (resultType == char.class || resultType == Character.class) {
148 Number n = number(o);
149 if (n != null)
150 return (char) n.shortValue();
151 resultType = Character.class;
152 } else if (resultType == short.class || resultType == Short.class) {
153 Number n = number(o);
154 if (n != null)
155 return n.shortValue();
156
157 resultType = Short.class;
158 } else if (resultType == int.class || resultType == Integer.class) {
159 Number n = number(o);
160 if (n != null)
161 return n.intValue();
162
163 resultType = Integer.class;
164 } else if (resultType == long.class || resultType == Long.class) {
165 Number n = number(o);
166 if (n != null)
167 return n.longValue();
168
169 resultType = Long.class;
170 } else if (resultType == float.class || resultType == Float.class) {
171 Number n = number(o);
172 if (n != null)
173 return n.floatValue();
174
175 resultType = Float.class;
176 } else if (resultType == double.class || resultType == Double.class) {
177 Number n = number(o);
178 if (n != null)
179 return n.doubleValue();
180
181 resultType = Double.class;
Stuart McCullochbb014372012-06-07 21:57:32 +0000182 }
183
184 assert !resultType.isPrimitive();
185
186 if (actualType == String.class) {
187 String input = (String) o;
188 if (resultType == char[].class)
189 return input.toCharArray();
190
191 if (resultType == byte[].class)
192 return Base64.decodeBase64(input);
193
194 if (Enum.class.isAssignableFrom(resultType)) {
Stuart McCulloch3ed949e2012-10-18 20:01:21 +0000195 try {
196 return Enum.valueOf((Class<Enum>) resultType, input);
197 } catch( Exception e) {
198 input = input.toUpperCase();
199 return Enum.valueOf((Class<Enum>) resultType, input);
200 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000201 }
202 if (resultType == Pattern.class) {
203 return Pattern.compile(input);
204 }
205
206 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000207 Constructor< ? > c = resultType.getConstructor(String.class);
Stuart McCullochbb014372012-06-07 21:57:32 +0000208 return c.newInstance(o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000209 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000210 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000211 try {
212 Method m = resultType.getMethod("valueOf", String.class);
213 if (Modifier.isStatic(m.getModifiers()))
214 return m.invoke(null, o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000215 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000216 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000217
218 if (resultType == Character.class && input.length() == 1)
219 return input.charAt(0);
220 }
221 Number n = number(o);
222 if (n != null) {
223 if (Enum.class.isAssignableFrom(resultType)) {
224 try {
225 Method values = resultType.getMethod("values");
226 Enum[] vs = (Enum[]) values.invoke(null);
227 int nn = n.intValue();
228 if (nn > 0 && nn < vs.length)
229 return vs[nn];
Stuart McCulloch285034f2012-06-12 12:41:16 +0000230 }
231 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000232 // Ignore
233 }
234 }
235 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000236
237 // Translate arrays with length 1 by picking the single element
238 if (actualType.isArray() && Array.getLength(o) == 1) {
239 return convert(type, Array.get(o, 0));
240 }
241
242 // Translate collections with size 1 by picking the single element
243 if (o instanceof Collection) {
244 Collection col = (Collection) o;
245 if (col.size() == 1)
246 return convert(type, col.iterator().next());
247 }
248
249 if (o instanceof Map) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000250 String key = null;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000251 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000252 Map<Object,Object> map = (Map) o;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000253 Object instance = resultType.newInstance();
254 for (Map.Entry e : map.entrySet()) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000255 key = (String) e.getKey();
256 try {
257 Field f = resultType.getField(key);
258 Object value = convert(f.getGenericType(), e.getValue());
259 f.set(instance, value);
260 }
261 catch (Exception ee) {
Stuart McCullochec47fe72012-09-19 12:56:05 +0000262
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000263 // We cannot find the key, so try the __extra field
264 Field f = resultType.getField("__extra");
265 Map<String,Object> extra = (Map<String,Object>) f.get(instance);
Stuart McCullochec47fe72012-09-19 12:56:05 +0000266 if (extra == null) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000267 extra = new HashMap<String,Object>();
268 f.set(instance, extra);
269 }
Stuart McCullochec47fe72012-09-19 12:56:05 +0000270 extra.put(key, convert(Object.class, e.getValue()));
271
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000272 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000273 }
274 return instance;
275 }
276 catch (Exception e) {
Stuart McCullochec47fe72012-09-19 12:56:05 +0000277 return error("No conversion found for " + o.getClass() + " to " + type + ", error " + e + " on key "
278 + key);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000279 }
280 }
281
Stuart McCullochbb014372012-06-07 21:57:32 +0000282 return error("No conversion found for " + o.getClass() + " to " + type);
283 }
284
285 private Number number(Object o) {
286 if (o instanceof Number)
287 return (Number) o;
288
289 if (o instanceof Boolean)
290 return ((Boolean) o).booleanValue() ? 1 : 0;
291
292 if (o instanceof Character)
293 return (int) ((Character) o).charValue();
294
295 if (o instanceof String) {
296 String s = (String) o;
297 try {
298 return Double.parseDouble(s);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000299 }
300 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000301 // Ignore
302 }
303 }
304 return null;
305 }
306
Stuart McCulloch2286f232012-06-15 13:27:53 +0000307 private Collection collection(Type collectionType, Class< ? extends Collection> rawClass, Object o)
308 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000309 Collection collection;
310 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
311 if (rawClass.isAssignableFrom(ArrayList.class))
312 collection = new ArrayList();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000313 else if (rawClass.isAssignableFrom(HashSet.class))
314 collection = new HashSet();
315 else if (rawClass.isAssignableFrom(TreeSet.class))
316 collection = new TreeSet();
317 else if (rawClass.isAssignableFrom(LinkedList.class))
318 collection = new LinkedList();
319 else if (rawClass.isAssignableFrom(Vector.class))
320 collection = new Vector();
321 else if (rawClass.isAssignableFrom(Stack.class))
322 collection = new Stack();
323 else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
324 collection = new ConcurrentLinkedQueue();
Stuart McCullochbb014372012-06-07 21:57:32 +0000325 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000326 return (Collection) error("Cannot find a suitable collection for the collection interface " + rawClass);
327 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000328 collection = rawClass.newInstance();
329
330 Type subType = Object.class;
331 if (collectionType instanceof ParameterizedType) {
332 ParameterizedType ptype = (ParameterizedType) collectionType;
333 subType = ptype.getActualTypeArguments()[0];
334 }
335
336 Collection input = toCollection(o);
337
338 for (Object i : input)
339 collection.add(convert(subType, i));
340
341 return collection;
342 }
343
Stuart McCulloch2286f232012-06-15 13:27:53 +0000344 private Map map(Type mapType, Class< ? extends Map< ? , ? >> rawClass, Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000345 Map result;
346 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
347 if (rawClass.isAssignableFrom(HashMap.class))
348 result = new HashMap();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000349 else if (rawClass.isAssignableFrom(TreeMap.class))
350 result = new TreeMap();
351 else if (rawClass.isAssignableFrom(ConcurrentHashMap.class))
352 result = new ConcurrentHashMap();
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000353 else {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000354 return (Map) error("Cannot find suitable map for map interface " + rawClass);
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000355 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000356 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000357 result = rawClass.newInstance();
358
Stuart McCulloch285034f2012-06-12 12:41:16 +0000359 Map< ? , ? > input = toMap(o);
Stuart McCullochbb014372012-06-07 21:57:32 +0000360
361 Type keyType = Object.class;
362 Type valueType = Object.class;
363 if (mapType instanceof ParameterizedType) {
364 ParameterizedType ptype = (ParameterizedType) mapType;
365 keyType = ptype.getActualTypeArguments()[0];
366 valueType = ptype.getActualTypeArguments()[1];
367 }
368
Stuart McCulloch285034f2012-06-12 12:41:16 +0000369 for (Map.Entry< ? , ? > entry : input.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000370 Object key = convert(keyType, entry.getKey());
371 Object value = convert(valueType, entry.getValue());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000372 if (key == null)
373 error("Key for map must not be null: " + input);
374 else
375 result.put(key, value);
Stuart McCullochbb014372012-06-07 21:57:32 +0000376 }
377
378 return result;
379 }
380
381 public Object array(Type type, Object o) throws Exception {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000382 Collection< ? > input = toCollection(o);
383 Class< ? > componentClass = getRawClass(type);
Stuart McCullochbb014372012-06-07 21:57:32 +0000384 Object array = Array.newInstance(componentClass, input.size());
385
386 int i = 0;
387 for (Object next : input) {
388 Array.set(array, i++, convert(type, next));
389 }
390 return array;
391 }
392
Stuart McCulloch285034f2012-06-12 12:41:16 +0000393 private Class< ? > getRawClass(Type type) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000394 if (type instanceof Class)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000395 return (Class< ? >) type;
Stuart McCullochbb014372012-06-07 21:57:32 +0000396
397 if (type instanceof ParameterizedType)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000398 return (Class< ? >) ((ParameterizedType) type).getRawType();
Stuart McCullochbb014372012-06-07 21:57:32 +0000399
400 if (type instanceof GenericArrayType) {
401 Type componentType = ((GenericArrayType) type).getGenericComponentType();
402 return Array.newInstance(getRawClass(componentType), 0).getClass();
403 }
404
405 if (type instanceof TypeVariable) {
406 Type componentType = ((TypeVariable) type).getBounds()[0];
407 return Array.newInstance(getRawClass(componentType), 0).getClass();
408 }
409
410 if (type instanceof WildcardType) {
411 Type componentType = ((WildcardType) type).getUpperBounds()[0];
412 return Array.newInstance(getRawClass(componentType), 0).getClass();
413 }
414
415 return Object.class;
416 }
417
Stuart McCulloch285034f2012-06-12 12:41:16 +0000418 public Collection< ? > toCollection(Object o) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000419 if (o instanceof Collection)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000420 return (Collection< ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000421
422 if (o.getClass().isArray()) {
423 if (o.getClass().getComponentType().isPrimitive()) {
424 int length = Array.getLength(o);
425 List<Object> result = new ArrayList<Object>(length);
426 for (int i = 0; i < length; i++) {
427 result.add(Array.get(o, i));
428 }
429 return result;
430 }
431 return Arrays.asList((Object[]) o);
432 }
433
434 return Arrays.asList(o);
435 }
436
Stuart McCulloch285034f2012-06-12 12:41:16 +0000437 public Map< ? , ? > toMap(Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000438 if (o instanceof Map)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000439 return (Map< ? , ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000440 Map result = new HashMap();
441 Field fields[] = o.getClass().getFields();
442 for (Field f : fields)
443 result.put(f.getName(), f.get(o));
444 if (result.isEmpty())
445 return null;
446
447 return result;
448 }
449
450 private Object error(String string) {
451 if (fatal)
452 throw new IllegalArgumentException(string);
453 return null;
454 }
455
456 public void setFatalIsException(boolean b) {
457 fatal = b;
458 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000459
Stuart McCulloch285034f2012-06-12 12:41:16 +0000460 public Converter hook(Type type, Hook hook) {
Stuart McCullochec47fe72012-09-19 12:56:05 +0000461 if (type != null) {
462 if (hooks == null)
463 hooks = new HashMap<Type,Converter.Hook>();
464 this.hooks.put(type, hook);
465 } else {
466 if (allHooks == null)
467 allHooks = new ArrayList<Converter.Hook>();
468 allHooks.add(hook);
469 }
470
Stuart McCulloch285034f2012-06-12 12:41:16 +0000471 return this;
472 }
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000473
474 /**
475 * Convert a map to an interface.
476 *
477 * @param interfc
478 * @param properties
479 * @return
480 */
481 public <T> T proxy(Class<T> interfc, final Map< ? , ? > properties) {
482 return (T) Proxy.newProxyInstance(interfc.getClassLoader(), new Class[] {
483 interfc
484 }, new InvocationHandler() {
485
486 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
487 Object o = properties.get(method.getName());
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000488 if (o == null)
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000489 o = properties.get(mangleMethodName(method.getName()));
490
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000491 return convert(method.getGenericReturnType(), o);
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000492 }
493
494 });
495 }
496
497 public static String mangleMethodName(String id) {
498 StringBuilder sb = new StringBuilder(id);
499 for (int i = 0; i < sb.length(); i++) {
500 char c = sb.charAt(i);
501 boolean twice = i < sb.length() - 1 && sb.charAt(i + 1) == c;
502 if (c == '$' || c == '_') {
503 if (twice)
504 sb.deleteCharAt(i + 1);
505 else if (c == '$')
506 sb.deleteCharAt(i--); // Remove dollars
507 else
508 sb.setCharAt(i, '.'); // Make _ into .
509 }
510 }
511 return sb.toString();
512 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000513
514 public static <T> T cnv(TypeReference<T> tr, Object source) throws Exception {
515 return new Converter().convert(tr, source);
516 }
517
518 public static <T> T cnv(Class<T> tr, Object source) throws Exception {
519 return new Converter().convert(tr, source);
520 }
521
522 public static Object cnv(Type tr, Object source) throws Exception {
523 return new Converter().convert(tr, source);
524 }
525
Stuart McCullochbb014372012-06-07 21:57:32 +0000526}