/*
* 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.multilayer.node;
import java.util.Arrays;
import org.trie4j.NodeVisitor;
import org.trie4j.patricia.multilayer.Node;
import org.trie4j.patricia.multilayer.labeltrie.LabelNode;
import org.trie4j.patricia.multilayer.labeltrie.LabelTrie;
import org.trie4j.util.CharsUtil;
public class CharsNode extends Node{
public CharsNode(char[] letters) {
this.letters = letters;
}
public char[] getLetters() {
return letters;
}
public void setLetters(char[] letters) {
this.letters = letters;
}
public boolean isTerminate() {
return false;
}
@Override
public char getFirstLetter() {
return letters[0];
}
@Override
public Node getChild(char c) {
return null;
}
@Override
public Node[] getChildren() {
return null;
}
@Override
public void setChildren(Node[] children) {
throw new UnsupportedOperationException();
}
public void setChild(int index, Node child){
throw new UnsupportedOperationException();
}
public Node insertChild(char[] letters, int offset){
//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++;
Node[] children = getChildren();
if(i == n){
if(lettersRest == thisLettersLength){
if(isTerminate()) return null;
if(children != null){
if(children.length == 1){
return new TerminalSingleChildInternalCharsNode(this.letters, children[0]);
} else{
return new TerminalInternalCharsNode(this.letters, children);
}
} else{
return new TerminalCharsNode(this.letters);
}
}
if(lettersRest < thisLettersLength){
char[] newLetters = Arrays.copyOfRange(this.letters, 0, i);
char[] newChildLetters = Arrays.copyOfRange(this.letters, lettersRest, thisLettersLength);
this.letters = newChildLetters;
return new TerminalSingleChildInternalCharsNode(
newLetters
, this);
}
if(children != null){
int index = 0;
int end = getChildren().length;
if(end > 16){
int start = 0;
while(start < end){
index = (start + end) / 2;
Node child = children[index];
c = letters[i + offset] - child.getFirstLetter();
if(c == 0){
Node newChild = child.insertChild(letters, i + offset);
if(newChild != null){
setChild(index, newChild);
}
return null;
}
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.getFirstLetter();
if(c < 0) break;
if(c == 0){
Node newChild = child.insertChild(letters, i + offset);
if(newChild != null){
setChild(index, newChild);
}
return null;
}
}
}
return addChild(index, new TerminalCharsNode(Arrays.copyOfRange(letters, i + offset, letters.length)));
} else{
return newInternalCharsNode(
this.letters
, new Node[]{
new TerminalCharsNode(Arrays.copyOfRange(letters, i + offset, letters.length))
});
}
}
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] = cloneWithLetters(newLetter2);
newChildren[1] = new TerminalCharsNode(newLetter3);
} else{
newChildren[0] = new TerminalCharsNode(newLetter3);
newChildren[1] = cloneWithLetters(newLetter2);
}
return new InternalCharsNode(newLetter1, newChildren);
}
public Node pushLabel(LabelTrie trie){
Node[] children = getChildren();
Node ret = this;
if(letters.length > 0){
ret = newLabelTrieNode(
trie.insert(CharsUtil.revert(letters))
, children);
letters = LabelNode.emptyChars;
}
if(children != null){
int n = children.length;
for(int i = 0; i < n; i++){
ret.setChild(i, children[i].pushLabel(trie));
}
}
return ret;
}
@Override
public void visit(NodeVisitor visitor, int nest) {
visitor.visit(this, nest);
}
protected Node newInternalCharsNode(char[] letters, Node[] children){
if(children.length == 1){
return new SingleChildInternalCharsNode(letters, children[0]);
} else{
return new InternalCharsNode(letters, children);
}
}
protected Node cloneWithLetters(char[] letters){
return new CharsNode(letters);
}
protected Node newLabelTrieNode(LabelNode ln, Node[] children){
if(children != null){
return new InternalLabelTrieNode(ln, children);
} else{
return new LabelTrieNode(ln);
}
}
@Override
public Node addChild(int index, Node n) {
throw new UnsupportedOperationException();
}
public boolean contains(char[] letters, int offset){
int rest = letters.length - offset;
int tll = this.letters.length;
if(tll > 0){
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 isTerminate();
}
offset += tll;
}
if(rest > 0){
char c = letters[offset];
Node n = getChild(c);
if(n != null){
return n.contains(letters, offset);
}
}
return false;
}
private char[] letters;
}