001/**
002 * ArrayParam -- An array of point values in a case.
003 *
004 * Copyright (C) 2003-2017, 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 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 jahuwaldt.js.datareader;
019
020import jahuwaldt.js.param.Parameter;
021import java.text.MessageFormat;
022import java.util.Arrays;
023import java.util.List;
024import java.util.Objects;
025import static java.util.Objects.requireNonNull;
026import javax.measure.converter.ConversionException;
027import javax.measure.converter.UnitConverter;
028import javax.measure.quantity.Quantity;
029import javax.measure.unit.Unit;
030import javolution.context.ArrayFactory;
031import javolution.context.ObjectFactory;
032import javolution.util.FastTable;
033import org.jscience.mathematics.number.Float64;
034import org.jscience.mathematics.vector.Float64Vector;
035
036/**
037 * An array of parameter values in a {@link DataCase case} or run.
038 *
039 * <p> Modified by: Joseph A. Huwaldt </p>
040 *
041 * @author Joseph A. Huwaldt, Date: March 5, 2003
042 * @version March 18, 2017
043 *
044 * @param <Q> The Quantity or unit type of this array of values.
045 */
046public final class ArrayParam<Q extends Quantity> extends UnitParameter<Q, ArrayParam> {
047
048    //  The number of elements in the array (the size of the array).
049    private int _numElements = 0;
050
051    //  The array of points in current units.
052    private double[] _values;
053
054    /**
055     * Resets the internal state of this object to its default values.
056     */
057    @Override
058    protected void reset() {
059        super.reset();
060        ArrayFactory.DOUBLES_FACTORY.recycle(_values);
061        _values = null;
062        _numElements = 0;
063    }
064
065    /**
066     * Recycles a parameter instance immediately (on the stack when executing in a
067     * StackContext).
068     *
069     * @param instance The instance to be recycled.
070     */
071    public static void recycle(ArrayParam instance) {
072        FACTORY.recycle(instance);
073    }
074
075    /**
076     * Do not allow the default constructor to be used except by subclasses.
077     */
078    private ArrayParam() { }
079
080    /**
081     * Construct an array with the specified name and units with the array values all set
082     * to zero. The name and the units may <em>not</em> be null.
083     *
084     * @param <Q>  The quantity or unit type of this array of values.
085     * @param name The name of this array of parameters.
086     * @param unit The units to assume for each value in the array.
087     * @param size The size or number of elements in this array.
088     * @return An array with the specified name and units with the array values all set to
089     *         zero.
090     */
091    public static <Q extends Quantity> ArrayParam<Q> valueOf(CharSequence name, Unit<Q> unit, int size) {
092        ArrayParam<Q> param = ArrayParam.newInstance(name);
093        param._unit = requireNonNull(unit, MessageFormat.format(RESOURCES.getString("paramNullErr"),"unit"));
094
095        param._numElements = size;
096        param._values = ArrayFactory.DOUBLES_FACTORY.array(size);
097        Arrays.fill(param._values, 0, size, 0);
098
099        return param;
100    }
101
102    /**
103     * Construct an array with the specified name, array of values and units (name and
104     * units may <em>not</em> be null).
105     *
106     * @param <Q>    The quantity or unit type of this array of values.
107     * @param name   The name of this array of parameters.
108     * @param unit   The units to assume for each value in the array.
109     * @param values The Java array of values to be stored in this ArrayParam object.
110     * @return An array with the specified name, array of values and units.
111     */
112    public static <Q extends Quantity> ArrayParam<Q> valueOf(CharSequence name, Unit<Q> unit, double[] values) {
113        requireNonNull(unit, MessageFormat.format(RESOURCES.getString("paramNullErr"),"unit"));
114        requireNonNull(values, MessageFormat.format(RESOURCES.getString("paramNullErr"),"values"));
115
116        ArrayParam param = ArrayParam.newInstance(name);
117        param._unit = unit;
118        param._numElements = values.length;
119
120        //  Defensively copy the supplied array.
121        param._values = ArrayFactory.DOUBLES_FACTORY.array(values.length);
122        System.arraycopy(values, 0, param._values, 0, values.length);
123        Arrays.fill(param._values, values.length, param._values.length, 0);
124
125        return param;
126    }
127
128    /**
129     * Construct an array with the specified name, list of values and units (name and
130     * units may <em>not</em> be null).
131     *
132     * @param <Q>    The quantity or unit type of this array of values.
133     * @param name   The name of this array of parameters.
134     * @param unit   The units to assume for each value in the array.
135     * @param values The list of values to be stored in this array.
136     * @return An array with the specified name, list of values and units.
137     */
138    public static <Q extends Quantity> ArrayParam<Q> valueOf(CharSequence name, Unit<Q> unit, List<Double> values) {
139        requireNonNull(unit, MessageFormat.format(RESOURCES.getString("paramNullErr"),"unit"));
140        requireNonNull(values, MessageFormat.format(RESOURCES.getString("paramNullErr"),"values"));
141
142        int numElements = values.size();
143        ArrayParam param = ArrayParam.newInstance(name);
144        param._unit = unit;
145        param._numElements = numElements;
146
147        //  Defensively copy the supplied array.
148        param._values = ArrayFactory.DOUBLES_FACTORY.array(numElements);
149        for (int i = 0; i < numElements; ++i) {
150            param._values[i] = values.get(i);
151        }
152        Arrays.fill(param._values, numElements, param._values.length, 0);
153
154        return param;
155    }
156
157    /**
158     * Return the number of elements in this array.
159     *
160     * @return The number of elements in this array.
161     */
162    public int size() {
163        return _numElements;
164    }
165
166    /**
167     * Return the value of an element in the array in reference SI units.
168     *
169     * @param idx Index to the element in the array to be returned.
170     * @return The value of an element in the array in reference SI units.
171     * @throws IndexOutOfBoundsException if (index &lt; 0) || (index &gt; size()-1)
172     */
173    public double getValueSI(int idx) {
174        if (idx >= _numElements)
175            throw new IndexOutOfBoundsException(MessageFormat.format(
176                    RESOURCES.getString("idxOutOfBoundsErr"),idx,_numElements));
177
178        return getUnit().toStandardUnit().convert(_values[idx]);
179    }
180
181    /**
182     * Return the value of an element in the array in the current units.
183     *
184     * @param idx Index to the point in the array to be returned.
185     * @return The value of an element in the array in the current units.
186     * @throws IndexOutOfBoundsException if (index &lt; 0) || (index &gt; size()-1)
187     */
188    public double getValue(int idx) {
189        if (idx >= _numElements)
190            throw new IndexOutOfBoundsException(MessageFormat.format(
191                    RESOURCES.getString("idxOutOfBoundsErr"),idx,_numElements));
192
193        return _values[idx];
194    }
195
196    /**
197     * Return the list of values as a Float64Vector, in reference SI units.
198     *
199     * @return The list of values as a Float64Vector, in reference SI units.
200     */
201    public Float64Vector getValuesSI() {
202
203        FastTable<Float64> newValues = FastTable.newInstance();
204        UnitConverter cvtr = getUnit().toStandardUnit();
205
206        int length = _numElements;
207        for (int i = 0; i < length; ++i)
208            newValues.add(Float64.valueOf(cvtr.convert(_values[i])));
209
210        Float64Vector vector = Float64Vector.valueOf(newValues);
211        FastTable.recycle(newValues);
212
213        return vector;
214
215    }
216
217    /**
218     * Return the list of values as a Float64Vector, in current units.
219     *
220     * @return The list of values as a Float64Vector, in current units.
221     */
222    public Float64Vector getValues() {
223        FastTable<Float64> newValues = FastTable.newInstance();
224
225        int length = _numElements;
226        for (int i = 0; i < length; ++i)
227            newValues.add(Float64.valueOf(_values[i]));
228
229        Float64Vector vector = Float64Vector.valueOf(newValues);
230        FastTable.recycle(newValues);
231
232        return vector;
233    }
234
235    /**
236     * Returns the equivalent to this parameter but stated in the specified unit. The
237     * values <em>are</em> converted to the new units.
238     *
239     * @param unit the unit of the parameter to be returned.
240     * @return an equivalent to this parameter but stated in the specified unit.
241     * @throws ConversionException if the current model does not allows for conversion to
242     * the specified unit.
243     */
244    @Override
245    public ArrayParam<Q> to(Unit<Q> unit) throws ConversionException {
246        if (Objects.equals(_unit, requireNonNull(unit)))
247            return this;
248        UnitConverter cvtr = Parameter.converterOf(_unit, unit);
249        if (cvtr == UnitConverter.IDENTITY) { // No conversion necessary.
250            return this;
251        }
252        ArrayParam<Q> result = ArrayParam.newInstance(getName());
253        int length = _numElements;
254        result._numElements = length;
255        result._unit = unit;
256        result._values = ArrayFactory.DOUBLES_FACTORY.array(length);
257        for (int i = 0; i < length; ++i) {
258            double value = cvtr.convert(_values[i]);
259            result._values[i] = value;
260        }
261        Arrays.fill(result._values, length, result._values.length, 0);
262        return result;
263    }
264
265    /**
266     * Returns the parameter that has the same values as this parameter but with the units
267     * changed (<em>without</em> converting the values).
268     *
269     * @param <R>  The Quantity or unit type of the new unit.
270     * @param unit the unit of the parameter to be returned.
271     * @return an equivalent to this parameter but stated in the specified unit.
272     * @throws ConversionException if the current model does not allows for conversion to
273     * the specified unit.
274     */
275    @Override
276    public <R extends Quantity> ArrayParam<R> changeTo(Unit<R> unit) throws ConversionException {
277        requireNonNull(unit, MessageFormat.format(RESOURCES.getString("paramNullErr"),"unit"));
278        if (Objects.equals(_unit, unit))
279            return (ArrayParam<R>)this;
280        ArrayParam<R> result = (ArrayParam<R>)ArrayParam.copyOf(this);
281        result._unit = unit;
282        return result;
283    }
284
285    /**
286     * Returns a copy of this ArrayParam instance
287     * {@link javolution.context.AllocatorContext allocated} by the calling thread
288     * (possibly on the stack).
289     *
290     * @return an identical and independent copy of this parameter.
291     */
292    @Override
293    public ArrayParam<Q> copy() {
294        return copyOf(this);
295    }
296
297    /**
298     * Method that sorts the values in this array in ascending numerical order. And
299     * returns the values as a new array.
300     *
301     * @return A new ArrayParam with the values sorted in ascending numerical order.
302     */
303    public ArrayParam<Q> sort() {
304        ArrayParam<Q> P = copyOf(this);
305        Arrays.sort(P._values);
306        return P;
307    }
308
309    /**
310     * Compares the specified object with this parameter for strict equality same value,
311     * same units, same name, same user data.
312     *
313     * @param obj the object to compare with.
314     * @return <code>true</code> if this parameter is identical to that parameter;
315     *         <code>false</code> otherwise.
316     */
317    @Override
318    public boolean equals(Object obj) {
319        if (this == obj)
320            return true;
321        if ((obj == null) || (obj.getClass() != this.getClass()))
322            return false;
323
324        ArrayParam that = (ArrayParam)obj;
325        if (this._numElements != that._numElements)
326            return false;
327        else if (!equalArrays(this._values, that._values, this._numElements))
328            return false;
329
330        return super.equals(obj);
331    }
332
333    private boolean equalArrays(double[] arr1, double[] arr2, int length) {
334        for (int i = 0; i < length; ++i) {
335            double v = arr1[i];
336            if (arr2[i] != v)
337                return false;
338        }
339        return true;
340    }
341
342    /**
343     * Returns the hash code for this <code>ArrayParam</code>.
344     *
345     * @return the hash code value.
346     */
347    @Override
348    public int hashCode() {
349        int hash = super.hashCode();
350
351        hash = hash * 31 + _numElements;
352        hash = hash * 31 + Arrays.hashCode(_values);
353
354        return hash;
355    }
356
357    //////////////////////
358    // Factory Creation //
359    //////////////////////
360    /**
361     * Returns a new, preallocated or recycled <code>ArrayParam</code> instance (on the
362     * stack when executing in a <code>StackContext</code>) with the specified name, no
363     * units, and no values (zero size).
364     *
365     * @param name The name to be assigned to this parameter (may not be
366     *             <code>null</code>).
367     * @return A new ArrayParam instance with the given name and not data (zero size).
368     */
369    public static ArrayParam newInstance(CharSequence name) {
370        ArrayParam o = FACTORY.object();
371        o._numElements = 0;
372        try {
373            o.setName(name);
374        } catch (NullPointerException e) {
375            FACTORY.recycle(o);
376            throw e;
377        }
378        return o;
379    }
380
381    private static final ObjectFactory<ArrayParam> FACTORY = new ObjectFactory<ArrayParam>() {
382        @Override
383        protected ArrayParam create() {
384            return new ArrayParam();
385        }
386
387        @Override
388        protected void cleanup(ArrayParam obj) {
389            obj.reset();
390        }
391    };
392
393    private static ArrayParam copyOf(ArrayParam orig) {
394        ArrayParam param = ArrayParam.newInstance(orig.getName());
395        param._unit = orig.getUnit();
396        int length = orig._numElements;
397        param._numElements = length;
398        param._values = ArrayFactory.DOUBLES_FACTORY.array(length);
399        System.arraycopy(orig._values, 0, param._values, 0, length);
400        Arrays.fill(param._values, length, param._values.length, 0);
401        return param;
402    }
403}