/** * MDAG is a Java library capable of constructing character-sequence-storing, * directed acyclic graphs of minimal size. * * Copyright (C) 2012 Kevin Lawson <[email protected]> * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.BoxOfC.MDAG; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; import java.util.Stack; /** * The class which represents a node in a MDAG. * @author Kevin */ public class MDAGNode { public int id; //The boolean denoting the accept state status of this node private boolean isAcceptNode; //The TreeMap to containing entries that represent a transition (label and target node) private final TreeMap<Character, MDAGNode> outgoingTransitionTreeMap; //The int representing this node's incoming transition node count private int incomingTransitionCount = 0; //The int denoting index in a simplified mdag data array that this node's transition set begins at private int transitionSetBeginIndex = -1; //The int which will store this node's hash code after its been calculated (necessary due to how expensive the hashing calculation is) private Integer storedHashCode = null; /** * Constructs an MDAGNode. * @param isAcceptNode a boolean denoting the accept state status of this node */ public MDAGNode(boolean isAcceptNode) { this.isAcceptNode = isAcceptNode; outgoingTransitionTreeMap = new TreeMap<Character, MDAGNode>(); } /** * Constructs an MDAGNode possessing the same accept state status and outgoing transitions as another. * @param node the MDAGNode possessing the accept state status and * outgoing transitions that the to-be-created MDAGNode is to take on */ private MDAGNode(MDAGNode node) { isAcceptNode = node.isAcceptNode; outgoingTransitionTreeMap = new TreeMap<Character, MDAGNode>(node.outgoingTransitionTreeMap); //Loop through the nodes in this node's outgoing transition set, incrementing the number of //incoming transitions of each by 1 (to account for this newly created node's outgoing transitions) for(Entry<Character, MDAGNode> transitionKeyValuePair : outgoingTransitionTreeMap.entrySet()) transitionKeyValuePair.getValue().incomingTransitionCount++; ///// } public MDAGNode(boolean isAcceptNode, int id) { this.id = id; this.isAcceptNode = isAcceptNode; outgoingTransitionTreeMap = new TreeMap<Character, MDAGNode>(); } private MDAGNode(MDAGNode node, int id) { this.id = id; isAcceptNode = node.isAcceptNode; outgoingTransitionTreeMap = new TreeMap<Character, MDAGNode>(node.outgoingTransitionTreeMap); for(Map.Entry<Character, MDAGNode> transitionKeyValuePair : outgoingTransitionTreeMap.entrySet()) transitionKeyValuePair.getValue().incomingTransitionCount++; } /** * Creates an MDAGNode possessing the same accept state status and outgoing transitions as this node. * @return an MDAGNode possessing the same accept state status and outgoing transitions as this node */ public MDAGNode clone() { return new MDAGNode(this); } /** * Creates an MDAGNode possessing the same accept state status ant transition set * (incoming & outgoing) as this node. outgoing transitions as this node. * @param soleParentNode the MDAGNode possessing the only transition that targets this node * @param parentToCloneTransitionLabelChar the char which labels the transition from {@code soleParentNode} to this node * @return an MDAGNode possessing the same accept state status and transition set as this node. */ public MDAGNode clone(MDAGNode soleParentNode, char parentToCloneTransitionLabelChar) { MDAGNode cloneNode = new MDAGNode(this); soleParentNode.reassignOutgoingTransition(parentToCloneTransitionLabelChar, this, cloneNode); return cloneNode; } /** * Retrieves the index in a simplified mdag data array that the SimpleMDAGNode * representation of this node's outgoing transition set begins at. * @return the index in a simplified mdag data array that this node's transition set begins at, * or -1 if its transition set is not present in such an array */ public int getTransitionSetBeginIndex() { return transitionSetBeginIndex; } public Map.Entry<Character, MDAGNode> getLastTransition() { return outgoingTransitionTreeMap.lastEntry(); } /** * Retrieves this node's outgoing transition count. * @return an int representing this node's number of outgoing transitions */ public int getOutgoingTransitionCount() { return outgoingTransitionTreeMap.size(); } /** * Retrieves this node's incoming transition count * @return an int representing this node's number of incoming transitions */ public int getIncomingTransitionCount() { return incomingTransitionCount; } /** * Determines if this node is a confluence node * (defined as a node with two or more incoming transitions * @return true if this node has two or more incoming transitions, false otherwise */ public boolean isConfluenceNode() { return (incomingTransitionCount > 1); } /** * Retrieves the accept state status of this node. * @return true if this node is an accept state, false otherwise */ public boolean isAcceptNode() { return isAcceptNode; } /** * Sets this node's accept state status. * * @param isAcceptNode a boolean representing the desired accept state status */ public void setAcceptStateStatus(boolean isAcceptNode) { this.isAcceptNode = isAcceptNode; } /** * Records the index that this node's transition set starts at * in an array containing this node's containing MDAG data (simplified MDAG). * @param transitionSetBeginIndex a transition set */ public void setTransitionSetBeginIndex(int transitionSetBeginIndex) { this.transitionSetBeginIndex = transitionSetBeginIndex; } /** * Determines whether this node has an outgoing transition with a given label. * @param letter the char labeling the desired transition * @return true if this node possesses a transition labeled with * {@code letter}, and false otherwise */ public boolean hasOutgoingTransition(char letter) { return outgoingTransitionTreeMap.containsKey(letter); } /** * Determines whether this node has any outgoing transitions. * @return true if this node has at least one outgoing transition, false otherwise */ public boolean hasOutgoingTransitions() { return !outgoingTransitionTreeMap.isEmpty(); } /** * Follows an outgoing transition of this node labeled with a given char. * @param letter the char representation of the desired transition's label * @return the MDAGNode that is the target of the transition labeled with {@code letter}, * or null if there is no such labeled transition from this node */ public MDAGNode transition(char letter) { return outgoingTransitionTreeMap.get(letter); } /** * Follows a transition path starting from this node. * @param str a String corresponding a transition path in the MDAG * @return the MDAGNode at the end of the transition path corresponding to * {@code str}, or null if such a transition path is not present in the MDAG */ public MDAGNode transition(String str) { int charCount = str.length(); MDAGNode currentNode = this; //Iteratively transition through the MDAG using the chars in str for(int i = 0; i < charCount; i++) { currentNode = currentNode.transition(str.charAt(i)); if(currentNode == null) break; } ///// return currentNode; } /** * Retrieves the nodes in the transition path starting * from this node corresponding to a given String . * @param str a String corresponding to a transition path starting from this node * @return a Stack of MDAGNodes containing the nodes in the transition path * denoted by {@code str}, in the order they are encountered in during transitioning */ public Stack<MDAGNode> getTransitionPathNodes(String str) { Stack<MDAGNode> nodeStack = new Stack<MDAGNode>(); MDAGNode currentNode = this; int numberOfChars = str.length(); //Iteratively transition through the MDAG using the chars in str, //putting each encountered node in nodeStack for(int i = 0; i < numberOfChars && currentNode != null; i++) { currentNode = currentNode.transition(str.charAt(i)); nodeStack.add(currentNode); } ///// return nodeStack; } /** * Retrieves this node's outgoing transitions. * @return a TreeMap containing entries collectively representing * all of this node's outgoing transitions */ public TreeMap<Character, MDAGNode> getOutgoingTransitions() { return outgoingTransitionTreeMap; } /** * Decrements (by 1) the incoming transition counts of all of the nodes * that are targets of outgoing transitions from this node. */ public void decrementTargetIncomingTransitionCounts() { for(Entry<Character, MDAGNode> transitionKeyValuePair: outgoingTransitionTreeMap.entrySet()) transitionKeyValuePair.getValue().incomingTransitionCount--; } /** * Reassigns the target node of one of this node's outgoing transitions. * @param letter the char which labels the outgoing transition of interest * @param oldTargetNode the MDAGNode that is currently the target of the transition of interest * @param newTargetNode the MDAGNode that is to be the target of the transition of interest */ public void reassignOutgoingTransition(char letter, MDAGNode oldTargetNode, MDAGNode newTargetNode) { oldTargetNode.incomingTransitionCount--; newTargetNode.incomingTransitionCount++; outgoingTransitionTreeMap.put(letter, newTargetNode); } /** * Creates an outgoing transition labeled with a * given char that has a new node as its target. * @param letter a char representing the desired label of the transition * @param targetAcceptStateStatus a boolean representing to-be-created transition target node's accept status * @return the (newly created) MDAGNode that is the target of the created transition */ public MDAGNode addOutgoingTransition(char letter, boolean targetAcceptStateStatus) { MDAGNode newTargetNode = new MDAGNode(targetAcceptStateStatus); newTargetNode.incomingTransitionCount++; outgoingTransitionTreeMap.put(letter, newTargetNode); return newTargetNode; } public MDAGNode addOutgoingTransition(char letter, boolean isEndOfWord, int id) { MDAGNode newTargetNode = new MDAGNode(isEndOfWord, id); newTargetNode.incomingTransitionCount++; newTargetNode.id = id; outgoingTransitionTreeMap.put(letter, newTargetNode); return newTargetNode; } /** * Removes a transition labeled with a given char. This only removes the connection * between this node and the transition's target node; the target node is not deleted. * @param letter the char labeling the transition of interest */ public void removeOutgoingTransition(char letter) { outgoingTransitionTreeMap.remove(letter); } /** * Determines whether the sets of transition paths from two MDAGNodes are equivalent. This is an expensive operation. * @param outgoingTransitionTreeMap1 a TreeMap containing entries collectively representing * all of a node's outgoing transitions * @param outgoingTransitionTreeMap2 a TreeMap containing entries collectively representing * all of a node's outgoing transitions * @return true if the set of transition paths from {@code node1} * and {@code node2} are equivalent */ public static boolean haveSameTransitions(MDAGNode node1, MDAGNode node2) { TreeMap<Character, MDAGNode> outgoingTransitionTreeMap1 = node1.outgoingTransitionTreeMap; TreeMap<Character, MDAGNode> outgoingTransitionTreeMap2 = node2.outgoingTransitionTreeMap; if(outgoingTransitionTreeMap1.size() == outgoingTransitionTreeMap2.size()) { //For each transition in outgoingTransitionTreeMap1, get the identically lableed transition //in outgoingTransitionTreeMap2 (if present), and test the equality of the transitions' target nodes for(Entry<Character, MDAGNode> transitionKeyValuePair : outgoingTransitionTreeMap1.entrySet()) { Character currentCharKey = transitionKeyValuePair.getKey(); MDAGNode currentTargetNode = transitionKeyValuePair.getValue(); if(!outgoingTransitionTreeMap2.containsKey(currentCharKey) || !outgoingTransitionTreeMap2.get(currentCharKey).equals(currentTargetNode)) return false; } ///// } else return false; return true; } /** * Clears this node's stored hash value */ public void clearStoredHashCode() { storedHashCode = null; } /** * Evaluates the equality of this node with another object. * This node is equal to obj if and only if obj is also an MDAGNode, * and the set of transitions paths from this node and obj are equivalent. * @param obj an object * @return true of {@code obj} is an MDAGNode and the set of * transition paths from this node and obj are equivalent */ @Override public boolean equals(Object obj) { boolean areEqual = (this == obj); if(!areEqual && obj != null && obj.getClass().equals(MDAGNode.class)) { MDAGNode node = (MDAGNode)obj; areEqual = (isAcceptNode == node.isAcceptNode && haveSameTransitions(this, node)); } return areEqual; } /** * Hashes this node using its accept state status and set of outgoing transition paths. * This is an expensive operation, so the result is cached and only cleared when necessary. * @return an int of this node's hash code */ @Override public int hashCode() { if(storedHashCode == null) { int hash = 7; hash = 53 * hash + (this.isAcceptNode ? 1 : 0); hash = 53 * hash + (this.outgoingTransitionTreeMap != null ? this.outgoingTransitionTreeMap.hashCode() : 0); //recursively hashes the nodes in all the //transition paths stemming from this node storedHashCode = hash; return hash; } else return storedHashCode; } }