All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unirostock.sems.bives.ds.Patch Maven / Gradle / Ivy

Go to download

BiVeS - BioModel Version Control System This package provides core functionality

The newest version!
/**
 * 
 */
package de.unirostock.sems.bives.ds;

import java.net.URI;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.jdom2.Comment;
import org.jdom2.Document;
import org.jdom2.Element;

import de.binfalse.bflog.LOGGER;
import de.unirostock.sems.bives.algorithm.DiffAnnotator;
import de.unirostock.sems.bives.algorithm.NodeConnection;
import de.unirostock.sems.bives.algorithm.SimpleConnectionManager;
import de.unirostock.sems.bives.algorithm.general.DefaultDiffAnnotator;
import de.unirostock.sems.bives.tools.BivesTools;
import de.unirostock.sems.comodi.Change;
import de.unirostock.sems.comodi.ChangeFactory;
import de.unirostock.sems.xmlutils.ds.DocumentNode;
import de.unirostock.sems.xmlutils.ds.TextNode;
import de.unirostock.sems.xmlutils.ds.TreeNode;
import de.unirostock.sems.xmlutils.tools.XmlTools;


/**
 * The Class Patch storing all operations necessary to transfer one document into another.
 *
 * @author Martin Scharm
 * @see delta documentation
 */
public class Patch
{
	
	/** The default file name of the path. This name will be used in the annotations to refer to elements in the path file. */
	public static URI PATCH_FILE_NAME = URI.create ("file://bives-differences.patch");
	
	/** The actual current patch file name. */
	private URI patchFileName;
	
	/** The change annotation factory. */
	private ChangeFactory changeAnnotationFactory;
	
	/** The annotator for differences. */
	private DiffAnnotator diffAnnotator;
	
	/** The latest used id in this document. */
	private int id;
	
	/** The XML document that will contain all operations. */
	private Document xmlDoc;
	
	/** The nodes rooting insert operations. */
	private Element insert;
	/** The nodes rooting delete operations. */
	private Element delete;
	/** The nodes rooting update operations. */
	private Element update;
	/** The nodes rooting move operations. */
	private Element move;
	//, copy, glue;
	
	/** The fullDiff flag indicating whether this diff is a full diff. */
	private boolean fullDiff;
	
	
	/**
	 * Instantiates a new patch specifying the file name of the resulting patch and an annotator to annotate the differences.
	 * This will result in a patch of type fullDiff.
	 * 
	 * @param patchFileName the file name of the final patch
	 * @param diffAnnotator the annotator for detected differences
	 * @see fullDiff documentation
	 */
	public Patch (URI patchFileName, DiffAnnotator diffAnnotator)
	{
		fullDiff = true;
		init (patchFileName, diffAnnotator);
	}
	
	/**
	 * Instantiates a new patch specifying. an annotator to annotate the differences.
	 * This constructor assumes that the final patch will be called {@link #PATCH_FILE_NAME}.
	 * This will result in a patch of type fullDiff.
	 *
	 * @param diffAnnotator the annotator for detected differences
	 * @see fullDiff documentation
	 */
	public Patch (DiffAnnotator diffAnnotator)
	{
		fullDiff = true;
		init (PATCH_FILE_NAME, diffAnnotator);
	}
	
	/**
	 * Instantiates a new patch specifying the fullDiff flag, the file name of the resulting patch, and an annotator to annotate the differences.
	 * If the fullDiff flag is set to false only a partially delta will be generated.
	 * Thus, the delta will briefly describe the modifications, but cannot be used to transform one version of a document into the other.
	 * Set it to true to obtain a full delta.
	 *
	 * @param fullDiff the fullDiff flag
	 * @param patchFileName the file name of the final patch
	 * @param diffAnnotator the annotator for detected differences
	 * @see fullDiff documentation
	 */
	public Patch (boolean fullDiff, URI patchFileName, DiffAnnotator diffAnnotator)
	{
		this.fullDiff = fullDiff;
		init (patchFileName, diffAnnotator);
	}
	
	/**
	 * Instantiates a new patch specifying the fullDiff flag and an annotator to annotate the differences.
	 * If the fullDiff flag is set to false only a partial delta will be generated.
	 * Thus, the delta will briefly describe the modifications, but cannot be used to transform one version of a document into the other.
	 * Set it to true to obtain a full delta.
	 * This constructor assumes that the final patch will be called {@link #PATCH_FILE_NAME}.
	 *
	 * @param fullDiff the fullDiff flag
	 * @param diffAnnotator the annotator for detected differences
	 * @see fullDiff documentation
	 */
	public Patch (boolean fullDiff, DiffAnnotator diffAnnotator)
	{
		this.fullDiff = fullDiff;
		init (PATCH_FILE_NAME, diffAnnotator);
	}
	
	
	/**
	 * Instantiates a new patch specifying the file name of the resulting patch.
	 * This constructor assumes that the final patch will be called {@link #PATCH_FILE_NAME}.
	 * To annotate the differences a new instance of the {@link de.unirostock.sems.bives.algorithm.general.DefaultDiffAnnotator DefaultDiffAnnotator} will be created.
	 * This will result in a patch of type fullDiff.
	 * 
	 * @param patchFileName the file name of the final patch
	 * @see fullDiff documentation
	 */
	public Patch (URI patchFileName)
	{
		fullDiff = true;
		init (patchFileName, new DefaultDiffAnnotator ());
	}
	
	/**
	 * Instantiates a new patch.
	 * This constructor assumes that the final patch will be called {@link #PATCH_FILE_NAME}.
	 * To annotate the differences a new instance of the {@link de.unirostock.sems.bives.algorithm.general.DefaultDiffAnnotator DefaultDiffAnnotator} will be created.
	 * This will result in a patch of type fullDiff.
	 * 
	 * 
	 * @see fullDiff documentation
	 * 
	 */
	public Patch ()
	{
		fullDiff = true;
		init (PATCH_FILE_NAME, new DefaultDiffAnnotator ());
	}
	
	/**
	 * Instantiates a new patch specifying the fullDiff flag and the file name of the resulting patch.
	 * If the fullDiff flag is set to false only a partial delta will be generated.
	 * Thus, the delta will briefly describe the modifications, but cannot be used to transform one version of a document into the other.
	 * Set it to true to obtain a full delta.
	 * To annotate the differences a new instance of the {@link de.unirostock.sems.bives.algorithm.general.DefaultDiffAnnotator DefaultDiffAnnotator} will be created.
	 * 
	 *
	 * @param fullDiff the fullDiff flag
	 * @param patchFileName the file name of the final patch
	 * @see fullDiff documentation
	 */
	public Patch (boolean fullDiff, URI patchFileName)
	{
		this.fullDiff = fullDiff;
		init (patchFileName, new DefaultDiffAnnotator ());
	}
	
	/**
	 * Instantiates a new patch specifying the fullDiff flag.
	 * If the fullDiff flag is set to false only a partial delta will be generated.
	 * Thus, the delta will briefly describe the modifications, but cannot be used to transform one version of a document into the other.
	 * Set it to true to obtain a full delta.
	 * This constructor assumes that the final patch will be called {@link #PATCH_FILE_NAME}.
	 * To annotate the differences a new instance of the {@link de.unirostock.sems.bives.algorithm.general.DefaultDiffAnnotator DefaultDiffAnnotator} will be created.
	 * 
	 * 
	 * @param fullDiff the fullDiff flag
	 * @see fullDiff documentation
	 */
	public Patch (boolean fullDiff)
	{
		this.fullDiff = fullDiff;
		init (PATCH_FILE_NAME, new DefaultDiffAnnotator ());
	}
	
	/**
	 * Gets the number of stored move operations.
	 *
	 * @return the number of moves
	 */
	public int getNumMoves ()
	{
		return move.getChildren ().size ();
	}
	
	/**
	 * Gets the number of stored update operations.
	 *
	 * @return the number of updates
	 */
	public int getNumUpdates ()
	{
		return update.getChildren ().size ();
	}
	
	/**
	 * Gets the number of stored delete operations.
	 *
	 * @return the number of deletes
	 */
	public int getNumDeletes ()
	{
		return delete.getChildren ().size ();
	}
	
	/**
	 * Gets the number of stored insert operations.
	 *
	 * @return the number of inserts
	 */
	public int getNumInserts ()
	{
		return insert.getChildren ().size ();
	}
	
	/**
	 * Gets the deletes.
	 *
	 * @return the deletes
	 */
	public Element getDeletes ()
	{
		return delete;
	}
	
	/**
	 * Gets the inserts.
	 *
	 * @return the inserts
	 */
	public Element getInserts ()
	{
		return insert;
	}
	
	/**
	 * Gets the updates.
	 *
	 * @return the updates
	 */
	public Element getUpdates ()
	{
		return update;
	}
	
	/**
	 * Gets the moves.
	 *
	 * @return the moves
	 */
	public Element getMoves ()
	{
		return move;
	}
	
	/**
	 * Gets the number of node changes.
	 * That is the number of differences in this patch that affect XML nodes.
	 *
	 * @return the number of node changes
	 */
	public int getNumNodeChanges ()
	{
		return insert.getChildren ("node").size ()
			+ delete.getChildren ("node").size ()
			+ update.getChildren ("node").size ()
			+ move.getChildren ("node").size ();
	}

	
	/**
	 * Gets the number of text changes.
	 * That is the number of differences in this patch that affect XML text nodes.
	 *
	 * @return the number of text changes
	 */
	public int getNumTextChanges ()
	{
		return insert.getChildren ("text").size ()
			+ delete.getChildren ("text").size ()
			+ update.getChildren ("text").size ()
			+ move.getChildren ("text").size ();
	}

	
	/**
	 * Gets the number of attribute changes.
	 * That is the number of differences in this patch that affect XML attributes.
	 *
	 * @return the number of attribute changes
	 */
	public int getNumAttributeChanges ()
	{
		return insert.getChildren ("attribute").size ()
			+ delete.getChildren ("attribute").size ()
			+ update.getChildren ("attribute").size ()
			+ move.getChildren ("attribute").size ();
	}
	
	/**
	 * Gets the document containing all changes.
	 * If inclAnnotations is set to true the patch will have an embedded RDF subtree with annotations of the differences.
	 *
	 * @param inclAnnotations should the annotations be embedded into the patch
	 * @return the document
	 */
	public Document getDocument (boolean inclAnnotations)
	{
		// add root element 
		Element rootElement = new Element ("bives");
		xmlDoc = new Document (rootElement);
		
		rootElement.setAttribute ("type", "fullDiff");// TODO: implement shortDiff
		rootElement.setAttribute ("id", "bivesPatch");
		
		rootElement.addContent (new Comment (BivesTools.getBivesVersion ()));
		
		rootElement.addContent (update.clone ());
		rootElement.addContent (delete.clone ());
		rootElement.addContent (insert.clone ());
		rootElement.addContent (move.clone ());
		
		if (inclAnnotations)
		{
			String xml = changeAnnotationFactory.getRdfXml ();
			try
			{
				rootElement.addContent (XmlTools.readDocument (xml.replaceAll (patchFileName.toString (), "")).getRootElement ().detach ());
			}
			catch (Exception e)
			{
				LOGGER.error (e, "wasn't able to read rdf-annotations subtree to add it to the diff");
			}
		}
		
		return xmlDoc;
	}
	
	
	/**
	 * Gets the number of annotations for the differences in this patch.
	 *
	 * @return the number annotations
	 */
	public int getNumAnnotations ()
	{
		return changeAnnotationFactory.getNumStatements ();
	}
	
	/**
	 * Gets the document containing all changes.
	 *
	 * @return the document
	 */
	public Document getDocument ()
	{
		return getDocument (true);
	}
	
	/**
	 * Gets the annotation document encoded in XML.
	 *
	 * @return the annotation document
	 */
	public String getAnnotationDocumentXml ()
	{
		return changeAnnotationFactory.getRdfXml ();
	}
	
	/**
	 * Gets the actual annotations.
	 *
	 * @return the annotations
	 */
	public ChangeFactory getAnnotations ()
	{
		return changeAnnotationFactory;
	}
	
	/**
	 * Initializes the patch.
	 * Creates the the nodes which will root the different kinds of operations.
	 * 
	 * @param patchFileName the file name of the final patch
	 * @param diffAnnotator the annotator to be used to annotate the differences
	 */
	private void init (URI patchFileName, DiffAnnotator diffAnnotator)
	{
		this.patchFileName = patchFileName;
		this.diffAnnotator = diffAnnotator;
		LOGGER.info ("initializing patch w/ fullDiff = ", fullDiff);
		id = 0;
		
		// create nodes for inserts/updates/moves tec
		update = new Element("update");
		delete = new Element("delete");
		insert = new Element("insert");
		move = new Element("move");
		
		changeAnnotationFactory = new ChangeFactory (this.patchFileName);
		
		LOGGER.info ("initialized patch");
	}
	
	/**
	 * Creates an attribute element. (not an attribute, but a whole node defining an operation on an attribute)
	 *
	 * @param nodeId the node id
	 * @param oldPath the old path of the node hosting this attribute, set null to omit
	 * @param newPath the new path of the node hosting this attribute, set null to omit
	 * @param name the name of the attribute
	 * @param oldValue the old value
	 * @param newValue the new value
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 * @return the created node
	 */
	private Element createAttributeElement (int nodeId, String oldPath, String newPath, String name, String oldValue, String newValue, int chainId)
	{
		LOGGER.info ("create attribute element for ", oldPath, " -> ", newPath);
		Element attribute = new Element("attribute");

		attribute.setAttribute ("name", name);
		attribute.setAttribute ("id", nodeId + "");
		
		if (chainId > 0)
			attribute.setAttribute ("triggeredBy", chainId + "");
		
		if (oldValue != null)
			attribute.setAttribute ("oldValue", oldValue);
		
		if (newValue != null)
			attribute.setAttribute ("newValue", newValue);

		if (oldPath != null)
			attribute.setAttribute ("oldPath", oldPath);
		
		if (newPath != null)
			attribute.setAttribute ("newPath", newPath);
		
		return attribute;
	}
	
	/**
	 * Creates a node element: A node in the XML delta defining an operation on a node.
	 *
	 * @param nodeId the node id
	 * @param oldParent the XPath to the old parent, set null to omit
	 * @param newParent the XPath to the new parent, set null to omit
	 * @param oldPath the old path to the node, set null to omit
	 * @param newPath the new path to the node, set null to omit
	 * @param oldChildNo the old child no, set < 1 to omit
	 * @param newChildNo the new child no, set < 1 to omit
	 * @param oldTag the old tag
	 * @param newTag the new tag
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 * @return the element
	 */
	private Element createNodeElement (int nodeId, String oldParent, String newParent, String oldPath, String newPath, int oldChildNo, int newChildNo, String oldTag, String newTag, int chainId)
	{
		LOGGER.info ("create node element for ", oldPath, " -> ", newPath);
		Element node = new Element("node");

		node.setAttribute ("id", "" + nodeId);
		
		if (chainId > 0)
			node.setAttribute ("triggeredBy", chainId + "");

		if (oldParent != null)
			node.setAttribute ("oldParent", oldParent);

		if (newParent != null)
			node.setAttribute ("newParent", newParent);

		if (oldChildNo > 0)
			node.setAttribute ("oldChildNo", "" + oldChildNo);
		
		if (newChildNo > 0)
			node.setAttribute ("newChildNo", newChildNo + "");
		
		if (oldPath != null)
			node.setAttribute ("oldPath", oldPath);
		
		if (newPath != null)
			node.setAttribute ("newPath", newPath);
		
		if (oldTag != null)
			node.setAttribute ("oldTag", oldTag);
		
		if (newTag != null)
			node.setAttribute ("newTag", newTag);
		
		return node;
	}
	
	/**
	 * Creates a text element.
	 *
	 * @param nodeId the node id
	 * @param oldParent the XPath to the old parent, set null to omit
	 * @param newParent the XPath to the new parent, set null to omit
	 * @param oldPath the old path to the node, set null to omit
	 * @param newPath the new path to the node, set null to omit
	 * @param oldChildNo the old child no, set < 1 to omit
	 * @param newChildNo the new child no, set < 1 to omit
	 * @param oldText the old text content
	 * @param newText the new text content
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 * @return the element
	 */
	private Element createTextElement (int nodeId, String oldParent, String newParent, String oldPath, String newPath, int oldChildNo, int newChildNo, String oldText, String newText, int chainId)
	{
		LOGGER.info ("create text element for ", oldPath, " -> ", newPath);
		Element node = new Element("text");
		
		node.setAttribute ("id", nodeId + "");
		
		if (chainId > 0)
			node.setAttribute ("triggeredBy", chainId + "");

		if (oldParent != null)
			node.setAttribute ("oldParent", oldParent);

		if (newParent != null)
			node.setAttribute ("newParent", newParent);

		if (oldChildNo > 0)
			node.setAttribute ("oldChildNo", "" + oldChildNo);
		
		if (newChildNo > 0)
			node.setAttribute ("newChildNo", "" + newChildNo);
		
		if (oldPath != null)
			node.setAttribute ("oldPath", oldPath);
		
		if (newPath != null)
			node.setAttribute ("newPath", newPath);

		if (fullDiff)
		{
			if (oldText != null)
				node.setAttribute ("oldText", oldText);
			
			if (newText != null)
				node.setAttribute ("newText", newText);
		}
		
		return node;
		
	}

	/**
	 * Mark a whole subtree as deleted.
	 *
	 * @param toDelete the node rooting the subtree to delete
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	public void deleteSubtree (TreeNode toDelete, int chainId)
	{
		switch (toDelete.getType ())
		{
			case TreeNode.DOC_NODE:
			{
				DocumentNode dnode = (DocumentNode) toDelete;
				int parentId = deleteNode (dnode, chainId);
				for (TreeNode tn : dnode.getChildren ())
					deleteSubtree (tn, parentId);
				break;
			}
			case TreeNode.TEXT_NODE:
				deleteNode ((TextNode) toDelete, chainId);
				break;
			default:
			{
				LOGGER.error ("unsupported tree node type for deletion...");
				throw new UnsupportedOperationException ("unsupported tree node type...");
			}
		}
	}

	/**
	 * Delete a single node.
	 *
	 * @param toDelete the node to delete
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	public void deleteNode (TreeNode toDelete, int chainId)
	{
		switch (toDelete.getType ())
		{
			case TreeNode.DOC_NODE:
				deleteNode ((DocumentNode) toDelete, chainId);
				break;
			case TreeNode.TEXT_NODE:
				deleteNode ((TextNode) toDelete, chainId);
				break;
			default:
			{
				LOGGER.error ("unsupported tree node type for deletion...");
				throw new UnsupportedOperationException ("unsupported tree node type...");
			}
		}
	}
	
	/**
	 * Delete a single document node.
	 *
	 * @param toDelete the node to delete
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 * @return the id of the delete-operation-node in the XML delta
	 */
	private int deleteNode (DocumentNode toDelete, int chainId)
	{
		LOGGER.info ("deleting node ", toDelete.getXPath ());
		int nodeId = ++id;
		
		Element diffElement = createNodeElement (nodeId, getParentXpath (toDelete), null, toDelete.getXPath (), null, getChildNo (toDelete), -1, toDelete.getTagName (), null, chainId);
		delete.addContent (diffElement);
		diffAnnotator.annotateDeletion (toDelete, diffElement, changeAnnotationFactory);
		
		if (!fullDiff)
			return nodeId;
		LOGGER.info ("checking attributes for full diff");
		Set attr = toDelete.getAttributes ();
		for (String a: attr)
			deleteAttribute (toDelete, a, nodeId);
		return nodeId;
	}

	/**
	 * Delete an attribute.
	 *
	 * @param node the node carrying the attribute
	 * @param attribute the attribute to delete
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	private void deleteAttribute (DocumentNode node, String attribute, int chainId)
	{
		LOGGER.info ("deleting attribute ", attribute, " of ", node.getXPath ());
		
		Element diffElement = createAttributeElement (++id, node.getXPath (), null, attribute, node.getAttributeValue (attribute), null, chainId);
		delete.addContent (diffElement);
		diffAnnotator.annotateDeletion (node, diffElement, changeAnnotationFactory);
	}
	
	/**
	 * Delete a single text node.
	 *
	 * @param toDelete the node to delete
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	private void deleteNode (TextNode toDelete, int chainId)
	{
		LOGGER.info ("deleting text of ", toDelete.getXPath ());
		
		Element diffElement = createTextElement (++id, getParentXpath (toDelete), null, toDelete.getXPath (), null, getChildNo (toDelete), -1, toDelete.getText (), null, chainId); 
		delete.addContent (diffElement);
		diffAnnotator.annotateDeletion (toDelete, diffElement, changeAnnotationFactory);
	}
	
	/**
	 * Mark a whole subtree as inserted.
	 *
	 * @param toInsert the node rooting the subtree to insert
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	public void insertSubtree (TreeNode toInsert, int chainId)
	{
		switch (toInsert.getType ())
		{
			case TreeNode.DOC_NODE:
				DocumentNode dnode = (DocumentNode) toInsert;
				int parentId = insertNode (dnode, chainId);
				for (TreeNode tn : dnode.getChildren ())
					insertSubtree (tn, parentId);
				break;
			case TreeNode.TEXT_NODE:
				insertNode ((TextNode) toInsert, chainId);
				break;
			default:
			{
				LOGGER.error ("unsupported tree node type for insertion...");
				throw new UnsupportedOperationException ("unsupported tree node type...");
			}
		}
	}
	
	/**
	 * Insert a single node.
	 *
	 * @param toInsert the node to insert
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	public void insertNode (TreeNode toInsert, int chainId)
	{
		switch (toInsert.getType ())
		{
			case TreeNode.DOC_NODE:
				insertNode ((DocumentNode) toInsert, chainId);
				break;
			case TreeNode.TEXT_NODE:
				insertNode ((TextNode) toInsert, chainId);
				break;
			default:
			{
				LOGGER.error ("unsupported tree node type for insertion...");
				throw new UnsupportedOperationException ("unsupported tree node type...");
			}
		}
	}
	
	/**
	 * Insert a single node.
	 *
	 * @param toInsert the document node to insert
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 * @return the id of the insert-operation-node in the XML delta
	 */
	public int insertNode (DocumentNode toInsert, int chainId)
	{
		LOGGER.info ("inserting node ", toInsert.getXPath ());
		int nodeId = ++id;
		
		Element diffElement = createNodeElement (nodeId, null, getParentXpath (toInsert), null, toInsert.getXPath (), -1, getChildNo (toInsert), null, toInsert.getTagName (), chainId);
		insert.addContent (diffElement);
		diffAnnotator.annotateInsertion (toInsert, diffElement, changeAnnotationFactory);
		
		if (!fullDiff)
			return nodeId;
		LOGGER.info ("checking attributes for full diff");
		Set attr = toInsert.getAttributes ();
		for (String a: attr)
			insertAttribute (toInsert, a, nodeId);
		return nodeId;
	}
	
	/**
	 * Insert an attribute.
	 *
	 * @param node the node carrying the attribute to insert
	 * @param attribute the attribute to insert
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	private void insertAttribute (DocumentNode node, String attribute, int chainId)
	{
		LOGGER.info ("inserting attribute ", attribute, " of ", node.getXPath ());
		
		Element diffElement = createAttributeElement (++id, null, node.getXPath (), attribute, null, node.getAttributeValue (attribute), chainId);
		insert.addContent (diffElement);
		diffAnnotator.annotateInsertion (node, diffElement, changeAnnotationFactory);
	}
	
	/**
	 * Insert a single node.
	 *
	 * @param toInsert the text node to insert
	 * @param chainId the chain id: id of the trigger, if this modification was triggered, or <1 otherwise
	 */
	private void insertNode (TextNode toInsert, int chainId)
	{
		LOGGER.info ("inserting text of ", toInsert.getXPath ());
		
		Element diffElement = createTextElement (++id, null, getParentXpath (toInsert), null, toInsert.getXPath (), -1, getChildNo (toInsert), null, toInsert.getText (), chainId);
		insert.addContent (diffElement);
		diffAnnotator.annotateInsertion (toInsert, diffElement, changeAnnotationFactory);
	}
	
	/**
	 * Update a node.
	 *
	 * @param c the connection between the old version of the node and the new version of it.
	 * @param conMgmt the connection manager
	 */
	public void updateNode (NodeConnection c, SimpleConnectionManager conMgmt)
	{
		TreeNode a = c.getTreeA ();
		TreeNode b = c.getTreeB ();
		LOGGER.info ("updating node ", a.getXPath (), " to become ", b.getXPath ());
		
		if (a.getType () != b.getType ())
		{
			LOGGER.error ("node types differ, not supported");
			throw new UnsupportedOperationException ("cannot update nodes of different type...");
		}
		
		if (((a.getModification () | b.getModification ()) & (TreeNode.GLUED | TreeNode.COPIED)) != 0)
			throw new UnsupportedOperationException ("copy & glue not supported yet...");
		
		boolean moveThem = (a.getModification () & (TreeNode.MOVED | TreeNode.SWAPPEDKID)) != 0;
		if (moveThem && LOGGER.isInfoEnabled ())
			LOGGER.info ("will move them: par: ", conMgmt.parentsConnected (c), " chNoA: ", getChildNo (a), " chNoB: ", getChildNo (b));
		
		Change change = null;
		
		// text node
		if (a.getType () == TreeNode.TEXT_NODE)
		{
			if ((a.getModification () & TreeNode.MODIFIED) != 0)
			{
				LOGGER.info ("text differs");
				Element e = createTextElement (++id, getParentXpath (a), getParentXpath (b), a.getXPath (), b.getXPath (), getChildNo (a), getChildNo (b), ((TextNode) a).getText (), ((TextNode) b).getText (), -1);
				change = diffAnnotator.annotateUpdateText ((TextNode) a, (TextNode) b, e, changeAnnotationFactory);
				
				if (moveThem)
				{
					move.addContent (e);
					change = diffAnnotator.annotateMove (a, b, e, changeAnnotationFactory, conMgmt.parentsConnected (c));
				}
				else
				{
					update.addContent (e);
				}
			}
			else if (moveThem)
			{
				LOGGER.info ("equal text");
				Element diffElement = createTextElement (++id, getParentXpath (a), getParentXpath (b), a.getXPath (), b.getXPath (), getChildNo (a), getChildNo (b), null, null, -1);
				move.addContent (diffElement);
				change = diffAnnotator.annotateMove (a, b, diffElement, changeAnnotationFactory, conMgmt.parentsConnected (c));
			}
			if (change != null)
			{
				for (Map.Entry annotation : c.getAnnotations ())
					change.addAnnotation (annotation.getKey (), annotation.getValue ());
			}
			return;
		}

		// xml node
		
		DocumentNode dA = (DocumentNode) a;
		DocumentNode dB = (DocumentNode) b;
		
		if ((a.getModification () & TreeNode.MODIFIED) == 0)
		{
			// unmodified -> just move
			if (moveThem)
			{
				LOGGER.info ("nodes unmodified");
				
				Element diffElement = createNodeElement (++id, getParentXpath (a), getParentXpath (b), a.getXPath (), b.getXPath (), getChildNo (a), getChildNo (b), null, null, -1);
				move.addContent (diffElement);
				change = diffAnnotator.annotateMove (a, b, diffElement, changeAnnotationFactory, conMgmt.parentsConnected (c));
			}
		}
		else
		{
			// matching label? -> update more extreme than move...
			if (!dA.getTagName ().equals (dB.getTagName ()))
			{
				LOGGER.info ("label of nodes differ -> updating");
				
				Element diffElement = createNodeElement (++id, getParentXpath (a), getParentXpath (b), a.getXPath (), b.getXPath (), getChildNo (a), getChildNo (b), dA.getTagName (), dB.getTagName (), -1);
				update.addContent (diffElement);
			}
			else if (moveThem)
			{
				LOGGER.info ("label of nodes do not differ -> moving");
				
				Element diffElement = createNodeElement (++id, getParentXpath (a), getParentXpath (b), a.getXPath (), b.getXPath (), getChildNo (a), getChildNo (b), null, null, -1);
				move.addContent (diffElement);
				change = diffAnnotator.annotateMove (a, b, diffElement, changeAnnotationFactory, conMgmt.parentsConnected (c));
			}
			
			if (fullDiff)
			{
				// arguments
				LOGGER.info ("checking attributes for full diff");

				Set allAttr = new HashSet ();
				allAttr.addAll (dA.getAttributes ());
				allAttr.addAll (dB.getAttributes ());
				for (String attr : allAttr)
				{
					String aA = dA.getAttributeValue (attr), bA = dB.getAttributeValue (attr);
					if (aA == null)
						insertAttribute (dB, attr, -1);
					else if (bA == null)
						deleteAttribute (dA, attr, -1);
					else if (!aA.equals (bA))
					{
						Element diffElement = createAttributeElement (++id, a.getXPath (), b.getXPath (), attr, aA, bA, -1);
						update.addContent (diffElement);
						change = diffAnnotator.annotateUpdateAttribute (a, b, attr, diffElement, changeAnnotationFactory);
					}
				}
			}
		}
		if (change != null)
		{
			for (Map.Entry annotation : c.getAnnotations ())
				change.addAnnotation (annotation.getKey (), annotation.getValue ());
		}
	}
	
	/**
	 * Gets the child number of a node.
	 *
	 * @param n the node of interest
	 * @return the child no
	 */
	private int getChildNo (TreeNode n)
	{
		return n.isRoot () ? -1 : n.getParent ().getNoOfChild (n);
	}
	
	/**
	 * Gets the XPath of the node's parent.
	 *
	 * @param n the node of interest
	 * @return the parent XPath
	 */
	private String getParentXpath (TreeNode n)
	{
		return n.isRoot () ? "" : n.getParent ().getXPath ();
	}

	/**
	 * Annotate the whole patch.
	 */
	public void annotatePatch ()
	{
		diffAnnotator.annotatePatch ("bivesPatch", changeAnnotationFactory);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy