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 * The character used to begin an identifier segment within a path. 125 */ 126 public static final char IDENTIFIER_LEADING_TERMINAL = '['; 127 128 /** 129 * The character used to end an identifier segment within a path. 130 */ 131 public static final char IDENTIFIER_TRAILING_TERMINAL = ']'; 132 133 /** 134 * String form of the delimiter used to separate segments within a path. 135 */ 136 public static final String DELIMITER_STR = new String(new char[] {DELIMITER}); 137 138 /** 139 * String representation of the segment that references a parent. 140 */ 141 public static final String PARENT = ".."; 142 143 /** 144 * String representation of the segment that references the same segment. 145 */ 146 public static final String SELF = "."; 147 148 /** 149 * The default index for a {@link Segment}. 150 */ 151 public static final int DEFAULT_INDEX = 1; 152 153 /** 154 * Representation of the segments that occur within a path. 155 * 156 * @author Randall Hauch 157 */ 158 @Immutable 159 public static interface Segment extends Cloneable, Comparable<Segment>, Serializable, Readable { 160 161 /** 162 * Get the name component of this segment. 163 * 164 * @return the segment's name 165 */ 166 public Name getName(); 167 168 /** 169 * Get the index for this segment, which will be 1 by default. 170 * 171 * @return the index 172 */ 173 public int getIndex(); 174 175 /** 176 * Return whether this segment has an index. 177 * 178 * @return true if this segment has an index, or false otherwise. 179 */ 180 public boolean hasIndex(); 181 182 /** 183 * Return whether this segment is a self-reference. 184 * 185 * @return true if the segment is a self-reference, or false otherwise. 186 */ 187 public boolean isSelfReference(); 188 189 /** 190 * Return whether this segment is a reference to a parent. 191 * 192 * @return true if the segment is a parent-reference, or false otherwise. 193 */ 194 public boolean isParentReference(); 195 196 /** 197 * Return whether this segment is an identifier segment. 198 * 199 * @return true if the segment is an identifier segment, or false otherwise. 200 */ 201 public boolean isIdentifier(); 202 203 /** 204 * Get the raw string form of the segment using the {@link Path#NO_OP_ENCODER no-op encoder}. This is equivalent to 205 * calling <code>getString(Path.NO_OP_ENCODER)</code>. 206 * 207 * @return the un-encoded string 208 * @see #getString(TextEncoder) 209 */ 210 public String getUnencodedString(); 211 } 212 213 /** 214 * Singleton instance of the name referencing a self, provided as a convenience. 215 */ 216 public static final Name SELF_NAME = new BasicName(null, SELF); 217 218 /** 219 * Singleton instance of the name referencing a parent, provided as a convenience. 220 */ 221 public static final Name PARENT_NAME = new BasicName(null, PARENT); 222 223 /** 224 * Singleton instance of the path segment referencing a parent, provided as a convenience. 225 */ 226 public static final Path.Segment SELF_SEGMENT = new BasicPathSegment(SELF_NAME); 227 228 /** 229 * Singleton instance of the path segment referencing a parent, provided as a convenience. 230 */ 231 public static final Path.Segment PARENT_SEGMENT = new BasicPathSegment(PARENT_NAME); 232 233 /** 234 * Return the number of segments in this path. 235 * 236 * @return the number of path segments 237 */ 238 public int size(); 239 240 /** 241 * Return whether this path represents the root path. 242 * 243 * @return true if this path is the root path, or false otherwise 244 */ 245 public boolean isRoot(); 246 247 /** 248 * Returns whether this path represents an identifier path. An identifier path is absolute, and contains a single 249 * {@link Segment#isIdentifier() identifier Segment} that contains an identifier in the name. 250 * 251 * @return true if this path is an identifier path, or false otherwise. 252 */ 253 public boolean isIdentifier(); 254 255 /** 256 * Determine whether this path represents the same as the supplied path. This is equivalent to calling <code> 257 * this.compareTo(other) == 0 </code> 258 * . 259 * 260 * @param other the other path to compare with this path; may be null 261 * @return true if the paths are equivalent, or false otherwise 262 */ 263 public boolean isSameAs( Path other ); 264 265 /** 266 * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isAncestorOf(Path) ancestor of} the 267 * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || 268 * isAncestor(other)</code>, and is a convenience method that is identical to calling <code>other.isAtOrBelow(this)</code>. 269 * 270 * @param other the other path to compare with this path; may be null 271 * @return true if the paths are equivalent or if this path is considered an ancestor of the other path, or false otherwise 272 */ 273 public boolean isAtOrAbove( Path other ); 274 275 /** 276 * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isDecendantOf(Path) decendant of} the 277 * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || 278 * isAncestor(other)</code>. 279 * 280 * @param other the other path to compare with this path; may be null 281 * @return true if the paths are equivalent or if this path is considered a decendant of the other path, or false otherwise 282 */ 283 public boolean isAtOrBelow( Path other ); 284 285 /** 286 * Determine whether this path is an ancestor of the supplied path. A path is considered an ancestor of another path if the 287 * the ancestor path appears in its entirety at the beginning of the decendant path, and where the decendant path contains at 288 * least one additional segment. 289 * 290 * @param decendant the path that may be the decendant; may be null 291 * @return true if this path is an ancestor of the supplied path, or false otherwise 292 */ 293 public boolean isAncestorOf( Path decendant ); 294 295 /** 296 * Determine whether this path is an decendant of the supplied path. A path is considered a decendant of another path if the 297 * the decendant path starts exactly with the entire ancestor path but contains at least one additional segment. 298 * 299 * @param ancestor the path that may be the ancestor; may be null 300 * @return true if this path is an decendant of the supplied path, or false otherwise 301 */ 302 public boolean isDecendantOf( Path ancestor ); 303 304 /** 305 * Return whether this path is an absolute path. A path is either relative or {@link #isAbsolute() absolute}. An absolute path 306 * starts with a "/". 307 * 308 * @return true if the path is absolute, or false otherwise 309 */ 310 public boolean isAbsolute(); 311 312 /** 313 * Return whether this path is normalized and contains no unnecessary "." segments and as few ".." segments as possible. For 314 * example, the path "../a" is normalized, while "/a/b/c/../d" is not normalized. 315 * 316 * @return true if this path is normalized, or false otherwise 317 */ 318 public boolean isNormalized(); 319 320 /** 321 * Get a normalized path with as many ".." segments and all "." resolved. The relative path ".", however, will return itself 322 * as the normalized path, since it cannot be resolved any further. 323 * 324 * @return the normalized path, or this object if this path is already normalized 325 * @throws InvalidPathException if the normalized form would result in a path with negative length (e.g., "/a/../../..") 326 */ 327 public Path getNormalizedPath(); 328 329 /** 330 * Get the canonical form of this path. A canonical path has is {@link #isAbsolute() absolute} and {@link #isNormalized()}. 331 * 332 * @return the canonical path, or this object if it is already in its canonical form 333 * @throws InvalidPathException if the path is not absolute and cannot be canonicalized 334 */ 335 public Path getCanonicalPath(); 336 337 /** 338 * Obtain a path that is relative to the root node. This is equivalent to calling {@link #relativeTo(Path)} with the root 339 * path. 340 * 341 * @return the relative path from the root node; never null 342 */ 343 public Path relativeToRoot(); 344 345 /** 346 * Get a relative path from the supplied path to this path. 347 * 348 * @param startingPath the path specifying the starting point for the new relative path; may not be null 349 * @return the relative path 350 * @throws IllegalArgumentException if the supplied path is null 351 * @throws PathNotFoundException if both this path and the supplied path are not absolute 352 */ 353 public Path relativeTo( Path startingPath ); 354 355 /** 356 * Get the absolute path by resolving the supplied relative (non-absolute) path against this absolute path. 357 * 358 * @param relativePath the relative path that is to be resolved against this path 359 * @return the absolute and normalized path resolved from this path and the supplied absolute path 360 * @throws IllegalArgumentException if the supplied path is null 361 * @throws InvalidPathException if the this path is not absolute or if the supplied path is not relative. 362 */ 363 public Path resolve( Path relativePath ); 364 365 /** 366 * Get the absolute path by resolving this relative (non-absolute) path against the supplied absolute path. 367 * 368 * @param absolutePath the absolute path to which this relative path should be resolve 369 * @return the absolute path resolved from this path and the supplied absolute path 370 * @throws IllegalArgumentException if the supplied path is null 371 * @throws InvalidPathException if the supplied path is not absolute or if this path is not relative. 372 */ 373 public Path resolveAgainst( Path absolutePath ); 374 375 /** 376 * Return the path to the parent, or this path if it is the {@link #isRoot() root}. This is an efficient operation that does 377 * not require copying any data. 378 * 379 * @return the parent path, or this null if it is already the root 380 */ 381 public Path getParent(); 382 383 /** 384 * Return the path to the ancestor of the supplied degree. An ancestor of degree <code>x</code> is the path that is <code>x 385 * </code> 386 * levels up along the path. For example, <code>degree = 0</code> returns this path, while <code>degree = 1</code> returns the 387 * parent of this path, <code>degree = 2</code> returns the grandparent of this path, and so on. Note that the result may be 388 * unexpected if this path is not {@link #isNormalized() normalized}, as a non-normalized path contains ".." and "." segments. 389 * 390 * @param degree 391 * @return the ancestor of the supplied degree 392 * @throws IllegalArgumentException if the degree is negative 393 * @throws InvalidPathException if the degree is greater than the {@link #size() length} of this path 394 */ 395 public Path getAncestor( int degree ); 396 397 /** 398 * Determine whether this path and the supplied path have the same immediate ancestor. In other words, this method determines 399 * whether the node represented by this path is a sibling of the node represented by the supplied path. 400 * 401 * @param that the other path 402 * @return true if this path and the supplied path have the same immediate ancestor. 403 * @throws IllegalArgumentException if the supplied path is null 404 */ 405 public boolean hasSameAncestor( Path that ); 406 407 /** 408 * Find the lowest common ancestor of this path and the supplied path. 409 * 410 * @param that the other path 411 * @return the lowest common ancestor, which may be the root path if there is no other. 412 * @throws IllegalArgumentException if the supplied path is null 413 */ 414 public Path getCommonAncestor( Path that ); 415 416 /** 417 * Get the last segment in this path. 418 * 419 * @return the last segment, or null if the path is empty 420 */ 421 public Segment getLastSegment(); 422 423 /** 424 * Determine if the path's {@link #getLastSegment()} has the supplied name and no {@link Segment#getIndex() SNS index}. 425 * 426 * @param nameOfLastSegment the name 427 * @return return true if the supplied name is the name in the path's last segment (and there is no SNS index), or false 428 * otherwise 429 */ 430 public boolean endsWith( Name nameOfLastSegment ); 431 432 /** 433 * Determine if the path's {@link #getLastSegment()} has the supplied name and {@link Segment#getIndex() SNS index}. 434 * 435 * @param nameOfLastSegment the name 436 * @param snsIndex the SNS index 437 * @return return true if the path's last segment has the supplied name and SNS, or false otherwise 438 */ 439 public boolean endsWith( Name nameOfLastSegment, 440 int snsIndex ); 441 442 /** 443 * Get the segment at the supplied index. 444 * 445 * @param index the index 446 * @return the segment 447 * @throws IndexOutOfBoundsException if the index is out of bounds 448 */ 449 public Segment getSegment( int index ); 450 451 /** 452 * Return a new path consisting of the segments starting at <code>beginIndex</code> index (inclusive). This is equivalent to 453 * calling <code>path.subpath(beginIndex,path.size()-1)</code>. 454 * 455 * @param beginIndex the beginning index, inclusive. 456 * @return the specified subpath 457 * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative or larger than the length of this <code> 458 * Path</code> 459 * object 460 */ 461 public Path subpath( int beginIndex ); 462 463 /** 464 * Return a new path consisting of the segments between the <code>beginIndex</code> index (inclusive) and the <code>endIndex 465 * </code> index 466 * (exclusive). 467 * 468 * @param beginIndex the beginning index, inclusive. 469 * @param endIndex the ending index, exclusive. 470 * @return the specified subpath 471 * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger than 472 * the length of this <code>Path</code> object, or <code>beginIndex</code> is larger than <code>endIndex</code>. 473 */ 474 public Path subpath( int beginIndex, 475 int endIndex ); 476 477 /** 478 * {@inheritDoc} 479 */ 480 public Iterator<Segment> iterator(); 481 482 /** 483 * Return an iterator that walks the paths from the root path down to this path. This method always returns at least one path 484 * (the root returns an iterator containing itself). 485 * 486 * @return the path iterator; never null 487 */ 488 public Iterator<Path> pathsFromRoot(); 489 490 /** 491 * Obtain a copy of the segments in this path. None of the segments are encoded. 492 * 493 * @return the array of segments as a copy 494 */ 495 public Segment[] getSegmentsArray(); 496 497 /** 498 * Get an unmodifiable list of the path segments. 499 * 500 * @return the unmodifiable list of path segments; never null 501 */ 502 public List<Segment> getSegmentsList(); 503 504 }