package org.openflexo.model.factory; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javassist.util.proxy.ProxyObject; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.output.Format; import org.jdom2.output.LineSeparator; import org.jdom2.output.XMLOutputter; import org.openflexo.model.ModelContextLibrary; import org.openflexo.model.ModelEntity; import org.openflexo.model.ModelProperty; import org.openflexo.model.StringEncoder; import org.openflexo.model.annotations.XMLElement; import org.openflexo.model.exceptions.InvalidDataException; import org.openflexo.model.exceptions.ModelDefinitionException; import org.openflexo.model.exceptions.ModelExecutionException; import org.openflexo.model.exceptions.RestrictiveSerializationException; class XMLSerializer { public static final String ID = "id"; public static final String ID_REF = "idref"; // Keys are objects and values are ObjectReference private Map<Object, ObjectReference> objectReferences; /** * Stores already serialized objects where key is the serialized object and value is a * * <pre> * Object * </pre> * * instance coding the unique identifier of the object */ private Map<Object, Object> alreadySerialized; private int id = 0; private final ModelFactory modelFactory; private final SerializationPolicy policy; public XMLSerializer(ModelFactory modelFactory) { this(modelFactory, SerializationPolicy.PERMISSIVE); } public XMLSerializer(ModelFactory modelFactory, SerializationPolicy policy) { this.modelFactory = modelFactory; this.policy = policy; } private StringEncoder getStringEncoder() { return modelFactory.getStringEncoder(); } public Document serializeDocument(Object object, OutputStream out) throws IOException { Document builtDocument = new Document(); try { id = 0; objectReferences = new HashMap<Object, ObjectReference>(); alreadySerialized = new HashMap<Object, Object>(); Element rootElement = serializeElement(object, null); postProcess(rootElement); builtDocument.setRootElement(rootElement); Format prettyFormat = Format.getPrettyFormat(); prettyFormat.setLineSeparator(LineSeparator.SYSTEM); XMLOutputter outputter = new XMLOutputter(prettyFormat); try { outputter.output(builtDocument, out); } catch (IOException e) { e.printStackTrace(); } out.flush(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ModelDefinitionException e) { // TODO Auto-generated catch block e.printStackTrace(); } return builtDocument; } public String buildXMLOutput(Document doc) { StringWriter writer = new StringWriter(); Format prettyFormat = Format.getPrettyFormat(); prettyFormat.setLineSeparator(LineSeparator.SYSTEM); XMLOutputter outputter = new XMLOutputter(prettyFormat); try { outputter.output(doc, writer); } catch (IOException e) { e.printStackTrace(); } return writer.toString(); } private String generateReference(Object o) { return String.valueOf(id++); } private <I> Element serializeElement(Object object, XMLElement context) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, ModelDefinitionException { Element returned; if (object instanceof ProxyObject) { ProxyMethodHandler<I> handler = (ProxyMethodHandler<I>) ((ProxyObject) object).getHandler(); ModelEntity<I> modelEntity = handler.getModelEntity(); Class<I> implementedInterface = modelEntity.getImplementedInterface(); boolean serializeModelEntityName = false; XMLElement xmlElement = modelEntity.getXMLElement(); String xmlTag = modelEntity.getXMLTag(); if (modelFactory.getModelContext().getModelEntity(implementedInterface) == null) { serializeModelEntityName = true; switch (policy) { case EXTENSIVE: List<ModelEntity<?>> upperEntities = modelFactory.getModelContext().getUpperEntities(object); if (upperEntities.size() == 0) { throw new ModelDefinitionException("Cannot serialize object of type: " + object.getClass().getName() + " in context " + context.xmlTag() + ". No model entity could be found in the model mapping"); } else if (upperEntities.size() > 1) { throw new ModelDefinitionException("Ambiguous entity for object " + object.getClass().getName() + ". More than one entities are known in this model mapping."); } ModelEntity e = upperEntities.get(0); xmlTag = e.getXMLTag(); modelEntity = ModelContextLibrary.getModelContext(implementedInterface).getModelEntity(implementedInterface); break; case PERMISSIVE: upperEntities = modelFactory.getModelContext().getUpperEntities(object); if (upperEntities.size() == 0) { throw new ModelDefinitionException("Cannot serialize object of type: " + object.getClass().getName() + " in context " + context.xmlTag() + ". No model entity could be found in the model mapping"); } else if (upperEntities.size() > 1) { throw new ModelDefinitionException("Ambiguous entity for object " + object.getClass().getName() + ". More than one entities are known in this model mapping."); } modelEntity = (ModelEntity<I>) upperEntities.get(0); break; case RESTRICTIVE: throw new RestrictiveSerializationException("Entity of type " + implementedInterface.getName() + " cannot be serialized in this model context"); } } String contextString = context != null ? context.context() : ""; String elementName = contextString + xmlTag; String namespace = null; if (xmlElement != null) { namespace = xmlElement.namespace() != XMLElement.NO_NAME_SPACE ? xmlElement.namespace() : null; } // Is this object already serialized ? Object reference = alreadySerialized.get(object); if (reference == null) { // First time i see this object try { handler.setSerializing(true); // Put this object in alreadySerialized objects reference = generateReference(object); alreadySerialized.put(object, reference); if (xmlElement != null) { returned = new Element(elementName, namespace); returned.setAttribute(ID, reference.toString()); if (serializeModelEntityName) { returned.setAttribute(PAMELAConstants.MODEL_ENTITY_ATTRIBUTE, handler.getModelEntity() .getImplementedInterface().getName(), PAMELAConstants.NAMESPACE); if (handler.getOverridingSuperClass() != null) { returned.setAttribute(PAMELAConstants.CLASS_ATTRIBUTE, handler.getOverridingSuperClass().getName(), PAMELAConstants.NAMESPACE); } } Iterator<ModelProperty<? super I>> properties = modelEntity.getProperties(); while (properties.hasNext()) { ModelProperty<? super I> p = properties.next(); if (p.getXMLAttribute() != null) { Object oValue = handler.invokeGetter(p); if (oValue != null) { String value; try { value = getStringEncoder().toString(oValue); returned.setAttribute(p.getXMLTag(), value); } catch (InvalidDataException e) { throw new ModelExecutionException(e); } } } else if (p.getXMLElement() != null) { XMLElement propertyXMLElement = p.getXMLElement(); switch (p.getCardinality()) { case SINGLE: Object oValue = handler.invokeGetter(p); if (oValue != null) { Element propertyElement = serializeElement(oValue, propertyXMLElement); returned.addContent(propertyElement); } break; case LIST: List<?> values = (List<?>) handler.invokeGetter(p); for (Object o : values) { if (o != null) { Element propertyElement2 = serializeElement(o, propertyXMLElement); returned.addContent(propertyElement2); } } break; case MAP: throw new UnsupportedOperationException("Cannot serialize maps for now"); default: break; } } } } else if (getStringEncoder().isConvertable(modelEntity.getImplementedInterface())) { try { returned = new Element(elementName, namespace); returned.setText(getStringEncoder().toString(object)); returned.setAttribute(ID, reference.toString()); } catch (InvalidDataException e) { // This should not happen. If it does, then it is likely that the StringEncoder class is messed up by saying // that a // given type is convertable but does not convert it when asked throw new ModelDefinitionException( "Hu hoh, really don't know how you got into this state: your object is string convertable but conversion could not be performed", e); } } else { throw new ModelDefinitionException("No XML element for " + object); } } finally { handler.setSerializing(false); } } else { // This object was already serialized somewhere, only put an idref // Debugging.debug ("This object has already been serialized // somewhere "+anObject); returned = new Element(elementName, namespace); returned.setAttribute(ID_REF, reference.toString()); } // OK, Element is now built ObjectReference ref = objectReferences.get(object); if (ref != null) { ref.notifyNewElementReference(xmlElement, context, returned); } else { ref = new ObjectReference(object, xmlElement, context, returned); objectReferences.put(object, ref); } return returned; } else if (getStringEncoder().isConvertable(object.getClass())) { try { returned = new Element(context.xmlTag(), context.namespace()); returned.setText(getStringEncoder().toString(object)); return returned; } catch (InvalidDataException e) { throw new ModelDefinitionException( "Hu hoh, really don't know how you got into this state: your object is string convertable but conversion could not be performed", e); } } else { throw new ModelDefinitionException("Cannot serialize non-proxy object " + object); } } private void postProcess(Element rootElement) { int requiredSwaps = objectReferences.size(); while (requiredSwaps > 0) { int newRequiredSwaps = 0; for (ObjectReference ref : objectReferences.values()) { if (!ref.postProcess()) { newRequiredSwaps++; } } if (newRequiredSwaps == requiredSwaps) { requiredSwaps = 0; // To avoid infinite loop } else { requiredSwaps = newRequiredSwaps; } } } protected static class ObjectReference { protected Object serializedObject; protected ElementReference primaryElement; protected Vector<ElementReference> referenceElements; protected ObjectReference(Object anObject, XMLElement xmlElement, XMLElement context, Element anElement) { super(); serializedObject = anObject; referenceElements = new Vector<ElementReference>(); addElementReference(new ElementReference(xmlElement, context, anElement)); } protected void delete() { serializedObject = null; primaryElement.delete(); for (Enumeration<ElementReference> en = referenceElements.elements(); en.hasMoreElements();) { ElementReference next = en.nextElement(); next.delete(); } primaryElement = null; referenceElements.clear(); referenceElements = null; } protected void notifyNewElementReference(XMLElement xmlElement, XMLElement context, Element anElement) { addElementReference(new ElementReference(xmlElement, context, anElement)); } /* * protected int getId() { return id; } * * protected void changeId(int newId) { // System.out.println("changeId() to "+newId+" for "+primaryElement.element); if * ((primaryElement != null) && (primaryElement.element != null)) changeIdForElement(newId,primaryElement.element); for (Enumeration * en=referenceElements.elements(); en.hasMoreElements();) { ElementReference next = (ElementReference)en.nextElement(); if * (next.element != null) changeIdForElement(newId,next.element); } } * * protected void changeIdForElement(int newId, Element element) { if (element.getAttribute(ID) != null) { * element.setAttribute(ID,encodeInteger(newId)); } else if (element.getAttribute(ID_REF) != null) { * element.setAttribute(ID_REF,encodeInteger(newId)); } } */ private void addElementReference(ElementReference elementReference) { if (isFullyDescribed(elementReference.element)) { // System.out.println("object: "+serializedObject.getClass().getName()+"/"+serializedObject.hashCode()+" PRIMARY "+outputter.outputString(elementReference.element)); primaryElement = elementReference; } else { // System.out.println("object: "+serializedObject.getClass().getName()+"/"+serializedObject.hashCode()+" "+outputter.outputString(elementReference.element)); referenceElements.add(elementReference); } } protected boolean isFullyDescribed(Element element) { return element.getAttribute(ID) != null; } private boolean done = false; protected boolean postProcess() { // System.out.println("***** postProcess("+this+")"); // System.out.println("PRIMARY= (primary="+primaryElement.isPrimary()+") "+primaryElement.element); /* * for (ElementReference ref : referenceElements) { System.out.println("REFERENCE= (primary="+ref.isPrimary()+") "+ref.element); * } */ if (done) { return true; } if (primaryElement.context != null && primaryElement.context.primary()) { // That's OK return done = true; } else { // It might be NOK for (ElementReference ref : referenceElements) { if (ref.context != null && ref.context.primary()) { return setAsNewPrimaryElement(ref); } } done = true; return true; } } private boolean setAsNewPrimaryElement(ElementReference newElementReference) { // System.out.println("Need to exchange "+primaryElement.element+" and "+newElementReference.element); if (exchange(primaryElement, newElementReference)) { referenceElements.remove(newElementReference); referenceElements.add(primaryElement); primaryElement = newElementReference; return done = true; } else { return false; } } private boolean exchange(ElementReference ref1, ElementReference ref2) { Element element1 = ref1.element; Element element2 = ref2.element; Element father1 = element1.getParentElement(); Element father2 = element2.getParentElement(); if (isAncestorOf(element1, element2)) { // In this case, do nothing and try later (in another loop) return false; } else if (isAncestorOf(element2, element1)) { // In this case, do nothing and try later (in another loop) return false; } else { int index1 = father1.indexOf(element1); father1.removeContent(index1); int index2 = father2.indexOf(element2); father2.removeContent(index2); father2.addContent(index2, element1); father1.addContent(index1, element2); if (!ref1.xmlTag.equals(ref2.xmlTag)) { // System.out.println("Exchange names "+ref1.xmlTag+" and "+ref2.xmlTag); element1.setName(ref2.xmlTag); element2.setName(ref1.xmlTag); } return true; } } /* * private int idForElement(Element el) { int returned = decodeAsInteger(el.getAttributeValue(ID)); if (returned == -1) returned = * decodeAsInteger(el.getAttributeValue(ID_REF)); return returned; } */ private boolean isAncestorOf(Element e1, Element e2) { return e1.isAncestor(e2); } protected class ElementReference { protected XMLElement xmlElement; protected XMLElement context; protected String xmlTag; protected Element element; protected ElementReference(XMLElement xmlElement, XMLElement context, Element anElement) { super(); this.xmlElement = xmlElement; this.context = context; element = anElement; xmlTag = anElement.getName(); } protected void delete() { xmlElement = null; context = null; xmlTag = null; element = null; } protected boolean isPrimary() { return context != null && context.primary(); } } } }