Java tutorial
/* Copyright (c) 2013 OpenPlans - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.catalog.impl; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.beanutils.ConstructorUtils; import org.apache.commons.lang.SerializationUtils; import org.geoserver.catalog.CatalogInfo; import org.geoserver.config.util.XStreamPersister; import org.geoserver.config.util.XStreamPersisterFactory; import org.geotools.util.logging.Logging; import com.thoughtworks.xstream.XStream; /** * Utility class used to wrap/clone objects and collections by various strategies: * <ul> * <li>Avoid cloning ModificationProxy proxies, as well as any CatalogInfo object</li> * <li>Avoid cloning at all well known objects that are known to be immutable (several classes in * java.lang)</li> * <li>Wrap in ModificatinoProxy any object that is a CatalogInfo</li> * <li>Using {@link Cloneable} if available</li> * <li>Using copy constructors if available</li> * <li>Falling back on XStream serialization if the above fails * * @author Andrea Aime - GeoSolutions * */ class ModificationProxyCloner { private static final XStreamPersisterFactory XSTREAM_PERSISTER_FACTORY = new XStreamPersisterFactory(); static final Logger LOGGER = Logging.getLogger(ModificationProxyCloner.class); static final Map<Class, Class> CATALOGINFO_INTERFACE_CACHE = new ConcurrentHashMap<Class, Class>(); /** * Best effort object cloning utility, tries different lightweight strategies, then falls back * on copy by XStream serialization (we use that one as we have a number of hooks to avoid deep * copying the catalog, and re-attaching to it, in there) * * @param source * @return */ static <T> T clone(T source) { // null? if (source == null) { return null; } // already a modification proxy? if (ModificationProxy.handler(source) != null) { return source; } // is it a catalog info? if (source instanceof CatalogInfo) { // mumble... shouldn't we wrap this one in a modification proxy object? return (T) ModificationProxy.create(source, getDeepestCatalogInfoInterface((CatalogInfo) source)); } // if a known immutable? if (source instanceof String || source instanceof Byte || source instanceof Short || source instanceof Integer || source instanceof Float || source instanceof Double || source instanceof BigInteger || source instanceof BigDecimal) { return (T) source; } // is it cloneable? try { if (source instanceof Cloneable) { // methodutils does not seem to work against "clone()"... // return (T) MethodUtils.invokeExactMethod(source, "clone", null, null); Method method = source.getClass().getDeclaredMethod("clone"); if (Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0) { return (T) method.invoke(source); } } } catch (Exception e) { LOGGER.log(Level.FINE, "Source object is cloneable, yet it does not have a public no argument method 'clone'", e); } // does it have a copy constructor? Constructor copyConstructor = ConstructorUtils.getAccessibleConstructor(source.getClass(), source.getClass()); if (copyConstructor != null) { try { return (T) copyConstructor.newInstance(source); } catch (Exception e) { LOGGER.log(Level.FINE, "Source has a copy constructor, but it failed, skipping to XStream", e); } } if (source instanceof Serializable) { return (T) SerializationUtils.clone((Serializable) source); } else { XStreamPersister persister = XSTREAM_PERSISTER_FACTORY.createXMLPersister(); XStream xs = persister.getXStream(); String xml = xs.toXML(source); T copy = (T) xs.fromXML(xml); return copy; } } static Class getDeepestCatalogInfoInterface(CatalogInfo object) { Class<? extends CatalogInfo> sourceClass = object.getClass(); Class result = CATALOGINFO_INTERFACE_CACHE.get(sourceClass); if (result == null) { Class[] interfaces = sourceClass.getInterfaces(); // collect only CatalogInfo related interfaces List<Class> cis = new ArrayList<Class>(); for (Class clazz : interfaces) { if (CatalogInfo.class.isAssignableFrom(clazz)) { cis.add(clazz); } } if (cis.size() == 0) { result = null; } else if (cis.size() == 1) { result = cis.get(0); } else { Collections.sort(cis, new Comparator<Class>() { @Override public int compare(Class c1, Class c2) { if (c1.isAssignableFrom(c2)) { return 1; } else if (c2.isAssignableFrom(c1)) { return -1; } else { return 0; } } }); result = cis.get(0); } CATALOGINFO_INTERFACE_CACHE.put(sourceClass, result); } return result; } /** * Shallow or deep copies the provided collection * * @param source * @param deepCopy If true, a deep copy will be done, otherwise the cloned collection will * contain the exact same objects as the source * @return * @throws InstantiationException * @throws IllegalAccessException */ public static <T> Collection<T> cloneCollection(Collection<T> source, boolean deepCopy) throws InstantiationException, IllegalAccessException { if (source == null) { // nothing to copy return null; } Collection<T> copy = source.getClass().newInstance(); if (deepCopy) { for (T object : source) { T objectCopy = clone(object); copy.add(objectCopy); } } else { copy.addAll(source); } return copy; } /** * Shallow or deep copies the provided collection * * @param <K> * @param <V> * * @param source * @param deepCopy If true, a deep copy will be done, otherwise the cloned collection will * contain the exact same objects as the source * @return * @throws InstantiationException * @throws IllegalAccessException */ public static <K, V> Map<K, V> cloneMap(Map<K, V> source, boolean deepCopy) throws InstantiationException, IllegalAccessException { if (source == null) { // nothing to copy return null; } Map<K, V> copy = source.getClass().newInstance(); if (deepCopy) { for (Map.Entry<K, V> entry : source.entrySet()) { K keyCopy = clone(entry.getKey()); V valueCopy = clone(entry.getValue()); copy.put(keyCopy, valueCopy); } } else { copy.putAll(source); } return copy; } }