001/**
002 * PointArray -- A collection of PointString objects that make up a "list of strings" or
003 * an "array of points".
004 *
005 * Copyright (C) 2003-2025, 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.1 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.Collection;
024import static java.util.Objects.requireNonNull;
025import javax.measure.converter.ConversionException;
026import javax.measure.quantity.Area;
027import javax.measure.quantity.Length;
028import javax.measure.unit.NonSI;
029import javax.measure.unit.Unit;
030import javolution.context.ObjectFactory;
031import javolution.context.StackContext;
032import javolution.util.FastTable;
033import javolution.xml.XMLFormat;
034import javolution.xml.XMLReferenceResolver;
035import javolution.xml.stream.XMLStreamException;
036
037/**
038 * A <code>PointArray</code> is a collection of {@link PointString} objects that make up a
039 * "list of strings of points". Any number of strings may be added to an array. All the
040 * strings in an array must have the same dimensions. In order to draw the array, all the
041 * strings in a array must also have the same number of points.
042 * <p>
043 * WARNING: This list allows geometry to be stored in different units. If consistent units
044 * are required, then the user must specifically convert the list items.
045 * </p>
046 *
047 * <p> Modified by: Joseph A. Huwaldt </p>
048 *
049 * @author Joseph A. Huwaldt, Date: March 5, 2003
050 * @version February 17, 2025
051 *
052 * @param <E> The type of GeomPoint stored in this array of points.
053 */
054@SuppressWarnings({"serial", "CloneableImplementsClone"})
055public final class PointArray<E extends GeomPoint> extends AbstractPointGeomList<PointArray<E>, PointString<E>> {
056
057    private FastTable<PointString<E>> _list;
058
059    /**
060     * Return the list underlying this geometry list.
061     *
062     * @return The list underlying this geometry list.
063     */
064    @Override
065    protected FastTable<PointString<E>> getList() {
066        return _list;
067    }
068
069    /**
070     * Returns a new, empty, preallocated or recycled <code>PointArray</code>
071     * instance (on the stack when executing in a <code>StackContext</code>),
072     * that can store a list of {@link PointString} objects.
073     *
074     * @return A new, empty PointArray
075     */
076    public static PointArray newInstance() {
077        PointArray list = FACTORY.object();
078        list._list = FastTable.newInstance();
079        return list;
080    }
081
082    /**
083     * Returns a new, preallocated or recycled <code>PointArray</code> instance
084     * (on the stack when executing in a <code>StackContext</code>) with the
085     * specified name, that can store a list of {@link PointString} objects.
086     *
087     * @param name The name to be assigned to this list (may be <code>null</code>).
088     * @return A new, empty PointArray
089     */
090    public static PointArray newInstance(String name) {
091        PointArray list = PointArray.newInstance();
092        list.setName(name);
093        return list;
094    }
095
096    /**
097     * Return a PointArray made up of the {@link PointString} objects in the
098     * specified collection.
099     *
100     * @param <E> The type of GeomPoint stored in this array of points.
101     * @param name The name to be assigned to this list (may be <code>null</code>).
102     * @param elements A collection that contains a set of strings. May not be null.
103     * @return A new PointArray containing the elements in the specified collection.
104     */
105    public static <E extends GeomPoint> PointArray<E> valueOf(String name, Collection<? extends PointString<E>> elements) {
106        for (Object element : elements) {
107            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
108            if (!(element instanceof PointString))
109                throw new ClassCastException(MessageFormat.format(
110                        RESOURCES.getString("listElementTypeErr"), "PointArray", "PointString"));
111        }
112
113        PointArray<E> list = PointArray.newInstance(name);
114        list.addAll(elements);
115
116        return list;
117    }
118
119    /**
120     * Return a PointArray made up of the {@link PointString} objects in the
121     * specified array.
122     *
123     * @param <E> The type of GeomPoint stored in this array of points.
124     * @param name The name to be assigned to this string (may be <code>null</code>).
125     * @param elements A list that contains a set of strings. May not be null.
126     * @return A new PointArray containing the elements in the specified array.
127     */
128    public static <E extends GeomPoint> PointArray<E> valueOf(String name, PointString<E>... elements) {
129        requireNonNull(elements);
130        PointArray<E> list = PointArray.newInstance(name);
131        list.addAll(elements);
132
133        return list;
134    }
135
136    /**
137     * Return a PointArray made up of the {@link PointString} objects in the
138     * specified array.
139     *
140     * @param <E> The type of GeomPoint stored in this array of points.
141     * @param elements A list that contains a set of strings. May not be null.
142     * @return A new PointArray containing the elements in the specified array.
143     */
144    public static <E extends GeomPoint> PointArray<E> valueOf(PointString<E>... elements) {
145        return PointArray.valueOf(null, elements);
146    }
147
148    /**
149     * Returns the {@link GeomPoint} at the specified pair of indices in this
150     * array. This is a convenience method that is identical to
151     * <code>array.get(row).get(col)</code>.
152     *
153     * @param row The row (string or "T") index of the panel to be returned
154     * @param col The column (point in a string or "S") index of the panel to be returned
155     * @return The point at the specified row and column in this array.
156     * @throws IndexOutOfBoundsException if there the specified indices do not
157     * exist in this array.
158     */
159    public E get(int row, int col) throws IndexOutOfBoundsException {
160        return this.get(row).get(col);
161    }
162
163    /**
164     * Return a column of points from this array as a new PointString object. A column is
165     * made up of a point from each string of the array.
166     *
167     * @param col The column of points to return.
168     * @return A new PointString object containing the points from the specified column of
169     * this array.
170     * @throws IndexOutOfBoundsException if there the specified index does not exist in
171     * this array.
172     */
173    public PointString<E> getColumn(int col) throws IndexOutOfBoundsException {
174        PointString<E> output = PointString.newInstance();
175        int size = size();
176        for (int row = 0; row < size; ++row) {
177            output.add(get(row).get(col));
178        }
179        return output;
180    }
181    
182    /**
183     * Returns the range of elements in this list from the specified start and
184     * ending indexes.
185     *
186     * @param first index of the first element to return (0 returns the 1st
187     *  element, -1 returns the last, etc).
188     * @param last index of the last element to return (0 returns the 1st
189     *  element, -1 returns the last, etc).
190     * @return the list of elements in the given range from this list.
191     * @throws IndexOutOfBoundsException if the given index is out of range:
192     *  <code>index &ge;= size()</code>
193     */
194    @Override
195    public PointArray<E> getRange(int first, int last) {
196        first = normalizeIndex(first);
197        last = normalizeIndex(last);
198
199        PointArray<E> list = PointArray.newInstance();
200        for (int i = first; i <= last; ++i)
201            list.add(get(i));
202        return list;
203    }
204
205    /**
206     * Returns an new {@link PointArray} with the elements in this list in
207     * reverse order.
208     *
209     * @return A new PointArray with the elements in this list in reverse order.
210     * This reverses the rows of the array.
211     */
212    @Override
213    public PointArray<E> reverse() {
214        PointArray<E> list = PointArray.newInstance();
215        copyState(list);
216        int size = this.size();
217        for (int i = size - 1; i >= 0; --i) {
218            list.add(get(i));
219        }
220        return list;
221    }
222
223    /**
224     * Returns an new {@link PointArray} with the points in each string in this
225     * array in reverse order.
226     *
227     * @return A new PointArray with the points in each string in reverse order.
228     * This reverses the columns of the array.
229     */
230    public PointArray<E> reverseStrings() {
231        PointArray<E> list = PointArray.newInstance();
232        copyState(list);
233        int size = this.size();
234        for (int i = 0; i < size; ++i) {
235            PointString<E> str = this.get(i);
236            list.add(str.reverse());
237        }
238        return list;
239    }
240
241    /**
242     * Returns a new {@link PointArray} that is the transpose this array (rows
243     * and columns swapped). This method can only be called if all the strings
244     * in this array have the same number of points in them.
245     *
246     * @return A new PointArray that is the transpose of this array.
247     * @throws IndexOutOfBoundsException if the number of points is not the same
248     * in each string in this array.
249     */
250    public PointArray<E> transpose() throws IndexOutOfBoundsException {
251
252        //  Check for string consistancy in this array.
253        checkStringConsistancy();
254
255        //  Build up the transposed array.
256        PointArray<E> arr = PointArray.newInstance();
257        copyState(arr);
258        int numStrings = size();
259        int numPointsPerString = get(0).size();
260        for (int i = 0; i < numPointsPerString; ++i) {
261            PointString<E> str = PointString.newInstance();
262            for (int j = 0; j < numStrings; ++j)
263                str.add(this.get(j).get(i));
264            arr.add(str);
265        }
266
267        return arr;
268    }
269
270    /**
271     * Returns a new {@link PointArray} that is identical to this array but with every
272     * other row (string of points) and column (points in the strings) removed. The 1st
273     * row and column and the last row and column are always retained. If there are less
274     * than 3 columns in the array, then a new array is returned that contains the same
275     * columns as this array. If there are less than 3 rows, then a new array is returned
276     * that contains the same rows as this array.
277     *
278     * @return A new PointArray identical to this array, but with every other row and
279     * column removed.
280     */
281    public PointArray<E> thin() {
282        PointArray<E> arr1 = thinRows();
283        PointArray<E> arr2 = arr1.thinColumns();
284        PointArray.recycle(arr1);
285        return arr2;
286    }
287
288    /**
289     * Returns a new {@link PointArray} that is identical to this array but with every
290     * other row (string of points) removed. The 1st row (index = 0) and last row (index =
291     * size()-1) are always retained. If there are less than 3 rows in the array, then a
292     * new array is returned that contains the same rows as this array.
293     *
294     * @return A new PointArray that is identical to this array but with every other row
295     * removed.
296     */
297    public PointArray<E> thinRows() {
298        PointArray<E> list = PointArray.newInstance();
299        copyState(list);
300
301        int size = size();
302        if (size < 3) {
303            list.addAll(this);
304            return list;
305        }
306
307        int sizem1 = size - 1;
308        for (int i = 0; i < sizem1; i += 2) {
309            list.add(get(i));
310        }
311        list.add(get(size - 1));
312
313        return list;
314    }
315
316    /**
317     * Returns a new {@link PointArray} that is identical to this array but with every
318     * other column (points in strings) removed. The 1st column (index = 0) and last
319     * column (index = get(0).size()-1) are always retained. If there are less than 3
320     * columns in the array, then a new array is returned that contains the same columns
321     * as this array.
322     *
323     * @return A new PointArray that is identical to this array, but with every other
324     * column (points in the strings) removed.
325     * @throws IndexOutOfBoundsException if the number of points is not the same in each
326     * string in this array.
327     */
328    public PointArray<E> thinColumns() throws IndexOutOfBoundsException {
329
330        //  Check for string consistancy in this array.
331        checkStringConsistancy();
332
333        PointArray<E> list = PointArray.newInstance();
334        copyState(list);
335
336        int size = this.size();
337        for (int i = 0; i < size; ++i) {
338            PointString<E> str = this.get(i);
339            list.add(str.thin());
340        }
341
342        return list;
343    }
344
345    /**
346     * Returns a new {@link PointArray} that is identical to this array but with new rows
347     * (strings of points) and columns (points in each string) of points linearly
348     * interpolated between each of the existing rows and columns. If there are less than
349     * 2 columns in the array, then a new array is returned that contains the one column
350     * in this array. If there are less than 2 points in each string of the array, then a
351     * new array is returned that contains the one point in each string in this array.
352     *
353     * @return A new PointArray with new rows and columns linearly interpolated between
354     * each of the existing rows and columns.
355     * @throws IndexOutOfBoundsException if the number of points is not the same in each
356     * string in this array.
357     */
358    public PointArray<E> enrich() throws IndexOutOfBoundsException {
359        PointArray<E> arr1 = enrichColumns();
360        PointArray<E> arr2 = arr1.enrichRows();
361        PointArray.recycle(arr1);
362        return arr2;
363    }
364
365    /**
366     * Returns a new {@link PointArray} that is identical to this array but with
367     * a new row (string of points) linearly interpolated between each of the existing
368     * rows. If there are less than 2 rows in the array, then a new array
369     * is returned that contains the one row in this array.
370     *
371     * @return A new PointArray with new rows linearly interpolated between
372     *  each of the existing rows.
373     * @throws IndexOutOfBoundsException if the number of points is not the same
374     *  in each string in this array.
375     */
376    public PointArray<E> enrichRows() throws IndexOutOfBoundsException {
377
378        //  Check for string consistancy in this array.
379        checkStringConsistancy();
380
381        PointArray list = PointArray.newInstance();
382        copyState(list);
383
384        int size = size();
385        if (size < 2) {
386            list.add(get(0));
387            return list;
388        }
389
390        PointString<? extends GeomPoint> str1 = get(0);
391        list.add(str1);
392        int numPnts = str1.size();  //  Number of points in each string.
393
394        for (int i = 1; i < size; ++i) {
395            PointString<? extends GeomPoint> str2 = get(i);
396            PointString stra = PointString.newInstance();
397            for (int j = 0; j < numPnts; ++j) {
398                GeomPoint p1 = str1.get(j);
399                GeomPoint p2 = str2.get(j);
400                Point pa = p1.plus(p2).divide(2);   //  Calculate the average point.
401                stra.add(pa);
402            }
403            list.add(stra);
404            list.add(str2);
405            str1 = str2;
406        }
407
408        return list;
409    }
410
411    /**
412     * Returns a new {@link PointArray} that is identical to this array but with
413     * a new column (point) linearly interpolated between each point of the existing
414     * strings. If there are less than 2 points in each string of the array,
415     * then a new array is returned that contains the one point in each string
416     * in this array.
417     *
418     * @return A new PointArray with new column linearly interpolated between
419     *  each of the existing columns.
420     * @throws IndexOutOfBoundsException if the number of points is not the same
421     *  in each string in this array.
422     */
423    public PointArray<E> enrichColumns() throws IndexOutOfBoundsException {
424
425        //  Check for string consistancy in this array.
426        checkStringConsistancy();
427
428        PointArray list = PointArray.newInstance();
429        copyState(list);
430
431        int size = this.size();
432        for (int i = 0; i < size; ++i) {
433            PointString str = this.get(i);
434            list.add(str.enrich());
435        }
436
437        return list;
438    }
439
440    /**
441     * Return a TriangleList containing a simple triangulation of the input array of
442     * quadrilateral panels. The input panels are triangulated by dividing each quad,
443     * formed by two adjacent strings of points, into two triangles. Any subranges or
444     * transforms on the input points is lost (using copyToReal()) by this triangulation.
445     *
446     * @return A new TriangleList containing a triangulation of the this array of
447     * quadrilateral panels.
448     * @see #copyToReal() 
449     */
450    public TriangleList<Triangle> triangulate() {
451        return TriangleList.triangulateQuads(this);
452    }
453
454    /**
455     * Return the equivalent of this list converted to the specified number of
456     * physical dimensions. If the number of dimensions is greater than this
457     * element, then zeros are added to the additional dimensions. If the number
458     * of dimensions is less than this element, then the extra dimensions are
459     * simply dropped (truncated). If the new dimensions are the same as the
460     * dimension of this element, then this list is simply returned.
461     *
462     * @param newDim The dimension of the element to return.
463     * @return The equivalent of this list converted to the new dimensions.
464     */
465    @Override
466    public PointArray<E> toDimension(int newDim) {
467        if (getPhyDimension() == newDim)
468            return this;
469        PointArray<E> newList = PointArray.newInstance();
470        copyState(newList);
471        int size = this.size();
472        for (int i = 0; i < size; ++i) {
473            PointString element = this.get(i);
474            newList.add(element.toDimension(newDim));
475        }
476        return newList;
477    }
478
479    /**
480     * Returns the equivalent to this list but with <I>all</I> the elements stated in the
481     * specified unit.
482     *
483     * @param unit the length unit of the list to be returned. May not be null.
484     * @return an equivalent to this list but stated in the specified unit.
485     * @throws ConversionException if the the input unit is not a length unit.
486     */
487    @Override
488    public PointArray to(Unit<Length> unit) {
489        requireNonNull(unit);
490        PointArray list = PointArray.newInstance();
491        copyState(list);
492        int size = this.size();
493        for (int i = 0; i < size; ++i) {
494            PointString e = this.get(i);
495            list.add(e.to(unit));
496        }
497        return list;
498    }
499
500    /**
501     * Returns a copy of this <code>PointArray</code> instance
502     * {@link javolution.context.AllocatorContext allocated} by the calling
503     * thread (possibly on the stack).
504     *
505     * @return an identical and independent copy of this object.
506     */
507    @Override
508    public PointArray<E> copy() {
509        return copyOf(this);
510    }
511
512    /**
513     * Return a copy of this object with any transformations or subranges
514     * removed (applied).
515     *
516     * @return A copy of this list with any sub-element transformations or
517     * subranges removed.
518     */
519    @Override
520    public PointArray copyToReal() {
521        PointArray newList = PointArray.newInstance();
522        copyState(newList);
523        int size = this.size();
524        for (int i = 0; i < size; ++i) {
525            PointString element = this.get(i);
526            newList.add(element.copyToReal());
527        }
528        return newList;
529    }
530
531    /**
532     * Return the total number of quadrilateral panels in this array of points.
533     *
534     * @return the total number of panels in this array.
535     * @throws IndexOutOfBoundsException if the strings in this array have
536     * different lengths.
537     */
538    public int getNumberOfPanels() throws IndexOutOfBoundsException {
539        int numStrings = size();
540        if (numStrings < 2)
541            return 0;
542
543        //  Check for string consistancy in this array.
544        checkStringConsistancy();
545
546        int numPointsPerString = get(0).size();
547        if (numPointsPerString < 2)
548            return 0;
549
550        return (numStrings - 1) * (numPointsPerString - 1);
551    }
552
553    /**
554     * Return the total surface area of all the panels formed by this array of
555     * points.
556     *
557     * @return The total surface area of all the panels formed by this array of
558     * points.
559     */
560    public Parameter<Area> getArea() {
561        //  Break the array up into triangle strips and sum the area of all the triangles.
562        //  This code is essentially a repeat of TriangleList.getArea(), but is faster
563        //  since it doesn't create as much garbage.
564
565        StackContext.enter();
566        try {
567            //  Check for degenerate cases.
568            Unit<Area> areaUnit = getUnit().pow(2).asType(Area.class);
569            Parameter<Area> area = Parameter.ZERO_AREA.to(areaUnit);
570            int numStr = size();
571            if (numStr < 2)
572                return StackContext.outerCopy(area);
573            int numPnts = get(0).size();
574            if (numPnts < 2)
575                return StackContext.outerCopy(area);
576
577            int numPntsm1 = numPnts - 1;
578            int numStrm1 = numStr - 1;
579            for (int i = 0; i < numStrm1; ++i) {
580                PointString<E> str1 = get(i);
581                PointString<E> str2 = get(i + 1);
582                for (int j = 0; j < numPntsm1; ++j) {
583                    GeomPoint p1 = str1.get(j);
584                    GeomPoint p2 = str1.get(j + 1);
585                    GeomPoint p3 = str2.get(j);
586                    GeomPoint p4 = str2.get(j + 1);
587
588                    //  Triange #1
589                    Parameter<Length> a = p1.distance(p2);
590                    Parameter<Length> b = p2.distance(p4);
591                    Parameter<Area> dA = a.times(b).asType(Area.class);
592                    area = area.plus(dA);
593
594                    //  Triange #2
595                    a = p3.distance(p4);
596                    b = p3.distance(p1);
597                    dA = a.times(b).asType(Area.class);
598                    area = area.plus(dA);
599                }
600            }
601
602            return StackContext.outerCopy(area.times(0.5));
603
604        } finally {
605            StackContext.exit();
606        }
607    }
608
609    /**
610     * Returns transformed version of this element. The returned object
611     * implements {@link GeomTransform} and contains transformed versions of the
612     * contents of this list as children.
613     *
614     * @param transform The transform to apply to this geometry element. May not be null.
615     * @return A transformed version of this geometry element.
616     * @throws DimensionException if this element is not 3D.
617     */
618    @Override
619    public PointArray getTransformed(GTransform transform) {
620        requireNonNull(transform);
621        PointArray list = PointArray.newInstance();
622        copyState(list);
623        int size = this.size();
624        for (int i = 0; i < size; ++i) {
625            PointString element = this.get(i);
626            list.add(element.getTransformed(transform));
627        }
628        return list;
629    }
630
631    /**
632     * Replaces the {@link PointString} at the specified position in this list with the
633     * specified element. Null elements are ignored. The input element must have the same
634     * physical dimensions as the other items in this list, or an exception is thrown.
635     *
636     * @param index   The index of the element to replace (0 returns the 1st element, -1
637     *                returns the last, -2 returns the 2nd from last, etc).
638     * @param element The element to be stored at the specified position.
639     *                <code>null</code> elements are ignored.
640     * @return The element previously at the specified position in this list. May not be
641     *         null.
642     * @throws java.lang.IndexOutOfBoundsException - if <code>index &gt; size()</code>
643     * @throws DimensionException if the input element's dimensions are different from
644     * this list's dimensions.
645     */
646    @Override
647    public PointString<E> set(int index, PointString<E> element) {
648        return super.set(index, requireNonNull(element));
649    }
650
651    /**
652     * Inserts the specified {@link PointString} at the specified position in this list.
653     * Shifts the element currently at that position (if any) and any subsequent elements
654     * to the right (adds one to their indices). Null values are ignored. The input
655     * value must have the same physical dimensions as the other items in this list, or
656     * an exception is thrown.
657     * <p>
658     * Note: If this method is used concurrent access must be synchronized (the list is
659     * not thread-safe).
660     * </p>
661     *
662     * @param index the index at which the specified element is to be inserted. (0 returns
663     *              the 1st element, -1 returns the last, -2 returns the 2nd from last,
664     *              etc).
665     * @param value the element to be inserted. May not be null.
666     * @throws IndexOutOfBoundsException if <code>index &gt; size()</code>
667     * @throws DimensionException if the input value dimensions are different from
668     * this list's dimensions.
669     */
670    @Override
671    public void add(int index, PointString<E> value) {
672        super.add(index, requireNonNull(value));
673    }
674
675    /**
676     * Inserts all of the {@link PointString} objects in the specified collection into
677     * this list at the specified position. Shifts the element currently at that position
678     * (if any) and any subsequent elements to the right (increases their indices). The
679     * new elements will appear in this list in the order that they are returned by the
680     * specified collection's iterator. The behavior of this operation is unspecified if
681     * the specified collection is modified while the operation is in progress. Note that
682     * this will occur if the specified collection is this list, and it's nonempty.  The
683     * input elements must have the same physical dimensions as the other items in this
684     * list, or an exception is thrown.
685     *
686     * @param index index at which to insert first element from the specified collection.
687     * @param c     elements to be inserted into this collection. May not be null.
688     * @return <code>true</code> if this collection changed as a result of the call.
689     * @throws DimensionException if the input element's dimensions are different from
690     * this list's dimensions.
691     */
692    @Override
693    public boolean addAll(int index, Collection<? extends PointString<E>> c) {
694        int thisSize = this.size();
695        for (Object element : c) {
696            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
697            if (!(element instanceof PointString))
698                throw new ClassCastException(MessageFormat.format(
699                        RESOURCES.getString("listElementTypeErr"), "PointArray", "PointString"));
700            if (thisSize != 0) {
701                if (((GeomElement)element).getPhyDimension() != this.getPhyDimension())
702                    throw new DimensionException(RESOURCES.getString("dimensionErr"));
703            }
704        }
705        return super.addAll(index, c);
706    }
707
708    /**
709     * Determines if each string in this array has the same number of points and
710     * throws an exception if they do not.
711     */
712    private void checkStringConsistancy() throws IndexOutOfBoundsException {
713
714        FastTable<PointString<E>> tlist = getList();
715        int numPointsPerString = tlist.get(0).size();
716        int numStrings = tlist.size();
717        for (int i = 0; i < numStrings; ++i) {
718            PointString str = tlist.get(i);
719            if (str.size() != numPointsPerString)
720                throw new IndexOutOfBoundsException(
721                        MessageFormat.format(RESOURCES.getString("arrStringSizeErr"), i));
722        }
723
724    }
725
726    /**
727     * Holds the default XML representation for this object.
728     */
729    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
730    protected static final XMLFormat<PointArray> XML = new XMLFormat<PointArray>(PointArray.class) {
731
732        @Override
733        public PointArray newInstance(Class<PointArray> cls, XMLFormat.InputElement xml) throws XMLStreamException {
734            PointArray obj = PointArray.newInstance();
735            return obj;
736        }
737
738        @Override
739        public void read(XMLFormat.InputElement xml, PointArray obj) throws XMLStreamException {
740            AbstractPointGeomList.XML.read(xml, obj);     // Call parent read.
741        }
742
743        @Override
744        public void write(PointArray obj, XMLFormat.OutputElement xml) throws XMLStreamException {
745            AbstractPointGeomList.XML.write(obj, xml);    // Call parent write.
746        }
747    };
748
749    //////////////////////
750    // Factory Creation //
751    //////////////////////
752    private static final ObjectFactory<PointArray> FACTORY = new ObjectFactory<PointArray>() {
753        @Override
754        protected PointArray create() {
755            return new PointArray();
756        }
757
758        @Override
759        protected void cleanup(PointArray obj) {
760            obj.reset();
761        }
762    };
763
764    /**
765     * Recycles a case instance immediately (on the stack when executing in a
766     * StackContext).
767     *
768     * @param instance The instance to be recycled.
769     */
770    public static void recycle(PointArray instance) {
771        FACTORY.recycle(instance);
772    }
773
774    /**
775     * Do not allow the default constructor to be used except by subclasses.
776     */
777    protected PointArray() {
778    }
779
780    private static <E2 extends GeomPoint> PointArray<E2> copyOf(PointArray<E2> original) {
781        PointArray<E2> o = PointArray.newInstance();
782        original.copyState(o);
783        int size = original.size();
784        for (int i = 0; i < size; ++i) {
785            PointString<E2> element = original.get(i);
786            o.add(element.copy());
787        }
788        return o;
789    }
790
791    /**
792     * Tests the methods in this class.
793     *
794     * @param args  Command-line arguments (ignored).
795     */
796    public static void main(String args[]) {
797        System.out.println("Testing PointArray:");
798
799        Point p1 = Point.valueOf(0, 0, 0, NonSI.FOOT);
800        Point p2 = Point.valueOf(1, 0, 0, NonSI.FOOT);
801        Point p3 = Point.valueOf(2, 0, 0, NonSI.FOOT);
802        Point p4 = Point.valueOf(3, 0, 0, NonSI.FOOT);
803        PointString str1 = PointString.valueOf("String 1", p1, p2, p3, p4);
804        System.out.println("str1 = " + str1);
805
806        GTransform T1 = GTransform.newTranslation(Vector.valueOf(NonSI.FOOT, 0, 1, 0));
807        PointString str2 = str1.getTransformed(T1);
808        str2.setName("String 2");
809        System.out.println("str2 = " + str2);
810
811        PointString str3 = str2.getTransformed(T1);
812        str3.setName("String 3");
813        System.out.println("str3 = " + str3);
814
815        PointString str4 = str3.getTransformed(T1);
816        str4.setName("String 4");
817        System.out.println("str4 = " + str4);
818
819        PointArray arr1 = PointArray.valueOf("An array", str1, str2, str3, str4);
820        System.out.println("arr1 = " + arr1);
821        System.out.println("arr1.getPhyDimension() = " + arr1.getPhyDimension());
822        System.out.println("arr1.size()         = " + arr1.size());
823
824        System.out.println("arr1.getBoundsMin() = " + arr1.getBoundsMin());
825        System.out.println("arr1.getBoundsMax() = " + arr1.getBoundsMax());
826
827        GTransform T2 = GTransform.newRotationX(Parameter.valueOf(90, NonSI.DEGREE_ANGLE));
828        PointArray arr2 = arr1.getTransformed(T2);
829        System.out.println("\nRotate about X by 90 deg:");
830        System.out.println("arr2 = " + arr2);
831        System.out.println("arr2.getBoundsMin() = " + arr2.getBoundsMin());
832        System.out.println("arr2.getBoundsMax() = " + arr2.getBoundsMax());
833
834        //  Write out XML data.
835        try {
836            System.out.println();
837
838            // Creates some useful aliases for class names.
839            javolution.xml.XMLBinding binding = new GeomXMLBinding();
840
841            javolution.xml.XMLObjectWriter writer = javolution.xml.XMLObjectWriter.newInstance(System.out);
842            writer.setIndentation("    ");
843            writer.setBinding(binding);
844            writer.setReferenceResolver(new XMLReferenceResolver()); // Enables cross-references.
845            writer.write(arr1, "PointArray", PointArray.class);
846            writer.flush();
847
848            System.out.println();
849        } catch (Exception e) {
850            e.printStackTrace();
851        }
852
853    }
854
855}