blob: 72b380ca2baab4661a48bc26063efa5c8208374e [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)) {
195 return Enum.valueOf((Class<Enum>) resultType, input);
196 }
197 if (resultType == Pattern.class) {
198 return Pattern.compile(input);
199 }
200
201 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000202 Constructor< ? > c = resultType.getConstructor(String.class);
Stuart McCullochbb014372012-06-07 21:57:32 +0000203 return c.newInstance(o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000204 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000205 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000206 try {
207 Method m = resultType.getMethod("valueOf", String.class);
208 if (Modifier.isStatic(m.getModifiers()))
209 return m.invoke(null, o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000210 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000211 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000212
213 if (resultType == Character.class && input.length() == 1)
214 return input.charAt(0);
215 }
216 Number n = number(o);
217 if (n != null) {
218 if (Enum.class.isAssignableFrom(resultType)) {
219 try {
220 Method values = resultType.getMethod("values");
221 Enum[] vs = (Enum[]) values.invoke(null);
222 int nn = n.intValue();
223 if (nn > 0 && nn < vs.length)
224 return vs[nn];
Stuart McCulloch285034f2012-06-12 12:41:16 +0000225 }
226 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000227 // Ignore
228 }
229 }
230 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000231
232 // Translate arrays with length 1 by picking the single element
233 if (actualType.isArray() && Array.getLength(o) == 1) {
234 return convert(type, Array.get(o, 0));
235 }
236
237 // Translate collections with size 1 by picking the single element
238 if (o instanceof Collection) {
239 Collection col = (Collection) o;
240 if (col.size() == 1)
241 return convert(type, col.iterator().next());
242 }
243
244 if (o instanceof Map) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000245 String key = null;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000246 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000247 Map<Object,Object> map = (Map) o;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000248 Object instance = resultType.newInstance();
249 for (Map.Entry e : map.entrySet()) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000250 key = (String) e.getKey();
251 try {
252 Field f = resultType.getField(key);
253 Object value = convert(f.getGenericType(), e.getValue());
254 f.set(instance, value);
255 }
256 catch (Exception ee) {
Stuart McCullochec47fe72012-09-19 12:56:05 +0000257
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000258 // We cannot find the key, so try the __extra field
259 Field f = resultType.getField("__extra");
260 Map<String,Object> extra = (Map<String,Object>) f.get(instance);
Stuart McCullochec47fe72012-09-19 12:56:05 +0000261 if (extra == null) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000262 extra = new HashMap<String,Object>();
263 f.set(instance, extra);
264 }
Stuart McCullochec47fe72012-09-19 12:56:05 +0000265 extra.put(key, convert(Object.class, e.getValue()));
266
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000267 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000268 }
269 return instance;
270 }
271 catch (Exception e) {
Stuart McCullochec47fe72012-09-19 12:56:05 +0000272 return error("No conversion found for " + o.getClass() + " to " + type + ", error " + e + " on key "
273 + key);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000274 }
275 }
276
Stuart McCullochbb014372012-06-07 21:57:32 +0000277 return error("No conversion found for " + o.getClass() + " to " + type);
278 }
279
280 private Number number(Object o) {
281 if (o instanceof Number)
282 return (Number) o;
283
284 if (o instanceof Boolean)
285 return ((Boolean) o).booleanValue() ? 1 : 0;
286
287 if (o instanceof Character)
288 return (int) ((Character) o).charValue();
289
290 if (o instanceof String) {
291 String s = (String) o;
292 try {
293 return Double.parseDouble(s);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000294 }
295 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000296 // Ignore
297 }
298 }
299 return null;
300 }
301
Stuart McCulloch2286f232012-06-15 13:27:53 +0000302 private Collection collection(Type collectionType, Class< ? extends Collection> rawClass, Object o)
303 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000304 Collection collection;
305 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
306 if (rawClass.isAssignableFrom(ArrayList.class))
307 collection = new ArrayList();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000308 else if (rawClass.isAssignableFrom(HashSet.class))
309 collection = new HashSet();
310 else if (rawClass.isAssignableFrom(TreeSet.class))
311 collection = new TreeSet();
312 else if (rawClass.isAssignableFrom(LinkedList.class))
313 collection = new LinkedList();
314 else if (rawClass.isAssignableFrom(Vector.class))
315 collection = new Vector();
316 else if (rawClass.isAssignableFrom(Stack.class))
317 collection = new Stack();
318 else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
319 collection = new ConcurrentLinkedQueue();
Stuart McCullochbb014372012-06-07 21:57:32 +0000320 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000321 return (Collection) error("Cannot find a suitable collection for the collection interface " + rawClass);
322 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000323 collection = rawClass.newInstance();
324
325 Type subType = Object.class;
326 if (collectionType instanceof ParameterizedType) {
327 ParameterizedType ptype = (ParameterizedType) collectionType;
328 subType = ptype.getActualTypeArguments()[0];
329 }
330
331 Collection input = toCollection(o);
332
333 for (Object i : input)
334 collection.add(convert(subType, i));
335
336 return collection;
337 }
338
Stuart McCulloch2286f232012-06-15 13:27:53 +0000339 private Map map(Type mapType, Class< ? extends Map< ? , ? >> rawClass, Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000340 Map result;
341 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
342 if (rawClass.isAssignableFrom(HashMap.class))
343 result = new HashMap();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000344 else if (rawClass.isAssignableFrom(TreeMap.class))
345 result = new TreeMap();
346 else if (rawClass.isAssignableFrom(ConcurrentHashMap.class))
347 result = new ConcurrentHashMap();
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000348 else {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000349 return (Map) error("Cannot find suitable map for map interface " + rawClass);
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000350 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000351 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000352 result = rawClass.newInstance();
353
Stuart McCulloch285034f2012-06-12 12:41:16 +0000354 Map< ? , ? > input = toMap(o);
Stuart McCullochbb014372012-06-07 21:57:32 +0000355
356 Type keyType = Object.class;
357 Type valueType = Object.class;
358 if (mapType instanceof ParameterizedType) {
359 ParameterizedType ptype = (ParameterizedType) mapType;
360 keyType = ptype.getActualTypeArguments()[0];
361 valueType = ptype.getActualTypeArguments()[1];
362 }
363
Stuart McCulloch285034f2012-06-12 12:41:16 +0000364 for (Map.Entry< ? , ? > entry : input.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000365 Object key = convert(keyType, entry.getKey());
366 Object value = convert(valueType, entry.getValue());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000367 if (key == null)
368 error("Key for map must not be null: " + input);
369 else
370 result.put(key, value);
Stuart McCullochbb014372012-06-07 21:57:32 +0000371 }
372
373 return result;
374 }
375
376 public Object array(Type type, Object o) throws Exception {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000377 Collection< ? > input = toCollection(o);
378 Class< ? > componentClass = getRawClass(type);
Stuart McCullochbb014372012-06-07 21:57:32 +0000379 Object array = Array.newInstance(componentClass, input.size());
380
381 int i = 0;
382 for (Object next : input) {
383 Array.set(array, i++, convert(type, next));
384 }
385 return array;
386 }
387
Stuart McCulloch285034f2012-06-12 12:41:16 +0000388 private Class< ? > getRawClass(Type type) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000389 if (type instanceof Class)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000390 return (Class< ? >) type;
Stuart McCullochbb014372012-06-07 21:57:32 +0000391
392 if (type instanceof ParameterizedType)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000393 return (Class< ? >) ((ParameterizedType) type).getRawType();
Stuart McCullochbb014372012-06-07 21:57:32 +0000394
395 if (type instanceof GenericArrayType) {
396 Type componentType = ((GenericArrayType) type).getGenericComponentType();
397 return Array.newInstance(getRawClass(componentType), 0).getClass();
398 }
399
400 if (type instanceof TypeVariable) {
401 Type componentType = ((TypeVariable) type).getBounds()[0];
402 return Array.newInstance(getRawClass(componentType), 0).getClass();
403 }
404
405 if (type instanceof WildcardType) {
406 Type componentType = ((WildcardType) type).getUpperBounds()[0];
407 return Array.newInstance(getRawClass(componentType), 0).getClass();
408 }
409
410 return Object.class;
411 }
412
Stuart McCulloch285034f2012-06-12 12:41:16 +0000413 public Collection< ? > toCollection(Object o) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000414 if (o instanceof Collection)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000415 return (Collection< ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000416
417 if (o.getClass().isArray()) {
418 if (o.getClass().getComponentType().isPrimitive()) {
419 int length = Array.getLength(o);
420 List<Object> result = new ArrayList<Object>(length);
421 for (int i = 0; i < length; i++) {
422 result.add(Array.get(o, i));
423 }
424 return result;
425 }
426 return Arrays.asList((Object[]) o);
427 }
428
429 return Arrays.asList(o);
430 }
431
Stuart McCulloch285034f2012-06-12 12:41:16 +0000432 public Map< ? , ? > toMap(Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000433 if (o instanceof Map)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000434 return (Map< ? , ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000435 Map result = new HashMap();
436 Field fields[] = o.getClass().getFields();
437 for (Field f : fields)
438 result.put(f.getName(), f.get(o));
439 if (result.isEmpty())
440 return null;
441
442 return result;
443 }
444
445 private Object error(String string) {
446 if (fatal)
447 throw new IllegalArgumentException(string);
448 return null;
449 }
450
451 public void setFatalIsException(boolean b) {
452 fatal = b;
453 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000454
Stuart McCulloch285034f2012-06-12 12:41:16 +0000455 public Converter hook(Type type, Hook hook) {
Stuart McCullochec47fe72012-09-19 12:56:05 +0000456 if (type != null) {
457 if (hooks == null)
458 hooks = new HashMap<Type,Converter.Hook>();
459 this.hooks.put(type, hook);
460 } else {
461 if (allHooks == null)
462 allHooks = new ArrayList<Converter.Hook>();
463 allHooks.add(hook);
464 }
465
Stuart McCulloch285034f2012-06-12 12:41:16 +0000466 return this;
467 }
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000468
469 /**
470 * Convert a map to an interface.
471 *
472 * @param interfc
473 * @param properties
474 * @return
475 */
476 public <T> T proxy(Class<T> interfc, final Map< ? , ? > properties) {
477 return (T) Proxy.newProxyInstance(interfc.getClassLoader(), new Class[] {
478 interfc
479 }, new InvocationHandler() {
480
481 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
482 Object o = properties.get(method.getName());
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000483 if (o == null)
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000484 o = properties.get(mangleMethodName(method.getName()));
485
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000486 return convert(method.getGenericReturnType(), o);
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000487 }
488
489 });
490 }
491
492 public static String mangleMethodName(String id) {
493 StringBuilder sb = new StringBuilder(id);
494 for (int i = 0; i < sb.length(); i++) {
495 char c = sb.charAt(i);
496 boolean twice = i < sb.length() - 1 && sb.charAt(i + 1) == c;
497 if (c == '$' || c == '_') {
498 if (twice)
499 sb.deleteCharAt(i + 1);
500 else if (c == '$')
501 sb.deleteCharAt(i--); // Remove dollars
502 else
503 sb.setCharAt(i, '.'); // Make _ into .
504 }
505 }
506 return sb.toString();
507 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000508
509 public static <T> T cnv(TypeReference<T> tr, Object source) throws Exception {
510 return new Converter().convert(tr, source);
511 }
512
513 public static <T> T cnv(Class<T> tr, Object source) throws Exception {
514 return new Converter().convert(tr, source);
515 }
516
517 public static Object cnv(Type tr, Object source) throws Exception {
518 return new Converter().convert(tr, source);
519 }
520
Stuart McCullochbb014372012-06-07 21:57:32 +0000521}