/* * 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.nonconcurrent; import com.addthis.basis.util.ClosableIterator; import com.addthis.basis.util.MemoryCounter.Mem; import com.addthis.hydra.data.tree.AbstractTreeNode; import com.addthis.hydra.data.tree.DataTreeNode; import com.addthis.hydra.data.tree.DataTreeNodeActor; import com.addthis.hydra.data.tree.DataTreeNodeInitializer; import com.addthis.hydra.data.tree.DataTreeNodeUpdater; import com.addthis.hydra.data.tree.TreeDataParameters; import com.addthis.hydra.data.tree.TreeDataParent; import com.addthis.hydra.data.tree.TreeNodeData; import com.addthis.hydra.data.tree.TreeNodeDataDeferredOperation; import com.addthis.hydra.store.db.DBKey; import com.addthis.hydra.store.db.IPageDB.Range; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.NoSuchElementException; /** * A non-concurrent implementation of {@link AbstractTreeNode}. This * instance is not thread safe (as the name implies) and is designed * to work efficiently in a single threaded environment */ public class NonConcurrentTreeNode extends AbstractTreeNode { private String name; private DBKey dbkey; public static NonConcurrentTreeNode getTreeRoot(NonConcurrentTree tree) { NonConcurrentTreeNode node = new NonConcurrentTreeNode(); node.tree = tree; node.nodedb = 1L; return node; } /** * required for Codable. must be followed by an init() call. */ public NonConcurrentTreeNode() { } protected void initNode(NonConcurrentTree tree, DBKey key, String name) { this.tree = tree; this.dbkey = key; this.name = name; } protected void init(NonConcurrentTree tree, DBKey key, String name) { this.tree = tree; this.dbkey = key; this.name = name; } @Mem(estimate = false, size = 64) private NonConcurrentTree tree; public String toString() { return "TN[k=" + dbkey + ",db=" + nodedb + ",n#=" + nodes + ",h#=" + hits + ",nm=" + name + ",bi=" + bits + "]"; } @Override public String getName() { return name; } protected int updateNodeCount(int delta) { nodes += delta; return nodes; } protected void requireNodeDB() { if (!hasNodes()) { nodedb = tree.getNextNodeDB(); } } /** * returns an iterator of read-only nodes */ public ClosableIterator<DataTreeNode> getNodeIterator() { return !hasNodes() ? new Iter(null) : new Iter(tree.fetchNodeRange(nodedb)); } @Override public NonConcurrentTree getTreeRoot() { return tree; } /** * returns an iterator of read-only nodes */ public ClosableIterator<DataTreeNode> getNodeIterator(String prefix) { if (prefix != null && prefix.length() > 0) { /** * the reason this behaves as a prefix as opposed to a "from" is the way * the "to" endpoint is calculated. the last byte of the prefix is incremented * by a value of one and used as "to". this is the effect of excluding all * other potential matches with a lexicographic value greater than prefix. */ StringBuilder sb = new StringBuilder(prefix.substring(0, prefix.length() - 1)); sb.append((char) (prefix.charAt(prefix.length() - 1) + 1)); return getNodeIterator(prefix, sb.toString()); } else { return new Iter(null); } } /** * returns an iterator of read-only nodes */ public ClosableIterator<DataTreeNode> getNodeIterator(String from, String to) { if (!hasNodes()) { return new Iter(null); } return new Iter(tree.fetchNodeRange(nodedb, from, to)); } @Override public NonConcurrentTreeNode getNode(String name) { return tree.getNode(this, name, false); } @Override public ClosableIterator<DataTreeNode> getIterator() { return getNodeIterator(); } @Override public ClosableIterator<DataTreeNode> getIterator(String prefix) { return getNodeIterator(prefix); } @Override public ClosableIterator<DataTreeNode> getIterator(String from, String to) { return getNodeIterator(from, to); } @Override public NonConcurrentTreeNode getLeasedNode(String name) { return tree.getNode(this, name, true); } public NonConcurrentTreeNode getOrCreateEditableNode(String name) { return getOrCreateEditableNode(name, null); } public NonConcurrentTreeNode getOrCreateEditableNode(String name, DataTreeNodeInitializer creator) { return tree.getOrCreateNode(this, name, creator); } @Override public boolean deleteNode(String name) { return tree.deleteNode(this, name); } /** * link this node (aliasing) to another node in the tree. they will share * children, but not meta-data. should only be called from within a * TreeNodeInitializer passed to getOrCreateEditableNode. */ @Override public boolean aliasTo(DataTreeNode node) { if (node.getClass() != NonConcurrentTreeNode.class) { return false; } if (hasNodes()) { return false; } ((NonConcurrentTreeNode) node).requireNodeDB(); nodedb = ((NonConcurrentTreeNode) node).nodedb; markAlias(); return true; } /** * TODO: warning. if you annotate a path with data then have another path * that intersects that node in the tree with some other data, the first one * wins and the new data will not be added. further, every time the * annotated node is crossed, the attached data will be updated if that path * declares annotated data. */ @Override @SuppressWarnings("unchecked") public void updateChildData(DataTreeNodeUpdater state, TreeDataParent path) { HashMap<String, TreeDataParameters> dataconf = path.dataConfig(); if (path.assignHits()) { hits = state.getAssignmentValue(); } else if (path.countHits()) { hits += state.getCountValue(); } if (dataconf != null) { if (data == null) { data = new HashMap<>(dataconf.size()); } for (Entry<String, TreeDataParameters> el : dataconf.entrySet()) { TreeNodeData tnd = data.get(el.getKey()); if (tnd == null) { tnd = el.getValue().newInstance(this); data.put(el.getKey(), tnd); } if (tnd.updateChildData(state, this, el.getValue())) { } } } } /** * @return true if data was changed */ @Override public void updateParentData(DataTreeNodeUpdater state, DataTreeNode child, boolean isnew) { List<TreeNodeDataDeferredOperation> deferredOps = null; if (child != null && data != null) { deferredOps = new ArrayList<>(1); for (TreeNodeData<?> tnd : data.values()) { if (isnew) { tnd.updateParentNewChild(state, this, child, deferredOps); } tnd.updateParentData(state, this, child, deferredOps); } } if (deferredOps != null) { for (TreeNodeDataDeferredOperation currentOp : deferredOps) { currentOp.run(); } } } @Override public DataTreeNodeActor getData(String key) { return data != null ? data.get(key) : null; } @Override public void postDecode() { super.postDecode(); } @Override public Iterator<DataTreeNode> iterator() { return null; } private final class Iter implements ClosableIterator<DataTreeNode> { private Range<DBKey, NonConcurrentTreeNode> range; private NonConcurrentTreeNode next; private Iter(Range<DBKey, NonConcurrentTreeNode> range) { this.range = range; fetchNext(); } public String toString() { return "Iter(" + range + "," + next + ")"; } void fetchNext() { if (range != null) { next = null; while (range.hasNext()) { Entry<DBKey, NonConcurrentTreeNode> tne = range.next(); next = tree.getNode(NonConcurrentTreeNode.this, tne.getKey().rawKey().toString(), false); if (next != null) { break; } } } } @Override public boolean hasNext() { return next != null; } @Override public NonConcurrentTreeNode next() { if (!hasNext()) { throw new NoSuchElementException(); } NonConcurrentTreeNode ret = next; fetchNext(); return ret; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void close() { if (range != null) { range.close(); range = null; } } } @Override public DataTreeNode getOrCreateNode(String name, DataTreeNodeInitializer init) { return getOrCreateEditableNode(name, init); } @Override public long getCounter() { return hits; } @Override public void incrementCounter() { hits++; } @Override public long incrementCounter(long val) { hits += val; return hits; } @Override public void setCounter(long val) { hits = val; } @Override public void release() { // do nothing } public long nodeDB() { return nodedb; } protected HashMap<String, TreeNodeData> createMap() { if (data == null) { data = new HashMap<>(); } return data; } @Override public int getNodeCount() { return nodes; } final boolean isBitSet(int bitcheck) { return (bits & bitcheck) == bitcheck; } public boolean isAlias() { return isBitSet(ALIAS); } protected final void bitSet(int set) { bits |= set; } protected synchronized void markAlias() { bitSet(ALIAS); } public DBKey getDbkey() { return dbkey; } }