// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed 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.eclipse.che.ide.util; import org.eclipse.che.ide.runtime.Assert; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Trie-based implementation of the prefix index. * * @param <T> * value object type */ public class AbstractTrie<T> implements PrefixIndex<T> { // TODO: This member should be static and unmodifiable. private final List<T> emptyList = new ArrayList<>(); private TrieNode<T> root; public AbstractTrie() { clear(); } public TrieNode<T> getRoot() { return root; } public void clear() { this.root = TrieNode.<T>makeNode(""); } /** @return {@code true} is this trie is empty */ public boolean isEmpty() { return this.root.getChildren().isEmpty(); } public TrieNode<T> put(String key, T value) { return insertIntoTrie(key, root, value); } @Override public List<T> search(String prefix) { TrieNode<T> searchRoot = findNode(prefix, root); return (searchRoot == null) ? emptyList : collectSubtree(searchRoot); } /** * Returns all leaf nodes from a subtree rooted by {@code searchRoot} node * and restricted with a {@code stopFunction} * * @param searchRoot * node to start from * @return all leaf nodes in the matching subtree */ public static <T> List<T> collectSubtree(TrieNode<T> searchRoot) { List<TrieNode<T>> leaves = new ArrayList<>(); getAllLeavesInSubtree(searchRoot, leaves); List<T> result = new ArrayList<>(); for (TrieNode<T> leave : leaves) { result.add(leave.getValue()); } return result; } /** * Traverses the subtree rooted at {@code root} and collects all nodes in the * subtree that are leaves (i.e., valid elements, not only prefixes). * * @param root * a node in the trie from which to start collecting * @param leaves * output array */ private static <T> void getAllLeavesInSubtree(TrieNode<T> root, List<TrieNode<T>> leaves) { if (root.getIsLeaf()) { leaves.add(root); } for (int i = 0, size = root.getChildren().size(); i < size; i++) { TrieNode<T> child = root.getChildren().get(i); getAllLeavesInSubtree(child, leaves); } } private TrieNode<T> insertIntoTrie(String prefix, TrieNode<T> node, T value) { String nodePrefix = node.getPrefix(); if (nodePrefix.equals(prefix)) { node.setValue(value); return node; } else { TrieNode<T> branch = node.findInsertionBranch(prefix); if (branch != null) { return insertIntoTrie(prefix, branch, value); } else { // create new trie nodes LinkedList<TrieNode<T>> suffixChain = makeSuffixChain(node, prefix.substring(nodePrefix.length()), value); return suffixChain.peek(); } } } /** * Inserts a chain of children into the given node. * * @param root * node to insert into * @param suffix * suffix of the last node in the chain * @param value * value of the last node in the chain * @return the inserted chain in direct order (from the root to the leaf) */ LinkedList<TrieNode<T>> makeSuffixChain(TrieNode<T> root, String suffix, T value) { LinkedList<TrieNode<T>> result = new LinkedList<>(); String rootPrefix = root.getPrefix(); for (int i = 1, suffixSize = suffix.length(); i <= suffixSize; i++) { String newPrefix = rootPrefix + suffix.substring(0, i); TrieNode<T> newNode = TrieNode.makeNode(newPrefix); result.add(newNode); root.addChild(newNode); root = newNode; } root.setValue(value); return result; } /** * Searches the subtree rooted at {@code searchRoot} for a node * corresponding to the prefix. * <p/> * There can only ever be one such node, or zero. If no node is found, returns * null. * <p/> * Note that the {@code prefix} is relative to the whole trie root, not * to the {@code searchRoot}. * * @param prefix * the prefix to be found * @param searchRoot * the root of the subtree that is searched * @return the node in the tree corresponding to prefix, or null if no such * node exists */ public static <T> TrieNode<T> findNode(String prefix, TrieNode<T> searchRoot) { Assert.isNotNull(prefix); if (prefix.equals(searchRoot.getPrefix())) { return searchRoot; } TrieNode<T> closestAncestor = findClosestAncestor(prefix, searchRoot); return (closestAncestor.getPrefix().equals(prefix)) ? closestAncestor : null; } /** * Finds the closest ancestor of a search key. Formally it returns * a node x, such that: {@code prefix.startsWith(x.prefix)} * and there is no other node y, such that * {@code (prefix.startsWith(y.prefix) and y.prefix.length > x.prefix)} * * @param key * search key * @param searchRoot * node to start from * @return closest ancestor */ static <T> TrieNode<T> findClosestAncestor(String key, TrieNode<T> searchRoot) { Assert.isNotNull(key); Assert.isLegal(key.startsWith(searchRoot.getPrefix()), "key=" + key + " root prefix=" + searchRoot.getPrefix()); TrieNode<T> result = searchRoot; for (TrieNode<T> child = searchRoot; child != null; child = child.findInsertionBranch(key)) { result = child; } return result; } }