mirror of
https://github.com/revanced/smali.git
synced 2025-05-02 15:44:30 +02:00
Add support for class names that differ only by case on case insensitive file systems
git-svn-id: https://smali.googlecode.com/svn/trunk@784 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
parent
bcc4d2d9e1
commit
a6e5671a62
31
NOTICE
31
NOTICE
@ -66,4 +66,33 @@ 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.
|
||||
*******************************************************************************
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
The RadixTree implementation in the "util" project is taken from
|
||||
http://code.google.com/p/radixtree/ (version .3), and is used with minor
|
||||
modifications in accordance with the following license:
|
||||
|
||||
*******************************************************************************
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008 Tahseen Ur Rehman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
@ -34,6 +34,9 @@ import org.jf.dexlib.Code.Analysis.ClassPath;
|
||||
import org.jf.dexlib.DexFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -115,7 +118,20 @@ public class baksmali {
|
||||
}
|
||||
}
|
||||
|
||||
for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) {
|
||||
//sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
|
||||
//name collisions, then we'll use the same name for each class, if the dex file goes through multiple
|
||||
//baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
|
||||
//may still change of course
|
||||
ArrayList<ClassDefItem> classDefItems = new ArrayList<ClassDefItem>(dexFile.ClassDefsSection.getItems());
|
||||
Collections.sort(classDefItems, new Comparator<ClassDefItem>() {
|
||||
public int compare(ClassDefItem classDefItem1, ClassDefItem classDefItem2) {
|
||||
return classDefItem1.getClassType().getTypeDescriptor().compareTo(classDefItem1.getClassType().getTypeDescriptor());
|
||||
}
|
||||
});
|
||||
|
||||
fileNameHandler fileNameHandler = new fileNameHandler(outputDirectoryFile);
|
||||
|
||||
for (ClassDefItem classDefItem: classDefItems) {
|
||||
/**
|
||||
* The path for the disassembly file is based on the package name
|
||||
* The class descriptor will look something like:
|
||||
@ -142,21 +158,7 @@ public class baksmali {
|
||||
continue;
|
||||
}
|
||||
|
||||
//trim off the leading L and trailing ;
|
||||
classDescriptor = classDescriptor.substring(1, classDescriptor.length()-1);
|
||||
|
||||
//trim off the leading 'L' and trailing ';', and get the individual package elements
|
||||
String[] pathElements = classDescriptor.split("/");
|
||||
|
||||
//build the path to the smali file to generate for this class
|
||||
StringBuilder smaliPath = new StringBuilder(outputDirectory);
|
||||
for (String pathElement: pathElements) {
|
||||
smaliPath.append(File.separatorChar);
|
||||
smaliPath.append(pathElement);
|
||||
}
|
||||
smaliPath.append(".smali");
|
||||
|
||||
File smaliFile = new File(smaliPath.toString());
|
||||
File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor);
|
||||
|
||||
//create and initialize the top level string template
|
||||
ClassDefinition classDefinition = new ClassDefinition(classDefItem);
|
||||
|
318
baksmali/src/main/java/org/jf/baksmali/fileNameHandler.java
Normal file
318
baksmali/src/main/java/org/jf/baksmali/fileNameHandler.java
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2010 Ben Gruver
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.jf.baksmali;
|
||||
|
||||
import ds.tree.RadixTree;
|
||||
import ds.tree.RadixTreeImpl;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
public class fileNameHandler {
|
||||
private PackageNameEntry top;
|
||||
|
||||
public fileNameHandler(File path) {
|
||||
this.top = new PackageNameEntry(path);
|
||||
}
|
||||
|
||||
public File getUniqueFilenameForClass(String className) {
|
||||
//class names should be passed in the normal dalvik style, with a leading L, a trailing ;, and using
|
||||
//'/' as a separator.
|
||||
if (className.charAt(0) != 'L' || className.charAt(className.length()-1) != ';') {
|
||||
throw new RuntimeException("Not a valid dalvik class name");
|
||||
}
|
||||
|
||||
String blah;
|
||||
int packageElementCount = 1;
|
||||
for (int i=1; i<className.length()-1; i++) {
|
||||
if (className.charAt(i) == '/') {
|
||||
packageElementCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String[] packageElements = new String[packageElementCount];
|
||||
int elementIndex = 0;
|
||||
int elementStart = 1;
|
||||
for (int i=1; i<className.length()-1; i++) {
|
||||
if (className.charAt(i) == '/') {
|
||||
//if the first char after the initial L is a '/', or if there are
|
||||
//two consecutive '/'
|
||||
if (i-elementStart==0) {
|
||||
throw new RuntimeException("Not a valid dalvik class name");
|
||||
}
|
||||
|
||||
packageElements[elementIndex++] = className.substring(elementStart, i);
|
||||
elementStart = ++i;
|
||||
}
|
||||
}
|
||||
|
||||
//at this point, we have added all the package elements to packageElements, but still need to add
|
||||
//the final class name. elementStart should point to the beginning of the class name
|
||||
|
||||
//this will be true if the class ends in a '/', i.e. Lsome/package/className/;
|
||||
if (elementStart >= className.length()-1) {
|
||||
throw new RuntimeException("Not a valid dalvik class name");
|
||||
}
|
||||
packageElements[elementIndex] = className.substring(elementStart, className.length()-1);
|
||||
|
||||
return top.addUniqueChild(packageElements, 0);
|
||||
}
|
||||
|
||||
private static abstract class FileSystemEntry {
|
||||
public final File file;
|
||||
|
||||
public FileSystemEntry(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public abstract File addUniqueChild(String[] pathElements, int pathElementsIndex);
|
||||
|
||||
public FileSystemEntry makeVirtual(File parent) {
|
||||
return new VirtualGroupEntry(this, parent);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PackageNameEntry extends FileSystemEntry {
|
||||
//this contains the FileSystemEntries for all of this package's children
|
||||
//the associated keys are all lowercase
|
||||
private RadixTree<FileSystemEntry> children = new RadixTreeImpl<FileSystemEntry>();
|
||||
|
||||
public PackageNameEntry(File parent, String name) {
|
||||
super(new File(parent, name));
|
||||
}
|
||||
|
||||
public PackageNameEntry(File path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||
String elementName;
|
||||
String elementNameLower;
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
elementName = pathElements[pathElementsIndex] + ".smali";
|
||||
} else {
|
||||
elementName = pathElements[pathElementsIndex];
|
||||
}
|
||||
elementNameLower = elementName.toLowerCase();
|
||||
|
||||
FileSystemEntry existingEntry = children.find(elementNameLower);
|
||||
if (existingEntry != null) {
|
||||
FileSystemEntry virtualEntry = existingEntry;
|
||||
//if there is already another entry with the same name but different case, we need to
|
||||
//add a virtual group, and then add the existing entry and the new entry to that group
|
||||
if (!(existingEntry instanceof VirtualGroupEntry)) {
|
||||
if (existingEntry.file.getName().equals(elementName)) {
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
return existingEntry.file;
|
||||
} else {
|
||||
return existingEntry.addUniqueChild(pathElements, pathElementsIndex + 1);
|
||||
}
|
||||
} else {
|
||||
virtualEntry = existingEntry.makeVirtual(file);
|
||||
children.replace(elementNameLower, virtualEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return virtualEntry.addUniqueChild(pathElements, pathElementsIndex);
|
||||
}
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
ClassNameEntry classNameEntry = new ClassNameEntry(file, elementName);
|
||||
children.insert(elementNameLower, classNameEntry);
|
||||
return classNameEntry.file;
|
||||
} else {
|
||||
PackageNameEntry packageNameEntry = new PackageNameEntry(file, elementName);
|
||||
children.insert(elementNameLower, packageNameEntry);
|
||||
return packageNameEntry.addUniqueChild(pathElements, pathElementsIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A virtual group that groups together file system entries with the same name, differing only in case
|
||||
*/
|
||||
private static class VirtualGroupEntry extends FileSystemEntry {
|
||||
//this contains the FileSystemEntries for all of the files/directories in this group
|
||||
//the key is the unmodified name of the entry, before it is modified to be made unique (if needed).
|
||||
private RadixTree<FileSystemEntry> groupEntries = new RadixTreeImpl<FileSystemEntry>();
|
||||
|
||||
//whether the containing directory is case sensitive or not.
|
||||
//-1 = unset
|
||||
//0 = false;
|
||||
//1 = true;
|
||||
private int isCaseSensitive = -1;
|
||||
|
||||
public VirtualGroupEntry(FileSystemEntry firstChild, File parent) {
|
||||
super(parent);
|
||||
|
||||
//use the name of the first child in the group as-is
|
||||
groupEntries.insert(firstChild.file.getName(), firstChild);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||
String elementName = pathElements[pathElementsIndex];
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
elementName = elementName + ".smali";
|
||||
}
|
||||
|
||||
FileSystemEntry existingEntry = groupEntries.find(elementName);
|
||||
if (existingEntry != null) {
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
return existingEntry.file;
|
||||
} else {
|
||||
return existingEntry.addUniqueChild(pathElements, pathElementsIndex+1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
String fileName;
|
||||
if (!isCaseSensitive()) {
|
||||
fileName = pathElements[pathElementsIndex] + "." + (groupEntries.getSize()+1) + ".smali";
|
||||
} else {
|
||||
fileName = elementName;
|
||||
}
|
||||
|
||||
ClassNameEntry classNameEntry = new ClassNameEntry(file, fileName);
|
||||
groupEntries.insert(elementName, classNameEntry);
|
||||
return classNameEntry.file;
|
||||
} else {
|
||||
String fileName;
|
||||
if (!isCaseSensitive()) {
|
||||
fileName = pathElements[pathElementsIndex] + "." + (groupEntries.getSize()+1);
|
||||
} else {
|
||||
fileName = elementName;
|
||||
}
|
||||
|
||||
PackageNameEntry packageNameEntry = new PackageNameEntry(file, fileName);
|
||||
groupEntries.insert(elementName, packageNameEntry);
|
||||
return packageNameEntry.addUniqueChild(pathElements, pathElementsIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCaseSensitive() {
|
||||
if (isCaseSensitive != -1) {
|
||||
return isCaseSensitive == 1;
|
||||
}
|
||||
|
||||
File path = file;
|
||||
|
||||
if (path.exists() && path.isFile()) {
|
||||
path = path.getParentFile();
|
||||
}
|
||||
|
||||
if ((!file.exists() && !file.mkdirs())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean result = testCaseSensitivity(path);
|
||||
isCaseSensitive = result?1:0;
|
||||
return result;
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean testCaseSensitivity(File path) throws IOException {
|
||||
int num = 1;
|
||||
File f, f2;
|
||||
do {
|
||||
f = new File(path, "test." + num);
|
||||
f2 = new File(path, "TEST." + num++);
|
||||
} while(f.exists() || f2.exists());
|
||||
|
||||
try {
|
||||
try {
|
||||
FileWriter writer = new FileWriter(f);
|
||||
writer.write("test");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
try {f.delete();} catch (Exception ex2) {}
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (f2.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f2.createNewFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//the above 2 tests should catch almost all cases. But maybe there was a failure while creating f2
|
||||
//that isn't related to case sensitivity. Let's see if we can open the file we just created using
|
||||
//f2
|
||||
try {
|
||||
CharBuffer buf = CharBuffer.allocate(32);
|
||||
FileReader reader = new FileReader(f2);
|
||||
|
||||
while (reader.read(buf) != -1 && buf.length() < 4);
|
||||
if (buf.length() == 4 && buf.toString().equals("test")) {
|
||||
return false;
|
||||
} else {
|
||||
//we probably shouldn't get here. If the filesystem was case-sensetive, creating a new
|
||||
//FileReader should have thrown a FileNotFoundException. Otherwise, we should have opened
|
||||
//the file and read in the string "test". It's remotely possible that someone else modified
|
||||
//the file after we created it. Let's be safe and return false here as well
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
} catch (FileNotFoundException ex) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
try { f.delete(); } catch (Exception ex) {}
|
||||
try { f2.delete(); } catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemEntry makeVirtual(File parent) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassNameEntry extends FileSystemEntry {
|
||||
public ClassNameEntry(File parent, String name) {
|
||||
super(new File(parent, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||
assert false;
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
41
util/src/main/java/ds/tree/DuplicateKeyException.java
Normal file
41
util/src/main/java/ds/tree/DuplicateKeyException.java
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008 Tahseen Ur Rehman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ds.tree;
|
||||
|
||||
/**
|
||||
* excepion thrown if a duplicate key is inserted in a {@link RadixTree}
|
||||
*
|
||||
* @author Tahseen Ur Rehman
|
||||
* email: tahseen.ur.rehman {at.spam.me.not} gmail.com
|
||||
*/
|
||||
public class DuplicateKeyException extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = 3141795907493885706L;
|
||||
|
||||
public DuplicateKeyException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
115
util/src/main/java/ds/tree/RadixTree.java
Normal file
115
util/src/main/java/ds/tree/RadixTree.java
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008 Tahseen Ur Rehman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ds.tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This interface represent the operation of a radix tree. A radix tree,
|
||||
* Patricia trie/tree, or crit bit tree is a specialized set data structure
|
||||
* based on the trie that is used to store a set of strings. In contrast with a
|
||||
* regular trie, the edges of a Patricia trie are labelled with sequences of
|
||||
* characters rather than with single characters. These can be strings of
|
||||
* characters, bit strings such as integers or IP addresses, or generally
|
||||
* arbitrary sequences of objects in lexicographical order. Sometimes the names
|
||||
* radix tree and crit bit tree are only applied to trees storing integers and
|
||||
* Patricia trie is retained for more general inputs, but the structure works
|
||||
* the same way in all cases.
|
||||
*
|
||||
* @author Tahseen Ur Rehman
|
||||
* email: tahseen.ur.rehman {at.spam.me.not} gmail.com
|
||||
*/
|
||||
public interface RadixTree<T> {
|
||||
/**
|
||||
* Insert a new string key and its value to the tree.
|
||||
*
|
||||
* @param key
|
||||
* The string key of the object
|
||||
* @param value
|
||||
* The value that need to be stored corresponding to the given
|
||||
* key.
|
||||
* @throws DuplicateKeyException
|
||||
*/
|
||||
public void insert(String key, T value);
|
||||
|
||||
/**
|
||||
* Delete a key and its associated value from the tree.
|
||||
* @param key The key of the node that need to be deleted
|
||||
* @return
|
||||
*/
|
||||
public boolean delete(String key);
|
||||
|
||||
/**
|
||||
* Find a value based on its corresponding key.
|
||||
*
|
||||
* @param key The key for which to search the tree.
|
||||
* @return The value corresponding to the key. null if iot can not find the key
|
||||
*/
|
||||
public T find(String key);
|
||||
|
||||
/**
|
||||
* Find an existing entry and replace it's value. If no existing entry, do nothing
|
||||
*
|
||||
* @param key The key for which to search the tree.
|
||||
* @param value The value to set for the entry
|
||||
* @return true if an entry was found for the given key, false if not found
|
||||
*/
|
||||
public boolean replace(String key, final T value);
|
||||
|
||||
/**
|
||||
* Check if the tree contains any entry corresponding to the given key.
|
||||
*
|
||||
* @param key The key that needto be searched in the tree.
|
||||
* @return retun true if the key is present in the tree otherwise false
|
||||
*/
|
||||
public boolean contains(String key);
|
||||
|
||||
/**
|
||||
* Search for all the keys that start with given prefix. limiting the results based on the supplied limit.
|
||||
*
|
||||
* @param prefix The prefix for which keys need to be search
|
||||
* @param recordLimit The limit for the results
|
||||
* @return The list of values those key start with the given prefix
|
||||
*/
|
||||
public ArrayList<T> searchPrefix(String prefix, int recordLimit);
|
||||
|
||||
/**
|
||||
* Return the size of the Radix tree
|
||||
* @return the size of the tree
|
||||
*/
|
||||
public long getSize();
|
||||
|
||||
/**
|
||||
* Complete the a prefix to the point where ambiguity starts.
|
||||
*
|
||||
* Example:
|
||||
* If a tree contain "blah1", "blah2"
|
||||
* complete("b") -> return "blah"
|
||||
*
|
||||
* @param prefix The prefix we want to complete
|
||||
* @return The unambiguous completion of the string.
|
||||
*/
|
||||
public String complete(String prefix);
|
||||
}
|
463
util/src/main/java/ds/tree/RadixTreeImpl.java
Normal file
463
util/src/main/java/ds/tree/RadixTreeImpl.java
Normal file
@ -0,0 +1,463 @@
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
|
||||
|
||||
http://code.google.com/p/radixtree/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ds.tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formattable;
|
||||
import java.util.Formatter;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Implementation for Radix tree {@link RadixTree}
|
||||
*
|
||||
* @author Tahseen Ur Rehman (tahseen.ur.rehman {at.spam.me.not} gmail.com)
|
||||
* @author Javid Jamae
|
||||
* @author Dennis Heidsiek
|
||||
*/
|
||||
public class RadixTreeImpl<T> implements RadixTree<T>, Formattable {
|
||||
|
||||
protected RadixTreeNode<T> root;
|
||||
|
||||
protected long size;
|
||||
|
||||
/**
|
||||
* Create a Radix Tree with only the default node root.
|
||||
*/
|
||||
public RadixTreeImpl() {
|
||||
root = new RadixTreeNode<T>();
|
||||
root.setKey("");
|
||||
size = 0;
|
||||
}
|
||||
|
||||
public T find(String key) {
|
||||
Visitor<T,T> visitor = new VisitorImpl<T,T>() {
|
||||
|
||||
public void visit(String key, RadixTreeNode<T> parent,
|
||||
RadixTreeNode<T> node) {
|
||||
if (node.isReal())
|
||||
result = node.getValue();
|
||||
}
|
||||
};
|
||||
|
||||
visit(key, visitor);
|
||||
|
||||
return visitor.getResult();
|
||||
}
|
||||
|
||||
public boolean replace(String key, final T value) {
|
||||
Visitor<T,T> visitor = new VisitorImpl<T,T>() {
|
||||
public void visit(String key, RadixTreeNode<T> parent, RadixTreeNode<T> node) {
|
||||
if (node.isReal()) {
|
||||
node.setValue(value);
|
||||
result = value;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
visit(key, visitor);
|
||||
|
||||
return visitor.getResult() != null;
|
||||
}
|
||||
|
||||
public boolean delete(String key) {
|
||||
Visitor<T, Boolean> visitor = new VisitorImpl<T, Boolean>(Boolean.FALSE) {
|
||||
public void visit(String key, RadixTreeNode<T> parent,
|
||||
RadixTreeNode<T> node) {
|
||||
result = node.isReal();
|
||||
|
||||
// if it is a real node
|
||||
if (result) {
|
||||
// If there no children of the node we need to
|
||||
// delete it from the its parent children list
|
||||
if (node.getChildern().size() == 0) {
|
||||
Iterator<RadixTreeNode<T>> it = parent.getChildern()
|
||||
.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (it.next().getKey().equals(node.getKey())) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if parent is not real node and has only one child
|
||||
// then they need to be merged.
|
||||
if (parent.getChildern().size() == 1
|
||||
&& parent.isReal() == false) {
|
||||
mergeNodes(parent, parent.getChildern().get(0));
|
||||
}
|
||||
} else if (node.getChildern().size() == 1) {
|
||||
// we need to merge the only child of this node with
|
||||
// itself
|
||||
mergeNodes(node, node.getChildern().get(0));
|
||||
} else { // we jus need to mark the node as non real.
|
||||
node.setReal(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a child into its parent node. Operation only valid if it is
|
||||
* only child of the parent node and parent node is not a real node.
|
||||
*
|
||||
* @param parent
|
||||
* The parent Node
|
||||
* @param child
|
||||
* The child Node
|
||||
*/
|
||||
private void mergeNodes(RadixTreeNode<T> parent,
|
||||
RadixTreeNode<T> child) {
|
||||
parent.setKey(parent.getKey() + child.getKey());
|
||||
parent.setReal(child.isReal());
|
||||
parent.setValue(child.getValue());
|
||||
parent.setChildern(child.getChildern());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
visit(key, visitor);
|
||||
|
||||
if(visitor.getResult()) {
|
||||
size--;
|
||||
}
|
||||
return visitor.getResult().booleanValue();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see ds.tree.RadixTree#insert(java.lang.String, java.lang.Object)
|
||||
*/
|
||||
public void insert(String key, T value) throws DuplicateKeyException {
|
||||
try {
|
||||
insert(key, root, value);
|
||||
} catch (DuplicateKeyException e) {
|
||||
// re-throw the exception with 'key' in the message
|
||||
throw new DuplicateKeyException("Duplicate key: '" + key + "'");
|
||||
}
|
||||
size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively insert the key in the radix tree.
|
||||
*
|
||||
* @param key The key to be inserted
|
||||
* @param node The current node
|
||||
* @param value The value associated with the key
|
||||
* @throws DuplicateKeyException If the key already exists in the database.
|
||||
*/
|
||||
private void insert(String key, RadixTreeNode<T> node, T value)
|
||||
throws DuplicateKeyException {
|
||||
|
||||
int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
|
||||
|
||||
// we are either at the root node
|
||||
// or we need to go down the tree
|
||||
if (node.getKey().equals("") == true || numberOfMatchingCharacters == 0 || (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
|
||||
boolean flag = false;
|
||||
String newText = key.substring(numberOfMatchingCharacters, key.length());
|
||||
for (RadixTreeNode<T> child : node.getChildern()) {
|
||||
if (child.getKey().startsWith(newText.charAt(0) + "")) {
|
||||
flag = true;
|
||||
insert(newText, child, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// just add the node as the child of the current node
|
||||
if (flag == false) {
|
||||
RadixTreeNode<T> n = new RadixTreeNode<T>();
|
||||
n.setKey(newText);
|
||||
n.setReal(true);
|
||||
n.setValue(value);
|
||||
|
||||
node.getChildern().add(n);
|
||||
}
|
||||
}
|
||||
// there is a exact match just make the current node as data node
|
||||
else if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters == node.getKey().length()) {
|
||||
if (node.isReal() == true) {
|
||||
throw new DuplicateKeyException("Duplicate key");
|
||||
}
|
||||
|
||||
node.setReal(true);
|
||||
node.setValue(value);
|
||||
}
|
||||
// This node need to be split as the key to be inserted
|
||||
// is a prefix of the current node key
|
||||
else if (numberOfMatchingCharacters > 0 && numberOfMatchingCharacters < node.getKey().length()) {
|
||||
RadixTreeNode<T> n1 = new RadixTreeNode<T>();
|
||||
n1.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
|
||||
n1.setReal(node.isReal());
|
||||
n1.setValue(node.getValue());
|
||||
n1.setChildern(node.getChildern());
|
||||
|
||||
node.setKey(key.substring(0, numberOfMatchingCharacters));
|
||||
node.setReal(false);
|
||||
node.setChildern(new ArrayList<RadixTreeNode<T>>());
|
||||
node.getChildern().add(n1);
|
||||
|
||||
if(numberOfMatchingCharacters < key.length()) {
|
||||
RadixTreeNode<T> n2 = new RadixTreeNode<T>();
|
||||
n2.setKey(key.substring(numberOfMatchingCharacters, key.length()));
|
||||
n2.setReal(true);
|
||||
n2.setValue(value);
|
||||
|
||||
node.getChildern().add(n2);
|
||||
} else {
|
||||
node.setValue(value);
|
||||
node.setReal(true);
|
||||
}
|
||||
}
|
||||
// this key need to be added as the child of the current node
|
||||
else {
|
||||
RadixTreeNode<T> n = new RadixTreeNode<T>();
|
||||
n.setKey(node.getKey().substring(numberOfMatchingCharacters, node.getKey().length()));
|
||||
n.setChildern(node.getChildern());
|
||||
n.setReal(node.isReal());
|
||||
n.setValue(node.getValue());
|
||||
|
||||
node.setKey(key);
|
||||
node.setReal(true);
|
||||
node.setValue(value);
|
||||
|
||||
node.getChildern().add(n);
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<T> searchPrefix(String key, int recordLimit) {
|
||||
ArrayList<T> keys = new ArrayList<T>();
|
||||
|
||||
RadixTreeNode<T> node = searchPefix(key, root);
|
||||
|
||||
if (node != null) {
|
||||
if (node.isReal()) {
|
||||
keys.add(node.getValue());
|
||||
}
|
||||
getNodes(node, keys, recordLimit);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
private void getNodes(RadixTreeNode<T> parent, ArrayList<T> keys, int limit) {
|
||||
Queue<RadixTreeNode<T>> queue = new LinkedList<RadixTreeNode<T>>();
|
||||
|
||||
queue.addAll(parent.getChildern());
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
RadixTreeNode<T> node = queue.remove();
|
||||
if (node.isReal() == true) {
|
||||
keys.add(node.getValue());
|
||||
}
|
||||
|
||||
if (keys.size() == limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
queue.addAll(node.getChildern());
|
||||
}
|
||||
}
|
||||
|
||||
private RadixTreeNode<T> searchPefix(String key, RadixTreeNode<T> node) {
|
||||
RadixTreeNode<T> result = null;
|
||||
|
||||
int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(key);
|
||||
|
||||
if (numberOfMatchingCharacters == key.length() && numberOfMatchingCharacters <= node.getKey().length()) {
|
||||
result = node;
|
||||
} else if (node.getKey().equals("") == true
|
||||
|| (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters >= node.getKey().length())) {
|
||||
String newText = key.substring(numberOfMatchingCharacters, key.length());
|
||||
for (RadixTreeNode<T> child : node.getChildern()) {
|
||||
if (child.getKey().startsWith(newText.charAt(0) + "")) {
|
||||
result = searchPefix(newText, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean contains(String key) {
|
||||
Visitor<T, Boolean> visitor = new VisitorImpl<T,Boolean>(Boolean.FALSE) {
|
||||
public void visit(String key, RadixTreeNode<T> parent,
|
||||
RadixTreeNode<T> node) {
|
||||
result = node.isReal();
|
||||
}
|
||||
};
|
||||
|
||||
visit(key, visitor);
|
||||
|
||||
return visitor.getResult().booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* visit the node those key matches the given key
|
||||
* @param key The key that need to be visited
|
||||
* @param visitor The visitor object
|
||||
*/
|
||||
public <R> void visit(String key, Visitor<T, R> visitor) {
|
||||
if (root != null) {
|
||||
visit(key, visitor, null, root);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* recursively visit the tree based on the supplied "key". calls the Visitor
|
||||
* for the node those key matches the given prefix
|
||||
*
|
||||
* @param prefix
|
||||
* The key o prefix to search in the tree
|
||||
* @param visitor
|
||||
* The Visitor that will be called if a node with "key" as its
|
||||
* key is found
|
||||
* @param node
|
||||
* The Node from where onward to search
|
||||
*/
|
||||
private <R> void visit(String prefix, Visitor<T, R> visitor,
|
||||
RadixTreeNode<T> parent, RadixTreeNode<T> node) {
|
||||
|
||||
int numberOfMatchingCharacters = node.getNumberOfMatchingCharacters(prefix);
|
||||
|
||||
// if the node key and prefix match, we found a match!
|
||||
if (numberOfMatchingCharacters == prefix.length() && numberOfMatchingCharacters == node.getKey().length()) {
|
||||
visitor.visit(prefix, parent, node);
|
||||
} else if (node.getKey().equals("") == true // either we are at the
|
||||
// root
|
||||
|| (numberOfMatchingCharacters < prefix.length() && numberOfMatchingCharacters >= node.getKey().length())) { // OR we need to
|
||||
// traverse the childern
|
||||
String newText = prefix.substring(numberOfMatchingCharacters, prefix.length());
|
||||
for (RadixTreeNode<T> child : node.getChildern()) {
|
||||
// recursively search the child nodes
|
||||
if (child.getKey().startsWith(newText.charAt(0) + "")) {
|
||||
visit(newText, visitor, node, child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Trie on console.
|
||||
*
|
||||
* WARNING! Do not use this for a large Trie, it's for testing purpose only.
|
||||
* @see formatTo
|
||||
*/
|
||||
@Deprecated
|
||||
public void display() {
|
||||
formatNodeTo(new Formatter(System.out), 0, root);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private void display(int level, RadixTreeNode<T> node) {
|
||||
formatNodeTo(new Formatter(System.out), level, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING! Do not use this for a large Trie, it's for testing purpose only.
|
||||
*/
|
||||
private void formatNodeTo(Formatter f, int level, RadixTreeNode<T> node) {
|
||||
for (int i = 0; i < level; i++) {
|
||||
f.format(" ");
|
||||
}
|
||||
f.format("|");
|
||||
for (int i = 0; i < level; i++) {
|
||||
f.format("-");
|
||||
}
|
||||
|
||||
if (node.isReal() == true)
|
||||
f.format("%s[%s]*%n", node.getKey(), node.getValue());
|
||||
else
|
||||
f.format("%s%n", node.getKey());
|
||||
|
||||
for (RadixTreeNode<T> child : node.getChildern()) {
|
||||
formatNodeTo(f, level + 1, child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a textual representation of this tree to the given formatter.
|
||||
*
|
||||
* Currently, all options are simply ignored.
|
||||
*
|
||||
* WARNING! Do not use this for a large Trie, it's for testing purpose only.
|
||||
*/
|
||||
@Override
|
||||
public void formatTo(Formatter formatter, int flags, int width, int precision) {
|
||||
formatNodeTo(formatter, 0, root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the a prefix to the point where ambiguity starts.
|
||||
*
|
||||
* Example:
|
||||
* If a tree contain "blah1", "blah2"
|
||||
* complete("b") -> return "blah"
|
||||
*
|
||||
* @param prefix The prefix we want to complete
|
||||
* @return The unambiguous completion of the string.
|
||||
*/
|
||||
public String complete(String prefix) {
|
||||
return complete(prefix, root, "");
|
||||
}
|
||||
|
||||
private String complete(String key, RadixTreeNode<T> node, String base) {
|
||||
int i = 0;
|
||||
int keylen = key.length();
|
||||
int nodelen = node.getKey().length();
|
||||
|
||||
while (i < keylen && i < nodelen) {
|
||||
if (key.charAt(i) != node.getKey().charAt(i)) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == keylen && i <= nodelen) {
|
||||
return base + node.getKey();
|
||||
}
|
||||
else if (nodelen == 0 || (i < keylen && i >= nodelen)) {
|
||||
String beginning = key.substring(0, i);
|
||||
String ending = key.substring(i, keylen);
|
||||
for (RadixTreeNode<T> child : node.getChildern()) {
|
||||
if (child.getKey().startsWith(ending.charAt(0) + "")) {
|
||||
return complete(ending, child, base + beginning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
103
util/src/main/java/ds/tree/RadixTreeNode.java
Normal file
103
util/src/main/java/ds/tree/RadixTreeNode.java
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008 Tahseen Ur Rehman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ds.tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a node of a Radix tree {@link RadixTreeImpl}
|
||||
*
|
||||
* @author Tahseen Ur Rehman
|
||||
* @email tahseen.ur.rehman {at.spam.me.not} gmail.com
|
||||
* @param <T>
|
||||
*/
|
||||
class RadixTreeNode<T> {
|
||||
private String key;
|
||||
|
||||
private List<RadixTreeNode<T>> childern;
|
||||
|
||||
private boolean real;
|
||||
|
||||
private T value;
|
||||
|
||||
/**
|
||||
* intailize the fields with default values to avoid null reference checks
|
||||
* all over the places
|
||||
*/
|
||||
public RadixTreeNode() {
|
||||
key = "";
|
||||
childern = new ArrayList<RadixTreeNode<T>>();
|
||||
real = false;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T data) {
|
||||
this.value = data;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String value) {
|
||||
this.key = value;
|
||||
}
|
||||
|
||||
public boolean isReal() {
|
||||
return real;
|
||||
}
|
||||
|
||||
public void setReal(boolean datanode) {
|
||||
this.real = datanode;
|
||||
}
|
||||
|
||||
public List<RadixTreeNode<T>> getChildern() {
|
||||
return childern;
|
||||
}
|
||||
|
||||
public void setChildern(List<RadixTreeNode<T>> childern) {
|
||||
this.childern = childern;
|
||||
}
|
||||
|
||||
public int getNumberOfMatchingCharacters(String key) {
|
||||
int numberOfMatchingCharacters = 0;
|
||||
while (numberOfMatchingCharacters < key.length() && numberOfMatchingCharacters < this.getKey().length()) {
|
||||
if (key.charAt(numberOfMatchingCharacters) != this.getKey().charAt(numberOfMatchingCharacters)) {
|
||||
break;
|
||||
}
|
||||
numberOfMatchingCharacters++;
|
||||
}
|
||||
return numberOfMatchingCharacters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key;
|
||||
}
|
||||
}
|
56
util/src/main/java/ds/tree/Visitor.java
Normal file
56
util/src/main/java/ds/tree/Visitor.java
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008 Tahseen Ur Rehman, Javid Jamae
|
||||
|
||||
http://code.google.com/p/radixtree/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package ds.tree;
|
||||
|
||||
/**
|
||||
* The visitor interface that is used by {@link RadixTreeImpl} for perfroming
|
||||
* task on a searched node.
|
||||
*
|
||||
* @author Tahseen Ur Rehman (tahseen.ur.rehman {at.spam.me.not} gmail.com)
|
||||
* @author Javid Jamae
|
||||
* @author Dennis Heidsiek
|
||||
* @param <T,R>
|
||||
*/
|
||||
public interface Visitor<T, R> {
|
||||
/**
|
||||
* This method gets called by {@link RadixTreeImpl#visit(String, Visitor) visit}
|
||||
* when it finds a node matching the key given to it.
|
||||
*
|
||||
* @param key The key that matched the node
|
||||
* @param parent The parent of the node being visited
|
||||
* @param node The node that is being visited
|
||||
*/
|
||||
public void visit(String key, RadixTreeNode<T> parent, RadixTreeNode<T> node);
|
||||
|
||||
/**
|
||||
* The visitor can store any type of result object, depending on the context of
|
||||
* what it is being used for.
|
||||
*
|
||||
* @return The result captured by the visitor.
|
||||
*/
|
||||
public R getResult();
|
||||
}
|
30
util/src/main/java/ds/tree/VisitorImpl.java
Normal file
30
util/src/main/java/ds/tree/VisitorImpl.java
Normal file
@ -0,0 +1,30 @@
|
||||
package ds.tree;
|
||||
|
||||
|
||||
/**
|
||||
* A simple standard implementation for a {@link visitor}.
|
||||
*
|
||||
* @author Dennis Heidsiek
|
||||
* @param <T,R>
|
||||
*/
|
||||
public abstract class VisitorImpl<T, R> implements Visitor<T, R> {
|
||||
|
||||
protected R result;
|
||||
|
||||
public VisitorImpl() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
public VisitorImpl(R initialValue) {
|
||||
this.result = initialValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
abstract public void visit(String key, RadixTreeNode<T> parent, RadixTreeNode<T> node);
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user