blob: a227d71e5395eb51462380eb1dfd40142670dfa8 [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 McCulloch2286f232012-06-15 13:27:53 +000025 Map<Type,Hook> hooks = new HashMap<Type,Converter.Hook>();
Stuart McCullochbb014372012-06-07 21:57:32 +000026
27 public <T> T convert(Class<T> type, Object o) throws Exception {
Stuart McCulloch285034f2012-06-12 12:41:16 +000028 // Is it a compatible type?
29 if (type.isAssignableFrom(o.getClass()))
30 return (T) o;
Stuart McCullochbb014372012-06-07 21:57:32 +000031 return (T) convert((Type) type, o);
32 }
33
Stuart McCulloch2286f232012-06-15 13:27:53 +000034 public <T> T convert(TypeReference<T> type, Object o) throws Exception {
Stuart McCulloch81d48de2012-06-29 19:23:09 +000035 return (T) convert(type.getType(), o);
Stuart McCulloch2286f232012-06-15 13:27:53 +000036 }
Stuart McCullochbb014372012-06-07 21:57:32 +000037
Stuart McCulloch81d48de2012-06-29 19:23:09 +000038 public Object convert(Type type, Object o) throws Exception {
39 Class resultType = getRawClass(type);
40 if (o == null) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +000041 if (resultType.isPrimitive() || Number.class.isAssignableFrom(resultType))
42 return convert(type, 0);
Stuart McCulloch81d48de2012-06-29 19:23:09 +000043
44 return null; // compatible with any
45 }
46
Stuart McCulloch285034f2012-06-12 12:41:16 +000047 Hook hook = hooks.get(type);
Stuart McCulloch2286f232012-06-15 13:27:53 +000048 if (hook != null) {
Stuart McCulloch285034f2012-06-12 12:41:16 +000049 Object value = hook.convert(type, o);
Stuart McCulloch2286f232012-06-15 13:27:53 +000050 if (value != null)
Stuart McCulloch285034f2012-06-12 12:41:16 +000051 return value;
52 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000053
Stuart McCulloch285034f2012-06-12 12:41:16 +000054 Class< ? > actualType = o.getClass();
Stuart McCullochbb014372012-06-07 21:57:32 +000055
56 // We can always make a string
57
58 if (resultType == String.class) {
59 if (actualType.isArray()) {
60 if (actualType == char[].class)
61 return new String((char[]) o);
62 if (actualType == byte[].class)
63 return Base64.encodeBase64((byte[]) o);
64 int l = Array.getLength(o);
65 StringBuilder sb = new StringBuilder("[");
66 String del = "";
67 for (int i = 0; i < l; i++) {
68 sb.append(del);
69 del = ",";
70 sb.append(convert(String.class, Array.get(o, i)));
71 }
72 sb.append("]");
73 return sb.toString();
74 }
75 return o.toString();
76 }
77
78 if (Collection.class.isAssignableFrom(resultType))
79 return collection(type, resultType, o);
80
81 if (Map.class.isAssignableFrom(resultType))
82 return map(type, resultType, o);
83
84 if (type instanceof GenericArrayType) {
85 GenericArrayType gType = (GenericArrayType) type;
86 return array(gType.getGenericComponentType(), o);
87 }
88
89 if (resultType.isArray()) {
90 if (actualType == String.class) {
91 String s = (String) o;
92 if (byte[].class == resultType)
93 return Base64.decodeBase64(s);
94
95 if (char[].class == resultType)
96 return s.toCharArray();
97 }
98 if (byte[].class == resultType) {
99 // Sometimes classes implement toByteArray
100 try {
101 Method m = actualType.getMethod("toByteArray");
102 if (m.getReturnType() == byte[].class)
103 return m.invoke(o);
104
Stuart McCulloch285034f2012-06-12 12:41:16 +0000105 }
106 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000107 // Ignore
108 }
109 }
110
111 return array(resultType.getComponentType(), o);
112 }
113
Stuart McCulloch285034f2012-06-12 12:41:16 +0000114 if (resultType.isAssignableFrom(o.getClass()))
115 return o;
116
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000117 if (Map.class.isAssignableFrom(actualType) && resultType.isInterface()) {
118 return proxy(resultType, (Map) o);
119 }
120
Stuart McCullochbb014372012-06-07 21:57:32 +0000121 // Simple type coercion
122
123 if (resultType == boolean.class || resultType == Boolean.class) {
124 if (actualType == boolean.class || actualType == Boolean.class)
125 return o;
126 Number n = number(o);
127 if (n != null)
128 return n.longValue() == 0 ? false : true;
129
130 resultType = Boolean.class;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000131 } else if (resultType == byte.class || resultType == Byte.class) {
132 Number n = number(o);
133 if (n != null)
134 return n.byteValue();
135 resultType = Byte.class;
136 } else if (resultType == char.class || resultType == Character.class) {
137 Number n = number(o);
138 if (n != null)
139 return (char) n.shortValue();
140 resultType = Character.class;
141 } else if (resultType == short.class || resultType == Short.class) {
142 Number n = number(o);
143 if (n != null)
144 return n.shortValue();
145
146 resultType = Short.class;
147 } else if (resultType == int.class || resultType == Integer.class) {
148 Number n = number(o);
149 if (n != null)
150 return n.intValue();
151
152 resultType = Integer.class;
153 } else if (resultType == long.class || resultType == Long.class) {
154 Number n = number(o);
155 if (n != null)
156 return n.longValue();
157
158 resultType = Long.class;
159 } else if (resultType == float.class || resultType == Float.class) {
160 Number n = number(o);
161 if (n != null)
162 return n.floatValue();
163
164 resultType = Float.class;
165 } else if (resultType == double.class || resultType == Double.class) {
166 Number n = number(o);
167 if (n != null)
168 return n.doubleValue();
169
170 resultType = Double.class;
Stuart McCullochbb014372012-06-07 21:57:32 +0000171 }
172
173 assert !resultType.isPrimitive();
174
175 if (actualType == String.class) {
176 String input = (String) o;
177 if (resultType == char[].class)
178 return input.toCharArray();
179
180 if (resultType == byte[].class)
181 return Base64.decodeBase64(input);
182
183 if (Enum.class.isAssignableFrom(resultType)) {
184 return Enum.valueOf((Class<Enum>) resultType, input);
185 }
186 if (resultType == Pattern.class) {
187 return Pattern.compile(input);
188 }
189
190 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000191 Constructor< ? > c = resultType.getConstructor(String.class);
Stuart McCullochbb014372012-06-07 21:57:32 +0000192 return c.newInstance(o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000193 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000194 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000195 try {
196 Method m = resultType.getMethod("valueOf", String.class);
197 if (Modifier.isStatic(m.getModifiers()))
198 return m.invoke(null, o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000199 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000200 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000201
202 if (resultType == Character.class && input.length() == 1)
203 return input.charAt(0);
204 }
205 Number n = number(o);
206 if (n != null) {
207 if (Enum.class.isAssignableFrom(resultType)) {
208 try {
209 Method values = resultType.getMethod("values");
210 Enum[] vs = (Enum[]) values.invoke(null);
211 int nn = n.intValue();
212 if (nn > 0 && nn < vs.length)
213 return vs[nn];
Stuart McCulloch285034f2012-06-12 12:41:16 +0000214 }
215 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000216 // Ignore
217 }
218 }
219 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000220
221 // Translate arrays with length 1 by picking the single element
222 if (actualType.isArray() && Array.getLength(o) == 1) {
223 return convert(type, Array.get(o, 0));
224 }
225
226 // Translate collections with size 1 by picking the single element
227 if (o instanceof Collection) {
228 Collection col = (Collection) o;
229 if (col.size() == 1)
230 return convert(type, col.iterator().next());
231 }
232
233 if (o instanceof Map) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000234 String key = null;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000235 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000236 Map<Object,Object> map = (Map) o;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000237 Object instance = resultType.newInstance();
238 for (Map.Entry e : map.entrySet()) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000239 key = (String) e.getKey();
240 try {
241 Field f = resultType.getField(key);
242 Object value = convert(f.getGenericType(), e.getValue());
243 f.set(instance, value);
244 }
245 catch (Exception ee) {
246
247 // We cannot find the key, so try the __extra field
248 Field f = resultType.getField("__extra");
249 Map<String,Object> extra = (Map<String,Object>) f.get(instance);
250 if ( extra == null) {
251 extra = new HashMap<String,Object>();
252 f.set(instance, extra);
253 }
254 extra.put(key, convert(Object.class,e.getValue()));
255
256 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000257 }
258 return instance;
259 }
260 catch (Exception e) {
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000261 return error("No conversion found for " + o.getClass() + " to " + type + ", error " + e + " on key " + key);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000262 }
263 }
264
Stuart McCullochbb014372012-06-07 21:57:32 +0000265 return error("No conversion found for " + o.getClass() + " to " + type);
266 }
267
268 private Number number(Object o) {
269 if (o instanceof Number)
270 return (Number) o;
271
272 if (o instanceof Boolean)
273 return ((Boolean) o).booleanValue() ? 1 : 0;
274
275 if (o instanceof Character)
276 return (int) ((Character) o).charValue();
277
278 if (o instanceof String) {
279 String s = (String) o;
280 try {
281 return Double.parseDouble(s);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000282 }
283 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000284 // Ignore
285 }
286 }
287 return null;
288 }
289
Stuart McCulloch2286f232012-06-15 13:27:53 +0000290 private Collection collection(Type collectionType, Class< ? extends Collection> rawClass, Object o)
291 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000292 Collection collection;
293 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
294 if (rawClass.isAssignableFrom(ArrayList.class))
295 collection = new ArrayList();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000296 else if (rawClass.isAssignableFrom(HashSet.class))
297 collection = new HashSet();
298 else if (rawClass.isAssignableFrom(TreeSet.class))
299 collection = new TreeSet();
300 else if (rawClass.isAssignableFrom(LinkedList.class))
301 collection = new LinkedList();
302 else if (rawClass.isAssignableFrom(Vector.class))
303 collection = new Vector();
304 else if (rawClass.isAssignableFrom(Stack.class))
305 collection = new Stack();
306 else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
307 collection = new ConcurrentLinkedQueue();
Stuart McCullochbb014372012-06-07 21:57:32 +0000308 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000309 return (Collection) error("Cannot find a suitable collection for the collection interface " + rawClass);
310 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000311 collection = rawClass.newInstance();
312
313 Type subType = Object.class;
314 if (collectionType instanceof ParameterizedType) {
315 ParameterizedType ptype = (ParameterizedType) collectionType;
316 subType = ptype.getActualTypeArguments()[0];
317 }
318
319 Collection input = toCollection(o);
320
321 for (Object i : input)
322 collection.add(convert(subType, i));
323
324 return collection;
325 }
326
Stuart McCulloch2286f232012-06-15 13:27:53 +0000327 private Map map(Type mapType, Class< ? extends Map< ? , ? >> rawClass, Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000328 Map result;
329 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
330 if (rawClass.isAssignableFrom(HashMap.class))
331 result = new HashMap();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000332 else if (rawClass.isAssignableFrom(TreeMap.class))
333 result = new TreeMap();
334 else if (rawClass.isAssignableFrom(ConcurrentHashMap.class))
335 result = new ConcurrentHashMap();
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000336 else {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000337 return (Map) error("Cannot find suitable map for map interface " + rawClass);
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000338 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000339 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000340 result = rawClass.newInstance();
341
Stuart McCulloch285034f2012-06-12 12:41:16 +0000342 Map< ? , ? > input = toMap(o);
Stuart McCullochbb014372012-06-07 21:57:32 +0000343
344 Type keyType = Object.class;
345 Type valueType = Object.class;
346 if (mapType instanceof ParameterizedType) {
347 ParameterizedType ptype = (ParameterizedType) mapType;
348 keyType = ptype.getActualTypeArguments()[0];
349 valueType = ptype.getActualTypeArguments()[1];
350 }
351
Stuart McCulloch285034f2012-06-12 12:41:16 +0000352 for (Map.Entry< ? , ? > entry : input.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000353 Object key = convert(keyType, entry.getKey());
354 Object value = convert(valueType, entry.getValue());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000355 if (key == null)
356 error("Key for map must not be null: " + input);
357 else
358 result.put(key, value);
Stuart McCullochbb014372012-06-07 21:57:32 +0000359 }
360
361 return result;
362 }
363
364 public Object array(Type type, Object o) throws Exception {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000365 Collection< ? > input = toCollection(o);
366 Class< ? > componentClass = getRawClass(type);
Stuart McCullochbb014372012-06-07 21:57:32 +0000367 Object array = Array.newInstance(componentClass, input.size());
368
369 int i = 0;
370 for (Object next : input) {
371 Array.set(array, i++, convert(type, next));
372 }
373 return array;
374 }
375
Stuart McCulloch285034f2012-06-12 12:41:16 +0000376 private Class< ? > getRawClass(Type type) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000377 if (type instanceof Class)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000378 return (Class< ? >) type;
Stuart McCullochbb014372012-06-07 21:57:32 +0000379
380 if (type instanceof ParameterizedType)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000381 return (Class< ? >) ((ParameterizedType) type).getRawType();
Stuart McCullochbb014372012-06-07 21:57:32 +0000382
383 if (type instanceof GenericArrayType) {
384 Type componentType = ((GenericArrayType) type).getGenericComponentType();
385 return Array.newInstance(getRawClass(componentType), 0).getClass();
386 }
387
388 if (type instanceof TypeVariable) {
389 Type componentType = ((TypeVariable) type).getBounds()[0];
390 return Array.newInstance(getRawClass(componentType), 0).getClass();
391 }
392
393 if (type instanceof WildcardType) {
394 Type componentType = ((WildcardType) type).getUpperBounds()[0];
395 return Array.newInstance(getRawClass(componentType), 0).getClass();
396 }
397
398 return Object.class;
399 }
400
Stuart McCulloch285034f2012-06-12 12:41:16 +0000401 public Collection< ? > toCollection(Object o) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000402 if (o instanceof Collection)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000403 return (Collection< ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000404
405 if (o.getClass().isArray()) {
406 if (o.getClass().getComponentType().isPrimitive()) {
407 int length = Array.getLength(o);
408 List<Object> result = new ArrayList<Object>(length);
409 for (int i = 0; i < length; i++) {
410 result.add(Array.get(o, i));
411 }
412 return result;
413 }
414 return Arrays.asList((Object[]) o);
415 }
416
417 return Arrays.asList(o);
418 }
419
Stuart McCulloch285034f2012-06-12 12:41:16 +0000420 public Map< ? , ? > toMap(Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000421 if (o instanceof Map)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000422 return (Map< ? , ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000423 Map result = new HashMap();
424 Field fields[] = o.getClass().getFields();
425 for (Field f : fields)
426 result.put(f.getName(), f.get(o));
427 if (result.isEmpty())
428 return null;
429
430 return result;
431 }
432
433 private Object error(String string) {
434 if (fatal)
435 throw new IllegalArgumentException(string);
436 return null;
437 }
438
439 public void setFatalIsException(boolean b) {
440 fatal = b;
441 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000442
Stuart McCulloch285034f2012-06-12 12:41:16 +0000443 public Converter hook(Type type, Hook hook) {
444 this.hooks.put(type, hook);
445 return this;
446 }
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000447
448 /**
449 * Convert a map to an interface.
450 *
451 * @param interfc
452 * @param properties
453 * @return
454 */
455 public <T> T proxy(Class<T> interfc, final Map< ? , ? > properties) {
456 return (T) Proxy.newProxyInstance(interfc.getClassLoader(), new Class[] {
457 interfc
458 }, new InvocationHandler() {
459
460 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
461 Object o = properties.get(method.getName());
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000462 if (o == null)
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000463 o = properties.get(mangleMethodName(method.getName()));
464
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000465 return convert(method.getGenericReturnType(), o);
Stuart McCulloch81d48de2012-06-29 19:23:09 +0000466 }
467
468 });
469 }
470
471 public static String mangleMethodName(String id) {
472 StringBuilder sb = new StringBuilder(id);
473 for (int i = 0; i < sb.length(); i++) {
474 char c = sb.charAt(i);
475 boolean twice = i < sb.length() - 1 && sb.charAt(i + 1) == c;
476 if (c == '$' || c == '_') {
477 if (twice)
478 sb.deleteCharAt(i + 1);
479 else if (c == '$')
480 sb.deleteCharAt(i--); // Remove dollars
481 else
482 sb.setCharAt(i, '.'); // Make _ into .
483 }
484 }
485 return sb.toString();
486 }
Stuart McCulloch39cc9ac2012-07-16 13:43:38 +0000487
488 public static <T> T cnv(TypeReference<T> tr, Object source) throws Exception {
489 return new Converter().convert(tr, source);
490 }
491
492 public static <T> T cnv(Class<T> tr, Object source) throws Exception {
493 return new Converter().convert(tr, source);
494 }
495
496 public static Object cnv(Type tr, Object source) throws Exception {
497 return new Converter().convert(tr, source);
498 }
499
Stuart McCullochbb014372012-06-07 21:57:32 +0000500}