Source: diff/match/Matching.js

import {Logger} from '../../util/Logger.js';
import xmldom from '@xmldom/xmldom';
import {DiffConfig} from '../../config/DiffConfig.js';
import vkbeautify from 'vkbeautify';
import {Dsl} from '../../config/Dsl.js';
import {IdExtractor} from '../../extract/IdExtractor.js';

/**
 * Wrapper class for a bidirectional mapping between the nodes of two process
 * trees.
 *
 * @implements {XmlSerializable<Matching>}
 */
export class Matching {
  /**
   * The mapping from new to old nodes.
   * @type Map<Node,Node>
   * @const
   */
  newToOldMap;
  /**
   * The mapping from old to new nodes.
   * @type Map<Node,Node>
   * @const
   */
  oldToNewMap;

  /**
   * Construct a new Matching instance.
   */
  constructor() {
    this.newToOldMap = new Map();
    this.oldToNewMap = new Map();
  }

  /**
   * @param {Node} oldNode
   * @param {Node} newNode
   * @return {Boolean} True, iff they are matched.
   */
  areMatched(oldNode, newNode) {
    return this.isMatched(newNode) && this.getMatch(newNode) === oldNode;
  }

  /**
   * Get the matching partner of a new or old node, if it exists.
   * @param {Node} node
   * @return {?Node}
   */
  getMatch(node) {
    return this.newToOldMap.get(node) ?? this.oldToNewMap.get(node);
  }

  /**
   * @param {Node} node
   * @return {Boolean} True, iff the node is matched.
   */
  isMatched(node) {
    return this.newToOldMap.has(node) || this.oldToNewMap.has(node);
  }

  /**
   * Match a new node to an old node.
   * @param {Node} newNode
   * @param {Node} oldNode
   */
  matchNew(newNode, oldNode) {
    if (this.isMatched(newNode) || this.isMatched(oldNode)) {
      Logger.error('Matching of already matched node', this);
    }
    if (newNode == null || oldNode == null) {
      Logger.error('Matching of undefined or null', this);
    }
    this.newToOldMap.set(newNode, oldNode);
    this.oldToNewMap.set(oldNode, newNode);
  }

  /**
   * @return {Number} The number of matched nodes.
   */
  size() {
    return this.newToOldMap.size;
  }

  /**
   * @param {Object} ownerDocument The owner document of the generated XML
   *     element.
   * @return {Object} The XML DOM object for this matching.
   */
  toXmlDom(ownerDocument = xmldom
      .DOMImplementation
      .prototype
      .createDocument(Dsl.DEFAULT_NAMESPACE)) {
    const xmlRoot = ownerDocument.createElement('matching');
    xmlRoot.setAttribute('size', this.size());
    const idExtractor = new IdExtractor();
    for (const [oldNode, newNode] of this.oldToNewMap) {
      const xmlMatch = ownerDocument.createElement('match');
      xmlMatch.setAttribute('old_id', idExtractor.get(oldNode));
      xmlMatch.setAttribute('new_id', idExtractor.get(newNode));
      xmlRoot.appendChild(xmlMatch);
    }
    return xmlRoot;
  }

  /**
   * @return {String} The XML document for this matching.
   */
  toXmlString() {
    const str = new xmldom.XMLSerializer().serializeToString(this.toXmlDom());
    if (DiffConfig.PRETTY_XML) {
      return vkbeautify.xml(str);
    } else {
      return str;
    }
  }
}