package org.maltparser.concurrent.graph; import java.util.Collections; import java.util.List; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.maltparser.concurrent.graph.dataformat.ColumnDescription; import org.maltparser.concurrent.graph.dataformat.DataFormat; /** * Immutable and tread-safe dependency node implementation. * * @author Johan Hall */ public final class ConcurrentDependencyNode implements Comparable { private final ConcurrentDependencyGraph graph; private final int index; private final SortedMap labels; private final int headIndex; protected ConcurrentDependencyNode(ConcurrentDependencyNode node) throws ConcurrentGraphException { this(node.graph, node); } protected ConcurrentDependencyNode(ConcurrentDependencyGraph _graph, ConcurrentDependencyNode node) throws ConcurrentGraphException { if (_graph == null) { throw new ConcurrentGraphException("The graph node must belong to a dependency graph."); } this.graph = _graph; this.index = node.index; this.labels = new TreeMap(node.labels); this.headIndex = node.headIndex; } protected ConcurrentDependencyNode(ConcurrentDependencyGraph _graph, int _index, SortedMap _labels, int _headIndex) throws ConcurrentGraphException { if (_graph == null) { throw new ConcurrentGraphException("The graph node must belong to a dependency graph."); } if (_index < 0) { throw new ConcurrentGraphException("Not allowed to have negative node index"); } if (_headIndex < -1) { throw new ConcurrentGraphException("Not allowed to have head index less than -1."); } if (_index == 0 && _headIndex != -1) { throw new ConcurrentGraphException("Not allowed to add head to a root node."); } if (_index == _headIndex) { throw new ConcurrentGraphException("Not allowed to add head to itself"); } this.graph = _graph; this.index = _index; this.labels = new TreeMap(_labels); this.headIndex = _headIndex; } protected ConcurrentDependencyNode(ConcurrentDependencyGraph _graph, int _index, String[] _labels) throws ConcurrentGraphException { if (_graph == null) { throw new ConcurrentGraphException("The graph node must belong to a dependency graph."); } if (_index < 0) { throw new ConcurrentGraphException("Not allowed to have negative node index"); } this.graph = _graph; this.index = _index; this.labels = new TreeMap(); int tmpHeadIndex = -1; if (_labels != null) { for (int i = 0; i < _labels.length; i++) { int columnCategory = graph.getDataFormat().getColumnDescription(i).getCategory(); if (columnCategory == ColumnDescription.HEAD) { tmpHeadIndex = Integer.parseInt(_labels[i]); } else if (columnCategory == ColumnDescription.INPUT || columnCategory == ColumnDescription.DEPENDENCY_EDGE_LABEL) { this.labels.put(i, _labels[i]); } } } this.headIndex = tmpHeadIndex; if (this.headIndex < -1) { throw new ConcurrentGraphException("Not allowed to have head index less than -1."); } if (this.index == 0 && this.headIndex != -1) { throw new ConcurrentGraphException("Not allowed to add head to a root node."); } if (this.index == this.headIndex) { throw new ConcurrentGraphException("Not allowed to add head to itself"); } } /** * Returns the index of the node. * * @return the index of the node. */ public int getIndex() { return this.index; } /** * Returns a label * * @param columnPosition the column position of the column that describes the label * @return a label. An empty string is returned if the label is not found. */ public String getLabel(int columnPosition) { if (labels.containsKey(columnPosition)) { return labels.get(columnPosition); } else if (graph.getDataFormat().getColumnDescription(columnPosition).getCategory() == ColumnDescription.IGNORE) { return graph.getDataFormat().getColumnDescription(columnPosition).getDefaultOutput(); } return ""; } /** * Returns a label * * @param columnName the name of the column that describes the label. * @return a label. An empty string is returned if the label is not found. */ public String getLabel(String columnName) { ColumnDescription column = graph.getDataFormat().getColumnDescription(columnName); if (column != null) { return getLabel(column.getPosition()); } return ""; } /** * Returns a label * * @param column a column description that describes the label * @return a label described by the column description. An empty string is returned if the label is not found. */ public String getLabel(ColumnDescription column) { return getLabel(column.getPosition()); } /** * Returns true if the label exists, otherwise false * * @param columnPosition the column position of the column that describes the label * @return true if the label exists, otherwise false */ public boolean hasLabel(int columnPosition) { return labels.containsKey(columnPosition); } /** * Returns true if the label exists, otherwise false * * @param columnName the name of the column that describes the label. * @return true if the label exists, otherwise false */ public boolean hasLabel(String columnName) { ColumnDescription column = graph.getDataFormat().getColumnDescription(columnName); if (column != null) { return hasLabel(column.getPosition()); } return false; } /** * Returns true if the label exists, otherwise false * * @param column a column description that describes the label * @return true if the label exists, otherwise false */ public boolean hasLabel(ColumnDescription column) { return labels.containsKey(column.getPosition()); } /** * Returns true if the node has one or more labels, otherwise false. * * @return true if the node has one or more labels, otherwise false. */ public boolean isLabeled() { for (Integer key : labels.keySet()) { if (graph.getDataFormat().getColumnDescription(key).getCategory() == ColumnDescription.INPUT) { return true; } } return false; } /** * Returns true if the head edge has one or more labels, otherwise false. * * @return true if the head edge has one or more labels, otherwise false. */ public boolean isHeadLabeled() { for (Integer key : labels.keySet()) { if (graph.getDataFormat().getColumnDescription(key).getCategory() == ColumnDescription.DEPENDENCY_EDGE_LABEL) { return true; } } return false; } /** * Returns the index of the head node * * @return the index of the head node */ public int getHeadIndex() { return headIndex; } /** * Returns a sorted map that maps column descriptions to node labels * * @return a sorted map that maps column descriptions to node labels */ public SortedMap getNodeLabels() { SortedMap nodeLabels = Collections.synchronizedSortedMap(new TreeMap()); for (Integer key : labels.keySet()) { if (graph.getDataFormat().getColumnDescription(key).getCategory() == ColumnDescription.INPUT) { nodeLabels.put(graph.getDataFormat().getColumnDescription(key), labels.get(key)); } } return nodeLabels; } /** * Returns a sorted map that maps column descriptions to head edge labels * * @return a sorted map that maps column descriptions to head edge labels */ public SortedMap getEdgeLabels() { SortedMap edgeLabels = Collections.synchronizedSortedMap(new TreeMap()); for (Integer key : labels.keySet()) { if (graph.getDataFormat().getColumnDescription(key).getCategory() == ColumnDescription.DEPENDENCY_EDGE_LABEL) { edgeLabels.put(graph.getDataFormat().getColumnDescription(key), labels.get(key)); } } return edgeLabels; } /** * Returns the predecessor dependency node in the linear order of the token nodes. * * @return the predecessor dependency node in the linear order of the token nodes. */ public ConcurrentDependencyNode getPredecessor() { return index > 1 ? graph.getDependencyNode(index - 1) : null; } /** * Returns the successor dependency node in the linear order of the token nodes. * * @return the successor dependency node in the linear order of the token nodes. */ public ConcurrentDependencyNode getSuccessor() { return graph.getDependencyNode(index + 1); } /** * Returns true if the node is a root node, otherwise false. * * @return true if the node is a root node, otherwise false. */ public boolean isRoot() { return index == 0; } /** * Returns true if the node has at most one head, otherwise false. * * Note: this method will always return true because the concurrent dependency graph implementation only supports one or zero head. * * @return true if the node has at most one head, otherwise false. */ public boolean hasAtMostOneHead() { return true; } /** * Returns true if the node has one or more head(s), otherwise false. * * @return true if the node has one or more head(s), otherwise false. */ public boolean hasHead() { return headIndex != -1; } /** * Returns true if the node has one or more dependents, otherwise false. * * @return true if the node has one or more dependents, otherwise false. */ public boolean hasDependent() { return graph.hasDependent(index); } /** * Returns true if the node has one or more left dependents, otherwise false. * * @return true if the node has one or more left dependents, otherwise false. */ public boolean hasLeftDependent() { return graph.hasLeftDependent(index); } /** * Returns true if the node has one or more right dependents, otherwise false. * * @return true if the node has one or more right dependents, otherwise false. */ public boolean hasRightDependent() { return graph.hasRightDependent(index); } /** * Returns a sorted set of head nodes. If the head is missing a empty set is returned. * * Note: In this implementation there will at most be one head. * * @return a sorted set of head nodes. */ public SortedSet getHeads() { SortedSet heads = Collections.synchronizedSortedSet(new TreeSet()); ConcurrentDependencyNode head = getHead(); if (head != null) { heads.add(head); } return heads; } /** * Returns the head dependency node if it exists, otherwise null. * * @return the head dependency node if it exists, otherwise null. */ public ConcurrentDependencyNode getHead() { return graph.getDependencyNode(headIndex); } /** * Returns the left dependent at the position leftDependentIndex, where leftDependentIndex==0 equals the left most dependent. * * @param leftDependentIndex the index of the left dependent * @return the left dependent at the position leftDependentIndex, where leftDependentIndex==0 equals the left most dependent */ public ConcurrentDependencyNode getLeftDependent(int leftDependentIndex) { List leftDependents = graph.getListOfLeftDependents(index); if (leftDependentIndex >= 0 && leftDependentIndex < leftDependents.size()) { return leftDependents.get(leftDependentIndex); } return null; } /** * Return the number of left dependents * * @return the number of left dependents */ public int getLeftDependentCount() { return graph.getListOfLeftDependents(index).size(); } /** * Returns a sorted set of left dependents. * * @return a sorted set of left dependents. */ public SortedSet getLeftDependents() { return graph.getSortedSetOfLeftDependents(index); } /** * Returns a list of left dependents. * * @return a list of left dependents. */ public List getListOfLeftDependents() { return graph.getListOfLeftDependents(index); } /** * Returns the left sibling if it exists, otherwise null * * @return the left sibling if it exists, otherwise null */ public ConcurrentDependencyNode getLeftSibling() { if (headIndex == -1) { return null; } int nodeDepedentPosition = 0; List headDependents = getHead().getListOfDependents(); for (int i = 0; i < headDependents.size(); i++) { if (headDependents.get(i).getIndex() == index) { nodeDepedentPosition = i; break; } } return (nodeDepedentPosition > 0) ? headDependents.get(nodeDepedentPosition - 1) : null; } /** * Returns the left sibling at the same side of head as the node it self. If not found nullnull headDependents; if (index < headIndex) { headDependents = getHead().getListOfLeftDependents(); } else { //(index > headIndex) headDependents = getHead().getListOfRightDependents(); } int nodeDepedentPosition = 0; for (int i = 0; i < headDependents.size(); i++) { if (headDependents.get(i).getIndex() == index) { nodeDepedentPosition = i; break; } } return (nodeDepedentPosition > 0) ? headDependents.get(nodeDepedentPosition - 1) : null; } /** * Returns the closest left dependent of the node * * @return the closest left dependent of the node */ public ConcurrentDependencyNode getClosestLeftDependent() { List leftDependents = graph.getListOfLeftDependents(index); return (leftDependents.size() > 0) ? leftDependents.get(leftDependents.size() - 1) : null; } /** * Returns the leftmost dependent * * @return the leftmost dependent */ public ConcurrentDependencyNode getLeftmostDependent() { List leftDependents = graph.getListOfLeftDependents(index); return (leftDependents.size() > 0) ? leftDependents.get(0) : null; } /** * Returns the right dependent at the position rightDependentIndex, where rightDependentIndex==0 equals the right most dependent * * @param rightDependentIndex the index of the right dependent * @return the right dependent at the position rightDependentIndex, where rightDependentIndex==0 equals the right most dependent */ public ConcurrentDependencyNode getRightDependent(int rightDependentIndex) { List rightDependents = graph.getListOfRightDependents(index); if (rightDependentIndex >= 0 && rightDependentIndex < rightDependents.size()) { return rightDependents.get(rightDependents.size() - 1 - rightDependentIndex); } return null; } /** * Return the number of right dependents * * @return the number of right dependents */ public int getRightDependentCount() { return graph.getListOfRightDependents(index).size(); } /** * Returns a sorted set of right dependents. * * @return a sorted set of right dependents. */ public SortedSet getRightDependents() { return graph.getSortedSetOfRightDependents(index); } /** * Returns a list of right dependents. * * @return a list of right dependents. */ public List getListOfRightDependents() { return graph.getListOfRightDependents(index); } /** * Returns the right sibling if it exists, otherwise null * * @return the right sibling if it exists, otherwise null */ public ConcurrentDependencyNode getRightSibling() { if (headIndex == -1) { return null; } List headDependents = getHead().getListOfDependents(); int nodeDepedentPosition = headDependents.size() - 1; for (int i = headDependents.size() - 1; i >= 0 ; i--) { if (headDependents.get(i).getIndex() == index) { nodeDepedentPosition = i; break; } } return (nodeDepedentPosition < headDependents.size() - 1) ? headDependents.get(nodeDepedentPosition + 1) : null; } /** * Returns the right sibling at the same side of head as the node it self. If not found nullnull headDependents; if (index < headIndex) { headDependents = getHead().getListOfLeftDependents(); } else { headDependents = getHead().getListOfRightDependents(); } int nodeDepedentPosition = headDependents.size() - 1; for (int i = headDependents.size() - 1; i >= 0 ; i--) { if (headDependents.get(i).getIndex() == index) { nodeDepedentPosition = i; break; } } return (nodeDepedentPosition < headDependents.size() - 1) ? headDependents.get(nodeDepedentPosition + 1) : null; } /** * Returns the closest right dependent of the node * * @return the closest right dependent of the node */ public ConcurrentDependencyNode getClosestRightDependent() { List rightDependents = graph.getListOfRightDependents(index); return (rightDependents.size() > 0) ? rightDependents.get(0) : null; } /** * Returns the rightmost dependent * * @return the rightmost dependent */ public ConcurrentDependencyNode getRightmostDependent(){ List rightDependents = graph.getListOfRightDependents(index); return (rightDependents.size() > 0) ? rightDependents.get(rightDependents.size() - 1) : null; } /** * Returns a sorted set of dependents. * * @return a sorted set of dependents. */ public SortedSet getDependents() { return graph.getSortedSetOfDependents(index); } /** * Returns a list of dependents. * * @return a list of dependents. */ public List getListOfDependents() { return graph.getListOfDependents(index); } /** * Returns the in degree of the node (number of incoming edges). * * @return the in degree of the node (number of incoming edges). */ public int getInDegree() { if (hasHead()) { return 1; } return 0; } /** * Returns the out degree of the node (number of outgoing edges). * * @return the out degree of the node (number of outgoing edges). */ public int getOutDegree() { return graph.getListOfDependents(index).size(); } public ConcurrentDependencyNode getAncestor() { if (!this.hasHead()) { return this; } ConcurrentDependencyNode tmp = this; while (tmp.hasHead()) { tmp = tmp.getHead(); } return tmp; } public ConcurrentDependencyNode getProperAncestor() { if (!this.hasHead()) { return null; } ConcurrentDependencyNode tmp = this; while (tmp.hasHead() && !tmp.isRoot()) { tmp = tmp.getHead(); } return tmp; } public boolean hasAncestorInside(int left, int right) { if (index == 0) { return false; } ConcurrentDependencyNode tmp = this; if (tmp.getHead() != null) { tmp = tmp.getHead(); if (tmp.getIndex() >= left && tmp.getIndex() <= right) { return true; } } return false; } /** * Returns true if the head edge is projective, otherwise false. * * @return true if the head edge is projective, otherwise false. */ public boolean isProjective() { if (headIndex > 0) { final ConcurrentDependencyNode head = getHead(); if (headIndex < index) { ConcurrentDependencyNode terminals = head; ConcurrentDependencyNode tmp = null; while (true) { if (terminals == null || terminals.getSuccessor() == null) { return false; } if (terminals.getSuccessor() == this) { break; } tmp = terminals = terminals.getSuccessor(); while (tmp != this && tmp != head) { if (!tmp.hasHead()) { return false; } tmp = tmp.getHead(); } } } else { ConcurrentDependencyNode terminals = this; ConcurrentDependencyNode tmp = null; while (true) { if (terminals == null || terminals.getSuccessor() == null) { return false; } if (terminals.getSuccessor() == head) { break; } tmp = terminals = terminals.getSuccessor(); while (tmp != this && tmp != head) { if (!tmp.hasHead()) { return false; } tmp = tmp.getHead(); } } } } return true; } /** * Returns the depth of the node. The root node has the depth 0. * @return the depth of the node. */ public int getDependencyNodeDepth() { ConcurrentDependencyNode tmp = this; int depth = 0; while (tmp.hasHead()) { depth++; tmp = tmp.getHead(); } return depth; } /** * Returns the left-most proper terminal descendant node (excluding itself). * * @return the left-most proper terminal descendant node. */ public ConcurrentDependencyNode getLeftmostProperDescendant() { ConcurrentDependencyNode candidate = null; List dependents = graph.getListOfDependents(index); for (int i = 0; i < dependents.size(); i++) { final ConcurrentDependencyNode dep = dependents.get(i); if (candidate == null || dep.getIndex() < candidate.getIndex()) { candidate = dep; } final ConcurrentDependencyNode tmp = dep.getLeftmostProperDescendant(); if (tmp == null) { continue; } if (candidate == null || tmp.getIndex() < candidate.getIndex()) { candidate = tmp; } if (candidate.getIndex() == 1) { return candidate; } } return candidate; } /** * Returns the right-most proper terminal descendant node (excluding itself). * * @return the right-most proper terminal descendant node. */ public ConcurrentDependencyNode getRightmostProperDescendant() { ConcurrentDependencyNode candidate = null; List dependents = graph.getListOfDependents(index); for (int i = 0; i < dependents.size(); i++) { final ConcurrentDependencyNode dep = dependents.get(i); if (candidate == null || dep.getIndex() > candidate.getIndex()) { candidate = dep; } final ConcurrentDependencyNode tmp = dep.getRightmostProperDescendant(); if (tmp == null) { continue; } if (candidate == null || tmp.getIndex() > candidate.getIndex()) { candidate = tmp; } } return candidate; } /** * Returns the index of the left-most proper terminal descendant node (excluding itself). * * @return the index of the left-most proper terminal descendant node. */ public int getLeftmostProperDescendantIndex() { ConcurrentDependencyNode node = getLeftmostProperDescendant(); return (node != null)?node.getIndex():-1; } /** * Returns the index of the right-most proper terminal descendant node (excluding itself). * * @return the index of the right-most proper terminal descendant node. */ public int getRightmostProperDescendantIndex() { ConcurrentDependencyNode node = getRightmostProperDescendant(); return (node != null)?node.getIndex():-1; } /** * Returns the left-most terminal descendant node. * * @return the left-most terminal descendant node. */ public ConcurrentDependencyNode getLeftmostDescendant() { ConcurrentDependencyNode candidate = this; List dependents = graph.getListOfDependents(index); for (int i = 0; i < dependents.size(); i++) { final ConcurrentDependencyNode dep = dependents.get(i); if (dep.getIndex() < candidate.getIndex()) { candidate = dep; } final ConcurrentDependencyNode tmp = dep.getLeftmostDescendant(); if (tmp == null) { continue; } if (tmp.getIndex() < candidate.getIndex()) { candidate = tmp; } if (candidate.getIndex() == 1) { return candidate; } } return candidate; } /** * Returns the right-most terminal descendant node. * * @return the right-most terminal descendant node. */ public ConcurrentDependencyNode getRightmostDescendant() { ConcurrentDependencyNode candidate = this; List dependents = graph.getListOfDependents(index); for (int i = 0; i < dependents.size(); i++) { final ConcurrentDependencyNode dep = dependents.get(i); if (dep.getIndex() > candidate.getIndex() ) { candidate = dep; } final ConcurrentDependencyNode tmp = dep.getRightmostDescendant(); if (tmp == null) { continue; } if (tmp.getIndex() > candidate.getIndex() ) { candidate = tmp; } } return candidate; } /** * Returns the index of the left-most terminal descendant node. * * @return the index of the left-most terminal descendant node. */ public int getLeftmostDescendantIndex() { ConcurrentDependencyNode node = getLeftmostDescendant(); return (node != null)?node.getIndex():this.getIndex(); } /** * Returns the index of the right-most terminal descendant node. * * @return the index of the right-most terminal descendant node. */ public int getRightmostDescendantIndex() { ConcurrentDependencyNode node = getRightmostDescendant(); return (node != null)?node.getIndex():this.getIndex(); } public ConcurrentDependencyNode findComponent() { return graph.findComponent(index); } public int getRank() { return graph.getRank(index); } public ConcurrentDependencyEdge getHeadEdge() throws ConcurrentGraphException { if (!hasHead()) { return null; } return new ConcurrentDependencyEdge(graph.getDataFormat(), getHead(), this, labels); } public SortedSet getHeadEdges() throws ConcurrentGraphException { SortedSet edges = Collections.synchronizedSortedSet(new TreeSet()); if (hasHead()) { edges.add(new ConcurrentDependencyEdge(graph.getDataFormat(), getHead(), this, labels)); } return edges; } public boolean isHeadEdgeLabeled() { return getEdgeLabels().size() > 0; } public int nHeadEdgeLabels() { return getEdgeLabels().size(); } public DataFormat getDataFormat() { return graph.getDataFormat(); } public int compareTo(ConcurrentDependencyNode that) { final int BEFORE = -1; final int EQUAL = 0; final int AFTER = 1; if (this == that) return EQUAL; if (this.index < that.getIndex()) return BEFORE; if (this.index > that.getIndex()) return AFTER; return EQUAL; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + headIndex; result = prime * result + index; result = prime * result + ((labels == null) ? 0 : labels.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ConcurrentDependencyNode other = (ConcurrentDependencyNode) obj; if (headIndex != other.headIndex) return false; if (index != other.index) return false; if (labels == null) { if (other.labels != null) return false; } else if (!labels.equals(other.labels)) return false; return true; } public String toString() { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < graph.getDataFormat().numberOfColumns(); i++) { ColumnDescription column = graph.getDataFormat().getColumnDescription(i); if (!column.isInternal()) { if (column.getCategory() == ColumnDescription.HEAD) { sb.append(headIndex); } else if (column.getCategory() == ColumnDescription.INPUT || column.getCategory() == ColumnDescription.DEPENDENCY_EDGE_LABEL) { sb.append(labels.get(column.getPosition())); } else if (column.getCategory() == ColumnDescription.IGNORE) { sb.append(column.getDefaultOutput()); } sb.append('\t'); } } sb.setLength((sb.length() > 0)?sb.length()-1:0); return sb.toString(); } }