package org.sef4j.core.helpers.proptree.model;
import java.util.concurrent.Callable;
import org.sef4j.core.util.CopyOnWriteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
/**
* PropTreeNode are nodes of a Tree, containing property name-values
* <p/>
* They are simply used to represent property (metrics, counters...) per node-path (per CallStack path, per instrumented node...)
* <p/>
*
* They are actually independant of CallStack and CallStackElt
* When using with corresponding method name, they can be interpreted as metrics node values for
* a stack path, from root to any method "meth1/meth2/.../methN"
* <p/>
*
* PropTreeNode are lazily created on first access, using thread-safe <code>getOrCreateChild(childName)</code>
* <BR/> (atomic and thread safe, preserving uniqueness per childname)
* <p/>
*
* PropTreeNode can attach any free name-value property, using thread safe <code>getOrCreateProp(name, propFactory</code>
* <BR/> (atomic and thread safe, preserving uniqueness per property name)
*
*
* To create a dump snapshot of a whole Tree, see <code>recursiveCopyToDTO()</code> corresponding to DTO class
*
* <PRE>
*
* "/"
* +---------------+
* | Root TreeNode |
* +---------------+
* |
* +--
* +--
*
*
* "meth1/meth2/.../methN"
* +-------------------------------+
* | TreeNode |
* | - parent |
* | - childName |
* | - props : { name1 : prop1 |
* | name2 : prop2 |
* | .. |
* | } |
* | - childMap : { child1:.. } |
* +-------------------------------+
* |
* |
* "meth1/meth2/.../methN/child1"
* | +-----------------+
* +-- | Child1 TreeNode |
* | +-----------------+
* | |
* | +--
* | +--
* | |
* | +--
* |
* "meth1/meth2/.../methN/child2"
* | +-----------------+
* +-- | Child2 TreeNode |
* | +-----------------+
* | |
* | +--
* | +--
* | |
* | +--
* |
* ..
*
* |
* "meth1/meth2/.../methN/childP"
* | +-----------------+
* +-- | ChildP TreeNode |
* +-----------------+
* |
* +--
* +--
* |
* +--
*
* </PRE>
*/
public class PropTreeNode {
private static final Logger LOG = LoggerFactory.getLogger(PropTreeNode.class);
private static final boolean DEBUG_NEW_NODE = true;
private static final boolean DEBUG_NEW_PROP = false;
private static int nodeIdGenerator = 1;
private final int nodeId = nodeIdGenerator++;
private final PropTreeNode parent;
private final String name;
private static Object childLock = new Object();
// copy-on-write => no lock for reading, lock on childLock for writing copy
private ImmutableMap<String,PropTreeNode> childMap = ImmutableMap.of();
private static Object propsLock = new Object();
private ImmutableMap<String,Object> propsMap = ImmutableMap.of();
// private ImmutableMap<String,PropTreeValueListener<?>[]> inheritablePropListeners = ImmutableMap.of();
// private ImmutableMap<String,PropTreeValueListener<?>[]> inheritedPropListeners = ImmutableMap.of();
//
// private static final PropTreeValueListener<?>[] EMPTY_LISTENERS_ARRAY = new PropTreeValueListener[0];
// ------------------------------------------------------------------------
/**
* should be called only for ROOT element, or using CallTreeNode.getOrCreateChild(name)
*/
private PropTreeNode(PropTreeNode parent, String name) {
this.parent = parent;
this.name = name;
}
public static PropTreeNode newRoot() {
return new PropTreeNode(null, "");
}
// ------------------------------------------------------------------------
public PropTreeNode getParent() {
return parent;
}
public String getName() {
return name;
}
public ImmutableMap<String, PropTreeNode> getChildMap() {
return childMap;
}
public ImmutableCollection<PropTreeNode> getChildList() {
return childMap.values();
}
// ------------------------------------------------------------------------
// public void putInheritablePropListener(String propName, PropTreeValueListener<?> listener) {
// synchronized(childLock) {
// PropTreeValueListener<?>[] listeners = inheritablePropListeners.get(propName);
// if (listeners == null) listeners = EMPTY_LISTENERS_ARRAY;
// PropTreeValueListener<?>[] newListeners = CopyOnWriteUtils.newWithAdd(PropTreeValueListener.class, listeners, listener);
// this.inheritablePropListeners = CopyOnWriteUtils.newWithPut(inheritablePropListeners, propName, newListeners);
//
// // recursive recompute inherited from parent+inheritable
// doRecursiveRecomputeInheritedPropListeners(propName);
// }
// }
//
// public void removeInheritablePropListener(String propName, PropTreeValueListener<?> listener) {
// synchronized(childLock) {
// PropTreeValueListener<?>[] listeners = inheritablePropListeners.get(propName);
// if (listeners == null) listeners = EMPTY_LISTENERS_ARRAY;
// PropTreeValueListener<?>[] newListeners = CopyOnWriteUtils.newWithRemove(PropTreeValueListener.class, listeners, listener);
// this.inheritablePropListeners = CopyOnWriteUtils.newWithPut(inheritablePropListeners, propName, newListeners);
//
// // recursive recompute inherited from parent+inheritable
// doRecursiveRecomputeInheritedPropListeners(propName);
// }
// }
//
// private void doRecursiveRecomputeInheritedPropListeners(String propName) {
// PropTreeValueListener<?>[] mergeListeners;
// PropTreeValueListener<?>[] parentPropListeners = getParent() != null?
// getParent().inheritedPropListeners.get(propName) : null;
// if (! inheritablePropListeners.isEmpty()) {
// PropTreeValueListener<?>[] propListeners = inheritablePropListeners.get(propName);
// mergeListeners = CopyOnWriteUtils.newWithMerge(PropTreeValueListener.class,
// parentPropListeners!=null? parentPropListeners : EMPTY_LISTENERS_ARRAY,
// propListeners!=null? propListeners : EMPTY_LISTENERS_ARRAY);
// } else {
// mergeListeners = parentPropListeners;
// }
// this.inheritedPropListeners = CopyOnWriteUtils.newWithPut(inheritedPropListeners, propName, mergeListeners);
// // *** recurse ***
// for(PropTreeNode child : childMap.values()) {
// synchronized(child.childLock) {
// child.doRecursiveRecomputeInheritedPropListeners(propName);
// }
// }
// }
// ------------------------------------------------------------------------
public int getDepth() {
int res = 0;
for(PropTreeNode curr = this; curr.parent != null; curr = curr.parent) {
res++;
}
return res;
}
/**
* @return path names for element from root to self
*/
public String[] getPath() {
int depth = getDepth();
String[] res = new String[depth];
int i = depth - 1;
for(PropTreeNode curr = this; curr.parent != null; curr = curr.parent) {
res[i--] = curr.name;
}
return res;
}
/**
* @return path for element from root to self
*/
public String getPathStr() {
String[] tmpres = getPath();
StringBuilder sb = new StringBuilder();
final int len = tmpres.length;
for (int i = 0; i < len; i++) {
sb.append(tmpres[i]);
if (i + 1 < len) {
sb.append("/");
}
}
return sb.toString();
}
/**
* @return reverse path names for element from self to root
*/
public String[] getReversePath() {
int depth = getDepth();
String[] res = new String[depth];
int i = 0;
for(PropTreeNode curr = this; curr.parent != null; curr = curr.parent) {
res[i++] = curr.name;
}
return res;
}
/**
* @param childName
* @return child with name "childName", newly created if did not exist before
*/
public PropTreeNode getOrCreateChild(String childName) {
PropTreeNode res = childMap.get(childName);
if (res == null) {
synchronized(childLock) {
res = childMap.get(childName);
if (res != null) return res;
res = new PropTreeNode(this, childName);
this.childMap = CopyOnWriteUtils.newWithPut(childMap, childName, res);
if (DEBUG_NEW_NODE) {
LOG.info("created node: " + childName + " (" + res.nodeId + ") on parent (" + nodeId + ")");
}
}
}
return res;
}
public PropTreeNode getChildOrNull(String childName) {
return childMap.get(childName);
}
/**
* helper method for repeating getOrCreateChild() with path elements
* @param path
* @return sub-sub child for path names
*/
public PropTreeNode getOrCreateChildPath(String[] path) {
PropTreeNode res = this;
final int len = path.length;
for (int i = 0; i < len; i++) {
res = res.getOrCreateChild(path[i]);
}
return res;
}
public PropTreeNode getChildByPathOrNull(String[] path) {
PropTreeNode res = this;
final int len = path.length;
for (int i = 0; i < len; i++) {
res = res.getChildOrNull(path[i]);
if (res == null) {
return null;
}
}
return res;
}
public <T> void updateOrCreateProp(String propName, Callable<T> valueFactory, PropTreeValueCallback<T> callback) {
T propValue = getOrCreateProp(propName, valueFactory);
callback.doWith(this, propName, propValue);
}
public <T> void updateProp(String propName, Class<T> clss, PropTreeValueCallback<T> callback) {
T propValue = getPropOrNull(propName, clss);
callback.doWith(this, propName, propValue);
}
/**
* @param propName
* @return prop with name "propName", newly created if did not exist before
*/
@SuppressWarnings("unchecked")
public <T> T getOrCreateProp(String propName, Callable<T> valueFactory) {
T res = (T) propsMap.get(propName);
if (res == null) {
synchronized(propsLock) {
res = (T) propsMap.get(propName);
if (res != null) return res;
try {
res = valueFactory.call();
} catch (Exception ex) {
throw new RuntimeException("Failed to create prop value", ex);
}
this.propsMap = CopyOnWriteUtils.newWithPut(propsMap, propName, res);
if (DEBUG_NEW_PROP) {
LOG.info("created Prop " + propName + " on node: " + name + " (" + nodeId + ")");
}
}
}
return res;
}
/** @return immutable Map */
public ImmutableMap<String,Object> getPropsMap() {
return propsMap;
}
public Object getPropOrNull(String propName) {
return propsMap.get(propName);
}
@SuppressWarnings("unchecked")
public <T> T getPropOrNull(String propName, Class<T> clss) {
return (T) getPropOrNull(propName);
}
// ------------------------------------------------------------------------
@Override
public String toString() {
return "CallTreeNode[" + name + "]";
}
}