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