1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.graph.request;
25
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.NoSuchElementException;
33 import net.jcip.annotations.NotThreadSafe;
34 import org.modeshape.common.util.CheckArg;
35 import org.modeshape.common.util.HashCode;
36 import org.modeshape.graph.GraphI18n;
37 import org.modeshape.graph.Location;
38 import org.modeshape.graph.connector.RepositoryConnection;
39 import org.modeshape.graph.property.Name;
40 import org.modeshape.graph.property.Path;
41 import org.modeshape.graph.property.Property;
42
43 /**
44 * Instruction to read the properties and children of the nodes in the branch at the supplied location. The children of the nodes
45 * at the bottom of the branch are not read.
46 */
47 @NotThreadSafe
48 public class ReadBranchRequest extends CacheableRequest implements Iterable<Location> {
49
50 private static final long serialVersionUID = 1L;
51
52 public static final int DEFAULT_MAXIMUM_DEPTH = 2;
53 public static final int NO_MAXIMUM_DEPTH = Integer.MAX_VALUE;
54
55 private static class Node {
56 private final Location location;
57 private final Map<Name, Property> properties = new HashMap<Name, Property>();
58 private List<Location> children;
59
60 protected Node( Location location ) {
61 assert location != null;
62 this.location = location;
63 }
64
65 protected Location getLocation() {
66 return location;
67 }
68
69 protected Map<Name, Property> getProperties() {
70 return properties;
71 }
72
73 protected List<Location> getChildren() {
74 return children;
75 }
76
77 protected void setChildren( List<Location> children ) {
78 this.children = children;
79 }
80 }
81
82 private final Location at;
83 private final String workspaceName;
84 private final int maxDepth;
85 private final Map<Path, Node> nodes = new HashMap<Path, Node>();
86 private Location actualLocation;
87
88 /**
89 * Create a request to read the branch at the supplied location, to a maximum depth of 2.
90 *
91 * @param at the location of the branch
92 * @param workspaceName the name of the workspace containing the parent
93 * @throws IllegalArgumentException if the location or workspace name is null
94 */
95 public ReadBranchRequest( Location at,
96 String workspaceName ) {
97 CheckArg.isNotNull(at, "at");
98 CheckArg.isNotNull(workspaceName, "workspaceName");
99 this.workspaceName = workspaceName;
100 this.at = at;
101 this.maxDepth = DEFAULT_MAXIMUM_DEPTH;
102 }
103
104 /**
105 * Create a request to read the branch (of given depth) at the supplied location.
106 *
107 * @param at the location of the branch
108 * @param workspaceName the name of the workspace containing the branch
109 * @param maxDepth the maximum depth to read
110 * @throws IllegalArgumentException if the location or workspace name is null or if the maximum depth is not positive
111 */
112 public ReadBranchRequest( Location at,
113 String workspaceName,
114 int maxDepth ) {
115 CheckArg.isNotNull(at, "at");
116 CheckArg.isPositive(maxDepth, "maxDepth");
117 CheckArg.isNotNull(workspaceName, "workspaceName");
118 this.workspaceName = workspaceName;
119 this.at = at;
120 this.maxDepth = maxDepth;
121 }
122
123 /**
124 * {@inheritDoc}
125 *
126 * @see org.modeshape.graph.request.Request#isReadOnly()
127 */
128 @Override
129 public boolean isReadOnly() {
130 return true;
131 }
132
133 /**
134 * Get the location defining the top of the branch to be read
135 *
136 * @return the location of the branch; never null
137 */
138 public Location at() {
139 return at;
140 }
141
142 /**
143 * Get the name of the workspace in which the branch exists.
144 *
145 * @return the name of the workspace; never null
146 */
147 public String inWorkspace() {
148 return workspaceName;
149 }
150
151 /**
152 * Get the maximum depth of the branch that is to be read.
153 *
154 * @return the maximum depth; always positive
155 */
156 public int maximumDepth() {
157 return maxDepth;
158 }
159
160 /**
161 * Return whether this branch contains the specified location.
162 *
163 * @param location the location
164 * @return true if this branch includes the location, or false otherwise
165 */
166 public boolean includes( Location location ) {
167 if (location == null || !location.hasPath()) return false;
168 return this.nodes.containsKey(location.getPath());
169 }
170
171 /**
172 * Return whether this branch contains the specified path.
173 *
174 * @param path the path
175 * @return true if this branch includes the path, or false otherwise
176 */
177 public boolean includes( Path path ) {
178 if (path == null) return false;
179 return this.nodes.containsKey(path);
180 }
181
182 /**
183 * Get the location for the supplied path.
184 *
185 * @param path the path
186 * @return the location for the path, or null if the path is not known
187 */
188 public Location getLocationFor( Path path ) {
189 Node node = nodes.get(path);
190 return node != null ? node.getLocation() : null;
191 }
192
193 /**
194 * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
195 * indeed on the branch and that it is at a level prescribed by the request.
196 *
197 * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
198 * @param properties the properties on the node
199 * @throws IllegalArgumentException if the node is null
200 * @throws IllegalStateException if the request is frozen
201 */
202 public void setProperties( Location node,
203 Property... properties ) {
204 checkNotFrozen();
205 CheckArg.isNotNull(node, "node");
206 assert node.hasPath();
207 Node nodeObj = nodes.get(node.getPath());
208 if (nodeObj == null) {
209 nodeObj = new Node(node);
210 nodes.put(node.getPath(), nodeObj);
211 }
212 Map<Name, Property> propertiesMap = nodeObj.getProperties();
213 for (Property property : properties) {
214 propertiesMap.put(property.getName(), property);
215 }
216 }
217
218 /**
219 * Add a node that was read from the {@link RepositoryConnection}. This method does not verify or check that the node is
220 * indeed on the branch and that it is at a level prescribed by the request.
221 *
222 * @param node the location of the node that appears on this branch; must {@link Location#hasPath() have a path}
223 * @param properties the properties on the node
224 * @throws IllegalArgumentException if the node is null
225 * @throws IllegalStateException if the request is frozen
226 */
227 public void setProperties( Location node,
228 Iterable<Property> properties ) {
229 checkNotFrozen();
230 CheckArg.isNotNull(node, "node");
231 assert node.hasPath();
232 Node nodeObj = nodes.get(node.getPath());
233 if (nodeObj == null) {
234 nodeObj = new Node(node);
235 nodes.put(node.getPath(), nodeObj);
236 }
237 Map<Name, Property> propertiesMap = nodeObj.getProperties();
238 for (Property property : properties) {
239 propertiesMap.put(property.getName(), property);
240 }
241 }
242
243 /**
244 * Record the children for a parent node in the branch.
245 *
246 * @param parent the location of the parent; must {@link Location#hasPath() have a path}
247 * @param children the location of each child, in the order they appear in the parent
248 * @throws IllegalStateException if the request is frozen
249 */
250 public void setChildren( Location parent,
251 Location... children ) {
252 checkNotFrozen();
253 CheckArg.isNotNull(parent, "parent");
254 CheckArg.isNotNull(children, "children");
255 assert parent.hasPath();
256 Node nodeObj = nodes.get(parent.getPath());
257 if (nodeObj == null) {
258 nodeObj = new Node(parent);
259 nodes.put(parent.getPath(), nodeObj);
260 }
261 nodeObj.setChildren(Arrays.asList(children));
262 }
263
264 /**
265 * Record the children for a parent node in the branch.
266 *
267 * @param parent the location of the parent; must {@link Location#hasPath() have a path}
268 * @param children the location of each child, in the order they appear in the parent
269 * @throws IllegalStateException if the request is frozen
270 */
271 public void setChildren( Location parent,
272 List<Location> children ) {
273 checkNotFrozen();
274 CheckArg.isNotNull(parent, "parent");
275 CheckArg.isNotNull(children, "children");
276 assert parent.hasPath();
277 Node nodeObj = nodes.get(parent.getPath());
278 if (nodeObj == null) {
279 nodeObj = new Node(parent);
280 nodes.put(parent.getPath(), nodeObj);
281 }
282 nodeObj.setChildren(children);
283 }
284
285 // /**
286 // * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map
287 // maintains
288 // * the order that the nodes were {@link #setProperties(Location, Property...) added}.
289 // *
290 // * @return the branch information
291 // * @see #iterator()
292 // */
293 // public Map<Path, Map<Name, Property>> getPropertiesByNode() {
294 // return nodeProperties;
295 // }
296
297 /**
298 * Get the nodes that make up this branch. If this map is empty, the branch has not yet been read. The resulting map maintains
299 * the order that the nodes were {@link #setProperties(Location, Property...) added}.
300 *
301 * @param location the location of the node for which the properties are to be obtained
302 * @return the properties for the location, as a map keyed by the property name, or null if there is no such location
303 * @see #iterator()
304 */
305 public Map<Name, Property> getPropertiesFor( Location location ) {
306 if (location == null || !location.hasPath()) return null;
307 Node node = nodes.get(location.getPath());
308 return node != null ? node.getProperties() : null;
309 }
310
311 /**
312 * Get the children of the node at the supplied location.
313 *
314 * @param parent the location of the parent
315 * @return the children, or null if there are no children (or if the parent has not been read)
316 */
317 public List<Location> getChildren( Location parent ) {
318 if (parent == null || !parent.hasPath()) return null;
319 Node node = nodes.get(parent.getPath());
320 return node != null ? node.getChildren() : null;
321 }
322
323 /**
324 * {@inheritDoc}
325 * <p>
326 * The resulting iterator accesses the {@link Location} objects in the branch, in pre-order traversal order.
327 * </p>
328 *
329 * @see java.lang.Iterable#iterator()
330 */
331 public Iterator<Location> iterator() {
332 final LinkedList<Location> queue = new LinkedList<Location>();
333 if (getActualLocationOfNode() != null) {
334 Location actual = getActualLocationOfNode();
335 if (actual != null) queue.addFirst(getActualLocationOfNode());
336 }
337 return new Iterator<Location>() {
338 public boolean hasNext() {
339 return queue.peek() != null;
340 }
341
342 public Location next() {
343 // Add the children of the next node to the queue ...
344 Location next = queue.poll();
345 if (next == null) throw new NoSuchElementException();
346 List<Location> children = getChildren(next);
347 if (children != null && children.size() > 0) {
348 // We should only add the children if they are nodes in the branch, so check the first one...
349 Location firstChild = children.get(0);
350 if (includes(firstChild)) queue.addAll(0, children);
351 }
352 return next;
353 }
354
355 public void remove() {
356 throw new UnsupportedOperationException();
357 }
358 };
359 }
360
361 /**
362 * Sets the actual and complete location of the node being read. This method must be called when processing the request, and
363 * the actual location must have a {@link Location#getPath() path}.
364 *
365 * @param actual the actual location of the node being read, or null if the {@link #at() current location} should be used
366 * @throws IllegalArgumentException if the actual location is null or does not have a path
367 */
368 public void setActualLocationOfNode( Location actual ) {
369 checkNotFrozen();
370 CheckArg.isNotNull(actual, "actual");
371 if (!actual.hasPath()) {
372 throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
373 }
374 this.actualLocation = actual;
375 }
376
377 /**
378 * Get the actual location of the node that was read.
379 *
380 * @return the actual location, or null if the actual location was not set
381 */
382 public Location getActualLocationOfNode() {
383 return actualLocation;
384 }
385
386 /**
387 * {@inheritDoc}
388 *
389 * @see org.modeshape.graph.request.Request#cancel()
390 */
391 @Override
392 public void cancel() {
393 super.cancel();
394 this.actualLocation = null;
395 this.nodes.clear();
396 }
397
398 /**
399 * {@inheritDoc}
400 *
401 * @see java.lang.Object#hashCode()
402 */
403 @Override
404 public int hashCode() {
405 return HashCode.compute(at, workspaceName);
406 }
407
408 /**
409 * {@inheritDoc}
410 *
411 * @see java.lang.Object#equals(java.lang.Object)
412 */
413 @Override
414 public boolean equals( Object obj ) {
415 if (obj == this) return true;
416 if (this.getClass().isInstance(obj)) {
417 ReadBranchRequest that = (ReadBranchRequest)obj;
418 if (!this.at().isSame(that.at())) return false;
419 if (this.maximumDepth() != that.maximumDepth()) return false;
420 if (!this.inWorkspace().equals(that.inWorkspace())) return false;
421 return true;
422 }
423 return false;
424 }
425
426 /**
427 * {@inheritDoc}
428 *
429 * @see java.lang.Object#toString()
430 */
431 @Override
432 public String toString() {
433 return "read branch " + at() + " in the \"" + workspaceName + "\" workspace to depth " + maximumDepth();
434 }
435
436 @Override
437 public RequestType getType() {
438 return RequestType.READ_BRANCH;
439 }
440
441 /**
442 * Obtain a copy of this request (without any results) with the new supplied maximum depth.
443 *
444 * @param maxDepth the maximum depth for the new request
445 * @return the copy of thist request, but with the desired maximum depth
446 * @throws IllegalArgumentException if the maximum depth is not positive
447 */
448 public ReadBranchRequest withMaximumDepth( int maxDepth ) {
449 return new ReadBranchRequest(at, workspaceName, maxDepth);
450 }
451 }