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:
JesusFreke@JesusFreke.com 2010-08-02 00:58:12 +00:00
parent bcc4d2d9e1
commit a6e5671a62
9 changed files with 1174 additions and 17 deletions

31
NOTICE
View File

@ -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.
*******************************************************************************

View File

@ -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);

View 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;
}
}
}

View 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);
}
}

View 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);
}

View 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 "";
}
}

View 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;
}
}

View 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();
}

View 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);
}