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    }