package org.openflexo.model.factory; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.filter.ElementFilter; import org.jdom2.input.SAXBuilder; import org.openflexo.model.ModelContext.ModelPropertyXMLTag; import org.openflexo.model.ModelEntity; import org.openflexo.model.ModelProperty; import org.openflexo.model.StringEncoder; import org.openflexo.model.exceptions.InvalidDataException; import org.openflexo.model.exceptions.ModelDefinitionException; import org.openflexo.model.exceptions.ModelExecutionException; import org.openflexo.model.exceptions.RestrictiveDeserializationException; class XMLDeserializer { public static final String ID = "id"; public static final String ID_REF = "idref"; private ModelFactory modelFactory; private Map<String, Element> index; /** * Stores already serialized objects where value is the serialized object and key is an object coding the unique identifier of the * object */ private Map<Object, Object> alreadyDeserialized; private List<ProxyMethodHandler<?>> deserializingHandlers; private final DeserializationPolicy policy; public XMLDeserializer(ModelFactory factory) { this(factory, DeserializationPolicy.PERMISSIVE); } public XMLDeserializer(ModelFactory factory, DeserializationPolicy policy) { this.modelFactory = factory; this.policy = policy; alreadyDeserialized = new HashMap<Object, Object>(); deserializingHandlers = new ArrayList<ProxyMethodHandler<?>>(); } private StringEncoder getStringEncoder() { return modelFactory.getStringEncoder(); } public Object deserializeDocument(InputStream in) throws IOException, JDOMException, InvalidDataException, ModelDefinitionException { alreadyDeserialized.clear(); Document dataDocument = parseXMLData(in); Element rootElement = dataDocument.getRootElement(); return buildObjectFromNode(rootElement); } public Object deserializeDocument(String xml) throws IOException, JDOMException, InvalidDataException, ModelDefinitionException { alreadyDeserialized.clear(); Document dataDocument = parseXMLData(xml); Element rootElement = dataDocument.getRootElement(); return buildObjectFromNode(rootElement); } private Object buildObjectFromNode(Element node) throws InvalidDataException, ModelDefinitionException { // System.out.println("What to do with "+node+" ?"); ModelEntity<?> modelEntity = modelFactory.getModelContext().getModelEntity(node.getName()); Object object = buildObjectFromNodeAndModelEntity(node, modelEntity); for (ProxyMethodHandler<?> handler : deserializingHandlers) { handler.setDeserializing(false); } return object; } private <I> Object buildObjectFromNodeAndModelEntity(Element node, ModelEntity<I> modelEntity) throws InvalidDataException, ModelDefinitionException { Object currentDeserializedReference = null; Attribute idAttribute = node.getAttribute(ID); Attribute idrefAttribute = node.getAttribute(ID_REF); if (idrefAttribute != null) { // This seems to be an already deserialized object Object reference; reference = idrefAttribute.getValue(); Object referenceObject = alreadyDeserialized.get(reference); if (referenceObject == null) { // Try to find this object elsewhere in the document // NOTE: This should never occur, except if the file was // manually edited, or // if the file was generated BEFORE development of ordered // properties feature // TODO: Throw here an error in future release but for backward compatibility we leave it for now. Element idRefElement = findElementWithId(idrefAttribute.getValue()); if (idRefElement != null) { return buildObjectFromNodeAndModelEntity(idRefElement, modelEntity); } throw new InvalidDataException("No reference to object with identifier " + reference); } else { // No need to go further: i've got my object // Debugging.debug ("Stopping decoding: object found as a // reference "+reference+" "+referenceObject); return referenceObject; } } if (idAttribute != null) { currentDeserializedReference = idAttribute.getValue(); Object referenceObject = alreadyDeserialized.get(currentDeserializedReference); if (referenceObject != null) { // No need to go further: i've got my object return referenceObject; } } // I need to rebuild it I returned; String text = node.getText(); if (text != null && getStringEncoder().isConvertable(modelEntity.getImplementedInterface())) { // GPO: I am not sure this is still useful. try { returned = getStringEncoder().fromString(modelEntity.getImplementedInterface(), text); } catch (InvalidDataException e) { throw new ModelExecutionException(e); } } else { returned = modelFactory._newInstance(modelEntity.getImplementedInterface(), policy == DeserializationPolicy.EXTENSIVE); } if (currentDeserializedReference != null) { alreadyDeserialized.put(currentDeserializedReference, returned); } ProxyMethodHandler<I> handler = modelFactory.getHandler(returned); deserializingHandlers.add(handler); handler.setDeserializing(true); for (Attribute attribute : node.getAttributes()) { ModelProperty<? super I> property = modelEntity.getPropertyForXMLAttributeName(attribute.getName()); if (property == null) { if (attribute.getNamespace().equals(PAMELAConstants.NAMESPACE) && (attribute.getName().equals(PAMELAConstants.CLASS_ATTRIBUTE) || attribute.getName().equals( PAMELAConstants.MODEL_ENTITY_ATTRIBUTE))) { continue; } if (attribute.getName().equals(ID) || attribute.getName().equals(ID_REF)) { continue; } switch (policy) { case PERMISSIVE: continue; case RESTRICTIVE: throw new RestrictiveDeserializationException("No attribute found for the attribute named: " + attribute.getName()); case EXTENSIVE: // TODO: handle extra values break; } } Object value = getStringEncoder().fromString(property.getType(), attribute.getValue()); if (value != null) { handler.invokeSetterForDeserialization(property, value); } } for (Element child : node.getChildren()) { ModelPropertyXMLTag<I> modelPropertyXMLTag = modelFactory.getModelContext().getPropertyForXMLTag(modelEntity, child.getName()); ModelProperty<? super I> property = null; ModelEntity<?> entity = null; if (modelPropertyXMLTag != null) { property = modelPropertyXMLTag.getProperty(); entity = modelPropertyXMLTag.getAccessedEntity(); } else if (policy == DeserializationPolicy.RESTRICTIVE) { throw new RestrictiveDeserializationException("Element with name does not fit any properties within entity " + modelEntity); } Class<?> implementedInterface = null; Class<?> implementingClass = null; String entityName = child.getAttributeValue(PAMELAConstants.MODEL_ENTITY_ATTRIBUTE, PAMELAConstants.NAMESPACE); String className = child.getAttributeValue(PAMELAConstants.CLASS_ATTRIBUTE, PAMELAConstants.NAMESPACE); if (entityName != null) { try { implementedInterface = Class.forName(entityName); } catch (ClassNotFoundException e) { // TODO: log something here } switch (policy) { case PERMISSIVE: break; case RESTRICTIVE: break; case EXTENSIVE: if (entityName != null) { entity = modelFactory.importClass(implementedInterface); if (className != null) { try { implementingClass = Class.forName(className); if (implementedInterface.isAssignableFrom(implementingClass)) { modelFactory.setImplementingClassForInterface((Class) implementingClass, implementedInterface, policy == DeserializationPolicy.EXTENSIVE); } else { throw new ModelExecutionException(className + " does not implement " + implementedInterface + " for node " + child.getName()); } } catch (ClassNotFoundException e) { // TODO: log something here } } } break; } if (implementedInterface != null) { if (policy == DeserializationPolicy.EXTENSIVE) { entity = modelFactory.getExtendedContext().getModelEntity(implementedInterface); } else { entity = modelFactory.getModelContext().getModelEntity(implementedInterface); } } if (entity == null && policy == DeserializationPolicy.RESTRICTIVE) { if (entityName != null) { throw new RestrictiveDeserializationException("Entity " + entityName + " is not part of this model context"); } else { throw new RestrictiveDeserializationException("No entity found for tag " + child.getName()); } } } if (property != null) { Object value = null; if (entity != null && !getStringEncoder().isConvertable(property.getType())) { value = buildObjectFromNodeAndModelEntity(child, entity); } else if (getStringEncoder().isConvertable(property.getType())) { value = getStringEncoder().fromString(property.getType(), child.getText()); } else { // Should not happen throw new ModelExecutionException("Found property " + property + " but was unable to deserialize the content of node " + child); } switch (property.getCardinality()) { case SINGLE: handler.invokeSetterForDeserialization(property, value); break; case LIST: handler.invokeAdderForDeserialization(property, value); break; case MAP: throw new UnsupportedOperationException("Cannot deserialize maps for now"); default: break; } } } return returned; } private class MatchingElement { private Element element; private ModelEntity<?> modelEntity; private MatchingElement(Element element, ModelEntity<?> modelEntity) { super(); this.element = element; this.modelEntity = modelEntity; } @Override public String toString() { return element.toString() + "/" + modelEntity; } } protected Document parseXMLData(InputStream xmlStream) throws IOException, JDOMException { SAXBuilder parser = new SAXBuilder(); Document reply = parser.build(xmlStream); makeIndex(reply); return reply; } protected Document parseXMLData(String xml) throws IOException, JDOMException { SAXBuilder parser = new SAXBuilder(); Document reply = parser.build(new StringReader(xml)); makeIndex(reply); return reply; } static private class ElementWithIDFilter extends ElementFilter { public ElementWithIDFilter() { super(); } @Override public Element filter(Object arg0) { Element element = super.filter(arg0); if (element != null && element.getAttributeValue("id") != null) { return element; } return null; } } public Document makeIndex(Document doc) { index = new Hashtable<String, Element>(); Iterator<Element> it = doc.getDescendants(new ElementWithIDFilter()); Element e = null; while (it.hasNext()) { e = it.next(); index.put(e.getAttributeValue("id"), e); } return doc; } private Element findElementWithId(String id) { return index.get(id); } }