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.property; 25 26 import java.io.Serializable; 27 import java.util.Iterator; 28 import java.util.List; 29 import net.jcip.annotations.Immutable; 30 import org.modeshape.common.text.Jsr283Encoder; 31 import org.modeshape.common.text.NoOpEncoder; 32 import org.modeshape.common.text.TextDecoder; 33 import org.modeshape.common.text.TextEncoder; 34 import org.modeshape.common.text.UrlEncoder; 35 import org.modeshape.graph.property.basic.BasicName; 36 import org.modeshape.graph.property.basic.BasicPathSegment; 37 38 /** 39 * An object representation of a node path within a repository. 40 * <p> 41 * A path consists of zero or more segments that can contain any characters, although the string representation may require some 42 * characters to be encoded. For example, if a path contains a segment with a forward slash, then this forward slash must be 43 * escaped when writing the whole path to a string (since a forward slash is used as the {@link #DELIMITER delimiter} between 44 * segments). 45 * </p> 46 * <p> 47 * Because of this encoding and decoding issue, there is no standard representation of a path as a string. Instead, this class 48 * uses {@link TextEncoder text encoders} to escape certain characters when writing to a string or unescaping the string 49 * representation. These encoders and used only with individual segments, and therefore are not used to encode the 50 * {@link #DELIMITER delimiter}. Three standard encoders are provided, although others can certainly be used: 51 * <ul> 52 * <li>{@link #JSR283_ENCODER Jsr283Encoder} - an encoder and decoder that is compliant with <a 53 * href="http://jcp.org/en/jsr/detail?id=283">JSR-283</a> by converting the reserved characters (namely '*', '/', ':', '[', ']' 54 * and '|') to their unicode equivalent.</td> 55 * </li> 56 * <li>{@link #URL_ENCODER UrlEncoder} - an encoder and decoder that is useful for converting text to be used within a URL, as 57 * defined by Section 2.3 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. This encoder does encode many characters 58 * (including '`', '@', '#', '$', '^', '&', '{', '[', '}', ']', '|', ':', ';', '\', '"', '<', ',', '>', '?', '/', and ' '), while 59 * others are not encoded (including '-', '_', '.', '!', '~', '*', '\', ''', '(', and ')'). Note that only the '*' character is 60 * the only character reserved by JSR-283 that is not encoded by the URL encoder.</li> 61 * <li>{@link #NO_OP_ENCODER NoOpEncoder} - an {@link TextEncoder encoder} implementation that does nothing.</li> 62 * </ul> 63 * </p> 64 * <p> 65 * This class simplifies working with paths and using a <code>Path</code> is often more efficient that processing and 66 * manipulating the equivalent <code>String</code>. This class can easily {@link #iterator() iterate} over the segments, return 67 * the {@link #size() number of segments}, {@link #compareTo(Path) compare} with other paths, {@link #resolve(Path) resolve} 68 * relative paths, return the {@link #getParent() ancestor (or parent)}, determine whether one path is an 69 * {@link #isAncestorOf(Path) ancestor} or {@link #isDecendantOf(Path) decendent} of another path, and 70 * {@link #getCommonAncestor(Path) finding a common ancestor}. 71 * </p> 72 */ 73 @Immutable 74 public interface Path extends Comparable<Path>, Iterable<Path.Segment>, Serializable, Readable { 75 76 /** 77 * The text encoder that does nothing. 78 */ 79 public static final TextEncoder NO_OP_ENCODER = new NoOpEncoder(); 80 81 /** 82 * The text encoder that encodes according to JSR-283. 83 */ 84 public static final TextEncoder JSR283_ENCODER = new Jsr283Encoder(); 85 86 /** 87 * The text encoder that encodes text according to the rules of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. 88 */ 89 public static final TextEncoder URL_ENCODER = new UrlEncoder().setSlashEncoded(true); 90 91 /** 92 * The text decoder that does nothing. 93 */ 94 public static final TextDecoder NO_OP_DECODER = new NoOpEncoder(); 95 96 /** 97 * The text decoder that decodes according to JSR-283. 98 */ 99 public static final TextDecoder JSR283_DECODER = new Jsr283Encoder(); 100 101 /** 102 * The text decoder that decodes text according to the rules of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. 103 */ 104 public static final TextDecoder URL_DECODER = new UrlEncoder().setSlashEncoded(true); 105 106 /** 107 * The default text encoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283 108 * encoder}. 109 */ 110 public static final TextEncoder DEFAULT_ENCODER = JSR283_ENCODER; 111 112 /** 113 * The default text decoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283 114 * encoder}. 115 */ 116 public static final TextDecoder DEFAULT_DECODER = JSR283_DECODER; 117 118 /** 119 * The delimiter character used to separate segments within a path. 120 */ 121 public static final char DELIMITER = '/'; 122 123 /** 124 * String form of the delimiter used to separate segments within a path. 125 */ 126 public static final String DELIMITER_STR = new String(new char[] {DELIMITER}); 127 128 /** 129 * String representation of the segment that references a parent. 130 */ 131 public static final String PARENT = ".."; 132 133 /** 134 * String representation of the segment that references the same segment. 135 */ 136 public static final String SELF = "."; 137 138 /** 139 * The default index for a {@link Segment}. 140 */ 141 public static final int DEFAULT_INDEX = 1; 142 143 /** 144 * Representation of the segments that occur within a path. 145 * 146 * @author Randall Hauch 147 */ 148 @Immutable 149 public static interface Segment extends Cloneable, Comparable<Segment>, Serializable, Readable { 150 151 /** 152 * Get the name component of this segment. 153 * 154 * @return the segment's name 155 */ 156 public Name getName(); 157 158 /** 159 * Get the index for this segment, which will be 1 by default. 160 * 161 * @return the index 162 */ 163 public int getIndex(); 164 165 /** 166 * Return whether this segment has an index. 167 * 168 * @return true if this segment has an index, or false otherwise. 169 */ 170 public boolean hasIndex(); 171 172 /** 173 * Return whether this segment is a self-reference. 174 * 175 * @return true if the segment is a self-reference, or false otherwise. 176 */ 177 public boolean isSelfReference(); 178 179 /** 180 * Return whether this segment is a reference to a parent. 181 * 182 * @return true if the segment is a parent-reference, or false otherwise. 183 */ 184 public boolean isParentReference(); 185 186 /** 187 * Get the raw string form of the segment using the {@link Path#NO_OP_ENCODER no-op encoder}. This is equivalent to 188 * calling <code>getString(Path.NO_OP_ENCODER)</code>. 189 * 190 * @return the un-encoded string 191 * @see #getString(TextEncoder) 192 */ 193 public String getUnencodedString(); 194 } 195 196 /** 197 * Singleton instance of the name referencing a self, provided as a convenience. 198 */ 199 public static final Name SELF_NAME = new BasicName(null, SELF); 200 201 /** 202 * Singleton instance of the name referencing a parent, provided as a convenience. 203 */ 204 public static final Name PARENT_NAME = new BasicName(null, PARENT); 205 206 /** 207 * Singleton instance of the path segment referencing a parent, provided as a convenience. 208 */ 209 public static final Path.Segment SELF_SEGMENT = new BasicPathSegment(SELF_NAME); 210 211 /** 212 * Singleton instance of the path segment referencing a parent, provided as a convenience. 213 */ 214 public static final Path.Segment PARENT_SEGMENT = new BasicPathSegment(PARENT_NAME); 215 216 /** 217 * Return the number of segments in this path. 218 * 219 * @return the number of path segments 220 */ 221 public int size(); 222 223 /** 224 * Return whether this path represents the root path. 225 * 226 * @return true if this path is the root path, or false otherwise 227 */ 228 public boolean isRoot(); 229 230 /** 231 * Determine whether this path represents the same as the supplied path. This is equivalent to calling <code> 232 * this.compareTo(other) == 0 </code>. 233 * 234 * @param other the other path to compare with this path; may be null 235 * @return true if the paths are equivalent, or false otherwise 236 */ 237 public boolean isSameAs( Path other ); 238 239 /** 240 * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isAncestorOf(Path) ancestor of} the 241 * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || 242 * isAncestor(other)</code>, and is a convenience method that is identical to calling <code>other.isAtOrBelow(this)</code>. 243 * 244 * @param other the other path to compare with this path; may be null 245 * @return true if the paths are equivalent or if this path is considered an ancestor of the other path, or false otherwise 246 */ 247 public boolean isAtOrAbove( Path other ); 248 249 /** 250 * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isDecendantOf(Path) decendant of} the 251 * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || 252 * isAncestor(other)</code>. 253 * 254 * @param other the other path to compare with this path; may be null 255 * @return true if the paths are equivalent or if this path is considered a decendant of the other path, or false otherwise 256 */ 257 public boolean isAtOrBelow( Path other ); 258 259 /** 260 * Determine whether this path is an ancestor of the supplied path. A path is considered an ancestor of another path if the 261 * the ancestor path appears in its entirety at the beginning of the decendant path, and where the decendant path contains at 262 * least one additional segment. 263 * 264 * @param decendant the path that may be the decendant; may be null 265 * @return true if this path is an ancestor of the supplied path, or false otherwise 266 */ 267 public boolean isAncestorOf( Path decendant ); 268 269 /** 270 * Determine whether this path is an decendant of the supplied path. A path is considered a decendant of another path if the 271 * the decendant path starts exactly with the entire ancestor path but contains at least one additional segment. 272 * 273 * @param ancestor the path that may be the ancestor; may be null 274 * @return true if this path is an decendant of the supplied path, or false otherwise 275 */ 276 public boolean isDecendantOf( Path ancestor ); 277 278 /** 279 * Return whether this path is an absolute path. A path is either relative or {@link #isAbsolute() absolute}. An absolute path 280 * starts with a "/". 281 * 282 * @return true if the path is absolute, or false otherwise 283 */ 284 public boolean isAbsolute(); 285 286 /** 287 * Return whether this path is normalized and contains no unnecessary "." segments and as few ".." segments as possible. For 288 * example, the path "../a" is normalized, while "/a/b/c/../d" is not normalized. 289 * 290 * @return true if this path is normalized, or false otherwise 291 */ 292 public boolean isNormalized(); 293 294 /** 295 * Get a normalized path with as many ".." segments and all "." resolved. The relative path ".", however, will return itself 296 * as the normalized path, since it cannot be resolved any further. 297 * 298 * @return the normalized path, or this object if this path is already normalized 299 * @throws InvalidPathException if the normalized form would result in a path with negative length (e.g., "/a/../../..") 300 */ 301 public Path getNormalizedPath(); 302 303 /** 304 * Get the canonical form of this path. A canonical path has is {@link #isAbsolute() absolute} and {@link #isNormalized()}. 305 * 306 * @return the canonical path, or this object if it is already in its canonical form 307 * @throws InvalidPathException if the path is not absolute and cannot be canonicalized 308 */ 309 public Path getCanonicalPath(); 310 311 /** 312 * Obtain a path that is relative to the root node. This is equivalent to calling {@link #relativeTo(Path)} with the root 313 * path. 314 * 315 * @return the relative path from the root node; never null 316 */ 317 public Path relativeToRoot(); 318 319 /** 320 * Get a relative path from the supplied path to this path. 321 * 322 * @param startingPath the path specifying the starting point for the new relative path; may not be null 323 * @return the relative path 324 * @throws IllegalArgumentException if the supplied path is null 325 * @throws PathNotFoundException if both this path and the supplied path are not absolute 326 */ 327 public Path relativeTo( Path startingPath ); 328 329 /** 330 * Get the absolute path by resolving the supplied relative (non-absolute) path against this absolute path. 331 * 332 * @param relativePath the relative path that is to be resolved against this path 333 * @return the absolute and normalized path resolved from this path and the supplied absolute path 334 * @throws IllegalArgumentException if the supplied path is null 335 * @throws InvalidPathException if the this path is not absolute or if the supplied path is not relative. 336 */ 337 public Path resolve( Path relativePath ); 338 339 /** 340 * Get the absolute path by resolving this relative (non-absolute) path against the supplied absolute path. 341 * 342 * @param absolutePath the absolute path to which this relative path should be resolve 343 * @return the absolute path resolved from this path and the supplied absolute path 344 * @throws IllegalArgumentException if the supplied path is null 345 * @throws InvalidPathException if the supplied path is not absolute or if this path is not relative. 346 */ 347 public Path resolveAgainst( Path absolutePath ); 348 349 /** 350 * Return the path to the parent, or this path if it is the {@link #isRoot() root}. This is an efficient operation that does 351 * not require copying any data. 352 * 353 * @return the parent path, or this null if it is already the root 354 */ 355 public Path getParent(); 356 357 /** 358 * Return the path to the ancestor of the supplied degree. An ancestor of degree <code>x</code> is the path that is <code>x 359 * </code> levels up along the path. For example, <code>degree = 0</code> returns this path, while <code>degree = 1</code> 360 * returns the parent of this path, <code>degree = 2</code> returns the grandparent of this path, and so on. Note that the 361 * result may be unexpected if this path is not {@link #isNormalized() normalized}, as a non-normalized path contains ".." and 362 * "." segments. 363 * 364 * @param degree 365 * @return the ancestor of the supplied degree 366 * @throws IllegalArgumentException if the degree is negative 367 * @throws InvalidPathException if the degree is greater than the {@link #size() length} of this path 368 */ 369 public Path getAncestor( int degree ); 370 371 /** 372 * Determine whether this path and the supplied path have the same immediate ancestor. In other words, this method determines 373 * whether the node represented by this path is a sibling of the node represented by the supplied path. 374 * 375 * @param that the other path 376 * @return true if this path and the supplied path have the same immediate ancestor. 377 * @throws IllegalArgumentException if the supplied path is null 378 */ 379 public boolean hasSameAncestor( Path that ); 380 381 /** 382 * Find the lowest common ancestor of this path and the supplied path. 383 * 384 * @param that the other path 385 * @return the lowest common ancestor, which may be the root path if there is no other. 386 * @throws IllegalArgumentException if the supplied path is null 387 */ 388 public Path getCommonAncestor( Path that ); 389 390 /** 391 * Get the last segment in this path. 392 * 393 * @return the last segment, or null if the path is empty 394 */ 395 public Segment getLastSegment(); 396 397 /** 398 * Determine if the path's {@link #getLastSegment()} has the supplied name and no {@link Segment#getIndex() SNS index}. 399 * 400 * @param nameOfLastSegment the name 401 * @return return true if the supplied name is the name in the path's last segment (and there is no SNS index), or false 402 * otherwise 403 */ 404 public boolean endsWith( Name nameOfLastSegment ); 405 406 /** 407 * Determine if the path's {@link #getLastSegment()} has the supplied name and {@link Segment#getIndex() SNS index}. 408 * 409 * @param nameOfLastSegment the name 410 * @param snsIndex the SNS index 411 * @return return true if the path's last segment has the supplied name and SNS, or false otherwise 412 */ 413 public boolean endsWith( Name nameOfLastSegment, 414 int snsIndex ); 415 416 /** 417 * Get the segment at the supplied index. 418 * 419 * @param index the index 420 * @return the segment 421 * @throws IndexOutOfBoundsException if the index is out of bounds 422 */ 423 public Segment getSegment( int index ); 424 425 /** 426 * Return a new path consisting of the segments starting at <code>beginIndex</code> index (inclusive). This is equivalent to 427 * calling <code>path.subpath(beginIndex,path.size()-1)</code>. 428 * 429 * @param beginIndex the beginning index, inclusive. 430 * @return the specified subpath 431 * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative or larger than the length of this <code> 432 * Path</code> object 433 */ 434 public Path subpath( int beginIndex ); 435 436 /** 437 * Return a new path consisting of the segments between the <code>beginIndex</code> index (inclusive) and the <code>endIndex 438 * </code> index (exclusive). 439 * 440 * @param beginIndex the beginning index, inclusive. 441 * @param endIndex the ending index, exclusive. 442 * @return the specified subpath 443 * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger than 444 * the length of this <code>Path</code> object, or <code>beginIndex</code> is larger than <code>endIndex</code>. 445 */ 446 public Path subpath( int beginIndex, 447 int endIndex ); 448 449 /** 450 * {@inheritDoc} 451 */ 452 public Iterator<Segment> iterator(); 453 454 /** 455 * Return an iterator that walks the paths from the root path down to this path. This method always returns at least one path 456 * (the root returns an iterator containing itself). 457 * 458 * @return the path iterator; never null 459 */ 460 public Iterator<Path> pathsFromRoot(); 461 462 /** 463 * Obtain a copy of the segments in this path. None of the segments are encoded. 464 * 465 * @return the array of segments as a copy 466 */ 467 public Segment[] getSegmentsArray(); 468 469 /** 470 * Get an unmodifiable list of the path segments. 471 * 472 * @return the unmodifiable list of path segments; never null 473 */ 474 public List<Segment> getSegmentsList(); 475 476 }