blob: f22f840711795f79e7cd6a5d0391620ee8c8646c [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 {
35 return (T) convert( type.getType(), o);
36 }
37
Stuart McCullochbb014372012-06-07 21:57:32 +000038 public Object convert(Type type, Object o) throws Exception {
39 if (o == null)
40 return null; // compatible with any
41
Stuart McCulloch285034f2012-06-12 12:41:16 +000042
43 Hook hook = hooks.get(type);
Stuart McCulloch2286f232012-06-15 13:27:53 +000044 if (hook != null) {
Stuart McCulloch285034f2012-06-12 12:41:16 +000045 Object value = hook.convert(type, o);
Stuart McCulloch2286f232012-06-15 13:27:53 +000046 if (value != null)
Stuart McCulloch285034f2012-06-12 12:41:16 +000047 return value;
48 }
Stuart McCulloch2286f232012-06-15 13:27:53 +000049
Stuart McCullochbb014372012-06-07 21:57:32 +000050 Class resultType = getRawClass(type);
Stuart McCulloch285034f2012-06-12 12:41:16 +000051 Class< ? > actualType = o.getClass();
Stuart McCullochbb014372012-06-07 21:57:32 +000052
53 // We can always make a string
54
55 if (resultType == String.class) {
56 if (actualType.isArray()) {
57 if (actualType == char[].class)
58 return new String((char[]) o);
59 if (actualType == byte[].class)
60 return Base64.encodeBase64((byte[]) o);
61 int l = Array.getLength(o);
62 StringBuilder sb = new StringBuilder("[");
63 String del = "";
64 for (int i = 0; i < l; i++) {
65 sb.append(del);
66 del = ",";
67 sb.append(convert(String.class, Array.get(o, i)));
68 }
69 sb.append("]");
70 return sb.toString();
71 }
72 return o.toString();
73 }
74
75 if (Collection.class.isAssignableFrom(resultType))
76 return collection(type, resultType, o);
77
78 if (Map.class.isAssignableFrom(resultType))
79 return map(type, resultType, o);
80
81 if (type instanceof GenericArrayType) {
82 GenericArrayType gType = (GenericArrayType) type;
83 return array(gType.getGenericComponentType(), o);
84 }
85
86 if (resultType.isArray()) {
87 if (actualType == String.class) {
88 String s = (String) o;
89 if (byte[].class == resultType)
90 return Base64.decodeBase64(s);
91
92 if (char[].class == resultType)
93 return s.toCharArray();
94 }
95 if (byte[].class == resultType) {
96 // Sometimes classes implement toByteArray
97 try {
98 Method m = actualType.getMethod("toByteArray");
99 if (m.getReturnType() == byte[].class)
100 return m.invoke(o);
101
Stuart McCulloch285034f2012-06-12 12:41:16 +0000102 }
103 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000104 // Ignore
105 }
106 }
107
108 return array(resultType.getComponentType(), o);
109 }
110
Stuart McCulloch285034f2012-06-12 12:41:16 +0000111 if (resultType.isAssignableFrom(o.getClass()))
112 return o;
113
Stuart McCullochbb014372012-06-07 21:57:32 +0000114 // Simple type coercion
115
116 if (resultType == boolean.class || resultType == Boolean.class) {
117 if (actualType == boolean.class || actualType == Boolean.class)
118 return o;
119 Number n = number(o);
120 if (n != null)
121 return n.longValue() == 0 ? false : true;
122
123 resultType = Boolean.class;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000124 } else if (resultType == byte.class || resultType == Byte.class) {
125 Number n = number(o);
126 if (n != null)
127 return n.byteValue();
128 resultType = Byte.class;
129 } else if (resultType == char.class || resultType == Character.class) {
130 Number n = number(o);
131 if (n != null)
132 return (char) n.shortValue();
133 resultType = Character.class;
134 } else if (resultType == short.class || resultType == Short.class) {
135 Number n = number(o);
136 if (n != null)
137 return n.shortValue();
138
139 resultType = Short.class;
140 } else if (resultType == int.class || resultType == Integer.class) {
141 Number n = number(o);
142 if (n != null)
143 return n.intValue();
144
145 resultType = Integer.class;
146 } else if (resultType == long.class || resultType == Long.class) {
147 Number n = number(o);
148 if (n != null)
149 return n.longValue();
150
151 resultType = Long.class;
152 } else if (resultType == float.class || resultType == Float.class) {
153 Number n = number(o);
154 if (n != null)
155 return n.floatValue();
156
157 resultType = Float.class;
158 } else if (resultType == double.class || resultType == Double.class) {
159 Number n = number(o);
160 if (n != null)
161 return n.doubleValue();
162
163 resultType = Double.class;
Stuart McCullochbb014372012-06-07 21:57:32 +0000164 }
165
166 assert !resultType.isPrimitive();
167
168 if (actualType == String.class) {
169 String input = (String) o;
170 if (resultType == char[].class)
171 return input.toCharArray();
172
173 if (resultType == byte[].class)
174 return Base64.decodeBase64(input);
175
176 if (Enum.class.isAssignableFrom(resultType)) {
177 return Enum.valueOf((Class<Enum>) resultType, input);
178 }
179 if (resultType == Pattern.class) {
180 return Pattern.compile(input);
181 }
182
183 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000184 Constructor< ? > c = resultType.getConstructor(String.class);
Stuart McCullochbb014372012-06-07 21:57:32 +0000185 return c.newInstance(o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000186 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000187 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000188 try {
189 Method m = resultType.getMethod("valueOf", String.class);
190 if (Modifier.isStatic(m.getModifiers()))
191 return m.invoke(null, o.toString());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000192 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000193 catch (Throwable t) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000194
195 if (resultType == Character.class && input.length() == 1)
196 return input.charAt(0);
197 }
198 Number n = number(o);
199 if (n != null) {
200 if (Enum.class.isAssignableFrom(resultType)) {
201 try {
202 Method values = resultType.getMethod("values");
203 Enum[] vs = (Enum[]) values.invoke(null);
204 int nn = n.intValue();
205 if (nn > 0 && nn < vs.length)
206 return vs[nn];
Stuart McCulloch285034f2012-06-12 12:41:16 +0000207 }
208 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000209 // Ignore
210 }
211 }
212 }
Stuart McCulloch285034f2012-06-12 12:41:16 +0000213
214 // Translate arrays with length 1 by picking the single element
215 if (actualType.isArray() && Array.getLength(o) == 1) {
216 return convert(type, Array.get(o, 0));
217 }
218
219 // Translate collections with size 1 by picking the single element
220 if (o instanceof Collection) {
221 Collection col = (Collection) o;
222 if (col.size() == 1)
223 return convert(type, col.iterator().next());
224 }
225
226 if (o instanceof Map) {
227 try {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000228 Map<Object,Object> map = (Map) o;
Stuart McCulloch285034f2012-06-12 12:41:16 +0000229 Object instance = resultType.newInstance();
230 for (Map.Entry e : map.entrySet()) {
231 String key = (String) e.getKey();
232 Field f = resultType.getField(key);
233 Object value = convert(f.getGenericType(), e.getValue());
234 f.set(instance, value);
235 }
236 return instance;
237 }
238 catch (Exception e) {
239 // fall through
240 }
241 }
242
Stuart McCullochbb014372012-06-07 21:57:32 +0000243 return error("No conversion found for " + o.getClass() + " to " + type);
244 }
245
246 private Number number(Object o) {
247 if (o instanceof Number)
248 return (Number) o;
249
250 if (o instanceof Boolean)
251 return ((Boolean) o).booleanValue() ? 1 : 0;
252
253 if (o instanceof Character)
254 return (int) ((Character) o).charValue();
255
256 if (o instanceof String) {
257 String s = (String) o;
258 try {
259 return Double.parseDouble(s);
Stuart McCulloch285034f2012-06-12 12:41:16 +0000260 }
261 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000262 // Ignore
263 }
264 }
265 return null;
266 }
267
Stuart McCulloch2286f232012-06-15 13:27:53 +0000268 private Collection collection(Type collectionType, Class< ? extends Collection> rawClass, Object o)
269 throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000270 Collection collection;
271 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
272 if (rawClass.isAssignableFrom(ArrayList.class))
273 collection = new ArrayList();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000274 else if (rawClass.isAssignableFrom(HashSet.class))
275 collection = new HashSet();
276 else if (rawClass.isAssignableFrom(TreeSet.class))
277 collection = new TreeSet();
278 else if (rawClass.isAssignableFrom(LinkedList.class))
279 collection = new LinkedList();
280 else if (rawClass.isAssignableFrom(Vector.class))
281 collection = new Vector();
282 else if (rawClass.isAssignableFrom(Stack.class))
283 collection = new Stack();
284 else if (rawClass.isAssignableFrom(ConcurrentLinkedQueue.class))
285 collection = new ConcurrentLinkedQueue();
Stuart McCullochbb014372012-06-07 21:57:32 +0000286 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000287 return (Collection) error("Cannot find a suitable collection for the collection interface " + rawClass);
288 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000289 collection = rawClass.newInstance();
290
291 Type subType = Object.class;
292 if (collectionType instanceof ParameterizedType) {
293 ParameterizedType ptype = (ParameterizedType) collectionType;
294 subType = ptype.getActualTypeArguments()[0];
295 }
296
297 Collection input = toCollection(o);
298
299 for (Object i : input)
300 collection.add(convert(subType, i));
301
302 return collection;
303 }
304
Stuart McCulloch2286f232012-06-15 13:27:53 +0000305 private Map map(Type mapType, Class< ? extends Map< ? , ? >> rawClass, Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000306 Map result;
307 if (rawClass.isInterface() || Modifier.isAbstract(rawClass.getModifiers())) {
308 if (rawClass.isAssignableFrom(HashMap.class))
309 result = new HashMap();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000310 else if (rawClass.isAssignableFrom(TreeMap.class))
311 result = new TreeMap();
312 else if (rawClass.isAssignableFrom(ConcurrentHashMap.class))
313 result = new ConcurrentHashMap();
Stuart McCullochbb014372012-06-07 21:57:32 +0000314 else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000315 return (Map) error("Cannot find suitable map for map interface " + rawClass);
316 } else
Stuart McCullochbb014372012-06-07 21:57:32 +0000317 result = rawClass.newInstance();
318
Stuart McCulloch285034f2012-06-12 12:41:16 +0000319 Map< ? , ? > input = toMap(o);
Stuart McCullochbb014372012-06-07 21:57:32 +0000320
321 Type keyType = Object.class;
322 Type valueType = Object.class;
323 if (mapType instanceof ParameterizedType) {
324 ParameterizedType ptype = (ParameterizedType) mapType;
325 keyType = ptype.getActualTypeArguments()[0];
326 valueType = ptype.getActualTypeArguments()[1];
327 }
328
Stuart McCulloch285034f2012-06-12 12:41:16 +0000329 for (Map.Entry< ? , ? > entry : input.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000330 Object key = convert(keyType, entry.getKey());
331 Object value = convert(valueType, entry.getValue());
Stuart McCulloch285034f2012-06-12 12:41:16 +0000332 if (key == null)
333 error("Key for map must not be null: " + input);
334 else
335 result.put(key, value);
Stuart McCullochbb014372012-06-07 21:57:32 +0000336 }
337
338 return result;
339 }
340
341 public Object array(Type type, Object o) throws Exception {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000342 Collection< ? > input = toCollection(o);
343 Class< ? > componentClass = getRawClass(type);
Stuart McCullochbb014372012-06-07 21:57:32 +0000344 Object array = Array.newInstance(componentClass, input.size());
345
346 int i = 0;
347 for (Object next : input) {
348 Array.set(array, i++, convert(type, next));
349 }
350 return array;
351 }
352
Stuart McCulloch285034f2012-06-12 12:41:16 +0000353 private Class< ? > getRawClass(Type type) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000354 if (type instanceof Class)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000355 return (Class< ? >) type;
Stuart McCullochbb014372012-06-07 21:57:32 +0000356
357 if (type instanceof ParameterizedType)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000358 return (Class< ? >) ((ParameterizedType) type).getRawType();
Stuart McCullochbb014372012-06-07 21:57:32 +0000359
360 if (type instanceof GenericArrayType) {
361 Type componentType = ((GenericArrayType) type).getGenericComponentType();
362 return Array.newInstance(getRawClass(componentType), 0).getClass();
363 }
364
365 if (type instanceof TypeVariable) {
366 Type componentType = ((TypeVariable) type).getBounds()[0];
367 return Array.newInstance(getRawClass(componentType), 0).getClass();
368 }
369
370 if (type instanceof WildcardType) {
371 Type componentType = ((WildcardType) type).getUpperBounds()[0];
372 return Array.newInstance(getRawClass(componentType), 0).getClass();
373 }
374
375 return Object.class;
376 }
377
Stuart McCulloch285034f2012-06-12 12:41:16 +0000378 public Collection< ? > toCollection(Object o) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000379 if (o instanceof Collection)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000380 return (Collection< ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000381
382 if (o.getClass().isArray()) {
383 if (o.getClass().getComponentType().isPrimitive()) {
384 int length = Array.getLength(o);
385 List<Object> result = new ArrayList<Object>(length);
386 for (int i = 0; i < length; i++) {
387 result.add(Array.get(o, i));
388 }
389 return result;
390 }
391 return Arrays.asList((Object[]) o);
392 }
393
394 return Arrays.asList(o);
395 }
396
Stuart McCulloch285034f2012-06-12 12:41:16 +0000397 public Map< ? , ? > toMap(Object o) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +0000398 if (o instanceof Map)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000399 return (Map< ? , ? >) o;
Stuart McCullochbb014372012-06-07 21:57:32 +0000400 Map result = new HashMap();
401 Field fields[] = o.getClass().getFields();
402 for (Field f : fields)
403 result.put(f.getName(), f.get(o));
404 if (result.isEmpty())
405 return null;
406
407 return result;
408 }
409
410 private Object error(String string) {
411 if (fatal)
412 throw new IllegalArgumentException(string);
413 return null;
414 }
415
416 public void setFatalIsException(boolean b) {
417 fatal = b;
418 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000419
Stuart McCulloch285034f2012-06-12 12:41:16 +0000420 public Converter hook(Type type, Hook hook) {
421 this.hooks.put(type, hook);
422 return this;
423 }
Stuart McCullochbb014372012-06-07 21:57:32 +0000424}