[77] | 1 | package com.framsticks.core; |
---|
| 2 | |
---|
| 3 | import com.framsticks.params.AccessInterface; |
---|
[84] | 4 | import com.framsticks.params.CompositeParam; |
---|
[77] | 5 | import com.framsticks.params.Param; |
---|
[90] | 6 | import com.framsticks.util.FramsticksException; |
---|
[84] | 7 | import com.framsticks.util.dispatching.Dispatching; |
---|
[85] | 8 | |
---|
[77] | 9 | import java.util.Iterator; |
---|
| 10 | import java.util.LinkedList; |
---|
[84] | 11 | import java.util.List; |
---|
[77] | 12 | |
---|
[96] | 13 | import javax.annotation.Nonnull; |
---|
[85] | 14 | import javax.annotation.concurrent.Immutable; |
---|
| 15 | |
---|
[84] | 16 | import org.apache.commons.collections.ListUtils; |
---|
| 17 | |
---|
[77] | 18 | /** |
---|
| 19 | * @author Piotr Sniegowski |
---|
| 20 | */ |
---|
[85] | 21 | @Immutable |
---|
[87] | 22 | public final class Path { |
---|
[84] | 23 | // private final static Logger log = Logger.getLogger(Path.class.getName()); |
---|
[77] | 24 | |
---|
[87] | 25 | final Instance instance; |
---|
| 26 | final String textual; |
---|
| 27 | final LinkedList<Node> nodes; |
---|
[77] | 28 | |
---|
[87] | 29 | protected static Object getKnownChild(Instance instance, AccessInterface access, CompositeParam param) { |
---|
[84] | 30 | Object child = access.get(param, Object.class); |
---|
| 31 | if (child == null) { |
---|
| 32 | return null; |
---|
| 33 | } |
---|
[90] | 34 | try { |
---|
[96] | 35 | instance.prepareAccess(param); |
---|
[90] | 36 | return child; |
---|
| 37 | } catch (FramsticksException e) { |
---|
| 38 | } |
---|
| 39 | return null; |
---|
[84] | 40 | } |
---|
[77] | 41 | |
---|
[87] | 42 | /** |
---|
| 43 | * @param instance |
---|
| 44 | * @param textual |
---|
| 45 | * @param nodes |
---|
| 46 | */ |
---|
| 47 | Path(Instance instance, String textual, LinkedList<Node> nodes) { |
---|
| 48 | this.instance = instance; |
---|
| 49 | this.textual = textual; |
---|
| 50 | this.nodes = nodes; |
---|
| 51 | } |
---|
[77] | 52 | |
---|
[87] | 53 | public Path appendNode(Node node) { |
---|
| 54 | assert isResolved(); |
---|
| 55 | return new PathBuilder().instance(instance).textual(textual + ((size() == 1) ? "" : "/") + node.getParam().getId()).add(nodes).add(node).finish(); |
---|
| 56 | } |
---|
[77] | 57 | |
---|
[87] | 58 | public Path appendParam(CompositeParam param) { |
---|
| 59 | assert isResolved(); |
---|
| 60 | return appendNode(new Node(param, null)); |
---|
| 61 | } |
---|
[77] | 62 | |
---|
[87] | 63 | public static class PathBuilder { |
---|
[77] | 64 | |
---|
[87] | 65 | Instance instance; |
---|
| 66 | String textual; |
---|
| 67 | final LinkedList<Node> nodes = new LinkedList<Node>(); |
---|
| 68 | |
---|
| 69 | public Path finish() { |
---|
| 70 | assert instance != null; |
---|
| 71 | assert textual != null; |
---|
| 72 | return new Path(instance, textual, nodes); |
---|
[84] | 73 | } |
---|
[77] | 74 | |
---|
[87] | 75 | public PathBuilder() |
---|
| 76 | { |
---|
| 77 | } |
---|
| 78 | |
---|
| 79 | public PathBuilder instance(Instance instance) { |
---|
| 80 | this.instance = instance; |
---|
| 81 | return this; |
---|
| 82 | } |
---|
| 83 | |
---|
| 84 | public PathBuilder textual(String textual) { |
---|
| 85 | this.textual = textual; |
---|
| 86 | return this; |
---|
| 87 | } |
---|
| 88 | |
---|
| 89 | public PathBuilder add(List<Node> nodes) { |
---|
| 90 | this.nodes.addAll(nodes); |
---|
| 91 | return this; |
---|
| 92 | } |
---|
| 93 | |
---|
| 94 | public PathBuilder add(Node node) { |
---|
| 95 | this.nodes.add(node); |
---|
| 96 | return this; |
---|
| 97 | } |
---|
| 98 | |
---|
| 99 | public PathBuilder setLast(Object object) { |
---|
| 100 | Node n = nodes.pollLast(); |
---|
| 101 | nodes.add(new Node(n.getParam(), object)); |
---|
| 102 | return this; |
---|
| 103 | } |
---|
| 104 | |
---|
| 105 | public PathBuilder buildUpTo(List<Node> nodes, Node node) { |
---|
| 106 | StringBuilder b = new StringBuilder(); |
---|
| 107 | boolean add = false; |
---|
| 108 | for (Node n : nodes) { |
---|
| 109 | this.nodes.add(n); |
---|
| 110 | if (add) { |
---|
| 111 | b.append("/").append(n.getParam().getId()); |
---|
| 112 | } |
---|
| 113 | add = true; |
---|
| 114 | if (n == node) { |
---|
| 115 | break; |
---|
| 116 | } |
---|
[84] | 117 | } |
---|
[87] | 118 | this.textual = (nodes.size() == 1) ? "/" : b.toString(); |
---|
| 119 | return this; |
---|
[84] | 120 | } |
---|
[77] | 121 | |
---|
[96] | 122 | public static Iterator<String> splitPath(String path) { |
---|
| 123 | List<String> list = new LinkedList<String>(); |
---|
| 124 | for (String s : path.split("/")) { |
---|
| 125 | if (!s.isEmpty()) { |
---|
| 126 | list.add(s); |
---|
| 127 | } |
---|
| 128 | } |
---|
| 129 | return list.iterator(); |
---|
| 130 | } |
---|
[77] | 131 | |
---|
[96] | 132 | public PathBuilder resolve(@Nonnull Instance instance, String textual) { |
---|
| 133 | |
---|
[87] | 134 | assert nodes.isEmpty(); |
---|
| 135 | assert instance.isActive(); |
---|
| 136 | this.instance = instance; |
---|
[77] | 137 | |
---|
[90] | 138 | nodes.add(instance.getRoot()); |
---|
| 139 | Node current = instance.getRoot(); |
---|
[87] | 140 | |
---|
| 141 | StringBuilder b = new StringBuilder(); |
---|
[96] | 142 | Iterator<String> i = splitPath(textual); |
---|
[87] | 143 | while (i.hasNext() && current.getObject() != null) { |
---|
[96] | 144 | AccessInterface access = instance.prepareAccess(current.getParam()); |
---|
[87] | 145 | if (access == null) { |
---|
| 146 | break; |
---|
| 147 | } |
---|
| 148 | String e = i.next(); |
---|
| 149 | Param p = access.getParam(e); |
---|
| 150 | if (!(p instanceof CompositeParam)) { |
---|
| 151 | //entries.add(new Entry()); |
---|
| 152 | break; |
---|
| 153 | } |
---|
| 154 | CompositeParam c = (CompositeParam)p; |
---|
| 155 | b.append("/").append(e); |
---|
| 156 | access.select(current.getObject()); |
---|
| 157 | current = new Node(c, getKnownChild(instance, access, c)); |
---|
| 158 | nodes.add(current); |
---|
| 159 | } |
---|
| 160 | this.textual = (nodes.size() == 1) ? "/" : b.toString(); |
---|
| 161 | |
---|
| 162 | return this; |
---|
| 163 | } |
---|
[84] | 164 | } |
---|
[77] | 165 | |
---|
[87] | 166 | public static PathBuilder build() { |
---|
| 167 | return new PathBuilder(); |
---|
[84] | 168 | } |
---|
[77] | 169 | |
---|
[84] | 170 | public Path appendResolution(Object object) { |
---|
| 171 | assert !isResolved(); |
---|
[87] | 172 | Path result = new PathBuilder().textual(textual).instance(instance).add(nodes).setLast(object).finish(); |
---|
| 173 | assert size() == result.size(); |
---|
[84] | 174 | return result; |
---|
| 175 | } |
---|
[77] | 176 | |
---|
[84] | 177 | public final Object getTopObject() { |
---|
| 178 | return getTop().getObject(); |
---|
| 179 | } |
---|
[77] | 180 | |
---|
[84] | 181 | public final Node getTop() { |
---|
| 182 | return nodes.getLast(); |
---|
| 183 | } |
---|
[77] | 184 | |
---|
[84] | 185 | public final Node getUnder() { |
---|
| 186 | assert nodes.size() >= 2; |
---|
| 187 | return nodes.get(nodes.size() - 2); |
---|
| 188 | } |
---|
[77] | 189 | |
---|
[84] | 190 | public final String getTextual() { |
---|
| 191 | return textual; |
---|
| 192 | } |
---|
[77] | 193 | |
---|
[84] | 194 | public String toString() { |
---|
| 195 | return instance + textual + (!isResolved() ? "!" : ""); |
---|
| 196 | } |
---|
[77] | 197 | |
---|
[84] | 198 | public final int size() { |
---|
| 199 | assert Dispatching.isThreadSafe(); |
---|
| 200 | return nodes.size(); |
---|
| 201 | } |
---|
| 202 | |
---|
| 203 | public final boolean isResolved() { |
---|
| 204 | assert Dispatching.isThreadSafe(); |
---|
| 205 | return getTop().getObject() != null; |
---|
| 206 | } |
---|
| 207 | |
---|
| 208 | public final boolean isResolved(String textual) { |
---|
| 209 | assert Dispatching.isThreadSafe(); |
---|
| 210 | return isTheSame(textual) && isResolved(); |
---|
| 211 | } |
---|
| 212 | |
---|
| 213 | public final boolean isTheSame(String textual) { |
---|
| 214 | assert Dispatching.isThreadSafe(); |
---|
| 215 | return this.textual.equals(textual); |
---|
| 216 | } |
---|
| 217 | |
---|
[96] | 218 | public final @Nonnull Instance getInstance() { |
---|
[84] | 219 | assert Dispatching.isThreadSafe(); |
---|
| 220 | return instance; |
---|
| 221 | } |
---|
| 222 | |
---|
[96] | 223 | public Path tryResolveIfNeeded() { |
---|
| 224 | if (isResolved()) { |
---|
| 225 | return this; |
---|
| 226 | } |
---|
| 227 | return tryFindResolution(); |
---|
| 228 | } |
---|
[84] | 229 | |
---|
| 230 | /** Attach resolution at end, if available. |
---|
| 231 | * |
---|
| 232 | * @return Modified path, if resolution was available, this otherwise. |
---|
| 233 | */ |
---|
| 234 | public Path tryFindResolution() { |
---|
| 235 | assert instance.isActive(); |
---|
| 236 | assert !isResolved(); |
---|
| 237 | if (size() == 1) { |
---|
[87] | 238 | return Path.build().resolve(instance, "/").finish();//appendResolution(instance.root.object); |
---|
[84] | 239 | } |
---|
[96] | 240 | Object child = getKnownChild(instance, InstanceUtils.bindAccess(instance, getUnder()), getTop().getParam()); |
---|
[84] | 241 | if (child == null) { |
---|
| 242 | return this; |
---|
| 243 | } |
---|
| 244 | return appendResolution(child); |
---|
| 245 | } |
---|
[77] | 246 | |
---|
[84] | 247 | public boolean matches(Path p) { |
---|
| 248 | assert Dispatching.isThreadSafe(); |
---|
| 249 | assert instance == p.instance; |
---|
| 250 | Iterator<Node> a = nodes.iterator(); |
---|
| 251 | Iterator<Node> b = p.nodes.iterator(); |
---|
| 252 | while (a.hasNext() && b.hasNext()) { |
---|
| 253 | Node an = a.next(); |
---|
| 254 | Node bn = b.next(); |
---|
| 255 | if (an.object != bn.object) { |
---|
| 256 | return false; |
---|
| 257 | } |
---|
| 258 | } |
---|
| 259 | return a.hasNext() == b.hasNext(); |
---|
| 260 | } |
---|
[77] | 261 | |
---|
[84] | 262 | public String getLastElement() { |
---|
| 263 | return getTop().getParam().getId(); |
---|
| 264 | } |
---|
[77] | 265 | |
---|
[84] | 266 | public final boolean isOwner(Instance instance) { |
---|
| 267 | return this.instance == instance; |
---|
| 268 | } |
---|
| 269 | |
---|
[87] | 270 | // public void setInstance(Instance instance) { |
---|
| 271 | // this.instance = instance; |
---|
| 272 | // } |
---|
[85] | 273 | |
---|
[84] | 274 | @SuppressWarnings("unchecked") |
---|
| 275 | public |
---|
| 276 | List<Node> getNodes() { |
---|
| 277 | return ListUtils.unmodifiableList(nodes); |
---|
| 278 | } |
---|
[90] | 279 | |
---|
| 280 | public void assureResolved() { |
---|
| 281 | if (!isResolved()) { |
---|
| 282 | throw new FramsticksException().msg("path is not resolved").arg("path", this); |
---|
| 283 | } |
---|
| 284 | } |
---|
[96] | 285 | |
---|
| 286 | public static Path to(@Nonnull Instance instance, String textual) { |
---|
| 287 | return Path.build().resolve(instance, textual).finish(); |
---|
| 288 | } |
---|
[77] | 289 | } |
---|
| 290 | |
---|