001    package ps3.graph;
002    
003    import java.util.*;
004    
005    /**
006     * A NodeCountingPath characterizes a path of WeightedNodes.  The cost
007     * for a path is the number of WeightedNodes it contains; the cost of the
008     * WeightedNodes are ignored.<p>
009     *
010     * A NodeCountingPath is immutable. A new NodeCountingPath is returned through
011     * the extend path operation. <p>
012     *
013     * <p>The main purpose of this class is to illustrate that there can be multiple
014     * implementations of Paths of WeightedNodes.</p>
015     *
016     * Specfields inherited from Path:
017     *
018     * @specfield  cost :     double       // cost of traversing this path.
019     * @specfield  elements : sequence     // sequence of nodes in this path.
020     * 
021     **/
022    
023    public class NodeCountingPath implements Path<WeightedNode, NodeCountingPath> {
024    
025        // The representation invariant describes what invariants hold for
026        // the concrete representation. 
027        // 
028        // RepInvariant:
029        //  RI(c) = 
030        //   (c.node != null) &&
031        //   (c.path == null) ==> (c.cost == 1) &&
032        //   (c.path != null) ==> (c.cost == 1 + c.path.cost)
033        //
034    
035        //
036        // Abstraction Function:
037        //
038        //  The abstract state is given in terms of the specfields of the
039        //  Path interface, namely the cost and elements of a path. 
040        //
041        //  The AF uses two helper functions, which map a concrete state
042        //  'c' to the abstract state.
043        //
044        //  AF(c) = < ncpcost(c), ncpelms(c) > 
045        //  (Maps c to ncp cost and ncp elements abstract fields recursively)
046        //
047        //    ncpcost(c) = c.cost
048        //    ncpelms(c) = / [c.node]                    if (c.path == null)
049        //                  \ ncpelms(c.path) + [c.node] if (c.path != null)
050        //
051        //  (Note that [c.node] appears at the *end* not the *start* of the
052        //   path sequence.)
053        //  
054        //  To make the AF(c) clearer, we could also write the following:
055        //  AF(c) = < cost, elements >  where
056        //   cost     = c.cost
057        //   elements = [c.node]                     if c.path == null 
058        //              [c.node] + c.path.elements   if c.path != null
059        //  
060    
061        /** The WeightedNode at the end of the path. */
062        private final WeightedNode node;
063        /** A NodeCountingPath which, when extended with 'node' at the end,
064         *  is equal to this.  May be null iff this path has only 1 element. */
065        private final NodeCountingPath path;
066        /** The cost of this NodeCountingPath (that is, its length). */
067        private final int cost;
068      
069    
070        /**
071         * Constructs a NodeCountingPath containing one node.
072         * 
073         * @requires node != null
074         * @effects Creates a new NodeCountingPath which originates at
075         * <code>node</code>.
076         **/
077        public NodeCountingPath(WeightedNode node) {
078            this(node, null);
079        }
080    
081        /**
082         * @requires node != null
083         * @effects Creates a new NodeCountingPath 'res' such that
084         * res.elements = path.elements + [ node ]
085         **/
086        private NodeCountingPath(WeightedNode node, NodeCountingPath path) {
087            if (node == null) {
088                throw new IllegalArgumentException();
089            }
090            this.node = node;
091            this.path = path;
092            if (path != null) {
093                this.cost = 1 + path.cost;
094            } else {
095                this.cost = 1;
096            }
097        }
098    
099    
100    
101        // Specified by Path interface.
102        public NodeCountingPath extend(WeightedNode node) {
103            return new NodeCountingPath(node, this);
104        }
105    
106        // Specified by Path interface.
107        public double cost() {
108            return cost;
109        }
110    
111        // Specified by Path interface (which extends Iterable)
112        public Iterator<WeightedNode> iterator() {
113            // reverse the linked list, so that elements are returned in order
114            // from start to end of the path.
115            List<WeightedNode> accumulator = new LinkedList<WeightedNode>();
116            for (NodeCountingPath cur = this; cur!=null; cur = cur.path) {
117                accumulator.add(0, cur.end());
118            }
119            return accumulator.iterator();
120        }
121    
122        /**
123         * @return a string representation of this. 
124         **/
125        public String toString() {
126            StringBuilder sb = new StringBuilder();
127            sb.append("[NodeCountingPath: ");
128            boolean first=true;
129            for (WeightedNode wn : this) {
130                if (first) first = false;
131                else sb.append(", ");
132                sb.append(wn);
133            }
134            sb.append("]");
135            return sb.toString();
136        }
137    
138        /**
139         * @return true iff o is a NodeCountingPath and o.elements is the
140         * same sequence as this.elements
141         **/
142        @Override
143        public boolean equals(Object o) {
144            if (!(o instanceof NodeCountingPath))
145                return false;
146            return this.equals((NodeCountingPath) o);
147        }
148    
149        /**
150         * @return true iff wnp.elements is the same sequence as this.elements
151         **/
152        public boolean equals(NodeCountingPath wnp) {
153            return (wnp != null) &&
154                this.node.equals(wnp.node) && 
155                (this.path == null ? wnp.path==null : this.path.equals(wnp.path));
156        }
157    
158        /**
159         * @return a valid hashcode for this. 
160         **/
161        public int hashCode() {
162            return node.hashCode() + (this.path==null ? 0 : 13 * path.hashCode());
163        }
164    
165    
166        /**
167         * Compares the cost of this path to the given path.
168         * @return the value 0 if the cost of this path is equal to the
169         *         cost of the given path; a value less than 0 if this.cost is
170         *         less than p.cost; and a value greater than 0 if this.cost
171         *         is greater than p.cost.
172         * @see java.lang.Comparable#compareTo
173         */
174        public int compareTo(Path<?,?> p) {
175            return Double.compare(this.cost(), p.cost());
176        }
177        
178        /**
179         * Return the end of this path
180         */
181        public WeightedNode end() {
182            return node;
183        }
184    }