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.common.util.HashCode;
036    import org.jboss.dna.graph.GraphI18n;
037    import org.jboss.dna.graph.Location;
038    import org.jboss.dna.graph.connector.RepositoryConnection;
039    import org.jboss.dna.graph.property.Name;
040    import org.jboss.dna.graph.property.Path;
041    import org.jboss.dna.graph.property.Property;
042    
043    /**
044     * Instruction to read the properties and children of the nodes in the branch at the supplied location. The children of the nodes
045     * at the bottom of the branch are not read.
046     * 
047     * @author Randall Hauch
048     */
049    @NotThreadSafe
050    public class ReadBranchRequest extends CacheableRequest implements Iterable<Location> {
051    
052        private static final long serialVersionUID = 1L;
053    
054        public static final int DEFAULT_MAXIMUM_DEPTH = 2;
055        public static final int NO_MAXIMUM_DEPTH = Integer.MAX_VALUE;
056    
057        private static class Node {
058            private final Location location;
059            private final Map<Name, Property> properties = new HashMap<Name, Property>();
060            private List<Location> children;
061    
062            protected Node( Location location ) {
063                assert location != null;
064                this.location = location;
065            }
066    
067            protected Location getLocation() {
068                return location;
069            }
070    
071            protected Map<Name, Property> getProperties() {
072                return properties;
073            }
074    
075            protected List<Location> getChildren() {
076                return children;
077            }
078    
079            protected void setChildren( List<Location> children ) {
080                this.children = children;
081            }
082        }
083    
084        private final Location at;
085        private final String workspaceName;
086        private final int maxDepth;
087        private final Map<Path, Node> nodes = new HashMap<Path, Node>();
088        private Location actualLocation;
089    
090        /**
091         * Create a request to read the branch at the supplied location, to a maximum depth of 2.
092         * 
093         * @param at the location of the branch
094         * @param workspaceName the name of the workspace containing the parent
095         * @throws IllegalArgumentException if the location or workspace name is null
096         */
097        public ReadBranchRequest( Location at,
098                                  String workspaceName ) {
099            CheckArg.isNotNull(at, "at");
100            CheckArg.isNotNull(workspaceName, "workspaceName");
101            this.workspaceName = workspaceName;
102            this.at = at;
103            this.maxDepth = DEFAULT_MAXIMUM_DEPTH;
104        }
105    
106        /**
107         * Create a request to read the branch (of given depth) at the supplied location.
108         * 
109         * @param at the location of the branch
110         * @param workspaceName the name of the workspace containing the branch
111         * @param maxDepth the maximum depth to read
112         * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
113         */
114        public ReadBranchRequest( Location at,
115                                  String workspaceName,
116                                  int maxDepth ) {
117            CheckArg.isNotNull(at, "at");
118            CheckArg.isPositive(maxDepth, "maxDepth");
119            CheckArg.isNotNull(workspaceName, "workspaceName");
120            this.workspaceName = workspaceName;
121            this.at = at;
122            this.maxDepth = maxDepth;
123        }
124    
125        /**
126         * {@inheritDoc}
127         * 
128         * @see org.jboss.dna.graph.request.Request#isReadOnly()
129         */
130        @Override
131        public boolean isReadOnly() {
132            return true;
133        }
134    
135        /**
136         * Get the location defining the top of the branch to be read
137         * 
138         * @return the location of the branch; never null
139         */
140        public Location at() {
141            return at;
142        }
143    
144        /**
145         * Get the name of the workspace in which the branch exists.
146         * 
147         * @return the name of the workspace; never null
148         */
149        public String inWorkspace() {
150            return workspaceName;
151        }
152    
153        /**
154         * Get the maximum depth of the branch that is to be read.
155         * 
156         * @return the maximum depth; always positive
157         */
158        public int maximumDepth() {
159            return maxDepth;
160        }
161    
162        /**
163         * Return whether this branch contains the specified location.
164         * 
165         * @param location the location
166         * @return true if this branch includes the location, or false otherwise
167         */
168        public boolean includes( Location location ) {
169            if (location == null || !location.hasPath()) return false;
170            return this.nodes.containsKey(location.getPath());
171        }
172    
173        /**
174         * Return whether this branch contains the specified path.
175         * 
176         * @param path the path
177         * @return true if this branch includes the path, or false otherwise
178         */
179        public boolean includes( Path path ) {
180            if (path == null) return false;
181            return this.nodes.containsKey(path);
182        }
183    
184        /**
185         * Get the location for the supplied path.
186         * 
187         * @param path the path
188         * @return the location for the path, or null if the path is not known
189         */
190        public Location getLocationFor( Path path ) {
191            Node node = nodes.get(path);
192            return node != null ? node.getLocation() : null;
193        }
194    
195        /**
196         * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
197         * indeed on the branch and that it is at a level prescribed by the request.
198         * 
199         * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
200         * @param properties the properties on the node
201         * @throws IllegalArgumentException if the node is null
202         * @throws IllegalStateException if the request is frozen
203         */
204        public void setProperties( Location node,
205                                   Property... properties ) {
206            checkNotFrozen();
207            CheckArg.isNotNull(node, "node");
208            assert node.hasPath();
209            Node nodeObj = nodes.get(node.getPath());
210            if (nodeObj == null) {
211                nodeObj = new Node(node);
212                nodes.put(node.getPath(), nodeObj);
213            }
214            Map<Name, Property> propertiesMap = nodeObj.getProperties();
215            for (Property property : properties) {
216                propertiesMap.put(property.getName(), property);
217            }
218        }
219    
220        /**
221         * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
222         * indeed on the branch and that it is at a level prescribed by the request.
223         * 
224         * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
225         * @param properties the properties on the node
226         * @throws IllegalArgumentException if the node is null
227         * @throws IllegalStateException if the request is frozen
228         */
229        public void setProperties( Location node,
230                                   Iterable<Property> properties ) {
231            checkNotFrozen();
232            CheckArg.isNotNull(node, "node");
233            assert node.hasPath();
234            Node nodeObj = nodes.get(node.getPath());
235            if (nodeObj == null) {
236                nodeObj = new Node(node);
237                nodes.put(node.getPath(), nodeObj);
238            }
239            Map<Name, Property> propertiesMap = nodeObj.getProperties();
240            for (Property property : properties) {
241                propertiesMap.put(property.getName(), property);
242            }
243        }
244    
245        /**
246         * Record the children for a parent node in the branch.
247         * 
248         * @param parent the location of the parent; must {@link Location#hasPath() have a path}
249         * @param children the location of each child, in the order they appear in the parent
250         * @throws IllegalStateException if the request is frozen
251         */
252        public void setChildren( Location parent,
253                                 Location... children ) {
254            checkNotFrozen();
255            CheckArg.isNotNull(parent, "parent");
256            CheckArg.isNotNull(children, "children");
257            assert parent.hasPath();
258            Node nodeObj = nodes.get(parent.getPath());
259            if (nodeObj == null) {
260                nodeObj = new Node(parent);
261                nodes.put(parent.getPath(), nodeObj);
262            }
263            nodeObj.setChildren(Arrays.asList(children));
264        }
265    
266        /**
267         * Record the children for a parent node in the branch.
268         * 
269         * @param parent the location of the parent; must {@link Location#hasPath() have a path}
270         * @param children the location of each child, in the order they appear in the parent
271         * @throws IllegalStateException if the request is frozen
272         */
273        public void setChildren( Location parent,
274                                 List<Location> children ) {
275            checkNotFrozen();
276            CheckArg.isNotNull(parent, "parent");
277            CheckArg.isNotNull(children, "children");
278            assert parent.hasPath();
279            Node nodeObj = nodes.get(parent.getPath());
280            if (nodeObj == null) {
281                nodeObj = new Node(parent);
282                nodes.put(parent.getPath(), nodeObj);
283            }
284            nodeObj.setChildren(children);
285        }
286    
287        // /**
288        // * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map
289        // maintains
290        // * the order that the nodes were {@link #setProperties(Location, Property...) added}.
291        // *
292        // * @return the branch information
293        // * @see #iterator()
294        // */
295        // public Map<Path, Map<Name, Property>> getPropertiesByNode() {
296        // return nodeProperties;
297        // }
298    
299        /**
300         * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map maintains
301         * the order that the nodes were {@link #setProperties(Location, Property...) added}.
302         * 
303         * @param location the location of the node for which the properties are to be obtained
304         * @return the properties for the location, as a map keyed by the property name, or null if there is no such location
305         * @see #iterator()
306         */
307        public Map<Name, Property> getPropertiesFor( Location location ) {
308            if (location == null || !location.hasPath()) return null;
309            Node node = nodes.get(location.getPath());
310            return node != null ? node.getProperties() : null;
311        }
312    
313        /**
314         * Get the children of the node at the supplied location.
315         * 
316         * @param parent the location of the parent
317         * @return the children, or null if there are no children (or if the parent has not been read)
318         */
319        public List<Location> getChildren( Location parent ) {
320            if (parent == null || !parent.hasPath()) return null;
321            Node node = nodes.get(parent.getPath());
322            return node != null ? node.getChildren() : null;
323        }
324    
325        /**
326         * {@inheritDoc}
327         * <p>
328         * The resulting iterator accesses the {@link Location} objects in the branch, in pre-order traversal order.
329         * </p>
330         * 
331         * @see java.lang.Iterable#iterator()
332         */
333        public Iterator<Location> iterator() {
334            final LinkedList<Location> queue = new LinkedList<Location>();
335            if (getActualLocationOfNode() != null) {
336                Location actual = getActualLocationOfNode();
337                if (actual != null) queue.addFirst(getActualLocationOfNode());
338            }
339            return new Iterator<Location>() {
340                public boolean hasNext() {
341                    return queue.peek() != null;
342                }
343    
344                public Location next() {
345                    // Add the children of the next node to the queue ...
346                    Location next = queue.poll();
347                    if (next == null) throw new NoSuchElementException();
348                    List<Location> children = getChildren(next);
349                    if (children != null && children.size() > 0) {
350                        // We should only add the children if they are nodes in the branch, so check the first one...
351                        Location firstChild = children.get(0);
352                        if (includes(firstChild)) queue.addAll(0, children);
353                    }
354                    return next;
355                }
356    
357                public void remove() {
358                    throw new UnsupportedOperationException();
359                }
360            };
361        }
362    
363        /**
364         * Sets the actual and complete location of the node being read. This method must be called when processing the request, and
365         * the actual location must have a {@link Location#getPath() path}.
366         * 
367         * @param actual the actual location of the node being read, or null if the {@link #at() current location} should be used
368         * @throws IllegalArgumentException if the actual location does not represent the {@link Location#isSame(Location) same
369         *         location} as the {@link #at() current location}, or if the actual location does not have a path.
370         */
371        public void setActualLocationOfNode( Location actual ) {
372            if (!at.isSame(actual)) { // not same if actual is null
373                throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actual, at));
374            }
375            assert actual != null;
376            if (!actual.hasPath()) {
377                throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
378            }
379            this.actualLocation = actual;
380        }
381    
382        /**
383         * Get the actual location of the node that was read.
384         * 
385         * @return the actual location, or null if the actual location was not set
386         */
387        public Location getActualLocationOfNode() {
388            return actualLocation;
389        }
390    
391        /**
392         * {@inheritDoc}
393         * 
394         * @see org.jboss.dna.graph.request.Request#cancel()
395         */
396        @Override
397        public void cancel() {
398            super.cancel();
399            this.actualLocation = null;
400            this.nodes.clear();
401        }
402    
403        /**
404         * {@inheritDoc}
405         * 
406         * @see java.lang.Object#hashCode()
407         */
408        @Override
409        public int hashCode() {
410            return HashCode.compute(at, workspaceName);
411        }
412    
413        /**
414         * {@inheritDoc}
415         * 
416         * @see java.lang.Object#equals(java.lang.Object)
417         */
418        @Override
419        public boolean equals( Object obj ) {
420            if (obj == this) return true;
421            if (this.getClass().isInstance(obj)) {
422                ReadBranchRequest that = (ReadBranchRequest)obj;
423                if (!this.at().equals(that.at())) return false;
424                if (this.maximumDepth() != that.maximumDepth()) return false;
425                if (!this.inWorkspace().equals(that.inWorkspace())) return false;
426                return true;
427            }
428            return false;
429        }
430    
431        /**
432         * {@inheritDoc}
433         * 
434         * @see java.lang.Object#toString()
435         */
436        @Override
437        public String toString() {
438            return "read branch " + at() + " in the \"" + workspaceName + "\" workspace to depth " + maximumDepth();
439        }
440    }