├── .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 toto 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 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 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 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 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 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 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 | --------------------------------------------------------------------------------