/* * Carrot2 project. * * Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński. * All rights reserved. * * Refer to the full license file "carrot2.LICENSE" * in the root folder of the repository checkout or at: * http://www.carrot2.org/carrot2.LICENSE */ package org.carrot2.util.simplexml; import java.util.*; import org.simpleframework.xml.Root; import org.carrot2.shaded.guava.common.collect.Maps; /** * Enables SimpleXML-based serialization of collections of arbitrary types, also those * contained in libraries. Depending on the actual type, 3 wrapping scenarios are * possible: * <ul> * <li><strong>Primitive types.</strong> Primitive types, and also {@link Class}, * {@link String} and {@link Enum}, are handled directly by {@link SimpleXmlWrapperValue}, * no extra code is required for serialization / deserialization.</li> * <li><strong>SimpleXML-annotated types.</strong> Types annotated with SimpleXML's * {@link Root} annotation will not be wrapped and serialized / deserialized directly by * SimpleXML</li> * <li><strong>Other types.</strong> For any other types, a {@link ISimpleXmlWrapper} * implementation must be registered using the {@link #addWrapper(Class, Class)} method.</li> * </ul> */ public class SimpleXmlWrappers { /** * The supported {@link ISimpleXmlWrapper}s. */ static Map<Class<?>, Class<? extends ISimpleXmlWrapper<?>>> wrappers = Maps.newHashMap(); /** * Indicates whether the wrapper mapping is strict. */ static Map<Class<?>, Boolean> strict = Maps.newHashMap(); /** * Registers a new {@link ISimpleXmlWrapper}. If a wrapper for the provide * <code>wrappedClass</code> already exists, it will be replaced with the new value. * The wrapper mapping added using this method will be strict, it will apply only to * objects of <code>wrappedClass</code>, but not its subclasses. * * @param wrappedClass type to be wrapped * @param wrapperClass class name of the {@link ISimpleXmlWrapper} implementation to * wrap <code>wrappedClass</code> * @see SimpleXmlWrappers#addWrapper(Class, Class, boolean) */ public static synchronized <T> void addWrapper(Class<T> wrappedClass, Class<? extends ISimpleXmlWrapper<? super T>> wrapperClass) { addWrapper(wrappedClass, wrapperClass, true); } /** * Registers a new {@link ISimpleXmlWrapper}. If a wrapper for the provide * <code>wrappedClass</code> already exists, it will be replaced with the new value. * * @param wrappedClass type to be wrapped * @param wrapperClass class name of the {@link ISimpleXmlWrapper} implementation to * wrap <code>wrappedClass</code> * @param strict if <code>true</code>, the mapping will apply only to objects of * <code>wrappedClass</code>, but not to its subclasses. If * <code>false</code>, if no exact mapping is found for * <code>wrappedClass</code>, the first available superclass mapping will * be sought. */ public static synchronized <T> void addWrapper(Class<T> wrappedClass, Class<? extends ISimpleXmlWrapper<? super T>> wrapperClass, boolean strict) { wrappers.put(wrappedClass, wrapperClass); SimpleXmlWrappers.strict.put(wrappedClass, strict); } /** * Wraps the provided map for serialization. * * @return map for SimpleXML serialization */ public static <K> Map<K, SimpleXmlWrapperValue> wrap(Map<K, ?> toWrap) { final HashMap<K, SimpleXmlWrapperValue> wrapped = Maps.newHashMap(); for (Map.Entry<K, ?> entry : toWrap.entrySet()) { wrapped.put(entry.getKey(), SimpleXmlWrapperValue.wrap(entry.getValue())); } return wrapped; } /** * Unwraps the provided map after deserialization. * * @return map with original values */ public static <K> Map<K, Object> unwrap(Map<K, SimpleXmlWrapperValue> wrapped) { final HashMap<K, Object> result = Maps.newHashMap(); for (Map.Entry<K, SimpleXmlWrapperValue> entry : wrapped.entrySet()) { result.put(entry.getKey(), unwrap(entry.getValue())); } return result; } /** * Wraps the provided list for serialization. * * @return list for SimpleXML serialization */ public static List<SimpleXmlWrapperValue> wrap(List<?> toWrap) { return (List<SimpleXmlWrapperValue>) wrap(toWrap, new ArrayList<SimpleXmlWrapperValue>()); } /** * Unwraps the provided list after deserialization. * * @return list with original values */ public static List<Object> unwrap(List<SimpleXmlWrapperValue> wrapped) { return (List<Object>) unwrap(wrapped, new ArrayList<Object>()); } /** * Wraps the provided set for serialization. * * @return set for SimpleXML serialization */ public static Set<SimpleXmlWrapperValue> wrap(Set<?> toWrap) { return (Set<SimpleXmlWrapperValue>) wrap(toWrap, new HashSet<SimpleXmlWrapperValue>()); } /** * Unwraps the provided set after deserialization. * * @return set with original values */ public static Set<Object> unwrap(Set<SimpleXmlWrapperValue> wrapped) { return (Set<Object>) unwrap(wrapped, new HashSet<Object>()); } /** * Wraps the provided collection for serialization. * * @param toWrap collection to wrap * @param wrapped collection to which wrapped values will be added * @return the wrapped collection for convenience */ public static Collection<SimpleXmlWrapperValue> wrap(Collection<?> toWrap, Collection<SimpleXmlWrapperValue> wrapped) { for (Object value : toWrap) { wrapped.add(SimpleXmlWrapperValue.wrap(value)); } return wrapped; } /** * Unwraps the provided collection after deserialization. * * @param wrapped the SimpleXML-deserialized collection * @param unwrapped collection to which the original values should be added * @return the unwrapped collection for convenience */ public static Collection<Object> unwrap(Collection<SimpleXmlWrapperValue> wrapped, Collection<Object> unwrapped) { for (SimpleXmlWrapperValue wrappedValue : wrapped) { unwrapped.add(unwrap(wrappedValue)); } return unwrapped; } /** * Wraps an individual object for serialization. */ public static SimpleXmlWrapperValue wrap(Object value) { return SimpleXmlWrapperValue.wrap(value); } /** * Unwraps an object after deserialization. * * @return original value */ @SuppressWarnings("unchecked") public static <T> T unwrap(final SimpleXmlWrapperValue value) { if (value != null) { return (T) value.unwrap(); } else { return null; } } static synchronized <T> Class<? extends ISimpleXmlWrapper<?>> getWrapper(T value) { final Class<? extends Object> valueClass = value.getClass(); // First try to find exact mapping final Class<? extends ISimpleXmlWrapper<?>> clazz = wrappers.get(valueClass); // Check for some common fallback cases if (clazz == null) { // Try to find a non-strict (subclass) mapping for (Map.Entry<Class<?>, Class<? extends ISimpleXmlWrapper<?>>> entry : wrappers .entrySet()) { if (!strict.get(entry.getKey()) && entry.getKey().isAssignableFrom(valueClass)) { return entry.getValue(); } } if (List.class.isInstance(value)) { return ListSimpleXmlWrapper.class; } if (Map.class.isInstance(value)) { return MapSimpleXmlWrapper.class; } // Check if the value's class has a default constructor. If so, return // a generic wrapper that will serialize the class name and deserialize // a new instance by calling the default constructor. try { value.getClass().getConstructor(); return DefaultConstructorSimpleXmlWrapper.class; } catch (SecurityException e) { // ignored } catch (NoSuchMethodException e) { // ignored } } return clazz; } }