/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.xmlcode; import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; /** * <p> * Utility class providing cloning facilities * </p> * * @author <a href="mailto:Sylvain.Guerin@enst-bretagne.fr">Sylvain Guerin</a> * @see XMLCoder * @see XMLDecoder * @see XMLMapping */ public class Cloner { /** Stores mapping that will be used for decoding */ private XMLMapping xmlMapping; /** * Stores already serialized objects where key is the serialized object and value is a * * <pre> * Integer * </pre> * * instance coding the unique identifier of the object */ private Hashtable<Object, Object> alreadyCloned; /** Stores builder object that will be used for cloning */ private Object builder; private XMLDecoder decoder; private StringEncoder stringEncoder; /** * Creates a new <code>Cloner</code> instance given an <code>XMLMapping</code> object * * @param anXmlMapping * an <code>XMLMapping</code> value */ public Cloner(XMLMapping anXmlMapping) { this(anXmlMapping, null, StringEncoder.getDefaultInstance()); } /** * Creates a new <code>Cloner</code> instance given an <code>XMLMapping</code> object * * @param anXmlMapping * an <code>XMLMapping</code> value */ public Cloner(XMLMapping anXmlMapping, StringEncoder encoder) { this(anXmlMapping, null, encoder); } /** * Creates a new <code>Cloner</code> instance given an <code>XMLMapping</code> object and a builder * * @param anXmlMapping * an <code>XMLMapping</code> value */ public Cloner(XMLMapping anXmlMapping, Object aBuilder) { this(anXmlMapping, aBuilder, StringEncoder.getDefaultInstance()); } /** * Creates a new <code>Cloner</code> instance given an <code>XMLMapping</code> object and a builder * * @param anXmlMapping * an <code>XMLMapping</code> value * @param encoder * TODO */ public Cloner(XMLMapping anXmlMapping, Object aBuilder, StringEncoder encoder) { super(); xmlMapping = anXmlMapping; alreadyCloned = new Hashtable<Object, Object>(); decoder = new XMLDecoder(xmlMapping, aBuilder, encoder); builder = aBuilder; stringEncoder = encoder; } /** * Clone <code>anObject</code> according to mapping <code>xmlMapping</code>, and returns a newly created object * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ public static Object cloneObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { return cloneObjectWithMapping(anObject, xmlMapping, StringEncoder.getDefaultInstance()); } /** * Clone <code>anObject</code> with <code>stringEncoder</code> according to mapping <code>xmlMapping</code>, and returns a newly created * object * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param encoder * a <code>StringEncoder</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ public static Object cloneObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, StringEncoder encoder) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { return cloneObjectWithMapping(anObject, xmlMapping, null, encoder); } /** * Clone <code>anObject</code> according to mapping <code>xmlMapping</code>, and returns a newly created object * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ public static Object cloneObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, Object aBuilder) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { return cloneObjectWithMapping(anObject, xmlMapping, aBuilder, StringEncoder.getDefaultInstance()); } /** * Clone <code>anObject</code> with <code>stringEncoder</code> according to mapping <code>xmlMapping</code>, and returns a newly created * object * * @param anObject * an <code>Object</code> value * @param xmlMapping * a <code>XMLMapping</code> value * @param stringEncoder * a <code>StringEncoder</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ public static Object cloneObjectWithMapping(XMLSerializable anObject, XMLMapping xmlMapping, Object aBuilder, StringEncoder stringEncoder) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { Cloner cloner = new Cloner(xmlMapping, aBuilder, stringEncoder); Object returned = cloner.cloneObject(anObject, anObject); cloner.runCloningFinalization(); return returned; } /** * Creates a new instance according to mapping defined for this <code>XMLCoder</code>, and returns this newly created object. * * @param anObject * an <code>Object</code> value * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if no valid mapping nor mapping file were specified * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ private Object cloneObject(Object anObject) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { return cloneObject(anObject, (Object) null); } /** * Creates a new instance according to mapping defined for this <code>XMLCoder</code>, and returns this newly created object. * * @param anObject * an <code>Object</code> value * @param rootObject * root object being deserialized: if a converter is defined for that value, don't use it * @return an <code>Object</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception SAXException * if an error occurs * @exception ParserConfigurationException * if an error occurs * @exception InvalidModelException * if no valid mapping nor mapping file were specified * @exception InvalidXMLDataException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ private Object cloneObject(Object anObject, Object rootObject) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { if (anObject == null) { throw new InvalidObjectSpecificationException("Object is null"); } if (xmlMapping == null) { throw new InvalidModelException("No mapping specified."); } if (stringEncoder._isEncodable(anObject.getClass()) && anObject != rootObject) { return stringEncoder._decodeObject(stringEncoder._encodeObject(anObject), anObject.getClass()); } if (anObject instanceof PropertiesKeyValueProperty.UndecodableProperty) { return ((PropertiesKeyValueProperty.UndecodableProperty) anObject).clone(); } return cloneObject(anObject, entityForObject(anObject)); } /** * Internally used during coding process.<br> * Build and returns new element given an object <code>anObject</code> and an XML document <code>aDocument</code> * * @param anObject * an <code>Object</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Element</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ private ModelEntity entityForObject(Object anObject) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { ModelEntity modelEntity = null; if (anObject == null) { throw new InvalidObjectSpecificationException("Object is null"); } // Search the right ModelEntity from class name // NB: the best one is the more specialized. Class<?> searchedClass = anObject.getClass(); while (searchedClass != null && xmlMapping.entityWithClassName(searchedClass.getName()) == null) { searchedClass = searchedClass.getSuperclass(); } if (searchedClass != null) { modelEntity = xmlMapping.entityWithClassName(searchedClass.getName()); } if (modelEntity == null) { throw new InvalidModelException("Tag matching '" + anObject.getClass().getName() + "' not found in model"); } return modelEntity; } /** * Internally used during coding process.<br> * Build and returns new element given an object <code>anObject</code>, an XML document <code>aDocument</code> and a model entity * <code>aModelEntity</code> * * @param anObject * an <code>Object</code> value * @param aModelEntity * a <code>ModelEntity</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Element</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ private Object cloneObject(Object anObject, ModelEntity aModelEntity) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { Object returnedObject; ModelProperty modelProperty; if (anObject == null) { return null; } // Is this object already cloned ? returnedObject = alreadyCloned.get(anObject); if (returnedObject != null) { // Already cloned object return returnedObject; } // Clone new object returnedObject = decoder.instanciateNewObject(aModelEntity); alreadyCloned.put(anObject, returnedObject); for (Enumeration e = aModelEntity.getModelProperties(); e.hasMoreElements();) { modelProperty = (ModelProperty) e.nextElement(); try { cloneProperty(anObject, returnedObject, modelProperty); } catch (AccessorInvocationException ex) { // May happen when a primitive object is cast to a primitive // (eg an Integer null value casted to int) } } // end of for () return returnedObject; } private void runCloningFinalization() throws AccessorInvocationException { Object[] params = { builder }; for (Enumeration e = alreadyCloned.elements(); e.hasMoreElements();) { Object next = e.nextElement(); ModelEntity entity = xmlMapping.entityWithClassName(next.getClass().getName()); try { if (xmlMapping.hasBuilderClass()) { if (entity.hasFinalizerWithParameter()) { entity.getFinalizerWithParameter().invoke(next, params); } else if (entity.hasFinalizerWithoutParameter()) { entity.getFinalizerWithoutParameter().invoke(next, (Object[]) null); } } } catch (IllegalAccessException e1) { e1.printStackTrace(); } catch (InvocationTargetException e2) { e2.getTargetException().printStackTrace(); throw new AccessorInvocationException("Exception " + e2.getClass().getName() + " caught during finalization.", e2); } } for (Enumeration e = alreadyCloned.keys(); e.hasMoreElements();) { Object next = e.nextElement(); alreadyCloned.remove(next); } } /** * Internally used during coding process.<br> * Build and returns new element given an object <code>anObject</code>, an XML document <code>aDocument</code> and a model entity * <code>aModelEntity</code> * * @param anObject * an <code>Object</code> value * @param aModelEntity * a <code>ModelEntity</code> value * @param aDocument * a <code>Document</code> value * @return an <code>Element</code> value * @exception InvalidObjectSpecificationException * if an error occurs * @exception InvalidModelException * if an error occurs * @exception AccessorInvocationException * if an error occurs during accessor invocation */ private void cloneProperty(Object clonedObject, Object newObject, ModelProperty modelProperty) throws InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException { if (!modelProperty.isCopyable()) { return; } boolean isCloneable = modelProperty.isCloneable(); // First, get the key-value property KeyValueProperty keyValueProperty = modelProperty.getKeyValueProperty(); if (keyValueProperty instanceof SingleKeyValueProperty) { SingleKeyValueProperty singleKeyValueProperty = (SingleKeyValueProperty) keyValueProperty; boolean primitiveProperty = singleKeyValueProperty.classIsPrimitive(stringEncoder); if (primitiveProperty) { String stringValue = KeyValueDecoder.valueForKey(clonedObject, singleKeyValueProperty, stringEncoder); if (stringValue != null) { KeyValueCoder.setValueForKey(newObject, stringValue, singleKeyValueProperty, stringEncoder); } } else { Object newValue = KeyValueDecoder.objectForKey(clonedObject, singleKeyValueProperty); if (isCloneable && newValue != null) { newValue = cloneObject(newValue); } if (newValue != null) { KeyValueCoder.setObjectForKey(newObject, newValue, singleKeyValueProperty); } } } else if (keyValueProperty instanceof VectorKeyValueProperty) { VectorKeyValueProperty vectorKeyValueProperty = (VectorKeyValueProperty) keyValueProperty; List<?> values = KeyValueDecoder.vectorForKey(clonedObject, (VectorKeyValueProperty) keyValueProperty); if (values != null) { List<Object> newVector = new Vector<Object>(); for (Object o : values) { if (isCloneable && o != null) { o = cloneObject(o); } newVector.add(o); } KeyValueCoder.setVectorForKey(newObject, newVector, vectorKeyValueProperty); } } else if (keyValueProperty instanceof ArrayKeyValueProperty) { ArrayKeyValueProperty arrayKeyValueProperty = (ArrayKeyValueProperty) keyValueProperty; Object[] values = KeyValueDecoder.arrayForKey(clonedObject, arrayKeyValueProperty); if (values != null) { List<Object> newVector = new Vector<Object>(); for (int i = 0; i < values.length; i++) { Object temp = values[i]; if (isCloneable && temp != null) { temp = cloneObject(temp); } newVector.add(temp); } KeyValueCoder.setArrayForKey(newObject, newVector, arrayKeyValueProperty); } } else if (keyValueProperty instanceof HashtableKeyValueProperty) { HashtableKeyValueProperty hashtableKeyValueProperty = (HashtableKeyValueProperty) keyValueProperty; Map<?, ?> values = KeyValueDecoder.hashtableForKey(clonedObject, hashtableKeyValueProperty); if (values != null) { Map<Object, Object> newHashtable = new Hashtable<Object, Object>(); for (Entry<?, ?> e : values.entrySet()) { Object key = e.getKey(); Object newValue = e.getValue(); Object newKey; if (key instanceof XMLSerializable && isCloneable && key != null) { newKey = cloneObject(key); } else { newKey = key; } if (isCloneable && newValue != null) { newValue = cloneObject(newValue); } newHashtable.put(newKey, newValue); } KeyValueCoder.setHashtableForKey(newObject, newHashtable, hashtableKeyValueProperty); } } } }