/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.internal;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import org.geotoolkit.lang.Static;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Classes;
import org.geotoolkit.util.collection.XCollections;
import org.geotoolkit.resources.Errors;
import static java.lang.Math.*;
import static org.apache.sis.util.collection.Containers.hashMapCapacity;
import static org.geotoolkit.util.collection.XCollections.unmodifiableOrCopy;
/**
* Various utility methods not to be put in public API.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @since 3.18 (derived from 3.00)
* @module
*/
public final class InternalUtilities extends Static {
/**
* Relative difference tolerated when comparing floating point numbers using
* {@link ComparisonMode#APPROXIMATIVE}.
* <p>
* Historically, this was the relative tolerance threshold for considering two matrixes
* as {@linkplain org.geotoolkit.referencing.operation.matrix.XMatrix#equals(Object,
* ComparisonMode) equal}. This value has been determined empirically in order to allow
* {@link org.apache.sis.referencing.operation.transform.MathTransforms} to detect the
* cases where two {@link org.apache.sis.referencing.operation.transform.LinearTransform}
* are equal for practical purpose. This threshold can be used as below:
*
* {@preformat java
* Matrix m1 = ...;
* Matrix m2 = ...;
* if (MatrixUtilities.epsilonEqual(m1, m2, EQUIVALENT_THRESHOLD, true)) {
* // Consider that matrixes are equal.
* }
* }
*
* By extension, the same threshold value is used for comparing other floating point values.
*
* @since 3.20
*/
public static final double COMPARISON_THRESHOLD = 1E-14;
/**
* Default tolerance threshold for comparing ordinate values in a projected CRS,
* assuming that the unit of measurement is metre. This is not a tolerance for
* testing map projection accuracy.
*
* @since 3.20
*/
public static final double LINEAR_TOLERANCE = 1.0;
/**
* Default tolerance threshold for comparing ordinate values in a geographic CRS,
* assuming that the unit of measurement is decimal degrees and using the standard
* nautical mile length.
*
* @since 3.20
*/
public static final double ANGULAR_TOLERANCE = LINEAR_TOLERANCE / (1852 * 60);
/**
* Workaround for rounding errors.
*/
private static final double EPS = 1E-8;
/**
* Floating point tolerance in <cite>Unit in Last Place</cite> (ULP).
* Used in order to determine if an integer can be rounded.
*/
private static final int ULP_TOLERANCE = 4;
/**
* Do not allow instantiation of this class.
*/
private InternalUtilities() {
}
/**
* Returns an identity string for the given value. This method returns a string similar to
* the one returned by the default implementation of {@link Object#toString()}, except that
* a simple class name (without package name) is used instead than the fully-qualified name.
*
* @param value The object for which to get the identity string, or {@code null}.
* @return The identity string for the given object.
*
* @since 3.17
*/
public static String identity(final Object value) {
return Classes.getShortClassName(value) + '@' + Integer.toHexString(System.identityHashCode(value));
}
/**
* Returns {@code true} if {@code ymin} is the south pole and {@code ymax} is the north pole.
*
* @param ymin The minimal latitude to test.
* @param ymax The maximal latitude to test.
* @return {@code true} if the given latitudes are south pole to noth pole respectively.
*
* @since 3.20
*/
public static boolean isPoleToPole(final double ymin, final double ymax) {
return abs(ymin + 90) <= ANGULAR_TOLERANCE && abs(ymax - 90) <= ANGULAR_TOLERANCE;
}
/**
* Returns {@code true} if the given values are approximatively equal.
* Two NaN values are considered equal.
*
* @param v1 The first value to compare.
* @param v2 The second value to compare.
* @param epsilon The tolerance threshold, which must be positive.
* @return {@code true} If both values are approximatively equal.
*
* @since 3.20
*/
public static boolean epsilonEqual(final double v1, final double v2, final double epsilon) {
return (abs(v1 - v2) <= epsilon) || Double.doubleToLongBits(v1) == Double.doubleToLongBits(v2);
}
/**
* Returns {@code true} if the given values are approximatively equal given the
* comparison mode.
*
* @param v1 The first value to compare.
* @param v2 The second value to compare.
* @param mode The comparison mode to use for comparing the numbers.
* @return {@code true} If both values are approximatively equal.
*
* @since 3.18
*/
public static boolean epsilonEqual(final double v1, final double v2, final ComparisonMode mode) {
switch (mode) {
default: return org.geotoolkit.util.Utilities.equals(v1, v2);
case APPROXIMATIVE: return epsilonEqual(v1, v2);
case DEBUG: {
final boolean equal = epsilonEqual(v1, v2);
assert equal : "v1=" + v1 + " v2=" + v2 + " Δv=" + abs(v1-v2);
return equal;
}
}
}
/**
* Returns {@code true} if the given values are approximatively equal, up to the
* {@linkplain #COMPARISON_THRESHOLD comparison threshold}.
*
* @param v1 The first value to compare.
* @param v2 The second value to compare.
* @return {@code true} If both values are approximatively equal.
*
* @since 3.18
*/
public static boolean epsilonEqual(final double v1, final double v2) {
final double threshold = COMPARISON_THRESHOLD * max(abs(v1), abs(v2));
if (threshold == Double.POSITIVE_INFINITY || Double.isNaN(threshold)) {
return Double.doubleToLongBits(v1) == Double.doubleToLongBits(v2);
}
return abs(v1 - v2) <= threshold;
}
/**
* Returns {@code true} if the following objects are floating point numbers ({@link Float} or
* {@link Double} types) and approximatively equal. If the given object are not floating point
* numbers, then this method returns {@code false} unconditionally on the assumption that
* strict equality has already been checked before this method call.
*
* @param v1 The first value to compare.
* @param v2 The second value to compare.
* @return {@code true} If both values are real number and approximatively equal.
*
* @since 3.18
*/
public static boolean floatEpsilonEqual(final Object v1, final Object v2) {
return (v1 instanceof Float || v1 instanceof Double) &&
(v2 instanceof Float || v2 instanceof Double) &&
epsilonEqual(((Number) v1).doubleValue(), ((Number) v2).doubleValue());
}
/**
* Compares the given objects in {@link ComparisonMode#DEBUG} mode.
*
* @param o1 The first object to compare.
* @param o2 The second object to compare.
* @return {@code true} if the given objects are equal.
* @throws AssertionError If the given objects are not equal and the cause can be
* specified in the exception message.
*
* @since 3.20
*/
public static boolean debugEquals(final Object o1, final Object o2) throws AssertionError {
return Utilities.deepEquals(o1, o2, ComparisonMode.DEBUG);
}
/**
* Rounds the specified value, providing that the difference between the original value and
* the rounded value is not greater than the specified amount of floating point units. This
* method can be used for hiding floating point error likes 2.9999999996.
*
* @param value The value to round.
* @param scale The scale by which to multiply the value before to round it.
* @param maxULP The maximal change allowed in ULPs (Unit in the Last Place).
* @return The rounded value, of {@code value} if it was not close enough to an integer.
*
* @since 3.20
*/
public static double adjustForRoundingError(final double value, double scale, final int maxULP) {
scale = Math.abs(scale);
final double target = Math.rint(value * scale) / scale;
return (Math.abs(value - target) <= maxULP*Math.ulp(value)) ? target : value;
}
/**
* Work-around for rounding error. This method invokes {@link #adjustForRoundingError(double,
* double, int)} with arbitrary values that may change in any future version. Current values
* were determined empirically from practical experience with IFREMER and other data.
*
* @param value The value to fix.
* @return The "fixed" value.
*
* @since 3.20
*/
public static double adjustForRoundingError(final double value) {
return adjustForRoundingError(value, 360, ULP_TOLERANCE);
}
/**
* Converts a {@code float} value to {@code double} value while preserving the string
* representation in base 10. The result may be different from the value that we would
* get from a normal conversion - which preserve the value in base 2, but it may be
* closer to the user intend.
* <p>
* <b>Example:</b> {@code 99.99f} converted to {@code double} by the normal cast operation
* produces {@code 99.98999786376953}, while the user's intend was probably {@code 99.99}.
* <p>
* The current algorithm is inefficient, but we define this method so we have a single
* place where to improve it if needed.
*
* @param value The value to convert.
* @return The converted value.
*
* @since 3.19
*/
public static double convert10(final float value) {
return Double.parseDouble(Float.toString(value));
}
/**
* Returns a copy of the given array as a non-empty immutable set.
* If the given array is empty, then this method returns {@code null}.
* <p>
* This method is not provided in the public API because the recommended
* practice is usually to return an empty collection rather than {@code null}.
*
* @param <T> The type of elements.
* @param elements The elements to copy in a set.
* @return An unmodifiable set which contains all the given elements.
*
* @since 3.17
*/
@SafeVarargs
public static <T> Set<T> nonEmptySet(final T... elements) {
final Set<T> asSet = XCollections.immutableSet(elements);
return (asSet != null && asSet.isEmpty()) ? null : asSet;
}
/**
* Returns an unmodifiable map which contains a copy of the given map, only for the given keys.
* The value for the given keys shall be of the given type. Other values can be of any types,
* since they will be ignored.
*
* @param <K> The type of keys in the map.
* @param <V> The type of values in the map.
* @param map The map to copy, or {@code null}.
* @param valueType The base type of retained values.
* @param keys The keys of values to retain.
* @return A copy of the given map containing only the given keys, or {@code null}
* if the given map was null.
* @throws ClassCastException If at least one retained value is not of the expected type.
*
* @since 3.17
*/
@SafeVarargs
public static <K,V> Map<K,V> subset(final Map<?,?> map, final Class<V> valueType, final K... keys)
throws ClassCastException
{
Map<K,V> copy = null;
if (map != null) {
copy = new HashMap<>(hashMapCapacity(Math.min(map.size(), keys.length)));
for (final K key : keys) {
final V value = valueType.cast(map.get(key));
if (value != null) {
copy.put(key, value);
}
}
copy = unmodifiableOrCopy(copy);
}
return copy;
}
/**
* Returns the first non-null element in the given iterable, or {@code null} if none.
* This method makes sense only for collections having determinist iteration order like
* {@link List} and {@link SortedSet} interfaces, or {@link LinkedHashSet} implementation.
*
* @param <E> The type of elements in the iterable.
* @param collection Where to search for the first non-null element.
* @return The first non-null element, or {@code null} if none or if the given iterable is null.
*
* @since 3.20
*/
public static <E> E firstNonNull(final Iterable<E> collection) {
if (collection != null) {
for (final E element : collection) {
if (element != null) {
return element;
}
}
}
return null;
}
/**
* Returns the separator to use between numbers. Current implementation returns the coma
* character, unless the given number already use the coma as the decimal separator.
*
* @param format The format used for formatting numbers.
* @return The character to use as a separator between numbers.
*
* @since 3.11
*/
public static char getSeparator(final NumberFormat format) {
if (format instanceof DecimalFormat) {
final char c = ((DecimalFormat) format).getDecimalFormatSymbols().getDecimalSeparator();
if (c == ',') {
return ';';
}
}
return ',';
}
/**
* Sets the {@linkplain NumberFormat#getMinimumFractionDigits() minimum fraction digits} and
* {@linkplain NumberFormat#getMaximumFractionDigits() maximum fraction digits} of the given
* format objects for "acceptable" formatting of the given value. The work performed by this
* method is heuristic and may change in any future version.
*
* @param format The format to configure.
* @param value The sample value to use for formatting the given format.
* @param maxPrecision The maximal precision to use (e.g. 6).
*
* @since 3.20
*/
public static void configure(final NumberFormat format, double value, final int maxPrecision) {
value = abs(value);
if (format instanceof DecimalFormat) {
value *= ((DecimalFormat) format).getMultiplier();
}
int precision;
for (precision=0; precision<maxPrecision; precision++) {
final double check = rint(value*1E+4) % 1E+4;
if (!(check > value*EPS)) { // 'step' may be NaN
break;
}
value *= 10;
}
format.setMinimumFractionDigits(precision);
format.setMaximumFractionDigits(precision);
}
/**
* Gets the ARGB values for the given hexadecimal value. If the given code begins with the
* {@code '#'} character, then this method accepts the following hexadecimal patterns:
* <p>
* <ul>
* <li>{@code "#AARRGGBB"}: an explicit ARGB code used verbatim.</li>
* <li>{@code "#RRGGBB"}: a fully opaque RGB color.</li>
* <li>{@code "#ARGB"}: an abbreviation for {@code "#AARRGGBB"}.</li>
* <li>{@code "#RGB"}: an abbreviation for {@code "#RRGGBB"}.
* For example #0BC means #00BBCC.</li>
* </ul>
*
* @param color The color code to parse.
* @throws NumberFormatException If the given code can not be parsed.
* @return The ARGB code.
*
* @see java.awt.Color#decode(String)
*
* @since 3.19
*/
@SuppressWarnings("fallthrough")
public static int parseColor(String color) throws NumberFormatException {
color = color.trim();
if (color.startsWith("#")) {
final String code = color.substring(1);
// Parses as a long in order to accept ARGB codes in the 80000000 to FF000000 range.
// The check for the string length will ensure that we are not outside those bounds.
int value = (int) Long.parseLong(code, 16);
switch (code.length()) {
case 3: value |= 0xF000; // Fallthrough
case 4: {
int t;
return (((t=(value & 0xF000)) | (t << 4)) << 12) |
(((t=(value & 0x0F00)) | (t << 4)) << 8) |
(((t=(value & 0x00F0)) | (t << 4)) << 4) |
(((t=(value & 0x000F)) | (t << 4)));
}
case 6: value |= 0xFF000000; // Fallthrough
case 8: return value;
}
} else {
/*
* Parses the string as an opaque color unless an alpha value was provided.
* This matches closing the default java.awt.Color.decode(String) behavior,
* which considers every colors as opaque. We relax slightly the condition
* by allowing non-zero alpha values. The inconvenient is that specifying
* a fully transparent color is not possible with syntax - please use the
* above "#" syntax instead.
*/
final long n = Long.decode(color);
int value = (int) n;
if (value == n) {
if ((value & 0xFF000000) == 0) {
value |= 0xFF000000;
}
return value;
}
}
throw new NumberFormatException(Errors.format(
Errors.Keys.IllegalArgument_2, "color", color));
}
}