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