/*
* 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.patricia.terminletters;
import java.util.Arrays;
import org.trie4j.NodeVisitor;
import org.trie4j.util.Pair;
public class Node implements org.trie4j.Node{
public Node() {
}
public Node(char[] letters) {
this.letters = letters;
}
public Node(char[] letters, Node[] children) {
this.children = children;
this.letters = letters;
}
public org.trie4j.Node[] getChildren() {
return children;
}
public void setChildren(Node[] children) {
this.children = children;
}
public char[] getLetters() {
return letters;
}
public void setLetters(char[] letters) {
this.letters = letters;
}
public Pair<Boolean, Integer> compareLetters(char[] value, int offset){
int rest = value.length - offset;
int len = letters.length;
if(letters[len - 1] == 0xffff){
len--;
}
int n = Math.min(len, rest);
for(int i = 0; i < len; i++){
int c = letters[i] - value[offset + i];
if(c == 0) continue;
if(c < 0) return Pair.create(false, -i);
return Pair.create(false, i);
}
if(len == rest) return Pair.create(true, n);
if(len - rest < 0) return Pair.create(true, -n);
return Pair.create(true, n);
}
public boolean isTerminate() {
return children == null || (letters != null && letters.length > 0 && letters[letters.length - 1] == 0xffff);
}
public Node getChild(char c){
if(children != null){
int end = children.length;
if(end > 16){
int start = 0;
while(start < end){
int i = (start + end) / 2;
Node 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++){
Node n = children[i];
if(n.letters != null && n.letters.length > 0 && n.letters[0] == c){
return n;
}
}
}
}
return null;
}
public void insertChild(char[] letters, int offset){
if(this.letters == null){
this.letters = CharsUtil.newTerminatedCharsFrom(letters, offset, letters.length);
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;
boolean terminated = false;
if(thisLettersLength > 0 && this.letters[thisLettersLength - 1] == (char)-1){
thisLettersLength--;
terminated = true;
}
if(children == null){
terminated = true;
}
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){
if(!terminated && children != null){
this.letters = CharsUtil.newTerminatedChars(this.letters);
}
return;
}
if(lettersRest < thisLettersLength){
Node child = new Node(
Arrays.copyOfRange(this.letters, lettersRest, this.letters.length)
, this.children);
this.letters = CharsUtil.newTerminatedCharsFrom(this.letters, 0, i);
this.children = new Node[]{child};
return;
}
if(children != null){
int index = 0;
int end = children.length;
if(end > 16){
int start = 0;
while(start < end){
index = (start + end) / 2;
Node 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++){
Node 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 Node(Arrays.copyOfRange(letters, i + offset, letters.length)));
} else{
this.letters = CharsUtil.newTerminatedChars(this.letters);
this.children = new Node[]{
new Node(Arrays.copyOfRange(letters, i + offset, letters.length))
};
}
return;
}
char[] newLetter1 = Arrays.copyOfRange(this.letters, 0, i);
char[] newLetter2 = Arrays.copyOfRange(this.letters, i, this.letters.length);
char[] newLetter3 = Arrays.copyOfRange(letters, i + offset, letters.length);
Node[] newChildren = new Node[2];
if(newLetter2[0] < newLetter3[0]){
newChildren[0] = new Node(newLetter2, this.children);
newChildren[1] = new Node(newLetter3);
} else{
newChildren[0] = new Node(newLetter3);
newChildren[1] = new Node(newLetter2, this.children);
}
this.letters = newLetter1;
this.children = newChildren;
}
public boolean contains(char[] letters, int offset){
int rest = letters.length - offset;
int tll = this.letters.length;
boolean terminated = false;
if(tll > 0 && this.letters[tll - 1] == (char)-1){
tll--;
terminated = true;
}
if(children == null){
terminated = true;
}
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;
char c = letters[offset];
Node n = getChild(c);
if(n != null){
return n.contains(letters, offset);
}
return false;
}
public void visit(NodeVisitor visitor, int nest){
visitor.visit(this, nest);
nest++;
if(children != null){
for(Node n : children){
n.visit(visitor, nest);
}
}
}
private void addChild(int index, Node n){
Node[] newc = new Node[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 Node[] children;
private char[] letters;
}