001/** 002 * AbstractGeomList -- Interface and implementation in common to all geometry element 003 * lists. 004 * 005 * Copyright (C) 2002-2017, by Joseph A. Huwaldt. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or modify it under the terms 008 * of the GNU Lesser General Public License as published by the Free Software Foundation; 009 * either version 2 of the License, or (at your option) any later version. 010 * 011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 013 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along with 016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 018 */ 019package geomss.geom; 020 021import jahuwaldt.js.param.Parameter; 022import java.text.MessageFormat; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.List; 026import static java.util.Objects.isNull; 027import static java.util.Objects.nonNull; 028import static java.util.Objects.requireNonNull; 029import javax.measure.quantity.Length; 030import javax.measure.unit.Unit; 031import javax.swing.event.ChangeListener; 032import javolution.context.StackContext; 033import javolution.lang.Immutable; 034import javolution.text.Text; 035import javolution.text.TextBuilder; 036import javolution.util.FastTable; 037import javolution.xml.XMLFormat; 038import javolution.xml.stream.XMLStreamException; 039 040/** 041 * Partial implementation of a list of {@link GeomElement} objects. The list will not 042 * accept the addition of <code>null</code> elements. 043 * <p> 044 * WARNING: This list allows geometry to be stored in different units and with different 045 * physical dimensions. If consistent units or dimensions are required, then the user must 046 * specifically convert the list items. 047 * </p> 048 * 049 * <p> Modified by: Joseph A. Huwaldt </p> 050 * 051 * @author Joseph A. Huwaldt, Date: March 31, 2000 052 * @version January 30, 2017 053 * 054 * @param <T> The sub-type of this AbstractGeomList. 055 * @param <E> The type of GeomElement contained in this list. 056 */ 057@SuppressWarnings({"serial", "CloneableImplementsClone"}) 058public abstract class AbstractGeomList<T extends AbstractGeomList, E extends GeomElement> 059 extends AbstractGeomElement<T> implements GeometryList<T, E> { 060 061 /** 062 * Reference to a change listener for this object's child curves. 063 */ 064 private final ChangeListener _childChangeListener = new ForwardingChangeListener(this); 065 066 /** 067 * Return the list underlying this geometry list. 068 * 069 * @return The list underlying this geometry list. 070 */ 071 protected abstract FastTable<E> getList(); 072 073 /** 074 * Return the input index normalized into the range 0 ≤ index < size(). This 075 * allows negative indexing (-1 referring to the last element in the list), but does 076 * not allow wrap-around positive indexing. 077 * 078 * @param index The index to be normalized (put into range 0 ≤ index < size()). 079 * @return The normalized index. 080 */ 081 protected int normalizeIndex(int index) { 082 int size = size(); 083 while (index < 0) 084 index += size; 085 return index; 086 } 087 088 /** 089 * Returns the number of elements in this list. If the list contains more than 090 * Integer.MAX_VALUE elements, returns Integer.MAX_VALUE. 091 * 092 * @return the number of elements in this list. 093 */ 094 @Override 095 public int size() { 096 return getList().size(); 097 } 098 099 /** 100 * Returns <code>true</code> if this collection contains no elements. 101 */ 102 @Override 103 public boolean isEmpty() { 104 return getList().isEmpty(); 105 } 106 107 /** 108 * Returns <code>true</code> if this list actually contains any geometry and 109 * <code>false</code> if this list is empty or contains only non-geometry items such 110 * as empty lists. 111 * 112 * @return true if this list actually contains geometry. 113 */ 114 @Override 115 public boolean containsGeometry() { 116 FastTable<E> lst = getList(); 117 int size = this.size(); 118 for (int i = 0; i < size; ++i) { 119 GeomElement element = lst.get(i); 120 if (element instanceof GeometryList) { 121 GeometryList subLst = (GeometryList)element; 122 if (subLst.containsGeometry()) 123 return true; 124 } else 125 return true; 126 } 127 return false; 128 } 129 130 /** 131 * Returns the element at the specified position in this list. 132 * 133 * @param index index of element to return (0 returns the 1st element, -1 returns the 134 * last, -2 returns the 2nd from last, etc). 135 * @return the element at the specified position in this list. 136 * @throws IndexOutOfBoundsException if the given index is out of range: 137 * <code>index > size()</code> 138 */ 139 @Override 140 public E get(int index) { 141 index = normalizeIndex(index); 142 return getList().get(index); 143 } 144 145 /** 146 * Returns the range of elements in this list from the specified start and ending 147 * indexes. 148 * 149 * @param first index of the first element to return (0 returns the 1st element, -1 150 * returns the last, etc). 151 * @param last index of the last element to return (0 returns the 1st element, -1 152 * returns the last, etc). 153 * @return the list of elements in the given range from this list. 154 * @throws IndexOutOfBoundsException if the given index is out of range: 155 * <code>index > size()</code> 156 */ 157 @Override 158 public abstract T getRange(int first, int last); 159 160 /** 161 * Returns a view of the portion of this list between fromIndex, inclusive, and 162 * toIndex, exclusive. (If fromIndex and toIndex are equal, the returned list is 163 * empty.) The returned list is backed by this list, so changes in the returned list 164 * are reflected in this list, and vice-versa. The returned list supports all of the 165 * optional list operations supported by this list. 166 * 167 * This method eliminates the need for explicit range operations (of the sort that 168 * commonly exist for arrays). Any operation that expects a list can be used as a 169 * range operation by passing a subList view instead of a whole list. For example, the 170 * following idiom removes a range of values from a list: <code> 171 * list.subList(from, to).clear();</code> Similar idioms may be constructed for 172 * <code>indexOf</code> and <code>lastIndexOf</code>, and all of the algorithms in the 173 * <code>Collections</code> class can be applied to a subList. 174 * 175 * The semantics of the list returned by this method become undefined if the backing 176 * list (i.e., this list) is <i>structurally modified</i> in any way other than via 177 * the returned list (structural modifications are those that change the size of this 178 * list, or otherwise perturb it in such a fashion that iterations in progress may 179 * yield incorrect results). 180 * 181 * @param fromIndex low endpoint (inclusive) of the subList. 182 * @param toIndex high endpoint (exclusive) of the subList. 183 * @return a view of the specified range within this list. 184 * @throws IndexOutOfBoundsException if the given index is out of range: 185 * <code>index > size()</code> 186 */ 187 @Override 188 public List<E> subList(int fromIndex, int toIndex) { 189 fromIndex = normalizeIndex(fromIndex); 190 toIndex = normalizeIndex(toIndex); 191 return getList().subList(fromIndex, toIndex); 192 } 193 194 /** 195 * Returns the first element from this list. 196 * 197 * @return the first element in this list. 198 */ 199 @Override 200 public E getFirst() { 201 return get(0); 202 } 203 204 /** 205 * Returns the last element from this list. 206 * 207 * @return the last element in this list. 208 */ 209 @Override 210 public E getLast() { 211 return get(size() - 1); 212 } 213 214 /** 215 * Returns the element with the specified name from this list. 216 * 217 * @param name The name of the element we are looking for in the list. 218 * @return The element matching the specified name. If the specified element name 219 * isn't found in the list, then <code>null</code> is returned. 220 */ 221 @Override 222 public E get(String name) { 223 224 E element = null; 225 int index = getIndexFromName(name); 226 if (index >= 0) 227 element = this.get(index); 228 229 return element; 230 } 231 232 /** 233 * Replaces the <@link GeomElement> at the specified position in this list with the 234 * specified element. Null elements are ignored. If the input element units are not 235 * the same as this list, the element is converted to the units of this list. 236 * 237 * @param index The index of the element to replace (0 returns the 1st element, -1 238 * returns the last, -2 returns the 2nd from last, etc). 239 * @param element The element to be stored at the specified position. 240 * May not be null. 241 * @return The element previously at the specified position in this list. 242 * @throws java.lang.IndexOutOfBoundsException - if <code>index > size()</code> 243 */ 244 @Override 245 public E set(int index, E element) { 246 requireNonNull(element); 247 if (!(element instanceof GeomElement)) 248 throw new ClassCastException(MessageFormat.format( 249 RESOURCES.getString("listElementTypeErr"), "list", "GeomElement")); 250 index = normalizeIndex(index); 251 252 E old = getList().set(index, element); 253 254 if (!(old instanceof Immutable)) 255 old.removeChangeListener(_childChangeListener); 256 if (!(element instanceof Immutable)) 257 element.addChangeListener(_childChangeListener); 258 259 fireChangeEvent(); // Notify change listeners. 260 261 return old; 262 } 263 264 /** 265 * Inserts the specified {@link GeomElement} at the specified position in this list. 266 * Shifts the element currently at that position (if any) and any subsequent elements 267 * to the right (adds one to their indices). Null values are ignored. 268 * <p> 269 * Note: If this method is used, concurrent access must be synchronized (the list is 270 * not thread-safe). 271 * </p> 272 * 273 * @param index the index at which the specified element is to be inserted. (0 returns 274 * the 1st element, -1 returns the last, -2 returns the 2nd from last, 275 * etc). 276 * @param value the element to be inserted. May not be null. 277 * @throws IndexOutOfBoundsException if <code>index > size()</code> 278 */ 279 @Override 280 public void add(int index, E value) { 281 requireNonNull(value); 282 if (!(value instanceof GeomElement)) 283 throw new ClassCastException(MessageFormat.format( 284 RESOURCES.getString("listElementTypeErr"), "list", "GeomElement")); 285 index = normalizeIndex(index); 286 getList().add(index, value); 287 288 if (!(value instanceof Immutable)) 289 value.addChangeListener(_childChangeListener); 290 291 fireChangeEvent(); // Notify change listeners. 292 293 } 294 295 /** 296 * Appends the specified {@link GeomElement} to the end of this list. Null values are 297 * ignored. 298 * <p> 299 * Note: If this method is used concurrent access must be synchronized (the table is 300 * not thread-safe). 301 * </p> 302 * 303 * @param value the element to be inserted. May not be null. 304 * @return <code>true</code> if this collection changed as a result of the call. 305 * @throws DimensionException if the input element's dimensions are different from 306 * this list's dimensions. 307 */ 308 @Override 309 public boolean add(E value) { 310 add(size(), value); 311 return true; 312 } 313 314 /** 315 * Adds all of the elements in the specified collection to this geometry element list. 316 * The behavior of this operation is undefined if the specified collection is modified 317 * while the operation is in progress. (This implies that the behavior of this call is 318 * undefined if the specified collection is this collection, and this collection is 319 * nonempty.) 320 * 321 * @param c elements to be inserted into this collection. May not be null. 322 * @return <code>true</code> if this collection changed as a result of the call. 323 */ 324 @Override 325 public boolean addAll(Collection<? extends E> c) { 326 return addAll(size(), c); 327 } 328 329 /** 330 * Inserts all of the {@link GeomElement} objects in the specified collection into 331 * this list at the specified position. Shifts the element currently at that position 332 * (if any) and any subsequent elements to the right (increases their indices). The 333 * new elements will appear in this list in the order that they are returned by the 334 * specified collection's iterator. The behavior of this operation is unspecified if 335 * the specified collection is modified while the operation is in progress. (Note that 336 * this will occur if the specified collection is this list, and it's nonempty.) 337 * 338 * @param index index at which to insert first element from the specified collection. 339 * @param c elements to be inserted into this collection. May not be null. 340 * @return <code>true</code> if this collection changed as a result of the call. 341 */ 342 @Override 343 public boolean addAll(int index, Collection<? extends E> c) { 344 for (Object element : c) { 345 requireNonNull(element, RESOURCES.getString("collectionElementsNullErr")); 346 if (!(element instanceof GeomElement)) 347 throw new ClassCastException(MessageFormat.format( 348 RESOURCES.getString("listElementTypeErr"), "list", "GeomElement")); 349 } 350 index = normalizeIndex(index); 351 boolean changed = getList().addAll(index, c); 352 if (changed) { 353 for (E element : c) { 354 if (!(element instanceof Immutable)) 355 element.addChangeListener(_childChangeListener); 356 } 357 fireChangeEvent(); 358 } 359 return changed; 360 } 361 362 /** 363 * Appends all of the elements in the specified array to this geometry element list. The 364 * behavior of this operation is undefined if the specified collection is modified 365 * while the operation is in progress. 366 * 367 * @param arr elements to be appended onto this collection. May not be null. 368 * @return <code>true</code> if this collection changed as a result of the call. 369 */ 370 @Override 371 public boolean addAll(E[] arr) { 372 return addAll(size(), arr); 373 } 374 375 /** 376 * Inserts all of the {@link GeomElement} objects in the specified array into this 377 * list at the specified position. Shifts the element currently at that position (if 378 * any) and any subsequent elements to the right (increases their indices). The new 379 * elements will appear in this list in the order that they are returned by the 380 * specified collection's iterator. 381 * 382 * @param index index at which to insert first element from the specified array. 383 * @param arr elements to be inserted into this collection. May not be null. 384 * @return <code>true</code> if this collection changed as a result of the call. 385 */ 386 @Override 387 public boolean addAll(int index, E[] arr) { 388 return addAll(index, Arrays.asList(requireNonNull(arr))); 389 } 390 391 /** 392 * Appends all of the elements in the specified list of arguments to this geometry 393 * element list. 394 * 395 * @param array elements to be inserted into this collection. May not be null. 396 * @return <code>true</code> if this collection changed as a result of the call. 397 */ 398 @Override 399 public boolean add(E... array) { 400 return add(size(), array); 401 } 402 403 /** 404 * Inserts all of the {@link GeomElement} objects in the specified list of arguments 405 * into this list at the specified position. Shifts the element currently at that 406 * position (if any) and any subsequent elements to the right (increases their 407 * indices). The new elements will appear in this list in the order that they are 408 * appeared in the array. 409 * 410 * @param index index at which to insert first element from the specified array. 411 * @param array elements to be inserted into this collection. May not be null. 412 * @return <code>true</code> if this collection changed as a result of the call. 413 */ 414 @Override 415 public boolean add(int index, E... array) { 416 return addAll(index, Arrays.asList(requireNonNull(array))); 417 } 418 419 /** 420 * Removes the element at the specified position in this list. Shifts any subsequent 421 * elements to the left (subtracts one from their indices). Returns the element that 422 * was removed from the list. 423 * 424 * @param index the index of the element to remove. (0 returns the 1st element, -1 425 * returns the last, -2 returns the 2nd from last, etc). 426 * @return the element previously at the specified position. 427 */ 428 @Override 429 public E remove(int index) { 430 index = normalizeIndex(index); 431 E old = getList().remove(index); 432 433 if (!(old instanceof Immutable)) 434 old.removeChangeListener(_childChangeListener); 435 436 fireChangeEvent(); // Notify change listeners. 437 438 return old; 439 } 440 441 /** 442 * Removes a single instance of the specified element from this collection, if it is 443 * present (optional operation). More formally, removes an element e such that 444 * <code>(o==null ? e==null : o.equals(e))</code>, if this collection contains one or 445 * more such elements. Returns true if this collection contained the specified element 446 * (or equivalently, if this collection changed as a result of the call). 447 * 448 * @param o element to be removed from this collection, if present. 449 * @return <code>true</code> if this collection changed as a result of the call. 450 */ 451 @Override 452 public boolean remove(Object o) { 453 boolean changed = getList().remove(o); 454 if (changed) { 455 if (o instanceof AbstractGeomElement && !(o instanceof Immutable)) { 456 ((AbstractGeomElement)o).removeChangeListener(_childChangeListener); 457 } 458 fireChangeEvent(); 459 } 460 return changed; 461 } 462 463 /** 464 * Removes the element with the specified name from this list. Shifts any subsequent 465 * elements to the left (subtracts one from their indices). Returns the element that 466 * was removed from the list. 467 * 468 * @param name the name of the element to remove. 469 * @return the element previously at the specified position. 470 */ 471 @Override 472 public E remove(String name) { 473 474 E element = null; 475 int index = getIndexFromName(name); 476 if (index >= 0) 477 element = this.remove(index); 478 479 return element; 480 } 481 482 /** 483 * Removes all of the elements from this collection. The collection will be empty 484 * after this call returns. 485 */ 486 @Override 487 public void clear() { 488 int size = size(); 489 for (int i = 0; i < size; ++i) { 490 E element = get(i); 491 if (!(element instanceof Immutable)) 492 element.removeChangeListener(_childChangeListener); 493 } 494 getList().clear(); 495 496 fireChangeEvent(); // Notify change listeners. 497 } 498 499 /** 500 * Returns an iterator over the elements in this list. 501 * 502 * @return an iterator over this list values. 503 */ 504 @Override 505 public java.util.Iterator<E> iterator() { 506 return getList().iterator(); 507 } 508 509 /** 510 * Returns a list iterator over the elements in this list. 511 * 512 * @return an iterator over this list values. 513 */ 514 @Override 515 public java.util.ListIterator<E> listIterator() { 516 return getList().listIterator(); 517 } 518 519 /** 520 * Returns a list iterator from the specified position. 521 * 522 * @param index the index of first value to be returned from the list iterator (by a 523 * call to the next method). 524 * @return a list iterator of the values in this table starting at the specified 525 * position in this list. 526 */ 527 @Override 528 public java.util.ListIterator<E> listIterator(int index) { 529 return getList().listIterator(index); 530 } 531 532 /** 533 * Returns an unmodifiable list view associated to this list. Attempts to modify the 534 * returned collection result in an UnsupportedOperationException being thrown. 535 * 536 * @return the unmodifiable view over this list. 537 */ 538 @Override 539 public List<E> unmodifiableList() { 540 return getList().unmodifiable(); 541 } 542 543 /** 544 * Returns a new {@link GeomList} with the elements in this list. 545 * 546 * @return A new GeomList with the elements in this list. 547 */ 548 @Override 549 public GeomList<E> getAll() { 550 GeomList<E> list = GeomList.newInstance(); 551 list.addAll(this); 552 return list; 553 } 554 555 /** 556 * Removes from this list all the elements that are contained in the specified 557 * collection. 558 * 559 * @param c collection that defines which elements will be removed from this list. 560 * @return <code>true</code> if this list changed as a result of the call. 561 */ 562 @Override 563 public boolean removeAll(Collection<?> c) { 564 boolean changed = getList().removeAll(c); 565 if (changed) { 566 for (Object o : c) { 567 if (o instanceof AbstractGeomElement && !(o instanceof Immutable)) 568 ((AbstractGeomElement)o).removeChangeListener(_childChangeListener); 569 } 570 fireChangeEvent(); 571 } 572 return changed; 573 } 574 575 /** 576 * Retains only the elements in this list that are contained in the specified 577 * collection. In other words, removes from this list all the elements that are not 578 * contained in the specified collection. 579 * 580 * @param c collection that defines which elements this set will retain. May not be null. 581 * @return <code>true</code> if this list changed as a result of the call. 582 */ 583 @Override 584 public boolean retainAll(Collection<?> c) { 585 boolean changed = getList().retainAll(requireNonNull(c)); 586 if (changed) 587 fireChangeEvent(); 588 return changed; 589 } 590 591 /** 592 * Returns an new {@link GeomList} with the elements in this list in reverse order. 593 */ 594 @Override 595 public abstract T reverse(); 596 597 /** 598 * Returns the index in this list of the first occurrence of the specified element, or 599 * -1 if the list does not contain this element. 600 * 601 * @param element The element to search for. 602 * @return the index in this List of the first occurrence of the specified element, or 603 * -1 if the List does not contain this element. 604 */ 605 @Override 606 public int indexOf(Object element) { 607 return getList().indexOf(element); 608 } 609 610 /** 611 * Returns the index in this list of the last occurrence of the specified element, or 612 * -1 if the list does not contain this element. More formally, returns the highest 613 * index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no 614 * such index. 615 * 616 * @param element The element to search for. 617 * @return the index in this list of the last occurrence of the specified element, or 618 * -1 if the list does not contain this element. 619 */ 620 @Override 621 public int lastIndexOf(Object element) { 622 return getList().lastIndexOf(element); 623 } 624 625 /** 626 * Return the index to the 1st geometry element in this list with the specified name. 627 * Objects with <code>null</code> names are ignored. 628 * 629 * @param name The name of the geometry element to find in this list 630 * @return The index to the named geometry element or -1 if it is not found. 631 */ 632 @Override 633 public int getIndexFromName(String name) { 634 if (name == null) 635 return -1; 636 637 List<E> list = getList(); 638 int result = -1; 639 int size = this.size(); 640 for (int i = 0; i < size; ++i) { 641 GeomElement element = list.get(i); 642 String eName = element.getName(); 643 if (name.equals(eName)) { 644 result = i; 645 break; 646 } 647 } 648 return result; 649 } 650 651 /** 652 * Returns true if this collection contains the specified element. More formally, 653 * returns true if and only if this collection contains at least one element e such 654 * that (o==null ? e==null : o.equals(e)). 655 * 656 * @param o object to be checked for containment in this collection. 657 * @return <code>true</code> if this collection contains the specified element. 658 */ 659 @Override 660 public boolean contains(Object o) { 661 return getList().contains(o); 662 } 663 664 /** 665 * Returns true if this collection contains all of the elements in the specified 666 * collection. 667 * 668 * @param c collection to be checked for containment in this collection. May not be null. 669 * @return <code>true</code> if this collection contains all of the elements in the 670 * specified collection. 671 */ 672 @Override 673 public boolean containsAll(Collection<?> c) { 674 return getList().containsAll(requireNonNull(c)); 675 } 676 677 /** 678 * Returns an array containing all of the elements in this collection. 679 */ 680 @Override 681 public Object[] toArray() { 682 return getList().toArray(); 683 } 684 685 /** 686 * Returns an array containing all of the elements in this collection. If the 687 * collection fits in the specified array, it is returned therein. Otherwise, a new 688 * array is allocated with the runtime type of the specified array and the size of 689 * this collection. 690 * 691 * @param <T> The type of elements in this collection. 692 * @param a the array into which the elements of the collection are to be stored, if 693 * it is big enough; otherwise, a new array of the same type is allocated 694 * for this purpose. 695 * @return an array containing the elements of the collection. 696 */ 697 @Override 698 @SuppressWarnings("SuspiciousToArrayCall") 699 public <T> T[] toArray(T[] a) { 700 return getList().toArray(a); 701 } 702 703 /** 704 * Return <code>true</code> if this geometry list contains valid and finite numerical 705 * components. A value of <code>false</code> will be returned if any of the elements 706 * in this list are invalid. 707 */ 708 @Override 709 public boolean isValid() { 710 List<E> lst = getList(); 711 int size = lst.size(); 712 for (int i = 0; i < size; ++i) { 713 E element = lst.get(i); 714 if (!element.isValid()) 715 return false; 716 } 717 return true; 718 } 719 720 /** 721 * Returns the unit in which the <I>first</I> geometry element in this list is stated. 722 * If the list contains no geometry elements, then the default unit is returned. 723 */ 724 @Override 725 public Unit<Length> getUnit() { 726 if (isEmpty() || !containsGeometry()) 727 return GeomUtil.getDefaultUnit(); 728 return elementWithGeometry().getUnit(); 729 } 730 731 /** 732 * Compares the specified object with this list of <code>GeomElement</code> objects 733 * for equality. Returns true if and only if both collections are of the same type and 734 * both collections contain equal elements in the same order. 735 * 736 * @param obj the object to compare with. 737 * @return <code>true</code> if this list is identical to that list; 738 * <code>false</code> otherwise. 739 */ 740 @Override 741 public boolean equals(Object obj) { 742 if (this == obj) 743 return true; 744 if ((obj == null) || (obj.getClass() != this.getClass())) 745 return false; 746 747 AbstractGeomList that = (AbstractGeomList)obj; 748 return this.getList().equals(that.getList()) 749 && super.equals(obj); 750 } 751 752 /** 753 * Returns the hash code for this <code>AbstractGeomList</code>. 754 * 755 * @return the hash code value. 756 */ 757 @Override 758 public int hashCode() { 759 int hash = 7; 760 761 hash = hash * 31 + getList().hashCode(); 762 hash = hash * 31 + super.hashCode(); 763 764 return hash; 765 } 766 767 /** 768 * Returns the text representation of this geometry element. 769 */ 770 @Override 771 public Text toText() { 772 TextBuilder tmp = TextBuilder.newInstance(); 773 String className = this.getClass().getName(); 774 tmp.append(className.substring(className.lastIndexOf(".") + 1)); 775 tmp.append(": {\n"); 776 int size = this.size(); 777 for (int i = 0; i < size; ++i) { 778 GeomElement e = this.get(i); 779 if (e instanceof AbstractGeomList) { 780 className = e.getClass().getName(); 781 tmp.append(className.substring(className.lastIndexOf(".") + 1)); 782 String name = e.getName(); 783 if (nonNull(name)) { 784 tmp.append("(\""); 785 tmp.append(name); 786 tmp.append("\")"); 787 } 788 tmp.append(": "); 789 tmp.append(RESOURCES.getString("size")); 790 tmp.append(" = "); 791 tmp.append(((AbstractGeomList)e).size()); 792 793 } else { 794 tmp.append(e.toText()); 795 } 796 if (i < size - 1) 797 tmp.append(",\n"); 798 else 799 tmp.append("\n"); 800 } 801 tmp.append("}"); 802 Text txt = tmp.toText(); 803 TextBuilder.recycle(tmp); 804 return txt; 805 } 806 807 /** 808 * Returns the number of physical dimensions of the geometry element. This 809 * implementation always returns 0. 810 */ 811 @Override 812 public int getPhyDimension() { 813 return 0; 814 } 815 816 /** 817 * Returns the number of parametric dimensions of the geometry element. This 818 * implementation always returns 0. 819 */ 820 @Override 821 public int getParDimension() { 822 return 0; 823 } 824 825 /** 826 * Return the coordinate point representing the minimum bounding box corner of this 827 * geometry element (e.g.: min X, min Y, min Z). The physical dimension of the 828 * returned point will be that of the highest physical dimension object in this list. 829 * 830 * @return The minimum bounding box coordinate for this geometry element. 831 * @throws IndexOutOfBoundsException if this list contains no geometry. 832 */ 833 @Override 834 public Point getBoundsMin() { 835 836 // Find any element that contains geometry. 837 GeomElement geom = elementWithGeometry(); 838 if (isNull(geom)) 839 throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry")); 840 841 StackContext.enter(); 842 try { 843 Point minPoint = geom.getBoundsMin(); 844 int dim = minPoint.getPhyDimension(); 845 846 int size = this.size(); 847 for (int i = 0; i < size; ++i) { 848 GeomElement element = this.get(i); 849 try { 850 851 // Get the element's minimum point. 852 Point emin = element.getBoundsMin(); 853 854 // Ensure that the physical dimensions match. 855 int eDim = emin.getPhyDimension(); 856 if (eDim > dim) { 857 minPoint = minPoint.toDimension(eDim); 858 dim = eDim; 859 } else if (eDim < dim) 860 emin = emin.toDimension(dim); 861 862 // Track the minimum point. 863 minPoint = minPoint.min(emin); 864 } catch (Exception ignored) { 865 } // Ignore elements that contain no geometry. 866 } 867 868 return StackContext.outerCopy(minPoint); 869 870 } finally { 871 StackContext.exit(); 872 } 873 } 874 875 /** 876 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 877 * X, max Y, max Z). The physical dimension of the returned point will be that of the 878 * highest physical dimension object in this list. 879 * 880 * @return The maximum bounding box coordinate for this geometry element. 881 * @throws IndexOutOfBoundsException if this list contains no elements. 882 */ 883 @Override 884 public Point getBoundsMax() { 885 886 // Find any element that contains geometry. 887 GeomElement geom = elementWithGeometry(); 888 if (isNull(geom)) 889 throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry")); 890 891 StackContext.enter(); 892 try { 893 Point maxPoint = geom.getBoundsMax(); 894 int dim = maxPoint.getPhyDimension(); 895 896 int size = this.size(); 897 for (int i = 0; i < size; ++i) { 898 GeomElement element = this.get(i); 899 try { 900 // Get the element's maximum point. 901 Point emax = element.getBoundsMax(); 902 903 // Ensure that the physical dimensions match. 904 int eDim = emax.getPhyDimension(); 905 if (eDim > dim) { 906 maxPoint = maxPoint.toDimension(eDim); 907 dim = eDim; 908 } else if (eDim < dim) 909 emax = emax.toDimension(dim); 910 911 // Track the maximum point. 912 maxPoint = maxPoint.max(emax); 913 } catch (Exception ignored) { 914 } // Ignore elements that contain no geometry. 915 } 916 917 return StackContext.outerCopy(maxPoint); 918 919 } finally { 920 StackContext.exit(); 921 } 922 } 923 924 /** 925 * Returns the most extreme point, either minimum or maximum, in the specified 926 * coordinate direction for the geometry in this list. 927 * 928 * @param dim An index indicating the dimension to find the min/max point for (0=X, 929 * 1=Y, 2=Z, etc). 930 * @param max Set to <code>true</code> to return the maximum value, <code>false</code> 931 * to return the minimum. 932 * @param tol Fractional tolerance to refine the min/max point position to if 933 * necessary. 934 * @return The point found on this element that is the min or max in the specified 935 * coordinate direction. 936 */ 937 @Override 938 public GeomPoint getLimitPoint(int dim, boolean max, double tol) { 939 940 // Find any element that contains geometry. 941 GeomElement geom = elementWithGeometry(); 942 if (isNull(geom)) 943 throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry")); 944 945 // Get the limit of whatever geometry element was returned from this list. 946 GeomPoint limPnt = geom.getLimitPoint(dim, max, tol); 947 Parameter<Length> lim = limPnt.get(dim); 948 949 // Loop over all the elements in this list, get their limit points and find the global limit point. 950 int size = this.size(); 951 for (int i = 0; i < size; ++i) { 952 GeomElement element = this.get(i); 953 if (!geom.equals(element)) { 954 try { 955 // First check to see if the bounds make it possible for this element to 956 // contain the limit point. 957 if (max) { 958 Point pmax = element.getBoundsMax(); 959 if (!pmax.get(dim).isGreaterThan(lim)) 960 continue; 961 } else { 962 Point pmin = element.getBoundsMin(); 963 if (!pmin.get(dim).isLessThan(lim)) 964 continue; 965 } 966 // It is possible that this element could contain the limit point, 967 // so search for this element's limit point. 968 GeomPoint limPnt2 = element.getLimitPoint(dim, max, tol); 969 if ((max && limPnt2.get(dim).isGreaterThan(lim)) || (!max && limPnt2.get(dim).isLessThan(lim))) { 970 limPnt = limPnt2; 971 lim = limPnt.get(dim); 972 } 973 } catch (Exception ignore) { 974 // Ignore elements that contain no geometry. 975 } 976 } 977 } 978 979 // Return the global limit point. 980 return limPnt; 981 } 982 983 /** 984 * Resets the internal state of this object to its default values. Subclasses that 985 * override this method must call <code>super.reset();</code> to ensure that the state 986 * is reset properly. 987 */ 988 @Override 989 public void reset() { 990 getList().reset(); 991 super.reset(); 992 } 993 994 /** 995 * Returns an element from this list that actually contains geometry or null if there 996 * is none. 997 */ 998 private E elementWithGeometry() { 999 FastTable<E> lst = getList(); 1000 int size = this.size(); 1001 for (int i = 0; i < size; ++i) { 1002 E element = lst.get(i); 1003 if (element instanceof AbstractGeomList) { 1004 AbstractGeomList subLst = (AbstractGeomList)element; 1005 element = (E)subLst.elementWithGeometry(); 1006 if (element != null) 1007 return element; 1008 } else 1009 return element; 1010 } 1011 return null; 1012 } 1013 1014 /** 1015 * Holds the default XML representation for this object. 1016 */ 1017 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 1018 protected static final XMLFormat<AbstractGeomList> XML = new XMLFormat<AbstractGeomList>(AbstractGeomList.class) { 1019 1020 @Override 1021 public void read(XMLFormat.InputElement xml, AbstractGeomList obj) throws XMLStreamException { 1022 AbstractGeomElement.XML.read(xml, obj); // Call parent read. 1023 FastTable lst = xml.get("Contents", FastTable.class); 1024 obj.addAll(lst); 1025 } 1026 1027 @Override 1028 public void write(AbstractGeomList obj, XMLFormat.OutputElement xml) throws XMLStreamException { 1029 AbstractGeomElement.XML.write(obj, xml); // Call parent write. 1030 xml.add(obj.getList(), "Contents", FastTable.class); 1031 } 1032 }; 1033 1034}