/**
* 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 org.apache.zookeeper.common;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* a class that implements prefix matching for
* components of a filesystem path. the trie
* looks like a tree with edges mapping to
* the component of a path.
* example /ab/bc/cf would map to a trie
* /
* ab/
* (ab)
* bc/
* /
* (bc)
* cf/
* (cf)
*/
public class PathTrie {
/**
* the logger for this class
*/
private static final Logger LOG = LoggerFactory.getLogger(PathTrie.class);
/**
* the root node of PathTrie
*/
private final TrieNode rootNode ;
static class TrieNode {
boolean property = false;
final HashMap<String, TrieNode> children;
TrieNode parent = null;
/**
* create a trienode with parent
* as parameter
* @param parent the parent of this trienode
*/
private TrieNode(TrieNode parent) {
children = new HashMap<String, TrieNode>();
this.parent = parent;
}
/**
* get the parent of this node
* @return the parent node
*/
TrieNode getParent() {
return this.parent;
}
/**
* set the parent of this node
* @param parent the parent to set to
*/
void setParent(TrieNode parent) {
this.parent = parent;
}
/**
* a property that is set
* for a node - making it
* special.
*/
void setProperty(boolean prop) {
this.property = prop;
}
/** the property of this
* node
* @return the property for this
* node
*/
boolean getProperty() {
return this.property;
}
/**
* add a child to the existing node
* @param childName the string name of the child
* @param node the node that is the child
*/
void addChild(String childName, TrieNode node) {
synchronized(children) {
if (children.containsKey(childName)) {
return;
}
children.put(childName, node);
}
}
/**
* delete child from this node
* @param childName the string name of the child to
* be deleted
*/
void deleteChild(String childName) {
synchronized(children) {
if (!children.containsKey(childName)) {
return;
}
TrieNode childNode = children.get(childName);
// this is the only child node.
if (childNode.getChildren().length == 1) {
childNode.setParent(null);
children.remove(childName);
}
else {
// their are more child nodes
// so just reset property.
childNode.setProperty(false);
}
}
}
/**
* return the child of a node mapping
* to the input childname
* @param childName the name of the child
* @return the child of a node
*/
TrieNode getChild(String childName) {
synchronized(children) {
if (!children.containsKey(childName)) {
return null;
}
else {
return children.get(childName);
}
}
}
/**
* get the list of children of this
* trienode.
* @param node to get its children
* @return the string list of its children
*/
String[] getChildren() {
synchronized(children) {
return children.keySet().toArray(new String[0]);
}
}
/**
* get the string representation
* for this node
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Children of trienode: ");
synchronized(children) {
for (String str: children.keySet()) {
sb.append(" " + str);
}
}
return sb.toString();
}
}
/**
* construct a new PathTrie with
* a root node of /
*/
public PathTrie() {
this.rootNode = new TrieNode(null);
}
/**
* add a path to the path trie
* @param path
*/
public void addPath(String path) {
if (path == null) {
return;
}
String[] pathComponents = path.split("/");
TrieNode parent = rootNode;
String part = null;
if (pathComponents.length <= 1) {
throw new IllegalArgumentException("Invalid path " + path);
}
for (int i=1; i<pathComponents.length; i++) {
part = pathComponents[i];
if (parent.getChild(part) == null) {
parent.addChild(part, new TrieNode(parent));
}
parent = parent.getChild(part);
}
parent.setProperty(true);
}
/**
* delete a path from the trie
* @param path the path to be deleted
*/
public void deletePath(String path) {
if (path == null) {
return;
}
String[] pathComponents = path.split("/");
TrieNode parent = rootNode;
String part = null;
if (pathComponents.length <= 1) {
throw new IllegalArgumentException("Invalid path " + path);
}
for (int i=1; i<pathComponents.length; i++) {
part = pathComponents[i];
if (parent.getChild(part) == null) {
//the path does not exist
return;
}
parent = parent.getChild(part);
LOG.info("{}",parent);
}
TrieNode realParent = parent.getParent();
realParent.deleteChild(part);
}
/**
* return the largest prefix for the input path.
* @param path the input path
* @return the largest prefix for the input path.
*/
public String findMaxPrefix(String path) {
if (path == null) {
return null;
}
if ("/".equals(path)) {
return path;
}
String[] pathComponents = path.split("/");
TrieNode parent = rootNode;
List<String> components = new ArrayList<String>();
if (pathComponents.length <= 1) {
throw new IllegalArgumentException("Invalid path " + path);
}
int i = 1;
String part = null;
StringBuilder sb = new StringBuilder();
int lastindex = -1;
while((i < pathComponents.length)) {
if (parent.getChild(pathComponents[i]) != null) {
part = pathComponents[i];
parent = parent.getChild(part);
components.add(part);
if (parent.getProperty()) {
lastindex = i-1;
}
}
else {
break;
}
i++;
}
for (int j=0; j< (lastindex+1); j++) {
sb.append("/" + components.get(j));
}
return sb.toString();
}
}