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