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