001: /*
002: * Copyright (C) 2007, 2008 XStream Committers.
003: * All rights reserved.
004: *
005: * The software in this package is published under the terms of the BSD
006: * style license a copy of which has been included with this distribution in
007: * the LICENSE.txt file.
008: *
009: * Created on 07. November 2007 by Joerg Schaible
010: */
011: package com.thoughtworks.xstream.mapper;
012:
013: import com.thoughtworks.xstream.InitializationException;
014: import com.thoughtworks.xstream.XStream;
015: import com.thoughtworks.xstream.annotations.XStreamAlias;
016: import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
017: import com.thoughtworks.xstream.annotations.XStreamConverter;
018: import com.thoughtworks.xstream.annotations.XStreamConverters;
019: import com.thoughtworks.xstream.annotations.XStreamImplicit;
020: import com.thoughtworks.xstream.annotations.XStreamImplicitCollection;
021: import com.thoughtworks.xstream.annotations.XStreamOmitField;
022: import com.thoughtworks.xstream.converters.Converter;
023: import com.thoughtworks.xstream.converters.ConverterRegistry;
024: import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
025: import com.thoughtworks.xstream.core.JVM;
026: import com.thoughtworks.xstream.core.util.DependencyInjectionFactory;
027:
028: import java.lang.reflect.Field;
029: import java.lang.reflect.GenericArrayType;
030: import java.lang.reflect.Modifier;
031: import java.lang.reflect.ParameterizedType;
032: import java.lang.reflect.Type;
033: import java.lang.reflect.TypeVariable;
034: import java.util.ArrayList;
035: import java.util.Arrays;
036: import java.util.Collection;
037: import java.util.HashMap;
038: import java.util.HashSet;
039: import java.util.Iterator;
040: import java.util.LinkedHashSet;
041: import java.util.List;
042: import java.util.Map;
043: import java.util.Set;
044: import java.util.WeakHashMap;
045:
046: /**
047: * A mapper that uses annotations to prepare the remaining mappers in the chain.
048: *
049: * @author Jörg Schaible
050: * @since 1.3
051: */
052: public class AnnotationMapper extends MapperWrapper implements
053: AnnotationConfiguration {
054:
055: private boolean locked;
056: private final Object[] arguments;
057: private final ConverterRegistry converterRegistry;
058: private final ClassAliasingMapper classAliasingMapper;
059: private final DefaultImplementationsMapper defaultImplementationsMapper;
060: private final ImplicitCollectionMapper implicitCollectionMapper;
061: private final FieldAliasingMapper fieldAliasingMapper;
062: private final AttributeMapper attributeMapper;
063: private final LocalConversionMapper localConversionMapper;
064: private final Map<Class<?>, Converter> converterCache = new HashMap<Class<?>, Converter>();
065: private final Set<Class<?>> annotatedTypes = new WeakHashSet<Class<?>>();
066:
067: /**
068: * Construct an AnnotationMapper.
069: *
070: * @param wrapped the next {@link Mapper} in the chain
071: * @since 1.3
072: */
073: public AnnotationMapper(final Mapper wrapped,
074: final ConverterRegistry converterRegistry,
075: final ClassLoader classLoader,
076: final ReflectionProvider reflectionProvider, final JVM jvm) {
077: super (wrapped);
078: this .converterRegistry = converterRegistry;
079: annotatedTypes.add(Object.class);
080: classAliasingMapper = (ClassAliasingMapper) lookupMapperOfType(ClassAliasingMapper.class);
081: defaultImplementationsMapper = (DefaultImplementationsMapper) lookupMapperOfType(DefaultImplementationsMapper.class);
082: implicitCollectionMapper = (ImplicitCollectionMapper) lookupMapperOfType(ImplicitCollectionMapper.class);
083: fieldAliasingMapper = (FieldAliasingMapper) lookupMapperOfType(FieldAliasingMapper.class);
084: attributeMapper = (AttributeMapper) lookupMapperOfType(AttributeMapper.class);
085: localConversionMapper = (LocalConversionMapper) lookupMapperOfType(LocalConversionMapper.class);
086: locked = true;
087: arguments = new Object[] { this , classLoader,
088: reflectionProvider, jvm };
089: }
090:
091: @Override
092: public String realMember(Class type, String serialized) {
093: if (!locked) {
094: processAnnotations(type);
095: }
096: return super .realMember(type, serialized);
097: }
098:
099: @Override
100: public String serializedClass(Class type) {
101: if (!locked) {
102: processAnnotations(type);
103: }
104: return super .serializedClass(type);
105: }
106:
107: @Override
108: public Class defaultImplementationOf(Class type) {
109: if (!locked) {
110: processAnnotations(type);
111: }
112: final Class defaultImplementation = super
113: .defaultImplementationOf(type);
114: if (!locked) {
115: processAnnotations(defaultImplementation);
116: }
117: return defaultImplementation;
118: }
119:
120: @Override
121: public Converter getLocalConverter(final Class definedIn,
122: final String fieldName) {
123: if (!locked) {
124: processAnnotations(definedIn);
125: }
126: return super .getLocalConverter(definedIn, fieldName);
127: }
128:
129: public void autodetectAnnotations(boolean mode) {
130: locked = !mode;
131: }
132:
133: public void processAnnotations(final Class[] initialTypes) {
134: if (initialTypes == null || initialTypes.length == 0) {
135: return;
136: }
137: locked = true;
138: synchronized (annotatedTypes) {
139: final Set<Class<?>> types = new UnprocessedTypesSet();
140: for (Class initialType : initialTypes) {
141: types.add(initialType);
142: }
143: processTypes(types);
144: }
145: }
146:
147: private void processAnnotations(final Class initialType) {
148: if (initialType == null) {
149: return;
150: }
151: synchronized (annotatedTypes) {
152: final Set<Class<?>> types = new UnprocessedTypesSet();
153: types.add(initialType);
154: processTypes(types);
155: }
156: }
157:
158: private void processTypes(final Set<Class<?>> types) {
159: while (!types.isEmpty()) {
160: final Iterator<Class<?>> iter = types.iterator();
161: final Class<?> type = iter.next();
162: iter.remove();
163:
164: if (annotatedTypes.add(type)) {
165: if (type.isPrimitive()) {
166: continue;
167: }
168:
169: addParametrizedTypes(type, types);
170:
171: processConverterAnnotations(type);
172: processAliasAnnotation(type, types);
173:
174: if (type.isInterface()) {
175: continue;
176: }
177:
178: processImplicitCollectionAnnotation(type);
179:
180: final Field[] fields = type.getDeclaredFields();
181: for (int i = 0; i < fields.length; i++) {
182: final Field field = fields[i];
183: if (field.isEnumConstant()
184: || (field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) > 0) {
185: continue;
186: }
187:
188: addParametrizedTypes(field.getGenericType(), types);
189:
190: if (field.isSynthetic()) {
191: continue;
192: }
193:
194: processFieldAliasAnnotation(field);
195: processAsAttributeAnnotation(field);
196: processImplicitAnnotation(field);
197: processOmitFieldAnnotation(field);
198: processLocalConverterAnnotation(field);
199: }
200: }
201: }
202: }
203:
204: private void addParametrizedTypes(Type type,
205: final Set<Class<?>> types) {
206: final Set<Type> processedTypes = new HashSet<Type>();
207: Set<Type> localTypes = new LinkedHashSet<Type>() {
208:
209: @Override
210: public boolean add(Type o) {
211: if (o instanceof Class) {
212: return types.add((Class<?>) o);
213: }
214: return o == null || processedTypes.contains(o) ? false
215: : super .add(o);
216: }
217:
218: };
219: while (type != null) {
220: processedTypes.add(type);
221: if (type instanceof Class) {
222: Class<?> clazz = (Class<?>) type;
223: types.add(clazz);
224: if (!clazz.isPrimitive()) {
225: final TypeVariable<?>[] typeParameters = clazz
226: .getTypeParameters();
227: for (TypeVariable<?> typeVariable : typeParameters) {
228: localTypes.add(typeVariable);
229: }
230: localTypes.add(clazz.getGenericSuperclass());
231: for (final Type iface : clazz
232: .getGenericInterfaces()) {
233: localTypes.add(iface);
234: }
235: }
236: } else if (type instanceof TypeVariable) {
237: final TypeVariable<?> typeVariable = (TypeVariable<?>) type;
238: final Type[] bounds = typeVariable.getBounds();
239: for (Type bound : bounds) {
240: localTypes.add(bound);
241: }
242: } else if (type instanceof ParameterizedType) {
243: final ParameterizedType parametrizedType = (ParameterizedType) type;
244: localTypes.add(parametrizedType.getRawType());
245: Type[] actualArguments = parametrizedType
246: .getActualTypeArguments();
247: for (Type actualArgument : actualArguments) {
248: localTypes.add(actualArgument);
249: }
250: } else if (type instanceof GenericArrayType) {
251: final GenericArrayType arrayType = (GenericArrayType) type;
252: localTypes.add(arrayType.getGenericComponentType());
253: }
254:
255: if (!localTypes.isEmpty()) {
256: Iterator<Type> iter = localTypes.iterator();
257: type = iter.next();
258: iter.remove();
259: } else {
260: type = null;
261: }
262: }
263: }
264:
265: private void processConverterAnnotations(final Class<?> type) {
266: if (converterRegistry != null) {
267: final XStreamConverters convertersAnnotation = type
268: .getAnnotation(XStreamConverters.class);
269: final XStreamConverter converterAnnotation = type
270: .getAnnotation(XStreamConverter.class);
271: final List<XStreamConverter> annotations = convertersAnnotation != null ? new ArrayList<XStreamConverter>(
272: Arrays.asList(convertersAnnotation.value()))
273: : new ArrayList<XStreamConverter>();
274: if (converterAnnotation != null) {
275: annotations.add(converterAnnotation);
276: }
277: for (final XStreamConverter annotation : annotations) {
278: final Class<? extends Converter> converterType = annotation
279: .value();
280: final Converter converter = cacheConverter(converterType);
281: if (converter != null) {
282: if (converter != converterAnnotation
283: || converter.canConvert(type)) {
284: converterRegistry.registerConverter(converter,
285: XStream.PRIORITY_NORMAL);
286: } else {
287: throw new InitializationException("Converter "
288: + converterType.getName()
289: + " cannot handle annotated class "
290: + type.getName());
291: }
292: }
293: }
294: }
295: }
296:
297: private void processAliasAnnotation(final Class<?> type,
298: final Set<Class<?>> types) {
299: final XStreamAlias aliasAnnotation = type
300: .getAnnotation(XStreamAlias.class);
301: if (aliasAnnotation != null) {
302: if (classAliasingMapper == null) {
303: throw new InitializationException("No "
304: + ClassAliasingMapper.class.getName()
305: + " available");
306: }
307: if (aliasAnnotation.impl() != Void.class) {
308: // Alias for Interface/Class with an impl
309: classAliasingMapper.addClassAlias(aliasAnnotation
310: .value(), type);
311: defaultImplementationsMapper.addDefaultImplementation(
312: aliasAnnotation.impl(), type);
313: if (type.isInterface()) {
314: types.add(aliasAnnotation.impl()); // alias Interface's impl
315: }
316: } else {
317: classAliasingMapper.addClassAlias(aliasAnnotation
318: .value(), type);
319: }
320: }
321: }
322:
323: @Deprecated
324: private void processImplicitCollectionAnnotation(final Class<?> type) {
325: final XStreamImplicitCollection implicitColAnnotation = type
326: .getAnnotation(XStreamImplicitCollection.class);
327: if (implicitColAnnotation != null) {
328: if (implicitCollectionMapper == null) {
329: throw new InitializationException("No "
330: + ImplicitCollectionMapper.class.getName()
331: + " available");
332: }
333: final String fieldName = implicitColAnnotation.value();
334: final String itemFieldName = implicitColAnnotation.item();
335: final Field field;
336: try {
337: field = type.getDeclaredField(fieldName);
338: } catch (final NoSuchFieldException e) {
339: throw new InitializationException(type.getName()
340: + " does not have a field named '" + fieldName
341: + "' as required by "
342: + XStreamImplicitCollection.class.getName());
343: }
344: Class itemType = null;
345: final Type genericType = field.getGenericType();
346: if (genericType instanceof ParameterizedType) {
347: final Type typeArgument = ((ParameterizedType) genericType)
348: .getActualTypeArguments()[0];
349: itemType = getClass(typeArgument);
350: }
351: if (itemType == null) {
352: implicitCollectionMapper.add(type, fieldName, null,
353: Object.class);
354: } else {
355: if (itemFieldName.equals("")) {
356: implicitCollectionMapper.add(type, fieldName, null,
357: itemType);
358: } else {
359: implicitCollectionMapper.add(type, fieldName,
360: itemFieldName, itemType);
361: }
362: }
363: }
364: }
365:
366: private void processFieldAliasAnnotation(final Field field) {
367: final XStreamAlias aliasAnnotation = field
368: .getAnnotation(XStreamAlias.class);
369: if (aliasAnnotation != null) {
370: if (fieldAliasingMapper == null) {
371: throw new InitializationException("No "
372: + FieldAliasingMapper.class.getName()
373: + " available");
374: }
375: fieldAliasingMapper.addFieldAlias(aliasAnnotation.value(),
376: field.getDeclaringClass(), field.getName());
377: }
378: }
379:
380: private void processAsAttributeAnnotation(final Field field) {
381: final XStreamAsAttribute asAttributeAnnotation = field
382: .getAnnotation(XStreamAsAttribute.class);
383: if (asAttributeAnnotation != null) {
384: if (attributeMapper == null) {
385: throw new InitializationException("No "
386: + AttributeMapper.class.getName()
387: + " available");
388: }
389: attributeMapper.addAttributeFor(field);
390: }
391: }
392:
393: private void processImplicitAnnotation(final Field field) {
394: final XStreamImplicit implicitAnnotation = field
395: .getAnnotation(XStreamImplicit.class);
396: if (implicitAnnotation != null) {
397: if (implicitCollectionMapper == null) {
398: throw new InitializationException("No "
399: + ImplicitCollectionMapper.class.getName()
400: + " available");
401: }
402: String fieldName = field.getName();
403: String itemFieldName = implicitAnnotation.itemFieldName();
404: Class itemType = null;
405: final Type genericType = field.getGenericType();
406: if (genericType instanceof ParameterizedType) {
407: final Type typeArgument = ((ParameterizedType) genericType)
408: .getActualTypeArguments()[0];
409: itemType = getClass(typeArgument);
410: }
411: if (itemFieldName != null && !"".equals(itemFieldName)) {
412: implicitCollectionMapper.add(field.getDeclaringClass(),
413: fieldName, itemFieldName, itemType);
414: } else {
415: implicitCollectionMapper.add(field.getDeclaringClass(),
416: fieldName, itemType);
417: }
418: }
419: }
420:
421: private void processOmitFieldAnnotation(final Field field) {
422: final XStreamOmitField omitFieldAnnotation = field
423: .getAnnotation(XStreamOmitField.class);
424: if (omitFieldAnnotation != null) {
425: if (fieldAliasingMapper == null) {
426: throw new InitializationException("No "
427: + FieldAliasingMapper.class.getName()
428: + " available");
429: }
430: fieldAliasingMapper.omitField(field.getDeclaringClass(),
431: field.getName());
432: }
433: }
434:
435: private void processLocalConverterAnnotation(final Field field) {
436: final XStreamConverter annotation = field
437: .getAnnotation(XStreamConverter.class);
438: if (annotation != null) {
439: final Class<? extends Converter> converterType = annotation
440: .value();
441: final Converter converter = cacheConverter(converterType);
442: if (converter != null) {
443: if (localConversionMapper == null) {
444: throw new InitializationException("No "
445: + LocalConversionMapper.class.getName()
446: + " available");
447: }
448: localConversionMapper.registerLocalConverter(field
449: .getDeclaringClass(), field.getName(),
450: converter);
451: }
452: }
453: }
454:
455: private Converter cacheConverter(
456: final Class<? extends Converter> converterType) {
457: Converter converter = converterCache.get(converterType);
458: if (converter == null) {
459: try {
460: converter = (Converter) DependencyInjectionFactory
461: .newInstance(converterType, arguments);
462: converterCache.put(converterType, converter);
463: } catch (final Exception e) {
464: throw new InitializationException(
465: "Cannot instantiate converter "
466: + converterType.getName(), e);
467: }
468: }
469: return converter;
470: }
471:
472: private Class<?> getClass(final Type typeArgument) {
473: Class<?> type = null;
474: if (typeArgument instanceof ParameterizedType) {
475: type = (Class<?>) ((ParameterizedType) typeArgument)
476: .getRawType();
477: } else if (typeArgument instanceof Class) {
478: type = (Class<?>) typeArgument;
479: }
480: return type;
481: }
482:
483: private final class UnprocessedTypesSet extends
484: LinkedHashSet<Class<?>> {
485: @Override
486: public boolean add(Class<?> type) {
487: if (type == null) {
488: return false;
489: }
490: while (type.isArray()) {
491: type = type.getComponentType();
492: }
493: final String name = type.getName();
494: if (name.startsWith("java.") || name.startsWith("java.")) {
495: return false;
496: }
497: return annotatedTypes.contains(type) ? false : super
498: .add(type);
499: }
500: }
501:
502: private static class WeakHashSet<K> implements Set<K> {
503:
504: private static Object NULL = new Object();
505: private WeakHashMap<K, Object> map = new WeakHashMap<K, Object>();
506:
507: public boolean add(K o) {
508: return map.put(o, NULL) == null;
509: }
510:
511: public boolean addAll(Collection<? extends K> c) {
512: boolean ret = false;
513: for (K k : c) {
514: ret = add(k) | false;
515: }
516: return ret;
517: }
518:
519: public void clear() {
520: map.clear();
521: }
522:
523: public boolean contains(Object o) {
524: return map.containsKey(o);
525: }
526:
527: public boolean containsAll(Collection<?> c) {
528: return map.keySet().containsAll(c);
529: }
530:
531: public boolean isEmpty() {
532: return map.isEmpty();
533: }
534:
535: public Iterator<K> iterator() {
536: return map.keySet().iterator();
537: }
538:
539: public boolean remove(Object o) {
540: return map.remove(o) != null;
541: }
542:
543: public boolean removeAll(Collection<?> c) {
544: boolean ret = false;
545: for (Object object : c) {
546: ret = remove(object) | false;
547: }
548: return ret;
549: }
550:
551: public boolean retainAll(Collection<?> c) {
552: boolean ret = false;
553: for (final Iterator<K> iter = iterator(); iter.hasNext();) {
554: final K element = iter.next();
555: if (!c.contains(element)) {
556: iter.remove();
557: ret = true;
558: }
559: }
560: return ret;
561: }
562:
563: public int size() {
564: return map.size();
565: }
566:
567: public Object[] toArray() {
568: return map.keySet().toArray();
569: }
570:
571: public <T> T[] toArray(T[] a) {
572: return map.keySet().toArray(a);
573: }
574:
575: }
576: }
|