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 }