package com.framsticks.core; import com.framsticks.params.Access; import com.framsticks.params.CompositeParam; import com.framsticks.params.Param; import com.framsticks.util.FramsticksException; import com.framsticks.util.dispatching.Dispatching; import com.framsticks.util.dispatching.ExceptionResultHandler; import com.framsticks.util.dispatching.IgnoreExceptionHandler; import com.framsticks.util.lang.Pair; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; 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 = LogManager.getLogger(Path.class.getName()); final Tree tree; final String textual; final LinkedList nodes; public static final SideNoteKey OBJECT_PARAM_KEY = SideNoteKey.make(CompositeParam.class); protected static Object getKnownChild(Tree tree, Access access, CompositeParam param, ExceptionResultHandler handler) { Object child = access.get(param, Object.class); if (child == null) { return null; } try { tree.prepareAccess(param); tree.putSideNote(child, OBJECT_PARAM_KEY, param); return child; } catch (FramsticksException e) { handler.handle(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) { return resolve(tree, textual, IgnoreExceptionHandler.getInstance()); } public PathBuilder resolve(@Nonnull Tree tree, String textual, ExceptionResultHandler handler) { 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) { Access access = TreeOperations.bindAccess(current);// tree.prepareAccess(current.getParam()); String e = i.next(); Param p = access.getParam(e); if (p == null) { break; } if (!(p instanceof CompositeParam)) { throw new FramsticksException().msg("param is not a composite").arg("param", p).arg("tree", tree).arg("textual", textual).arg("access", access); } CompositeParam c = (CompositeParam) p; b.append("/").append(e); access.select(current.getObject()); tree.putSideNote(current.getObject(), OBJECT_PARAM_KEY, current.getParam()); current = new Node(current.getTree(), c, getKnownChild(tree, access, c, handler)); 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(), IgnoreExceptionHandler.getInstance()); 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 tryTo(@Nonnull Tree tree, String textual) { return Path.build().resolve(tree, textual).finish(); } public static Path to(@Nonnull Tree tree, String textual) { Path path = tryTo(tree, textual); if (path.getTextual().equals(textual)) { return path; } throw new FramsticksException().msg("failed to create path").arg("textual", textual).arg("result", path).arg("tree", tree); } 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 static final Pattern pathPattern = Pattern.compile("(\\/)|((\\/[^/]+)+)"); public static boolean isValidString(String path) { return pathPattern.matcher(path).matches(); } public static String appendString(String path, String element) { if (path.equals("/")) { return path + element; } return path + "/" + element; } public static Pair removeLastElement(String path) { assert isValidString(path); if (path.equals("/")) { throw new FramsticksException().msg("cannot remove last element from root path"); } int index = path.lastIndexOf('/'); assert index != -1; if (index == 0) { return new Pair("/", path.substring(1)); } return new Pair(path.substring(0, index), path.substring(index + 1)); } public static String validateString(String path) { if (!isValidString(path)) { throw new FramsticksException().msg("path string validation failured").arg("path", path); } return path; } // public boolean isEmpty() { // return nodes.isEmpty(); // } }