1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.sisu.plexus;
20
21 import javax.annotation.Priority;
22 import javax.inject.Inject;
23 import javax.inject.Singleton;
24
25 import java.io.StringReader;
26 import java.lang.reflect.Array;
27 import java.lang.reflect.InvocationTargetException;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.Properties;
33
34 import com.google.inject.Injector;
35 import com.google.inject.Key;
36 import com.google.inject.Module;
37 import com.google.inject.TypeLiteral;
38 import com.google.inject.spi.TypeConverter;
39 import com.google.inject.spi.TypeConverterBinding;
40 import org.apache.maven.api.xml.XmlNode;
41 import org.apache.maven.internal.xml.XmlNodeBuilder;
42 import org.codehaus.plexus.util.xml.Xpp3Dom;
43 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
44 import org.codehaus.plexus.util.xml.pull.MXParser;
45 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
46 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
47 import org.eclipse.sisu.bean.BeanProperties;
48 import org.eclipse.sisu.bean.BeanProperty;
49 import org.eclipse.sisu.inject.Logs;
50 import org.eclipse.sisu.inject.TypeArguments;
51
52
53
54
55 @Singleton
56 @Priority(10)
57 public final class PlexusXmlBeanConverter implements PlexusBeanConverter {
58
59
60
61
62 private static final String CONVERSION_ERROR = "Cannot convert: \"%s\" to: %s";
63
64
65
66
67
68 private final Collection<TypeConverterBinding> typeConverterBindings;
69
70
71
72
73
74 @Inject
75 PlexusXmlBeanConverter(final Injector injector) {
76 typeConverterBindings = injector.getTypeConverterBindings();
77 }
78
79
80
81
82
83 @SuppressWarnings({"unchecked", "rawtypes"})
84 public Object convert(final TypeLiteral role, final String value) {
85 if (value.trim().startsWith("<")) {
86 try {
87 final MXParser parser = new MXParser();
88 parser.setInput(new StringReader(value));
89 parser.nextTag();
90
91 return parse(parser, role);
92 } catch (final Exception e) {
93 throw new IllegalArgumentException(String.format(CONVERSION_ERROR, value, role), e);
94 }
95 }
96
97 return convertText(value, role);
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111 private Object parse(final MXParser parser, final TypeLiteral<?> toType) throws Exception {
112 parser.require(XmlPullParser.START_TAG, null, null);
113
114 final Class<?> rawType = toType.getRawType();
115 if (XmlNode.class.isAssignableFrom(rawType)) {
116 return XmlNodeBuilder.build(parser);
117 }
118 if (Xpp3Dom.class.isAssignableFrom(rawType)) {
119 return parseXpp3Dom(parser);
120 }
121 if (Properties.class.isAssignableFrom(rawType)) {
122 return parseProperties(parser);
123 }
124 if (Map.class.isAssignableFrom(rawType)) {
125 return parseMap(parser, TypeArguments.get(toType.getSupertype(Map.class), 1));
126 }
127 if (Collection.class.isAssignableFrom(rawType)) {
128 return parseCollection(parser, TypeArguments.get(toType.getSupertype(Collection.class), 0));
129 }
130 if (rawType.isArray()) {
131 return parseArray(parser, TypeArguments.get(toType, 0));
132 }
133 return parseBean(parser, toType, rawType);
134 }
135
136
137
138
139
140
141
142 private static Xpp3Dom parseXpp3Dom(final XmlPullParser parser) throws Exception {
143 return Xpp3DomBuilder.build(parser);
144 }
145
146
147
148
149
150
151
152 private static Properties parseProperties(final XmlPullParser parser) throws Exception {
153 final Properties properties = newImplementation(parser, Properties.class);
154 while (parser.nextTag() == XmlPullParser.START_TAG) {
155 parser.nextTag();
156
157 if ("name".equals(parser.getName())) {
158 final String name = parser.nextText();
159 parser.nextTag();
160 properties.put(name, parser.nextText());
161 } else {
162 final String value = parser.nextText();
163 parser.nextTag();
164 properties.put(parser.nextText(), value);
165 }
166 parser.nextTag();
167 }
168 return properties;
169 }
170
171
172
173
174
175
176
177 private Map<String, Object> parseMap(final MXParser parser, final TypeLiteral<?> toType) throws Exception {
178 @SuppressWarnings("unchecked")
179 final Map<String, Object> map = newImplementation(parser, HashMap.class);
180 while (parser.nextTag() == XmlPullParser.START_TAG) {
181 map.put(parser.getName(), parse(parser, toType));
182 }
183 return map;
184 }
185
186
187
188
189
190
191
192 private Collection<Object> parseCollection(final MXParser parser, final TypeLiteral<?> toType) throws Exception {
193 @SuppressWarnings("unchecked")
194 final Collection<Object> collection = newImplementation(parser, ArrayList.class);
195 while (parser.nextTag() == XmlPullParser.START_TAG) {
196 collection.add(parse(parser, toType));
197 }
198 return collection;
199 }
200
201
202
203
204
205
206
207 private Object parseArray(final MXParser parser, final TypeLiteral<?> toType) throws Exception {
208
209 final Collection<?> collection = parseCollection(parser, toType);
210 final Object array = Array.newInstance(toType.getRawType(), collection.size());
211
212 int i = 0;
213 for (final Object element : collection) {
214 Array.set(array, i++, element);
215 }
216
217 return array;
218 }
219
220
221
222
223
224
225
226 private Object parseBean(final MXParser parser, final TypeLiteral<?> toType, final Class<?> rawType)
227 throws Exception {
228 final Class<?> clazz = loadImplementation(parseImplementation(parser), rawType);
229
230
231 if (parser.next() == XmlPullParser.TEXT) {
232 final String text = parser.getText();
233
234
235 if (parser.next() != XmlPullParser.START_TAG) {
236 return convertText(text, clazz == rawType ? toType : TypeLiteral.get(clazz));
237 }
238 }
239
240 if (String.class == clazz) {
241
242 while (parser.getEventType() == XmlPullParser.START_TAG) {
243 final String pos = parser.getPositionDescription();
244 Logs.warn("Expected TEXT, not XML: {}", pos, new Throwable());
245 parser.skipSubTree();
246 parser.nextTag();
247 }
248 return "";
249 }
250
251 final Object bean = newImplementation(clazz);
252
253
254 final Map<String, BeanProperty<Object>> propertyMap = new HashMap<String, BeanProperty<Object>>();
255 for (final BeanProperty<Object> property : new BeanProperties(clazz)) {
256 final String name = property.getName();
257 if (!propertyMap.containsKey(name)) {
258 propertyMap.put(name, property);
259 }
260 }
261
262 while (parser.getEventType() == XmlPullParser.START_TAG) {
263
264 final BeanProperty<Object> property = propertyMap.get(Roles.camelizeName(parser.getName()));
265 if (property != null) {
266 property.set(bean, parse(parser, property.getType()));
267 parser.nextTag();
268 } else {
269 throw new XmlPullParserException("Unknown bean property: " + parser.getName(), parser, null);
270 }
271 }
272
273 return bean;
274 }
275
276
277
278
279
280
281
282 private static String parseImplementation(final XmlPullParser parser) {
283 return parser.getAttributeValue(null, "implementation");
284 }
285
286
287
288
289
290
291
292
293 private static Class<?> loadImplementation(final String name, final Class<?> defaultClazz) {
294 if (null == name) {
295 return defaultClazz;
296 }
297
298
299 final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
300 if (tccl != null) {
301 try {
302 return tccl.loadClass(name);
303 } catch (final Exception e) {
304
305 } catch (final LinkageError e) {
306
307 }
308 }
309
310
311 final ClassLoader peer = defaultClazz.getClassLoader();
312 if (peer != null) {
313 try {
314 return peer.loadClass(name);
315 } catch (final Exception e) {
316
317 } catch (final LinkageError e) {
318
319 }
320 }
321
322 try {
323
324 return Class.forName(name);
325 } catch (final Exception e) {
326 throw new TypeNotPresentException(name, e);
327 } catch (final LinkageError e) {
328 throw new TypeNotPresentException(name, e);
329 }
330 }
331
332
333
334
335
336
337
338 private static <T> T newImplementation(final Class<T> clazz) {
339 try {
340 return clazz.newInstance();
341 } catch (final Exception e) {
342 throw new IllegalArgumentException("Cannot create instance of: " + clazz, e);
343 } catch (final LinkageError e) {
344 throw new IllegalArgumentException("Cannot create instance of: " + clazz, e);
345 }
346 }
347
348
349
350
351
352
353
354
355 private static <T> T newImplementation(final Class<T> clazz, final String value) {
356 try {
357 return clazz.getConstructor(String.class).newInstance(value);
358 } catch (final Exception e) {
359 final Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
360 throw new IllegalArgumentException(String.format(CONVERSION_ERROR, value, clazz), cause);
361 } catch (final LinkageError e) {
362 throw new IllegalArgumentException(String.format(CONVERSION_ERROR, value, clazz), e);
363 }
364 }
365
366
367
368
369
370
371
372
373 @SuppressWarnings("unchecked")
374 private static <T> T newImplementation(final XmlPullParser parser, final Class<T> defaultClazz) {
375 return (T) newImplementation(loadImplementation(parseImplementation(parser), defaultClazz));
376 }
377
378
379
380
381
382
383
384
385 private Object convertText(final String value, final TypeLiteral<?> toType) {
386 final String text = value.trim();
387
388 final Class<?> rawType = toType.getRawType();
389 if (rawType.isAssignableFrom(String.class)) {
390 return text;
391 }
392
393
394 final TypeLiteral<?> boxedType =
395 rawType.isPrimitive() ? Key.get(rawType).getTypeLiteral() : toType;
396
397 for (final TypeConverterBinding b : typeConverterBindings) {
398 if (b.getTypeMatcher().matches(boxedType)) {
399 return b.getTypeConverter().convert(text, toType);
400 }
401 }
402
403
404 return text.length() == 0 ? newImplementation(rawType) : newImplementation(rawType, text);
405 }
406 }