001/**
002 * PointComponent -- A list of PointArray objects.
003 *
004 * Copyright (C) 2009-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 java.text.MessageFormat;
021import java.util.Arrays;
022import java.util.Collection;
023import static java.util.Objects.requireNonNull;
024import javax.measure.converter.ConversionException;
025import javax.measure.quantity.Length;
026import javax.measure.unit.Unit;
027import javolution.context.ObjectFactory;
028import javolution.util.FastTable;
029import javolution.xml.XMLFormat;
030import javolution.xml.stream.XMLStreamException;
031
032/**
033 * A list that holds only {@link PointArray} objects.
034 * <p>
035 * WARNING: This list allows geometry to be stored in different units. If consistent units
036 * are required, then the user must specifically convert the list items.
037 * </p>
038 *
039 * <p> Modified by: Joseph A. Huwaldt </p>
040 *
041 * @author Joseph A. Huwaldt, Date: May 1, 2009
042 * @version February 17, 2025
043 */
044@SuppressWarnings({"serial", "CloneableImplementsClone"})
045public final class PointComponent extends AbstractPointGeomList<PointComponent, PointArray> {
046
047    private FastTable<PointArray> _list;
048
049    /**
050     * Return the list underlying this geometry list.
051     *
052     * @return The list underlying this geometry list.
053     */
054    @Override
055    protected FastTable<PointArray> getList() {
056        return _list;
057    }
058
059    /**
060     * Returns a new, empty, preallocated or recycled <code>PointComponent</code> instance
061     * (on the stack when executing in a <code>StackContext</code>), that can store a list
062     * of {@link PointArray} objects.
063     *
064     * @return A new, empty PointComponent.
065     */
066    public static PointComponent newInstance() {
067        PointComponent list = FACTORY.object();
068        list._list = FastTable.newInstance();
069        return list;
070    }
071
072    /**
073     * Returns a new, empty, preallocated or recycled <code>PointComponent</code> instance
074     * (on the stack when executing in a <code>StackContext</code>) with the specified
075     * name, that can store a list of {@link PointArray} objects.
076     *
077     * @param name The name to be assigned to this list (may be <code>null</code>).
078     * @return A new, empty PointComponent.
079     */
080    public static PointComponent newInstance(String name) {
081        PointComponent list = PointComponent.newInstance();
082        list.setName(name);
083        return list;
084    }
085
086    /**
087     * Return a PointComponent made up of the {@link PointArray} objects in the specified
088     * collection.
089     *
090     * @param name     The name to be assigned to this list (may be <code>null</code>).
091     * @param elements A collection of PointArray elements. May not be null.
092     * @return A new PointComponent containing the elements in the specified collection.
093     */
094    public static PointComponent valueOf(String name, Collection<? extends PointArray> elements) {
095        for (Object element : elements) {
096            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
097            if (!(element instanceof PointArray))
098                throw new ClassCastException(MessageFormat.format(
099                        RESOURCES.getString("listElementTypeErr"), "PointComponent", "PointArray"));
100        }
101
102        PointComponent list = PointComponent.newInstance(name);
103        list.addAll(elements);
104
105        return list;
106    }
107
108    /**
109     * Return a PointComponent made up of the {@link PointArray} objects in the specified
110     * array.
111     *
112     * @param name     The name to be assigned to this list (may be <code>null</code>).
113     * @param elements A list of PointArray elements. May not be null.
114     * @return A new PointComponent containing the elements in the specified array.
115     */
116    public static PointComponent valueOf(String name, PointArray... elements) {
117        requireNonNull(elements);
118        PointComponent list = PointComponent.newInstance(name);
119        list.addAll(Arrays.asList(elements));
120
121        return list;
122    }
123
124    /**
125     * Return a PointComponent made up of the {@link PointArray} objects in the specified
126     * array.
127     *
128     * @param elements A list of PointArray elements. May not be null.
129     * @return A new PointComponent containing the elements in the specified array.
130     */
131    public static PointComponent valueOf(PointArray... elements) {
132        return PointComponent.valueOf(null, elements);
133    }
134
135    /**
136     * Returns the range of elements in this list from the specified start and ending
137     * indexes.
138     *
139     * @param first index of the first element to return.
140     * @param last  index of the last element to return.
141     * @return the list of elements in the given range from this list.
142     * @throws IndexOutOfBoundsException if the given index is out of range (index &lt; 0
143     * || index &ge; size())
144     */
145    @Override
146    public PointComponent getRange(int first, int last) {
147        first = normalizeIndex(first);
148        last = normalizeIndex(last);
149
150        PointComponent list = PointComponent.newInstance();
151        for (int i = first; i <= last; ++i)
152            list.add(get(i));
153        return list;
154    }
155
156    /**
157     * Returns an new {@link PointString} with the elements in this list in reverse order.
158     *
159     * @return A new PointComponent with the elements in this list in reverse order.
160     */
161    @Override
162    public PointComponent reverse() {
163        PointComponent list = PointComponent.newInstance();
164        copyState(list);
165        int size = this.size();
166        for (int i = size - 1; i >= 0; --i) {
167            list.add(get(i));
168        }
169        return list;
170    }
171
172    /**
173     * Return the equivalent of this list converted to the specified number of physical
174     * dimensions. If the number of dimensions is greater than this element, then zeros
175     * are added to the additional dimensions. If the number of dimensions is less than
176     * this element, then the extra dimensions are simply dropped (truncated). If the new
177     * dimensions are the same as the dimension of this element, then this list is simply
178     * returned.
179     *
180     * @param newDim The dimension of the element to return.
181     * @return The equivalent of this list converted to the new dimensions.
182     */
183    @Override
184    public PointComponent toDimension(int newDim) {
185        if (getPhyDimension() == newDim)
186            return this;
187        PointComponent newList = PointComponent.newInstance();
188        copyState(newList);
189        int size = this.size();
190        for (int i = 0; i < size; ++i) {
191            PointArray element = this.get(i);
192            newList.add(element.toDimension(newDim));
193        }
194        return newList;
195    }
196
197    /**
198     * Returns the equivalent to this list but with <I>all</I> the elements stated in the
199     * specified unit.
200     *
201     * @param unit the length unit of the list to be returned. May not be null.
202     * @return an equivalent to this list but stated in the specified unit.
203     * @throws ConversionException if the the input unit is not a length unit.
204     */
205    @Override
206    public PointComponent to(Unit<Length> unit) {
207        requireNonNull(unit);
208        PointComponent list = PointComponent.newInstance();
209        copyState(list);
210        int size = this.size();
211        for (int i = 0; i < size; ++i) {
212            PointArray e = this.get(i);
213            list.add(e.to(unit));
214        }
215        return list;
216    }
217
218    /**
219     * Returns a copy of this <code>PointComponent</code> instance
220     * {@link javolution.context.AllocatorContext allocated} by the calling thread
221     * (possibly on the stack).
222     *
223     * @return an identical and independent copy of this object.
224     */
225    @Override
226    public PointComponent copy() {
227        return copyOf(this);
228    }
229
230    /**
231     * Return a copy of this object with any transformations or subranges removed
232     * (applied).
233     *
234     * @return A copy of this list with any sub-element transformations or subranges
235     *         removed.
236     */
237    @Override
238    public PointComponent copyToReal() {
239        PointComponent newList = PointComponent.newInstance();
240        copyState(newList);
241        int size = this.size();
242        for (int i = 0; i < size; ++i) {
243            PointArray element = this.get(i);
244            newList.add(element.copyToReal());
245        }
246        return newList;
247    }
248
249    /**
250     * Return the total number of quadrilateral panels in this component.
251     *
252     * @return the total number of panels in this component.
253     * @throws IndexOutOfBoundsException if the strings in any array in this component
254     * have different lengths.
255     */
256    public int getNumberOfPanels() throws IndexOutOfBoundsException {
257        int sum = 0;
258        int size = this.size();
259        for (int i = 0; i < size; ++i) {
260            PointArray e = this.get(i);
261            sum += e.getNumberOfPanels();
262        }
263        return sum;
264    }
265
266    /**
267     * Returns transformed version of this element. The returned object implements
268     * {@link GeomTransform} and contains transformed versions of the contents of this
269     * list as children.
270     *
271     * @param transform The transform to apply to this geometry element. May not be null.
272     * @return A transformed version of this geometry element.
273     * @throws DimensionException if this element is not 3D.
274     */
275    @Override
276    public PointComponent getTransformed(GTransform transform) {
277        requireNonNull(transform);
278        PointComponent list = PointComponent.newInstance();
279        copyState(list);
280        int size = this.size();
281        for (int i = 0; i < size; ++i) {
282            PointArray element = this.get(i);
283            list.add(element.getTransformed(transform));
284        }
285        return list;
286    }
287
288    /**
289     * Replaces the {@link PointArray} at the specified position in this list with the
290     * specified element. Null elements are ignored. The input element must have the same
291     * physical dimensions as the other items in this list, or an exception is thrown.
292     *
293     * @param index   The index of the element to replace (0 returns the 1st element, -1
294     *                returns the last, -2 returns the 2nd from last, etc).
295     * @param element The element to be stored at the specified position. May not be null.
296     * @return The element previously at the specified position in this list.
297     * @throws java.lang.IndexOutOfBoundsException - if <code>index &gt; size()</code>
298     * @throws DimensionException if the input element's dimensions are different from
299     * this list's dimensions.
300     */
301    @Override
302    public PointArray set(int index, PointArray element) {
303        return super.set(index, requireNonNull(element));
304    }
305
306    /**
307     * Inserts the specified {@link PointArray} at the specified position in this list.
308     * Shifts the element currently at that position (if any) and any subsequent elements
309     * to the right (adds one to their indices). Null values are ignored. The input
310     * value must have the same physical dimensions as the other items in this list, or
311     * an exception is thrown.
312     * <p>
313     * Note: If this method is used concurrent access must be synchronized (the list is
314     * not thread-safe).
315     * </p>
316     *
317     * @param index the index at which the specified element is to be inserted. (0 returns
318     *              the 1st element, -1 returns the last, -2 returns the 2nd from last,
319     *              etc).
320     * @param value the element to be inserted. May not be null.
321     * @throws IndexOutOfBoundsException if <code>index &gt; size()</code>
322     * @throws DimensionException if the input value dimensions are different from
323     * this list's dimensions.
324     */
325    @Override
326    public void add(int index, PointArray value) {
327        super.add(index, requireNonNull(value));
328    }
329
330    /**
331     * Inserts all of the {@link PointArray} objects in the specified collection into this
332     * list at the specified position. Shifts the element currently at that position (if
333     * any) and any subsequent elements to the right (increases their indices). The new
334     * elements will appear in this list in the order that they are returned by the
335     * specified collection's iterator. The behavior of this operation is unspecified if
336     * the specified collection is modified while the operation is in progress. Note that
337     * this will occur if the specified collection is this list, and it's nonempty.  The
338     * input elements must have the same physical dimensions as the other items in this
339     * list, or an exception is thrown.
340     *
341     * @param index index at which to insert first element from the specified collection.
342     * @param c     Elements to be inserted into this collection. May not be null.
343     * @return <code>true</code> if this collection changed as a result of the call.
344     * @throws DimensionException if the input element's dimensions are different from
345     * this list's dimensions.
346     */
347    @Override
348    public boolean addAll(int index, Collection<? extends PointArray> c) {
349        int thisSize = this.size();
350        for (Object element : c) {
351            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
352            if (!(element instanceof PointArray))
353                throw new ClassCastException(MessageFormat.format(
354                        RESOURCES.getString("listElementTypeErr"), "PointComponent", "PointArray"));
355            if (thisSize != 0) {
356                if (((GeomElement)element).getPhyDimension() != this.getPhyDimension())
357                    throw new DimensionException(RESOURCES.getString("dimensionErr"));
358            }
359        }
360        return super.addAll(index, c);
361    }
362
363    /**
364     * Holds the default XML representation for this object.
365     */
366    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
367    protected static final XMLFormat<PointComponent> XML = new XMLFormat<PointComponent>(PointComponent.class) {
368
369        @Override
370        public PointComponent newInstance(Class<PointComponent> cls, XMLFormat.InputElement xml) throws XMLStreamException {
371            PointComponent obj = PointComponent.newInstance();
372            return obj;
373        }
374
375        @Override
376        public void read(XMLFormat.InputElement xml, PointComponent obj) throws XMLStreamException {
377            AbstractPointGeomList.XML.read(xml, obj);     // Call parent read.
378        }
379
380        @Override
381        public void write(PointComponent obj, XMLFormat.OutputElement xml) throws XMLStreamException {
382            AbstractPointGeomList.XML.write(obj, xml);    // Call parent write.
383        }
384    };
385
386    //////////////////////
387    // Factory Creation //
388    //////////////////////
389    private static final ObjectFactory<PointComponent> FACTORY = new ObjectFactory<PointComponent>() {
390        @Override
391        protected PointComponent create() {
392            return new PointComponent();
393        }
394
395        @Override
396        protected void cleanup(PointComponent obj) {
397            obj.reset();
398        }
399    };
400
401    /**
402     * Recycles a case instance immediately (on the stack when executing in a
403     * StackContext).
404     *
405     * @param instance The instance to be recycled.
406     */
407    public static void recycle(PointComponent instance) {
408        FACTORY.recycle(instance);
409    }
410
411    /**
412     * Do not allow the default constructor to be used except by subclasses.
413     */
414    protected PointComponent() {
415    }
416
417    private static PointComponent copyOf(PointComponent original) {
418        PointComponent o = PointComponent.newInstance();
419        original.copyState(o);
420        int size = original.size();
421        for (int i = 0; i < size; ++i) {
422            PointArray element = original.get(i);
423            o.add(element.copy());
424        }
425        return o;
426    }
427}