blob: d94a77df98c07bcc03e5818f0dc00484b082cc27 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.parameter;
import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.lang.reflect.Array;
import javax.xml.bind.annotation.XmlTransient;
import org.opengis.referencing.operation.Matrix;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.internal.referencing.WKTKeywords;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.Classes;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
/**
* The values for a group of tensor parameters. This value group is extensible, i.e. the number of
* <code>"elt_<var>row</var>_<var>col</var>"</code> parameters depends on the {@code "num_row"} and
* {@code "num_col"} parameter values. Consequently, this {@code ParameterValueGroup} is also its own
* mutable {@code ParameterDescriptorGroup}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 0.6
*
* @param <E> the type of tensor element values.
*
* @since 0.4
* @module
*/
@XmlTransient
final class TensorValues<E> extends AbstractParameterDescriptor
implements ParameterDescriptorGroup, ParameterValueGroup, Cloneable
{
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -7747712999115044943L;
/**
* A provider of descriptors for matrix parameters. This object is used like a collection of
* {@link ParameterDescriptor}s, even if it does not implement any standard collection API.
*
* @see TensorParameters#descriptor(ParameterDescriptorGroup, String, int[])
* @see TensorParameters#getAllDescriptors(int[])
*/
private final TensorParameters<E> descriptors;
/**
* The parameter for the number of row, columns and other dimensions in the tensor.
*/
private final ParameterValue<Integer>[] dimensions;
/**
* The parameter values. Each array element is itself an {@code ParameterValue} array,
* and so on until we have nested {@link TensorParameters#rank()} arrays.
*
* <p>Will be constructed only when first requested.
* May be resized at any moment if a {@link #dimensions} parameter value change.</p>
*/
private Object[] values;
/**
* Constructs a new group of tensor parameters for the given properties.
*/
@SuppressWarnings({"unchecked","rawtypes"})
TensorValues(final Map<String,?> properties, final TensorParameters<E> descriptors) {
super(properties, 1, 1);
this.descriptors = descriptors;
dimensions = new ParameterValue[descriptors.rank()];
for (int i=0; i<dimensions.length; i++) {
dimensions[i] = descriptors.getDimensionDescriptor(i).createValue();
}
}
/**
* Constructs a copy of the given matrix parameters.
* If {@code clone} is true, the new group will be a clone of the given group.
* If {@code clone} is false, the new group will be initialized to default values.
*/
TensorValues(final TensorValues<E> other, final boolean clone) {
super(other);
descriptors = other.descriptors;
dimensions = other.dimensions.clone();
for (int i=0; i<dimensions.length; i++) {
final ParameterValue<Integer> dim = dimensions[i];
dimensions[i] = clone ? dim.clone() : dim.getDescriptor().createValue();
}
if (clone) {
values = clone(other.values);
}
}
/**
* Clones the given array of parameters.
* This method invokes itself for cloning sub-arrays.
*/
private static Object[] clone(Object[] values) {
if (values != null) {
values = values.clone();
for (int i=0; i<values.length; i++) {
Object element = values[i];
if (element instanceof GeneralParameterValue) {
element = ((GeneralParameterValue) element).clone();
} else {
element = clone((Object[]) element);
}
values[i] = element;
}
}
return values;
}
/**
* Returns a clone of this group.
*/
@Override
@SuppressWarnings("CloneDoesntCallSuperClone")
public ParameterValueGroup clone() {
return new TensorValues<>(this, true);
}
/**
* Returns a new group initialized to default values.
*/
@Override
public ParameterValueGroup createValue() {
return new TensorValues<>(this, false);
}
/**
* Returns a description of this parameter value group. Returns always {@code this}, since
* the description depends on {@code "num_row"} and {@code "num_col"} parameter values.
*/
@Override
public ParameterDescriptorGroup getDescriptor() {
return this;
}
/**
* Returns the parameters descriptors in this group. The amount of parameters depends
* on the value of {@code "num_row"} and {@code "num_col"} parameters.
*/
@Override
public List<GeneralParameterDescriptor> descriptors() {
return UnmodifiableArrayList.wrap(descriptors.getAllDescriptors(size()));
}
/**
* Returns the current tensor size for each dimensions.
*/
private int[] size() {
final int[] indices = new int[dimensions.length];
for (int i=0; i<indices.length; i++) {
indices[i] = dimensions[i].intValue();
}
return indices;
}
/**
* Returns the parameter descriptor in this group for the specified name.
*
* @param name the name of the parameter to search for.
* @return the parameter descriptor for the given name.
* @throws ParameterNotFoundException if there is no parameter for the given name.
*/
@Override
public GeneralParameterDescriptor descriptor(String name) throws ParameterNotFoundException {
name = CharSequences.trimWhitespaces(name);
ArgumentChecks.ensureNonEmpty("name", name);
return descriptors.descriptor(this, name, size());
}
/**
* Returns the parameter value in this group for the specified name.
*
* @param name the name of the parameter to search for.
* @return the parameter value for the given name.
* @throws ParameterNotFoundException if there is no parameter for the given name.
*/
@Override
public ParameterValue<?> parameter(String name) throws ParameterNotFoundException {
name = CharSequences.trimWhitespaces(name);
ArgumentChecks.ensureNonEmpty("name", name);
IllegalArgumentException cause = null;
int[] indices = null;
try {
indices = descriptors.nameToIndices(name);
} catch (IllegalArgumentException exception) {
cause = exception;
}
if (indices != null) {
final int[] actualSize = size();
if (TensorParameters.isInBounds(indices, actualSize)) {
return parameter(indices, actualSize);
}
}
/*
* The given name is not a matrix (or tensor) element name.
* Verify if the requested parameters is one of those that
* specify the matrix/tensor size ("num_row" or "num_col").
*/
final int rank = descriptors.rank();
for (int i=0; i<rank; i++) {
final ParameterDescriptor<Integer> param = descriptors.getDimensionDescriptor(i);
if (IdentifiedObjects.isHeuristicMatchForName(param, name)) {
return dimensions[i];
}
}
throw (ParameterNotFoundException) new ParameterNotFoundException(Resources.format(
Resources.Keys.ParameterNotFound_2, getName(), name), name).initCause(cause);
}
/**
* Returns the tensor element at the given indices.
*/
private ParameterValue<E> parameter(final int[] indices, final int[] actualSize) {
final int rank = dimensions.length;
if (indices.length != rank) {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.UnexpectedArrayLength_2, rank, indices.length));
}
/*
* At the end of the following loop, 'element' will be the TensorValues element
* and 'parent' will be the array which contain it at index indices[rank - 1].
*/
Object[] parent = null;
Object element = values;
for (int i=0; i<rank; i++) {
if (element == null) {
/*
* Creates new arrays only when first needed.
* For rank 2, creates ParameterValue[][];
* For rank 3, creates ParameterValue[][][];
* etc.
*/
final Class<?> componentType = Classes.changeArrayDimension(ParameterValue.class, rank - i - 1);
element = Array.newInstance(componentType, actualSize[i]);
if (parent != null) {
parent[indices[i-1]] = element;
} else {
values = (Object[]) element;
}
} else {
/*
* If we already have an array, makes sure that its length is sufficient. Note that the array
* could also be too long if the user reduced some tensor dimensions. We do not trim too long
* arrays in order to avoid inconsistent behavior if the user later brings back the sensor
* dimension to its old value. The inconsistent behavior would be to discard the references to
* existing values above 'actualSize[i]', because we would have some sequences of operations
* that discard them and some other sequences of operations that do not discard them.
* The easiest strategy is to never discard those extraneous references - may not be ideal,
* but at least it keep the behavior consistent for all sequences of operations.
*/
if (((Object[]) element).length <= indices[i]) {
element = Arrays.copyOf((Object[]) element, actualSize[i]);
parent[indices[i-1]] = element;
}
}
parent = (Object[]) element;
element = parent[indices[i]];
}
if (element == null) {
element = descriptors.getElementDescriptor(indices).createValue();
parent[indices[rank - 1]] = element;
}
return Parameters.cast((ParameterValue<?>) element, descriptors.getElementType());
}
/**
* Returns the parameter values in this group. The amount of parameters depends on the value of
* {@code "num_row"} and {@code "num_col"} parameters. The parameter array will contain only
* matrix elements which have been requested at least once by one of {@code parameter(…)} methods.
* Never requested elements are left to their default value and omitted from the returned array.
*/
@Override
public List<GeneralParameterValue> values() {
final List<GeneralParameterValue> addTo = new ArrayList<>();
for (final ParameterValue<Integer> dimension : dimensions) {
if (!isOmitted(dimension)) {
addTo.add(dimension);
}
}
addValues(values, size(), 0, addTo);
return Collections.unmodifiableList(addTo);
}
/**
* Implementation of {@link #values()} which adds parameter values to the given list.
* This method invokes itself recursively.
*/
private static void addValues(final Object[] values, final int[] actualSize, int j,
final List<GeneralParameterValue> addTo)
{
if (values != null) {
final int length = Math.min(values.length, actualSize[j]);
if (++j != actualSize.length) {
for (int i=0; i<length; i++) {
addValues((Object[]) values[i], actualSize, j, addTo);
}
} else {
for (int i=0; i<length; i++) {
final ParameterValue<?> parameter = (ParameterValue<?>) values[i];
if (parameter != null && !isOmitted(parameter)) {
addTo.add(parameter);
}
}
}
}
}
/**
* Returns {@code true} if the given parameter can be omitted. A parameter can be omitted
* if it is not mandatory and has a value equals to the default value.
*/
private static boolean isOmitted(final ParameterValue<?> parameter) {
final Object value = parameter.getValue();
if (value == null) { // Implies that the default value is also null.
return true;
}
final ParameterDescriptor<?> descriptor = parameter.getDescriptor();
return descriptor.getMinimumOccurs() == 0 && value.equals(descriptor.getDefaultValue());
}
/**
* Always throws an exception since this group does not contain subgroups.
*/
@Override
public List<ParameterValueGroup> groups(final String name) throws ParameterNotFoundException {
throw new ParameterNotFoundException(Resources.format(Resources.Keys.ParameterNotFound_2, getName(), name), name);
}
/**
* Always throws an exception since this group does not contain subgroups.
*/
@Override
public ParameterValueGroup addGroup(String name) throws ParameterNotFoundException, IllegalStateException {
throw new ParameterNotFoundException(Resources.format(Resources.Keys.ParameterNotFound_2, getName(), name), name);
}
/**
* Creates a matrix from this group of parameters.
* This operation is allowed only for tensors of {@linkplain TensorParameters#rank() rank} 2.
*
* @return a matrix created from this group of parameters.
*/
final Matrix toMatrix() {
final int numRow = dimensions[0].intValue();
final int numCol = dimensions[1].intValue();
final Matrix matrix = Matrices.createDiagonal(numRow, numCol);
if (values != null) {
for (int j=0; j<numRow; j++) {
final ParameterValue<?>[] row = (ParameterValue<?>[]) values[j];
if (row != null) {
for (int i=0; i<numCol; i++) {
final ParameterValue<?> element = row[i];
if (element != null) {
matrix.setElement(j, i, element.doubleValue());
}
}
}
}
}
return matrix;
}
/**
* Sets all parameter values to the element value in the specified matrix.
* After this method call, {@link #values} will returns only the elements
* different from the default value.
*
* @param matrix the matrix to copy in this group of parameters.
*/
final void setMatrix(final Matrix matrix) {
final int numRow = matrix.getNumRow();
final int numCol = matrix.getNumCol();
dimensions[0].setValue(numRow);
dimensions[1].setValue(numCol);
values = null;
final int[] indices = new int[2];
for (int j=0; j<numRow; j++) {
indices[0] = j;
ParameterValue<?>[] row = null;
for (int i=0; i<numCol; i++) {
indices[1] = i;
ParameterDescriptor<E> descriptor = descriptors.getElementDescriptor(indices);
final E def = descriptor.getDefaultValue();
final double element = matrix.getElement(j,i);
if (!(def instanceof Number) || !Numerics.equalsIgnoreZeroSign(element, ((Number) def).doubleValue())) {
final ParameterValue<?> value = descriptor.createValue();
value.setValue(element);
if (row == null) {
row = new ParameterValue<?>[numCol];
if (values == null) {
values = new ParameterValue<?>[numRow][];
}
values[j] = row;
}
row[i] = value;
}
}
}
}
/**
* Compares this object with the specified one for equality.
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true; // Slight optimization.
}
if (super.equals(object, mode)) {
final TensorValues<?> that = (TensorValues<?>) object;
return Utilities.deepEquals(descriptors, that.descriptors, mode) &&
Utilities.deepEquals(values(), that.values(), mode);
}
return false;
}
/**
* Invoked by {@link #hashCode()} for computing the hash code when first needed.
*
* @return {@inheritDoc}
*/
@Override
protected long computeHashCode() {
return super.computeHashCode() + descriptors.hashCode();
// Do not use any field other than descriptors, because they are not immutable.
}
/**
* Formats this group as a pseudo-<cite>Well Known Text</cite> element.
*
* @param formatter the formatter where to format the inner content of this WKT element.
* @return {@code "ParameterGroup"}.
*/
@Override
protected String formatTo(final Formatter formatter) {
WKTUtilities.appendParamMT(this, formatter);
return WKTKeywords.ParameterGroup;
}
}