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