/*
* Copyright 2012 Takao Nakaguchi
*
* 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.trie4j.bytes;
import java.util.Arrays;
public class PatriciaTrieNode implements Node{
public PatriciaTrieNode() {
}
public PatriciaTrieNode(byte[] letters, boolean terminated) {
this.children = emptyChildren;
this.letters = letters;
this.terminated = terminated;
}
public PatriciaTrieNode(byte[] letters, PatriciaTrieNode[] children, boolean terminated) {
this.children = children;
this.letters = letters;
this.terminated = terminated;
}
public PatriciaTrieNode[] getChildren() {
return children;
}
public void setChildren(PatriciaTrieNode[] children) {
this.children = children;
}
public byte[] getLetters() {
return letters;
}
public void setLetters(byte[] letters) {
this.letters = letters;
}
public boolean isTerminate() {
return terminated;
}
public void setTerminated(boolean terminated) {
this.terminated = terminated;
}
public PatriciaTrieNode getChild(byte c){
if(children != null){
int end = children.length;
if(end > 16){
int start = 0;
while(start < end){
int i = (start + end) / 2;
PatriciaTrieNode n = children[i];
int d = c - n.letters[0];
if(d == 0) return n;
if(d < 0){
end = i;
} else if(start == i){
break;
} else{
start = i;
}
}
} else{
for(int i = 0; i < end; i++){
PatriciaTrieNode n = children[i];
if(n.letters != null && n.letters.length > 0 && n.letters[0] == c){
return n;
}
}
}
}
return null;
}
public void insertChild(byte[] letters, int offset){
if(this.letters == null){
this.letters = Arrays.copyOfRange(letters, offset, letters.length);
this.terminated = true;
return;
}
//hello$
//h -> insert to children (a)
//h$ -> insert to children (b)
//hat -> h, at, ello$ (c)
//hat$ -> h, at$, ello$ (d)
//hello -> hello$ (e)
//hello$ -> hello$ (f)
//helloworld$ -> hello$, helloworld$ (g)
//helloworld (h)
int i = 0;
int lettersRest = letters.length - offset;
int thisLettersLength = this.letters.length;
int n = Math.min(lettersRest, thisLettersLength);
int c = 0;
while(i < n && (c = letters[i + offset] - this.letters[i]) == 0) i++;
if(i == n){
if(lettersRest == thisLettersLength){
this.terminated = true;
return;
}
if(lettersRest < thisLettersLength){
PatriciaTrieNode child = new PatriciaTrieNode(
Arrays.copyOfRange(this.letters, lettersRest, this.letters.length)
, this.children, this.terminated);
this.letters = Arrays.copyOfRange(this.letters, 0, i);
this.children = new PatriciaTrieNode[]{child};
this.terminated = true;
return;
}
if(children != null){
int index = 0;
int end = children.length;
if(end > 16){
int start = 0;
while(start < end){
index = (start + end) / 2;
PatriciaTrieNode child = children[index];
c = letters[i + offset] - child.letters[0];
if(c == 0){
child.insertChild(letters, i + offset);
return;
}
if(c < 0){
end = index;
} else if(start == index){
index = end;
break;
} else{
start = index;
}
}
} else{
for(; index < end; index++){
PatriciaTrieNode child = children[index];
c = letters[i + offset] - child.letters[0];
if(c < 0) break;
if(c == 0){
child.insertChild(letters, i + offset);
return;
}
}
}
addChild(index, new PatriciaTrieNode(Arrays.copyOfRange(letters, i + offset, letters.length), true));
} else{
this.children = new PatriciaTrieNode[]{
new PatriciaTrieNode(Arrays.copyOfRange(letters, i + offset, letters.length), true)
};
this.terminated = true;
}
return;
}
byte[] newLetter1 = Arrays.copyOfRange(this.letters, 0, i);
byte[] newLetter2 = Arrays.copyOfRange(this.letters, i, this.letters.length);
byte[] newLetter3 = Arrays.copyOfRange(letters, i + offset, letters.length);
PatriciaTrieNode[] newChildren = new PatriciaTrieNode[2];
if(newLetter2[0] < newLetter3[0]){
newChildren[0] = new PatriciaTrieNode(newLetter2, this.children, true);
newChildren[1] = new PatriciaTrieNode(newLetter3, true);
} else{
newChildren[0] = new PatriciaTrieNode(newLetter3, true);
newChildren[1] = new PatriciaTrieNode(newLetter2, this.children, true);
}
this.letters = newLetter1;
this.children = newChildren;
this.terminated = false;
}
public boolean contains(byte[] letters, int offset){
int rest = letters.length - offset;
int tll = this.letters.length;
if(tll > rest) return false;
for(int i = 0; i < tll; i++){
if(this.letters[i] != letters[i + offset]) return false;
}
if(tll == rest){
return terminated;
}
offset += tll;
byte c = letters[offset];
PatriciaTrieNode n = getChild(c);
if(n != null){
return n.contains(letters, offset);
}
return false;
}
public void visit(TrieVisitor visitor, int nest){
visitor.accept(this, nest);
nest++;
if(children != null){
for(PatriciaTrieNode n : children){
n.visit(visitor, nest);
}
}
}
private void addChild(int index, PatriciaTrieNode n){
PatriciaTrieNode[] newc = new PatriciaTrieNode[children.length + 1];
System.arraycopy(children, 0, newc, 0, index);
newc[index] = n;
System.arraycopy(children, index, newc, index + 1, children.length - index);
this.children = newc;
}
private PatriciaTrieNode[] children;
private byte[] letters;
private boolean terminated;
private static PatriciaTrieNode[] emptyChildren = {};
}