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 }