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.properties; 023 024 import java.io.Serializable; 025 import java.util.Iterator; 026 import java.util.List; 027 import net.jcip.annotations.Immutable; 028 import org.jboss.dna.common.text.Jsr283Encoder; 029 import org.jboss.dna.common.text.NoOpEncoder; 030 import org.jboss.dna.common.text.TextDecoder; 031 import org.jboss.dna.common.text.TextEncoder; 032 import org.jboss.dna.common.text.UrlEncoder; 033 import org.jboss.dna.graph.properties.basic.BasicName; 034 import org.jboss.dna.graph.properties.basic.BasicPathSegment; 035 036 /** 037 * An object representation of a node path within a repository. 038 * <p> 039 * A path consists of zero or more segments that can contain any characters, although the string representation may require some 040 * characters to be encoded. For example, if a path contains a segment with a forward slash, then this forward slash must be 041 * escaped when writing the whole path to a string (since a forward slash is used as the {@link #DELIMITER delimiter} between 042 * segments). 043 * </p> 044 * <p> 045 * Because of this encoding and decoding issue, there is no standard representation of a path as a string. Instead, this class 046 * uses {@link TextEncoder text encoders} to escape certain characters when writing to a string or unescaping the string 047 * representation. These encoders and used only with individual segments, and therefore are not used to encode the 048 * {@link #DELIMITER delimiter}. Three standard encoders are provided, although others can certainly be used: 049 * <ul> 050 * <li>{@link #JSR283_ENCODER Jsr283Encoder} - an encoder and decoder that is compliant with <a 051 * href="http://jcp.org/en/jsr/detail?id=283">JSR-283</a> by converting the reserved characters (namely '*', '/', ':', '[', ']' 052 * and '|') to their unicode equivalent.</td> 053 * </li> 054 * <li>{@link #URL_ENCODER UrlEncoder} - an encoder and decoder that is useful for converting text to be used within a URL, as 055 * defined by Section 2.3 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. This encoder does encode many characters 056 * (including '`', '@', '#', '$', '^', '&', '{', '[', '}', ']', '|', ':', ';', '\', '"', '<', ',', '>', '?', '/', and ' '), while 057 * others are not encoded (including '-', '_', '.', '!', '~', '*', '\', ''', '(', and ')'). Note that only the '*' character is 058 * the only character reserved by JSR-283 that is not encoded by the URL encoder.</li> 059 * <li>{@link #NO_OP_ENCODER NoOpEncoder} - an {@link TextEncoder encoder} implementation that does nothing.</li> 060 * </ul> 061 * </p> 062 * <p> 063 * This class simplifies working with paths and using a <code>Path</code> is often more efficient that processing and 064 * manipulating the equivalent <code>String</code>. This class can easily {@link #iterator() iterate} over the segments, return 065 * the {@link #size() number of segments}, {@link #compareTo(Path) compare} with other paths, {@link #resolve(Path) resolve} 066 * relative paths, return the {@link #getParent() ancestor (or parent)}, determine whether one path is an 067 * {@link #isAncestorOf(Path) ancestor} or {@link #isDecendantOf(Path) decendent} of another path, and 068 * {@link #getCommonAncestor(Path) finding a common ancestor}. 069 * </p> 070 * 071 * @author Randall Hauch 072 * @author John Verhaeg 073 */ 074 @Immutable 075 public interface Path extends Comparable<Path>, Iterable<Path.Segment>, Serializable { 076 077 /** 078 * The text encoder that does nothing. 079 */ 080 public static final TextEncoder NO_OP_ENCODER = new NoOpEncoder(); 081 082 /** 083 * The text encoder that encodes according to JSR-283. 084 */ 085 public static final TextEncoder JSR283_ENCODER = new Jsr283Encoder(); 086 087 /** 088 * The text encoder that encodes text according to the rules of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. 089 */ 090 public static final TextEncoder URL_ENCODER = new UrlEncoder().setSlashEncoded(true); 091 092 /** 093 * The text decoder that does nothing. 094 */ 095 public static final TextDecoder NO_OP_DECODER = new NoOpEncoder(); 096 097 /** 098 * The text decoder that decodes according to JSR-283. 099 */ 100 public static final TextDecoder JSR283_DECODER = new Jsr283Encoder(); 101 102 /** 103 * The text decoder that decodes text according to the rules of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. 104 */ 105 public static final TextDecoder URL_DECODER = new UrlEncoder().setSlashEncoded(true); 106 107 /** 108 * The default text encoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283 109 * encoder}. 110 */ 111 public static final TextEncoder DEFAULT_ENCODER = JSR283_ENCODER; 112 113 /** 114 * The default text decoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283 115 * encoder}. 116 */ 117 public static final TextDecoder DEFAULT_DECODER = JSR283_DECODER; 118 119 /** 120 * The delimiter character used to separate segments within a path. 121 */ 122 public static final char DELIMITER = '/'; 123 124 /** 125 * String form of the delimiter used to separate segments within a path. 126 */ 127 public static final String DELIMITER_STR = new String(new char[] {DELIMITER}); 128 129 /** 130 * String representation of the segment that references a parent. 131 */ 132 public static final String PARENT = ".."; 133 134 /** 135 * String representation of the segment that references the same segment. 136 */ 137 public static final String SELF = "."; 138 139 /** 140 * The index that will be returned for a {@link Segment} that {@link Segment#hasIndex() has no index}. 141 */ 142 public static final int NO_INDEX = -1; 143 144 /** 145 * Representation of the segments that occur within a path. 146 * 147 * @author Randall Hauch 148 */ 149 @Immutable 150 public static interface Segment extends Cloneable, Comparable<Segment>, Serializable { 151 152 /** 153 * Get the name component of this segment. 154 * 155 * @return the segment's name 156 */ 157 public Name getName(); 158 159 /** 160 * Get the index for this segment, which will be {@link Path#NO_INDEX 0} if this segment has no specific index. 161 * 162 * @return the index 163 */ 164 public int getIndex(); 165 166 /** 167 * Return whether this segment has an index. 168 * 169 * @return true if this segment has an index, or false otherwise. 170 */ 171 public boolean hasIndex(); 172 173 /** 174 * Return whether this segment is a self-reference. 175 * 176 * @return true if the segment is a self-reference, or false otherwise. 177 */ 178 public boolean isSelfReference(); 179 180 /** 181 * Return whether this segment is a reference to a parent. 182 * 183 * @return true if the segment is a parent-reference, or false otherwise. 184 */ 185 public boolean isParentReference(); 186 187 /** 188 * Get the raw string form of the segment using the {@link Path#NO_OP_ENCODER no-op encoder}. This is equivalent to 189 * calling <code>getString(Path.NO_OP_ENCODER)</code>. 190 * 191 * @return the un-encoded string 192 * @see #getString(TextEncoder) 193 */ 194 public String getUnencodedString(); 195 196 /** 197 * Get the string form of the segment. The {@link #DEFAULT_ENCODER default encoder} is used to encode characters in each 198 * of the path segments. 199 * 200 * @return the encoded string 201 * @see #getString(TextEncoder) 202 */ 203 public String getString(); 204 205 /** 206 * Get the encoded string form of the segment, using the supplied encoder to encode characters in each of the path 207 * segments. 208 * 209 * @param encoder the encoder to use, or null if the {@link #DEFAULT_ENCODER default encoder} should be used 210 * @return the encoded string 211 * @see #getString() 212 */ 213 public String getString( TextEncoder encoder ); 214 215 /** 216 * Get the string form of the segment, using the supplied namespace registry to convert the name's namespace URI to a 217 * prefix. The {@link #DEFAULT_ENCODER default encoder} is used to encode characters in each of the path segments. 218 * 219 * @param namespaceRegistry the namespace registry that should be used to obtain the prefix for the 220 * {@link Name#getNamespaceUri() namespace URI} in the segment's {@link #getName() name} 221 * @return the encoded string 222 * @throws IllegalArgumentException if the namespace registry is null 223 * @see #getString(NamespaceRegistry,TextEncoder) 224 */ 225 public String getString( NamespaceRegistry namespaceRegistry ); 226 227 /** 228 * Get the encoded string form of the segment, using the supplied namespace registry to convert the name's namespace URI 229 * to a prefix and the supplied encoder to encode characters in each of the path segments. 230 * 231 * @param namespaceRegistry the namespace registry that should be used to obtain the prefix for the 232 * {@link Name#getNamespaceUri() namespace URI} in the segment's {@link #getName() name} 233 * @param encoder the encoder to use, or null if the {@link #DEFAULT_ENCODER default encoder} should be used 234 * @return the encoded string 235 * @throws IllegalArgumentException if the namespace registry is null 236 * @see #getString(NamespaceRegistry) 237 */ 238 public String getString( NamespaceRegistry namespaceRegistry, 239 TextEncoder encoder ); 240 241 /** 242 * Get the encoded string form of the segment, using the supplied namespace registry to convert the names' namespace URIs 243 * to prefixes and the supplied encoder to encode characters in each of the path segments. The second encoder is used to 244 * encode (or convert) the delimiter between the {@link Name#getNamespaceUri() namespace prefix} and the 245 * {@link Name#getLocalName() local part}. 246 * 247 * @param namespaceRegistry the namespace registry that should be used to obtain the prefix for the 248 * {@link Name#getNamespaceUri() namespace URIs} in the segment {@link Segment#getName() names} 249 * @param encoder the encoder to use for encoding the {@link Name#getLocalName() local part} and 250 * {@link Name#getNamespaceUri() namespace prefix} in the segment's {@link #getName() name}, or null if the 251 * {@link #DEFAULT_ENCODER default encoder} should be used 252 * @param delimiterEncoder the encoder to use for encoding the delimiter between the {@link Name#getLocalName() local 253 * part} and {@link Name#getNamespaceUri() namespace prefix} of each {@link Path#getSegmentsList() segment}, or 254 * null if the standard delimiters should be used 255 * @return the encoded string 256 * @see #getString(NamespaceRegistry) 257 * @see #getString(NamespaceRegistry, TextEncoder) 258 */ 259 public String getString( NamespaceRegistry namespaceRegistry, 260 TextEncoder encoder, 261 TextEncoder delimiterEncoder ); 262 } 263 264 /** 265 * Singleton instance of the name referencing a self, provided as a convenience. 266 */ 267 public static final Name SELF_NAME = new BasicName(null, SELF); 268 269 /** 270 * Singleton instance of the name referencing a parent, provided as a convenience. 271 */ 272 public static final Name PARENT_NAME = new BasicName(null, PARENT); 273 274 /** 275 * Singleton instance of the path segment referencing a parent, provided as a convenience. 276 */ 277 public static final Path.Segment SELF_SEGMENT = new BasicPathSegment(SELF_NAME); 278 279 /** 280 * Singleton instance of the path segment referencing a parent, provided as a convenience. 281 */ 282 public static final Path.Segment PARENT_SEGMENT = new BasicPathSegment(PARENT_NAME); 283 284 /** 285 * Return the number of segments in this path. 286 * 287 * @return the number of path segments 288 */ 289 public int size(); 290 291 /** 292 * Return whether this path represents the root path. 293 * 294 * @return true if this path is the root path, or false otherwise 295 */ 296 public boolean isRoot(); 297 298 /** 299 * Determine whether this path represents the same as the supplied path. This is equivalent to calling <code> 300 * this.compareTo(other) == 0 </code>. 301 * 302 * @param other the other path to compare with this path; may be null 303 * @return true if the paths are equivalent, or false otherwise 304 */ 305 public boolean isSameAs( Path other ); 306 307 /** 308 * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isAncestorOf(Path) ancestor of} the 309 * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || 310 * isAncestor(other)</code>, and is a convenience method that is identical to calling <code>other.isAtOrBelow(this)</code>. 311 * 312 * @param other the other path to compare with this path; may be null 313 * @return true if the paths are equivalent or if this path is considered an ancestor of the other path, or false otherwise 314 */ 315 public boolean isAtOrAbove( Path other ); 316 317 /** 318 * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isDecendantOf(Path) decendant of} the 319 * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) || 320 * isAncestor(other)</code>. 321 * 322 * @param other the other path to compare with this path; may be null 323 * @return true if the paths are equivalent or if this path is considered a decendant of the other path, or false otherwise 324 */ 325 public boolean isAtOrBelow( Path other ); 326 327 /** 328 * Determine whether this path is an ancestor of the supplied path. A path is considered an ancestor of another path if the 329 * the ancestor path appears in its entirety at the beginning of the decendant path, and where the decendant path contains at 330 * least one additional segment. 331 * 332 * @param decendant the path that may be the decendant; may be null 333 * @return true if this path is an ancestor of the supplied path, or false otherwise 334 */ 335 public boolean isAncestorOf( Path decendant ); 336 337 /** 338 * Determine whether this path is an decendant of the supplied path. A path is considered a decendant of another path if the 339 * the decendant path starts exactly with the entire ancestor path but contains at least one additional segment. 340 * 341 * @param ancestor the path that may be the ancestor; may be null 342 * @return true if this path is an decendant of the supplied path, or false otherwise 343 */ 344 public boolean isDecendantOf( Path ancestor ); 345 346 /** 347 * Return whether this path is an absolute path. A path is either relative or {@link #isAbsolute() absolute}. An absolute path 348 * starts with a "/". 349 * 350 * @return true if the path is absolute, or false otherwise 351 */ 352 public boolean isAbsolute(); 353 354 /** 355 * Return whether this path is normalized and contains no "." segments and as few ".." segments as possible. For example, the 356 * path "../a" is normalized, while "/a/b/c/../d" is not normalized. 357 * 358 * @return true if this path is normalized, or false otherwise 359 */ 360 public boolean isNormalized(); 361 362 /** 363 * Get a normalized path with as many ".." segments and all "." resolved. 364 * 365 * @return the normalized path, or this object if this path is already normalized 366 * @throws InvalidPathException if the normalized form would result in a path with negative length (e.g., "/a/../../..") 367 */ 368 public Path getNormalizedPath(); 369 370 /** 371 * Get the canonical form of this path. A canonical path has is {@link #isAbsolute() absolute} and {@link #isNormalized()}. 372 * 373 * @return the canonical path, or this object if it is already in its canonical form 374 * @throws InvalidPathException if the path is not absolute and cannot be canonicalized 375 */ 376 public Path getCanonicalPath(); 377 378 /** 379 * Get a relative path from the supplied path to this path. 380 * 381 * @param startingPath the path specifying the starting point for the new relative path; may not be null 382 * @return the relative path 383 * @throws IllegalArgumentException if the supplied path is null 384 * @throws PathNotFoundException if both this path and the supplied path are not absolute 385 */ 386 public Path relativeTo( Path startingPath ); 387 388 /** 389 * Get the absolute path by resolving the supplied relative (non-absolute) path against this absolute path. 390 * 391 * @param relativePath the relative path that is to be resolved against this path 392 * @return the absolute and normalized path resolved from this path and the supplied absolute path 393 * @throws IllegalArgumentException if the supplied path is null 394 * @throws InvalidPathException if the this path is not absolute or if the supplied path is not relative. 395 */ 396 public Path resolve( Path relativePath ); 397 398 /** 399 * Get the absolute path by resolving this relative (non-absolute) path against the supplied absolute path. 400 * 401 * @param absolutePath the absolute path to which this relative path should be resolve 402 * @return the absolute path resolved from this path and the supplied absolute path 403 * @throws IllegalArgumentException if the supplied path is null 404 * @throws InvalidPathException if the supplied path is not absolute or if this path is not relative. 405 */ 406 public Path resolveAgainst( Path absolutePath ); 407 408 /** 409 * Return the path to the parent, or this path if it is the {@link #isRoot() root}. This is an efficient operation that does 410 * not require copying any data. 411 * 412 * @return the parent path, or this path if it is already the root 413 */ 414 public Path getParent(); 415 416 /** 417 * Return the path to the ancestor of the supplied degree. An ancestor of degree <code>x</code> is the path that is <code>x 418 * </code> levels up along the path. For example, <code>degree = 0</code> returns this path, while <code>degree = 1</code> 419 * returns the parent of this path, <code>degree = 2</code> returns the grandparent of this path, and so on. Note that the 420 * result may be unexpected if this path is not {@link #isNormalized() normalized}, as a non-normalized path contains ".." and 421 * "." segments. 422 * 423 * @param degree 424 * @return the ancestor of the supplied degree 425 * @throws IllegalArgumentException if the degree is negative 426 * @throws InvalidPathException if the degree is greater than the {@link #size() length} of this path 427 */ 428 public Path getAncestor( int degree ); 429 430 /** 431 * Determine whether this path and the supplied path have the same immediate ancestor. In other words, this method determines 432 * whether the node represented by this path is a sibling of the node represented by the supplied path. 433 * 434 * @param that the other path 435 * @return true if this path and the supplied path have the same immediate ancestor. 436 * @throws IllegalArgumentException if the supplied path is null 437 */ 438 public boolean hasSameAncestor( Path that ); 439 440 /** 441 * Find the lowest common ancestor of this path and the supplied path. 442 * 443 * @param that the other path 444 * @return the lowest common ancestor, which may be the root path if there is no other. 445 * @throws IllegalArgumentException if the supplied path is null 446 */ 447 public Path getCommonAncestor( Path that ); 448 449 /** 450 * Get the last segment in this path. 451 * 452 * @return the last segment, or null if the path is empty 453 */ 454 public Segment getLastSegment(); 455 456 /** 457 * Get the segment at the supplied index. 458 * 459 * @param index the index 460 * @return the segment 461 * @throws IndexOutOfBoundsException if the index is out of bounds 462 */ 463 public Segment getSegment( int index ); 464 465 /** 466 * Return a new path consisting of the segments starting at <code>beginIndex</code> index (inclusive). This is equivalent to 467 * calling <code>path.subpath(beginIndex,path.size()-1)</code>. 468 * 469 * @param beginIndex the beginning index, inclusive. 470 * @return the specified subpath 471 * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative or larger than the length of this <code> 472 * Path</code> object 473 */ 474 public Path subpath( int beginIndex ); 475 476 /** 477 * Return a new path consisting of the segments between the <code>beginIndex</code> index (inclusive) and the <code>endIndex 478 * </code> index (exclusive). 479 * 480 * @param beginIndex the beginning index, inclusive. 481 * @param endIndex the ending index, exclusive. 482 * @return the specified subpath 483 * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger than 484 * the length of this <code>Path</code> object, or <code>beginIndex</code> is larger than <code>endIndex</code>. 485 */ 486 public Path subpath( int beginIndex, 487 int endIndex ); 488 489 /** 490 * {@inheritDoc} 491 */ 492 public Iterator<Segment> iterator(); 493 494 /** 495 * Obtain a copy of the segments in this path. None of the segments are encoded. 496 * 497 * @return the array of segments as a copy 498 */ 499 public Segment[] getSegmentsArray(); 500 501 /** 502 * Get an unmodifiable list of the path segments. 503 * 504 * @return the unmodifiable list of path segments; never null 505 */ 506 public List<Segment> getSegmentsList(); 507 508 /** 509 * Get the string form of the path. The {@link #DEFAULT_ENCODER default encoder} is used to encode characters in each of the 510 * path segments. 511 * 512 * @return the encoded string 513 * @see #getString(TextEncoder) 514 */ 515 public String getString(); 516 517 /** 518 * Get the encoded string form of the path, using the supplied encoder to encode characters in each of the path segments. 519 * 520 * @param encoder the encoder to use, or null if the {@link #DEFAULT_ENCODER default encoder} should be used 521 * @return the encoded string 522 * @see #getString() 523 */ 524 public String getString( TextEncoder encoder ); 525 526 /** 527 * Get the string form of the path, using the supplied namespace registry to convert the names' namespace URIs to prefixes. 528 * The {@link #DEFAULT_ENCODER default encoder} is used to encode characters in each of the path segments. The second encoder 529 * is used to encode (or convert) the delimiter between the {@link Name#getNamespaceUri() namespace prefix} and the 530 * {@link Name#getLocalName() local part}. 531 * 532 * @param namespaceRegistry the namespace registry that should be used to obtain the prefix for the 533 * {@link Name#getNamespaceUri() namespace URIs} in the segment {@link Segment#getName() names} 534 * @return the encoded string 535 * @throws IllegalArgumentException if the namespace registry is null 536 * @see #getString(NamespaceRegistry,TextEncoder) 537 * @see #getString(NamespaceRegistry, TextEncoder, TextEncoder) 538 */ 539 public String getString( NamespaceRegistry namespaceRegistry ); 540 541 /** 542 * Get the encoded string form of the path, using the supplied namespace registry to convert the names' namespace URIs to 543 * prefixes and the supplied encoder to encode characters in each of the path segments. 544 * 545 * @param namespaceRegistry the namespace registry that should be used to obtain the prefix for the 546 * {@link Name#getNamespaceUri() namespace URIs} in the segment {@link Segment#getName() names} 547 * @param encoder the encoder to use for encoding the {@link Name#getLocalName() local part} and 548 * {@link Name#getNamespaceUri() namespace prefix} of each {@link Path#getSegmentsList() segment}, or null if the 549 * {@link #DEFAULT_ENCODER default encoder} should be used 550 * @return the encoded string 551 * @throws IllegalArgumentException if the namespace registry is null 552 * @see #getString(NamespaceRegistry) 553 * @see #getString(NamespaceRegistry, TextEncoder, TextEncoder) 554 */ 555 public String getString( NamespaceRegistry namespaceRegistry, 556 TextEncoder encoder ); 557 558 /** 559 * Get the encoded string form of the path, using the supplied namespace registry to convert the names' namespace URIs to 560 * prefixes and the supplied encoder to encode characters in each of the path segments. 561 * 562 * @param namespaceRegistry the namespace registry that should be used to obtain the prefix for the 563 * {@link Name#getNamespaceUri() namespace URIs} in the segment {@link Segment#getName() names} 564 * @param encoder the encoder to use for encoding the {@link Name#getLocalName() local part} and 565 * {@link Name#getNamespaceUri() namespace prefix} of each {@link Path#getSegmentsList() segment}, or null if the 566 * {@link #DEFAULT_ENCODER default encoder} should be used 567 * @param delimiterEncoder the encoder to use for encoding the delimiter between the {@link Name#getLocalName() local part} 568 * and {@link Name#getNamespaceUri() namespace prefix} of each {@link Path#getSegmentsList() segment}, and for encoding 569 * the path delimiter, or null if the standard delimiters should be used 570 * @return the encoded string 571 * @see #getString(NamespaceRegistry) 572 * @see #getString(NamespaceRegistry, TextEncoder) 573 */ 574 public String getString( NamespaceRegistry namespaceRegistry, 575 TextEncoder encoder, 576 TextEncoder delimiterEncoder ); 577 578 }