package xapi.dev.ui; import com.github.javaparser.ast.expr.UiAttrExpr; import com.github.javaparser.ast.expr.UiContainerExpr; import com.github.javaparser.ast.expr.UiExpr; import xapi.fu.Rethrowable; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Created by James X. Nelson (james @wetheinter.net) on 6/19/16. */ public class ComponentGraph implements Rethrowable { // we don't use synchronized, so just make the writable field volatile private volatile ComponentGraph parent; private volatile ComponentGraph firstChild; private volatile ComponentGraph nextSibling; private volatile ComponentGraph lastChild; private UiExpr self; private UiAttrExpr parentAttr; private UiContainerExpr parentContainer; // binary semaphore; only one thread may appendChild at once on a given node, // and a single thread may not call .appendChild on a node while .appendChild // has already been called. private final Semaphore lock = new Semaphore(1); protected ComponentGraph() { } public ComponentGraph(UiExpr self) { this.self = self; } public ComponentGraph appendChild(UiExpr into) { try { if (!lock.tryAcquire(5, TimeUnit.MICROSECONDS)) { rethrow(new TimeoutException("Waiting 5 seconds to descend into NodeScope; " + "either you are doing way too much work in .createChild(), or you have " + "tried to call .descend() on a node that was already descending().")); } } catch (InterruptedException e) { // stop running if out thread has been cancelled... Thread.currentThread().interrupt(); throw rethrow(e); } try { // Creates a child with values copies from this class ComponentGraph child = createChild(into); // Update our child pointers accordingly if (firstChild == null) { firstChild = lastChild = child; } else { lastChild.nextSibling = child; lastChild = child; } // done return child; } finally { lock.release(); } } protected ComponentGraph createChild(UiExpr into) { final ComponentGraph child = new ComponentGraph(); initializeChild(child, into); return child; } protected void initializeChild(ComponentGraph child, UiExpr into) { child.self = into; child.parent = this; child.parentAttr = parentAttr; child.parentContainer = parentContainer; if (self instanceof UiAttrExpr) { child.parentAttr = (UiAttrExpr) self; } else if (self instanceof UiContainerExpr) { child.parentContainer = (UiContainerExpr) self; } } public ComponentGraph getParent() { return parent; } public ComponentGraph getFirstChild() { return firstChild; } public ComponentGraph getNextSibling() { return nextSibling; } public ComponentGraph getLastChild() { return lastChild; } public UiExpr getSelf() { return self; } public UiAttrExpr getParentAttr() { return parentAttr; } public UiContainerExpr getParentContainer() { return parentContainer; } public UiContainerExpr getDeepestContainer() { return self instanceof UiContainerExpr ? (UiContainerExpr) self : parentContainer; } /** * In order to determine if a node should be considered the child of * an attribute, that child must have the same container as the parent attribute. * * In most parsed xapi templates, this will return true until a container * child is encountered, and will be false for children in that container. */ public boolean isAttributeChild() { return parentAttr != null && parentAttr.getParentNode() == parentContainer; } }