Author: desruisseaux
Date: Sun Oct 16 15:52:03 2016
New Revision: 1765167

URL: http://svn.apache.org/viewvc?rev=1765167&view=rev
Log:
Initial implementation of UnitConverters, and partial implementation of 
SystemUnit.

Added:
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
   (with props)
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
   (with props)
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
   (with props)
Modified:
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java

Modified: 
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=1765167&r1=1765166&r2=1765167&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -113,7 +113,9 @@ public final class Fraction extends Numb
      * 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 == Long.MIN_VALUE || den == Long.MIN_VALUE) {
+            throw new ArithmeticException();
+        }
         if (num == 0) {
             den = Long.signum(den);             // Simplify  0/x  as  0/±1 or 
0/0.
         } else if (den == 0) {
@@ -416,21 +418,32 @@ public final class Fraction extends Numb
      */
     @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);
+        switch (denominator) {
+            case 0: {
+                if (numerator != 0) {
+                    return (numerator >= 0) ? "∞" : "−∞";
+                }
+                break;
+            }
+            case 1: {
+                return String.valueOf(numerator);
+            }
+            default: {
+                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);
+                            }
+                        }
                     }
                 }
+                break;
             }
         }
-        if (denominator == 0 && numerator != 0) {
-            return (numerator >= 0) ? "∞" : "−∞";
-        }
         return new 
StringBuilder().append(numerator).append('⁄').append(denominator).toString();
     }
 }

Modified: 
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=1765167&r1=1765166&r2=1765167&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -21,15 +21,30 @@ import java.util.Objects;
 import javax.measure.Unit;
 import javax.measure.Quantity;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.WeakHashSet;
 
 
 /**
- * Base class of all unit implementations. All unit instances shall be 
immutable.
+ * Base class of all unit implementations. There is conceptually 5 kinds of 
units,
+ * but some of them are implemented by the same class:
  *
- * <div class="section">Immutability and thread safety</div>
- * This base class is immutable and thus inherently thread-safe.
- * All subclasses shall be immutable too.
+ * <ul>
+ *   <li><b>Base units</b> are the 6 or 7 SI units used as building blocks for 
all other units.
+ *       The base units are metre, second, kilogram, Kelvin degrees, Ampere 
and Candela,
+ *       sometime with the addition of mole.</li>
+ *   <li><b>Derived units</b> are products of base units raised to some power.
+ *       For example "m/s" is a derived units.</li>
+ *   <li><b>Alternate units</b> are dimensionless units handled as if they had 
a dimension.
+ *       An example is angular degrees.</li>
+ *   <li><b>Scaled units</b> are units multiplied by a constant value compared 
to a base unit.
+ *       For example "km" is a scaled unit equals to 1000 metres.</li>
+ *   <li><b>Shifted units</b> are units shifted by a constant value compared 
to a base unit.
+ *       For example "°C" is a shifted unit equals to a value in degree Kelvin 
minus 273.15.</li>
+ * </ul>
+ *
+ * In Apache SIS implementation, base and derived units are represented by the 
same class: {@link SystemUnit}.
+ * All unit instances shall be immutable and thread-safe.
  *
  * @param  <Q>  the kind of quantity to be measured using this units.
  *
@@ -86,9 +101,15 @@ abstract class AbstractUnit<Q extends Qu
      * 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.
      *
+     * <div class="note"><b>Example:</b>
+     * {@link Units#METRE} has the {@code "m"} symbol and the same string 
representation.
+     * But {@link Units#METRES_PER_SECOND} has no symbol; it has only the 
{@code "m/s"}
+     * string representation.</div>
+     *
      * @return the unit symbol, or {@code null} if this unit has no specific 
symbol.
      *
      * @see #toString()
+     * @see UnitFormat
      */
     @Override
     public final String getSymbol() {
@@ -107,6 +128,16 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Returns the unscaled system unit from which this unit is derived.
+     * System units are either base units, {@linkplain #alternate(String) 
alternate}
+     * units or product of rational powers of system units.
+     *
+     * @return the system unit this unit is derived from, or {@code this} if 
this unit is a system unit.
+     */
+    @Override
+    public abstract SystemUnit<Q> getSystemUnit();
+
+    /**
      * Indicates if this unit is compatible with the given unit.
      * This implementation delegates to:
      *
@@ -126,6 +157,27 @@ abstract class AbstractUnit<Q extends Qu
     }
 
     /**
+     * Casts this unit to a parameterized unit of specified nature or throw a 
{@code ClassCastException}
+     * if the dimension of the specified quantity and this unit's dimension do 
not match.
+     *
+     * @param  <T>   the type of the quantity measured by the unit.
+     * @param  type  the quantity class identifying the nature of the unit.
+     * @return this unit parameterized with the specified type.
+     * @throws ClassCastException if the dimension of this unit is different 
from the specified quantity dimension.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public final <T extends Quantity<T>> Unit<T> asType(final Class<T> type) 
throws ClassCastException {
+        ArgumentChecks.ensureNonNull("type", type);
+        final Class<Quantity<Q>> quantity = getSystemUnit().quantity;
+        if (type.equals(quantity)) {
+            return (Unit<T>) this;
+        }
+        throw new 
ClassCastException(Errors.format(Errors.Keys.CanNotConvertFromType_2,
+                "Unit<" + quantity.getSimpleName() + '>', "Unit<" + 
type.getSimpleName() + '>'));
+    }
+
+    /**
      * 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.

Added: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java?rev=1765167&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
 (added)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -0,0 +1,152 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+import java.io.Serializable;
+import javax.measure.UnitConverter;
+
+
+/**
+ * The concatenation of two unit converters where at least one of them is not 
linear.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+final class ConcatenatedConverter implements UnitConverter, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 6506147355157815065L;
+
+    /**
+     * The first unit converter to apply.
+     */
+    private final UnitConverter c1;
+
+    /**
+     * The second unit converter to apply, after {@code c1}.
+     */
+    private final UnitConverter c2;
+
+    /**
+     * The inverse of this unit converter. Computed when first needed and 
stored in
+     * order to avoid rounding error if the user asks for the inverse of the 
inverse.
+     */
+    private transient ConcatenatedConverter inverse;
+
+    /**
+     * Creates a new concatenation of the given unit converters.
+     * Values will be converted according {@code c1} first, then {@code c2}.
+     */
+    ConcatenatedConverter(final UnitConverter c1, final UnitConverter c2) {
+        this.c1 = c1;
+        this.c2 = c2;
+    }
+
+    /**
+     * Returns {@code true} if the two unit converters are identity converters.
+     * Should always be {@code false}, otherwise we would not have created a 
{@code ConcatenatedConverter}.
+     */
+    @Override
+    public boolean isIdentity() {
+        return c1.isIdentity() && c2.isIdentity();
+    }
+
+    /**
+     * Returns {@code true} if the two unit converters are linear converters.
+     * Should always be {@code false}, otherwise we would not have created a 
{@code ConcatenatedConverter}.
+     */
+    @Override
+    public boolean isLinear() {
+        return c1.isLinear() && c2.isLinear();
+    }
+
+    /**
+     * Returns the inverse of this unit converter.
+     */
+    @Override
+    public synchronized UnitConverter inverse() {
+        if (inverse == null) {
+            inverse = new ConcatenatedConverter(c2.inverse(), c1.inverse());
+            inverse.inverse = this;
+        }
+        return inverse;
+    }
+
+    /**
+     * Applies the linear conversion on the given IEEE 754 floating-point 
value.
+     */
+    @Override
+    public double convert(final double value) {
+        return c2.convert(c1.convert(value));
+    }
+
+    /**
+     * Applies the linear conversion on the given value.
+     */
+    @Override
+    public Number convert(final Number value) {
+        return c2.convert(c1.convert(value));
+    }
+
+    /**
+     * Concatenates this converter with another converter. The resulting 
converter is equivalent to first converting
+     * by the specified converter (right converter), and then converting by 
this converter (left converter).
+     */
+    @Override
+    public UnitConverter concatenate(final UnitConverter converter) {
+        // Delegate to c1 and c2 because they may provide more intelligent 
'concatenate' implementations.
+        return c2.concatenate(c1.concatenate(converter));
+    }
+
+    /**
+     * Returns the steps of fundamental converters making up this converter.
+     * For example, {@code c1.getConversionSteps()} returns {@code c1} while
+     * {@code c1.concatenate(c2).getConversionSteps()} returns {@code c1, c2}.
+     */
+    @Override
+    public List<UnitConverter> getConversionSteps() {
+        final List<UnitConverter> converters = new ArrayList<>();
+        converters.addAll(c1.getConversionSteps());
+        converters.addAll(c2.getConversionSteps());
+        return converters;
+    }
+
+    /**
+     * Returns a hash code value for this unit converter.
+     */
+    @Override
+    public int hashCode() {
+        return (c1.hashCode() + 31 * c2.hashCode()) ^ (int) serialVersionUID;
+    }
+
+    /**
+     * Compares this converter with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof ConcatenatedConverter) {
+            final ConcatenatedConverter o = (ConcatenatedConverter) other;
+            return c1.equals(o.c1) && c2.equals(o.c2);
+        }
+        return false;
+    }
+}

Propchange: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConcatenatedConverter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java?rev=1765167&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
 (added)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -0,0 +1,233 @@
+/*
+ * 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.List;
+import java.util.Collections;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import javax.measure.UnitConverter;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.internal.util.Numerics;
+
+
+/**
+ * Conversions between units that can be represented by a linear operation 
(scale or offset).
+ * Note that the "linear" word in this class does not have the same meaning 
than the same word
+ * in the {@link #isLinear()} method inherited from JSR-363.
+ *
+ * <p><b>Implementation note:</b>
+ * for performance reason we should create specialized subtypes for the case 
where there is only a scale to apply,
+ * or only an offset, <i>etc.</i> But we don't do that in Apache SIS 
implementation because we will rarely use the
+ * {@code UnitConverter} for converting a lot of values. We rather use {@code 
MathTransform} for operations on
+ * <var>n</var>-dimensional tuples, and unit conversions are only a special 
case of those more generic operations.
+ * The {@code sis-referencing} module provided the specialized implementations 
needed for efficient operations
+ * and know how to copy the {@code UnitConverter} coefficients into an affine 
transform matrix.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+final class LinearConverter implements UnitConverter, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -3759983642723729926L;
+
+    /**
+     * The identity linear converter.
+     */
+    private static final LinearConverter IDENTITY = new LinearConverter(1, 0);
+
+    /**
+     * The scale to apply for converting values.
+     */
+    private final double scale;
+
+    /**
+     * The offset to apply after the scale.
+     */
+    private final double offset;
+
+    /**
+     * The inverse of this unit converter. Computed when first needed and 
stored in
+     * order to avoid rounding error if the user asks for the inverse of the 
inverse.
+     */
+    private transient LinearConverter inverse;
+
+    /**
+     * Creates a new linear converter for the given scale and offset.
+     */
+    private LinearConverter(final double scale, final double offset) {
+        this.scale  = scale;
+        this.offset = offset;
+    }
+
+    /**
+     * Returns a linear converter for the given scale and offset.
+     */
+    static LinearConverter create(final double scale, final double offset) {
+        if (offset == 0) {
+            if (scale == 1) return IDENTITY;
+        }
+        return new LinearConverter(scale, offset);
+    }
+
+    /**
+     * Indicates if this converter is linear.
+     * JSR-363 defines a converter as linear if:
+     *
+     * <ul>
+     *   <li>{@code convert(u + v) == convert(u) + convert(v)}</li>
+     *   <li>{@code convert(r * u) == r * convert(u)}</li>
+     * </ul>
+     *
+     * Note that this definition allows scale factors but does not allow 
offsets.
+     * Consequently this is a different definition of "linear" than this class 
and the rest of Apache SIS.
+     */
+    @Override
+    public boolean isLinear() {
+        return offset == 0;
+    }
+
+    /**
+     * Returns {@code true} if the scale is 1 and the offset is zero.
+     */
+    @Override
+    public boolean isIdentity() {
+        return scale == 1 && offset == 0;
+    }
+
+    /**
+     * Returns the inverse of this unit converter.
+     */
+    @Override
+    public synchronized UnitConverter inverse() {
+        if (inverse == null) {
+            inverse = new LinearConverter(1/scale, -offset/scale);
+            inverse.inverse = this;
+        }
+        return inverse;
+    }
+
+    /**
+     * Applies the linear conversion on the given IEEE 754 floating-point 
value.
+     */
+    @Override
+    public double convert(final double value) {
+        return value * scale + offset;
+    }
+
+    /**
+     * Applies the linear conversion on the given value. This method uses 
{@link BigDecimal} arithmetic if
+     * the given value is an instance of {@code BigDecimal}, or IEEE 754 
floating-point arithmetic otherwise.
+     *
+     * <p>This method is inefficient. Apache SIS rarely uses {@link 
BigDecimal} arithmetic, so providing an
+     * efficient implementation of this method is currently not a goal (this 
decision may be revisited in a
+     * future SIS version if the need for {@code BigDecimal} arithmetic 
increase).</p>
+     */
+    @Override
+    public Number convert(Number value) {
+        if (value instanceof BigDecimal) {
+            if (scale != 1) {
+                value = ((BigDecimal) 
value).multiply(BigDecimal.valueOf(scale));
+            }
+            if (offset != 0) {
+                value = ((BigDecimal) value).add(BigDecimal.valueOf(offset));
+            }
+        } else if (!isIdentity()) {
+            value = convert(value.doubleValue());
+        }
+        return value;
+    }
+
+    /**
+     * Concatenates this converter with another converter. The resulting 
converter is equivalent to first converting
+     * by the specified converter (right converter), and then converting by 
this converter (left converter).
+     */
+    @Override
+    public UnitConverter concatenate(final UnitConverter converter) {
+        ArgumentChecks.ensureNonNull("converter", converter);
+        if (converter.isIdentity()) {
+            return this;
+        }
+        if (isIdentity()) {
+            return converter;
+        }
+        final double otherScale, otherOffset;
+        if (converter instanceof LinearConverter) {
+            otherScale  = ((LinearConverter) converter).scale;
+            otherOffset = ((LinearConverter) converter).offset;
+        } else if (converter.isLinear()) {
+            /*
+             * Fallback for foreigner implementations. Note that 'otherOffset' 
should be restricted to zero
+             * according JSR-363 definition of 'isLinear()', but let be safe; 
maybe we are not the only one
+             * to have a different interpretation about the meaning of 
"linear".
+             */
+            otherOffset = converter.convert(0.0);
+            otherScale  = converter.convert(1.0) - otherOffset;
+        } else {
+            return new ConcatenatedConverter(converter, this);
+        }
+        return create(otherScale * scale, otherOffset * scale + offset);
+    }
+
+    /**
+     * Returns the steps of fundamental converters making up this converter, 
which is only {@code this}.
+     */
+    @Override
+    public List<LinearConverter> getConversionSteps() {
+        return Collections.singletonList(this);
+    }
+
+    /**
+     * Returns a hash code value for this unit converter.
+     */
+    @Override
+    public int hashCode() {
+        return Numerics.hashCode((Double.doubleToLongBits(scale) + 
31*Double.doubleToLongBits(offset)) ^ serialVersionUID);
+    }
+
+    /**
+     * Compares this converter with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof LinearConverter) {
+            final LinearConverter o = (LinearConverter) other;
+            return Numerics.equals(scale, o.scale) && Numerics.equals(offset, 
o.offset);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation of this converter.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder buffer = new StringBuilder().append("\uD835\uDC66 
= ");
+        if (scale != 1) {
+            buffer.append(scale).append('⋅');
+        }
+        buffer.append("\uD835\uDC65");
+        if (offset != 0) {
+            buffer.append(" + ").append(offset);
+        }
+        return buffer.toString();
+    }
+}

Propchange: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java?rev=1765167&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
 (added)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -0,0 +1,164 @@
+/*
+ * 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.Map;
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+import javax.measure.Unit;
+import javax.measure.Quantity;
+import javax.measure.Dimension;
+import org.apache.sis.util.ObjectConverters;
+import org.apache.sis.internal.converter.SurjectiveConverter;
+
+
+/**
+ * Implementation of base, alternate and derived units (see {@link 
AbstractUnit} for a description of unit kinds).
+ * A {@code SystemUnit} is a base or alternate unit if associated to a base 
{@link UnitDimension}, or is a derived
+ * units otherwise. No other type is allowed since {@code SystemUnit} is 
always a combination of fundamental units
+ * without scale factor or offset.
+ *
+ * @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 SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> {
+    /**
+     * The type of quantity that uses this unit.
+     */
+    final Class<Quantity<Q>> quantity;
+
+    /**
+     * The dimension of this unit of measurement.
+     */
+    final UnitDimension dimension;
+
+    /**
+     * Creates a new unit having the given symbol and EPSG code.
+     *
+     * @param  dimension  the unit dimension.
+     * @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.
+     */
+    SystemUnit(final Class<Quantity<Q>> quantity, final UnitDimension 
dimension,
+            final String name, final String symbol, final short epsg)
+    {
+        super(name, symbol, epsg);
+        this.quantity  = quantity;
+        this.dimension = dimension;
+    }
+
+    /**
+     * Returns the dimension of this unit.
+     * Two units {@code u1} and {@code u2} are {@linkplain #isCompatible(Unit) 
compatible}
+     * if and only if {@code u1.getDimension().equals(u2.getDimension())}.
+     *
+     * @return the dimension of this unit.
+     *
+     * @see #isCompatible(Unit)
+     */
+    @Override
+    public Dimension getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Returns the unscaled system unit from which this unit is derived.
+     * Since this unit is already a base, alternate or derived unit, this 
method returns {@code true}.
+     *
+     * @return {@code this}
+     */
+    @Override
+    public SystemUnit<Q> getSystemUnit() {
+        return this;
+    }
+
+    /**
+     * Returns the base units and their exponent whose product is this unit,
+     * or {@code null} if this unit is a base unit (not a product of existing 
units).
+     *
+     * @return the base units and their exponent making up this unit.
+     */
+    @Override
+    public Map<SystemUnit<?>, Integer> getBaseUnits() {
+        final Map<UnitDimension,Integer> dim = dimension.getBaseDimensions();
+        if (dim == null) {
+            return null;            // This unit is associated to a base 
dimension.
+        }
+        return ObjectConverters.derivedKeys(dim, DimToUnit.INSTANCE, 
Integer.class);
+    }
+
+    /**
+     * The converter for replacing the keys in the {@link 
SystemUnit#getBaseUnits()} map from {@link UnitDimension}
+     * instances to {@link SystemUnit} instances. We apply conversions on the 
fly instead than extracting the data in
+     * a new map once for all because the copy may fail if an entry contains a 
rational instead than an integer power.
+     * With on-the-fly conversions, the operation will not fail if the user 
never ask for that particular value.
+     */
+    private static final class DimToUnit extends 
SurjectiveConverter<UnitDimension, SystemUnit<?>> implements Serializable {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 7545067577687885675L;
+
+        /**
+         * The unique instance used by {@link SystemUnit#getBaseUnits()}.
+         */
+        static final DimToUnit INSTANCE = new DimToUnit();
+
+        /**
+         * Constructor for the singleton {@link #INSTANCE}.
+         */
+        private DimToUnit() {
+        }
+
+        /**
+         * Returns the type of key values in the map returned by {@link 
UnitDimension#getBaseDimensions()}.
+         */
+        @Override
+        public Class<UnitDimension> getSourceClass() {
+            return UnitDimension.class;
+        }
+
+        /**
+         * Returns the type of key values in the map to be returned by {@link 
SystemUnit#getBaseUnits()}.
+         */
+        @Override
+        @SuppressWarnings("unchecked")
+        public Class<SystemUnit<?>> getTargetClass() {
+            return (Class) SystemUnit.class;
+        }
+
+        /**
+         * Returns the unit associated to the given dimension, or {@code null} 
if none.
+         */
+        @Override
+        public SystemUnit<?> apply(final UnitDimension dim) {
+            return Units.get(dim);
+        }
+
+        /**
+         * Invoked on deserialization for replacing the deserialized instance 
by the unique instance.
+         */
+        Object readResolve() throws ObjectStreamException {
+            return INSTANCE;
+        }
+    }
+}

Propchange: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: 
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=1765167&r1=1765166&r2=1765167&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -22,13 +22,16 @@ import java.util.LinkedHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiFunction;
+import java.io.ObjectStreamException;
 import java.io.Serializable;
 import javax.measure.Dimension;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.util.Characters;
 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;
+import org.apache.sis.internal.util.CollectionsExt;
 
 
 /**
@@ -51,6 +54,8 @@ import org.apache.sis.internal.converter
  *       as T^2.5 / (M⋅L) dimension.</li>
  * </ul>
  *
+ * All {@code UnitDimension} instances are immutable and thus inherently 
thread-safe.
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
  * @version 0.8
@@ -123,9 +128,7 @@ final class UnitDimension implements Dim
      *
      * @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;
+    private static UnitDimension create(Map<UnitDimension,Fraction> 
components) {
         switch (components.size()) {
             case 0: return NONE;
             case 1: {
@@ -135,31 +138,56 @@ final class UnitDimension implements Dim
                 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());
+        /*
+         * Implementation note: following code duplicates the functionality of 
Map.computeIfAbsent(…),
+         * but we had to do it because we compute not only the value, but also 
the 'components' key.
+         */
+        UnitDimension dim = POOL.get(components);
+        if (dim == null) {
+            shareFractionInstances(components);
+            components = CollectionsExt.unmodifiableOrCopy(components);
+            dim = new UnitDimension(components);
+            final UnitDimension c = POOL.putIfAbsent(components, dim);
+            if (c != null) {
+                return c;       // UnitDimension created concurrently in 
another thread.
             }
-            return new UnitDimension(key);
-        });
+        }
+        return dim;
+    }
+
+    /**
+     * Replaces the {@link Fraction} values in the given map by unique 
instances.
+     * This is a micro-optimisation saving a little bit of memory and 
performance
+     * in the common case where all dimensions share a small set of power 
values.
+     */
+    private static void shareFractionInstances(final 
Map<UnitDimension,Fraction> components) {
+        components.replaceAll((c, power) -> power.unique());
     }
 
+    /**
+     * Invoked on deserialization for returning a unique instance of {@code 
UnitDimension}.
+     */
+    Object readResolve() throws ObjectStreamException {
+        UnitDimension dim = POOL.get(components);
+        if (dim == null) {
+            shareFractionInstances(components);
+            dim = POOL.putIfAbsent(components, this);
+            if (dim == null) {
+                return this;
+            }
+        }
+        return dim;
+    }
 
     /**
      * 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() {
+    public Map<UnitDimension,Integer> getBaseDimensions() {
         if (symbol != 0) {
             return null;
         }
@@ -189,44 +217,43 @@ final class UnitDimension implements Dim
     }
 
     /**
-     * Returns the quotient of this dimension with the one specified.
+     * Returns the product of this dimension with the one specified.
      *
-     * @param  divisor  the dimension by which to divide this dimension.
-     * @return {@code this} / {@code divisor}
+     * @param  multiplicand  the dimension by which to multiply this dimension.
+     * @return {@code this} × {@code multiplicand}
      */
     @Override
-    public Dimension divide(final Dimension divisor) {
-        return multiply(divisor, -1);
+    public Dimension multiply(final Dimension multiplicand) {
+        return combine(multiplicand, (sum, toAdd) -> {
+            sum = sum.add(toAdd);
+            return (sum.numerator != 0) ? sum : null;
+        });
     }
 
     /**
-     * Returns the product of this dimension with the one specified.
+     * Returns the quotient of this dimension with the one specified.
      *
-     * @param  multiplicand  the dimension by which to multiply this dimension.
-     * @return {@code this} × {@code multiplicand}
+     * @param  divisor  the dimension by which to divide this dimension.
+     * @return {@code this} ∕ {@code divisor}
      */
     @Override
-    public Dimension multiply(final Dimension multiplicand) {
-        return multiply(multiplicand, +1);
+    public Dimension divide(final Dimension divisor) {
+        return combine(divisor, (sum, toRemove) -> {
+            sum = sum.subtract(toRemove);
+            return (sum.numerator != 0) ? sum : null;
+        });
     }
 
     /**
-     * Returns the product of this dimension with the specified one raised to 
the given power.
+     * Returns the product or the quotient of this dimension with the 
specified one.
      *
-     * @param  multiplicand  the dimension by which to multiply this dimension.
-     * @param  power         the power at which to raise {@code multiplicand}.
+     * @param  other   the dimension by which to multiply or divide this 
dimension.
+     * @param  mapping the operation to apply between the powers of {@code 
this} and {@code other} dimensions.
      * @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;
-        };
+    private Dimension combine(final Dimension other, final 
BiFunction<Fraction, Fraction, Fraction> mapping) {
         final Map<UnitDimension,Fraction> product = new 
LinkedHashMap<>(components);
-        for (final Map.Entry<? extends Dimension, Fraction> entry : 
getBaseDimensions(multiplicand).entrySet()) {
+        for (final Map.Entry<? extends Dimension, Fraction> entry : 
getBaseDimensions(other).entrySet()) {
             final Dimension dim = entry.getKey();
             final Fraction p = entry.getValue();
             if (dim instanceof UnitDimension) {
@@ -279,4 +306,58 @@ final class UnitDimension implements Dim
             default: return pow(new Fraction(1,n));
         }
     }
+
+    /**
+     * Compares this dimension with the given object for equality.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (other instanceof UnitDimension) {
+            final UnitDimension that = (UnitDimension) other;
+            if (symbol == that.symbol) {
+                /*
+                 * Do not compare 'components' if 'symbols' is non-zero 
because in such case
+                 * the components map contains 'this', which would cause an 
infinite loop.
+                 */
+                return (symbol == 0) || components.equals(that.components);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this dimension.
+     */
+    @Override
+    public int hashCode() {
+        /*
+         * Do not use 'components' in hash code calculation if 'symbols' is 
non-zero
+         * beause in such case the map contains 'this', which would cause an 
infinite loop.
+         */
+        return (symbol != 0) ? symbol ^ (int) serialVersionUID : 
components.hashCode();
+    }
+
+    /**
+     * Returns a string representation of this dimension.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder buffer = new StringBuilder(8);
+        for (final Map.Entry<UnitDimension,Fraction> c : 
components.entrySet()) {
+            buffer.append(c.getKey().symbol);
+            final Fraction power = c.getValue();
+            if (power.denominator == 1) {
+                final int n = power.numerator;
+                if (n >= 0 && n <= 9) {
+                    buffer.append(Characters.toSuperScript((char) (n + '0')));
+                    continue;
+                }
+            }
+            buffer.append("^(").append(power).append(')');
+        }
+        return buffer.toString();
+    }
 }

Modified: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java?rev=1765167&r1=1765166&r2=1765167&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.measure;
 
+import java.util.Map;
+import java.util.HashMap;
+import javax.measure.Dimension;
 import javax.measure.Unit;
 import javax.measure.UnitConverter;
 import javax.measure.format.ParserException;
@@ -55,6 +58,30 @@ import static org.apache.sis.measure.Sex
  */
 public final class Units extends Static {
     /**
+     * The units for given {@link UnitDimension} instances. This map contains 
mostly SI units (no imperial units)
+     * with the addition of some alternative units. This map must be 
unmodified after it has been populated.
+     */
+    private static final Map<UnitDimension, SystemUnit<?>> SYSTEM = new 
HashMap<>();
+
+    /**
+     * Invoked by {@code Units} static class initializer for registering SI 
base and derived units.
+     * We do not synchronize that method on the assumption that {@link 
#SYSTEM} map will be fully
+     * populated in a single thread by the {@code Units} class initializer, 
then never modified.
+     */
+    private static void add(final SystemUnit<?> unit) {
+        if (SYSTEM.put(unit.dimension, unit) != null) {
+            throw new AssertionError();                 // Shall not map the 
same dimension twice.
+        }
+    }
+
+    /**
+     * Returns the unit for the given dimension, or {@code null} if none.
+     */
+    static SystemUnit<?> get(final Dimension dim) {
+        return SYSTEM.get(dim);
+    }
+
+    /**
      * The SI base unit for distances (symbol “m”).
      *
      * @see #NANOMETRE

Modified: 
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=1765167&r1=1765166&r2=1765167&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
 [UTF-8] Sun Oct 16 15:52:03 2016
@@ -120,6 +120,21 @@ public final strictfp class FractionTest
     }
 
     /**
+     * Tests the {@link Fraction#toString()} method.
+     */
+    @Test
+    public void testToString() {
+        assertEquals("3",   new Fraction(3, 1).toString());
+        assertEquals("¼",   new Fraction(1, 4).toString());
+        assertEquals("⅜",   new Fraction(3, 8).toString());
+        assertEquals("⅚",   new Fraction(5, 6).toString());
+        assertEquals("3⁄7", new Fraction(3, 7).toString());
+        assertEquals("⅞",   new Fraction(7, 8).toString());
+        assertEquals("4⁄8", new Fraction(4, 8).toString());
+        assertEquals("∞",   new Fraction(3, 0).toString());
+    }
+
+    /**
      * Tests serialization.
      */
     @Test



Reply via email to