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 }