This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new eebcfeeaab Refactor `org.apache.sis.filter.ArithmeticFunction` for
skipping the verifications of operand types when those types are known in
advance.
eebcfeeaab is described below
commit eebcfeeaabc3dcc41e9b6dc850d0317eac36b887
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jan 5 17:31:36 2026 +0100
Refactor `org.apache.sis.filter.ArithmeticFunction` for skipping the
verifications of operand types when those types are known in advance.
---
.../org/apache/sis/coverage/CoverageCombiner.java | 2 +-
.../org/apache/sis/filter/ComparisonFilter.java | 6 +-
.../apache/sis/filter/DefaultFilterFactory.java | 1 +
.../org/apache/sis/filter/IdentifierFilter.java | 2 +-
.../main/org/apache/sis/filter/PropertyValue.java | 10 +-
.../org/apache/sis/filter/base/BinaryFunction.java | 146 +------
.../sis/filter/base/BinaryFunctionWidening.java | 419 +++++++++++++++++++++
.../apache/sis/filter/base/ConvertFunction.java | 9 +-
.../main/org/apache/sis/filter/base/Node.java | 12 +
.../sis/filter/{ => math}/ArithmeticFunction.java | 221 +++++++----
.../org/apache/sis/filter/math/BinaryOperator.java | 10 +-
.../sis/filter/sqlmm/GeometryConstructor.java | 2 +-
.../apache/sis/filter/sqlmm/GeometryParser.java | 2 +-
.../org/apache/sis/filter/sqlmm/OneGeometry.java | 6 +-
.../main/org/apache/sis/filter/sqlmm/ST_Point.java | 2 +-
.../org/apache/sis/filter/sqlmm/ST_Transform.java | 4 +-
.../org/apache/sis/filter/sqlmm/TwoGeometries.java | 4 +-
.../org/apache/sis/filter/LogicalFilterTest.java | 12 +-
.../main/org/apache/sis/storage/gpx/Metadata.java | 2 +-
.../main/org/apache/sis/math/ArrayVector.java | 2 +-
.../main/org/apache/sis/math/Statistics.java | 2 +-
21 files changed, 644 insertions(+), 232 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
index 7e414f32cb..e9aa6ea06c 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
@@ -252,7 +252,7 @@ next: for (int j=0; j<sources.length; j++) {
final long[] max = new long[dimension];
for (int i=0; i<dimension; i++) {
/*
- * The conversion from `double` to `long` may loose precision.
The -1 (for making the maximum value
+ * The conversion from `double` to `long` may loss precision.
The -1 (for making the maximum value
* inclusive) is done on the floating point value instead of
the integer value in order to have the
* same rounding when the minimum and maximum values are close
to each other.
* The goal is to avoid spurious "disjoint extent" exceptions.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
index 40837252b4..1f07dff723 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
@@ -37,7 +37,7 @@ import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import org.apache.sis.math.Fraction;
import org.apache.sis.filter.base.Node;
-import org.apache.sis.filter.base.BinaryFunction;
+import org.apache.sis.filter.base.BinaryFunctionWidening;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Filter;
@@ -74,7 +74,7 @@ import org.opengis.filter.BetweenComparisonOperator;
*
* @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
*/
-abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
+abstract class ComparisonFilter<R> extends BinaryFunctionWidening<R, Object,
Object>
implements BinaryComparisonOperator<R>, Optimization.OnFilter<R>
{
/**
@@ -158,7 +158,7 @@ abstract class ComparisonFilter<R> extends
BinaryFunction<R,Object,Object>
@Override
public final boolean equals(final Object obj) {
if (super.equals(obj)) {
- final ComparisonFilter<?> other = (ComparisonFilter<?>) obj;
+ final var other = (ComparisonFilter<?>) obj;
return other.isMatchingCase == isMatchingCase &&
matchAction.equals(other.matchAction);
}
return false;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index efe4a01e24..7b173219e8 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -27,6 +27,7 @@ import org.apache.sis.geometry.WraparoundMethod;
import org.apache.sis.geometry.wrapper.Geometries;
import org.apache.sis.feature.internal.Resources;
import org.apache.sis.filter.base.UnaryFunction;
+import org.apache.sis.filter.math.ArithmeticFunction;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.iso.AbstractFactory;
import org.apache.sis.util.resources.Errors;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
index 72c7c1dc24..817d61d711 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
@@ -150,7 +150,7 @@ final class IdentifierFilter extends Node
Object id = object.getPropertyValue(property);
if (id != null) return identifier.equals(id.toString());
} catch (PropertyNotFoundException e) {
- warning(e, false);
+ warning(e);
}
return false;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index 8120298a2a..c6d049952e 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -191,7 +191,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
@SuppressWarnings("unchecked")
public final <N> PropertyValue<N> toValueType(final Class<N> target) {
// `getResultClass()` should never return null with our subtypes of
`PropertyValue`.
- if (target == getResultClass()) {
+ if (target.isAssignableFrom(getResultClass())) {
return (PropertyValue<N>) this;
} else if (target == Object.class) {
return (PropertyValue<N>) new AsObject(name, isVirtual);
@@ -249,7 +249,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
if (instance != null) try {
return instance.getPropertyValue(name);
} catch (PropertyNotFoundException e) {
- warning(e, false);
+ warning(e);
}
return null;
}
@@ -326,7 +326,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
if (instance != null) try {
return
ObjectConverters.convert(instance.getPropertyValue(name), type);
} catch (PropertyNotFoundException | UnconvertibleObjectException
e) {
- warning(e, false);
+ warning(e);
}
return null;
}
@@ -490,7 +490,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
if (instance != null) try {
return
converter.apply(source.cast(instance.getPropertyValue(name)));
} catch (PropertyNotFoundException | ClassCastException |
UnconvertibleObjectException e) {
- warning(e, false);
+ warning(e);
}
return null;
}
@@ -542,7 +542,7 @@ abstract class PropertyValue<V> extends
LeafExpression<Feature,V>
if (instance != null) try {
return (V) instance.getPropertyValue(name);
} catch (PropertyNotFoundException e) {
- warning(e, false);
+ warning(e);
}
return null;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
index 2c14cb2542..0501c7c14b 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
@@ -19,11 +19,6 @@ package org.apache.sis.filter.base;
import java.util.List;
import java.util.Collection;
import java.util.Objects;
-import java.math.BigInteger;
-import java.math.BigDecimal;
-import org.apache.sis.util.Numbers;
-import org.apache.sis.math.Fraction;
-import org.apache.sis.math.DecimalFunctions;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.filter.Filter;
@@ -34,16 +29,20 @@ import org.opengis.filter.Expression;
* Base class for expressions, comparators or filters performing operations on
two expressions.
* The nature of the operation depends on the subclass. If operands are
numerical values, they
* may be converted to a common type before the operation is performed. That
operation is not
- * necessarily an arithmetic operation; it may be a comparison for example.
+ * necessarily an arithmetic operation, it may also be a comparison for
example.
+ *
+ * <h2>Terminology</h2>
+ * "Binary function" takes two inputs from possibly different sets. This is
more general than
+ * "binary operator", which takes inputs and produce an output of the same set.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*
* @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
- * @param <V1> the type of value computed by the first expression.
- * @param <V2> the type of value computed by the second expression.
+ * @param <A1> the type of value computed by the first expression (left
operand).
+ * @param <A2> the type of value computed by the second expression (right
operand).
*/
-public abstract class BinaryFunction<R,V1,V2> extends Node {
+public abstract class BinaryFunction<R, A1, A2> extends Node {
/**
* For cross-version compatibility.
*/
@@ -55,7 +54,7 @@ public abstract class BinaryFunction<R,V1,V2> extends Node {
* @see #getExpression1()
*/
@SuppressWarnings("serial") // Most SIS implementations are
serializable.
- protected final Expression<R, ? extends V1> expression1;
+ protected final Expression<R, ? extends A1> expression1;
/**
* The second of the two expressions to be used by this function.
@@ -63,7 +62,7 @@ public abstract class BinaryFunction<R,V1,V2> extends Node {
* @see #getExpression2()
*/
@SuppressWarnings("serial") // Most SIS implementations are
serializable.
- protected final Expression<R, ? extends V2> expression2;
+ protected final Expression<R, ? extends A2> expression2;
/**
* Creates a new binary function.
@@ -71,8 +70,8 @@ public abstract class BinaryFunction<R,V1,V2> extends Node {
* @param expression1 the first of the two expressions to be used by
this function.
* @param expression2 the second of the two expressions to be used by
this function.
*/
- protected BinaryFunction(final Expression<R, ? extends V1> expression1,
- final Expression<R, ? extends V2> expression2)
+ protected BinaryFunction(final Expression<R, ? extends A1> expression1,
+ final Expression<R, ? extends A2> expression2)
{
this.expression1 = Objects.requireNonNull(expression1);
this.expression2 = Objects.requireNonNull(expression2);
@@ -117,125 +116,4 @@ public abstract class BinaryFunction<R,V1,V2> extends
Node {
protected final Collection<?> getChildren() {
return getExpressions();
}
-
- /**
- * Evaluates the expression for producing a result of numeric type.
- * This method delegates to one of the {@code applyAs(…)} methods.
- * If no {@code applyAs(…)} implementations can return null values,
- * this this method never return {@code null}.
- *
- * @param left the left operand. Cannot be null.
- * @param right the right operand. Cannot be null.
- * @return result of this function applied on the two given operands.
- * May be {@code null} only if an {@code applyAs(…)}
implementation returned a null value.
- */
- protected final Number apply(final Number left, final Number right) {
- final int type = Math.max(Numbers.getEnumConstant(left.getClass()),
- Numbers.getEnumConstant(right.getClass()));
- try {
- switch (type) {
- case Numbers.BIG_DECIMAL: {
- return applyAsDecimal(Numbers.cast(left,
BigDecimal.class),
- Numbers.cast(right,
BigDecimal.class));
- }
- case Numbers.BIG_INTEGER: {
- return applyAsInteger(Numbers.cast(left,
BigInteger.class),
- Numbers.cast(right,
BigInteger.class));
- }
- case Numbers.FRACTION: {
- return applyAsFraction(Numbers.cast(left, Fraction.class),
- Numbers.cast(right,
Fraction.class));
- }
- case Numbers.LONG:
- case Numbers.INTEGER:
- case Numbers.SHORT:
- case Numbers.BYTE: {
- return applyAsLong(left.longValue(), right.longValue());
- }
- }
- } catch (IllegalArgumentException | ArithmeticException e) {
- /*
- * Integer overflow, or division by zero, or attempt to convert
NaN or infinity
- * to `BigDecimal`, or division does not have a terminating
decimal expansion.
- * This is a recoverable because we can fallback on floating point
arithmetic.
- */
- warning(e, true);
- }
- return applyAsDouble((left instanceof Float) ?
DecimalFunctions.floatToDouble((Float) left) : left.doubleValue(),
- (right instanceof Float) ?
DecimalFunctions.floatToDouble((Float) right) : right.doubleValue());
- }
-
- /**
- * Calculates this function using given operands of {@code long} primitive
type. If this function is a filter,
- * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
- * Otherwise the result is usually a {@link Long}, except for division
which may produce other types.
- * This method may return {@code null} if the operation cannot apply on
numbers.
- *
- * @param left the first operand.
- * @param right the second operand.
- * @return the result of applying the function on the given operands.
- * @throws ArithmeticException if the operation overflows or if there is a
division by zero.
- */
- protected Number applyAsLong(long left, long right) {
- return null;
- }
-
- /**
- * Calculates this function using given operands of {@code double}
primitive type. If this function is a filter,
- * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
- * Otherwise the result is usually a {@link Double}.
- * This method may return {@code null} if the operation cannot apply on
numbers.
- *
- * @param left the first operand.
- * @param right the second operand.
- * @return the result of applying the function on the given operands.
- */
- protected Number applyAsDouble(double left, double right) {
- return null;
- }
-
- /**
- * Calculates this function using given operands of {@code Fraction} type.
If this function is a filter,
- * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
- * Otherwise the result is usually a {@link Fraction}.
- * This method may return {@code null} if the operation cannot apply on
numbers.
- *
- * @param left the first operand.
- * @param right the second operand.
- * @return the result of applying the function on the given operands.
- * @throws ArithmeticException if the operation overflows or if there is a
division by zero.
- */
- protected Number applyAsFraction(Fraction left, Fraction right) {
- return null;
- }
-
- /**
- * Calculates this function using given operands of {@code BigInteger}
type. If this function is a filter,
- * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
- * Otherwise the result is usually a {@link BigInteger}, except for
division which may produce other types.
- * This method may return {@code null} if the operation cannot apply on
numbers.
- *
- * @param left the first operand.
- * @param right the second operand.
- * @return the result of applying the function on the given operands.
- * @throws ArithmeticException if there is a division by zero.
- */
- protected Number applyAsInteger(BigInteger left, BigInteger right) {
- return null;
- }
-
- /**
- * Calculates this function using given operands of {@code BigDecimal}
type. If this function is a filter,
- * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
- * Otherwise the result is usually a {@link BigDecimal}.
- * This method may return {@code null} if the operation cannot apply on
numbers.
- *
- * @param left the first operand.
- * @param right the second operand.
- * @return the result of applying the function on the given operands.
- * @throws ArithmeticException if a division does not have a terminating
decimal expansion.
- */
- protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {
- return null;
- }
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java
new file mode 100644
index 0000000000..e3e69504f3
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java
@@ -0,0 +1,419 @@
+/*
+ * 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.filter.base;
+
+import java.util.List;
+import java.util.Collection;
+import java.math.BigInteger;
+import java.math.BigDecimal;
+import org.apache.sis.util.ConditionallySafe;
+import org.apache.sis.math.Fraction;
+import org.apache.sis.math.NumberType;
+import org.apache.sis.math.DecimalFunctions;
+import org.apache.sis.feature.internal.shared.FeatureExpression;
+import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
+import org.apache.sis.filter.Optimization;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Expression;
+import org.opengis.util.ScopedName;
+
+
+/**
+ * Expression performing an operation on two expressions with values
convertible to {@code Number}.
+ * Each {@link Number} instance will be converted to {@code long}, {@code
double}, {@link Fraction},
+ * {@link BigInteger} or {@link BigDecimal} before the operation is applied.
This class is used for
+ * operations where specialized methods exist for each of above-cited types.
+ *
+ * <p>The inputs are not necessarily of the same class, but typically need to
be promoted to the widest type
+ * before the operation is executed. The result may be of a type different to
all input types. For example,
+ * a division of two {@link Integer} values may produce a {@link Fraction},
and a multiplication of the same
+ * {@link Integer} values may produce a {@link Long}.</p>
+ *
+ * <p>The current version does not provide optimization for every cases. It is
not clear that it is worth
+ * to optimize the {@link Fraction}, {@link BigInteger} and {@link BigDecimal}
cases.</p>
+ *
+ * <h2>Requirement</h2>
+ * If a subclass implements {@link Expression}, then it shall also implement
{@link FeatureExpression}
+ * and the type parameters <strong>must</strong> be {@code Expression<R,
Number>}. That subclass shall
+ * also implement {@link Optimization.OnExpression}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <R> the type of resources (e.g. {@code Feature}) used as inputs.
+ * @param <A1> the type of value computed by the first expression (left
operand).
+ * @param <A2> the type of value computed by the second expression (right
operand).
+ */
+public abstract class BinaryFunctionWidening<R, A1, A2> extends
BinaryFunction<R, A1, A2> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -2515131813531876123L;
+
+ /**
+ * Creates a new binary function.
+ *
+ * @param expression1 the first of the two expressions to be used by
this function.
+ * @param expression2 the second of the two expressions to be used by
this function.
+ */
+ protected BinaryFunctionWidening(final Expression<R, ? extends A1>
expression1,
+ final Expression<R, ? extends A2>
expression2)
+ {
+ super(expression1, expression2);
+ }
+
+ /**
+ * Tries to return an expression which will invoke more directly an {@code
applyAsXXX(…)} method,
+ * without the need to inspect the argument type. The returned expression,
if non-null, should be
+ * more efficient than {@link #apply(Number, Number)}.
+ *
+ * @return the simplified or optimized function, or {@code null} if no
optimization has been applied.
+ */
+ @SuppressWarnings("unchecked")
+ protected final Expression<R, ? extends Number> specialize() {
+ switch (effective(widestOperandType())) {
+ case LONG: return new Longs<> ((BinaryFunctionWidening<R, ?
extends Number, ? extends Number>) this);
+ case DOUBLE: return new Doubles<>((BinaryFunctionWidening<R, ?
extends Number, ? extends Number>) this);
+ default: return null;
+ }
+ }
+
+ /**
+ * Returns the type of values computed by this expression.
+ * In case of doubt, this method returns the {@link Number} base class.
+ *
+ * @return the type of values computed by this expression.
+ *
+ * @see #widestOperandType()
+ * @see #effective(NumberType)
+ */
+ protected Class<? extends Number> getResultClass() {
+ return Number.class;
+ }
+
+ /**
+ * Returns an enumeration value identifying the type of return value of
the given expression.
+ * If the expression result cannot be mapped to a number type, returns
{@link NumberType#NULL}.
+ *
+ * @param expression the expression for which to identifying the type of
return value.
+ * @return type of numbers computed by the given expression.
+ *
+ * @see FeatureExpression#getResultClass()
+ */
+ private static NumberType getNumberType(final Expression<?,?> expression) {
+ return (expression instanceof FeatureExpression<?,?>)
+ ? NumberType.forClass(((FeatureExpression<?,?>)
expression).getResultClass()).orElse(NumberType.NULL) : NumberType.NULL;
+ }
+
+ /**
+ * Returns the widest type of the given arguments, or {@link
NumberType#NULL} if none.
+ * Note that conversions to the returned type are not guaranteed to be
lossless.
+ * For example, conversions from {@code long} to {@code double} may loss
accuracy.
+ *
+ * <p>Conversion from {@code float} to {@code double} is disallowed
because the
+ * {@link #apply(Number, Number)} method handles the decimal
representation.</p>
+ */
+ private static NumberType widest(final NumberType t1, final NumberType t2)
{
+ if (t1 == t2) return t1;
+ if (t1.isWiderThan(t2)) {
+ if (t2 != NumberType.FLOAT || t1 == NumberType.BIG_DECIMAL) return
t1;
+ } else if (t2.isWiderThan(t1)) {
+ if (t1 != NumberType.FLOAT || t2 == NumberType.BIG_DECIMAL) return
t2;
+ }
+ return NumberType.NULL;
+ }
+
+ /**
+ * Returns the widest operand type, or {@link NumberType#NULL} if it
cannot be determined.
+ * Note that conversions to the returned type are not guaranteed to be
lossless.
+ * For example, conversions from {@code long} to {@code double} may loss
accuracy.
+ *
+ * @return the widest operand type, or {@link NumberType#NULL}.
+ *
+ * @see FeatureExpression#getResultClass()
+ */
+ protected final NumberType widestOperandType() {
+ return widest(getNumberType(expression1), getNumberType(expression2));
+ }
+
+ /**
+ * Simplifies the given type to one of the types handled as a special case
in this class.
+ * The {@code switch} statement in this method's body shall be consistent
with the switch
+ * statement in {@link #apply(Number, Number)}, with the addition of the
{@code NULL} and
+ * {@code NUMBER} cases.
+ *
+ * @param type a number type.
+ * @return one of {@code LONG}, {@code DOUBLE}, {@code FRACTION}, {@code
BIG_INTEGER},
+ * {@code BIG_DECIMAL}, {@code NUMBER} or {@code NULL}.
+ */
+ protected static NumberType effective(final NumberType type) {
+ switch (type) {
+ case NULL: // Case of expressions without
`FeatureExpression.getResultType()`.
+ case NUMBER: // Case of expressions that declare only the
generic `Number` class.
+ case FRACTION:
+ case BIG_INTEGER:
+ case BIG_DECIMAL: return type;
+ case BYTE:
+ case SHORT:
+ case INTEGER:
+ case LONG: return NumberType.LONG;
+ default: return NumberType.DOUBLE; // The fallback used for
unrecognized types.
+ }
+ }
+
+ /**
+ * Evaluates the expression for producing a result of numeric type.
+ * This method delegates to one of the {@code applyAs(…)} methods.
+ * If no {@code applyAs(…)} implementations can return null values,
+ * this this method never return {@code null}.
+ *
+ * @param left the left operand. Cannot be null.
+ * @param right the right operand. Cannot be null.
+ * @return result of this function applied on the two given operands.
+ * May be {@code null} only if an {@code applyAs(…)}
implementation returned a null value.
+ */
+ protected final Number apply(final Number left, final Number right) {
+ final NumberType type = widest(
+ NumberType.forNumberClass(left.getClass()),
+ NumberType.forNumberClass(right.getClass()));
+ try {
+ switch (type) {
+ case FRACTION: {
+ return applyAsFraction((Fraction) type.cast(left),
+ (Fraction) type.cast(right));
+ }
+ case BIG_INTEGER: {
+ return applyAsInteger((BigInteger) type.cast(left),
+ (BigInteger) type.cast(right));
+ }
+ case BIG_DECIMAL: {
+ return applyAsDecimal((BigDecimal) type.cast(left),
+ (BigDecimal) type.cast(right));
+ }
+ case BYTE:
+ case SHORT:
+ case INTEGER:
+ case LONG: {
+ return applyAsLong(left.longValue(), right.longValue());
+ }
+ }
+ } catch (IllegalArgumentException | ArithmeticException e) {
+ /*
+ * Integer overflow, or division by zero, or attempt to convert
NaN or infinity
+ * to `BigDecimal`, or division does not have a terminating
decimal expansion.
+ * This is recoverable because we can fallback on floating point
arithmetic.
+ */
+ warning(e, true);
+ }
+ return applyAsDouble((left instanceof Float) ?
DecimalFunctions.floatToDouble((Float) left) : left.doubleValue(),
+ (right instanceof Float) ?
DecimalFunctions.floatToDouble((Float) right) : right.doubleValue());
+ }
+
+ /**
+ * Calculates this function using given operands of {@code long} primitive
type. If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
+ * Otherwise the result is usually a {@link Long}, except for division
which may produce other types.
+ * This method may return {@code null} if the operation cannot apply on
numbers.
+ *
+ * @param left the first operand.
+ * @param right the second operand.
+ * @return the result of applying the function on the given operands.
+ * @throws ArithmeticException if the operation overflows or if there is a
division by zero.
+ */
+ protected abstract Number applyAsLong(long left, long right);
+
+ /**
+ * Calculates this function using given operands of {@code double}
primitive type. If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
+ * Otherwise the result is usually a {@link Double}.
+ * This method may return {@code null} if the operation cannot apply on
numbers.
+ *
+ * @param left the first operand.
+ * @param right the second operand.
+ * @return the result of applying the function on the given operands.
+ */
+ protected abstract Number applyAsDouble(double left, double right);
+
+ /**
+ * Calculates this function using given operands of {@code Fraction} type.
If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
+ * Otherwise the result is usually a {@link Fraction}.
+ * This method may return {@code null} if the operation cannot apply on
numbers.
+ *
+ * @param left the first operand.
+ * @param right the second operand.
+ * @return the result of applying the function on the given operands.
+ * @throws ArithmeticException if the operation overflows or if there is a
division by zero.
+ */
+ protected abstract Number applyAsFraction(Fraction left, Fraction right);
+
+ /**
+ * Calculates this function using given operands of {@code BigInteger}
type. If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
+ * Otherwise the result is usually a {@link BigInteger}, except for
division which may produce other types.
+ * This method may return {@code null} if the operation cannot apply on
numbers.
+ *
+ * @param left the first operand.
+ * @param right the second operand.
+ * @return the result of applying the function on the given operands.
+ * @throws ArithmeticException if there is a division by zero.
+ */
+ protected abstract Number applyAsInteger(BigInteger left, BigInteger
right);
+
+ /**
+ * Calculates this function using given operands of {@code BigDecimal}
type. If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for
false or true respectively.
+ * Otherwise the result is usually a {@link BigDecimal}.
+ * This method may return {@code null} if the operation cannot apply on
numbers.
+ *
+ * @param left the first operand.
+ * @param right the second operand.
+ * @return the result of applying the function on the given operands.
+ * @throws ArithmeticException if a division does not have a terminating
decimal expansion.
+ */
+ protected abstract Number applyAsDecimal(BigDecimal left, BigDecimal
right);
+
+
+
+
+ /**
+ * An expression which will invoke more directly an {@code applyAsXXX(…)}
method,
+ * without the need to inspect the argument type.
+ *
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ * @param <A> the type of value computed by the two expressions used as
inputs.
+ */
+ private static abstract class Specialization<R, A extends Number> extends
Node
+ implements FeatureExpression<R, Number>,
Optimization.OnExpression<R, Number>
+ {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -6902891170861955149L;
+
+ /** The implementation of the function. */
+ protected final BinaryFunctionWidening<R, ? extends A, ? extends A>
delegate;
+
+ /** Creates a new specialization which will delegate the work to the
given implementation. */
+ protected Specialization(final BinaryFunctionWidening<R, ? extends A,
? extends A> delegate) {
+ this.delegate = delegate;
+ }
+
+ /** Delegates to the function. */
+ @Override public final ScopedName getFunctionName()
{return ((Expression<?,?>) delegate).getFunctionName();}
+ @Override public final Class<? super R> getResourceClass()
{return delegate.getResourceClass();}
+ @Override public final List<Expression<R,?>> getParameters()
{return delegate.getParameters();}
+ @Override protected final Collection<?> getChildren()
{return delegate.getChildren();}
+
+ /** Returns the type of values computed by this expression. */
+ @Override public final Class<? extends Number> getResultClass() {
+ return delegate.getResultClass();
+ }
+
+ /**
+ * Provides the type of results computed by the implementation of the
function.
+ * The value type is declared as the generic {@link Number} type
rather than {@code <V>},
+ * but this is desired as the result of division is not always of type
{@code <V>}.
+ */
+ @Override public final FeatureProjectionBuilder.Item
expectedType(FeatureProjectionBuilder addTo) {
+ return ((FeatureExpression<?,?>) delegate).expectedType(addTo);
+ }
+
+ /**
+ * Delegates the optimization to the implementation and checks if the
result is the same.
+ * This method performs a cast which is safe only if the requirement
documented in the
+ * {@link BinaryFunctionWidening} javadoc is true.
+ */
+ @Override public final Expression<R, ? extends Number> optimize(final
Optimization optimization) {
+ @ConditionallySafe
+ @SuppressWarnings("unchecked") // See Javadoc
+ final Expression<R, ? extends Number> result =
((Optimization.OnExpression<R, Number>) delegate).optimize(optimization);
+ if (result.getClass() == getClass() && ((Specialization<?,?>)
result).delegate == delegate) {
+ return this;
+ }
+ return result;
+ }
+ }
+
+
+
+
+ /**
+ * An expression which will invoke more directly the {@code
applyAsLong(…)} method.
+ * This implementation can be used with operands of type {@link Byte},
{@link Short},
+ * {@link Integer} and {@link Long}.
+ *
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ */
+ private static final class Longs<R> extends Specialization<R, Number> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 8799719407972742175L;
+
+ /** Creates a new specialization for integers. */
+ Longs(BinaryFunctionWidening<R, ? extends Number, ? extends Number>
delegate) {
+ super(delegate);
+ }
+
+ /** Executes the operation with the assumption that values are
convertible to {@code long}. */
+ @Override public Number apply(final R feature) {
+ final Number left = delegate.expression1.apply(feature);
+ if (left != null) {
+ final Number right = delegate.expression2.apply(feature);
+ if (right != null) try {
+ return delegate.applyAsLong(left.longValue(),
right.longValue());
+ } catch (IllegalArgumentException | ArithmeticException e) {
+ warning(e, true);
+ return delegate.applyAsDouble(left.doubleValue(),
right.doubleValue());
+ }
+ }
+ return null;
+ }
+ }
+
+
+
+
+ /**
+ * An expression which will invoke more directly the {@code
applyAsDouble(…)} method.
+ * This implementation can be used with operands of type {@link Byte},
{@link Short},
+ * {@link Integer}, {@link Long}, {@link Fraction} and {@link Double}.
Note that the
+ * {@link Float} type is excluded because we use a conversion that tries
to preserve
+ * the decimal representation.
+ *
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ */
+ private static final class Doubles<R> extends Specialization<R, Number> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -1962350161229383018L;
+
+ /** Creates a new specialization for integers. */
+ Doubles(BinaryFunctionWidening<R, ? extends Number, ? extends Number>
delegate) {
+ super(delegate);
+ }
+
+ /** Executes the operation with the assumption that values are
convertible to {@code long}. */
+ @Override public Number apply(final R feature) {
+ final Number left = delegate.expression1.apply(feature);
+ if (left != null) {
+ final Number right = delegate.expression2.apply(feature);
+ if (right != null) {
+ return delegate.applyAsDouble(left.doubleValue(),
right.doubleValue());
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
index 802f293207..10ef96a8e9 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
@@ -113,6 +113,8 @@ public final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
/**
* Returns an identification of this operation.
+ *
+ * @return "Convert".
*/
@Override
public ScopedName getFunctionName() {
@@ -130,8 +132,9 @@ public final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
}
/**
- * Returns the singleton expression tested by this operator
- * together with the source and target classes.
+ * Returns the singleton expression tested by this operator together with
the source and target classes.
+ *
+ * @return expression, source, target.
*/
@Override
protected Collection<?> getChildren() {
@@ -153,7 +156,7 @@ public final class ConvertFunction<R,S,V> extends
UnaryFunction<R,S>
try {
return converter.apply(value);
} catch (UnconvertibleObjectException e) {
- warning(e, false);
+ warning(e);
return null;
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
index 527aa60048..660e8988e8 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
@@ -358,6 +358,8 @@ public abstract class Node implements Serializable {
/**
* Returns a hash code value computed from the class and the children.
+ *
+ * @return a hash code value.
*/
@Override
public int hashCode() {
@@ -378,6 +380,16 @@ public abstract class Node implements Serializable {
return false;
}
+ /**
+ * Reports that an operation failed because of the given exception,
resulting in a null value.
+ * This method assumes that the warning occurred in a {@code test(…)} or
{@code apply(…)} method.
+ *
+ * @param exception the exception that occurred.
+ */
+ protected final void warning(final Exception exception) {
+ warning(exception, false);
+ }
+
/**
* Reports that an operation failed because of the given exception.
* This method assumes that the warning occurred in a {@code test(…)} or
{@code apply(…)} method.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java
similarity index 56%
rename from
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
rename to
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java
index 00491c90ac..a0e6f9c8eb 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java
@@ -14,16 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.filter;
+package org.apache.sis.filter.math;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.opengis.util.ScopedName;
import org.apache.sis.feature.internal.shared.FeatureExpression;
import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
-import org.apache.sis.filter.base.BinaryFunction;
+import org.apache.sis.filter.Optimization;
import org.apache.sis.filter.visitor.FunctionNames;
+import org.apache.sis.filter.base.BinaryFunctionWidening;
import org.apache.sis.math.Fraction;
+import org.apache.sis.math.NumberType;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.feature.AttributeType;
@@ -37,9 +39,10 @@ import org.opengis.filter.Expression;
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*
- * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ * @param <A> the type of value computed by the two expressions used as
inputs.
*/
-abstract class ArithmeticFunction<R> extends BinaryFunction<R, Number, Number>
+public abstract class ArithmeticFunction<R, A extends Number> extends
BinaryFunctionWidening<R, A, A>
implements FeatureExpression<R, Number>, Optimization.OnExpression<R,
Number>
{
/**
@@ -49,24 +52,42 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
/**
* Creates a new arithmetic function.
+ *
+ * @param expression1 the first of the two expressions to be used by
this function.
+ * @param expression2 the second of the two expressions to be used by
this function.
*/
- ArithmeticFunction(final Expression<R, ? extends Number> expression1,
- final Expression<R, ? extends Number> expression2)
+ protected ArithmeticFunction(final Expression<R, ? extends A> expression1,
+ final Expression<R, ? extends A> expression2)
{
super(expression1, expression2);
}
/**
* Returns the type of values computed by this expression.
+ * It should be {@code Long} if we are certain that all results will be of
that type.
+ * The default implementation is suitable to addition, subtraction and
multiplication.
+ * Other operations should override this method.
*/
@Override
- public final Class<Number> getResultClass() {
+ public Class<? extends Number> getResultClass() {
+ NumberType type = widestOperandType();
+ switch (type) {
+ case BYTE: return Short.class;
+ case SHORT: return Integer.class;
+ case INTEGER: return Long.class;
+ case LONG: return Number.class; // Because of the possibility
of overflow.
+ }
+ type = effective(type);
+ if (type.isReal()) {
+ return type.classOfValues(false).asSubclass(Number.class);
+ }
return Number.class;
}
/**
* Creates an attribute type for numeric values of the given name.
* The attribute is mandatory, unbounded and has no default value.
+ * This is used for implementations of {@link #expectedType()}.
*
* @param name name of the attribute to create.
* @return an attribute of the given name for numbers.
@@ -77,6 +98,18 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
/**
* Returns the type of results computed by this arithmetic function.
+ * It should be a constant of the following form:
+ *
+ * {@snippet lang="java" :
+ * private static final AttributeType<Number> TYPE =
createNumericType("Add");
+ *
+ * @Override
+ * protected AttributeType<Number> expectedType() {
+ * return TYPE;
+ * }
+ * }
+ *
+ * @return the type of result computed by this arithmetic function.
*/
protected abstract AttributeType<Number> expectedType();
@@ -89,11 +122,40 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
return addTo.addSourceProperty(expectedType(), false);
}
+ /**
+ * Tries to optimize this function. Fist, this method applies the
optimization documented
+ * in the {@linkplain Optimization.OnExpression#optimize default method
impmementation}.
+ * Then, if it is possible to avoid to inspect the number types every time
that the function
+ * is evaluated, this method returns a more direct implementation.
+ *
+ * @param optimization the simplifications or optimizations to apply on
this arithmetic function.
+ * @return the simplified or optimized function, or {@code this} if no
optimization has been applied.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public final Expression<R, ? extends Number> optimize(final Optimization
optimization) {
+ final Expression<R, ? extends Number> result =
Optimization.OnExpression.super.optimize(optimization);
+ if (result instanceof ArithmeticFunction<?,?>) {
+ final var optimized = ((ArithmeticFunction<R,?>)
result).specialize();
+ if (optimized != null) {
+ return optimized;
+ }
+ }
+ return result;
+ }
+
/**
* Evaluates the expression for producing a result of numeric type.
* This method delegates to one of the {@code applyAs(…)} methods.
- * If no {@code applyAs(…)} implementations can return null values,
- * this this method never return {@code null}.
+ * Thus is the default implementation used when no specialization
+ * was found by the {@link #optimize(Optimization)} method.
+ *
+ * <h4>Null values</h4>
+ * This method returns {@code null} if at least one operand evaluated to
{@code null}.
+ * Otherwise, this method never return {@code null} if no {@code
applyAs(…)} implementation can return null.
+ *
+ * @param feature the resource (usually a feature instance) from which
to take the inputs.
+ * @return result of the arithmetic operation, or {@code null} if at least
one operand is null.
*/
@Override
public final Number apply(final R feature) {
@@ -110,33 +172,39 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
/**
* The "Add" (+) expression.
*
- * @param <R> the type of resources used as inputs.
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ * @param <V> the type of value computed by the two expressions used as
inputs.
*/
- static final class Add<R> extends ArithmeticFunction<R> {
+ public static final class Add<R, V extends Number> extends
ArithmeticFunction<R,V> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 5445433312445869201L;
- /** Description of results of the {@code "Add"} expression. */
- private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Add);
- @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
- /** Creates a new expression for the {@code "Add"} operation. */
- Add(final Expression<R, ? extends Number> expression1,
- final Expression<R, ? extends Number> expression2)
+ /**
+ * Creates a new expression for the {@code "Add"} operation.
+ *
+ * @param expression1 the first of the two expressions to be used by
this operation.
+ * @param expression2 the second of the two expressions to be used
by this operation.
+ */
+ public Add(final Expression<R, ? extends V> expression1,
+ final Expression<R, ? extends V> expression2)
{
super(expression1, expression2);
}
- /** Creates a new expression of the same type but different
parameters. */
- @Override public Expression<R,Number> recreate(final Expression<R,?>[]
effective) {
+ /** Creates a new arithmetic function for the same operation but using
different parameters. */
+ @Override public Expression<R, Number> recreate(final
Expression<R,?>[] effective) {
return new Add<>(effective[0].toValueType(Number.class),
effective[1].toValueType(Number.class));
}
- /** Identification of the {@code "Add"} operation. */
- private static final ScopedName NAME = createName(FunctionNames.Add);
- @Override public ScopedName getFunctionName() {return NAME;}
+ /** Description of results of the {@code "Add"} expression. */
+ @Override protected AttributeType<Number> expectedType() {return TYPE;}
+ private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Add);
+
+ /** Representation of the {@code "Add"} operation. */
@Override protected char symbol() {return '+';}
+ @Override public ScopedName getFunctionName() {return NAME;}
+ private static final ScopedName NAME = createName(FunctionNames.Add);
/** Applies this expression to the given operands. */
@Override protected Number applyAsDouble (double left, double
right) {return left + right;}
@@ -150,33 +218,39 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
/**
* The "Subtract" (−) expression.
*
- * @param <R> the type of resources used as inputs.
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ * @param <V> the type of value computed by the two expressions used as
inputs.
*/
- static final class Subtract<R> extends ArithmeticFunction<R> {
+ public static final class Subtract<R, V extends Number> extends
ArithmeticFunction<R,V> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 3048878022726271508L;
- /** Description of results of the {@code "Subtract"} expression. */
- private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Subtract);
- @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
- /** Creates a new expression for the {@code "Subtract"} operation. */
- Subtract(final Expression<R, ? extends Number> expression1,
- final Expression<R, ? extends Number> expression2)
+ /**
+ * Creates a new expression for the {@code "Subtract"} operation.
+ *
+ * @param expression1 the first of the two expressions to be used by
this operation.
+ * @param expression2 the second of the two expressions to be used
by this operation.
+ */
+ public Subtract(final Expression<R, ? extends V> expression1,
+ final Expression<R, ? extends V> expression2)
{
super(expression1, expression2);
}
- /** Creates a new expression of the same type but different
parameters. */
- @Override public Expression<R,Number> recreate(final Expression<R,?>[]
effective) {
+ /** Creates a new arithmetic function for the same operation but using
different parameters. */
+ @Override public Expression<R, Number> recreate(final
Expression<R,?>[] effective) {
return new Subtract<>(effective[0].toValueType(Number.class),
effective[1].toValueType(Number.class));
}
- /** Identification of the {@code "Subtract"} operation. */
- private static final ScopedName NAME =
createName(FunctionNames.Subtract);
- @Override public ScopedName getFunctionName() {return NAME;}
+ /** Description of results of the {@code "Subtract"} expression. */
+ @Override protected AttributeType<Number> expectedType() {return TYPE;}
+ private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Subtract);
+
+ /** Representation of the {@code "Subtract"} operation. */
@Override protected char symbol() {return '−';}
+ @Override public ScopedName getFunctionName() {return NAME;}
+ private static final ScopedName NAME =
createName(FunctionNames.Subtract);
/** Applies this expression to the given operands. */
@Override protected Number applyAsDouble (double left, double
right) {return left - right;}
@@ -190,33 +264,39 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
/**
* The "Multiply" (×) expression.
*
- * @param <R> the type of resources used as inputs.
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ * @param <V> the type of value computed by the two expressions used as
inputs.
*/
- static final class Multiply<R> extends ArithmeticFunction<R> {
+ public static final class Multiply<R, V extends Number> extends
ArithmeticFunction<R,V> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = -1300022614832645625L;
- /** Description of results of the {@code "Multiply"} expression. */
- private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Multiply);
- @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
- /** Creates a new expression for the {@code "Multiply"} operation. */
- Multiply(final Expression<R, ? extends Number> expression1,
- final Expression<R, ? extends Number> expression2)
+ /**
+ * Creates a new expression for the {@code "Multiply"} operation.
+ *
+ * @param expression1 the first of the two expressions to be used by
this operation.
+ * @param expression2 the second of the two expressions to be used
by this operation.
+ */
+ public Multiply(final Expression<R, ? extends V> expression1,
+ final Expression<R, ? extends V> expression2)
{
super(expression1, expression2);
}
- /** Creates a new expression of the same type but different
parameters. */
- @Override public Expression<R,Number> recreate(final Expression<R,?>[]
effective) {
+ /** Creates a new arithmetic function for the same operation but using
different parameters. */
+ @Override public Expression<R, Number> recreate(final
Expression<R,?>[] effective) {
return new Multiply<>(effective[0].toValueType(Number.class),
effective[1].toValueType(Number.class));
}
- /** Identification of the {@code "Multiply"} operation. */
- private static final ScopedName NAME =
createName(FunctionNames.Multiply);
- @Override public ScopedName getFunctionName() {return NAME;}
+ /** Description of results of the {@code "Multiply"} expression. */
+ @Override protected AttributeType<Number> expectedType() {return TYPE;}
+ private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Multiply);
+
+ /** Representation of the {@code "Multiply"} operation. */
@Override protected char symbol() {return '×';}
+ @Override public ScopedName getFunctionName() {return NAME;}
+ private static final ScopedName NAME =
createName(FunctionNames.Multiply);
/** Applies this expression to the given operands. */
@Override protected Number applyAsDouble (double left, double
right) {return left * right;}
@@ -230,33 +310,39 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
/**
* The "Divide" (÷) expression.
*
- * @param <R> the type of resources used as inputs.
+ * @param <R> the type of resources (typically {@code Feature}) used as
inputs.
+ * @param <V> the type of value computed by the two expressions used as
inputs.
*/
- static final class Divide<R> extends ArithmeticFunction<R> {
+ public static final class Divide<R, V extends Number> extends
ArithmeticFunction<R,V> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = -7709291845568648891L;
- /** Description of results of the {@code "Divide"} expression. */
- private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Divide);
- @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
- /** Creates a new expression for the {@code "Divide"} operation. */
- Divide(final Expression<R, ? extends Number> expression1,
- final Expression<R, ? extends Number> expression2)
+ /**
+ * Creates a new expression for the {@code "Divide"} operation.
+ *
+ * @param expression1 the first of the two expressions to be used by
this operation.
+ * @param expression2 the second of the two expressions to be used
by this operation.
+ */
+ public Divide(final Expression<R, ? extends V> expression1,
+ final Expression<R, ? extends V> expression2)
{
super(expression1, expression2);
}
- /** Creates a new expression of the same type but different
parameters. */
- @Override public Expression<R,Number> recreate(final Expression<R,?>[]
effective) {
+ /** Creates a new arithmetic function for the same operation but using
different parameters. */
+ @Override public Expression<R, Number> recreate(final
Expression<R,?>[] effective) {
return new Divide<>(effective[0].toValueType(Number.class),
effective[1].toValueType(Number.class));
}
- /** Identification of the {@code "Divide"} operation. */
- private static final ScopedName NAME =
createName(FunctionNames.Divide);
- @Override public ScopedName getFunctionName() {return NAME;}
+ /** Description of results of the {@code "Divide"} expression. */
+ @Override protected AttributeType<Number> expectedType() {return TYPE;}
+ private static final AttributeType<Number> TYPE =
createNumericType(FunctionNames.Divide);
+
+ /** Representation of the {@code "Divide"} operation. */
@Override protected char symbol() {return '÷';}
+ @Override public ScopedName getFunctionName() {return NAME;}
+ private static final ScopedName NAME =
createName(FunctionNames.Divide);
/** Divides the given integers, changing the type if the result is not
an integer. */
@Override protected Number applyAsDouble (double left, double
right) {return left / right;}
@@ -281,5 +367,10 @@ abstract class ArithmeticFunction<R> extends
BinaryFunction<R, Number, Number>
return Fraction.valueOf(left, right);
}
}
+
+ /** {@return {@code Number} by default because the result may change
the class} */
+ @Override public Class<? extends Number> getResultClass() {
+ return effective(widestOperandType()) == NumberType.DOUBLE ?
Double.class : Number.class;
+ }
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
index b4cd3e2d3a..a39dd543e6 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
@@ -30,7 +30,13 @@ import org.opengis.filter.Expression;
/**
- * An operation upon two operands.
+ * An operation upon two numerical operands. Inputs are {@link Number}
instances
+ * which will be converted to {@link Double}. Output are {@link Double}.
+ *
+ * <h2>Terminology</h2>
+ * "Binary operator" is a specialization of "binary function" in that it
restricts
+ * the inputs and the output to the same set, which is the set of
double-precision
+ * floating point numbers.
*
* @author Martin Desruisseaux (Geomatys)
*
@@ -45,7 +51,7 @@ final class BinaryOperator<R> extends BinaryFunction<R,
Number, Number>
private static final long serialVersionUID = 8021641013005967925L;
/**
- * The function to apply.
+ * Description of the function to apply.
*/
private final Function function;
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
index 299fe553f8..5ecaab95f0 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
@@ -135,7 +135,7 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
}
return geomImpl;
} catch (Exception e) {
- warning(e, false);
+ warning(e);
}
return null;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
index 210d419f2e..d08942812a 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
@@ -113,7 +113,7 @@ abstract class GeometryParser<R,G> extends
GeometryConstructor<R,G> {
}
return library.getGeometry(result);
} catch (Exception e) {
- warning(Exceptions.unwrap(e), false);
+ warning(Exceptions.unwrap(e));
}
return null;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
index 2ca9c78bee..657fa28971 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
@@ -25,7 +25,7 @@ import org.opengis.filter.Expression;
/**
- * SQLMM spatial functions taking a single geometry operand.
+ * <abbr>SQLMM</abbr> spatial functions taking a single geometry operand.
* This base class assumes that the geometry is the only parameter.
* Subclasses may add other kind of parameters.
*
@@ -96,7 +96,7 @@ class OneGeometry<R> extends SpatialFunction<R> {
if (value != null) try {
return value.operation(operation);
} catch (RuntimeException e) {
- warning(e, false);
+ warning(e);
}
return null;
}
@@ -159,7 +159,7 @@ class OneGeometry<R> extends SpatialFunction<R> {
if (value != null) try {
return value.operationWithArgument(operation,
argument.apply(input));
} catch (RuntimeException e) {
- warning(e, false);
+ warning(e);
}
return null;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
index a6142a1883..9479c2e213 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
@@ -254,7 +254,7 @@ final class ST_Point<R> extends FunctionWithSRID<R> {
}
}
} catch (Exception e) {
- warning(e, false);
+ warning(e);
return null;
}
if (crs != null) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
index 0a3b7c8847..2f01362a58 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
@@ -133,9 +133,9 @@ final class ST_Transform<R> extends FunctionWithSRID<R> {
return
getGeometryLibrary().getGeometry(value.transform(getTargetCRS(input)));
} catch (BackingStoreException e) {
final Throwable cause = e.getCause();
- warning((cause instanceof Exception) ? (Exception) cause : e,
false);
+ warning((cause instanceof Exception) ? (Exception) cause : e);
} catch (UnsupportedOperationException | FactoryException |
TransformException e) {
- warning(e, false);
+ warning(e);
}
return null;
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
index 667c91b096..a42060b489 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
@@ -127,7 +127,7 @@ class TwoGeometries<R> extends SpatialFunction<R> {
if (other != null) try {
return value.operation(operation, other);
} catch (TransformException | RuntimeException e) {
- warning(e, false);
+ warning(e);
}
}
return null;
@@ -194,7 +194,7 @@ class TwoGeometries<R> extends SpatialFunction<R> {
if (other != null) try {
return value.operationWithArgument(operation, other,
argument.apply(input));
} catch (TransformException | RuntimeException e) {
- warning(e, false);
+ warning(e);
}
}
return null;
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
index e1bae3fb01..3d2169b8dc 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
@@ -247,20 +247,22 @@ public final class LogicalFilterTest extends TestCase {
*/
final var expression = factory.divide(factory.property(attribute,
Integer.class), factory.literal(5));
final var optimization = new Optimization();
- assertInstanceOf(DynamicOptimization.class,
optimization.apply(expression));
+ var optimized = optimization.apply(expression);
+ assertInstanceOf(DynamicOptimization.class, optimized);
assertEquals(200, expression.apply(instance).intValue());
+ assertEquals(200, optimized.apply(instance).intValue());
/*
* Notify the optimizer that property values will be of `String` type.
* The optimizer should compute an `ObjectConverter` in advance.
*/
optimization.setFinalFeatureType(feature);
- final var optimized = optimization.apply(expression);
- assertEquals(200, expression.apply(instance).intValue());
+ optimized = optimization.apply(expression);
assertNotSame(expression, optimized);
+ assertEquals(200, optimized.apply(instance).intValue());
final var property = assertInstanceOf(PropertyValue.class,
optimized.getParameters().get(0));
- assertEquals(String.class, property.getSourceClass());
- assertEquals(Number.class, property.getResultClass());
+ assertEquals(String.class, property.getSourceClass());
+ assertEquals(Integer.class, property.getResultClass());
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
index 9c6a3eeba5..309f516221 100644
---
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
+++
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
@@ -316,7 +316,7 @@ public final class Metadata extends SimpleMetadata {
public Collection<Responsibility> getPointOfContacts() {
if (creator != null) {
final var p = new Person(creator);
- return (author != null) ? List.of(p, author) : List.of(author);
+ return (author != null) ? List.of(p, author) : List.of(p);
}
return (author != null) ? List.of(author) : List.of();
}
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
index f6218e723c..baea507adc 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
@@ -238,7 +238,7 @@ abstract class ArrayVector<E extends Number> extends Vector
implements CheckedCo
}
/**
- * Returns the value cast as a {@code float}, since we may loose
precision but the
+ * Returns the value cast as a {@code float}, since we may loss
precision but the
* result of the cast is not completely wrong (at worst we get zero of
infinity values
* if the magnitude of the {@code double} value was too small or too
large).
*/
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
index 677458deaf..6cb292ca30 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
@@ -722,7 +722,7 @@ public class Statistics implements DoubleConsumer,
LongConsumer, Cloneable, Seri
if (last == (double) lastAsLong) {
/*
* 'lastAsLong' may have more precision than 'last' since the
cast to the
- * 'double' type may loose some digits. Invoke the
'delta.accept(long)' version.
+ * 'double' type may loss some digits. Invoke the
'delta.accept(long)' version.
*/
delta.accept(sample - lastAsLong);
} else {