Author: desruisseaux Date: Mon Oct 17 21:28:04 2016 New Revision: 1765374 URL: http://svn.apache.org/viewvc?rev=1765374&view=rev Log: First implementation of UnitFormat.format(...) method in replacement of the reference implementation. Begin tests.
Added: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.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/ConventionalUnit.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.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/UnitFormat.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/test/suite/UtilityTestSuite.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=1765374&r1=1765373&r2=1765374&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] Mon Oct 17 21:28:04 2016 @@ -154,6 +154,17 @@ public final class Fraction extends Numb } /** + * Returns the negative value of this fraction. + * This method does not simplify the fraction. + * + * @return the result of {@code -this}. + * @throws ArithmeticException if the result overflows. + */ + public Fraction negate() { + return (numerator == 0) ? this : new Fraction(Math.negateExact(numerator), denominator); + } + + /** * Returns the simplified result of adding the given fraction to this fraction. * * @param other the fraction to add to this fraction. 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=1765374&r1=1765373&r2=1765374&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] Mon Oct 17 21:28:04 2016 @@ -16,10 +16,12 @@ */ package org.apache.sis.measure; +import java.util.Map; import java.util.Objects; import java.io.Serializable; import javax.measure.Unit; import javax.measure.Quantity; +import org.apache.sis.math.Fraction; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; @@ -135,6 +137,12 @@ abstract class AbstractUnit<Q extends Qu public abstract SystemUnit<Q> getSystemUnit(); /** + * Returns the base units used by Apache SIS implementations. + * Contrarily to {@link #getBaseUnits()}, this method never returns {@code null}. + */ + abstract Map<SystemUnit<?>, Fraction> getBaseSystemUnits(); + + /** * Indicates if this unit is compatible with the given unit. * This implementation delegates to: * Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java?rev=1765374&r1=1765373&r2=1765374&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java [UTF-8] Mon Oct 17 21:28:04 2016 @@ -25,6 +25,7 @@ import javax.measure.UnconvertibleExcept import javax.measure.IncommensurableException; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.math.Fraction; /** @@ -114,6 +115,15 @@ final class ConventionalUnit<Q extends Q } /** + * Returns the base units used by Apache SIS implementations. + * Contrarily to {@link #getBaseUnits()}, this method never returns {@code null}. + */ + @Override + final Map<SystemUnit<?>, Fraction> getBaseSystemUnits() { + return target.getBaseSystemUnits(); + } + + /** * 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. * Modified: 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=1765374&r1=1765373&r2=1765374&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java [UTF-8] Mon Oct 17 21:28:04 2016 @@ -30,6 +30,7 @@ import org.apache.sis.util.ArgumentCheck import org.apache.sis.util.ObjectConverters; import org.apache.sis.util.resources.Errors; import org.apache.sis.internal.converter.SurjectiveConverter; +import org.apache.sis.math.Fraction; /** @@ -133,6 +134,15 @@ final class SystemUnit<Q extends Quantit } /** + * Returns the base units used by Apache SIS implementations. + * Contrarily to {@link #getBaseUnits()}, this method never returns {@code null}. + */ + @Override + final Map<SystemUnit<?>, Fraction> getBaseSystemUnits() { + return ObjectConverters.derivedKeys(dimension.components, DimToUnit.INSTANCE, Fraction.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. 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=1765374&r1=1765373&r2=1765374&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] Mon Oct 17 21:28:04 2016 @@ -22,11 +22,11 @@ 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 java.io.IOException; +import java.io.ObjectStreamException; 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; @@ -88,13 +88,13 @@ final class UnitDimension implements Dim * * @see #getBaseDimensions() */ - private final Map<UnitDimension,Fraction> components; + 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; + final char symbol; /** * Creates a new base dimension with the given symbol, which shall not be zero. @@ -338,17 +338,10 @@ final class UnitDimension implements Dim @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(')'); + try { + UnitFormat.formatComponents(components, buffer); + } catch (IOException e) { + throw new AssertionError(e); // Should never happen since we are writting to a StringBuilder. } return buffer.toString(); } Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java?rev=1765374&r1=1765373&r2=1765374&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java [UTF-8] Mon Oct 17 21:28:04 2016 @@ -16,19 +16,25 @@ */ package org.apache.sis.measure; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; import java.util.Locale; import java.text.Format; import java.text.FieldPosition; import java.text.ParsePosition; import java.io.IOException; -import java.io.UncheckedIOException; +import javax.measure.Dimension; import javax.measure.Unit; import javax.measure.format.ParserException; import org.apache.sis.internal.util.Constants; import org.apache.sis.internal.util.DefinitionURI; import org.apache.sis.internal.util.XPaths; +import org.apache.sis.math.Fraction; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.CharSequences; +import org.apache.sis.util.Characters; import org.apache.sis.util.Exceptions; import org.apache.sis.util.Localized; import org.apache.sis.util.resources.Errors; @@ -76,11 +82,9 @@ public class UnitFormat extends Format i * The default instance used by {@link Units#valueOf(String)} for parsing units of measurement. */ static final UnitFormat INSTANCE = new UnitFormat(); - - /** - * Temporary helper class before we replace by our own implementation. - */ - private final javax.measure.format.UnitFormat delegate = tec.units.ri.format.SimpleUnitFormat.getInstance(); + static { + INSTANCE.longName = false; + } /** * The locale specified at construction time. @@ -94,6 +98,12 @@ public class UnitFormat extends Format i private boolean isLocaleUS; /** + * Whether this {@code UnitFormat} should format long names like "metre". + * If {@code false}, then this instance will format only unit symbols. + */ + private boolean longName = true; + + /** * Creates a new format for the given locale. * * @param locale the locale to use for parsing and formatting units. @@ -152,7 +162,7 @@ public class UnitFormat extends Format i */ @Override public void label(final Unit<?> unit, final String label) { - delegate.label(unit, label); + // TODO } /** @@ -165,27 +175,139 @@ public class UnitFormat extends Format i */ @Override public Appendable format(final Unit<?> unit, final Appendable toAppendTo) throws IOException { - final String symbol = org.apache.sis.internal.util.PatchedUnitFormat.getSymbol(unit); + String symbol = org.apache.sis.internal.util.PatchedUnitFormat.getSymbol(unit); if (symbol != null) { return toAppendTo.append(symbol); } - /* - * Following are specific to the WKT format, which is currently the only user of this method. - * If we invoke this method for other purposes, then we would need to provide more control on - * what kind of formatting is desired. - */ - if (Units.ONE.equals(unit)) { - return toAppendTo.append("unity"); - } else if (Units.DEGREE.equals(unit)) { - return toAppendTo.append("degree"); - } else if (Units.METRE.equals(unit)) { - return toAppendTo.append(isLocaleUS ? "meter" : "metre"); - } else if (Units.FOOT_SURVEY_US.equals(unit)) { - return toAppendTo.append("US survey foot"); - } else if (Units.PPM.equals(unit)) { - return toAppendTo.append("parts per million"); + if (longName) { + /* + * Following are specific to the WKT format, which is currently the only user of this method. + * If we invoke this method for other purposes, then we would need to provide more control on + * what kind of formatting is desired. + */ + if (Units.ONE.equals(unit)) { + return toAppendTo.append("unity"); + } else if (Units.DEGREE.equals(unit)) { + return toAppendTo.append("degree"); + } else if (Units.METRE.equals(unit)) { + return toAppendTo.append(isLocaleUS ? "meter" : "metre"); + } else if (Units.FOOT_SURVEY_US.equals(unit)) { + return toAppendTo.append("US survey foot"); + } else if (Units.PPM.equals(unit)) { + return toAppendTo.append("parts per million"); + } + } + symbol = unit.getSymbol(); + if (symbol != null) { + return toAppendTo.append(symbol); + } + Map<? extends Unit<?>, ? extends Number> components; + if (unit instanceof AbstractUnit<?>) { + components = ((AbstractUnit<?>) unit).getBaseSystemUnits(); + } else { + // Fallback for foreigner implementations. + components = unit.getBaseUnits(); + if (components == null) { + components = Collections.singletonMap(unit, 1); + } + } + formatComponents(components, toAppendTo); + return toAppendTo; + } + + /** + * Creates a new symbol (e.g. "m/s") from the given symbols and factors. + * Keys in the given map can be either {@link Unit} or {@link Dimension} instances. + */ + static void formatComponents(final Map<?, ? extends Number> components, final Appendable toAppendTo) throws IOException { + boolean isFirst = true; + final List<Map.Entry<?,? extends Number>> deferred = new ArrayList<>(components.size()); + for (final Map.Entry<?,? extends Number> entry : components.entrySet()) { + final Number power = entry.getValue(); + final int n = (power instanceof Fraction) ? ((Fraction) power).numerator : power.intValue(); + if (n > 0) { + if (!isFirst) { + toAppendTo.append('⋅'); + } + isFirst = false; + formatComponent(entry, false, toAppendTo); + } else if (n != 0) { + deferred.add(entry); + } + } + if (!isFirst && deferred.size() == 1) { + formatComponent(deferred.get(0), true, toAppendTo.append('∕')); + } else { + for (final Map.Entry<?,? extends Number> entry : deferred) { + if (!isFirst) { + toAppendTo.append('⋅'); + } + isFirst = false; + formatComponent(entry, false, toAppendTo); + } + } + } + + /** + * Formats a single unit or dimension raised to the given power. + * + * @param entry the base unit or base dimension to format, together with its power. + * @param inverse {@code true} for inverting the power sign. + */ + private static void formatComponent(final Map.Entry<?,? extends Number> entry, + final boolean inverse, final Appendable toAppendTo) throws IOException + { + formatSymbol(entry.getKey(), toAppendTo); + final Number power = entry.getValue(); + int n; + if (power instanceof Fraction) { + Fraction f = (Fraction) power; + if (f.denominator != 1) { + if (inverse) { + f = f.negate(); + } + final String t = f.toString(); + if (t.length() == 1) { + toAppendTo.append('^').append(t); + } else { + toAppendTo.append("^(").append(t).append(')'); + } + } + n = f.numerator; + } else { + n = power.intValue(); + } + if (inverse) n = -n; + if (n != 1) { + final String t = String.valueOf(n); + for (int i=0; i<t.length(); i++) { + toAppendTo.append(Characters.toSuperScript(t.charAt(i))); + } + } + } + + /** + * Appends the symbol for the given base unit of base dimension, or "?" if no symbol was found. + * + * @param base the base unit or base dimension to format. + * @param toAppendTo where to append the symbol. + */ + private static void formatSymbol(final Object base, final Appendable toAppendTo) throws IOException { + if (base instanceof UnitDimension) { + final char symbol = ((UnitDimension) base).symbol; + if (symbol != 0) { + toAppendTo.append(symbol); + return; + } + } + if (base instanceof Unit<?>) { + final String symbol = ((Unit<?>) base).getSymbol(); + if (symbol != null) { + toAppendTo.append(symbol); + return; + } } - return delegate.format(unit, toAppendTo); + toAppendTo.append('?'); } /** @@ -201,7 +323,7 @@ public class UnitFormat extends Format i try { return (StringBuffer) format((Unit<?>) unit, toAppendTo); } catch (IOException e) { - throw new UncheckedIOException(e); // Should never happen since we are writting to a StringBuffer. + throw new AssertionError(e); // Should never happen since we are writting to a StringBuffer. } } @@ -216,7 +338,7 @@ public class UnitFormat extends Format i try { return format(unit, new StringBuilder()).toString(); } catch (IOException e) { - throw new UncheckedIOException(e); // Should never happen since we are writting to a StringBuilder. + throw new AssertionError(e); // Should never happen since we are writting to a StringBuilder. } } @@ -313,7 +435,7 @@ public class UnitFormat extends Format i } final Unit<?> unit; try { - unit = delegate.parse(symbols); + unit = tec.units.ri.format.SimpleUnitFormat.getInstance().parse(symbols); } catch (ParserException e) { // Provides a better error message than the default JSR-275 0.9.4 implementation. throw Exceptions.setMessage(e, Errors.format(Errors.Keys.IllegalArgumentValue_2, "uom", uom), true); 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=1765374&r1=1765373&r2=1765374&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] Mon Oct 17 21:28:04 2016 @@ -504,7 +504,7 @@ public final class Units extends Static /** * Dimensionless unit for pixels. */ - public static final Unit<Dimensionless> PIXEL = ONE.alternate("pixel"); + public static final Unit<Dimensionless> PIXEL = ONE.alternate("px"); static { final javax.measure.format.UnitFormat format = tec.units.ri.format.SimpleUnitFormat.getInstance(); Added: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java?rev=1765374&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java (added) +++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java [UTF-8] Mon Oct 17 21:28:04 2016 @@ -0,0 +1,66 @@ +/* + * 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 org.apache.sis.test.TestCase; +import org.junit.Test; + +import static org.junit.Assert.*; + + +/** + * Tests the {@link UnitFormat} class. + * + * @author Martin Desruisseaux (Geomatys) + * @since 0.8 + * @version 0.8 + * @module + */ +public final strictfp class UnitFormatTest extends TestCase { + /** + * Tests {@link UnitFormat#format(Object)} using the system-wide instance. + */ + @Test + public void testFormat() { + final UnitFormat f = UnitFormat.INSTANCE; + assertEquals("m", f.format(Units.METRE)); + assertEquals("rad", f.format(Units.RADIAN)); + assertEquals("s", f.format(Units.SECOND)); + assertEquals("min", f.format(Units.MINUTE)); + assertEquals("h", f.format(Units.HOUR)); + assertEquals("Hz", f.format(Units.HERTZ)); + assertEquals("Pa", f.format(Units.PASCAL)); + assertEquals("kg", f.format(Units.KILOGRAM)); + assertEquals("N", f.format(Units.NEWTON)); + assertEquals("J", f.format(Units.JOULE)); + assertEquals("W", f.format(Units.WATT)); + assertEquals("K", f.format(Units.KELVIN)); +// assertEquals("m∕s", f.format(Units.METRES_PER_SECOND)); +// assertEquals("km∕h", f.format(Units.KILOMETRES_PER_HOUR)); +// assertEquals("m²", f.format(Units.SQUARE_METRE)); +// assertEquals("m³", f.format(Units.CUBIC_METRE)); + assertEquals("", f.format(Units.ONE)); +// assertEquals("%", f.format(Units.PERCENT)); + assertEquals("psu", f.format(Units.PSU)); + assertEquals("px", f.format(Units.PIXEL)); + + // Following symbol needs revision. +// assertEquals("°C", f.format(Units.CELSIUS)); + assertEquals("d", f.format(Units.DAY)); +// assertEquals("?", f.format(Units.WEEK)); + } +} Propchange: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.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=1765374&r1=1765373&r2=1765374&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] Mon Oct 17 21:28:04 2016 @@ -103,6 +103,7 @@ import org.junit.BeforeClass; // Measurements and formatting. org.apache.sis.measure.SexagesimalConverterTest.class, + org.apache.sis.measure.UnitFormatTest.class, org.apache.sis.measure.UnitsTest.class, org.apache.sis.measure.RangeTest.class, org.apache.sis.measure.DateRangeTest.class,