package com.framsticks.core; import com.framsticks.params.AccessInterface; import com.framsticks.params.CompositeParam; import com.framsticks.params.Param; import com.framsticks.util.FramsticksException; import com.framsticks.util.dispatching.Dispatching; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import org.apache.commons.collections.ListUtils; /** * @author Piotr Sniegowski */ @Immutable public final class Path { // private final static Logger log = Logger.getLogger(Path.class.getName()); final Tree tree; final String textual; final LinkedList nodes; protected static Object getKnownChild(Tree tree, AccessInterface access, CompositeParam param) { Object child = access.get(param, Object.class); if (child == null) { return null; } try { tree.prepareAccess(param); return child; } catch (FramsticksException e) { } return null; } /** * @param tree * @param textual * @param nodes */ Path(Tree tree, String textual, LinkedList nodes) { this.tree = tree; this.textual = textual; this.nodes = nodes; } public Path appendNode(Node node) { assert isResolved(); return new PathBuilder().tree(tree).textual(textual + ((size() == 1) ? "" : "/") + node.getParam().getId()).add(nodes).add(node).finish(); } public Path appendParam(CompositeParam param) { assert isResolved(); return appendNode(new Node(tree, param, null)); } public static class PathBuilder { Tree tree; String textual; final LinkedList nodes = new LinkedList(); public Path finish() { assert tree != null; assert textual != null; return new Path(tree, textual, nodes); } public PathBuilder() { } public PathBuilder tree(Tree tree) { this.tree = tree; return this; } public PathBuilder textual(String textual) { this.textual = textual; return this; } public PathBuilder add(List nodes) { this.nodes.addAll(nodes); return this; } public PathBuilder add(Node node) { this.nodes.add(node); return this; } public PathBuilder setLast(Object object) { Node n = nodes.pollLast(); nodes.add(new Node(n.getTree(), n.getParam(), object)); return this; } public PathBuilder buildUpTo(List nodes, Node node) { StringBuilder b = new StringBuilder(); boolean add = false; for (Node n : nodes) { this.nodes.add(n); if (add) { b.append("/").append(n.getParam().getId()); } add = true; if (n == node) { break; } } this.textual = (nodes.size() == 1) ? "/" : b.toString(); return this; } public static Iterator splitPath(String path) { List list = new LinkedList(); for (String s : path.split("/")) { if (!s.isEmpty()) { list.add(s); } } return list.iterator(); } public PathBuilder resolve(@Nonnull Tree tree, String textual) { assert nodes.isEmpty(); assert tree.isActive(); this.tree = tree; Node current = tree.getAssignedRoot(); nodes.add(current); StringBuilder b = new StringBuilder(); Iterator i = splitPath(textual); while (i.hasNext() && current.getObject() != null) { AccessInterface access = TreeOperations.bindAccess(current);// tree.prepareAccess(current.getParam()); String e = i.next(); Param p = access.getParam(e); if (!(p instanceof CompositeParam)) { //entries.add(new Entry()); break; } CompositeParam c = (CompositeParam)p; b.append("/").append(e); access.select(current.getObject()); current = new Node(current.getTree(), c, getKnownChild(tree, access, c)); nodes.add(current); } this.textual = (nodes.size() == 1) ? "/" : b.toString(); return this; } } public static PathBuilder build() { return new PathBuilder(); } public Path appendResolution(Object object) { assert !isResolved(); Path result = new PathBuilder().textual(textual).tree(tree).add(nodes).setLast(object).finish(); assert size() == result.size(); return result; } public final Object getTopObject() { return getTop().getObject(); } public final Node getTop() { return nodes.getLast(); } public final Node getUnder() { assert nodes.size() >= 2; return nodes.get(nodes.size() - 2); } public final String getTextual() { return textual; } public String toString() { return tree + textual + (!isResolved() ? "!" : ""); } public String getFullTextual() { return tree.getName() + textual; } public final int size() { assert Dispatching.isThreadSafe(); return nodes.size(); } public final boolean isResolved() { assert Dispatching.isThreadSafe(); return getTop().getObject() != null; } public final boolean isResolved(String textual) { assert Dispatching.isThreadSafe(); return isTheSame(textual) && isResolved(); } public final boolean isTheSame(String textual) { assert Dispatching.isThreadSafe(); return this.textual.equals(textual); } public final boolean isTheSameTextually(Path path) { assert Dispatching.isThreadSafe(); return (tree == path.getTree()) && textual.equals(path.getTextual()); } public final @Nonnull Tree getTree() { assert Dispatching.isThreadSafe(); return tree; } public Path tryResolveIfNeeded() { if (isResolved()) { return this; } return tryFindResolution(); } /** Attach resolution at end, if available. * * @return Modified path, if resolution was available, this otherwise. */ public Path tryFindResolution() { assert tree.isActive(); assert !isResolved(); if (size() == 1) { return Path.build().resolve(tree, "/").finish(); } Object child = getKnownChild(tree, TreeOperations.bindAccess(getUnder()), getTop().getParam()); if (child == null) { return this; } return appendResolution(child); } public String getLastElement() { return getTop().getParam().getId(); } public final boolean isOwner(Tree tree) { return this.tree == tree; } @SuppressWarnings("unchecked") public List getNodes() { return ListUtils.unmodifiableList(nodes); } public Path assureResolved() { if (!isResolved()) { throw new FramsticksException().msg("path is not resolved").arg("path", this); } return this; } public static Path to(@Nonnull Tree tree, String textual) { return Path.build().resolve(tree, textual).finish(); } public boolean isTheSameObjects(Path path) { if (tree != path.getTree()) { return false; } if (size() != path.size()) { return false; } if (!getTextual().equals(path.getTextual())) { return false; } Iterator a = nodes.iterator(); Iterator b = path.getNodes().iterator(); while (a.hasNext()) { assert b.hasNext(); if (a.next() != b.next()) { return false; } } return true; } // public boolean isEmpty() { // return nodes.isEmpty(); // } }