├── .gitignore
├── LICENSE.txt
├── README.markdown
├── build.xml
├── examples
└── com
│ └── mu
│ └── zipper
│ └── examples
│ ├── getting_started
│ ├── Leaf.java
│ ├── Main.java
│ └── Node.java
│ └── zipstar
│ ├── Graph.java
│ ├── GraphFactory.java
│ ├── Node.java
│ ├── Path.java
│ ├── SortedList.java
│ └── ZipStar.java
├── lib
└── junit-4.7.jar
├── src
└── com
│ └── mu
│ └── zipper
│ ├── Context.java
│ ├── IZipNode.java
│ ├── Loc.java
│ ├── ZipNode.java
│ ├── Zipper.java
│ └── ZipperException.java
└── test
└── com
└── mu
└── zipper
├── ZipperTest.java
└── examples
└── zipstar
├── GraphFactoryTest.java
├── SortedListTest.java
└── ZipStarTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | bin
4 | docs
5 | junit
6 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, Mu Dynamics.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of the "Mu Dynamics" nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Zipper for Java
2 | ===============
3 |
4 | ## Description
5 |
6 | It you are looking for a data structure to easy create, traverse and manipulate trees, try the `Zipper`.
7 | The `Zipper` is a cursor based data structure. A cursor into a tree is represented by a location object. A location references the node in focus and a context, which describes the position of the node inside the tree. Four basic methods `down()`, `up()`, `right()` and `left()` defined in the location object move the cursor to next location. The tree is manipulated at a location only. All possible update methods are defined in the location object as well.
8 |
9 | A `Zipper` location can be seen as a local view into the tree. All (or almost all) tree changes are visible to this location only. The tree remains unchanged for other locations that reference the same tree. For more details see [`Zipper` data structure](http://en.wikipedia.org/wiki/Zipper_data_structure), included examples or unit tests.
10 |
11 | ## Build
12 |
13 | Just build with `ant` and copy the resulting `zipper.jar` file into your class path.
14 |
15 | ## Getting started
16 |
17 | Let us start with a simple `Node` and `Leaf` class:
18 |
19 |
20 | // Node
21 | public class Node implements IZipNode {
22 |
23 | private String name;
24 | private final List<Node> children;
25 |
26 | public Node(final String name, final Node... children) {
27 | super();
28 |
29 | assert(name != null);
30 | assert(children != null);
31 |
32 | this.name = name;
33 | this.children = Arrays.asList(children);
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 |
40 | public Collection<Node> getChildren() {
41 | return this.children;
42 | }
43 |
44 | }
45 |
46 | // Leaf
47 | public class Leaf extends Node {
48 |
49 | public Leaf(final String name) {
50 | super(name, new Node[0]);
51 | }
52 |
53 | @Override
54 | public Collection<Node> getChildren() {
55 | return null;
56 | }
57 |
58 | }
59 |
60 |
61 | Any zip-able class has to implement the `IZipNode` interface that only defines the `getChildren()` method. If `getChildren()` returns null, `Zipper` treats this node as a leaf node and throws a `ZipperException` on an attempt to add children. The base node always returns a collection, empty or not.
62 |
63 | Using these classes we can create a sample tree:
64 |
65 |
66 | /**
67 | * root
68 | * /\
69 | * / \
70 | * a d
71 | * / \ \
72 | * / \ \
73 | * b c e
74 | *
75 | * Or set notation:
76 | * root:[a:[b, c], d:[e]]
77 | */
78 | Node tree = new Node("root",
79 | new Node("a",
80 | new Leaf("b"),
81 | new Leaf("c")),
82 | new Node("d",
83 | new Leaf("e")));
84 |
85 |
86 | and zip it as following:
87 |
88 |
89 | Loc<Node> root = Zipper.zip(tree);
90 |
91 |
92 | The `Zipper` constructor `zip()` creates a location `Loc` object that references the root node.
93 |
94 | Let us do some moves and updates now:
95 |
96 |
97 | // Move to node 'e';
98 | Loc<Node> e = root.down().right().down();
99 |
100 | // Add a sibling on the right
101 | Loc<Node> f = e.insertRight(new Leaf("f"));
102 |
103 | // Move up and add a sibling on the left
104 | Loc<Node> g = f.up().insertLeft(new Leaf("g"));
105 |
106 |
107 | Every move returns a new location object. Since tree changes remain local to a location, the resulting trees build from locations `e.root()`, `f.root()' and `g.root()` look like following (using `Main` class `printTree()` method from getting started examples):
108 |
109 |
110 | Tree e:
111 | root:[a:[b, c], d:[e]]
112 | Tree f:
113 | root:[a:[b, c], d:[e, f]]
114 | Tree g:
115 | root:[a:[b, c], g, d:[e, f]]
116 |
117 |
118 | The method `root()` calls `up()` recursively until the root node is reached and returns the root locations.
119 |
120 | For more inside into the `Zipper` and this Java implementation see examples, unit tests and Javadoc.
121 |
122 | Have fun!
123 |
124 | ## Zipper for non persistent data
125 |
126 | Hopefully soon with a more in depth introduction to Zippers.
127 |
128 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Java Zipper build file.
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
103 |
104 |
105 |
106 |
107 | Zipper]]>
108 | Copyright © 2009 Mu Dynamics.]]>
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/getting_started/Leaf.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.getting_started;
2 |
3 | import java.util.Collection;
4 |
5 | /**
6 | * A sample leaf node.
7 | *
8 | * @author Adam Smyczek
9 | */
10 | public class Leaf extends Node {
11 |
12 | public Leaf(final String name) {
13 | super(name, new Node[0]);
14 | }
15 |
16 | /**
17 | * A leaf node returns null.
18 | *
19 | * @return null
20 | */
21 | @Override
22 | public Collection getChildren() {
23 | return null;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/getting_started/Main.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.getting_started;
2 |
3 | import com.mu.zipper.IZipNode;
4 | import com.mu.zipper.Loc;
5 | import com.mu.zipper.Zipper;
6 |
7 | /**
8 | * Getting started example
9 | * see README.markdown
10 | *
11 | * @author Adam Smyczek
12 | */
13 | public class Main {
14 |
15 | /**
16 | * A sample tree
17 | *
18 | * root
19 | * /\
20 | * / \
21 | * a d
22 | * / \ \
23 | * / \ \
24 | * b c e
25 | *
26 | * @return a sample tree
27 | */
28 | public final static Node createTree() {
29 | return new Node("root",
30 | new Node("a",
31 | new Leaf("b"),
32 | new Leaf("c")),
33 | new Node("d",
34 | new Leaf("e")));
35 | }
36 |
37 | /**
38 | * Main method performs sample tree modification
39 | * and prints the results to stdout.
40 | *
41 | * @param args not used
42 | */
43 | public static final void main(final String[] args) {
44 | // Create and zip a tree
45 | Node root = createTree();
46 | Loc loc = Zipper.zip(root);
47 |
48 | // Move to node 'e';
49 | Loc e = loc.down().right().down();
50 |
51 | // Add a sibling on the right
52 | Loc f = e.insertRight(new Leaf("f"));
53 |
54 | // Move up and add a sibling on the left
55 | Loc g = f.up().insertLeft(new Leaf("g"));
56 |
57 | // Print trees
58 | System.out.println("Tree e:");
59 | System.out.println(printTree(e));
60 |
61 | System.out.println("Tree f:");
62 | System.out.println(printTree(f));
63 |
64 | System.out.println("Tree g:");
65 | System.out.println(printTree(g));
66 | }
67 |
68 | /**
69 | * Print tree from root
70 | * @param loc
71 | * @return tree rendered to string
72 | */
73 | private static String printTree(final Loc loc) {
74 | StringBuffer buffer = new StringBuffer();
75 | printToBuffer(loc.root().node(), buffer);
76 | return buffer.toString();
77 | }
78 |
79 | /**
80 | * Recursive print node to buffer
81 | * @param node
82 | * @param buffer
83 | */
84 | private static void printToBuffer(final IZipNode node, final StringBuffer buffer) {
85 | buffer.append(node.toString());
86 | if ((node.getChildren() != null)) {
87 | buffer.append(":[");
88 | for (IZipNode n : node.getChildren()) {
89 | printToBuffer(n, buffer);
90 | buffer.append(", ");
91 | }
92 | buffer.delete(buffer.length() - 2, buffer.length());
93 | buffer.append("]");
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/getting_started/Node.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.getting_started;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collection;
5 | import java.util.List;
6 |
7 | import com.mu.zipper.IZipNode;
8 |
9 | /**
10 | * A sample node.
11 | *
12 | * @author Adam Smyczek
13 | */
14 | public class Node implements IZipNode {
15 |
16 | private String name;
17 |
18 | private final List children;
19 |
20 | /**
21 | * @param name node name
22 | * @param children children list, not null
23 | */
24 | public Node(final String name, final Node... children) {
25 | super();
26 |
27 | assert(name != null);
28 | assert(children != null);
29 |
30 | this.name = name;
31 | this.children = Arrays.asList(children);
32 | }
33 |
34 | public String getName() {
35 | return name;
36 | }
37 |
38 | public Collection getChildren() {
39 | return this.children;
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return name;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/zipstar/Graph.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.Collections;
6 | import java.util.Hashtable;
7 | import java.util.Iterator;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /**
12 | * A simple graph implementation.
13 | * The graph contains a list of nodes and
14 | * directional edges. All nodes are named
15 | * and have a 2D coordinate. The edges are
16 | * weighted.
17 | * Nodes and edges are created using constructor
18 | * methods newNode() , connect()
19 | * and direct() .
20 | *
21 | * @author Adam Smyczek
22 | */
23 | public final class Graph {
24 |
25 | private final List nodes;
26 | private final Map edges;
27 |
28 | public Graph() {
29 | super();
30 | this.nodes = new ArrayList();
31 | this.edges = new Hashtable();
32 | }
33 |
34 | /**
35 | * Create a new node associated with this graph.
36 | *
37 | * @param name the name of the node
38 | * @param coordX x coordinate
39 | * @param coordY y coordinate
40 | * @return a new node
41 | */
42 | public Node newNode(final String name, double coordX, double coordY) {
43 | Node node = new Node(this, name, coordX, coordY);
44 | nodes.add(node);
45 | return node;
46 | }
47 |
48 | /**
49 | * Creates two edges n1->n2 and n2->n1 , both with
50 | * the weight weight .
51 | *
52 | * @param n1 node 1
53 | * @param n2 node 2
54 | * @param weight the edge weight/cost
55 | */
56 | public void connect(final Node n1, final Node n2, double weight) {
57 | assert(nodes.contains(n1));
58 | assert(nodes.contains(n2));
59 | edges.put(new Key(n1, n2), new Edge(n1, n2, weight));
60 | edges.put(new Key(n2, n1),new Edge(n2, n1, weight));
61 | }
62 |
63 | /**
64 | * Create one directional edge n1->n2 .
65 | *
66 | * @param n1 node 1
67 | * @param n2 node 2
68 | * @param weight the weight/cost of the edge
69 | */
70 | public void direct(final Node n1, final Node n2, double weight) {
71 | assert(nodes.contains(n1));
72 | assert(nodes.contains(n2));
73 | edges.put(new Key(n1, n2), new Edge(n1, n2, weight));
74 | }
75 |
76 | /**
77 | * Removes the node and all adjacent edges from the graph.
78 | *
79 | * @param node to remove
80 | */
81 | public void removeNode(final Node node) {
82 | nodes.remove(node);
83 | for (Iterator i = edges.values().iterator(); i.hasNext(); ) {
84 | Edge e = i.next();
85 | if (e.from.equals(node) || e.to.equals(node)) {
86 | i.remove();
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * Remove all edges between nodes n1 and n2.
93 | *
94 | * @param n1
95 | * @param n2
96 | */
97 | public void disconnect(final Node n1, final Node n2) {
98 | edges.remove(new Key(n1, n2));
99 | edges.remove(new Key(n2, n1));
100 | }
101 |
102 | // ---- Accessors ----
103 |
104 | public List getNodes() {
105 | return Collections.unmodifiableList(nodes);
106 | }
107 |
108 | public Collection getEdges() {
109 | return Collections.unmodifiableCollection(edges.values());
110 | }
111 |
112 | /**
113 | * Returns all reachable nodes from the from node.
114 | *
115 | * @param from node
116 | * @return list of all reachable nodes
117 | */
118 | public List adjacentNodesFrom(final Node from) {
119 | List nodes = new ArrayList();
120 | for (Iterator i = edges.values().iterator(); i.hasNext(); ) {
121 | Edge e = i.next();
122 | if (e.from.equals(from)) {
123 | nodes.add(e.to);
124 | }
125 | }
126 | return Collections.unmodifiableList(nodes);
127 | }
128 |
129 | /**
130 | * Edge weight/cost of the edge from->to .
131 | *
132 | * @param from node
133 | * @param to node
134 | * @return the weight/cost
135 | * @throws IllegalStateException from to nodes are not direct connected.
136 | */
137 | public double getWeight(Node from, Node to) {
138 | Edge e = edges.get(new Key(from, to));
139 | if (e != null) {
140 | return e.weight;
141 | }
142 | throw new IllegalStateException("No edge from " + from.getName() + " to " + to.getName() + " exists.");
143 | }
144 |
145 | @Override
146 | public String toString() {
147 | StringBuffer buf = new StringBuffer();
148 | for (Node node : getNodes()) {
149 | buf.append(node.toString()).append('\n');
150 | }
151 | return buf.toString();
152 | }
153 |
154 |
155 | /**
156 | * Internal edge class.
157 | */
158 | private class Edge {
159 |
160 | private final Node from;
161 | private final Node to;
162 | private final double weight;
163 |
164 | public Edge(Node from, Node to, double weight) {
165 | super();
166 | this.from = from;
167 | this.to = to;
168 | this.weight = weight;
169 | }
170 |
171 | }
172 |
173 | /**
174 | * Hashtable key for edge objects.
175 | */
176 | private class Key {
177 |
178 | private final Node from;
179 | private final Node to;
180 |
181 | public Key(Node from, Node to) {
182 | super();
183 | this.from = from;
184 | this.to = to;
185 | }
186 |
187 | @Override
188 | public int hashCode() {
189 | final int prime = 31;
190 | int result = 1;
191 | result = prime * result + ((from == null) ? 0 : from.hashCode());
192 | result = prime * result + ((to == null) ? 0 : to.hashCode());
193 | return result;
194 | }
195 |
196 | @Override
197 | public boolean equals(Object obj) {
198 | if (this == obj)
199 | return true;
200 | if (obj == null)
201 | return false;
202 | if (getClass() != obj.getClass())
203 | return false;
204 | Key other = (Key) obj;
205 | if (from == null) {
206 | if (other.from != null)
207 | return false;
208 | } else if (!from.equals(other.from))
209 | return false;
210 | if (to == null) {
211 | if (other.to != null)
212 | return false;
213 | } else if (!to.equals(other.to))
214 | return false;
215 | return true;
216 | }
217 |
218 | }
219 |
220 | }
221 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/zipstar/GraphFactory.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * Few graph examples to play with.
7 | *
8 | * @author Adam Smyczek
9 | */
10 | public final class GraphFactory {
11 |
12 | /**
13 | * Just a simple graph for algorithm testing.
14 | *
15 | * @return a graph representing two paths from n1 to n5
16 | */
17 | public static Graph simpleTestGraph() {
18 | Graph graph = new Graph();
19 | Node n1 = graph.newNode("n1", 5, 5);
20 | Node n2 = graph.newNode("n2", 3, 4);
21 | Node n3 = graph.newNode("n3", 1, 4);
22 | Node n4 = graph.newNode("n4", 6, 4);
23 | Node n5 = graph.newNode("n5", 5, 0);
24 |
25 | graph.connect(n1, n2, 2);
26 | graph.connect(n2, n3, 2);
27 | graph.connect(n3, n5, 1);
28 | graph.connect(n1, n4, 1);
29 | graph.connect(n4, n5, 5);
30 |
31 | return graph;
32 | }
33 |
34 | /**
35 | * A grid with all adjacent grid nodes connected.
36 | *
37 | * @param width of the grid
38 | * @param height of the grid
39 | * @return the grid graph
40 | */
41 | public static Graph grid(int width, int height) {
42 | Graph graph = new Graph();
43 | buildGrid(graph, 0, 0, width, height);
44 | return graph;
45 | }
46 |
47 | /**
48 | * Two rooms simulation. The rooms are connected by a 1x1 grid
49 | * at the right side of the of the rooms.
50 | *
51 | * @param roomSize width and height of one room
52 | * @return the rooms grid
53 | */
54 | public static Graph twoRoom(int roomSize) {
55 | Graph graph = new Graph();
56 | buildGrid(graph, 0, 0, roomSize, roomSize);
57 | buildGrid(graph, 0, roomSize, roomSize, roomSize);
58 |
59 | // Connect rooms
60 | List nodes = graph.getNodes();
61 | graph.connect(nodes.get(roomSize*roomSize-1), nodes.get((roomSize+1)*roomSize-1), 1.0);
62 | graph.connect(nodes.get(roomSize*roomSize-1), nodes.get((roomSize+1)*roomSize-2), 1.0);
63 | graph.connect(nodes.get(roomSize*roomSize-2), nodes.get((roomSize+1)*roomSize-1), 1.0);
64 | graph.connect(nodes.get(roomSize*roomSize-2), nodes.get((roomSize+1)*roomSize-2), 1.0);
65 |
66 | return graph;
67 | }
68 |
69 | /**
70 | * A helper function to build grids.
71 | *
72 | * @param graph the graph
73 | * @param x left coordinate
74 | * @param y bottom coordinate
75 | * @param width width of the grid
76 | * @param height height of the grid
77 | */
78 | protected static void buildGrid(final Graph graph, int x, int y, int width, int height) {
79 | Node[][] matrix = new Node[height][width];
80 | // Create nodes
81 | for (int row = 0; row < height; row++) {
82 | for (int col = 0; col < width; col++) {
83 | matrix[row][col] = graph.newNode(String.format("(%1$s,%2$s)", y + row, x + col), y, x);
84 | }
85 | }
86 |
87 | // Create edges, in the most inefficient way.
88 | for (int row = 0; row < height; row++) {
89 | for (int col = 0; col < width; col++) {
90 | for (int top = row - 1; top <= row + 1; top++) {
91 | for (int left = col - 1; left <= col + 1; left++) {
92 | if (top >= 0 && top < height && left >= 0 && left < width) {
93 | if (top != row || left != col) {
94 | graph.direct(matrix[row][col], matrix[top][left], 1.0);
95 | }
96 | }
97 | }
98 | }
99 | }
100 | }
101 |
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/zipstar/Node.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.Collection;
4 |
5 | /**
6 | * A graph node.
7 | *
8 | * @author Adam Smyczek
9 | */
10 | public final class Node {
11 |
12 | private final String name;
13 | private final double coordX;
14 | private final double coordY;
15 |
16 | private final Graph graph;
17 |
18 | // Cached reachable nodes
19 | private Collection adjacentNodes = null;
20 |
21 | /**
22 | * Protected constructor.
23 | * Create a node using newNode() constructor
24 | * method of the Graph class.
25 | *
26 | * @param graph the associated graph
27 | * @param name node name
28 | * @param coordX x coordinate
29 | * @param coordY y coordinate
30 | */
31 | protected Node(final Graph graph, final String name, final double coordX, final double coordY) {
32 | super();
33 | this.graph = graph;
34 | this.name = name;
35 | this.coordX = coordX;
36 | this.coordY = coordY;
37 | }
38 |
39 | // ---- Accessors ----
40 |
41 | public String getName() {
42 | return name;
43 | }
44 |
45 | public double getCoordX() {
46 | return coordX;
47 | }
48 |
49 | public double getCoordY() {
50 | return coordY;
51 | }
52 |
53 | /**
54 | * Cached collection of nodes reachable from this node.
55 | *
56 | * @return the nodes reachable from this node
57 | */
58 | public Collection adjacentNodes() {
59 | if (adjacentNodes == null) {
60 | adjacentNodes = graph.adjacentNodesFrom(this);
61 | }
62 | return adjacentNodes;
63 | }
64 |
65 |
66 | /**
67 | * Edge weight/cost from this not to to to node are not direct connected.
72 | */
73 | public double edgeWeightTo(final Node to) {
74 | return graph.getWeight(this, to);
75 | }
76 |
77 | /**
78 | * Calculates air distance between this node coordinate
79 | * and to node.
80 | *
81 | * @param to node
82 | * @return the air distance
83 | */
84 | public double directDistanceTo(Node to) {
85 | double dx = Math.abs(coordX - to.getCoordX());
86 | double dy = Math.abs(coordY - to.getCoordY());
87 | return Math.sqrt(dx*dx + dy*dy);
88 | }
89 |
90 | @Override
91 | public String toString() {
92 | return getName();
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/zipstar/Path.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.List;
6 |
7 | import com.mu.zipper.Loc;
8 | import com.mu.zipper.ZipNode;
9 | import com.mu.zipper.examples.zipstar.ZipStar.ZipStarNode;
10 |
11 | /**
12 | * ZipStar result object.
13 | *
14 | * @author Adam Smyczek
15 | */
16 | public final class Path {
17 |
18 | private final Loc loc;
19 |
20 | protected Path(Loc loc) {
21 | super();
22 | this.loc = loc;
23 | }
24 |
25 | /**
26 | * @return the node path from start to target.
27 | */
28 | public Collection getPath() {
29 | List result = new ArrayList();
30 | for (ZipNode n : loc.nodePath()) {
31 | result.add(n._source().node);
32 | }
33 | return result;
34 | }
35 |
36 | /**
37 | * @return distance/cost from start node to target node.
38 | */
39 | public double getDistance() {
40 | return loc._source().distanceFromStart;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/zipstar/SortedList.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.Collection;
4 | import java.util.Collections;
5 | import java.util.Comparator;
6 | import java.util.Iterator;
7 | import java.util.LinkedList;
8 | import java.util.List;
9 | import java.util.ListIterator;
10 |
11 | /**
12 | * A simple SortedList implementation.
13 | *
14 | * @param element type
15 | *
16 | * @author Adam Smyczek
17 | */
18 | public class SortedList implements List {
19 |
20 | private final Comparator comparator;
21 |
22 | private final LinkedList delegate = new LinkedList();
23 |
24 | /**
25 | * The constructor takes a comparator used
26 | * for element insertion. See add() method.
27 | *
28 | * @param comparator
29 | */
30 | public SortedList(Comparator comparator) {
31 | super();
32 | if (comparator == null) throw new IllegalArgumentException("Comparator is null!");
33 | this.comparator = comparator;
34 | }
35 |
36 | /**
37 | * Inserts new element in the order defined by comparator .
38 | *
39 | * @param o the object to insert
40 | */
41 | public boolean add(E o) {
42 | int idx = Collections.binarySearch(delegate, o, comparator);
43 | delegate.add((int)Math.signum(idx) * (idx + 1), o);
44 | return true;
45 | }
46 |
47 | /**
48 | * Inserts all elements in the order defined by comparator .
49 | *
50 | * @param c
51 | * @return true
52 | */
53 | public boolean add(E... c) {
54 | for (E e : c) add(e);
55 | return true;
56 | }
57 |
58 | /**
59 | * Same as add(E... c) , but for collections.
60 | */
61 | public boolean addAll(Collection extends E> c) {
62 | for (E e : c) add(e);
63 | return true;
64 | }
65 |
66 | // ---- LinkedList methods ----
67 |
68 | public E getFirst() {
69 | return delegate.getFirst();
70 | }
71 |
72 | public E getLast() {
73 | return delegate.getLast();
74 | }
75 |
76 | public E removeFirst() {
77 | return delegate.removeFirst();
78 | }
79 |
80 | public E removeLast() {
81 | return delegate.removeLast();
82 | }
83 |
84 | // ---- Delegated methods ----
85 |
86 | public void clear() {
87 | delegate.clear();
88 | }
89 |
90 | public boolean contains(Object o) {
91 | return delegate.contains(o);
92 | }
93 |
94 | public boolean containsAll(Collection> c) {
95 | return delegate.containsAll(c);
96 | }
97 |
98 | public E get(int index) {
99 | return delegate.get(index);
100 | }
101 |
102 | public int indexOf(Object o) {
103 | return delegate.indexOf(o);
104 | }
105 |
106 | public boolean isEmpty() {
107 | return delegate.isEmpty();
108 | }
109 |
110 | public Iterator iterator() {
111 | return delegate.iterator();
112 | }
113 |
114 | public int lastIndexOf(Object o) {
115 | return delegate.lastIndexOf(o);
116 | }
117 |
118 | public ListIterator listIterator() {
119 | return delegate.listIterator();
120 | }
121 |
122 | public ListIterator listIterator(int index) {
123 | return delegate.listIterator(index);
124 | }
125 |
126 | public boolean remove(Object o) {
127 | return delegate.remove(o);
128 | }
129 |
130 | public E remove(int index) {
131 | return delegate.remove(index);
132 | }
133 |
134 | public boolean removeAll(Collection> c) {
135 | return delegate.removeAll(c);
136 | }
137 |
138 | public boolean retainAll(Collection> c) {
139 | return delegate.retainAll(c);
140 | }
141 |
142 | public int size() {
143 | return delegate.size();
144 | }
145 |
146 | public List subList(int fromIndex, int toIndex) {
147 | return delegate.subList(fromIndex, toIndex);
148 | }
149 |
150 | public Object[] toArray() {
151 | return delegate.toArray();
152 | }
153 |
154 | public T[] toArray(T[] a) {
155 | return delegate.toArray(a);
156 | }
157 |
158 | // ---- Unsupported methods ----
159 |
160 | public E set(int index, E element) {
161 | throw new UnsupportedOperationException();
162 | }
163 |
164 | public void add(int index, E element) {
165 | throw new UnsupportedOperationException();
166 | }
167 |
168 | public boolean addAll(int index, Collection extends E> c) {
169 | throw new UnsupportedOperationException();
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/examples/com/mu/zipper/examples/zipstar/ZipStar.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.Comparator;
6 | import java.util.List;
7 |
8 | import com.mu.zipper.IZipNode;
9 | import com.mu.zipper.Loc;
10 | import com.mu.zipper.Zipper;
11 |
12 | /**
13 | * A* algorithm in Zipper.
14 | * Not a conventional Zipper application, but it makes a nice example.
15 | * For details on A* see
16 | * 'A*' on Wikipedia
17 | *
18 | * @author Adam Smyczek
19 | */
20 | public final class ZipStar {
21 |
22 | /**
23 | * Calculate the best-first path from start
24 | * to target node for the given graph .
25 | *
26 | * @param graph the graph
27 | * @param start node
28 | * @param target node
29 | * @return Path result containing the path and distance information
30 | */
31 | public final static Path calcPath(final Graph graph, final Node start, final Node target) {
32 | return new ZipStar(graph, start, target).calcPath();
33 | }
34 |
35 | // ---- Private implementation ----
36 |
37 | private final Graph graph;
38 |
39 | private final Node start;
40 |
41 | private final Node target;
42 |
43 | // List of potential nodes to be evaluated.
44 | private final List activeNodes;
45 |
46 | /**
47 | * The constructor performs plain assignments,
48 | * no initialization required.
49 | *
50 | * @param graph
51 | * @param start
52 | * @param target
53 | */
54 | private ZipStar(final Graph graph, final Node start, final Node target) {
55 | super();
56 | this.graph = graph;
57 | this.start = start;
58 | this.target = target;
59 | this.activeNodes = new ArrayList(graph.getNodes());
60 | }
61 |
62 | /**
63 | * Calculates a set of currently reachable graph nodes from node .
64 | * getChildren() returns only the active/not evaluated reachable
65 | * nodes. The result set changes as the path search proceeds.
66 | *
67 | * @param node
68 | * @return current list of reachable and active/not visited nodes for a given node
69 | */
70 | private Collection getChildren(final ZipStarNode node) {
71 | List children = new ArrayList();
72 | for (Node n : node.node.adjacentNodes()) {
73 | if (activeNodes.contains(n)) {
74 | children.add(new ZipStarNode(n, n.directDistanceTo(target)));
75 | }
76 | }
77 | return children;
78 | }
79 |
80 | /**
81 | * The algorithm
82 | * @return path result
83 | * @throws IllegalStateException if no path form start to target exists.
84 | */
85 | private Path calcPath() {
86 | // List of possible paths locations sorted by the distance-plus-cost function, see comparator
87 | SortedList> paths = new SortedList>(distance_plus_cost_comparator);
88 |
89 | // Start location from the start node
90 | Loc startLoc = Zipper.zip(new ZipStarNode(start, start.directDistanceTo(target)));
91 | paths.add(startLoc);
92 |
93 | // Calculate possible best-first paths recursively
94 | follow(paths);
95 |
96 | // If paths is not empty, first location represents
97 | // the best-first path from start to target node
98 | if (!paths.isEmpty()) {
99 | return new Path(paths.get(0));
100 | }
101 | // otherwise no path exists between this nodes
102 | throw new IllegalStateException(String.format("No path exists from %1$s to %2$s", start, target));
103 | }
104 |
105 | /**
106 | * Calculate possible best-first paths recursively
107 | * @param paths sorted by the best distance-plus-cost function
108 | */
109 | private void follow(SortedList> paths) {
110 | // If paths is empty, no path between start and target node exists,
111 | // If first node is the target node, best-first path found.
112 | if (paths.isEmpty() || paths.getFirst()._source().node.equals(target)) return;
113 |
114 | // Follow the best location, first path in paths list:
115 | // 1. remove first node from the path list,
116 | // 2. follow all it's children and add this to the paths list.
117 | Loc first = paths.removeFirst();
118 | activeNodes.remove(first._source().node);
119 |
120 | for (int i = 0 ; i < first.node().getChildren().size(); i++) {
121 | Loc down = first.down(i);
122 | down._source().distanceFromStart = first._source().distanceFromStart +
123 | graph.getWeight(first._source().node, down._source().node);
124 | paths.add(down);
125 | }
126 |
127 | // Follow paths
128 | follow(paths);
129 | }
130 |
131 | /**
132 | * The link between graph and Zipper nodes,
133 | * a wrapper around a graph node which implements
134 | * the IZipNode interface.
135 | */
136 | protected class ZipStarNode implements IZipNode {
137 |
138 | double distanceFromStart;
139 | double distanceToTarget;
140 | final Node node;
141 |
142 | /**
143 | * @param node the wrapped graph node
144 | * @param distanceToTarget cached air distance from
145 | * this node to the target
146 | */
147 | public ZipStarNode(Node node, double distanceToTarget) {
148 | super();
149 | this.node = node;
150 | this.distanceFromStart = 0;
151 | this.distanceToTarget = distanceToTarget;
152 | }
153 |
154 | /**
155 | * The distance-plus-cost heuristic function calculated
156 | * from the distance from star node and the air distance
157 | * to the target node.
158 | *
159 | * @return sum of the traveled distance from start node
160 | * and the air distance to target node
161 | */
162 | public double distance() {
163 | return distanceFromStart + distanceToTarget;
164 | }
165 |
166 | /**
167 | * A delegate to ZipStar getChildren() .
168 | *
169 | * @return current list of reachable nodes,
170 | * already visited nodes excluded.
171 | */
172 | public Collection getChildren() {
173 | return ZipStar.this.getChildren(this);
174 | }
175 |
176 | }
177 |
178 | /**
179 | * Compares nodes inside a location based on distance-plus-cost
180 | * function distance()
181 | */
182 | private final static Comparator> distance_plus_cost_comparator =
183 | new Comparator>() {
184 | public int compare(Loc o1, Loc o2) {
185 | return (int) Math.signum(o1._source().distance() - o2._source().distance());
186 | }
187 | };
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/lib/junit-4.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asmyczek/java-zipper/9fab268fce942def7f51e3c7c86b9fbf15433004/lib/junit-4.7.jar
--------------------------------------------------------------------------------
/src/com/mu/zipper/Context.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | /**
4 | * Internal location context holds references
5 | * to the parent context and all sibling nodes
6 | * of the current location node.
7 | *
8 | * @author Adam Smyczek
9 | */
10 | final class Context {
11 |
12 | // Top context, marks the context of the root node
13 | protected static final Context TOP = new Context(null, null, null, null);
14 |
15 | // Left sibling nodes
16 | private final IZipNode[] left;
17 |
18 | // Right sibling nodes
19 | private final IZipNode[] right;
20 |
21 | // Parent context
22 | private final Context parentContext;
23 |
24 | // Parent node
25 | private final ZipNode> parentNode;
26 |
27 | protected Context(
28 | final ZipNode> parentNode,
29 | final Context parentContext,
30 | final IZipNode[] left,
31 | final IZipNode[] right) {
32 | super();
33 | this.parentNode = parentNode;
34 | this.parentContext = parentContext;
35 | this.left = (left == null)? new IZipNode[0] : left;
36 | this.right = (right == null)? new IZipNode[0] : right;
37 | }
38 |
39 | /**
40 | * @return true if the current location marks the root node
41 | */
42 | protected boolean isTop() {
43 | return parentNode == null;
44 | }
45 |
46 | /**
47 | * @return true if the current location is the first sibling
48 | */
49 | protected boolean isFirst() {
50 | return left.length == 0;
51 | }
52 |
53 | /**
54 | * @return true if the current location is the last sibling
55 | */
56 | protected boolean isLast() {
57 | return right.length == 0;
58 | }
59 |
60 | /**
61 | * @return left sibling nodes
62 | */
63 | protected IZipNode[] leftNodes() {
64 | return left;
65 | }
66 |
67 | /**
68 | * @return right sibling nodes
69 | */
70 | protected IZipNode[] rightNodes() {
71 | return right;
72 | }
73 |
74 | /**
75 | * @return parent context
76 | */
77 | protected Context getParentContext() {
78 | return parentContext;
79 | }
80 |
81 | /**
82 | * @return parent node
83 | */
84 | protected ZipNode> getParentNode() {
85 | return parentNode;
86 | }
87 |
88 | /**
89 | * Helper copy function.
90 | *
91 | * @return a shallow copy of this context
92 | */
93 | protected Context copy() {
94 | IZipNode[] l = new IZipNode[left.length];
95 | System.arraycopy(left, 0, l, 0, left.length);
96 | IZipNode[] r = new IZipNode[right.length];
97 | System.arraycopy(right, 0, r, 0, right.length);
98 | return new Context(parentNode, parentContext, l, r);
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/com/mu/zipper/IZipNode.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | import java.util.Collection;
4 |
5 | /**
6 | * The only hook to the Zipper data structure.
7 | * All nodes handled by the Zipper have
8 | * to implement this interface.
9 | *
10 | * @author Adam Smyczek
11 | */
12 | public interface IZipNode {
13 |
14 | /**
15 | * If getChildren() returns null
16 | * Zipper considers this node to be a leaf node.
17 | * Return a collection to add/remove child nodes.
18 | *
19 | * @return null if child node, a collection (empty or not) otherwise.
20 | */
21 | abstract public Collection extends IZipNode> getChildren();
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/com/mu/zipper/Loc.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.Collections;
6 | import java.util.Iterator;
7 | import java.util.LinkedList;
8 | import java.util.List;
9 |
10 | /**
11 | * A Loc object represents a location inside the
12 | * Zipper data structure. A location references the
13 | * node in focus and a context describing the position
14 | * of the node inside the tree.
15 | * A Zipper is traversed by moving from one location to
16 | * another using the basic down() , left() ,
17 | * right() and up() methods. Every move
18 | * operation returns a new location instance.
19 | * Changes to the tree are possible only at the
20 | * node in currently focused location.
21 | *
22 | * A location can be seen as a view into the tree.
23 | * Tree changes in one location are not visible to other
24 | * locations. See getting started example for details.
25 | *
26 | * @author Adam Smyczek
27 | *
28 | * @param the concrete node type
29 | */
30 | public final class Loc {
31 |
32 | // The node in focus
33 | private final ZipNode node;
34 |
35 | // The context of this location
36 | private final Context context;
37 |
38 | /**
39 | * Default constructor
40 | * @param node in focus
41 | * @param context location context
42 | */
43 | protected Loc(final ZipNode node, final Context context) {
44 | super();
45 | this.node = node;
46 | this.context = context;
47 | }
48 |
49 | /**
50 | * @return ZipNode for this location
51 | */
52 | public ZipNode node() {
53 | return node;
54 | }
55 |
56 | /**
57 | * Returns the underlying source node for this location.
58 | * Caution, all changes to this node are reflected
59 | * in all locations of the tree. If this is not
60 | * intended, use replace() or replaceNode() .
61 | *
62 | * @return the source node
63 | */
64 | public T _source() {
65 | return node._source();
66 | }
67 |
68 | // ---- Location predicates ----
69 |
70 | /**
71 | * @return true if this location marks the root node
72 | */
73 | public boolean isTop() {
74 | return context.isTop();
75 | }
76 |
77 | /**
78 | * @return true if this location marks the most left sibling node
79 | */
80 | public boolean isFirst() {
81 | return context.isFirst();
82 | }
83 |
84 | /**
85 | * @return true if this location marks the most right sibling node
86 | */
87 | public boolean isLast() {
88 | return context.isLast();
89 | }
90 |
91 | /**
92 | * Marks the last node in a deep-first traversal order.
93 | * Use this method in combination with next() .
94 | *
95 | * @return true if this location is the last node
96 | * in a deep-first traversal order
97 | */
98 | public boolean isEnd() {
99 | if (hasChildren() || !isLast()) {
100 | return false;
101 | } else {
102 | Loc up = this;
103 | while (up.isLast() && !up.isTop()) {
104 | up = up.up();
105 | }
106 | return up.isTop();
107 | }
108 | }
109 |
110 | // ---- Children accessors ----
111 |
112 | /**
113 | * @return if this location marks a leaf node
114 | */
115 | public boolean isLeaf() {
116 | return node.isLeaf();
117 | }
118 |
119 | /**
120 | * @return if the node contains children, false
121 | * if the node is a leaf node or has no children.
122 | */
123 | public boolean hasChildren() {
124 | return node.hasChildren();
125 | }
126 |
127 | /**
128 | * An iterator over all children nodes.
129 | * This iterator always returns the underlying
130 | * source nodes. In difference to it,
131 | * node().getChildren() returns a mixed
132 | * collection of T and ZipNodes, depending on if a
133 | * node was already traversed by the Zipper or not.
134 | *
135 | * @return an iterator over all children nodes of the current location
136 | * @throws ZipperException if this node is a leaf node
137 | */
138 | public Iterator childrenIterator() {
139 | if (!isLeaf()) {
140 | final Iterator extends IZipNode> iter = node.getChildren().iterator();
141 | return new Iterator() {
142 |
143 | public boolean hasNext() {
144 | return iter.hasNext();
145 | }
146 |
147 | @SuppressWarnings("unchecked")
148 | public T next() {
149 | IZipNode node = iter.next();
150 | return (node instanceof ZipNode>)? ((ZipNode)node)._source() : (T)node;
151 | }
152 |
153 | public void remove() {
154 | throw new UnsupportedOperationException("Use Loc remove methods to remove children.");
155 | }
156 |
157 | };
158 | }
159 | throw new ZipperException("This node is a leaf node!");
160 | }
161 |
162 | // **** Traversing the zipper ****
163 |
164 | /**
165 | * Move down to the first/most left child node
166 | *
167 | * @return the new location
168 | */
169 | public Loc down() {
170 | return down(0);
171 | }
172 |
173 | /**
174 | * Move down to the n-th node.
175 | *
176 | * @param index of the n-th node
177 | * @return the new location
178 | * @throws ZipperException if this node is a leaf node or index out of bound
179 | */
180 | public Loc down(int index) {
181 | if (hasChildren() && index < node.children().length) {
182 | IZipNode[] left = new IZipNode[index];
183 | System.arraycopy(node.children(), 0, left, 0, left.length);
184 | IZipNode[] right = new IZipNode[node.children().length - index - 1];
185 | System.arraycopy(node.children(), index + 1, right, 0, right.length);
186 | return new Loc(toZipNode(node.children()[index]), new Context(node, context, left, right));
187 | }
188 | throw new ZipperException("Current node does not have any children or index out of bound!");
189 | }
190 |
191 | /**
192 | * Move up to the parent node.
193 | *
194 | * @return new location
195 | * @throws ZipperException if this node is already a root node
196 | */
197 | @SuppressWarnings("unchecked")
198 | public Loc up() {
199 | if (!isTop()) {
200 | IZipNode[] ch = new IZipNode[1+
201 | context.leftNodes().length +
202 | context.rightNodes().length];
203 | System.arraycopy(context.leftNodes(), 0, ch, 0, context.leftNodes().length);
204 | ch[context.leftNodes().length] = node;
205 | System.arraycopy(context.rightNodes(), 0, ch, context.leftNodes().length + 1, context.rightNodes().length);
206 | return new Loc(new ZipNode((T)context.getParentNode()._source(), ch), context.getParentContext());
207 | }
208 | throw new ZipperException("Current node is already the top node!");
209 | }
210 |
211 |
212 | /**
213 | * Move to the next sibling node.
214 | *
215 | * @return new location
216 | * @throws ZipperException if this node is the most right sibling node already
217 | */
218 | public Loc right() {
219 | if (!isLast()) {
220 | IZipNode[] left = new IZipNode[context.leftNodes().length + 1];
221 | System.arraycopy(context.leftNodes(), 0, left, 0, context.leftNodes().length);
222 | left[left.length-1] = node;
223 |
224 | IZipNode[] right = new IZipNode[context.rightNodes().length - 1];
225 | System.arraycopy(context.rightNodes(), 1, right, 0, context.rightNodes().length - 1);
226 |
227 | Context ctx = new Context(context.getParentNode(), context.getParentContext(), left, right);
228 | return new Loc(toZipNode(context.rightNodes()[0]), ctx);
229 | }
230 | throw new ZipperException("Current node is already the the most right node!");
231 | }
232 |
233 |
234 | /**
235 | * Move to the previous sibling node.
236 | *
237 | * @return new location
238 | * @throws ZipperException if this location is the most left sibling node already
239 | */
240 | public Loc left() {
241 | if (!isFirst()) {
242 | IZipNode[] left = new IZipNode[context.leftNodes().length - 1];
243 | System.arraycopy(context.leftNodes(), 0, left, 0, context.leftNodes().length - 1);
244 |
245 | IZipNode[] right = new IZipNode[context.rightNodes().length + 1];
246 | System.arraycopy(context.rightNodes(), 0, right, 1, context.rightNodes().length);
247 | right[0] = node;
248 | Context ctx = new Context(context.getParentNode(), context.getParentContext(), left, right);
249 | return new Loc(toZipNode(context.leftNodes()[left.length]), ctx);
250 | }
251 | throw new ZipperException("Current node is already the the most left node!");
252 | }
253 |
254 | /**
255 | * Move to the right most sibling node.
256 | *
257 | * @return new location
258 | */
259 | public Loc rightMost() {
260 | Loc l = this;
261 | while (!l.isLast()) {
262 | l = l.right();
263 | }
264 | return l;
265 | }
266 |
267 | /**
268 | * Move to the most left sibling node.
269 | *
270 | * @return new location
271 | */
272 | public Loc leftMost() {
273 | Loc l = this;
274 | while (!l.isFirst()) {
275 | l = l.left();
276 | }
277 | return l;
278 | }
279 |
280 | /**
281 | * Move up to the root node.
282 | *
283 | * @return new location
284 | */
285 | public Loc root() {
286 | Loc l = this;
287 | while (!l.isTop()) {
288 | l = l.up();
289 | }
290 | return l;
291 | }
292 |
293 | /**
294 | * Move to the next node in a deep-first traversal order.
295 | * Use in combination with isEnd() predicate.
296 | *
297 | * @return new location
298 | */
299 | public Loc next() {
300 | if (hasChildren()) {
301 | return down();
302 | } else if (!isLast()) {
303 | return right();
304 | } else {
305 | Loc up = this;
306 | while (up.isLast() && !up.isTop()) {
307 | up = up.up();
308 | }
309 | if (!up.isLast()) {
310 | return up.right();
311 | } else {
312 | throw new ZipperException("Current node is a top node.");
313 | }
314 | }
315 | }
316 |
317 | // ---- Altering Zipper tree ----
318 |
319 | /**
320 | * Add nodes to the end of the children list.
321 | * This method returns a new location instance referencing
322 | * the same source node but and the updated children set.
323 | *
324 | * @param nodes to append
325 | * @return new location instance for the same node,
326 | * but containing the new children set
327 | */
328 | public Loc add(final T... nodes) {
329 | if (!isLeaf()) {
330 | IZipNode[] ch = new IZipNode[nodes.length + node.children().length];
331 | System.arraycopy(node.children(), 0, ch, 0, node.children().length);
332 | System.arraycopy(nodes, 0, ch, node.children().length, nodes.length);
333 | return new Loc(new ZipNode(_source(), ch), context);
334 | }
335 | throw new ZipperException("Current node is a leaf!");
336 | }
337 |
338 | /**
339 | * Same as add(T...) .
340 | */
341 | @SuppressWarnings("unchecked")
342 | public Loc addAll(final Collection nodes) {
343 | return add((T[]) nodes.toArray());
344 | }
345 |
346 | /**
347 | * Remove child node at position index .
348 | *
349 | * @param index to remove
350 | * @return new location with updated children set
351 | * @throws ZipperException if index out of bounds
352 | */
353 | public Loc removeChild(int index) {
354 | if (hasChildren() && index < node.getChildren().size()) {
355 | IZipNode[] ch = new IZipNode[node.children().length - 1];
356 | System.arraycopy(node.children(), 0, ch, 0, index);
357 | System.arraycopy(node.children(), index + 1, ch, index, node.children().length - index - 1);
358 | return new Loc(new ZipNode(_source(), ch), context);
359 | }
360 | throw new ZipperException("Current node does not have any children or index out of bound!");
361 | }
362 |
363 | /**
364 | * Remove all children.
365 | *
366 | * @return new location with updated children set
367 | * @throws ZipperException if location marks a leaf node
368 | */
369 | public Loc clear() {
370 | if (!isLeaf()) {
371 | return new Loc(new ZipNode(_source(), new IZipNode[0]), context);
372 | }
373 | throw new ZipperException("Current node is a leaf!");
374 | }
375 |
376 | /**
377 | * Inserts nodes to the left of the current location node.
378 | *
379 | * @param nodes to insert
380 | * @return new location referencing same node, but updated context
381 | */
382 | public Loc insertLeft(T... nodes) {
383 | IZipNode[] left = new IZipNode[context.leftNodes().length + nodes.length];
384 | System.arraycopy(context.leftNodes(), 0, left, 0, context.leftNodes().length);
385 | System.arraycopy(nodes, 0, left, context.leftNodes().length, nodes.length);
386 |
387 | IZipNode[] right = new IZipNode[context.rightNodes().length];
388 | System.arraycopy(context.rightNodes(), 0, right, 0, context.rightNodes().length);
389 |
390 | Context ctx = new Context(context.getParentNode(), context.getParentContext(), left, right);
391 | return new Loc(node, ctx);
392 | }
393 |
394 | /**
395 | * Inserts nodes to the right of the current location node.
396 | *
397 | * @param nodes to insert
398 | * @return new location referencing same node, but updated context
399 | */
400 | public Loc insertRight(T... nodes) {
401 | IZipNode[] left = new IZipNode[context.leftNodes().length];
402 | System.arraycopy(context.leftNodes(), 0, left, 0, context.leftNodes().length);
403 |
404 | IZipNode[] right = new IZipNode[context.rightNodes().length + nodes.length];
405 | System.arraycopy(nodes, 0, right, 0, nodes.length);
406 | System.arraycopy(context.rightNodes(), 0, right, nodes.length, context.rightNodes().length);
407 |
408 | Context ctx = new Context(context.getParentNode(), context.getParentContext(), left, right);
409 | return new Loc(node, ctx);
410 | }
411 |
412 | /**
413 | * Remove current node, returns parent location.
414 | *
415 | * @return location referencing parent node
416 | * @throws ZipperException if location marks the top node
417 | */
418 | @SuppressWarnings("unchecked")
419 | public Loc remove() {
420 | if (!isTop()) {
421 | IZipNode[] ch = new IZipNode[context.leftNodes().length +
422 | context.rightNodes().length];
423 | System.arraycopy(context.leftNodes(), 0, ch, 0, context.leftNodes().length);
424 | System.arraycopy(context.rightNodes(), 0, ch, context.leftNodes().length, context.rightNodes().length);
425 | return new Loc(new ZipNode((T)context.getParentNode()._source(), ch), context.getParentContext());
426 | }
427 | throw new ZipperException("Current node is already the top node!");
428 | }
429 |
430 | /**
431 | * Removes the sibling on the left.
432 | *
433 | * @return new location referencing same node, but updated context
434 | * @throws ZipperException if location marks the most left node
435 | */
436 | public Loc removeLeft() {
437 | if (!isFirst()) {
438 | IZipNode[] left = new IZipNode[context.leftNodes().length - 1];
439 | System.arraycopy(context.leftNodes(), 0, left, 0, context.leftNodes().length - 1);
440 |
441 | IZipNode[] right = new IZipNode[context.rightNodes().length];
442 | System.arraycopy(context.rightNodes(), 0, right, 0, context.rightNodes().length);
443 |
444 | Context ctx = new Context(context.getParentNode(), context.getParentContext(), left, right);
445 | return new Loc(node, ctx);
446 | }
447 | throw new ZipperException("Current node is the most left node!");
448 | }
449 |
450 | /**
451 | * Removes next sibling to the right.
452 | *
453 | * @return new location referencing same node, but updated context
454 | * @throws ZipperException if location marks the most right node
455 | */
456 | public Loc removeRight() {
457 | if (!isLast()) {
458 | IZipNode[] left = new IZipNode[context.leftNodes().length];
459 | System.arraycopy(context.leftNodes(), 0, left, 0, context.leftNodes().length);
460 |
461 | IZipNode[] right = new IZipNode[context.rightNodes().length - 1];
462 | System.arraycopy(context.rightNodes(), 1, right, 0, context.rightNodes().length - 1);
463 |
464 | Context ctx = new Context(context.getParentNode(), context.getParentContext(), left, right);
465 | return new Loc(node, ctx);
466 | }
467 | throw new ZipperException("Current node is the most right node!");
468 | }
469 |
470 | /**
471 | * Replaces current location node.
472 | *
473 | * @param node
474 | * @return new location with updated node
475 | */
476 | public Loc replace(IZipNode node) {
477 | return new Loc(toZipNode(node), context.copy());
478 | }
479 |
480 | /**
481 | * Replaces the source node for the current location.
482 | *
483 | * @param node
484 | * @return new location with updated source node
485 | */
486 | public Loc replaceSource(T node) {
487 | if (node instanceof ZipNode>) {
488 | throw new IllegalArgumentException("ZipNode not supported!");
489 | }
490 | return new Loc(this.node.replaceNode(node), context.copy());
491 | }
492 |
493 | // ---- Path ----
494 |
495 | /**
496 | * Path components
497 | */
498 | public enum Path {
499 | DOWN, UP, LEFT, RIGHT, LEFT_MOST, RIGHT_MOST, NEXT;
500 | }
501 |
502 | /**
503 | * Calculates the path from root node to the current location.
504 | *
505 | * @return Path array for the current location.
506 | */
507 | // TODO: it's not the most optimal path. Improve this.
508 | public Path[] path() {
509 | LinkedList path = new LinkedList();
510 | Loc l = this;
511 | while (!l.isTop()) {
512 | if (!l.isFirst()) {
513 | path.addFirst(Path.RIGHT);
514 | l = l.left();
515 | } else {
516 | path.addFirst(Path.DOWN);
517 | l = l.up();
518 | }
519 | }
520 | return path.toArray(new Path[path.size()]);
521 | }
522 |
523 | /**
524 | * Traverses Zipper data structure in the order
525 | * of path elements and returns the resulting
526 | * location.
527 | *
528 | * @param path array
529 | * @return resulting location
530 | * @throws ZipperException in case of an invalid path
531 | */
532 | public Loc location(final Path... path) {
533 | Loc l = this.root();
534 | for (Path p : path) {
535 | switch (p) {
536 | case DOWN: l = l.down(); break;
537 | case LEFT: l = l.left(); break;
538 | case RIGHT: l = l.right(); break;
539 | case UP: l = l.up(); break;
540 | case LEFT_MOST: l = l.leftMost(); break;
541 | case RIGHT_MOST: l = l.rightMost(); break;
542 | case NEXT: l = l.next(); break;
543 | }
544 | }
545 | return l;
546 | }
547 |
548 |
549 | /**
550 | * @return all ZipNodes at the direct path from root
551 | * to this location node.
552 | */
553 | public Collection> nodePath() {
554 | List> path = new ArrayList>();
555 | Loc l = this;
556 | while (!l.isTop()) {
557 | path.add(0, l.node());
558 | l = l.up();
559 | }
560 | path.add(0, l.node());
561 | return Collections.unmodifiableList(path);
562 | }
563 |
564 | // ---- Helper functions ----
565 |
566 | /**
567 | * ZipNode constructor helper
568 | *
569 | * @param node
570 | * @return same node if node is a ZipNode already,
571 | * a new ZipNode wrapper otherwise
572 | */
573 | @SuppressWarnings("unchecked")
574 | private ZipNode toZipNode(final IZipNode node) {
575 | if (node instanceof ZipNode>) {
576 | return (ZipNode) node;
577 | } else {
578 | return new ZipNode((T)node);
579 | }
580 | }
581 |
582 | }
583 |
--------------------------------------------------------------------------------
/src/com/mu/zipper/ZipNode.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collection;
5 |
6 | /**
7 | * Internal Zipper node, a wrapper around a tree IZipNode.
8 | * All changes to a Zipper node are not reflected
9 | * in the source tree node, but stored in this wrapper.
10 | * This allow every Zipper locations to maintain local
11 | * tree changes not visible to other locations.
12 | * Use _source() to get the original node,
13 | * or Zipper.unizp() to retrieve the tree
14 | * for the current location.
15 | *
16 | * @author Adam Smyczek
17 | *
18 | * @param concrete IZipNode type
19 | */
20 | public final class ZipNode implements IZipNode {
21 |
22 | // Marks children array as not initialized (default)
23 | private static final IZipNode[] NOT_INITIALIZED = new IZipNode[0];
24 |
25 | // The wrapped node
26 | private final T node;
27 |
28 | // Lazy initialized children list
29 | private IZipNode[] children;
30 |
31 | /**
32 | * Default constructor,
33 | * the children list is marked as not initialized.
34 | *
35 | * @param node wrapped node
36 | */
37 | protected ZipNode(final T node) {
38 | this(node, NOT_INITIALIZED);
39 | }
40 |
41 | /**
42 | * @param node wrapped node
43 | * @param children list
44 | */
45 | protected ZipNode(final T node, final IZipNode[] children) {
46 | super();
47 |
48 | if (node == null) throw new IllegalArgumentException("Node is null!");
49 | assert(!(node instanceof ZipNode>));
50 |
51 | this.node = node;
52 | this.children = children;
53 | }
54 |
55 | /**
56 | * Returns the wrapped node.
57 | * Caution, all changes to this node
58 | * are reflected in all locations of the
59 | * tree. See Loc#_source() or
60 | * Loc#replaceNode() for details.
61 | *
62 | * @return the wrapped node
63 | */
64 | public T _source() {
65 | return node;
66 | }
67 |
68 | /**
69 | * @return true if this node is a leaf node
70 | */
71 | public boolean isLeaf() {
72 | init();
73 | return children == null;
74 | }
75 |
76 | /**
77 | * @return true if this node has children
78 | */
79 | public boolean hasChildren() {
80 | init();
81 | return children != null && children.length > 0;
82 | }
83 |
84 | /**
85 | * @return the children array
86 | */
87 | protected IZipNode[] children() {
88 | init();
89 | return children;
90 | }
91 |
92 | /**
93 | * Implements IZipNode#getChildren() method.
94 | */
95 | public Collection extends IZipNode> getChildren() {
96 | init();
97 | return (children != null)? Arrays.asList(children) : null;
98 | }
99 |
100 | /**
101 | * Replaces the current wrapped node with node
102 | * and returns a new ZipNode instance.
103 | *
104 | * @param node to wrap
105 | * @return new ZipNode instance
106 | */
107 | protected ZipNode replaceNode(final T node) {
108 | init();
109 | return new ZipNode(node, children);
110 | }
111 |
112 | /**
113 | * Initializes the children array if not initialized yet.
114 | */
115 | private void init() {
116 | if (children == NOT_INITIALIZED) {
117 | Collection extends IZipNode> ch = node.getChildren();
118 | children = (ch == null)? null : ch.toArray(new IZipNode[0]);
119 | }
120 | }
121 |
122 | @Override
123 | public String toString() {
124 | return node.toString();
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/com/mu/zipper/Zipper.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | import java.util.Collection;
4 |
5 | /**
6 | * Zipper constructor and util functions.
7 | *
8 | * @author Adam Smyczek
9 | */
10 | public final class Zipper {
11 |
12 | /**
13 | * Zips the node and returns the Zipper
14 | * location for this node.
15 | *
16 | * @param concrete IZipNode type
17 | * @param node root node of the tree
18 | * @return Zipper root location
19 | */
20 | public static Loc zip(final T node) {
21 | return new Loc(new ZipNode(node), Context.TOP);
22 | }
23 |
24 | /**
25 | * Opposite to zip, unzip re-creates the
26 | * tree from the Zipper data structure.
27 | *
28 | * @param concrete note type
29 | * @param location a location in the tree
30 | * @return the unzipped tree
31 | */
32 | public static T unzip(final Loc location) {
33 | return Zipper.unzip(location.root().node());
34 | }
35 |
36 | /**
37 | * Recursive unzip call to all children nodes. The children
38 | * of every original source node are replaced with the
39 | * children of the corresponding ZipNode.
40 | */
41 | @SuppressWarnings("unchecked")
42 | private static T unzip(final IZipNode node) {
43 | if (node instanceof ZipNode>) {
44 | ZipNode zipNode = (ZipNode)node;
45 | T source = zipNode._source();
46 | if (!zipNode.isLeaf()) {
47 | Collection ch = (Collection)source.getChildren();
48 | source.getChildren().clear();
49 | for (IZipNode n : zipNode.children()) {
50 | ch.add((T)unzip(n));
51 | }
52 | }
53 | return source;
54 | } else {
55 | return (T) node;
56 | }
57 | }
58 |
59 | /**
60 | * Traverses the entire tree and wraps every tree node into
61 | * a ZipNode. Usually a call to Loc#node()#getChildren()
62 | * will return a mixed collection of T and ZipNode nodes, depending
63 | * on if a node was already traversed or not. Calling this function
64 | * first will guarantee that the call to getChildren()
65 | * returns ZipNode objects only. This method is expensive, before
66 | * you decide to use it, take a look at Loc#childrenIterator() .
67 | *
68 | * @param
69 | * @param node
70 | * @return location to a root node where every tree node
71 | * is a ZipNode
72 | */
73 | public static Loc unfold(final Loc node) {
74 | Loc l = node.root();
75 | while (!l.isEnd()) {
76 | l = l.next();
77 | }
78 | return l.root();
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/com/mu/zipper/ZipperException.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | /**
4 | * General Zipper exception.
5 | *
6 | * @author Adam Smyczek
7 | */
8 | public class ZipperException extends RuntimeException {
9 |
10 | public ZipperException(String message) {
11 | super(message);
12 | }
13 |
14 | public ZipperException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | private static final long serialVersionUID = 6752384268429655524L;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/test/com/mu/zipper/ZipperTest.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.Iterator;
7 | import java.util.List;
8 |
9 | import junit.framework.TestCase;
10 |
11 | import org.junit.Before;
12 | import org.junit.Test;
13 |
14 | public class ZipperTest extends TestCase {
15 |
16 | private Loc root = null;
17 |
18 | @Override
19 | @Before
20 | protected void setUp() throws Exception {
21 | super.setUp();
22 |
23 | // Test tree
24 | Node b1 = new Node("b1", true);
25 | Node b2 = new Node("b2", true);
26 |
27 | Node c1 = new Node("c1", true);
28 | Node c2 = new Node("c2", true);
29 |
30 | Node a1 = new Node("a1", b1, b2);
31 | Node a2 = new Node("a2", c1, c2);
32 | Node a3 = new Node("a3", false);
33 |
34 | root = Zipper.zip(new Node("root", a1, a2, a3));
35 | }
36 |
37 | @Test
38 | public void testValidMoves() {
39 | assertEquals("root", nodeName(root));
40 | assertEquals("a1", nodeName(root.down()));
41 | assertEquals("a2", nodeName(root.down().right()));
42 | assertEquals("a1", nodeName(root.down().right().left()));
43 | assertEquals("root", nodeName(root.down().right().up()));
44 |
45 | assertEquals("a1", nodeName(root.next()));
46 | assertEquals("b1", nodeName(root.next().next()));
47 | assertEquals("b2", nodeName(root.next().next().next()));
48 | assertEquals("a2", nodeName(root.next().next().next().next()));
49 |
50 |
51 | assertEquals("a3", nodeName(root.next().rightMost()));
52 | assertEquals("a1", nodeName(root.next().right().leftMost()));
53 |
54 | assertEquals("a1", nodeName(root.down(0)));
55 | assertEquals("a2", nodeName(root.down(1)));
56 | assertEquals("a3", nodeName(root.down(2)));
57 |
58 | Loc a2 = root.down().right();
59 | assertEquals("c1", nodeName(a2.down()));
60 | assertEquals("a3", nodeName(a2.right()));
61 |
62 | }
63 |
64 | @Test
65 | public void testPredicates() {
66 | assertTrue(root.isTop());
67 | assertTrue(root.down().isFirst());
68 | assertTrue(root.down().rightMost().isLast());
69 | assertTrue(root.down().rightMost().isEnd());
70 | assertTrue(root.down().right().down().isLeaf());
71 |
72 | assertFalse(root.isEnd());
73 | assertFalse(root.down().right().down().isLast());
74 | }
75 |
76 | @Test
77 | public void testAddChildren() {
78 | Loc b1 = root.next().rightMost();
79 | Loc b11 = b1.add(new Node("d1", true));
80 | Loc b12 = b1.add(new Node("e1", true));
81 |
82 | assertEquals(0, b1.node().getChildren().size());
83 |
84 | assertEquals(1, b11.node().getChildren().size());
85 | assertEquals(1, b12.node().getChildren().size());
86 | assertEquals("d1", nodeName(b11.next()));
87 | assertEquals("e1", nodeName(b12.next()));
88 | }
89 |
90 | @Test
91 | public void testRemoveChildren() {
92 | Loc a1 = root.next();
93 | Loc rem1 = a1.clear();
94 |
95 | assertEquals(2, a1.node().getChildren().size());
96 | assertEquals(0, rem1.node().getChildren().size());
97 |
98 | Loc a2 = root.down(1);
99 | Loc rem2 = a2.removeChild(1);
100 | assertEquals(2, a2.node().getChildren().size());
101 | assertEquals(1, rem2.node().getChildren().size());
102 | assertEquals("c1", nodeName(rem2.next()));
103 | }
104 |
105 | @Test
106 | public void testChildrenIterator() {
107 | Loc r = root.next().right().root();
108 | Iterator iter = r.childrenIterator();
109 | assertEquals("a1", iter.next().getName());
110 | assertEquals("a2", iter.next().getName());
111 | assertEquals("a3", iter.next().getName());
112 | assertFalse(iter.hasNext());
113 | }
114 |
115 | @Test
116 | public void testInsert() {
117 | Loc a2 = root.down(1);
118 |
119 | Loc insL = a2.insertLeft(new Node("l1"), new Node("l2"));
120 | assertEquals(3, root.node().getChildren().size());
121 | assertEquals(5, insL.root().node().getChildren().size());
122 | assertEquals("l2", nodeName(insL.left()));
123 | assertEquals("l1", nodeName(insL.left().left()));
124 |
125 | Loc insR = a2.insertRight(new Node("r1"), new Node("r2"));
126 | assertEquals(3, root.node().getChildren().size());
127 | assertEquals(5, insR.root().node().getChildren().size());
128 | assertEquals("r1", nodeName(insR.right()));
129 | assertEquals("r2", nodeName(insR.right().right()));
130 |
131 | }
132 |
133 | @Test
134 | public void testRemove() {
135 | Loc c1 = root.down(1).down();
136 | Loc rem1 = c1.remove();
137 |
138 | assertEquals("a2", nodeName(rem1));
139 | assertEquals(2, c1.up().node().getChildren().size());
140 | assertEquals(1, rem1.node().getChildren().size());
141 |
142 | Loc rem2 = rem1.remove();
143 |
144 | assertEquals("root", nodeName(rem2));
145 | assertEquals(3, c1.up().up().node().getChildren().size());
146 | assertEquals(2, rem2.node().getChildren().size());
147 | }
148 |
149 | @Test
150 | public void testRemoveLeftRight() {
151 | Loc a2 = root.next().right();
152 | Loc remL = a2.removeLeft();
153 |
154 | assertEquals(3, root.node().getChildren().size());
155 | assertEquals(2, remL.node().getChildren().size());
156 | assertEquals("a2", nodeName(remL));
157 | assertTrue(remL.isFirst());
158 | assertFalse(remL.isLast());
159 |
160 | Loc remR = a2.removeRight();
161 | assertEquals(3, root.node().getChildren().size());
162 | assertEquals(2, remR.node().getChildren().size());
163 | assertEquals("a2", nodeName(remR));
164 | assertTrue(remR.isLast());
165 | assertFalse(remR.isFirst());
166 |
167 | }
168 |
169 | @Test
170 | public void testPath() {
171 | Loc.Path[] path = new Loc.Path[] {
172 | Loc.Path.DOWN,
173 | Loc.Path.RIGHT,
174 | Loc.Path.DOWN
175 | };
176 | Loc c1 = root.location(path);
177 | assertEquals("c1", nodeName(c1));
178 |
179 | Loc.Path[] rpath = c1.path();
180 | assertEquals(path.length, rpath.length);
181 | for (int i = 0; i < path.length; i++) {
182 | assertEquals(path[i], rpath[i]);
183 | }
184 |
185 | Loc c2 = root.location(path);
186 | Collection> nodePath = c2.nodePath();
187 | assertEquals(3, nodePath.size());
188 | Iterator> iter = nodePath.iterator();
189 | ZipNode next = iter.next();
190 | assertEquals("root", next.toString());
191 | next = iter.next();
192 | assertEquals("a2", next.toString());
193 | next = iter.next();
194 | assertEquals("c1", next.toString());
195 |
196 | }
197 |
198 | @Test
199 | public void testReplace() {
200 | Loc a2 = root.next().right();
201 |
202 | Node repWith1 = new Node("r1", new Node("r11"), new Node("r12"));
203 | Loc rep1 = a2.replace(repWith1);
204 |
205 | assertEquals(3, rep1.root().node().getChildren().size());
206 | assertEquals("r1", nodeName(rep1));
207 | assertEquals(2, rep1.node().getChildren().size());
208 | assertEquals("r11", nodeName(rep1.down()));
209 | assertEquals("r12", nodeName(rep1.down().next()));
210 |
211 | Loc repWith2 = root.next();
212 | Loc rep2 = a2.replace(repWith2.node());
213 | assertEquals("a1", nodeName(rep2));
214 | assertEquals(2, rep2.node().getChildren().size());
215 | assertEquals("b1", nodeName(rep2.down()));
216 | assertEquals("b2", nodeName(rep2.down().next()));
217 | }
218 |
219 | @Test
220 | public void testUnzip() {
221 | Loc a2 = root.next().right();
222 | Loc insL = a2.insertLeft(new Node("l1", false));
223 | Loc add = insL.left().add(new Node("d1"), new Node("d2"));
224 |
225 | Node u = Zipper.unzip(add.root());
226 | assertEquals(4, u.getChildren().size());
227 | Iterator l1i = u.getChildren().iterator();
228 | l1i.next();
229 | Node l1 = l1i.next();
230 | assertEquals("l1", l1.getName());
231 | assertEquals(2, l1.getChildren().size());
232 | Iterator di = l1.getChildren().iterator();
233 | assertEquals("d1", di.next().getName());
234 | assertEquals("d2", di.next().getName());
235 | }
236 |
237 | /**
238 | * @param loc
239 | * @return name for the param location
240 | */
241 | private String nodeName(final Loc loc) {
242 | return loc._source().getName();
243 | }
244 |
245 | /**
246 | * Debug print tree method
247 | * @param node
248 | * @return tree string
249 | */
250 | public static String printTree(IZipNode node) {
251 | StringBuffer buf = new StringBuffer();
252 | buf.append(node.toString());
253 | if (node.getChildren() != null) {
254 | buf.append(":[");
255 | for (IZipNode n : node.getChildren()) {
256 | buf.append(printTree(n));
257 | buf.append(", ");
258 | }
259 | buf.delete(buf.length() - 2, buf.length());
260 | buf.append("]");
261 | }
262 | return buf.toString();
263 | }
264 |
265 | /**
266 | * Test IZipNode class
267 | */
268 | class Node implements IZipNode {
269 |
270 | private String name;
271 | private final List children;
272 |
273 | public Node(final String name) {
274 | this(name, (Node[])null);
275 | }
276 |
277 | public Node(final String name, final boolean leaf) {
278 | this(name, (leaf)? (Node[])null : new Node[0]);
279 | }
280 |
281 | public Node(final String name, final Node... children) {
282 | super();
283 | assert(name != null);
284 | this.name = name;
285 | this.children = (children == null)? null : new ArrayList(Arrays.asList(children));
286 | }
287 |
288 | public String getName() {
289 | return name;
290 | }
291 |
292 | public Collection getChildren() {
293 | return this.children;
294 | }
295 |
296 | @Override
297 | public String toString() {
298 | return name;
299 | }
300 |
301 | }
302 |
303 | }
304 |
--------------------------------------------------------------------------------
/test/com/mu/zipper/examples/zipstar/GraphFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.Iterator;
4 |
5 | import junit.framework.TestCase;
6 |
7 | import org.junit.Test;
8 |
9 | public class GraphFactoryTest extends TestCase {
10 |
11 | @Test
12 | public void testBuildGrid() {
13 | int rows = 2;
14 | int cols = 3;
15 |
16 | Node[][] m = new Node[rows][cols];
17 |
18 | // Build grid
19 | Graph graph = new Graph();
20 | GraphFactory.buildGrid(graph, 1, 1, cols, rows);
21 |
22 | // Test nodes
23 | Iterator i = graph.getNodes().iterator();
24 | for (int r = 0; r < rows; r++) {
25 | for (int c = 0; c < cols; c++) {
26 | Node n = i.next();
27 | m[r][c] = n;
28 | assertEquals(String.format("(%1$s,%2$s)", r + 1, c + 1), n.getName());
29 | }
30 | }
31 |
32 | // Test edges
33 | assertEquals(1.0, graph.getWeight(m[0][0], m[0][1]));
34 | assertEquals(1.0, graph.getWeight(m[0][0], m[1][1]));
35 | assertEquals(1.0, graph.getWeight(m[0][0], m[1][0]));
36 |
37 | assertEquals(1.0, graph.getWeight(m[0][1], m[0][0]));
38 | assertEquals(1.0, graph.getWeight(m[1][1], m[0][0]));
39 | assertEquals(1.0, graph.getWeight(m[1][0], m[0][0]));
40 |
41 | assertEquals(1.0, graph.getWeight(m[rows-1][cols-1], m[rows-2][cols-1]));
42 | assertEquals(1.0, graph.getWeight(m[rows-1][cols-1], m[rows-2][cols-2]));
43 | assertEquals(1.0, graph.getWeight(m[rows-1][cols-1], m[rows-1][cols-2]));
44 |
45 | assertEquals(1.0, graph.getWeight(m[rows-2][cols-1], m[rows-1][cols-1]));
46 | assertEquals(1.0, graph.getWeight(m[rows-2][cols-2], m[rows-1][cols-1]));
47 | assertEquals(1.0, graph.getWeight(m[rows-1][cols-2], m[rows-1][cols-1]));
48 |
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/test/com/mu/zipper/examples/zipstar/SortedListTest.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.Comparator;
4 |
5 | import junit.framework.TestCase;
6 |
7 | import org.junit.Test;
8 |
9 | public class SortedListTest extends TestCase {
10 |
11 | @Test
12 | public void testSortedList() {
13 |
14 | Comparator comparator = new Comparator() {
15 | public int compare(String o1, String o2) {
16 | return o1.compareTo(o2);
17 | }
18 | };
19 | SortedList list = new SortedList(comparator);
20 |
21 | list.add("b", "d", "e");
22 | list.add("a");
23 | list.add("c");
24 | list.add("d");
25 | list.add("f");
26 | list.add("g");
27 |
28 | assertEquals("a", list.removeFirst());
29 | assertEquals("b", list.removeFirst());
30 | assertEquals("c", list.removeFirst());
31 | assertEquals("d", list.removeFirst());
32 | assertEquals("d", list.removeFirst());
33 | assertEquals("e", list.removeFirst());
34 | assertEquals("f", list.removeFirst());
35 | assertEquals("g", list.removeFirst());
36 |
37 | assertTrue(list.isEmpty());
38 |
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/test/com/mu/zipper/examples/zipstar/ZipStarTest.java:
--------------------------------------------------------------------------------
1 | package com.mu.zipper.examples.zipstar;
2 |
3 | import java.util.Iterator;
4 |
5 | import junit.framework.TestCase;
6 |
7 | import org.junit.Test;
8 |
9 | public class ZipStarTest extends TestCase {
10 |
11 | @Test
12 | public void testGraph() {
13 | // Simple graph
14 | Graph graph = new Graph();
15 | Node n1 = graph.newNode("n1", 0, 0);
16 | Node n2 = graph.newNode("n2", 3, 4);
17 | Node n3 = graph.newNode("n3", 6, 8);
18 |
19 | // Test distance
20 | assertEquals(5.0, n1.directDistanceTo(n2));
21 | assertEquals(5.0, n2.directDistanceTo(n1));
22 | assertEquals(10.0, n1.directDistanceTo(n3));
23 | assertEquals(10.0, n3.directDistanceTo(n1));
24 | assertEquals(5.0, n2.directDistanceTo(n3));
25 | assertEquals(5.0, n3.directDistanceTo(n2));
26 | assertEquals(0.0, n1.directDistanceTo(n1));
27 | assertEquals(0.0, n2.directDistanceTo(n2));
28 |
29 | // Test edges
30 | graph.connect(n1, n2, 10);
31 | assertEquals(2, graph.getEdges().size());
32 | assertEquals(1, n1.adjacentNodes().size());
33 | assertEquals(n2, n1.adjacentNodes().iterator().next());
34 |
35 | graph.disconnect(n1, n2);
36 | assertEquals(0, graph.getEdges().size());
37 | assertEquals(1, n1.adjacentNodes().size());
38 | assertEquals(0, n2.adjacentNodes().size());
39 | assertEquals(n2, n1.adjacentNodes().iterator().next());
40 |
41 | graph.direct(n1, n2, 10);
42 | assertEquals(1, graph.getEdges().size());
43 |
44 | graph.disconnect(n1, n2);
45 | assertEquals(0, graph.getEdges().size());
46 | }
47 |
48 | @Test
49 | public void testSimpleGraphPathSearch() {
50 | Graph graph = GraphFactory.simpleTestGraph();
51 | Node start = graph.getNodes().get(0);
52 | Node end = graph.getNodes().get(4);
53 |
54 | Path path = ZipStar.calcPath(graph, start, end);
55 |
56 | assertEquals(6.0, path.getDistance());
57 | Iterator i = path.getPath().iterator();
58 | assertEquals("n1", i.next().getName());
59 | assertEquals("n4", i.next().getName());
60 | assertEquals("n5", i.next().getName());
61 | assertFalse(i.hasNext());
62 |
63 | // Debug output
64 | System.out.println("---- Simple graph ----");
65 | System.out.println("Distance: " + path.getDistance());
66 | for (Node n : path.getPath()) {
67 | System.out.println(" " + n.getName());
68 | }
69 |
70 | }
71 |
72 | @Test
73 | public void testTwoRoomsPathSearch() {
74 | Graph graph = GraphFactory.twoRoom(6);
75 | Node start = graph.getNodes().get(0);
76 | Node end = graph.getNodes().get(graph.getNodes().size() - 6);
77 |
78 | Path path = ZipStar.calcPath(graph, start, end);
79 |
80 | assertEquals(11.0, path.getDistance());
81 | assertEquals(12, path.getPath().size());
82 |
83 | // Debug output
84 | System.out.println("---- Two rooms graph ----");
85 | System.out.println("Distance: " + path.getDistance());
86 | for (Node n : path.getPath()) {
87 | System.out.println(" " + n.getName());
88 | }
89 |
90 | }
91 | }
92 |
--------------------------------------------------------------------------------