blob: 539ba81b70e173badc1676c00a0827aab5808e13 [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.internal.converter;
import java.util.Set;
import java.util.EnumSet;
import org.apache.sis.util.Numbers;
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.resources.Errors;
/**
* Handles conversions from {@link java.lang.Number} to other kind of numbers.
* This class supports only the type supported by {@link Numbers}.
*
* <h2>Performance note</h2>
* We provide a single class for all supported kinds of {@code Number} and delegate the actual
* work to the {@code Numbers} static methods. This is not a very efficient way to do the work.
* For example it may be more efficient to provide specialized subclasses for each target class,
* so we don't have to execute the {@code switch} inside the {@code Numbers} class every time a
* value is converted. However performance is not the primary concern here, since those converters
* will typically be used by code doing more costly work (e.g. the {@code sis-metadata} module
* providing {@code Map} views using Java reflection). So we rather try to be more compact.
* If nevertheless performance appears to be a problem, consider reverting to revision
* {@code d73a10558dda4b41723d4f5652a792ae9c24f69e} (subversion: 1455255) of this class,
* which was using one subclass per target type as described above.
*
* <h2>Immutability and thread safety</h2>
* This class and all inner classes are immutable, and thus inherently thread-safe.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.3
*
* @param <S> the source number type.
* @param <T> the target number type.
*
* @since 0.3
* @module
*/
final class NumberConverter<S extends Number, T extends Number> extends SystemConverter<S,T> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 3339549290992876106L;
/**
* The inverse converter, created when first needed.
*/
private transient volatile ObjectConverter<T,S> inverse;
/**
* Creates a new converter for the given source and target classes.
* This constructor does not verify the validity of parameter values.
* It is caller's responsibility to ensure that the given class are
* supported by the {@link Numbers} methods.
*/
NumberConverter(final Class<S> sourceClass, final Class<T> targetClass) {
super(sourceClass, targetClass);
}
/**
* Returns the inverse converter, creating it when first needed.
* This method delegates to {@link SystemRegistry#INSTANCE} and caches the result.
* We do not provide pre-defined constant for the various converter because there
* is too many possibly combinations.
*/
@Override
public ObjectConverter<T,S> inverse() throws UnsupportedOperationException {
/*
* No need to synchronize. This is not a big deal if the same object is fetched twice.
* The ConverterRegistry clas provides the required synchronization.
*/
ObjectConverter<T,S> candidate = inverse;
if (candidate == null) try {
inverse = candidate = SystemRegistry.INSTANCE.findExact(targetClass, sourceClass);
} catch (UnconvertibleObjectException e) {
throw new UnsupportedOperationException(Errors.format(Errors.Keys.NonInvertibleConversion), e);
}
return candidate;
}
/**
* Declares this converter as a injective or surjective function,
* depending on whether conversions loose information or not.
*/
@Override
public Set<FunctionProperty> properties() {
return EnumSet.of(Numbers.widestClass(sourceClass, targetClass) == targetClass
? FunctionProperty.INJECTIVE : FunctionProperty.SURJECTIVE,
FunctionProperty.ORDER_PRESERVING, FunctionProperty.INVERTIBLE);
}
/**
* Converts the given number to the target type if that type is different.
* This implementation is inefficient, but avoid us the need to create one
* subclass for each number type. See class javadoc for more details.
*/
@Override
public T apply(final S source) {
final double sourceValue = source.doubleValue();
T target;
try {
target = Numbers.cast(source, targetClass);
} catch (IllegalArgumentException e) {
throw new UnconvertibleObjectException(formatErrorMessage(source), e);
}
final double targetValue = target.doubleValue();
if (Double.doubleToLongBits(targetValue) != Double.doubleToLongBits(sourceValue)) {
/*
* Casted value is not equal to the source value. Maybe we just lost the fraction digits
* in a (double → long) cast, in which case the difference should be smaller than 1.
*/
final double delta = Math.abs(targetValue - sourceValue);
if (!(delta < 0.5)) { // Use '!' for catching NaN.
if (delta < 1) {
target = Numbers.cast(Math.round(sourceValue), targetClass);
} else {
/*
* The delta may be greater than 1 in a (BigInteger/BigDecimal → long) cast if the
* BigInteger/BigDecimal has more significant digits than what the double type can
* hold.
*/
throw new UnconvertibleObjectException(formatErrorMessage(source));
}
}
}
return target;
}
/**
* Converter from numbers to comparables. This special case exists because {@link Number}
* does not implement {@link java.lang.Comparable} directly, but all known subclasses do.
*/
static final class Comparable<S extends Number> extends SystemConverter<S, java.lang.Comparable<?>> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -6366381413315460619L;
/**
* Creates a new converter from the given type of numbers to {@code Comparable} instances.
*/
@SuppressWarnings({"rawtypes","unchecked"})
Comparable(final Class<S> sourceClass) {
super(sourceClass, (Class) java.lang.Comparable.class);
}
/**
* If the source class implements {@code Comparable}, then this converter is bijective.
* Otherwise there is no known property for this converter.
*/
@Override
public Set<FunctionProperty> properties() {
if (targetClass.isAssignableFrom(sourceClass)) {
return bijective();
}
return EnumSet.noneOf(FunctionProperty.class);
}
/**
* Converts the given number to a {@code Comparable} if its type is different.
*/
@Override
public java.lang.Comparable<?> apply(final Number source) {
if (source == null || source instanceof java.lang.Comparable<?>) {
return (java.lang.Comparable<?>) source;
}
return (java.lang.Comparable<?>) Numbers.narrowestNumber(source);
}
}
}