001/**
002 * LinearComboCurve -- Represents a linear combination of two or more curves.
003 *
004 * Copyright (C) 2015-2025, 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 Lesser 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 geomss.geom.nurbs.BasicNurbsCurve;
021import geomss.geom.nurbs.CurveFactory;
022import geomss.geom.nurbs.NurbsCurve;
023import jahuwaldt.js.param.Parameter;
024import java.text.MessageFormat;
025import java.util.*;
026import static java.util.Objects.nonNull;
027import static java.util.Objects.requireNonNull;
028import javax.measure.converter.ConversionException;
029import javax.measure.quantity.Length;
030import javax.measure.unit.Unit;
031import javax.swing.event.ChangeListener;
032import javolution.context.ObjectFactory;
033import javolution.context.StackContext;
034import javolution.lang.Immutable;
035import javolution.text.Text;
036import javolution.text.TextBuilder;
037import javolution.util.FastTable;
038import javolution.xml.XMLFormat;
039import javolution.xml.stream.XMLStreamException;
040
041/**
042 * Represents a linear combination made up of a list of {@link Curve} objects. A linear
043 * combination curve is formed by a weighted linear addition of a list of one or more
044 * curves. For example: <code>B = W1*Crv_1 + ... + Wn*Crv_n</code> where W1 through Wn are
045 * scalar weights. The linear addition is done independently of physical dimension (each
046 * dimension is added separately) and the weighted addition is done in parameter space:
047 * <code> B = W1*Crv_1(s) + ... + Wn*Crv_n(s)</code>.
048 *<p>
049 * Any number of curves may be added to a LinearComboCurve, but they all must have the
050 * same physical dimensions.
051 * </p>
052 * <p> Modified by: Joseph A. Huwaldt </p>
053 *
054 * @author Joseph A. Huwaldt, Date: September 8, 2015
055 * @version February 17, 2025
056 */
057@SuppressWarnings({"serial", "CloneableImplementsClone"})
058public class LinearComboCurve extends AbstractCurve<LinearComboCurve> implements LinearCombination<LinearComboCurve,Curve> {
059
060    //  The list of curves being combined.
061    private List<Curve> _crvs;
062
063    //  The list of weights applied to each curve.
064    private List<Double> _wts;
065
066    /**
067     * Reference to a change listener for this object's child curves.
068     */
069    private final ChangeListener _childChangeListener = new ForwardingChangeListener(this);
070
071
072    /**
073     * Returns a new, empty, preallocated or recycled <code>LinearComboCurve</code>
074     * instance (on the stack when executing in a <code>StackContext</code>), that can
075     * store a list of {@link Curve} objects and associated weighting factors. The list is
076     * initially empty and therefore the linear combination is initially undefined.
077     *
078     * @return A new empty LinearComboCurve.
079     */
080    public static LinearComboCurve newInstance() {
081        LinearComboCurve list = FACTORY.object();
082        list._crvs = FastTable.newInstance();
083        list._wts = FastTable.newInstance();
084        return list;
085    }
086
087    /**
088     * Returns a new, empty, preallocated or recycled <code>LinearComboCurve</code>
089     * instance (on the stack when executing in a <code>StackContext</code>), that can
090     * store a list of {@link Curve} objects and associated weighting factors. The list is
091     * initially empty and therefore the linear combination is initially undefined.
092     *
093     * @param name The name to be assigned to this LinearComboCurve (may be null).
094     * @return A new empty LinearComboCurve.
095     */
096    public static LinearComboCurve newInstance(String name) {
097        LinearComboCurve list = LinearComboCurve.newInstance();
098        list.setName(name);
099        return list;
100    }
101
102    /**
103     * Return a LinearComboCurve made up of the {@link Curve} and weight objects in the
104     * specified collections.
105     *
106     * @param curves  A collection of curves that define the LinearComboCurve. May not be
107     *                null.
108     * @param weights A collection of weights (one for each curve). May not be null and
109     *                must have the same size as "curves".
110     * @return A new LinearComboCurve made up of the curves and weights in the specified
111     *         collections.
112     */
113    public static LinearComboCurve valueOf(Collection<? extends Curve> curves, Collection<Double> weights) {
114
115        LinearComboCurve list = LinearComboCurve.newInstance();
116        list.addAll(requireNonNull(curves), requireNonNull(weights));
117
118        return list;
119    }
120
121    /**
122     * Return a LinearComboCurve made up of the {@link Curve} objects in the specified
123     * array each with a default weight of 1.0.
124     *
125     * @param curves An array of curves that define the LinearComboCurve. May not be null.
126     * @return A new LinearComboCurve made up of the curves in the specified
127     *         array each with a default weight of 1.0.
128     */
129    public static LinearComboCurve valueOf(Curve... curves) {
130
131        LinearComboCurve list = LinearComboCurve.newInstance();
132        list.addAll(Arrays.asList(requireNonNull(curves)));
133
134        return list;
135    }
136
137    /**
138     * Validate that the curve is properly formed, otherwise throw an exception.
139     */
140    private void validateCurve() {
141        if (size() < 1)
142            throw new IllegalArgumentException(MessageFormat.format(
143                    RESOURCES.getString("incDefiningCrvCount"), "LoftedSurface", 2, size()));
144    }
145
146    /**
147     * Return the input index normalized into the range 0 &le; index &lt; size(). This
148     * allows negative indexing (-1 referring to the last element in the list), but does
149     * not allow wrap-around positive indexing.
150     */
151    private int normalizeIndex(int index) {
152        int size = size();
153        while (index < 0)
154            index += size;
155        return index;
156    }
157
158    /**
159     * Returns an unmodifiable list view of the curves in this list. Attempts to modify
160     * the returned collection result in an UnsupportedOperationException being thrown.
161     *
162     * @return the unmodifiable view over this list of curves.
163     */
164    @Override
165    public List<Curve> unmodifiableList() {
166        return ((FastTable)_crvs).unmodifiable();
167    }
168
169    /**
170     * Returns an unmodifiable list view of the weights in this list. Attempts to modify
171     * the returned collection result in an UnsupportedOperationException being thrown.
172     *
173     * @return the unmodifiable view over this list of weights.
174     */
175    @Override
176    public List<Double> unmodifiableWeightList() {
177        return ((FastTable)_wts).unmodifiable();
178    }
179
180    /**
181     * Returns <code>true</code> if this collection contains no elements.
182     */
183    @Override
184    public boolean isEmpty() {
185        return _crvs.isEmpty();
186    }
187
188    /**
189     * Returns <code>true</code> if this list actually contains any curves and
190     * <code>false</code> if this list is empty.
191     *
192     * @return true if this list actually contains geometry.
193     */
194    @Override
195    public boolean containsGeometry() {
196        return !isEmpty();
197    }
198
199    /**
200     * Returns the number of {@link Curve} objects that make up this linear combination
201     * curve. If the surface contains more than Integer.MAX_VALUE elements, returns
202     * Integer.MAX_VALUE.
203     *
204     * @return the number of elements in this list of curves.
205     */
206    @Override
207    public int size() {
208        return _crvs.size();
209    }
210
211    /**
212     * Returns the Curve at the specified position in this LinearComboCurve.
213     *
214     * @param index index of curve to return (0 returns the 1st element, -1 returns the
215     *              last, -2 returns the 2nd from last, etc).
216     * @return the Curve at the specified position in this LinearComboCurve.
217     * @throws IndexOutOfBoundsException if the given index is out of range:
218     * <code>index &gt; size()</code>
219     */
220    @Override
221    public Curve get(int index) {
222        index = normalizeIndex(index);
223        return _crvs.get(index);
224    }
225
226    /**
227     * Returns the linear combination weight at the specified position in this
228     * LinearComboCurve.
229     *
230     * @param index index of weight to return (0 returns the 1st element, -1 returns the
231     *              last, -2 returns the 2nd from last, etc).
232     * @return the linear combination weight at the specified position in this
233     *         LinearComboCurve.
234     * @throws IndexOutOfBoundsException if the given index is out of range:
235     * <code>index &gt; size()</code>
236     */
237    @Override
238    public Double getWeight(int index) {
239        index = normalizeIndex(index);
240        return _wts.get(index);
241    }
242
243    /**
244     * Returns the first curve from this LinearComboCurve.
245     *
246     * @return the first curve in this list.
247     */
248    @Override
249    public Curve getFirst() {
250        return get(0);
251    }
252
253    /**
254     * Returns the first linear combination weight from this LinearComboCurve.
255     *
256     * @return the first weight in this list.
257     */
258    @Override
259    public Double getFirstWeight() {
260        return getWeight(0);
261    }
262
263    /**
264     * Returns the last curve from this LinearComboCurve list of curves.
265     *
266     * @return the last curve in this list.
267     */
268    @Override
269    public Curve getLast() {
270        return get(size() - 1);
271    }
272
273    /**
274     * Returns the last linear combination weight from this LinearComboCurve.
275     *
276     * @return the last weight in this list.
277     */
278    @Override
279    public Double getLastWeight() {
280        return getWeight(size() - 1);
281    }
282
283    /**
284     * Returns the range of Curves in this LinearComboCurve from the specified start and ending
285     * indexes as a new LinearComboCurve.
286     *
287     * @param first index of the first element to return (0 returns the 1st element, -1
288     *              returns the last, etc).
289     * @param last  index of the last element to return (0 returns the 1st element, -1
290     *              returns the last, etc).
291     * @return A LinearComboCurve made up of the curves in the given range from this LinearComboCurve.
292     * @throws IndexOutOfBoundsException if the given index is out of range:
293     * <code>index &ge; size()</code>
294     */
295    @Override
296    public LinearComboCurve getRange(int first, int last) {
297        first = normalizeIndex(first);
298        last = normalizeIndex(last);
299
300        LinearComboCurve list = LinearComboCurve.newInstance();
301        for (int i = first; i <= last; ++i)
302            list.add(get(i),getWeight(i));
303
304        return list;
305    }
306
307    /**
308     * Returns the range of linear combination weights in this LinearComboCurve from the
309     * specified start and ending indexes as a List of double values.
310     *
311     * @param first index of the first element to return (0 returns the 1st element, -1
312     *              returns the last, etc).
313     * @param last  index of the last element to return (0 returns the 1st element, -1
314     *              returns the last, etc).
315     * @return A List made up of the weights in the given range from this
316     *         LinearComboCurve.
317     * @throws IndexOutOfBoundsException if the given index is out of range:
318     * <code>index &ge; size()</code>
319     */
320    @Override
321    public List<Double> getWeightRange(int first, int last) {
322        first = normalizeIndex(first);
323        last = normalizeIndex(last);
324        
325        FastTable<Double> list = FastTable.newInstance();
326        for (int i = first; i <= last; ++i)
327            list.add(getWeight(i));
328
329        return list;
330    }
331
332    /**
333     * Returns the {@link Curve} with the specified name from this list.
334     *
335     * @param name The name of the curve we are looking for in the list.
336     * @return The curve matching the specified name. If the specified element name
337     *         isn't found in the list, then <code>null</code> is returned.
338     */
339    @Override
340    public Curve get(String name) {
341
342        Curve element = null;
343        int index = getIndexFromName(name);
344        if (index >= 0)
345            element = this.get(index);
346
347        return element;
348    }
349
350    /**
351     * Returns a view of the portion of this LinearComboCurve between fromIndex,
352     * inclusive, and toIndex, exclusive. (If fromIndex and toIndex are equal, the
353     * returned LinearComboCurve is empty.) The returned LinearComboCurve is backed by
354     * this LinearComboCurve, so changes in the returned LinearComboCurve are reflected in
355     * this LinearComboCurve, and vice-versa.
356     *
357     * This method eliminates the need for explicit range operations (of the sort that
358     * commonly exist for arrays). Any operation that expects a list can be used as a
359     * range operation by passing a subList view instead of a whole list. For example, the
360     * following idiom removes a range of values from a list: <code>
361     * list.subList(from, to).clear();</code> Similar idioms may be constructed for
362     * <code>indexOf</code> and <code>lastIndexOf</code>, and all of the algorithms in the
363     * <code>Collections</code> class can be applied to a subList.
364     *
365     * The semantics of the list returned by this method become undefined if the backing
366     * list (i.e., this list) is <i>structurally modified</i> in any way other than via
367     * the returned list (structural modifications are those that change the size of this
368     * list, or otherwise perturb it in such a fashion that iterations in progress may
369     * yield incorrect results).
370     *
371     * @param fromIndex low endpoint (inclusive) of the subList.
372     * @param toIndex   high endpoint (exclusive) of the subList.
373     * @return a view of the specified range within this LinearComboCurve.
374     * @throws IndexOutOfBoundsException if the given index is out of range:
375     * <code>index &gt; size()</code>
376     */
377    @Override
378    public LinearComboCurve subList(int fromIndex, int toIndex) {
379        fromIndex = normalizeIndex(fromIndex);
380        toIndex = normalizeIndex(toIndex);
381
382        LinearComboCurve crv = FACTORY.object();
383        crv._crvs = _crvs.subList(fromIndex, toIndex);
384        crv._wts = _wts.subList(fromIndex, toIndex);
385
386        return crv;
387    }
388
389    /**
390     * Returns an new {@link GeomList} with all the curves in this list.
391     *
392     * @return A new GeomList with all the curves in this list.
393     */
394    @Override
395    public GeomList<Curve> getAll() {
396        GeomList<Curve> list = GeomList.newInstance();
397        list.addAll(_crvs);
398        return list;
399    }
400
401    /**
402     * Return a new curve that is identical to this one, but with the
403     * parameterization reversed.
404     *
405     * @return A new curve that is identical to this one, but with the
406     * parameterization reversed.
407     */
408    @Override
409    public LinearComboCurve reverse() {
410        LinearComboCurve list = LinearComboCurve.newInstance();
411        copyState(list);
412        int size = this.size();
413        for (int i=0; i < size; ++i) {
414            list.add(_crvs.get(i).reverse(), _wts.get(i));
415        }
416        return list;
417    }
418
419    /**
420     * Returns the index in this list of the first occurrence of the specified
421     * {@link Curve}, or -1 if the list does not contain this curve.
422     *
423     * @param element The Curve to search for.
424     * @return the index in this List of the first occurrence of the specified curve, or
425     *         -1 if the List does not contain this curve.
426     */
427    @Override
428    public int indexOf(Object element) {
429        return _crvs.indexOf(element);
430    }
431
432    /**
433     * Returns the index in this list of the last occurrence of the specified
434     * {@link Curve}, or -1 if the list does not contain this curve. More formally,
435     * returns the highest index i such that (o==null ? get(i)==null : o.equals(get(i))),
436     * or -1 if there is no such index.
437     *
438     * @param element The Curve to search for.
439     * @return the index in this list of the last occurrence of the specified curve, or -1
440     *         if the list does not contain this curve.
441     */
442    @Override
443    public int lastIndexOf(Object element) {
444        return _crvs.lastIndexOf(element);
445    }
446
447    /**
448     * Returns true if this LinearComboCurve contains the specified {@link Curve}. More formally,
449     * returns true if and only if this LinearComboCurve's curves contains at least one element e such
450     * that (o==null ? e==null : o.equals(e)).
451     *
452     * @param o object to be checked for containment in this collection.
453     * @return <code>true</code> if this collection contains the specified element.
454     */
455    @Override
456    public boolean contains(Object o) {
457        return _crvs.contains(o);
458    }
459
460    /**
461     * Returns true if this collection contains all of the {@link Curve} objects in the specified
462     * collection.
463     *
464     * @param c collection of curves to be checked for containment in this collection.
465     * @return <code>true</code> if this collection contains all of the curves in the
466     *         specified collection.
467     */
468    @Override
469    public boolean containsAll(Collection<?> c) {
470        return _crvs.containsAll(c);
471    }
472
473    /**
474     * Return the index to the 1st Curve in this list with the specified name.
475     *
476     * @param name The name of the Curve to find in this list
477     * @return The index to the named Curve or -1 if it is not found.
478     */
479    @Override
480    public int getIndexFromName(String name) {
481        if (name == null)
482            return -1;
483
484        int result = -1;
485        int size = this.size();
486        for (int i = 0; i < size; ++i) {
487            GeomElement element = this.get(i);
488            String eName = element.getName();
489            if (name.equals(eName)) {
490                result = i;
491                break;
492            }
493        }
494        
495        return result;
496    }
497
498    /**
499     * Inserts the specified {@link Curve} at the specified position in this list with a
500     * default weight of 1.0. Shifts the element currently at that position (if any) and
501     * any subsequent elements to the right (adds one to their indices). Null values are
502     * ignored. The input value must have the same physical dimensions as the other items
503     * in this list, or an exception is thrown.
504     * <p>
505     * Note: If this method is used concurrent access must be synchronized (the table is
506     * not thread-safe).
507     * </p>
508     *
509     * @param index the index at which the specified element is to be inserted. (0 returns
510     *              the 1st element, -1 returns the last, -2 returns the 2nd from last,
511     *              etc).
512     * @param value the Curve to be inserted with a default weight of 1.0.
513     * @throws IndexOutOfBoundsException if <code>index &gt; size()</code>
514     * @throws DimensionException if the input value dimensions are different from this
515     * list's dimensions.
516     */
517    @Override
518    public void add(int index, Curve value) {
519        add(index, value, 1.0);
520    }
521
522    /**
523     * Inserts the specified {@link Curve} at the specified position in this list. Shifts
524     * the element currently at that position (if any) and any subsequent elements to the
525     * right (adds one to their indices). Null values are ignored. The input curve must
526     * have the same physical dimensions as the other items in this list, or an exception
527     * is thrown.
528     * <p>
529     * Note: If this method is used concurrent access must be synchronized (the table is
530     * not thread-safe).
531     * </p>
532     *
533     * @param index  The index at which the specified element is to be inserted. (0
534     *               returns the 1st element, -1 returns the last, -2 returns the 2nd from
535     *               last, etc).
536     * @param curve  The Curve to be inserted. May not be null.
537     * @param weight The linear combination weight of the curve to be inserted. May not be
538     *               null.
539     * @throws IndexOutOfBoundsException if <code>index &gt; size()</code>
540     * @throws DimensionException if the input curve dimensions are different from this
541     * list's dimensions.
542     */
543    @Override
544    public void add(int index, Curve curve, Double weight) {
545        requireNonNull(weight);
546        if (size() > 0 && curve.getPhyDimension() != getPhyDimension())
547            throw new DimensionException(MessageFormat.format(
548                    RESOURCES.getString("incCrvDimension"), "curve", getPhyDimension()));
549        index = normalizeIndex(index);
550
551        _wts.add(index, weight);
552        _crvs.add(index, curve);
553        if (!(curve instanceof Immutable))
554            curve.addChangeListener(_childChangeListener);
555
556        fireChangeEvent();  //  Notify change listeners.
557    }
558
559    /**
560     * Appends the specified {@link Curve} to the end of this LinearComboCurve with a
561     * default weight of 1.0. Null values are ignored. The input curve must have the same
562     * physical dimensions as the other items in this list, or an exception is thrown.
563     * <p>
564     * Note: If this method is used concurrent access must be synchronized (the table is
565     * not thread-safe).
566     * </p>
567     *
568     * @param curve The curve to be appended to this list with a default weight of 1.0.
569     *              May not be null.
570     * @return true if this list changed as a result of this call.
571     * @throws DimensionException if the input element's dimensions are different from
572     * this list's dimensions.
573     * @throws DimensionException if the input curve dimensions are different from this
574     * list's dimensions.
575     */
576    @Override
577    public boolean add(Curve curve) {
578        return add(curve, 1.0);
579    }
580
581    /**
582     * Appends the specified {@link Curve} to the end of this list of curves. Null values
583     * are ignored. The input curve must have the same physical dimensions as the other
584     * items in this list, or an exception is thrown.
585     * <p>
586     * Note: If this method is used concurrent access must be synchronized (the table is
587     * not thread-safe).
588     * </p>
589     *
590     * @param curve  The curve to be appended to this list. May not be null.
591     * @param weight The linear combination weight of the curve to be appended. May not be
592     *               null.
593     * @return true if this list changed as a result of this call.
594     * @throws DimensionException if the input element's dimensions are different from
595     * this list's dimensions.
596     * @throws DimensionException if the input curve dimensions are different from this
597     * list's dimensions.
598     */
599    @Override
600    public boolean add(Curve curve, Double weight) {
601        add(size(), curve, weight);
602        return true;
603    }
604
605    /**
606     * Appends all of the elements in the specified list of arguments to this
607     * LinearComboCurve with a default weight of 1.0 assigned to each. The input curve
608     * must have the same physical dimensions as the other items in this list, or an
609     * exception is thrown.
610     *
611     * @param array the curves to be inserted into this collection with a default weight
612     *              of 1.0 assigned to each. May not be null.
613     * @return <code>true</code> if this collection changed as a result of the call.
614     * @throws DimensionException if the input curve dimensions are different from this
615     * list's dimensions.
616     */
617    @Override
618    public boolean add(Curve... array) {
619        return add(size(), array);
620    }
621
622    /**
623     * Inserts all of the {@link Curve} objects in the specified list of arguments into
624     * this LinearComboCurve at the specified position with a default weight of 1.0
625     * assigned to each. Shifts the element currently at that position (if any) and any
626     * subsequent elements to the right (increases their indices). The new elements will
627     * appear in this list in the order that they are appeared in the array. The input
628     * curve must have the same physical dimensions as the other items in this list, or an
629     * exception is thrown.
630     *
631     * @param index index at which to insert first element from the specified array.
632     * @param array the curves to be inserted into this collection with a default weight
633     *              of 1.0 for each. May not be null.
634     * @return <code>true</code> if this collection changed as a result of the call.
635     * @throws DimensionException if the input curve dimensions are different from this
636     * list's dimensions.
637     */
638    @Override
639    public boolean add(int index, Curve... array) {
640        return addAll(index, Arrays.asList(requireNonNull(array)));
641    }
642
643    /**
644     * Adds all of the curves in the specified collection to this LinearComboCurve with a
645     * default weight of 1.0 for each. The behavior of this operation is undefined if the
646     * specified collection is modified while the operation is in progress. This implies
647     * that the behavior of this call is undefined if the specified collection is this
648     * collection, and this collection is nonempty. The input curves must have the same
649     * physical dimensions as the other items in this list, or an exception is thrown.
650     *
651     * @param curves curves to be inserted into this list with a default of 1.0 for each.
652     *               May not be null.
653     * @return <code>true</code> if this LinearComboCurve changed as a result of the call
654     * @throws DimensionException if the input curve dimensions are different from this
655     * list's dimensions.
656     */
657    @Override
658    public boolean addAll(Collection<? extends Curve> curves) {
659        return addAll(size(), curves);
660    }
661
662    /**
663     * Inserts all of the curves in the specified collection of curves into this
664     * LinearComboCurve with default weights of 1.0 for each. Shifts the curve currently
665     * at that position (if any) and any subsequent curves to the right (increases their
666     * indices). The new curves will appear in this list in the order that they are
667     * returned by the specified collection's iterator. The behavior of this operation is
668     * unspecified if the specified collection is modified while the operation is in
669     * progress. Note that this will occur if the specified collection is this list, and
670     * it's nonempty. The input curves must have the same physical dimensions as the other
671     * items in this list, or an exception is thrown.
672     *
673     * @param index  index at which to insert first curve from the specified collection.
674     * @param curves curves to be inserted into this list with default weights of 1.0 for
675     *               each. May not be null.
676     * @return <code>true</code> if this LinearComboCurve changed as a result of the call
677     * @throws DimensionException if the input curve dimensions are different from this
678     * list's dimensions.
679     */
680    @Override
681    public boolean addAll(int index, Collection<? extends Curve> curves) {
682        FastTable<Double> weights = FastTable.newInstance();
683        Double ONE = 1.0;
684
685        int size = curves.size();
686        for (int i=size-1; i >= 0; --i)
687            weights.add(ONE);
688
689        boolean output = addAll(index, curves, weights);
690
691        FastTable.recycle(weights);
692        return output;
693    }
694
695    /**
696     * Appends all of the curves in the specified collection to this LinearComboCurve. The
697     * behavior of this operation is undefined if the specified collection is modified
698     * while the operation is in progress. This implies that the behavior of this call is
699     * undefined if the specified collection is this collection, and this collection is
700     * nonempty. The input curves must have the same physical dimensions as the other
701     * items in this list, or an exception is thrown.
702     *
703     * @param curves  the curves to be appended onto this list of curves. May not be null.
704     * @param weights the linear combination weights associated with all of the curves
705     *                being appended. May not be null and must be the same size as
706     *                "curves".
707     * @return <code>true</code> if this LinearComboCurve changed as a result of the call
708     * @throws DimensionException if the input curve dimensions are different from this
709     * list's dimensions.
710     */
711    @Override
712    public boolean addAll(Collection<? extends Curve> curves, Collection<Double> weights) {
713        return addAll(size(), curves, weights);
714    }
715
716    /**
717     * Inserts all of the curves in the specified collection and their associated weights
718     * into this LinearComboCurve at the specified position. Shifts the curve currently at
719     * that position (if any) and any subsequent curves to the right (increases their
720     * indices). The new curves will appear in this list in the order that they are
721     * returned by the specified collection's iterator. The behavior of this operation is
722     * unspecified if the specified collection is modified while the operation is in
723     * progress. Note that this will occur if the specified collection is this list, and
724     * it's nonempty. The input curves must have the same physical dimensions as the other items
725     * in this list, or an exception is thrown.
726     *
727     * @param index   index at which to insert first curve from the specified collection.
728     * @param curves  the curves to be inserted into this linear combination. May not be null.
729     * @param weights the linear combination weights associated with each curve being
730     *                inserted. May not be null and must be the same size as "curves".
731     * @return <code>true</code> if this LinearComboCurve changed as a result of the call
732     * @throws DimensionException if the input curve dimensions are different from this
733     * list's dimensions.
734     */
735    @Override
736    public boolean addAll(int index, Collection<? extends Curve> curves, Collection<Double> weights) {
737        if (curves.size() != weights.size())
738            throw new IllegalArgumentException(MessageFormat.format(
739                    RESOURCES.getString("lstSameSizeErr"), "curves list", "weights list"));
740        if (size() > 0) {
741            int dim = getPhyDimension();
742            for (Curve crv : curves)
743                if (crv.getPhyDimension() != dim)
744                    throw new DimensionException(MessageFormat.format(
745                            RESOURCES.getString("incCrvDimension"), "curves", getPhyDimension()));
746        }
747        for (Double wt : weights)
748            requireNonNull(wt);
749
750        index = normalizeIndex(index);
751        boolean changed = _crvs.addAll(index, curves);
752        if (changed) {
753            for (Curve crv : curves) {
754                if (!(crv instanceof Immutable))
755                    crv.addChangeListener(_childChangeListener);
756            }
757            fireChangeEvent();  //  Notify change listeners.
758        }
759        _wts.addAll(index,weights);
760        return changed;
761    }
762
763    /**
764     * Appends all of the curves in the specified array to this LinearComboCurve with a
765     * default weight of 1.0 assigned to each. The behavior of this operation is undefined
766     * if the specified collection is modified while the operation is in progress. The
767     * input curves must have the same physical dimensions as the other items in this
768     * list, or an exception is thrown.
769     *
770     * @param arr curves to be appended onto this collection with a default weight of 1.0
771     *            for each. May not be null.
772     * @return <code>true</code> if this collection changed as a result of the call.
773     * @throws DimensionException if the input curve dimensions are different from this
774     * list's dimensions.
775     */
776    @Override
777    public boolean addAll(Curve[] arr) {
778        return addAll(size(), arr);
779    }
780
781    /**
782     * Inserts all of the {@link Curve} objects in the specified array into this
783     * LinearComboCurve at the specified position with a default weight of 1.0 assigned to
784     * each. Shifts the element currently at that position (if any) and any subsequent
785     * elements to the right (increases their indices). The new elements will appear in
786     * this list in the order that they are returned by the specified collection's
787     * iterator. The input curves must have the same physical dimensions as the other
788     * items in this list, or an exception is thrown.
789     *
790     * @param index index at which to insert first element from the specified array.
791     * @param arr   the curves to be inserted into this collection with a default weight
792     *              of 1.0 for each. May not be null.
793     * @return <code>true</code> if this collection changed as a result of the call.
794     * @throws DimensionException if the input curve dimensions are different from this
795     * list's dimensions.
796     */
797    @Override
798    public boolean addAll(int index, Curve[] arr) {
799        return addAll(index, Arrays.asList(requireNonNull(arr)));
800    }
801
802    /**
803     * Replaces the Curve at the specified position in this list of curves with
804     * the specified Curve. The weight at that position is left unchanged. Null elements
805     * are ignored. The input curve must have the same physical dimensions as the other
806     * items in this list, or an exception is thrown.
807     *
808     * @param index The index of the curve to replace (0 returns the 1st curve, -1 returns
809     *              the last, -2 returns the 2nd from last, etc).
810     * @param curve The curve to be stored at the specified position. The weight at that
811     *              position is left unchanged. May not be null.
812     * @return The curve previously at the specified position in this list.
813     * @throws java.lang.IndexOutOfBoundsException - if  <code>index &gt; size()</code>
814     * @throws DimensionException if the input curve dimensions are different from this
815     * list's dimensions.
816     */
817    @Override
818    public Curve set(int index, Curve curve) {
819        if (size() > 0 && curve.getPhyDimension() != getPhyDimension())
820            throw new DimensionException(MessageFormat.format(
821                    RESOURCES.getString("incCrvDimension"), "curve", getPhyDimension()));
822        index = normalizeIndex(index);
823
824        Curve old = _crvs.set(index, curve);
825        if (!(old instanceof Immutable))
826            old.removeChangeListener(_childChangeListener);
827        if (!(curve instanceof Immutable))
828            curve.addChangeListener(_childChangeListener);
829
830        fireChangeEvent();  //  Notify change listeners.
831
832        return old;
833    }
834
835    /**
836     * Replaces the weight at the specified position in this LinearComboCurve with the
837     * specified weight. The curve at that position is left unchanged. Null elements are
838     * ignored.
839     *
840     * @param index  The index of the weight to replace (0 returns the 1st element, -1
841     *               returns the last, -2 returns the 2nd from last, etc).
842     * @param weight The weight to be stored at the specified position. The curve at that
843     *               position is left unchanged. May not be null.
844     * @return The weight previously at the specified position in this list.
845     * @throws java.lang.IndexOutOfBoundsException - if  <code>index &gt; size()</code>
846     */
847    @Override
848    public Double setWeight(int index, Double weight) {
849        index = normalizeIndex(index);
850        return _wts.set(index, requireNonNull(weight));
851    }
852
853    /**
854     * Replaces the Curve and weight at the specified position in this
855     * LinearComboCurve with the specified curve and weight. Null elements are ignored.
856     * The input curve must have the same physical dimensions as the other items in this
857     * list, or an exception is thrown.
858     *
859     * @param index  The index of the curve and weight to replace (0 returns the 1st
860     *               element, -1 returns the last, -2 returns the 2nd from last, etc).
861     * @param curve  The curve to be stored at the specified position. May not be null.
862     * @param weight The weight to be stored at the specified position. May not be null.
863     * @return The curve previously at the specified position in this list. The previous
864     *         weight is lost.
865     * @throws java.lang.IndexOutOfBoundsException - if  <code>index &gt; size()</code>
866     * @throws DimensionException if the input curve dimensions are different from this
867     * list's dimensions.
868     */
869    @Override
870    public Curve set(int index, Curve curve, Double weight) {
871        requireNonNull(curve);
872        requireNonNull(weight);
873        Curve old = set(index, curve);
874        setWeight(index, weight);
875        return old;
876    }
877
878    /**
879     * Removes from this list all the {@link Curve} objects that are contained in the
880     * specified collection.
881     *
882     * @param c Collection that defines which curves will be removed from this list. May
883     *          not be null.
884     * @return <code>true</code> if this list changed as a result of the call.
885     */
886    @Override
887    public boolean removeAll(Collection<?> c) {
888        requireNonNull(c);
889        boolean modified = false;
890        Iterator<?> it = iterator();
891        Iterator<Double> itw = _wts.iterator();
892        while (it.hasNext()) {
893            itw.next();
894            if (c.contains(it.next())) {
895                it.remove();
896                itw.remove();
897                modified = true;
898            }
899        }
900        return modified;
901    }
902
903    /**
904     * Retains only the curves in this list that are contained in the specified
905     * collection. In other words, removes from this LinearComboCurve all the
906     * {@link Curve} objects that are not contained in the specified collection.
907     *
908     * @param c Collection that defines which curves this set will retain. May not be
909     *          null.
910     * @return <code>true</code> if this list changed as a result of the call.
911     */
912    @Override
913    public boolean retainAll(Collection<?> c) {
914        requireNonNull(c);
915        boolean modified = false;
916        Iterator<Curve> it = iterator();
917        Iterator<Double> itw = _wts.iterator();
918        while (it.hasNext()) {
919            itw.next();
920            if (!c.contains(it.next())) {
921                it.remove();
922                itw.remove();
923                modified = true;
924            }
925        }
926        return modified;
927    }
928
929    /**
930     * Removes a single instance of the specified {@link Curve} (and its associated
931     * weight) from this collection, if it is present. More formally, removes an element e
932     * such that (o==null ? e==null : o.equals(e)), if this collection contains one or
933     * more such elements. Returns true if this collection contained the specified curve
934     * (or equivalently, if this collection changed as a result of the call).
935     *
936     * @param o Curve to be removed from this collection (along with its associated
937     *          weight), if present.
938     * @return <code>true</code> if this collection changed as a result of the call
939     */
940    @Override
941    public boolean remove(Object o) {
942        int index = _crvs.indexOf(o);
943        boolean changed = _crvs.remove(o);
944        if (changed) {
945            _wts.remove(index);
946            if (o instanceof AbstractGeomElement && !(o instanceof Immutable)) {
947                ((AbstractGeomElement)o).removeChangeListener(_childChangeListener);
948            }
949            fireChangeEvent();
950        }
951        return changed;
952    }
953
954    /**
955     * Removes the curve (and its associated weight) at the specified position in this
956     * LinearComboCurve. Shifts any subsequent curves and weights to the left (subtracts
957     * one from their indices). Returns the curve that was removed from the list; the
958     * removed weight is lost.
959     *
960     * @param index the index of the curve and weight to remove. (0 returns the 1st
961     *              element, -1 returns the last, -2 returns the 2nd from last, etc).
962     * @return the curve previously at the specified position.
963     */
964    @Override
965    public Curve remove(int index) {
966        index = normalizeIndex(index);
967        _wts.remove(index);
968        Curve old = _crvs.remove(index);
969        if (!(old instanceof Immutable))
970            old.removeChangeListener(_childChangeListener);
971        fireChangeEvent();  //  Notify change listeners.
972        return old;
973    }
974
975    /**
976     * Removes the curve with the specified name from this list. Shifts any subsequent
977     * elements to the left (subtracts one from their indices). Returns the element that
978     * was removed from the list.
979     *
980     * @param name the name of the element to remove.
981     * @return the element previously at the specified position.
982     */
983    @Override
984    public Curve remove(String name) {
985
986        Curve element = null;
987        int index = getIndexFromName(name);
988        if (index >= 0)
989            element = this.remove(index);
990
991        return element;
992    }
993
994    /**
995     * Removes all of the curves from this linear combination. The linear combination will
996     * be empty and undefined after this call returns.
997     */
998    @Override
999    public void clear() {
1000        int size = _crvs.size();
1001        for (int i = 0; i < size; ++i) {
1002            Curve crv = _crvs.get(i);
1003            if (!(crv instanceof Immutable))
1004                crv.removeChangeListener(_childChangeListener);
1005        }
1006        _crvs.clear();
1007        _wts.clear();
1008        fireChangeEvent();  //  Notify change listeners.
1009    }
1010
1011    /**
1012     * Returns an iterator over the curves in this LinearComboCurve.
1013     *
1014     * @return an iterator over this list of curves.
1015     */
1016    @Override
1017    public java.util.Iterator<Curve> iterator() {
1018        return _crvs.iterator();
1019    }
1020
1021    /**
1022     * Returns a list iterator over the curves in this LinearComboCurve.
1023     *
1024     * @return an iterator over this list of curves.
1025     */
1026    @Override
1027    public java.util.ListIterator<Curve> listIterator() {
1028        return _crvs.listIterator();
1029    }
1030
1031    /**
1032     * Returns a Curve list iterator from the specified position.
1033     *
1034     * @param index the index of first Curve to be returned from the list iterator (by a
1035     *              call to the next method).
1036     * @return a list iterator of the curves in this list starting at the specified
1037     *         position in this list.
1038     */
1039    @Override
1040    public java.util.ListIterator<Curve> listIterator(int index) {
1041        return _crvs.listIterator(index);
1042    }
1043
1044    /**
1045     * Returns an array containing all of the curves in this collection.
1046     */
1047    @Override
1048    public Curve[] toArray() {
1049        return (Curve[])_crvs.toArray();
1050    }
1051
1052    /**
1053     * Returns an array containing all of the curves in this collection. If the
1054     * collection fits in the specified array, it is returned therein. Otherwise, a new
1055     * array is allocated with the runtime type of the specified array and the size of
1056     * this collection.
1057     *
1058     * @param <T> The type of elements in this LinearComboCurve (Curve type).
1059     * @param a   the array into which the elements of the collection are to be stored, if
1060     *            it is big enough; otherwise, a new array of the same type is allocated
1061     *            for this purpose.
1062     * @return an array containing the curves of this collection.
1063     */
1064    @Override
1065    @SuppressWarnings("SuspiciousToArrayCall")
1066    public <T> T[] toArray(T[] a) {
1067        return _crvs.toArray(a);
1068    }
1069
1070    /**
1071     * Returns the number of physical dimensions of the geometry element. This
1072     * implementation always returns the physical dimension of the underlying Curve
1073     * objects or 0 if this list has no Curve objects in it.
1074     *
1075     * @return The number of physical dimensions of the geometry element.
1076     */
1077    @Override
1078    public int getPhyDimension() {
1079        if (isEmpty())
1080            return 0;
1081        return get(0).getPhyDimension();
1082    }
1083
1084    /**
1085     * Calculate a point on the curve for the given parametric distance along the curve.
1086     *
1087     * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive).
1088     * @return the calculated point
1089     */
1090    @Override
1091    public Point getRealPoint(double s) {
1092        validateCurve();
1093        validateParameter(s);
1094
1095        StackContext.enter();
1096        try {
1097            Point p = _crvs.get(0).getRealPoint(s).times(_wts.get(0));
1098            int size = _crvs.size();
1099            for (int i = 1; i < size; ++i) {
1100                Point crvPnt = _crvs.get(i).getRealPoint(s);
1101                double Wi = _wts.get(i);
1102                p = p.plus(crvPnt.times(Wi));
1103            }
1104
1105            return StackContext.outerCopy(p);
1106        } finally {
1107            StackContext.exit();
1108        }
1109    }
1110
1111    /**
1112    * Calculate all the derivatives from <code>0</code> to <code>grade</code> with respect
1113    * to parametric distance on the curve for the given parametric distance along the curve,
1114    * <code>d^{grade}p(s)/d^{grade}s</code>.
1115    * <p>
1116    * Example:<br>
1117    * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br>
1118    * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc.
1119    * </p>
1120    *
1121    * @param s     Parametric distance to calculate derivatives for (0.0 to 1.0 inclusive).
1122    * @param grade The maximum grade to calculate the derivatives for (1=1st derivative, 2=2nd derivative, etc)
1123    * @return A list of derivatives up to the specified grade of the curve at the specified parametric position.
1124    * @throws IllegalArgumentException if the grade is &lt; 0.
1125    */
1126    @Override
1127    public List<Vector<Length>> getSDerivatives(double s, int grade) {
1128        validateCurve();
1129        validateParameter(s);
1130        if (grade < 0)
1131            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
1132
1133        //  Create a list to hold the output.
1134        List<Vector<Length>> output = _crvs.get(0).getSDerivatives(s, grade);
1135        double Wi = _wts.get(0);
1136        for (int g=0; g <= grade; ++g) {
1137            Vector<Length> v = output.get(g);
1138            v = v.times(Wi);
1139            output.set(g, v);
1140        }
1141
1142        int size = _crvs.size();
1143        for (int i=1; i < size; ++i) {
1144            List<Vector<Length>> ders = _crvs.get(i).getSDerivatives(s, grade);
1145            Wi = _wts.get(i);
1146            for (int g = 0; g <= grade; ++g) {
1147                Vector<Length> v = ders.get(g);
1148                v = v.times(Wi);
1149                v = v.plus(output.get(g));
1150                output.set(g, v);
1151            }
1152        }
1153
1154        //  Set the origin of all the vectors to p(s).
1155        Point p = Point.valueOf(output.get(0));
1156        for (int g=0; g <= grade; ++g) {
1157            output.get(g).setOrigin(p);
1158        }
1159
1160        return output;
1161    }
1162
1163    /**
1164     * Split this curve at the specified parametric position returning a list containing
1165     * two curves (a lower curve with smaller parametric positions than "s" and an upper
1166     * curve with larger parametric positions).
1167     *
1168     * @param s The parametric position where this curve should be split (must not be 0 or
1169     *          1!).
1170     * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve.
1171     */
1172    @Override
1173    public GeomList<LinearComboCurve> splitAt(double s) {
1174        validateCurve();
1175        validateParameter(s);
1176        if (parNearEnds(s, TOL_S))
1177            throw new IllegalArgumentException(MessageFormat.format(
1178                    RESOURCES.getString("canNotSplitAtEnds"), "curve"));
1179
1180        //  Create LinearComboCurve objects for each end.
1181        LinearComboCurve cl = LinearComboCurve.newInstance();
1182        LinearComboCurve cu = LinearComboCurve.newInstance();
1183
1184        //  Split each child curve at the specified position.
1185        int size = size();
1186        for (int i=0; i < size; ++i) {
1187            Curve crvi = _crvs.get(i);
1188            Double Wi = _wts.get(i);
1189            GeomList<Curve> splits = crvi.splitAt(s);
1190            cl.add(splits.get(0),Wi);
1191            cu.add(splits.get(1),Wi);
1192        }
1193
1194        //  Create the output list.
1195        GeomList output = GeomList.newInstance();
1196        output.add(cl, cu);
1197
1198        return output;
1199    }
1200
1201    /**
1202     * Return <code>true</code> if this LinearComboCurve contains valid and finite numerical
1203     * components. A value of <code>false</code> will be returned if any of the coordinate
1204     * values or weights are NaN or Inf.
1205     *
1206     * @return true if this line segment contains valid and finite data.
1207     */
1208    @Override
1209    public boolean isValid() {
1210        int size = _crvs.size();
1211        for (int i=size-1; i >= 0; --i) {
1212            Curve crv = _crvs.get(i);
1213            double w = _wts.get(i);
1214            if (!crv.isValid() || Double.isInfinite(w) || Double.isNaN(w))
1215                return false;
1216        }
1217        return true;
1218    }
1219
1220    /**
1221     * Returns a transformed version of this element. The returned list of objects
1222     * implement {@link GeomTransform} and contains transformed versions of the contents
1223     * of this list as children.
1224     *
1225     * @param transform The transformation to apply to this geometry. May not be null.
1226     * @return A new LinearCombonCurve that is identical to this one with the specified
1227     *         transformation applied to member curves.
1228     * @throws DimensionException if this surface is not 3D.
1229     */
1230    @Override
1231    public LinearComboCurve getTransformed(GTransform transform) {
1232        requireNonNull(transform);
1233        LinearComboCurve list = LinearComboCurve.newInstance();
1234        copyState(list);
1235
1236        int size = _crvs.size();
1237        for (int i = 0; i < size; ++i) {
1238            Curve crvi = _crvs.get(i);
1239            Double Wi = _wts.get(i);
1240            list.add((Curve)crvi.getTransformed(transform), Wi);
1241        }
1242        return list;
1243    }
1244
1245    /**
1246     * Return the coordinate point representing the minimum bounding box corner of this
1247     * geometry element (e.g.: min X, min Y, min Z).
1248     *
1249     * @return The minimum bounding box coordinate for this geometry element.
1250     * @throws IndexOutOfBoundsException if this list contains no geometry.
1251     */
1252    @Override
1253    public Point getBoundsMin() {
1254        if (isEmpty())
1255            throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry"));
1256
1257        StackContext.enter();
1258        try {
1259            Point minPoint = _crvs.get(0).getBoundsMin().times(_wts.get(0));
1260            int size = _crvs.size();
1261            for (int i = 1; i < size; ++i) {
1262                double Wi = _wts.get(i);
1263                GeomElement element = _crvs.get(i);
1264                minPoint = minPoint.plus(element.getBoundsMin().times(Wi));
1265            }
1266
1267            return StackContext.outerCopy(minPoint);
1268        } finally {
1269            StackContext.exit();
1270        }
1271    }
1272
1273    /**
1274     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
1275     * X, max Y, max Z).
1276     *
1277     * @return The maximum bounding box coordinate for this geometry element.
1278     * @throws IndexOutOfBoundsException if this list contains no elements.
1279     */
1280    @Override
1281    public Point getBoundsMax() {
1282        if (isEmpty())
1283            throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry"));
1284
1285        StackContext.enter();
1286        try {
1287            Point maxPoint = _crvs.get(0).getBoundsMax().times(_wts.get(0));
1288            int size = _crvs.size();
1289            for (int i = 1; i < size; ++i) {
1290                double Wi = _wts.get(i);
1291                GeomElement element = _crvs.get(i);
1292                maxPoint = maxPoint.plus(element.getBoundsMax().times(Wi));
1293            }
1294
1295            return StackContext.outerCopy(maxPoint);
1296        } finally {
1297            StackContext.exit();
1298        }
1299    }
1300
1301    /**
1302     * Returns the unit in which the curves in this linear combination curve are stated.
1303     *
1304     * @return The unit in which the curves in this LinearComboCurve are stated or the
1305     *         default unit if this surface has no member curves.
1306     */
1307    @Override
1308    public Unit<Length> getUnit() {
1309        if (isEmpty())
1310            return GeomUtil.getDefaultUnit();
1311
1312        return _crvs.get(0).getUnit();
1313    }
1314
1315    /**
1316     * Returns the equivalent to this LinearComboCurve but stated in the specified unit.
1317     *
1318     * @param unit the length unit of the curve to be returned. May not be null.
1319     * @return an equivalent to this LinearComboCurve but stated in the specified unit.
1320     * @throws ConversionException if the the input unit is not a length unit.
1321     */
1322    @Override
1323    public LinearComboCurve to(Unit<Length> unit) throws ConversionException {
1324        if (unit.equals(getUnit()))
1325            return this;
1326
1327        LinearComboCurve crv = LinearComboCurve.newInstance();
1328        copyState(crv);
1329
1330        //  Convert the curves.
1331        int size = _crvs.size();
1332        for (int i = 0; i < size; ++i) {
1333            Curve crvi = _crvs.get(i);
1334            Double Wi = _wts.get(i);
1335            crv.add(crvi.to(unit), Wi);
1336        }
1337
1338        return crv;
1339    }
1340
1341    /**
1342     * Return the equivalent of this LinearComboCurve converted to the specified number of physical
1343     * dimensions. If the number of dimensions is greater than this element, then zeros
1344     * are added to the additional dimensions. If the number of dimensions is less than
1345     * this element, then the extra dimensions are simply dropped (truncated). If the new
1346     * dimensions are the same as the dimension of this element, then this element is
1347     * simply returned.
1348     *
1349     * @param newDim The dimension of the curve to return.
1350     * @return This LinearComboCurve converted to the new dimensions.
1351     */
1352    @Override
1353    public LinearComboCurve toDimension(int newDim) {
1354        if (getPhyDimension() == newDim)
1355            return this;
1356
1357        LinearComboCurve crv = LinearComboCurve.newInstance();
1358        copyState(crv);
1359
1360        //  Convert the curves.
1361        int size = _crvs.size();
1362        for (int i = 0; i < size; ++i) {
1363            Curve crvi = _crvs.get(i);
1364            Double Wi = _wts.get(i);
1365            crv.add(crvi.toDimension(newDim), Wi);
1366        }
1367
1368        return crv;
1369    }
1370
1371    /**
1372     * Return a NURBS curve representation of this curve to within the specified
1373     * tolerance.
1374     *
1375     * @param tol The greatest possible difference between this curve and the NURBS
1376     *            representation returned. May not be null.
1377     * @return A NURBS curve that represents this curve to within the specified tolerance.
1378     */
1379    @Override
1380    public NurbsCurve toNurbs(Parameter<Length> tol) {
1381        StackContext.enter();
1382        try {
1383
1384            //  Grid some points onto the curve.
1385            PointString<SubrangePoint> str = this.gridToTolerance(requireNonNull(tol));
1386
1387            //  Fit a cubic NURBS curve to the points.
1388            int deg = 3;
1389            if (str.size() <= 3)
1390                deg = str.size() - 1;
1391            BasicNurbsCurve curve = CurveFactory.fitPoints(deg, str);
1392            copyState(curve);   //  Copy over the super-class state for this curve to the new one.
1393
1394            return StackContext.outerCopy(curve);
1395
1396        } finally {
1397            StackContext.exit();
1398        }
1399    }
1400
1401    /**
1402     * Returns the text representation of this geometry element.
1403     *
1404     * @return the text representation of this geometry element.
1405     */
1406    @Override
1407    public Text toText() {
1408        TextBuilder tmp = TextBuilder.newInstance();
1409        String className = this.getClass().getName();
1410        tmp.append(className.substring(className.lastIndexOf(".") + 1));
1411        tmp.append(": {");
1412        String nameStr = getName();
1413        boolean hasName = nonNull(nameStr);
1414        if (hasName) {
1415            tmp.append(nameStr);
1416            tmp.append(" = {");
1417        }
1418        tmp.append("\n");
1419        int size = this.size();
1420        for (int i = 0; i < size; ++i) {
1421            GeomElement e = this.get(i);
1422            tmp.append(e.toText());
1423            if (i < size - 1)
1424                tmp.append(",\n");
1425            else
1426                tmp.append("\n");
1427        }
1428        if (hasName)
1429            tmp.append('}');
1430        tmp.append("}");
1431        Text txt = tmp.toText();
1432        TextBuilder.recycle(tmp);
1433        return txt;
1434    }
1435
1436    /**
1437     * Compares the specified object with this list of <code>Curve</code> objects for
1438     * equality. Returns true if and only if both collections are of the same type and
1439     * both collections contain equal elements in the same order.
1440     *
1441     * @param obj the object to compare with.
1442     * @return <code>true</code> if this list is identical to that list;
1443     *         <code>false</code> otherwise.
1444     */
1445    @Override
1446    public boolean equals(Object obj) {
1447        if (this == obj)
1448            return true;
1449        if ((obj == null) || (obj.getClass() != this.getClass()))
1450            return false;
1451
1452        LinearComboCurve that = (LinearComboCurve)obj;
1453        return this._crvs.equals(that._crvs)
1454                && this._wts.equals(that._wts)
1455                && super.equals(obj);
1456    }
1457
1458    /**
1459     * Returns the hash code for this <code>LinearComboCurve</code>.
1460     *
1461     * @return the hash code value.
1462     */
1463    @Override
1464    public int hashCode() {
1465        return 31*super.hashCode() + Objects.hash(_crvs, _wts);
1466    }
1467
1468    /**
1469     * Returns a copy of this <code>LinearComboCurve</code> instance
1470     * {@link javolution.context.AllocatorContext allocated} by the calling thread
1471     * (possibly on the stack).
1472     *
1473     * @return an identical and independent copy of this object.
1474     */
1475    @Override
1476    public LinearComboCurve copy() {
1477        return copyOf(this);
1478    }
1479
1480    /**
1481     * Return a copy of this object with any transformations or subranges removed
1482     * (applied).
1483     *
1484     * @return A copy of this object with any transformations or subranges removed.
1485     */
1486    @Override
1487    public LinearComboCurve copyToReal() {
1488        LinearComboCurve newCrv = LinearComboCurve.newInstance();
1489        copyState(newCrv);
1490
1491        int size = this.size();
1492        for (int i = 0; i < size; ++i) {
1493            Curve crvi = this._crvs.get(i);
1494            Double Wi = this._wts.get(i);
1495            Curve crv2i = crvi.copyToReal();
1496            newCrv.add(crv2i,Wi);
1497        }
1498
1499        return newCrv;
1500    }
1501
1502    /**
1503     * Resets the internal state of this object to its default values. Subclasses that
1504     * override this method must call <code>super.reset();</code> to ensure that the state
1505     * is reset properly.
1506     */
1507    @Override
1508    public void reset() {
1509        clear();
1510        ((FastTable)_crvs).reset();
1511        ((FastTable)_wts).reset();
1512        super.reset();
1513    }
1514
1515    /**
1516     * Holds the default XML representation for this object.
1517     */
1518    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
1519    protected static final XMLFormat<LinearComboCurve> XML = new XMLFormat<LinearComboCurve>(LinearComboCurve.class) {
1520
1521        @Override
1522        public LinearComboCurve newInstance(Class<LinearComboCurve> cls, XMLFormat.InputElement xml) throws XMLStreamException {
1523            LinearComboCurve obj = FACTORY.object();
1524            return obj;
1525        }
1526
1527        @Override
1528        public void read(XMLFormat.InputElement xml, LinearComboCurve obj) throws XMLStreamException {
1529            AbstractCurve.XML.read(xml, obj);     // Call parent read.
1530
1531            FastTable<Curve> crvs = xml.get("Contents", FastTable.class);
1532            FastTable<Double> wts = xml.get("Weights", FastTable.class);
1533            obj._crvs = crvs;
1534            obj._wts = wts;
1535        }
1536
1537        @Override
1538        public void write(LinearComboCurve obj, XMLFormat.OutputElement xml) throws XMLStreamException {
1539            AbstractCurve.XML.write(obj, xml);    // Call parent write.
1540
1541            xml.add((FastTable)obj._crvs, "Contents", FastTable.class);
1542            xml.add((FastTable)obj._wts, "Weights", FastTable.class);
1543        }
1544    };
1545
1546    //////////////////////
1547    // Factory Creation //
1548    //////////////////////
1549    /**
1550     * Do not allow the default constructor to be used except by subclasses.
1551     */
1552    protected LinearComboCurve() {
1553    }
1554
1555    private static final ObjectFactory<LinearComboCurve> FACTORY = new ObjectFactory<LinearComboCurve>() {
1556        @Override
1557        protected LinearComboCurve create() {
1558            return new LinearComboCurve();
1559        }
1560
1561        @Override
1562        protected void cleanup(LinearComboCurve obj) {
1563            obj.reset();
1564            obj._crvs = null;
1565            obj._wts = null;
1566        }
1567    };
1568
1569    /**
1570     * Recycles a case instance immediately (on the stack when executing in a
1571     * StackContext).
1572     *
1573     * @param instance The instance to be recycled.
1574     */
1575    public static void recycle(LinearComboCurve instance) {
1576        FACTORY.recycle(instance);
1577    }
1578
1579    @SuppressWarnings("unchecked")
1580    private static LinearComboCurve copyOf(LinearComboCurve original) {
1581        LinearComboCurve obj = LinearComboCurve.newInstance();
1582
1583        int size = original.size();
1584        for (int i = 0; i < size; ++i) {
1585            Curve crvi = original.get(i).copy();
1586            Double Wi = original._wts.get(i);
1587            obj.add(crvi, Wi);
1588        }
1589
1590        original.copyState(obj);
1591        return obj;
1592    }
1593
1594}