001/**
002 * PointString -- A collection of GeomPoint objects that make up a "string of points".
003 *
004 * Copyright (C) 2003-2025, by Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.geom;
019
020import jahuwaldt.js.param.Parameter;
021import java.io.Serializable;
022import java.text.MessageFormat;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.Iterator;
027import static java.util.Objects.requireNonNull;
028import javax.measure.converter.ConversionException;
029import javax.measure.quantity.Dimensionless;
030import javax.measure.quantity.Length;
031import javax.measure.unit.NonSI;
032import javax.measure.unit.SI;
033import javax.measure.unit.Unit;
034import javolution.context.ObjectFactory;
035import javolution.context.StackContext;
036import javolution.util.FastTable;
037import javolution.xml.XMLFormat;
038import javolution.xml.stream.XMLStreamException;
039
040/**
041 * A <code>PointString</code> is a collection of {@link GeomPoint} objects that make up a
042 * "string of points". Any number of points may be added to a string, but all points in a
043 * string must have the same dimensions.
044 * <p>
045 * WARNING: This list allows geometry to be stored in different units. If consistent units
046 * are required, then the user must specifically convert the list items.
047 * </p>
048 * <p> Modified by: Joseph A. Huwaldt </p>
049 * 
050 * @author Joseph A. Huwaldt, Date: March 5, 2003
051 * @version February 22, 2025
052 * 
053 * @param <E> The type of GeomPoint contained in this list of points.
054 */
055@SuppressWarnings({"serial", "CloneableImplementsClone"})
056public final class PointString<E extends GeomPoint> extends AbstractPointGeomList<PointString<E>,E> {
057
058    private FastTable<E> _list;
059
060    /**
061     * Return the list underlying this geometry list.
062     *
063     * @return The list underlying this geometry list.
064     */
065    @Override
066    protected FastTable<E> getList() {
067        return _list;
068    }
069
070    /**
071     * Returns a new, preallocated or recycled <code>PointString</code> instance
072     * (on the stack when executing in a <code>StackContext</code>), that can
073     * store a list of {@link GeomPoint} objects.
074     *
075     * @return A new PointString instance.
076     */
077    public static PointString newInstance() {
078        PointString list = FACTORY.object();
079        list._list = FastTable.newInstance();
080        return list;
081    }
082
083    /**
084     * Returns a new, preallocated or recycled <code>PointString</code> instance
085     * (on the stack when executing in a <code>StackContext</code>) with the
086     * specified name, that can store a list of {@link GeomPoint} objects.
087     *
088     * @param name The name to be assigned to this list (may be <code>null</code>).
089     * @return A new PointString instance with the specified name.
090     */
091    public static PointString newInstance(String name) {
092        PointString list = PointString.newInstance();
093        list.setName(name);
094        return list;
095    }
096
097    /**
098     * Return a PointString made up of the {@link GeomPoint} objects in the
099     * specified collection.
100     *
101     * @param <E> The type of GeomPoint contained in this list of points.
102     * @param name The name to be assigned to this list (may be <code>null</code>).
103     * @param elements A collection of points. May not be null.
104     * @return A new PointString instance made up of the specified points.
105     */
106    public static <E extends GeomPoint> PointString<E> valueOf(String name, Collection<E> elements) {
107        for (Object element : elements) {
108            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
109            if (!(element instanceof GeomPoint))
110                throw new ClassCastException(MessageFormat.format(
111                        RESOURCES.getString("listElementTypeErr"), "PointString", "GeomPoint"));
112        }
113
114        PointString<E> list = PointString.newInstance(name);
115        list.addAll(elements);
116        
117        return list;
118    }
119
120    /**
121     * Return a PointString made up of the {@link GeomPoint} objects in the specified
122     * array.
123     *
124     * @param <E>      The type of GeomPoint contained in this list of points.
125     * @param name     The name to be assigned to this list (may be <code>null</code>).
126     * @param elements An array of points. May not be null.
127     * @return A new PointString instance made up of the specified points.
128     */
129    public static <E extends GeomPoint> PointString<E> valueOf(String name, E... elements) {
130        requireNonNull(elements);
131        PointString<E> list = PointString.newInstance(name);
132        list.addAll(elements);
133
134        return list;
135    }
136
137    /**
138     * Return a PointString made up of the {@link GeomPoint} objects in the
139     * specified array.
140     *
141     * @param <E> The type of GeomPoint contained in this list of points.
142     * @param elements An array of points. May not be null.
143     * @return A new PointString instance made up of the specified points.
144     */
145    public static <E extends GeomPoint> PointString<E> valueOf(E... elements) {
146        return PointString.valueOf(null, elements);
147    }
148
149    /**
150     * Return the total number of points in this geometry element.
151     *
152     * @return The total number of points in this geometry element.
153     */
154    @Override
155    public int getNumberOfPoints() {
156        return size();
157    }
158    
159    /**
160     * Returns the range of elements in this list from the specified start and
161     * ending indexes.
162     *
163     * @param first index of the first element to return (0 returns the 1st
164     *  element, -1 returns the last, etc).
165     * @param last index of the last element to return (0 returns the 1st
166     *  element, -1 returns the last, etc).
167     * @return the list of elements in the given range from this list.
168     * @throws IndexOutOfBoundsException if the given index is out of range:
169     *  <code>index &ge; size()</code>
170     */
171    @Override
172    public PointString<E> getRange(int first, int last) {
173        first = normalizeIndex(first);
174        last = normalizeIndex(last);
175
176        PointString<E> list = PointString.newInstance();
177        for (int i=first; i <= last; ++i)
178            list.add(get(i));
179
180        return list;
181    }
182
183    /**
184     * Returns an new {@link PointString} with the elements in this list in
185     * reverse order.
186     *
187     * @return A new PointString with the elements in this string in reverse order.
188     */
189    @Override
190    public PointString<E> reverse() {
191        PointString<E> list = PointString.newInstance();
192        copyState(list);
193        int size = this.size();
194        for (int i=size-1; i >= 0; --i) {
195            list.add(get(i));
196        }
197        return list;
198    }
199
200    /**
201     * Returns a new {@link PointString} that is identical to this string but
202     * with every other point removed. The 1st point (index = 0) and last point
203     * (index = size()-1) are always retained. If there are less than 3 points
204     * in the string, then a new string is returned that contains the same
205     * points as this string.
206     *
207     * @return A new PointString identical to this one but with every other
208     *      point removed.
209     */
210    public PointString<E> thin() {
211        PointString<E> list = PointString.newInstance();
212        copyState(list);
213        
214        int size = size();
215        if (size < 3) {
216            list.addAll(this);
217            return list;
218        }
219        
220        int sizem1 = size - 1;
221        for (int i=0; i < sizem1; i += 2) {
222            list.add(get(i));
223        }
224        list.add(get(size-1));
225        
226        return list;
227    }
228    
229    /**
230     * Returns a new {@link PointString} that is identical to this string but
231     * with a new point linearly interpolated half-way between each of the
232     * existing points. If there are less than 2 points in the string, then a
233     * new string is returned that contains the same point as this string.
234     *
235     * @return A new PointString that is identical to this string but with a new
236     * point linearly interpolated half-way between each of the existing points.
237     */
238    public PointString enrich() {
239        PointString list = PointString.newInstance();
240        copyState(list);
241        
242        int size = size();
243        if (size < 2) {
244            list.add(get(0));
245            return list;
246        }
247        
248        GeomPoint p1 = get(0);
249        list.add(p1);
250        for (int i=1; i < size; ++i) {
251            GeomPoint p2 = get(i);
252            Point pa = p1.plus(p2).divide(2);   //  Calculate the average point.
253            list.add(pa);
254            list.add(p2);
255            p1 = p2;
256        }
257        
258        return list;
259    }
260    
261    /**
262     * Returns a new PointString that contains the points in this PointString with all
263     * duplicate points removed.
264     * 
265     * @param tol The tolerance for determining if the points in this PointString
266     * are approx. equal. May not be null.
267     * @return A new PointString that contains the points in this PointString with all
268     * duplicate points removed.
269     */
270    public PointString unique(Parameter<Length> tol) {
271        requireNonNull(tol);
272        PointString<GeomPoint> output = PointString.newInstance();
273        copyState(output);
274        output.addAll(_list);
275        for (int i = 0; i < output.size(); i++) {
276            for (int j = i + 1; j < output.size(); j++) {
277                if (output.get(i).isApproxEqual(output.get(j), tol)) {
278                    output.remove(j);
279                    --j;
280                }
281            }
282        }
283        return output;
284     }
285    
286    /**
287     * Returns the length of this PointString in terms of the sum of the distances between
288     * each consecutive point in the string.
289     *
290     * @return The length of the string: sum(point(i).distance(point(i-1))).
291     */
292    public Parameter<Length> length() {
293        StackContext.enter();
294        try {
295            int numPts = size();
296            Parameter d = Parameter.ZERO_LENGTH.to(get(0).getUnit());
297            for (int i = 1; i < numPts; ++i) {
298                Parameter<Length> dist = get(i).distance(get(i - 1));
299                if (!dist.isApproxZero())
300                    d = d.plus(dist);
301            }
302            return StackContext.outerCopy(d);
303        } finally {
304            StackContext.exit();
305        }
306    }
307
308    /**
309     * Calculate the average or centroid of all the points in this PointString.
310     *
311     * @return The average or centroid of all the points in this PointString.
312     */
313    public Point getAverage() {
314        return GeomUtil.averagePoints(this);
315    }
316    
317    /**
318     * Return <code>true</code> if this string of points is degenerate (i.e.: has length 
319     * less than the specified tolerance).
320     *
321     * @param tol The tolerance for determining if this string of points is degenerate. 
322     * May not be null.
323     * @return <code>true</code> if this string of points is degenerate
324     */
325    public boolean isDegenerate(Parameter<Length> tol) {
326        requireNonNull(tol);
327        Parameter<Length> cLength = length();
328        return cLength.compareTo(tol) <= 0;
329    }
330
331    /**
332     * Returns <code>true</code> if this string of points is a line to within the
333     * specified tolerance.
334     *
335     * @param tol The tolerance for determining if this string of points is a line.
336     * @return <code>true</code> if this string of points is a line
337     */
338    public boolean isLine(Parameter<Length> tol) {
339        //  Reference:  Piegl, L.A., Tiller, W., "Computing Offsets of NURBS Curves and Surfaces",
340        //              Computer Aided Design 31, 1999, pgs. 147-156.
341        requireNonNull(tol);
342
343        StackContext.enter();
344        try {
345            //  Extract the string end points.
346            GeomPoint p0 = get(0), p1 = get(-1);
347
348            //  If the end points are coincident, then it can't be a line.
349            Vector<Length> tv = p1.minus(p0).toGeomVector();
350            if (!tv.mag().isGreaterThan(tol))
351                return false;
352            
353            //  If there are only two non-coincident points, then it must be a line.
354            if (size() == 2)
355                return true;
356            
357            //  Get a unit vector for the potential line.
358            Vector<Dimensionless> uv = tv.toUnitVector();
359
360            //  Loop over all the points in the string.
361            Parameter c2 = tv.dot(tv);
362            int numPnts = size() - 1;
363            for (int i = 1; i < numPnts; ++i) {
364                GeomPoint pi = get(i);
365
366                //  Check distance of this point from infinite line p0-uv.
367                if (GeomUtil.pointLineDistance(pi, p0, uv).isGreaterThan(tol))
368                    return false;
369
370                //  See if the point falls in the segment p0 to p1.
371                Vector<Length> w = pi.minus(p0).toGeomVector();
372                Parameter c1 = w.dot(tv);
373                if (c1.getValue() < 0)
374                    return false;
375                if (c2.isLessThan(c1))
376                    return false;
377            }
378
379            //  Must be a straight line.
380            return true;
381
382        } finally {
383            StackContext.exit();
384        }
385    }
386
387    /**
388     * Generic/default tolerance for use in root finders, etc.
389     */
390    public static final double GTOL = 1e-6;
391
392    /**
393     * Return <code>true</code> if this string of points is planar or <code>false</code>
394     * if it is not.
395     *
396     * @param tol The geometric tolerance to use in determining if the string of points is
397     *            planar.
398     * @return <code>true</code> if this string of points is planar
399     */
400    public boolean isPlanar(Parameter<Length> tol) {
401        //  If the string is less than 3D, then it must be planar.
402        int numDims = getPhyDimension();
403        if (numDims <= 2)
404            return true;
405
406        //  If there are 3 or fewer points, then it must be planar.
407        int numPnts = size();
408        if (numPnts < 3)
409            return true;
410
411        StackContext.enter();
412        try {
413            //  Is this string of points degenerate?
414            if (isDegenerate(tol))
415                return true;
416
417            //  Is the string of points linear?
418            if (isLine(tol))
419                return true;
420
421            //  Extract 3 points from the string of points and form a plane form them.
422            GeomPoint p0 = get(0);
423            int idx1 = numPnts / 3;
424            if (idx1 == 0)
425                ++idx1;
426            int idx2 = 2 * numPnts / 3;
427            if (idx2 == idx1)
428                ++idx2;
429            GeomPoint p1 = get(idx1);
430            GeomPoint p2 = get(idx2);
431            Vector<Length> v1 = p1.minus(p0).toGeomVector();
432            Vector<Length> v2 = p2.minus(p0).toGeomVector();
433            Vector n = v1.cross(v2);
434            Vector<Dimensionless> nhat;
435            if (n.magValue() > GTOL) {
436                nhat = n.toUnitVector();
437                nhat.setOrigin(Point.newInstance(getPhyDimension()));
438
439            } else {
440                //  Try a different set of points on the string.
441                idx1 = numPnts / 4;
442                if (idx1 == 0)
443                    ++idx1;
444                idx2 = 3 * numPnts / 4;
445                if (idx2 == idx1)
446                    ++idx2;
447                p1 = get(idx1);
448                p2 = get(idx2);
449                v1 = p1.minus(p0).toGeomVector();
450                v2 = p2.minus(p0).toGeomVector();
451                n = v1.cross(v2);
452                if (n.magValue() > GTOL) {
453                    nhat = n.toUnitVector();
454                    nhat.setOrigin(Point.newInstance(getPhyDimension()));
455                } else {
456                    //  Points from input curve likely form a straight line.
457                    nhat = GeomUtil.calcYHat(p1.minus(p2).toGeomVector().toUnitVector());
458                    nhat = v2.cross(nhat).toUnitVector();
459                }
460            }
461
462            //  Loop over all the points in the point string
463            //  (except 1st, which was used to define the plane).
464            for (int i = 1; i < numPnts; ++i) {
465                GeomPoint pi = get(i);
466
467                //  Find deviation from a plane for each point by projecting a vector from p0 to pi
468                //  onto the normal for the arbitrarily chosen points on the curve.
469                //  If the projection is anything other than zero, the points are not
470                //  in the same plane.
471                Vector<Length> p1pi = pi.minus(p0).toGeomVector();
472                if (!p1pi.mag().isApproxZero()) {
473                    Parameter proj = p1pi.dot(nhat);
474                    if (proj.isLargerThan(tol))
475                        return false;
476                }
477            }
478        } finally {
479            StackContext.exit();
480        }
481
482        return true;
483    }
484  
485    /**
486     * Return a new {@link PointString} with the points in this list sorted into ascending
487     * order with respect to the specified coordinate dimension. This can also be used to
488     * remove duplicate points after the list has been sorted. To sort in descending
489     * order, use this method followed immediately by reverse() (e.g.: 
490     * <code>str.sort(Point.X,false,null).reverse();</code>).
491     *
492     * @param dim       The physical coordinate dimension to be sorted (e.g.: 0=X, 1=Y, etc).
493     * @param removeDup Duplicate points are removed from the output if this is true.
494     * @param tol       The tolerance for identifying duplicate points. If null is passed,
495     *                  then essentially exact equality is required (if removeDup is
496     *                  false, you may pass null).
497     * @return A new PointString with the points in this list sorted into ascending order
498     *         with respect to the specified coordinate dimension and possibly with
499     *         duplicate points removed.
500     */
501    public PointString<E> sort(int dim, boolean removeDup, Parameter<Length> tol) {
502        //  Create a new PointString and add the points to it.
503        PointString<E> str = PointString.newInstance();
504        str.addAll(this);
505        
506        //  Sort the new PointString
507        Collections.sort(str, new PntComparator(dim));
508        
509        //  Remove duplicates if requested.
510        if (removeDup) {
511            E old = null;
512            Iterator<E> it = str.iterator();
513            while (it.hasNext()) {
514                E p = it.next();
515                if (p.isApproxEqual(old, tol))
516                    it.remove();
517                old = p;
518            }
519        }
520        
521        return str;
522    }
523
524    /**
525     * A Comparator that compares the specified dimension of two points for order.
526     */
527    private class PntComparator implements Comparator<GeomPoint>, Serializable {
528        private final int _dim;
529
530        public PntComparator(int dim) {
531            _dim = dim;
532        }
533
534        @Override
535        public int compare(GeomPoint o1, GeomPoint o2) {
536            double v1 = o1.getValue(_dim);
537            double v2 = o2.getValue(_dim, o1.getUnit());
538            return Double.compare(v1, v2);
539        }
540    }
541
542    /**
543     *  Return the equivalent of this list converted to the specified number
544     *  of physical dimensions.  If the number of dimensions is greater than
545     *  this element, then zeros are added to the additional dimensions.
546     *  If the number of dimensions is less than this element, then
547     *  the extra dimensions are simply dropped (truncated).  If
548     *  the new dimensions are the same as the dimension of this element,
549     *  then this list is simply returned.
550     *
551     *  @param newDim  The dimension of the element to return.
552     *  @return The equivalent of this list converted to the new dimensions.
553     */
554    @Override
555    public PointString toDimension(int newDim) {
556        if (getPhyDimension() == newDim)
557            return this;
558        PointString newList = PointString.newInstance();
559        copyState(newList);
560        int size = this.size();
561        for (int i=0; i < size; ++i) {
562            E element = this.get(i);
563            newList.add(element.toDimension(newDim));
564        }
565        return newList;
566    }
567
568    /**
569     * Returns the equivalent to this list but with <I>all</I> the elements stated in the
570     * specified unit.
571     *
572     * @param unit the length unit of the list to be returned. May not be null.
573     * @return an equivalent to this list but stated in the specified unit.
574     * @throws ConversionException if the the input unit is not a length unit.
575     */
576    @Override
577    public PointString to(Unit<Length> unit) {
578        requireNonNull(unit);
579        PointString list = PointString.newInstance();
580        copyState(list);
581        int size = this.size();
582        for (int i = 0; i < size; ++i) {
583            E e = this.get(i);
584            list.add(e.to(unit));
585        }
586        return list;
587    }
588
589    /**
590     * Returns a copy of this <code>PointString</code> instance
591     * {@link javolution.context.AllocatorContext allocated} by the calling
592     * thread (possibly on the stack).
593     *
594     * @return an identical and independent copy of this object.
595     */
596    @Override
597    public PointString<E> copy() {
598        return copyOf(this);
599    }
600
601    /**
602     * Return a copy of this object with any transformations or subranges
603     * removed (applied).
604     *
605     * @return A copy of this object with any transformations or subranges
606     * removed (applied).
607     */
608    @Override
609    public PointString<E> copyToReal() {
610        PointString<E> newList = PointString.newInstance();
611        copyState(newList);
612        int size = this.size();
613        for (int i=0; i < size; ++i) {
614            E element = this.get(i);
615            newList.add((E)element.copyToReal());
616        }
617        return newList;
618    }
619    
620    /**
621     * Returns transformed version of this element. The returned object
622     * implements {@link GeomTransform} and contains transformed versions of the
623     * contents of this list as children.
624     *
625     * @param transform The transformation to apply to this geometry. May not be null.
626     * @return A new PointString that is identical to this one with the
627     *      specified transformation applied.
628     * @throws DimensionException if this element is not 3D.
629     */
630    @Override
631    public PointString getTransformed(GTransform transform) {
632        requireNonNull(transform);
633        PointString list = PointString.newInstance();
634        copyState(list);
635        int size = this.size();
636        for (int i=0; i < size; ++i) {
637            E element = this.get(i);
638            list.add(element.getTransformed(transform));
639        }
640        return list;
641    }
642
643    /**
644     * Replaces the {@link GeomPoint} at the specified position in this list with the
645     * specified element. Null elements are ignored. The input element must have the same
646     * physical dimensions as the other items in this list, or an exception is thrown.
647     *
648     * @param index   The index of the element to replace (0 returns the 1st element, -1
649     *                returns the last, -2 returns the 2nd from last, etc).
650     * @param element The element to be stored at the specified position. May not be null.
651     * @return The element previously at the specified position in this list.
652     * @throws java.lang.IndexOutOfBoundsException - if <code>index &gt; size()</code>
653     * @throws DimensionException if the input element's dimensions are different from
654     * this list's dimensions.
655     */
656    @Override
657    public E set(int index, E element) {
658        return super.set(index, requireNonNull(element));
659    }
660
661    /**
662     * Inserts the specified {@link GeomPoint} at the specified position in this list.
663     * Shifts the element currently at that position (if any) and any subsequent elements
664     * to the right (adds one to their indices). Null values are ignored. The input
665     * value must have the same physical dimensions as the other items in this list, or
666     * an exception is thrown.
667     * <p>
668     * Note: If this method is used concurrent access must be synchronized (the list is
669     * not thread-safe).
670     * </p>
671     *
672     * @param index the index at which the specified element is to be inserted. (0 returns
673     *              the 1st element, -1 returns the last, -2 returns the 2nd from last,
674     *              etc).
675     * @param value the element to be inserted. May not be null.
676     * @throws IndexOutOfBoundsException if <code>index &gt; size()</code>
677     * @throws DimensionException if the input value dimensions are different from
678     * this list's dimensions.
679     */
680    @Override
681    public void add(int index, E value) {
682        super.add(index, requireNonNull(value));
683    }
684
685    /**
686     * Inserts all of the {@link GeomPoint} objects in the specified collection into this
687     * list at the specified position. Shifts the element currently at that position (if
688     * any) and any subsequent elements to the right (increases their indices). The new
689     * elements will appear in this list in the order that they are returned by the
690     * specified collection's iterator. The behavior of this operation is unspecified if
691     * the specified collection is modified while the operation is in progress. Note that
692     * this will occur if the specified collection is this list, and it's nonempty.  The
693     * input elements must have the same physical dimensions as the other items in this
694     * list, or an exception is thrown.
695     *
696     * @param index index at which to insert first element from the specified collection.
697     * @param c     Elements to be inserted into this collection. May not be null.
698     * @return <code>true</code> if this collection changed as a result of the call
699     * @throws DimensionException if the input element's dimensions are different from
700     * this list's dimensions.
701     */
702    @Override
703    public boolean addAll(int index, Collection<? extends E> c) {
704        int thisSize = this.size();
705        for (Object element : c) {
706            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
707            if (!(element instanceof GeomPoint))
708                throw new ClassCastException(MessageFormat.format(
709                        RESOURCES.getString("listElementTypeErr"), "PointString", "GeomPoint"));
710            if (thisSize != 0) {
711                if (((GeomElement)element).getPhyDimension() != this.getPhyDimension())
712                    throw new DimensionException(RESOURCES.getString("dimensionErr"));
713            }
714        }
715        return super.addAll(index, c);
716    }
717
718    /**
719     * Holds the default XML representation for this object.
720     */
721    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
722    protected static final XMLFormat<PointString> XML = new XMLFormat<PointString>(PointString.class) {
723
724        @Override
725        public PointString newInstance(Class<PointString> cls, XMLFormat.InputElement xml) throws XMLStreamException {
726            PointString obj = PointString.newInstance();
727            return obj;
728        }
729
730        @Override
731        public void read(XMLFormat.InputElement xml, PointString obj) throws XMLStreamException {
732            AbstractPointGeomList.XML.read(xml, obj);     // Call parent read.
733        }
734
735        @Override
736        public void write(PointString obj, XMLFormat.OutputElement xml) throws XMLStreamException {
737            AbstractPointGeomList.XML.write(obj, xml);    // Call parent write.
738        }
739    };
740
741    //////////////////////
742    // Factory Creation //
743    //////////////////////
744    private static final ObjectFactory<PointString> FACTORY = new ObjectFactory<PointString>() {
745        @Override
746        protected PointString create() {
747            return new PointString();
748        }
749        @Override
750        protected void cleanup(PointString obj) {
751            obj.reset();
752        }
753    };
754
755    /**
756     * Recycles a case instance immediately (on the stack when executing in a
757     * StackContext).
758     *
759     * @param instance The instance to be recycled immediately.
760     */
761    public static void recycle(PointString instance) {
762        FACTORY.recycle(instance);
763    }
764
765    /**
766     * Do not allow the default constructor to be used except by subclasses.
767     */
768    protected PointString() {}
769
770    private static <E2 extends GeomPoint> PointString<E2> copyOf(PointString<E2> original) {
771        PointString<E2> o = PointString.newInstance();
772        original.copyState(o);
773        int size = original.size();
774        for (int i=0; i < size; ++i) {
775            E2 element = original.get(i);
776            o.add((E2)element.copy());
777        }
778        return o;
779    }
780
781    /**
782     * Tests the methods in this class.
783     *
784     * @param args Command-line arguments (not used).
785     */
786    public static void main(String args[]) {
787        System.out.println("Testing PointString:");
788
789        Point p1 = Point.valueOf(1, 4, 6, NonSI.FOOT);
790        Point p2 = Point.valueOf(7, 2, 5, NonSI.FOOT);
791        Point p3 = Point.valueOf(10, 8, 3, NonSI.FOOT);
792        Point p4 = Point.valueOf(12, 11, 9, NonSI.FOOT);
793        PointString<Point> str1 = PointString.valueOf("A String", p1, p2, p3, p4);
794        str1.putUserData("Creator", "Joe Huwaldt");
795        str1.putUserData("Date", "June 18, 2013");
796        System.out.println("str1 = " + str1);
797        System.out.println("points = ");
798        for (GeomPoint point : str1) {
799            System.out.println(point);
800        }
801
802        Vector<Length> V = Vector.valueOf(SI.METER, 2, 0, 0);
803        PointString<?> str2 = str1.getTransformed(GTransform.newTranslation(V));
804        System.out.println("\nTranslate str1 by V = " + V);
805        System.out.println("str2 = " + str2);
806        System.out.println("points = ");
807        for (GeomPoint point : str2) {
808            System.out.println(point);
809        }
810
811        V = Vector.valueOf(NonSI.FOOT, 0,2,0);
812        PointString<?> str3 = str2.getTransformed(GTransform.newTranslation(V));
813        System.out.println("\nTranslate str2 by V = " + V);
814        System.out.println("str3 = " + str3);
815        System.out.println("points = ");
816        for (GeomPoint point : str3) {
817            System.out.println(point);
818        }
819
820        //  Write out XML data.
821        try {
822            System.out.println();
823
824            // Creates some useful aliases for class names.
825            javolution.xml.XMLBinding binding = new GeomXMLBinding();
826
827            javolution.xml.XMLObjectWriter writer = javolution.xml.XMLObjectWriter.newInstance(System.out);
828            writer.setIndentation("    ");
829            writer.setBinding(binding);
830            writer.write(str1, "PointString", PointString.class);
831            writer.flush();
832
833            System.out.println();
834        } catch (Exception e) {
835            e.printStackTrace();
836        }
837
838    }
839}
840
841