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 &le; index &lt; 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 &le; index &lt; 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}