001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.request;
025    
026    import java.util.Arrays;
027    import java.util.HashMap;
028    import java.util.Iterator;
029    import java.util.LinkedList;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.NoSuchElementException;
033    import net.jcip.annotations.NotThreadSafe;
034    import org.jboss.dna.common.util.CheckArg;
035    import org.jboss.dna.graph.GraphI18n;
036    import org.jboss.dna.graph.Location;
037    import org.jboss.dna.graph.connector.RepositoryConnection;
038    import org.jboss.dna.graph.property.Name;
039    import org.jboss.dna.graph.property.Path;
040    import org.jboss.dna.graph.property.Property;
041    
042    /**
043     * Instruction to read the properties and children of the nodes in the branch at the supplied location. The children of the nodes
044     * at the bottom of the branch are not read.
045     * 
046     * @author Randall Hauch
047     */
048    @NotThreadSafe
049    public class ReadBranchRequest extends CacheableRequest implements Iterable<Location> {
050    
051        private static final long serialVersionUID = 1L;
052    
053        public static final int DEFAULT_MAXIMUM_DEPTH = 2;
054        public static final int NO_MAXIMUM_DEPTH = Integer.MAX_VALUE;
055    
056        private static class Node {
057            private final Location location;
058            private final Map<Name, Property> properties = new HashMap<Name, Property>();
059            private List<Location> children;
060    
061            protected Node( Location location ) {
062                assert location != null;
063                this.location = location;
064            }
065    
066            protected Location getLocation() {
067                return location;
068            }
069    
070            protected Map<Name, Property> getProperties() {
071                return properties;
072            }
073    
074            protected List<Location> getChildren() {
075                return children;
076            }
077    
078            protected void setChildren( List<Location> children ) {
079                this.children = children;
080            }
081        }
082    
083        private final Location at;
084        private final String workspaceName;
085        private final int maxDepth;
086        private final Map<Path, Node> nodes = new HashMap<Path, Node>();
087        private Location actualLocation;
088    
089        /**
090         * Create a request to read the branch at the supplied location, to a maximum depth of 2.
091         * 
092         * @param at the location of the branch
093         * @param workspaceName the name of the workspace containing the parent
094         * @throws IllegalArgumentException if the location or workspace name is null
095         */
096        public ReadBranchRequest( Location at,
097                                  String workspaceName ) {
098            CheckArg.isNotNull(at, "at");
099            CheckArg.isNotNull(workspaceName, "workspaceName");
100            this.workspaceName = workspaceName;
101            this.at = at;
102            this.maxDepth = DEFAULT_MAXIMUM_DEPTH;
103        }
104    
105        /**
106         * Create a request to read the branch (of given depth) at the supplied location.
107         * 
108         * @param at the location of the branch
109         * @param workspaceName the name of the workspace containing the branch
110         * @param maxDepth the maximum depth to read
111         * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
112         */
113        public ReadBranchRequest( Location at,
114                                  String workspaceName,
115                                  int maxDepth ) {
116            CheckArg.isNotNull(at, "at");
117            CheckArg.isPositive(maxDepth, "maxDepth");
118            CheckArg.isNotNull(workspaceName, "workspaceName");
119            this.workspaceName = workspaceName;
120            this.at = at;
121            this.maxDepth = maxDepth;
122        }
123    
124        /**
125         * {@inheritDoc}
126         * 
127         * @see org.jboss.dna.graph.request.Request#isReadOnly()
128         */
129        @Override
130        public boolean isReadOnly() {
131            return true;
132        }
133    
134        /**
135         * Get the location defining the top of the branch to be read
136         * 
137         * @return the location of the branch; never null
138         */
139        public Location at() {
140            return at;
141        }
142    
143        /**
144         * Get the name of the workspace in which the branch exists.
145         * 
146         * @return the name of the workspace; never null
147         */
148        public String inWorkspace() {
149            return workspaceName;
150        }
151    
152        /**
153         * Get the maximum depth of the branch that is to be read.
154         * 
155         * @return the maximum depth; always positive
156         */
157        public int maximumDepth() {
158            return maxDepth;
159        }
160    
161        /**
162         * Return whether this branch contains the specified location.
163         * 
164         * @param location the location
165         * @return true if this branch includes the location, or false otherwise
166         */
167        public boolean includes( Location location ) {
168            if (location == null || !location.hasPath()) return false;
169            return this.nodes.containsKey(location.getPath());
170        }
171    
172        /**
173         * Return whether this branch contains the specified path.
174         * 
175         * @param path the path
176         * @return true if this branch includes the path, or false otherwise
177         */
178        public boolean includes( Path path ) {
179            if (path == null) return false;
180            return this.nodes.containsKey(path);
181        }
182    
183        /**
184         * Get the location for the supplied path.
185         * 
186         * @param path the path
187         * @return the location for the path, or null if the path is not known
188         */
189        public Location getLocationFor( Path path ) {
190            Node node = nodes.get(path);
191            return node != null ? node.getLocation() : null;
192        }
193    
194        /**
195         * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
196         * indeed on the branch and that it is at a level prescribed by the request.
197         * 
198         * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
199         * @param properties the properties on the node
200         * @throws IllegalArgumentException if the node is null
201         */
202        public void setProperties( Location node,
203                                   Property... properties ) {
204            CheckArg.isNotNull(node, "node");
205            assert node.hasPath();
206            Node nodeObj = nodes.get(node.getPath());
207            if (nodeObj == null) {
208                nodeObj = new Node(node);
209                nodes.put(node.getPath(), nodeObj);
210            }
211            Map<Name, Property> propertiesMap = nodeObj.getProperties();
212            for (Property property : properties) {
213                propertiesMap.put(property.getName(), property);
214            }
215        }
216    
217        /**
218         * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
219         * indeed on the branch and that it is at a level prescribed by the request.
220         * 
221         * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
222         * @param properties the properties on the node
223         * @throws IllegalArgumentException if the node is null
224         */
225        public void setProperties( Location node,
226                                   Iterable<Property> properties ) {
227            CheckArg.isNotNull(node, "node");
228            assert node.hasPath();
229            Node nodeObj = nodes.get(node.getPath());
230            if (nodeObj == null) {
231                nodeObj = new Node(node);
232                nodes.put(node.getPath(), nodeObj);
233            }
234            Map<Name, Property> propertiesMap = nodeObj.getProperties();
235            for (Property property : properties) {
236                propertiesMap.put(property.getName(), property);
237            }
238        }
239    
240        /**
241         * Record the children for a parent node in the branch.
242         * 
243         * @param parent the location of the parent; must {@link Location#hasPath() have a path}
244         * @param children the location of each child, in the order they appear in the parent
245         */
246        public void setChildren( Location parent,
247                                 Location... children ) {
248            CheckArg.isNotNull(parent, "parent");
249            CheckArg.isNotNull(children, "children");
250            assert parent.hasPath();
251            Node nodeObj = nodes.get(parent.getPath());
252            if (nodeObj == null) {
253                nodeObj = new Node(parent);
254                nodes.put(parent.getPath(), nodeObj);
255            }
256            nodeObj.setChildren(Arrays.asList(children));
257        }
258    
259        /**
260         * Record the children for a parent node in the branch.
261         * 
262         * @param parent the location of the parent; must {@link Location#hasPath() have a path}
263         * @param children the location of each child, in the order they appear in the parent
264         */
265        public void setChildren( Location parent,
266                                 List<Location> children ) {
267            CheckArg.isNotNull(parent, "parent");
268            CheckArg.isNotNull(children, "children");
269            assert parent.hasPath();
270            Node nodeObj = nodes.get(parent.getPath());
271            if (nodeObj == null) {
272                nodeObj = new Node(parent);
273                nodes.put(parent.getPath(), nodeObj);
274            }
275            nodeObj.setChildren(children);
276        }
277    
278        // /**
279        // * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map
280        // maintains
281        // * the order that the nodes were {@link #setProperties(Location, Property...) added}.
282        // *
283        // * @return the branch information
284        // * @see #iterator()
285        // */
286        // public Map<Path, Map<Name, Property>> getPropertiesByNode() {
287        // return nodeProperties;
288        // }
289    
290        /**
291         * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map maintains
292         * the order that the nodes were {@link #setProperties(Location, Property...) added}.
293         * 
294         * @param location the location of the node for which the properties are to be obtained
295         * @return the properties for the location, as a map keyed by the property name, or null if there is no such location
296         * @see #iterator()
297         */
298        public Map<Name, Property> getPropertiesFor( Location location ) {
299            if (location == null || !location.hasPath()) return null;
300            Node node = nodes.get(location.getPath());
301            return node != null ? node.getProperties() : null;
302        }
303    
304        /**
305         * Get the children of the node at the supplied location.
306         * 
307         * @param parent the location of the parent
308         * @return the children, or null if there are no children (or if the parent has not been read)
309         */
310        public List<Location> getChildren( Location parent ) {
311            if (parent == null || !parent.hasPath()) return null;
312            Node node = nodes.get(parent.getPath());
313            return node != null ? node.getChildren() : null;
314        }
315    
316        /**
317         * {@inheritDoc}
318         * <p>
319         * The resulting iterator accesses the {@link Location} objects in the branch, in pre-order traversal order.
320         * </p>
321         * 
322         * @see java.lang.Iterable#iterator()
323         */
324        public Iterator<Location> iterator() {
325            final LinkedList<Location> queue = new LinkedList<Location>();
326            if (getActualLocationOfNode() != null) {
327                Location actual = getActualLocationOfNode();
328                if (actual != null) queue.addFirst(getActualLocationOfNode());
329            }
330            return new Iterator<Location>() {
331                public boolean hasNext() {
332                    return queue.peek() != null;
333                }
334    
335                public Location next() {
336                    // Add the children of the next node to the queue ...
337                    Location next = queue.poll();
338                    if (next == null) throw new NoSuchElementException();
339                    List<Location> children = getChildren(next);
340                    if (children != null && children.size() > 0) queue.addAll(0, children);
341                    return next;
342                }
343    
344                public void remove() {
345                    throw new UnsupportedOperationException();
346                }
347            };
348        }
349    
350        /**
351         * Sets the actual and complete location of the node being read. This method must be called when processing the request, and
352         * the actual location must have a {@link Location#getPath() path}.
353         * 
354         * @param actual the actual location of the node being read, or null if the {@link #at() current location} should be used
355         * @throws IllegalArgumentException if the actual location does not represent the {@link Location#isSame(Location) same
356         *         location} as the {@link #at() current location}, or if the actual location does not have a path.
357         */
358        public void setActualLocationOfNode( Location actual ) {
359            if (!at.isSame(actual)) { // not same if actual is null
360                throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actual, at));
361            }
362            assert actual != null;
363            if (!actual.hasPath()) {
364                throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
365            }
366            this.actualLocation = actual;
367        }
368    
369        /**
370         * Get the actual location of the node that was read.
371         * 
372         * @return the actual location, or null if the actual location was not set
373         */
374        public Location getActualLocationOfNode() {
375            return actualLocation;
376        }
377    
378        /**
379         * {@inheritDoc}
380         * 
381         * @see java.lang.Object#equals(java.lang.Object)
382         */
383        @Override
384        public boolean equals( Object obj ) {
385            if (obj == this) return true;
386            if (this.getClass().isInstance(obj)) {
387                ReadBranchRequest that = (ReadBranchRequest)obj;
388                if (!this.at().equals(that.at())) return false;
389                if (this.maximumDepth() != that.maximumDepth()) return false;
390                if (!this.inWorkspace().equals(that.inWorkspace())) return false;
391                return true;
392            }
393            return false;
394        }
395    
396        /**
397         * {@inheritDoc}
398         * 
399         * @see java.lang.Object#toString()
400         */
401        @Override
402        public String toString() {
403            return "read branch " + at() + " in the \"" + workspaceName + "\" workspace to depth " + maximumDepth();
404        }
405    }