001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.schematic.internal.document; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Iterator; 021import java.util.List; 022import java.util.NoSuchElementException; 023import org.modeshape.schematic.annotation.Immutable; 024import org.modeshape.schematic.document.Path; 025 026public class Paths { 027 028 protected static final Path EMPTY_PATH = new EmptyPath(); 029 030 protected static final void notNull( Object value, 031 String name ) { 032 if (value == null) throw new IllegalArgumentException("The '" + name + "' argument may not be null"); 033 } 034 035 protected static final String notNull( String value, 036 String name ) { 037 if (value == null) throw new IllegalArgumentException("The '" + name + "' argument may not be null"); 038 value = value.trim(); 039 return value; 040 } 041 042 public static Path rootPath() { 043 return EMPTY_PATH; 044 } 045 046 public static Path path( String fieldName ) { 047 return new SinglePath(notNull(fieldName, "fieldName")); 048 } 049 050 public static Path path( String... fieldNames ) { 051 notNull(fieldNames, "fieldNames"); 052 switch (fieldNames.length) { 053 case 0: 054 return EMPTY_PATH; 055 case 1: 056 return new SinglePath(fieldNames[0]); 057 default: 058 return new MultiSegmentPath(Arrays.asList(fieldNames)); 059 } 060 } 061 062 public static Path path( List<String> fieldNames ) { 063 notNull(fieldNames, "fieldNames"); 064 switch (fieldNames.size()) { 065 case 0: 066 return EMPTY_PATH; 067 case 1: 068 return new SinglePath(fieldNames.get(0)); 069 default: 070 // make copy so we're guaranteed to be immutable 071 return new MultiSegmentPath(new ArrayList<>(fieldNames)); 072 } 073 } 074 075 public static Path path( Path path, 076 String fieldName ) { 077 notNull(path, "path"); 078 return path.with(notNull(fieldName, "fieldName")); 079 } 080 081 public static Path path( Path path, 082 String... fieldNames ) { 083 notNull(path, "path"); 084 notNull(fieldNames, "fieldNames"); 085 if (fieldNames.length == 0) return path; 086 ArrayList<String> names = new ArrayList<>(path.size() + fieldNames.length); 087 int i = 0; 088 for (String name : path) { 089 names.add(notNull(name, "fieldNames[" + i++ + "]")); 090 } 091 for (String name : fieldNames) { 092 names.add(notNull(name, "fieldNames[" + i++ + "]")); 093 } 094 return new MultiSegmentPath(names); 095 } 096 097 public static Path path( Path path, 098 List<String> fieldNames ) { 099 notNull(path, "path"); 100 notNull(fieldNames, "fieldNames"); 101 if (fieldNames.isEmpty()) return path; 102 ArrayList<String> names = new ArrayList<>(path.size() + fieldNames.size()); 103 int i = 0; 104 for (String name : path) { 105 names.add(notNull(name, "fieldNames[" + i++ + "]")); 106 } 107 for (String name : fieldNames) { 108 names.add(notNull(name, "fieldNames[" + i++ + "]")); 109 } 110 return new MultiSegmentPath(names); 111 } 112 113 @Immutable 114 protected static final class EmptyPath implements Path { 115 @Override 116 public Iterator<String> iterator() { 117 return new Iterator<String>() { 118 @Override 119 public boolean hasNext() { 120 return false; 121 } 122 123 @Override 124 public String next() { 125 throw new NoSuchElementException(); 126 } 127 128 @Override 129 public void remove() { 130 throw new UnsupportedOperationException(); 131 } 132 }; 133 } 134 135 @Override 136 public String get( int index ) { 137 throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); 138 } 139 140 @Override 141 public String getLast() { 142 return null; 143 } 144 145 @Override 146 public String getFirst() { 147 return null; 148 } 149 150 @Override 151 public int size() { 152 return 0; 153 } 154 155 @Override 156 public boolean startsWith( Path ancestor ) { 157 return ancestor.size() == 0; 158 } 159 160 @Override 161 public Path with( String fieldName ) { 162 return fieldName != null ? new SinglePath(notNull(fieldName, "fieldName")) : this; 163 } 164 165 @Override 166 public Path parent() { 167 return EMPTY_PATH; 168 } 169 170 @Override 171 public int compareTo( Path that ) { 172 if (that == this) return 0; 173 if (that instanceof EmptyPath) return 0; 174 return 0 - that.size(); 175 } 176 177 @Override 178 public int hashCode() { 179 return 1; 180 } 181 182 @Override 183 public boolean equals( Object obj ) { 184 if (obj == this) return true; 185 if (obj instanceof Path) { 186 Path that = (Path)obj; 187 return that.size() == 0; 188 } 189 return false; 190 } 191 192 @Override 193 public String toString() { 194 return ""; 195 } 196 } 197 198 @Immutable 199 protected static final class SinglePath implements Path { 200 201 private final String fieldName; 202 203 protected SinglePath( String fieldName ) { 204 this.fieldName = fieldName; 205 } 206 207 @Override 208 public Iterator<String> iterator() { 209 return new Iterator<String>() { 210 private boolean done = false; 211 212 @Override 213 public boolean hasNext() { 214 return !done; 215 } 216 217 @SuppressWarnings( "synthetic-access" ) 218 @Override 219 public String next() { 220 done = true; 221 return fieldName; 222 } 223 224 @Override 225 public void remove() { 226 throw new UnsupportedOperationException(); 227 } 228 }; 229 } 230 231 @Override 232 public String get( int index ) { 233 if (index != 0) throw new IndexOutOfBoundsException("Index: " + index + ", Size: 1"); 234 return fieldName; 235 } 236 237 @Override 238 public String getLast() { 239 return fieldName; 240 } 241 242 @Override 243 public String getFirst() { 244 return fieldName; 245 } 246 247 @Override 248 public Path with( String fieldName ) { 249 return Paths.path(this.fieldName, fieldName); 250 } 251 252 @Override 253 public Path parent() { 254 return EMPTY_PATH; 255 } 256 257 @Override 258 public int size() { 259 return 1; 260 } 261 262 @Override 263 public int hashCode() { 264 return fieldName.hashCode(); 265 } 266 267 @Override 268 public int compareTo( Path that ) { 269 if (that == this) return 0; 270 int diff = this.size() - that.size(); 271 if (diff != 0) return diff; 272 return this.fieldName.compareTo(that.get(0)); 273 } 274 275 @Override 276 public boolean startsWith( Path ancestor ) { 277 return this.equals(ancestor); 278 } 279 280 @Override 281 public boolean equals( Object obj ) { 282 if (obj == this) return true; 283 if (obj instanceof SinglePath) { 284 SinglePath that = (SinglePath)obj; 285 return this.fieldName.equals(that.fieldName); 286 } 287 if (obj instanceof Path) { 288 Path that = (Path)obj; 289 if (this.size() != that.size()) return false; 290 return this.fieldName.equals(that.get(0)); 291 } 292 return false; 293 } 294 295 @Override 296 public String toString() { 297 return fieldName; 298 } 299 } 300 301 @Immutable 302 protected static final class MultiSegmentPath implements Path { 303 304 private final List<String> fieldNames; 305 private transient String composite; 306 307 protected MultiSegmentPath( List<String> fieldNames ) { 308 assert fieldNames != null; 309 assert !fieldNames.isEmpty(); 310 this.fieldNames = fieldNames; 311 } 312 313 @Override 314 public Iterator<String> iterator() { 315 final Iterator<String> actualIter = fieldNames.iterator(); 316 return new Iterator<String>() { 317 @Override 318 public boolean hasNext() { 319 return actualIter.hasNext(); 320 } 321 322 @Override 323 public String next() { 324 return actualIter.next(); 325 } 326 327 @Override 328 public void remove() { 329 throw new UnsupportedOperationException(); 330 } 331 }; 332 } 333 334 @Override 335 public String get( int index ) { 336 return fieldNames.get(index); 337 } 338 339 @Override 340 public String getLast() { 341 return fieldNames.get(fieldNames.size() - 1); 342 } 343 344 @Override 345 public String getFirst() { 346 return fieldNames.get(0); 347 } 348 349 @Override 350 public int size() { 351 return fieldNames.size(); 352 } 353 354 @Override 355 public boolean startsWith( Path other ) { 356 if (other.size() > this.size()) return false; 357 Iterator<String> thatIter = other.iterator(); 358 Iterator<String> thisIter = this.iterator(); 359 while (thatIter.hasNext() && thisIter.hasNext()) { 360 if (!thisIter.next().equals(thatIter.next())) return false; 361 } 362 return !thatIter.hasNext(); 363 } 364 365 @Override 366 public Path with( String fieldName ) { 367 fieldName = notNull(fieldName, "fieldName"); 368 List<String> newFieldNames = new ArrayList<>(fieldNames.size() + 1); 369 newFieldNames.addAll(this.fieldNames); 370 newFieldNames.add(fieldName); 371 return new MultiSegmentPath(newFieldNames); 372 } 373 374 @Override 375 public Path parent() { 376 if (size() <= 1) return EMPTY_PATH; 377 List<String> parentFieldNames = this.fieldNames.subList(0, this.fieldNames.size() - 1); 378 return new MultiSegmentPath(parentFieldNames); 379 } 380 381 @Override 382 public int hashCode() { 383 return fieldNames.hashCode(); 384 } 385 386 @Override 387 public int compareTo( Path that ) { 388 if (that == this) return 0; 389 int diff = this.size() - that.size(); 390 if (diff != 0) return diff; 391 Iterator<String> thatIter = that.iterator(); 392 Iterator<String> thisIter = this.iterator(); 393 while (thatIter.hasNext()) { 394 int value = thisIter.next().compareTo(thatIter.next()); 395 if (value != 0) return value; 396 } 397 assert !thisIter.hasNext(); 398 return 0; 399 } 400 401 @Override 402 public boolean equals( Object obj ) { 403 if (obj == this) return true; 404 if (obj instanceof Path) { 405 Path that = (Path)obj; 406 if (this.size() != that.size()) return false; 407 Iterator<String> thatIter = that.iterator(); 408 Iterator<String> thisIter = this.iterator(); 409 while (thatIter.hasNext()) { 410 if (!thisIter.next().equals(thatIter.next())) return false; 411 } 412 assert !thisIter.hasNext(); 413 return true; 414 } 415 return false; 416 } 417 418 @Override 419 public String toString() { 420 if (composite == null) { 421 StringBuilder sb = new StringBuilder(); 422 Iterator<String> iter = fieldNames.iterator(); 423 if (iter.hasNext()) { 424 sb.append(iter.next()); 425 while (iter.hasNext()) { 426 sb.append('.'); 427 sb.append(iter.next()); 428 } 429 } 430 composite = sb.toString(); 431 } 432 return composite; 433 } 434 } 435}