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