/*
* 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 com.addthis.hydra.data.tree;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import com.addthis.basis.util.ClosableIterator;
import com.addthis.basis.util.MemoryCounter.Mem;
import com.addthis.hydra.store.db.DBKey;
import com.addthis.hydra.store.db.IPageDB;
import com.addthis.hydra.store.db.IReadWeighable;
import com.google.common.base.Objects;
/**
* <p/>
* read only tree node that plays nice with ReadTree
*/
public class ReadTreeNode extends AbstractTreeNode implements IReadWeighable {
//reference to the transient (in memory) tree object -- not serialized
@Mem(estimate = false, size = 64)
private ReadTree tree;
/** node's name (eg 'www.ianrules.com'). Combines with its parent's nodedb (not provided), to form its unique id. */
protected String name;
/** required for CodecBin2. must be followed by an init() call. */
public ReadTreeNode() {}
public ReadTreeNode(String name, int weight) {
this.name = name;
setWeight(weight);
}
/**
* Set by the tree when the node is deserialized since the name is kept in the key
* and the decoder does not have a reference to the tree.
*
* @param tree The ReadTree this node belongs to
* @param name The name/label/key of the node
*/
protected void init(ReadTree tree, String name) {
this.tree = tree;
this.name = name;
}
@Override public String getName() {
return name;
}
public long nodeDB() {
return nodedb;
}
@Nullable @Override public DataTreeNode getNode(String name) {
return tree.getNode(this, name);
}
@Override public Map<String, TreeNodeData> getDataMap() {
return data;
}
@Nullable @Override public DataTreeNodeActor getData(String key) {
return (data != null) ? data.get(key) : null;
}
@Override public long getCounter() {
return hits;
}
@Override public int getNodeCount() {
return nodes;
}
@Override public DataTree getTreeRoot() {
return tree;
}
// the bits field is co-opted here to store the weight since we have no need for it in the query system
@Override public void setWeight(int weight) {
bits = weight;
}
@Override public int getWeight() {
return bits;
}
@NotThreadSafe
private final class Iter implements ClosableIterator<DataTreeNode> {
private IPageDB.Range<DBKey, ReadTreeNode> range;
private DataTreeNode next;
private Iter(@Nullable IPageDB.Range<DBKey, ReadTreeNode> range) {
this.range = range;
fetchNext();
}
@Override public String toString() {
return "Iter(" + range + "," + next + ")";
}
/** Does not interact with node cache in tree */
void fetchNext() {
if (range != null) {
next = null;
if (range.hasNext()) {
Map.Entry<DBKey, ReadTreeNode> tne = range.next();
next = tne.getValue();
((ReadTreeNode) next).init(tree, tne.getKey().rawKey().toString());
}
}
}
@Override public boolean hasNext() {
return next != null;
}
@Override public DataTreeNode next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
DataTreeNode ret = next;
fetchNext();
return ret;
}
@Override public void close() {
if (range != null) {
range.close();
range = null;
}
}
}
@Override public Iterator<DataTreeNode> iterator() {
return getIterator();
}
@Override public ClosableIterator<DataTreeNode> getIterator() {
return !hasNodes() ? new Iter(null) : new Iter(tree.fetchNodeRange(nodedb));
}
@Override public ClosableIterator<DataTreeNode> getIterator(String prefix) {
if ((prefix != null) && !prefix.isEmpty()) {
StringBuilder sb = new StringBuilder(prefix.substring(0, prefix.length() - 1));
sb.append((char) (prefix.charAt(prefix.length() - 1) + 1));
return !hasNodes() ? new Iter(null) : new Iter(tree.fetchNodeRange(nodedb, prefix, sb.toString()));
} else {
return new Iter(null);
}
}
@Override public ClosableIterator<DataTreeNode> getIterator(String from, String to) {
return !hasNodes() ? new Iter(null) : new Iter(tree.fetchNodeRange(nodedb, from, to));
}
@Override public byte[] bytesEncode(long version) {
throw new UnsupportedOperationException("ReadTreeNode cannot be encoded");
}
/** Returns a clone of this node, but with a new hits value. */
public DataTreeNode getCloneWithCount(long val) {
ReadTreeNode tn = new ReadTreeNode();
tn.name = name;
tn.hits = val; //the count change
tn.nodes = nodes;
tn.nodedb = nodedb;
tn.bits = bits;
tn.data = data;
tn.tree = tree;
return tn;
}
@Override public String toString() {
return Objects.toStringHelper(this)
.add("name", name)
.add("nodedb", nodedb)
.add("nodes", nodes)
.add("hits", hits)
.add("weight", getWeight())
.toString();
}
}