/*
* 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.task.output.tree;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import com.addthis.basis.util.LessStrings;
import com.addthis.bundle.core.Bundle;
import com.addthis.bundle.core.BundleFactory;
import com.addthis.bundle.core.BundleFormat;
import com.addthis.bundle.core.BundleFormatted;
import com.addthis.hydra.data.tree.DataTreeNode;
import com.addthis.hydra.data.tree.DataTreeNodeInitializer;
import com.addthis.hydra.data.tree.DataTreeNodeUpdater;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("serial")
public final class TreeMapState implements DataTreeNodeUpdater, DataTreeNodeInitializer, BundleFactory, BundleFormatted {
private static final Logger log = LoggerFactory.getLogger(TreeMapState.class);
private static final int debug = Integer.parseInt(System.getProperty("hydra.process.debug", "0"));
private static final boolean debugthread = System.getProperty("hydra.process.debugthread", "0").equals("1");
// TODO: remove any uses of reference equality to this value and redefine as a proper empty list
private static final List<DataTreeNode> empty = Collections.unmodifiableList(new ArrayList<>());
static {
if (debugthread) {
log.warn("ENABLED debugthread");
}
}
/**
* test harness constructor
*/
public TreeMapState(Bundle p) {
this.bundle = p;
this.path = null;
this.processor = null;
this.stack = null;
this.thread = null;
this.profiling = false;
}
/** */
public TreeMapState(TreeMapper processor, DataTreeNode rootNode, PathElement[] path, Bundle bundle) {
this.path = path;
this.bundle = bundle;
this.processor = processor;
this.countValue = 1;
this.stack = new LinkedList<>();
this.thread = Thread.currentThread();
this.profiling = processor != null ? processor.isProfiling() : false;
push(rootNode);
}
private final LinkedList<DataTreeNode> stack;
private final TreeMapper processor;
private final PathElement[] path;
private final Bundle bundle;
private final Thread thread;
private final boolean profiling;
private boolean lastWasNew;
private int touched;
private int countValue;
private long assignmentValue;
private void checkThread() {
if (Thread.currentThread() != thread) {
throw new RuntimeException("invalid accessing thread " + Thread.currentThread() + " != " + thread);
}
}
public static List<DataTreeNode> empty() {
return empty;
}
@Override
public int getCountValue() {
return countValue;
}
public void setCountValue(int v) {
countValue = v;
}
@Override
public long getAssignmentValue() {
return assignmentValue;
}
public TreeMapState setAssignmentValue(long assignmentValue) {
this.assignmentValue = assignmentValue;
return this;
}
public boolean getAndClearLastWasNew() {
boolean ret = lastWasNew;
lastWasNew = false;
return ret;
}
public DataTreeNode getLeasedNode(String key) {
DataTreeNode tn = current().getLeasedNode(key);
return tn;
}
public DataTreeNode getOrCreateNode(String key, DataTreeNodeInitializer init) {
DataTreeNode tn = current().getOrCreateNode(key, init);
return tn;
}
public DataTreeNode pop() {
if (debugthread) {
checkThread();
}
return stack.pop();
}
public int getNodeCount() {
return current().getNodeCount();
}
public void push(List<DataTreeNode> tnl) {
if (tnl.size() == 1) {
push(tnl.get(0));
} else {
throw new RuntimeException("unexpected response: " + tnl.size() + " -> " + tnl);
}
}
public void push(DataTreeNode tn) {
if (debugthread) {
checkThread();
}
stack.push(tn);
}
/** */
public long getBundleTime() {
throw new RuntimeException("fix me");
}
public void setBundleTime(long time) {
throw new RuntimeException("fix me");
}
/** */
public DataTreeNode peek(int back) {
return (stack.size() > back) ? stack.get(back) : null;
}
/** */
public DataTreeNode current() {
return stack.peek();
}
@Override
public Bundle getBundle() {
return bundle;
}
/** */
public int touched() {
return touched;
}
/**
* used by path elements to schedule delivering the current bundle to a new
* rule, usually as part of conditional processing. currently called from
* PathCall and PathEachChild
*/
public void dispatchRule(TreeMapperPathReference t) {
if ((t != null) && (bundle != null) && (processor != null)) {
processor.processBundle(bundle, t);
} else if (debug > 0) {
log.warn("Proc Rule Dispatch DROP {} b/c p={} rp={}", t, bundle, processor);
}
}
public void process() {
List<DataTreeNode> list = processPath(path, 0);
if ((debug > 0) && ((list == null) || list.isEmpty())) {
log.warn("proc FAIL {}", list);
log.warn(".... PATH {}", LessStrings.join(path, " // "));
log.warn(".... PACK {}", bundle);
}
if (list != null) {
list.forEach(DataTreeNode::release);
}
}
/**
* called from PathCall.processNode(), PathCombo.processNode() and
* PathEach.processNode()
*/
public List<DataTreeNode> processPath(PathElement[] path) {
return processPath(path, 0);
}
/** */
@Nullable private List<DataTreeNode> processPath(PathElement[] path, int index) {
if ((path == null) || (path.length <= index)) {
return null;
}
List<DataTreeNode> nodes = processPathElement(path[index]);
if ((nodes != null) && nodes.isEmpty()) {
// we get here from op elements, each elements, etc
if ((index + 1) < path.length) {
return processPath(path, index + 1);
}
return null;
} else if (nodes == null) {
return null;
} else if ((index + 1) < path.length) {
List<DataTreeNode> childNodes = null;
for (DataTreeNode tn : nodes) {
push(tn);
List<DataTreeNode> childNodesPartition = processPath(path, index + 1);
if (childNodesPartition != null) {
if ((childNodes == null) || (childNodes == empty)) {
childNodes = childNodesPartition;
} else {
childNodes.addAll(childNodesPartition);
}
}
pop().release();
}
return childNodes;
} else {
return nodes;
}
}
/**
* called from this.processPath(), PathEach.processNode() and
* PathSplit.processNode()
*/
public List<DataTreeNode> processPathElement(PathElement pe) {
if (profiling) {
long mark = System.nanoTime();
List<DataTreeNode> list = processPathElementProfiled(pe);
processor.updateProfile(pe, System.nanoTime() - mark);
return list;
} else {
return processPathElementProfiled(pe);
}
}
private List<DataTreeNode> processPathElementProfiled(PathElement pe) {
if (pe.disabled()) {
return empty();
}
if (debugthread) {
checkThread();
}
List<DataTreeNode> list = pe.processNode(this);
if (list != null) {
touched += list.size();
}
return list;
}
@Override
public void onNewNode(DataTreeNode child) {
lastWasNew = true;
}
@Override
public Bundle createBundle() {
return processor.createBundle();
}
@Override
public BundleFormat getFormat() {
return processor.getFormat();
}
public boolean processorClosing() { return processor.isClosing(); }
}