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.basic;
023    
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.Iterator;
027    import java.util.LinkedList;
028    import java.util.List;
029    import net.jcip.annotations.Immutable;
030    import org.jboss.dna.common.CommonI18n;
031    import org.jboss.dna.common.text.Inflector;
032    import org.jboss.dna.common.text.TextEncoder;
033    import org.jboss.dna.common.util.CheckArg;
034    import org.jboss.dna.graph.GraphI18n;
035    import org.jboss.dna.graph.properties.InvalidPathException;
036    import org.jboss.dna.graph.properties.NamespaceRegistry;
037    import org.jboss.dna.graph.properties.Path;
038    
039    /**
040     * A basic implementation of {@link Path}.
041     * 
042     * @author Randall Hauch
043     * @author John Verhaeg
044     */
045    @Immutable
046    public class BasicPath implements Path {
047    
048        /**
049         */
050        private static final long serialVersionUID = 8488295345524209746L;
051    
052        private static final List<Segment> EMPTY_SEGMENTS = Collections.emptyList();
053    
054        public static final Path ROOT = new BasicPath(EMPTY_SEGMENTS, true);
055    
056        public static final Path SELF_PATH = new BasicPath(Collections.singletonList(Path.SELF_SEGMENT), false);
057    
058        private final List<Segment> segments;
059        private final boolean absolute;
060        private final boolean normalized;
061        private transient String path;
062    
063        /**
064         * @param segments the segments
065         * @param absolute true if this path is absolute, or false otherwise
066         */
067        public BasicPath( List<Segment> segments,
068                          boolean absolute ) {
069            CheckArg.isNotNull(segments, "segments");
070            this.segments = segments.isEmpty() ? EMPTY_SEGMENTS : Collections.unmodifiableList(segments);
071            this.absolute = absolute;
072            this.normalized = isNormalized(this.segments);
073        }
074    
075        protected boolean isNormalized( List<Segment> segments ) {
076            for (Segment segment : segments) {
077                if (segment.isSelfReference() || segment.isParentReference()) return false;
078            }
079            return true;
080        }
081    
082        /**
083         * {@inheritDoc}
084         */
085        public Path getParent() {
086            if (this.isRoot()) return null;
087            if (this.segments.size() == 1) return ROOT;
088            return subpath(0, this.segments.size() - 1);
089        }
090    
091        /**
092         * {@inheritDoc}
093         */
094        public Path getAncestor( int degree ) {
095            CheckArg.isNonNegative(degree, "degree");
096            if (degree == 0) return this;
097            if (this.isRoot()) return null;
098            int endIndex = this.segments.size() - degree;
099            if (endIndex < 0) {
100                String msg = GraphI18n.pathAncestorDegreeIsInvalid.text(this.getString(), Inflector.getInstance().ordinalize(degree));
101                throw new InvalidPathException(msg);
102            }
103            return subpath(0, endIndex);
104        }
105    
106        /**
107         * {@inheritDoc}
108         */
109        public Path getCanonicalPath() {
110            if (!this.isAbsolute()) {
111                String msg = GraphI18n.pathIsNotAbsolute.text(this);
112                throw new InvalidPathException(msg);
113            }
114            if (this.isNormalized()) return this;
115            return this.getNormalizedPath();
116        }
117    
118        /**
119         * {@inheritDoc}
120         */
121        public Path getCommonAncestor( Path that ) {
122            if (that == null) return null;
123            if (this.isRoot() || that.isRoot()) return ROOT;
124            Path normalizedPath = this.getNormalizedPath();
125            int lastIndex = 0;
126            Iterator<Segment> thisIter = normalizedPath.iterator();
127            Iterator<Segment> thatIter = that.getNormalizedPath().iterator();
128            while (thisIter.hasNext() && thatIter.hasNext()) {
129                Segment thisSeg = thisIter.next();
130                Segment thatSeg = thatIter.next();
131                if (thisSeg.equals(thatSeg)) {
132                    ++lastIndex;
133                } else {
134                    break;
135                }
136            }
137            if (lastIndex == 0) return ROOT;
138            return normalizedPath.subpath(0, lastIndex);
139        }
140    
141        /**
142         * {@inheritDoc}
143         */
144        public Path.Segment getLastSegment() {
145            if (this.isRoot()) return null;
146            return this.segments.get(size() - 1);
147        }
148    
149        /**
150         * {@inheritDoc}
151         */
152        public Path getNormalizedPath() {
153            if (this.isNormalized()) return this; // ROOT is normalized already
154            LinkedList<Segment> newSegments = new LinkedList<Segment>();
155            for (Segment segment : segments) {
156                if (segment.isSelfReference()) continue;
157                if (segment.isParentReference()) {
158                    if (newSegments.isEmpty()) {
159                        if (this.isAbsolute()) {
160                            throw new InvalidPathException(CommonI18n.pathCannotBeNormalized.text(this));
161                        }
162                    } else if (!newSegments.getLast().isParentReference()) {
163                        newSegments.removeLast();
164                        continue;
165                    }
166                }
167                newSegments.add(segment);
168            }
169            if (newSegments.isEmpty()) {
170                if (this.isAbsolute()) return ROOT;
171                // Otherwise relative and it had contained nothing but self references ...
172                return SELF_PATH;
173            }
174            return new BasicPath(newSegments, this.isAbsolute());
175        }
176    
177        /**
178         * {@inheritDoc}
179         */
180        public Segment getSegment( int index ) {
181            return this.segments.get(index);
182        }
183    
184        /**
185         * {@inheritDoc}
186         */
187        public Segment[] getSegmentsArray() {
188            return this.segments.toArray(new Path.Segment[this.segments.size()]);
189        }
190    
191        /**
192         * {@inheritDoc}
193         */
194        public List<Segment> getSegmentsList() {
195            return this.segments;
196        }
197    
198        /**
199         * {@inheritDoc}
200         */
201        public String getString() {
202            return doGetString(null, DEFAULT_ENCODER);
203        }
204    
205        /**
206         * {@inheritDoc}
207         */
208        public String getString( TextEncoder encoder ) {
209            return doGetString(null, encoder);
210        }
211    
212        /**
213         * {@inheritDoc}
214         */
215        public String getString( NamespaceRegistry namespaceRegistry ) {
216            CheckArg.isNotNull(namespaceRegistry, "namespaceRegistry");
217            return doGetString(namespaceRegistry, null);
218        }
219    
220        /**
221         * {@inheritDoc}
222         */
223        public String getString( NamespaceRegistry namespaceRegistry,
224                                 TextEncoder encoder ) {
225            CheckArg.isNotNull(namespaceRegistry, "namespaceRegistry");
226            return doGetString(namespaceRegistry, encoder);
227        }
228    
229        /**
230         * Method that creates the string representation. This method works two different ways depending upon whether the namespace
231         * registry is provided.
232         * 
233         * @param namespaceRegistry
234         * @param encoder
235         * @return this path as a string
236         */
237        protected String doGetString( NamespaceRegistry namespaceRegistry,
238                                      TextEncoder encoder ) {
239            if (encoder == null) encoder = DEFAULT_ENCODER;
240            if (encoder == DEFAULT_ENCODER && this.path != null) return this.path;
241    
242            // Since the segments are immutable, this code need not be synchronized because concurrent threads
243            // may just compute the same value (with no harm done)
244            StringBuilder sb = new StringBuilder();
245            if (this.isAbsolute()) sb.append(DELIMITER);
246            boolean first = true;
247            for (Segment segment : this.segments) {
248                if (first) {
249                    first = false;
250                } else {
251                    sb.append(DELIMITER);
252                }
253                assert segment != null;
254                if (namespaceRegistry != null) {
255                    sb.append(segment.getString(namespaceRegistry, encoder));
256                } else {
257                    sb.append(segment.getString(encoder));
258                }
259            }
260            String result = sb.toString();
261            // Save the result to the internal string if this the default encoder is used.
262            // This is not synchronized, but it's okay
263            if (encoder == DEFAULT_ENCODER && this.path == null) this.path = result;
264            return result;
265        }
266    
267        /**
268         * {@inheritDoc}
269         */
270        public boolean hasSameAncestor( Path that ) {
271            if (that == null) return false;
272            if (that.size() != this.size()) return false;
273            if (this.size() == 1) return false;
274            for (int i = this.size() - 2; i < 0; --i) {
275                Path.Segment thisSegment = this.getSegment(i);
276                Path.Segment thatSegment = that.getSegment(i);
277                if (!thisSegment.equals(thatSegment)) return false;
278            }
279            return true;
280        }
281    
282        /**
283         * {@inheritDoc}
284         */
285        public boolean isAbsolute() {
286            return this.absolute;
287        }
288    
289        /**
290         * {@inheritDoc}
291         */
292        public boolean isAncestorOf( Path decendant ) {
293            if (decendant == null) return false;
294            if (this == decendant) return false;
295            if (this.size() >= decendant.size()) return false;
296    
297            Iterator<Path.Segment> thisIter = this.iterator();
298            Iterator<Path.Segment> thatIter = decendant.iterator();
299            while (thisIter.hasNext()) {
300                Path.Segment thisSeg = thisIter.next();
301                Path.Segment thatSeg = thatIter.next();
302                if (!thisSeg.equals(thatSeg)) return false;
303            }
304            return true;
305        }
306    
307        /**
308         * {@inheritDoc}
309         * 
310         * @see org.jboss.dna.graph.properties.Path#isAtOrBelow(org.jboss.dna.graph.properties.Path)
311         */
312        public boolean isAtOrBelow( Path other ) {
313            if (other == null) return false;
314            if (this == other) return true;
315            Iterator<Segment> thisIter = this.segments.iterator();
316            Iterator<Segment> thatIter = other.iterator();
317            while (thisIter.hasNext() && thatIter.hasNext()) {
318                if (!thisIter.next().equals(thatIter.next())) return false;
319            }
320            if (thatIter.hasNext()) return false; // The other still has segments, but this doesn't
321            return true;
322        }
323    
324        /**
325         * {@inheritDoc}
326         * 
327         * @see org.jboss.dna.graph.properties.Path#isAtOrAbove(org.jboss.dna.graph.properties.Path)
328         */
329        public boolean isAtOrAbove( Path other ) {
330            if (other == null) return false;
331            if (this == other) return true;
332            Iterator<Segment> thisIter = this.segments.iterator();
333            Iterator<Segment> thatIter = other.iterator();
334            while (thisIter.hasNext() && thatIter.hasNext()) {
335                if (!thisIter.next().equals(thatIter.next())) return false;
336            }
337            if (thisIter.hasNext()) return false; // This still has segments, but other doesn't
338            return true;
339        }
340    
341        /**
342         * {@inheritDoc}
343         */
344        public boolean isDecendantOf( Path ancestor ) {
345            if (ancestor == null) return false;
346            return ancestor.isAncestorOf(this);
347        }
348    
349        /**
350         * {@inheritDoc}
351         */
352        public boolean isNormalized() {
353            return this.normalized;
354        }
355    
356        /**
357         * {@inheritDoc}
358         */
359        public boolean isRoot() {
360            return this == ROOT || this.segments.isEmpty();
361        }
362    
363        /**
364         * {@inheritDoc}
365         */
366        public boolean isSameAs( Path other ) {
367            return other != null && this.compareTo(other) == 0;
368        }
369    
370        /**
371         * {@inheritDoc}
372         */
373        public Iterator<Segment> iterator() {
374            return this.segments.iterator();
375        }
376    
377        /**
378         * {@inheritDoc}
379         */
380        public Path relativeTo( Path startingPath ) {
381            CheckArg.isNotNull(startingPath, "to");
382            if (!this.isAbsolute()) {
383                String msg = GraphI18n.pathIsNotAbsolute.text(this);
384                throw new InvalidPathException(msg);
385            }
386            if (!startingPath.isAbsolute()) {
387                String msg = GraphI18n.pathIsNotAbsolute.text(startingPath);
388                throw new InvalidPathException(msg);
389            }
390    
391            // Count the number of segments up to the common ancestor (relative path is what remains) ...
392            int lengthOfCommonAncestor = 0;
393            Iterator<Segment> thisIter = this.getNormalizedPath().iterator();
394            Iterator<Segment> toIter = startingPath.getNormalizedPath().iterator();
395            while (thisIter.hasNext() && toIter.hasNext()) {
396                Segment thisSeg = thisIter.next();
397                Segment toSeg = toIter.next();
398                if (thisSeg.equals(toSeg)) {
399                    ++lengthOfCommonAncestor;
400                } else {
401                    break;
402                }
403            }
404            // Create the relative path, starting with parent references to the common ancestor ...
405            int numberOfParentReferences = startingPath.size() - lengthOfCommonAncestor;
406            List<Segment> relativeSegments = new ArrayList<Segment>();
407            for (int i = 0; i != numberOfParentReferences; ++i) {
408                relativeSegments.add(Path.PARENT_SEGMENT);
409            }
410            // Add the segments of this path from the common ancestor ...
411            for (int i = lengthOfCommonAncestor; i < this.size(); ++i) {
412                relativeSegments.add(this.segments.get(i));
413            }
414            if (relativeSegments.isEmpty()) {
415                relativeSegments.add(Path.SELF_SEGMENT);
416            }
417            return new BasicPath(relativeSegments, false);
418        }
419    
420        /**
421         * {@inheritDoc}
422         */
423        public Path resolve( Path relativePath ) {
424            CheckArg.isNotNull(relativePath, "relative path");
425            if (!this.isAbsolute()) {
426                String msg = GraphI18n.pathIsAlreadyAbsolute.text(this.path);
427                throw new InvalidPathException(msg);
428            }
429            if (relativePath.isAbsolute()) {
430                String msg = GraphI18n.pathIsNotRelative.text(relativePath);
431                throw new InvalidPathException(msg);
432            }
433            // If the relative path is the self or parent reference ...
434            relativePath = relativePath.getNormalizedPath();
435            if (relativePath.size() == 1) {
436                Segment onlySegment = relativePath.getSegment(0);
437                if (onlySegment.isSelfReference()) return this;
438                if (onlySegment.isParentReference()) return this.getParent();
439            }
440            List<Segment> segments = new ArrayList<Segment>(this.size() + relativePath.size());
441            segments.addAll(this.segments);
442            segments.addAll(relativePath.getSegmentsList());
443            return new BasicPath(segments, true).getNormalizedPath();
444        }
445    
446        /**
447         * {@inheritDoc}
448         */
449        public Path resolveAgainst( Path absolutePath ) {
450            CheckArg.isNotNull(absolutePath, "absolute path");
451            return absolutePath.resolve(this);
452        }
453    
454        /**
455         * {@inheritDoc}
456         */
457        public int size() {
458            return this.segments.size();
459        }
460    
461        /**
462         * {@inheritDoc}
463         */
464        public Path subpath( int beginIndex ) {
465            if (beginIndex == 0) return this;
466            int size = size();
467            if (beginIndex >= size) {
468                throw new IndexOutOfBoundsException(GraphI18n.unableToCreateSubpathBeginIndexGreaterThanOrEqualToSize.text(beginIndex,
469                                                                                                                         size));
470            }
471            if (size == 0) return ROOT;
472            return new BasicPath(this.segments.subList(beginIndex, size), this.isAbsolute());
473        }
474    
475        /**
476         * {@inheritDoc}
477         */
478        public Path subpath( int beginIndex,
479                             int endIndex ) {
480            int size = size();
481            if (beginIndex == 0) {
482                if (endIndex == 0) return ROOT;
483                if (endIndex == size) return this;
484            }
485            if (beginIndex >= size) {
486                throw new IndexOutOfBoundsException(GraphI18n.unableToCreateSubpathBeginIndexGreaterThanOrEqualToSize.text(beginIndex,
487                                                                                                                         size));
488            }
489            if (beginIndex > endIndex) {
490                throw new IndexOutOfBoundsException(
491                                                    GraphI18n.unableToCreateSubpathBeginIndexGreaterThanOrEqualToEndingIndex.text(beginIndex,
492                                                                                                                                endIndex));
493            }
494            // This reuses the same list, so it's pretty efficient ...
495            return new BasicPath(this.segments.subList(beginIndex, endIndex), this.isAbsolute());
496        }
497    
498        /**
499         * {@inheritDoc}
500         */
501        @Override
502        public int hashCode() {
503            return this.segments.hashCode();
504        }
505    
506        /**
507         * {@inheritDoc}
508         */
509        @Override
510        public boolean equals( Object obj ) {
511            if (obj == this) return true;
512            if (obj instanceof Path) {
513                Path that = (Path)obj;
514                return this.segments.equals(that.getSegmentsList());
515            }
516            return false;
517        }
518    
519        /**
520         * {@inheritDoc}
521         */
522        public int compareTo( Path that ) {
523            if (this == that) return 0;
524            Iterator<Segment> thisIter = this.segments.iterator();
525            Iterator<Segment> thatIter = that.iterator();
526            while (thisIter.hasNext() && thatIter.hasNext()) {
527                Segment thisSegment = thisIter.next();
528                Segment thatSegment = thatIter.next();
529                int diff = thisSegment.compareTo(thatSegment);
530                if (diff != 0) return diff;
531            }
532            if (thisIter.hasNext()) return 1;
533            if (thatIter.hasNext()) return -1;
534            return 0;
535        }
536    
537        /**
538         * {@inheritDoc}
539         */
540        @Override
541        public String toString() {
542            return getString(Path.URL_ENCODER);
543        }
544    
545    }