Author: desruisseaux Date: Fri Oct 14 22:47:14 2016 New Revision: 1764996 URL: http://svn.apache.org/viewvc?rev=1764996&view=rev Log: Begin custom implementation of JSR-363 (Units of measurement).
Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java (with props) sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java (with props) sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java (with props) sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java (with props) sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java (with props) Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java?rev=1764996&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java (added) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java [UTF-8] Fri Oct 14 22:47:14 2016 @@ -0,0 +1,126 @@ +/* + * 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 org.apache.sis.math.Fraction; +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 Fraction} to other kind of numbers. + * + * @author Martin Desruisseaux (Geomatys) + * @since 0.8 + * @version 0.8 + * @module + */ +public final class FractionConverter extends SystemConverter<Fraction,Integer> { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 862676960870569201L; + + /** + * The unique instance of this converter. + */ + public static final FractionConverter INSTANCE = new FractionConverter(); + + /** + * Creates a new converter. Only one instance is enough, but this constructor + * needs to be public for allowing invocation by {@link java.util.ServiceLoader}. + */ + public FractionConverter() { + super(Fraction.class, Integer.class); + } + + /** + * Returns the unique instance of this converter. + * + * @return the unique instance of this converter. + */ + @Override + public ObjectConverter<Fraction,Integer> unique() { + return INSTANCE; + } + + /** + * Declares that this converter is injective, surjective, invertible and preserve order. + * + * @return the properties of this bijective converter. + */ + @Override + public Set<FunctionProperty> properties() { + return bijective(); + } + + /** + * Converts the given fraction to an integer. + * + * @param value the fraction to convert. + * @return the given fraction as an integer. + * @throws UnconvertibleObjectException if the given fraction is not an integer. + */ + @Override + public Integer apply(final Fraction value) throws UnconvertibleObjectException { + if ((value.numerator % value.denominator) == 0) { + return value.numerator / value.denominator; + } + throw new UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertValue_2, value, Integer.class)); + } + + /** + * Returns the converter from integers to fractions. + * + * @return the inverse converter. + */ + @Override + public ObjectConverter<Integer,Fraction> inverse() { + return FromInteger.INSTANCE; + } + + /** + * The inverse of the {@link FractionConverter}. + */ + public static final class FromInteger extends SystemConverter<Integer,Fraction> { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 7411811921783941007L; + + /** + * The unique instance of this converter. + */ + public static final FromInteger INSTANCE = new FromInteger(); + + /** + * Creates a new converter. Only one instance is enough, but this constructor + * needs to be public for allowing invocation by {@link java.util.ServiceLoader}. + */ + public FromInteger() { + super(Integer.class, Fraction.class); + } + + @Override public ObjectConverter<Integer,Fraction> unique() {return INSTANCE;} + @Override public ObjectConverter<Fraction,Integer> inverse() {return FractionConverter.INSTANCE;} + @Override public Set<FunctionProperty> properties() {return bijective();} + @Override public Fraction apply(Integer value) {return new Fraction(value, 1);} + } +} Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java?rev=1764996&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java (added) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java [UTF-8] Fri Oct 14 22:47:14 2016 @@ -0,0 +1,436 @@ +/* + * 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.math; + +import java.io.Serializable; +import org.apache.sis.util.collection.WeakHashSet; + + +/** + * A value class for rational numbers. {@code Fraction} objects are represented by a {@linkplain #numerator} and + * a {@linkplain #denominator} stored at 32 bits integers. Fractions can be {@linkplain #simplify() simplified}. + * All {@code Fraction} instances are immutable and thus inherently thread-safe. + * + * @author Martin Desruisseaux (MPO, Geomatys) + * @since 0.8 + * @version 0.8 + * @module + */ +public final class Fraction extends Number implements Comparable<Fraction>, Serializable { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -4501644254763471216L; + + /** + * Pool of fractions for which the {@link #unique()} method has been invoked. + */ + private static final WeakHashSet<Fraction> POOL = new WeakHashSet<>(Fraction.class); + + /** + * The <var>a</var> term in the <var>a</var>/<var>b</var> fraction. + * Can be positive, negative or zero. + * + * @see #doubleValue() + */ + public final int numerator; + + /** + * The <var>b</var> term in the <var>a</var>/<var>b</var> fraction. + * Can be positive, negative or zero. If zero, then the fraction {@linkplain #doubleValue() floating point} + * value will be positive infinity, negative infinity or NaN depending on the {@linkplain #numerator} value. + * + * @see #doubleValue() + */ + public final int denominator; + + /** + * Creates a new fraction. This constructor stores the fraction exactly as specified; it does not simplify it. + * The fraction can be simplified after construction by a call to {@link #simplify()}. + * + * @param numerator the <var>a</var> term in the <var>a</var>/<var>b</var> fraction. + * @param denominator the <var>b</var> term in the <var>a</var>/<var>b</var> fraction. + */ + public Fraction(final int numerator, final int denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + /** + * Returns a unique fraction instance equals to {@code this}. + * If this method has been invoked previously on another {@code Fraction} with the same value than {@code this}, + * then that previous instance is returned (provided that it has not yet been garbage collected). Otherwise this + * method adds this fraction to the pool of fractions that may be returned in next {@code unique()} invocations, + * then returns {@code this}. + * + * <p>This method is useful for saving memory when a potentially large amount of {@code Fraction} instances will + * be kept for a long time and many instances are likely to have the same values. + * It is usually not worth to invoke this method for short-lived instances.</p> + * + * @return a unique instance of a fraction equals to {@code this}. + */ + public Fraction unique() { + return POOL.unique(this); + } + + /** + * Returns a fraction equivalent to {@code this} but represented by the smallest possible numerator + * and denominator values. If this fraction can not be simplified, then this method returns {@code this}. + * + * @return the simplest fraction equivalent to this fraction. + */ + public Fraction simplify() { + return simplify(numerator, denominator); + } + + /** + * Returns a fraction equivalent to {@code num} / {@code den} after simplification. + * If the simplified fraction is equals to {@code this}, then this method returns {@code this}. + * + * <p>The arguments given to this method are the results of multiplications and additions of {@code int} values. + * This method fails if any argument value is {@link Long#MIN_VALUE} because that value can not be made positive. + * However it should never happen. Even in the worst scenario:</p> + * + * {@prefomat java + * long n = Integer.MIN_VALUE * (long) Integer.MAX_VALUE; + * n += n; + * } + * + * Above result still slightly smaller in magnitude than {@code Long.MIN_VALUE}. + */ + private Fraction simplify(long num, long den) { + assert (num != Long.MIN_VALUE) && (den != Long.MIN_VALUE) : this; + if (num == 0) { + den = Long.signum(den); // Simplify 0/x as 0/±1 or 0/0. + } else if (den == 0) { + num = Long.signum(num); // Simplify x/0 as ±1/0 + } else if (den % num == 0) { + den /= num; // Simplify x/xy as 1/y + if (den < 0) { + den = -den; // Math.negateExact(long) not needed - see javadoc. + num = -1; + } else { + num = 1; + } + } else { + long a = Math.abs(num); + long gcd = Math.abs(den); + long remainder = a % gcd; + if (remainder == 0) { + num /= den; // Simplify xy/x as y/1 + den = 1; + } else { + do { // Search for greater common divisor with Euclid's algorithm. + a = gcd; + gcd = remainder; + remainder = a % gcd; + } while (remainder != 0); + num /= gcd; + den /= gcd; + if (den < 0) { + num = -num; // Math.negateExact(long) not needed - see javadoc. + den = -den; + } + } + } + return (num == numerator && den == denominator) ? this + : new Fraction(Math.toIntExact(num), Math.toIntExact(den)); + } + + /** + * Returns the simplified result of adding the given fraction to this fraction. + * + * @param other the fraction to add to this fraction. + * @return the simplified result of {@code this} + {@code other}. + * @throws ArithmeticException if the result overflows. + */ + public Fraction add(final Fraction other) { + // Intermediate result must be computed in a type wider that the 'numerator' and 'denominator' type. + final long td = this .denominator; + final long od = other.denominator; + return simplify(Math.addExact(od * numerator, td * other.numerator), od * td); + } + + /** + * Returns the simplified result of subtracting the given fraction from this fraction. + * + * @param other the fraction to subtract from this fraction. + * @return the simplified result of {@code this} - {@code other}. + * @throws ArithmeticException if the result overflows. + */ + public Fraction subtract(final Fraction other) { + // Intermediate result must be computed in a type wider that the 'numerator' and 'denominator' type. + final long td = this .denominator; + final long od = other.denominator; + return simplify(Math.subtractExact(od * numerator, td * other.numerator), od * td); + } + + /** + * Returns the simplified result of multiplying the given fraction with this fraction. + * + * @param other the fraction to multiply with this fraction. + * @return the simplified result of {@code this} × {@code other}. + * @throws ArithmeticException if the result overflows. + */ + public Fraction multiply(final Fraction other) { + return simplify(numerator * (long) other.numerator, + denominator * (long) other.denominator); + } + + /** + * Returns the simplified result of dividing this fraction by the given fraction. + * + * @param other the fraction by which to divide this fraction. + * @return the simplified result of {@code this} ÷ {@code other}. + * @throws ArithmeticException if the result overflows. + */ + public Fraction divide(final Fraction other) { + return simplify(numerator * (long) other.denominator, + denominator * (long) other.numerator); + } + + /** + * Returns this fraction rounded toward nearest integer. If the result is located + * at equal distance from the two nearest integers, then rounds to the even one. + * + * @return {@link #numerator} / {@link denominator} rounded toward nearest integer. + */ + public int round() { + if (denominator == Integer.MIN_VALUE) { + if (numerator < (Integer.MIN_VALUE / +2)) return +1; + if (numerator > (Integer.MIN_VALUE / -2)) return -1; + return 0; + } + int n = numerator / denominator; + int r = numerator % denominator; + if (r != 0) { + r = Math.abs(r << 1); + final int d = Math.abs(denominator); + if (r > d || (r == d && (n & 1) != 0)) { + if ((numerator ^ denominator) >= 0) { + n++; + } else { + n--; + } + } + } + return n; + } + + /** + * Returns this fraction rounded toward negative infinity. + * This is different from the default operation on primitive types, which rounds toward zero. + * + * <p><b>Tip:</b> if the numerator and the denominator are both positive or both negative, + * then the result is positive and identical to {@code numerator / denominator}.</p> + * + * @return {@link #numerator} / {@link denominator} rounded toward negative infinity. + */ + public int floor() { + int n = numerator / denominator; + if ((numerator ^ denominator) < 0 && (numerator % denominator) != 0) { + n--; + } + return n; + } + + /** + * Returns this fraction rounded toward positive infinity. + * This is different from the default operation on primitive types, which rounds toward zero. + * + * @return {@link #numerator} / {@link denominator} rounded toward positive infinity. + */ + public int ceil() { + int n = numerator / denominator; + if ((numerator ^ denominator) >= 0 && (numerator % denominator) != 0) { + n++; + } + return n; + } + + /** + * Returns the fraction as a double-precision floating point number. + * Special cases: + * + * <ul> + * <li>If the {@linkplain #numerator} and the {@linkplain #denominator} are both 0, + * then this method returns {@link Double#NaN}.</li> + * <li>If only the {@linkplain #denominator} is zero, then this method returns + * {@linkplain Double#POSITIVE_INFINITY positive infinity} or + * {@linkplain Double#NEGATIVE_INFINITY negative infinity} accordingly the {@linkplain #numerator} sign.</li> + * </ul> + * + * @return this fraction as a floating point number. + */ + @Override + public double doubleValue() { + return numerator / (double) denominator; + } + + /** + * Returns the fraction as a single-precision floating point number. + * Special cases: + * + * <ul> + * <li>If the {@linkplain #numerator} and the {@linkplain #denominator} are both 0, + * then this method returns {@link Float#NaN}.</li> + * <li>If only the {@linkplain #denominator} is zero, then this method returns + * {@linkplain Float#POSITIVE_INFINITY positive infinity} or + * {@linkplain Float#NEGATIVE_INFINITY negative infinity} accordingly the {@linkplain #numerator} sign.</li> + * </ul> + * + * @return this fraction as a floating point number. + */ + @Override + public float floatValue() { + return (float) doubleValue(); + } + + /** + * Returns this fraction rounded toward zero. + * + * @return this fraction rounded toward zero. + */ + @Override + public long longValue() { + return intValue(); + } + + /** + * Returns this fraction rounded toward zero. + * + * @return {@link #numerator} / {@link denominator} rounded toward zero. + * + * @see #round() + * @see #floor() + * @see #ceil() + */ + @Override + public int intValue() { + return numerator / denominator; + } + + /** + * Returns this fraction rounded toward zero, if the result can be represented as a short integer. + * + * @return this fraction rounded toward zero. + * @throws ArithmeticException if the result can not be represented as a short integer. + */ + @Override + public short shortValue() { + final int n = intValue(); + if ((n & ~0xFFFF) == 0) return (short) n; + throw new ArithmeticException(); + } + + /** + * Returns this fraction rounded toward zero, if the result can be represented as a signed byte. + * + * @return this fraction rounded toward zero. + * @throws ArithmeticException if the result can not be represented as a signed byte. + */ + @Override + public byte byteValue() { + final int n = intValue(); + if ((n & ~0xFF) == 0) return (byte) n; + throw new ArithmeticException(); + } + + /** + * Compares this fraction with the given one for order. + * + * @param other the fraction to compare to this fraction for ordering. + * @return a negative number if this fraction is smaller than the given fraction, + * a positive number if greater, or 0 if equals. + */ + @Override + public int compareTo(final Fraction other) { + return Long.signum(numerator * (long) other.denominator - other.numerator * (long) denominator); + } + + /** + * Compares this fraction with the given object for equality. This method returns {@code true} only if + * the two objects are fractions with same {@linkplain #numerator} and {@linkplain #denominator} values. + * Fractions with different values are not considered equal even if the two fraction are equivalent. + * + * @param other the object to compare with this fraction for equality. + * @return {@code true} if the given object is an other fraction with the same numerator and denominator values. + */ + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (other instanceof Fraction) { + final Fraction that = (Fraction) other; + return numerator == that.numerator && + denominator == that.denominator; + } + return false; + } + + /** + * Returns a hash code value for this fraction. + */ + @Override + public int hashCode() { + return (numerator + 31 * denominator) ^ (int) serialVersionUID; + } + + /** + * The matrix of Unicode symbols available for some fractions. Each row contains all symbols for the same numerator. + * For example the first row contains the symbol of all fractions of the form 0/x, the second row all fractions of + * the form 1/x, <i>etc.</i>. In each row, the character at column <var>i</var> is for the fraction having the + * denominator i + (row index) + 1. + */ + private static final char[][] UNICODES = { + {0, 0, '↉'}, + { '½', '⅓', '¼', '⅕', '⅙', '⅐', '⅛', '⅑', '⅒'}, + { '⅔', 0, '⅖'}, + { '¾', '⅗', 0, 0, '⅜'}, + { '⅘'}, + { '⅚', 0, '⅝'}, + {}, + { '⅞'} + }; + + /** + * Returns a string representation of this fraction. + * This method returns Unicode symbol if possible. + * + * @return a string representation of this fraction. + */ + @Override + public String toString() { + if (numerator >= 0 && numerator < UNICODES.length) { + final int d = denominator - numerator - 1; + if (d >= 0) { + final char[] r = UNICODES[numerator]; + if (d < r.length) { + final char c = r[d]; + if (c != 0) { + return String.valueOf(c); + } + } + } + } + if (denominator == 0 && numerator != 0) { + return (numerator >= 0) ? "∞" : "−∞"; + } + return new StringBuilder().append(numerator).append('⁄').append(denominator).toString(); + } +} Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1764996&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java (added) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Fri Oct 14 22:47:14 2016 @@ -0,0 +1,227 @@ +/* + * 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.measure; + +import java.io.Serializable; +import java.util.Objects; +import javax.measure.Unit; +import javax.measure.Quantity; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.collection.WeakHashSet; + + +/** + * Base class of all unit implementations. All unit instances shall be immutable. + * + * <div class="section">Immutability and thread safety</div> + * This base class is immutable and thus inherently thread-safe. + * All subclasses shall be immutable too. + * + * @param <Q> the kind of quantity to be measured using this units. + * + * @author Martin Desruisseaux (MPO, Geomatys) + * @since 0.8 + * @version 0.8 + * @module + */ +abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, Serializable { + /** + * Pool of all {@code AbstractUnit} instances created up to date. + */ + @SuppressWarnings("rawtypes") + private static final WeakHashSet<AbstractUnit> POOL = new WeakHashSet<>(AbstractUnit.class); + + /** + * The unit symbol, or {@code null} if this unit has no specific symbol. If {@code null}, + * then the {@link #toString()} method is responsible for creating a representation on the fly. + * + * <p>Users can override this symbol by call to {@link UnitFormat#label(Unit, String)}, + * but such overriding applies only to the target {@code UnitFormat} instance.</p> + * + * @see #getSymbol() + */ + private final String symbol; + + /** + * The unit name, or {@code null} if this unit has no specific name. If this unit exists + * in the EPSG database, then the value should be the name as specified in the database. + * + * @see #getName() + */ + private final String name; + + /** + * The EPSG code, or 0 if this unit has no EPSG code. + */ + final short epsg; + + /** + * Creates a new unit having the given symbol and EPSG code. + * + * @param name the unit name, or {@code null} if this unit has no specific name. + * @param symbol the unit symbol, or {@code null} if this unit has no specific symbol. + * @param epsg the EPSG code, or 0 if this unit has no EPSG code. + */ + AbstractUnit(final String name, final String symbol, final short epsg) { + this.name = name; + this.symbol = symbol; + this.epsg = epsg; + } + + /** + * Returns the symbol (if any) of this unit. A unit may have no symbol, in which case + * the {@link #toString()} method is responsible for creating a string representation. + * + * @return the unit symbol, or {@code null} if this unit has no specific symbol. + * + * @see #toString() + */ + @Override + public final String getSymbol() { + return symbol; + } + + /** + * Returns the name (if any) of this unit. For example {@link Units#METRE} has the "m" symbol and the "metre" name. + * If this unit exists in the EPSG database, then this method should return the name as specified in the database. + * + * @return the unit name, or {@code null} if this unit has no specific name. + */ + @Override + public final String getName() { + return name; + } + + /** + * Indicates if this unit is compatible with the given unit. + * This implementation delegates to: + * + * {@preformat java + * return getDimension().equals(that.getDimension()); + * } + * + * @param that the other unit to compare for compatibility. + * @return {@code true} if the given unit is compatible with this unit. + * + * @see #getDimension() + */ + @Override + public final boolean isCompatible(final Unit<?> that) { + ArgumentChecks.ensureNonNull("that", that); + return getDimension().equals(that.getDimension()); + } + + /** + * Returns a unique instance of this unit. An initially empty pool of {@code AbstractUnit} + * instances is maintained. When invoked, this method first checks if an instance equals + * to this unit exists in the pool. If such instance is found, then it is returned. + * Otherwise this instance is added in the pool using weak references and returned. + */ + final AbstractUnit<Q> intern() { + return POOL.unique(this); + } + + /** + * Returns an instance equals to this unit, ignoring the symbol. + * If such instance exists in the pool of existing units, it is + * returned. Otherwise this method returns {@code this}. + */ + final AbstractUnit<Q> internIgnoreSymbol() { + @SuppressWarnings("unchecked") + AbstractUnit<Q> unit = POOL.get(new Unamed(this)); + if (unit == null) { + unit = this; + } + return unit; + } + + /** + * Compares this unit with the given unit, ignoring symbol. + * Implementations shall check the {@code obj} type. + * + * @param obj The object to compare with this unit, or {@code null}. + * @return {@code true} If the given unit is equals to this unit, ignoring symbol. + */ + protected abstract boolean equalsIgnoreSymbol(Object obj); + + /** + * Compares this unit with the given object for equality. + * + * @param other The other object to compares with this unit, or {@code null}. + * @return {@code true} if the given object is equals to this unit. + */ + @Override + public final boolean equals(Object other) { + if (other instanceof Unamed) { + other = ((Unamed) other).unit; + } + if (equalsIgnoreSymbol(other)) { + return Objects.equals(symbol, ((AbstractUnit<?>) other).symbol); + } + return false; + } + + /** + * A temporary proxy used by {@link #internIgnoreSymbol()} for finding an existing units, + * ignoring the symbol. + */ + private static final class Unamed { + final AbstractUnit<?> unit; + + Unamed(final AbstractUnit<?> unit) {this.unit = unit;} + @Override public int hashCode() {return unit.hashCode();} + @Override public boolean equals(Object obj) {return unit.equalsIgnoreSymbol(obj);} + } + + /** + * Returns a hash code value for this unit, ignoring symbol. + */ + @Override + public abstract int hashCode(); + + /** + * Replaces the deserialized unit instance by a unique instance, if any. + * + * @return The unique unit instance. + */ + protected final Object readResolve() { + return intern(); + } + + /** + * Returns the exception to throw when the given unit arguments are illegal + * for the operation to perform. + */ + final ArithmeticException illegalUnitOperation(final String operation, final AbstractUnit<?> that) { + return new ArithmeticException(); // TODO: provide a message. + } + + /** + * Returns the unlocalized string representation of this unit, either as a single symbol + * or a product of symbols or scale factors, eventually with an offset. + * + * @see #getSymbol() + */ + @Override + public final String toString() { + if (symbol != null) { + return symbol; + } else { + return UnitFormat.INSTANCE.format(this); + } + } +} Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java?rev=1764996&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java (added) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java [UTF-8] Fri Oct 14 22:47:14 2016 @@ -0,0 +1,282 @@ +/* + * 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.measure; + +import java.util.Collections; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.io.Serializable; +import javax.measure.Dimension; +import org.apache.sis.math.Fraction; +import org.apache.sis.util.ObjectConverters; +import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.UnsupportedImplementationException; +import org.apache.sis.internal.converter.FractionConverter; + + +/** + * Dimension (length, mass, time, <i>etc.</i>) of a unit of measurement. + * Only two kind of dimensions are defined in Apache SIS: + * + * <ul> + * <li>Base dimensions are the 7 base dimensions specified by the SI system.</li> + * <li>Derived dimensions are products of base dimensions raised to some power.</li> + * </ul> + * + * The powers should be integers, but this implementation nevertheless accepts fractional power of dimensions. + * While quantities with dimension such as √M makes no sense physically, on a pragmatic point of view it is easier + * to write programs in which such units appear in intermediate calculations but become integers in the final result. + * Furthermore, some dimensions with fractional power actually exist. Examples: + * + * <ul> + * <li>Voltage noise density measured per √(Hz).</li> + * <li><a href="http://en.wikipedia.org/wiki/Specific_detectivity">Specific detectivity</a> + * as T^2.5 / (M⋅L) dimension.</li> + * </ul> + * + * @author Martin Desruisseaux (Geomatys) + * @since 0.8 + * @version 0.8 + * @module + */ +final class UnitDimension implements Dimension, Serializable { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 2568769237612674235L; + + /** + * Pool of all {@code UnitDimension} instances created up to date. + * Keys are the same map than {@link UnitDimension#components}, which shall be immutable. + * We hold the dimensions by strong reference on the assumption that we will not create many of them. + */ + private static final ConcurrentMap<Map<UnitDimension,Fraction>, UnitDimension> POOL = new ConcurrentHashMap<>(); + + // The POOL map must be created before the following constants. + + /** + * Pseudo-dimension for dimensionless units. + */ + private static final UnitDimension NONE = new UnitDimension(Collections.emptyMap()); + + /** + * The product of base dimensions that make this dimension. All keys in this map shall be base dimensions + * (base dimensions are identified by non-zero {@link #symbol}). If this {@code UnitDimension} is itself a + * base dimension, then the map contains {@code this} raised to power 1. The map shall never be {@code null}. + * + * @see #getBaseDimensions() + */ + private final Map<UnitDimension,Fraction> components; + + /** + * If this {@code UnitDimension} is a base dimension, its symbol (not to be confused with unit symbol). + * Otherwise (i.e. if this {@code UnitDimension} is a derived dimension), zero. + */ + private final char symbol; + + /** + * Creates a new base dimension with the given symbol, which shall not be zero. + * This constructor shall be invoked only during construction of {@link Units} constants. + * + * @param symbol the symbol of this base dimension (not to be confused with unit symbol). + */ + @SuppressWarnings("ThisEscapedInObjectConstruction") // Safe because this class is final. + UnitDimension(final char symbol) { + this.symbol = symbol; + components = Collections.singletonMap(this, new Fraction(1,1).unique()); + if (POOL.putIfAbsent(components, this) != null) { + throw new AssertionError(this); + } + } + + /** + * Creates a new derived dimension. This constructor shall never be invoked directly; + * use {@link #create(Map)} instead. + * + * @param components the product of base dimensions together with their power. + */ + private UnitDimension(final Map<UnitDimension,Fraction> components) { + this.components = components; + this.symbol = 0; + } + + /** + * Creates a new derived dimension from the given product of base dimensions with their power. + * This method returns a shared instance if possible. + * + * @param components the product of base dimensions together with their power. + */ + private static UnitDimension create(final Map<UnitDimension,Fraction> components) { + final Map<UnitDimension,Fraction> unmodifiable; // Unmodifiable view of 'components'. + final boolean share; + switch (components.size()) { + case 0: return NONE; + case 1: { + final Map.Entry<UnitDimension,Fraction> entry = components.entrySet().iterator().next(); + final UnitDimension base = entry.getKey(); + final Fraction power = entry.getValue(); + if (power.numerator == 1 && power.denominator == 1) { + return base; + } + unmodifiable = Collections.singletonMap(base, power.unique()); + share = false; // Because we already invoked 'unique()'. + break; + } + default: { + unmodifiable = Collections.unmodifiableMap(components); + share = true; + break; + } + } + return POOL.computeIfAbsent(unmodifiable, (key) -> { + if (share) { + components.replaceAll((c, power) -> power.unique()); + } + return new UnitDimension(key); + }); + } + + + /** + * Returns the (fundamental) base dimensions and their exponent whose product is this dimension, + * or null if this dimension is a base dimension. + */ + @Override + public Map<? extends Dimension, Integer> getBaseDimensions() { + if (symbol != 0) { + return null; + } + return ObjectConverters.derivedValues(components, UnitDimension.class, FractionConverter.INSTANCE); + } + + /** + * Returns the base dimensions and their exponents whose product make the given dimension. + * If the given dimension is a base dimension, then this method returns {@code this} raised + * to power 1. This method never returns {@code null}. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because the map is unmodifiable. + private static Map<? extends Dimension, Fraction> getBaseDimensions(final Dimension dimension) { + if (dimension instanceof UnitDimension) { + return ((UnitDimension) dimension).components; + } + /* + * Fallback for non-SIS implementations. The cast from <? extends Dimension> to <Dimension> + * is safe if we use the 'components' map as a read-only map (no put operation allowed). + */ + @SuppressWarnings("unchecked") + Map<Dimension,Integer> components = (Map<Dimension,Integer>) dimension.getBaseDimensions(); + if (components == null) { + return Collections.singletonMap(dimension, new Fraction(1,1)); + } + return ObjectConverters.derivedValues(components, Dimension.class, FractionConverter.FromInteger.INSTANCE); + } + + /** + * Returns the quotient of this dimension with the one specified. + * + * @param divisor the dimension by which to divide this dimension. + * @return {@code this} / {@code divisor} + */ + @Override + public Dimension divide(final Dimension divisor) { + return multiply(divisor, -1); + } + + /** + * Returns the product of this dimension with the one specified. + * + * @param multiplicand the dimension by which to multiply this dimension. + * @return {@code this} × {@code multiplicand} + */ + @Override + public Dimension multiply(final Dimension multiplicand) { + return multiply(multiplicand, +1); + } + + /** + * Returns the product of this dimension with the specified one raised to the given power. + * + * @param multiplicand the dimension by which to multiply this dimension. + * @param power the power at which to raise {@code multiplicand}. + * @return the product of this dimension by the given dimension raised to the given power. + */ + private Dimension multiply(final Dimension multiplicand, final int power) { + final BiFunction<Fraction, Fraction, Fraction> mapping = (sum, toAdd) -> { + if (power != 1) { + toAdd = new Fraction(toAdd.numerator * power, toAdd.denominator); + } + sum = sum.add(toAdd); + return (sum.numerator != 0) ? sum : null; + }; + final Map<UnitDimension,Fraction> product = new LinkedHashMap<>(components); + for (final Map.Entry<? extends Dimension, Fraction> entry : getBaseDimensions(multiplicand).entrySet()) { + final Dimension dim = entry.getKey(); + final Fraction p = entry.getValue(); + if (dim instanceof UnitDimension) { + product.merge((UnitDimension) dim, p, mapping); + } else if (p.numerator != 0) { + throw new UnsupportedImplementationException(Errors.format(Errors.Keys.UnsupportedImplementation_1, dim.getClass())); + } + } + return create(product); + } + + /** + * Returns this dimension raised to an exponent. + * + * @param n power to raise this dimension to (can be negative). + * @return {@code this}ⁿ + */ + private Dimension pow(final Fraction n) { + final Map<UnitDimension,Fraction> product = new LinkedHashMap<>(components); + product.replaceAll((dim, power) -> power.multiply(n)); + return create(product); + } + + /** + * Returns this dimension raised to an exponent. + * + * @param n power to raise this dimension to (can be negative). + * @return {@code this}ⁿ + */ + @Override + public Dimension pow(final int n) { + switch (n) { + case 0: return NONE; + case 1: return this; + default: return pow(new Fraction(n,1)); + } + } + + /** + * Returns the given root of this dimension. + * + * @param n the root's order. + * @return {@code this} raised to power 1/n. + */ + @Override + public Dimension root(final int n) { + switch (n) { + case 0: throw new ArithmeticException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "n", 0)); + case 1: return this; + default: return pow(new Fraction(1,n)); + } + } +} Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter?rev=1764996&r1=1764995&r2=1764996&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter [UTF-8] Fri Oct 14 22:47:14 2016 @@ -38,3 +38,5 @@ org.apache.sis.internal.converter.DateCo org.apache.sis.internal.converter.DateConverter$Timestamp org.apache.sis.internal.converter.CollectionConverter$List org.apache.sis.internal.converter.CollectionConverter$Set +org.apache.sis.internal.converter.FractionConverter +org.apache.sis.internal.converter.FractionConverter$FromInteger Added: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java?rev=1764996&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java (added) +++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java [UTF-8] Fri Oct 14 22:47:14 2016 @@ -0,0 +1,130 @@ +/* + * 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.math; + +import org.apache.sis.test.TestCase; +import org.junit.Test; + +import static org.apache.sis.test.Assert.*; + + +/** + * Tests the {@link Fraction} class. + * + * @author Martin Desruisseaux (Geomatys) + * @since 0.8 + * @version 0.8 + * @module + */ +public final strictfp class FractionTest extends TestCase { + /** + * Tests the {@link Fraction#floor()} method. + */ + @Test + public void testFloor() { + final int[] numerators = { 0, 1, 2, 3, 9, 10, 11, 12}; + final int[] denominators = { 3, 3, 3, 3, 3, 3, 3, 3}; + final int[] positives = { 0, 0, 0, 1, 3, 3, 3, 4}; + final int[] negatives = {-0, -1, -1, -1, -3, -4, -4, -4}; + for (int i=0; i<numerators.length; i++) { + for (int s=0; s<4; s++) { + int numerator = numerators [i]; + int denominator = denominators[i]; + if ((s & 1) != 0) numerator = -numerator; + if ((s & 2) != 0) denominator = -denominator; + final int[] expected = (numerator * denominator >= 0) ? positives : negatives; + final String label = "floor(" + numerator + '/' + denominator + ')'; + assertEquals(label, expected[i], new Fraction(numerator, denominator).floor()); + } + } + } + + /** + * Tests the {@link Fraction#ceil()} method. + */ + @Test + public void testCeil() { + final int[] numerators = { 0, 1, 2, 3, 9, 10, 11, 12}; + final int[] denominators = { 3, 3, 3, 3, 3, 3, 3, 3}; + final int[] positives = { 0, 1, 1, 1, 3, 4, 4, 4}; + final int[] negatives = {-0, -0, -0, -1, -3, -3, -3, -4}; + for (int i=0; i<numerators.length; i++) { + for (int s=0; s<4; s++) { + int numerator = numerators [i]; + int denominator = denominators[i]; + if ((s & 1) != 0) numerator = -numerator; + if ((s & 2) != 0) denominator = -denominator; + final int[] expected = (numerator * denominator >= 0) ? positives : negatives; + final String label = "ceil(" + numerator + '/' + denominator + ')'; + assertEquals(label, expected[i], new Fraction(numerator, denominator).ceil()); + } + } + } + + /** + * Tests the {@link Fraction#round()} method. + */ + @Test + public void testRoundFraction() { + final int[] numerators = { 0, 1, 2, 3, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19}; + final int[] denominators = { 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}; + final int[] results = { 0, 0, 1, 1, 3, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 5}; + for (int i=10; i<numerators.length; i++) { + for (int s=0; s<4; s++) { + int numerator = numerators [i]; + int denominator = denominators[i]; + int expected = results [i]; + if ((s & 1) != 0) numerator = -numerator; + if ((s & 2) != 0) denominator = -denominator; + if (numerator * denominator < 0) expected = -expected; + final String label = "even(" + numerator + '/' + denominator + ')'; + assertEquals(label, expected, new Fraction(numerator, denominator).round()); + } + } + } + + /** + * Tests the {@link Fraction#simplify()} method. + */ + @Test + public void testSimplify() { + Fraction fraction = new Fraction(4, 7).simplify(); + assertEquals(4, fraction.numerator); + assertEquals(7, fraction.denominator); + + fraction = new Fraction(4, 8).simplify(); + assertEquals(1, fraction.numerator); + assertEquals(2, fraction.denominator); + + fraction = new Fraction(48, 18).simplify(); + assertEquals(8, fraction.numerator); + assertEquals(3, fraction.denominator); + + fraction = new Fraction(17*21, 31*21).simplify(); + assertEquals(17, fraction.numerator); + assertEquals(31, fraction.denominator); + } + + /** + * Tests serialization. + */ + @Test + public void testSerialization() { + final Fraction local = new Fraction(5, 7); + assertNotSame(local, assertSerializedEquals(local)); + } +} Propchange: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1764996&r1=1764995&r2=1764996&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java [UTF-8] Fri Oct 14 22:47:14 2016 @@ -56,6 +56,7 @@ import org.junit.BeforeClass; org.apache.sis.util.logging.WarningListenersTest.class, org.apache.sis.util.logging.MonolineFormatterTest.class, org.apache.sis.util.logging.LoggerAdapterTest.class, + org.apache.sis.math.FractionTest.class, org.apache.sis.math.VectorTest.class, org.apache.sis.math.MathFunctionsTest.class, org.apache.sis.math.DecimalFunctionsTest.class,