sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1401236 [1/2] - in /sis/branches/JDK7/sis-utility/src: main/java/org/apache/sis/math/ main/java/org/apache/sis/measure/ main/java/org/apache/sis/util/ main/java/org/apache/sis/util/logging/ main/java/org/apache/sis/util/resources/ test/jav...
Date Tue, 23 Oct 2012 10:11:35 GMT
Author: desruisseaux
Date: Tue Oct 23 10:11:34 2012
New Revision: 1401236

URL: http://svn.apache.org/viewvc?rev=1401236&view=rev
Log:
Ported the AngleFormat class.

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Localized.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/LocalizedException.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/LocalizedParseException.java   (with props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java   (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java?rev=1401236&r1=1401235&r2=1401236&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java Tue Oct 23 10:11:34 2012
@@ -154,6 +154,19 @@ public final class MathFunctions extends
     }
 
     /**
+     * Truncates the given value toward zero. Invoking this method is equivalent to invoking
+     * {@link Math#floor(double)} if the value is positive, or {@link Math#ceil(double)} if
+     * the value is negative.
+     *
+     * @param  value The value to truncate.
+     * @return The largest in magnitude (further from zero) integer value which is equals
+     *         or less in magnitude than the given value.
+     */
+    public static double truncate(final double value) {
+        return (value < 0) ? Math.ceil(value) : Math.floor(value);
+    }
+
+    /**
      * Returns the magnitude of the given vector. This is defined by:
      *
      * {@preformat math

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java?rev=1401236&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java Tue Oct 23 10:11:34 2012
@@ -0,0 +1,197 @@
+/*
+ * 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.Locale;
+import java.text.Format;
+import java.text.ParseException;
+import java.io.Serializable;
+import net.jcip.annotations.Immutable;
+
+import org.apache.sis.util.Utilities;
+
+
+/**
+ * An angle in decimal degrees. An angle is the amount of rotation needed to bring one line or
+ * plane into coincidence with another, generally measured in degrees, sexagesimal degrees or
+ * grads.
+ *
+ * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
+ * @since   0.3 (derived from geotk-1.0)
+ * @version 0.3
+ * @module
+ *
+ * @see Latitude
+ * @see Longitude
+ * @see AngleFormat
+ */
+@Immutable
+public class Angle implements Comparable<Angle>, Serializable {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 1158747349433104534L;
+
+    /**
+     * A shared instance of {@link AngleFormat}.
+     *
+     * @see #getAngleFormat()
+     */
+    private static Format format;
+
+    /**
+     * Angle value in decimal degrees. We use decimal degrees as the storage unit
+     * instead than radians in order to avoid rounding errors, since there is no
+     * way to represent 30°, 45°, 90°, 180°, <i>etc.</i> in radians without errors.
+     */
+    private final double θ;
+
+    /**
+     * Constructs a new angle with the specified value in decimal degrees.
+     *
+     * @param θ Angle in decimal degrees.
+     */
+    public Angle(final double θ) {
+        this.θ = θ;
+    }
+
+    /**
+     * Constructs a newly allocated {@code Angle} object that contain the angular value
+     * represented by the string. The string should represent an angle in either fractional
+     * degrees (e.g. 45.5°) or degrees with minutes and seconds (e.g. 45°30').
+     *
+     * <p>This is a convenience constructor mostly for testing purpose, since it uses a fixed
+     * locale. Developers should consider using {@link AngleFormat} for end-user applications
+     * instead than this constructor.</p>
+     *
+     * @param  string A string to be converted to an {@code Angle}.
+     * @throws NumberFormatException if the string does not contain a parsable angle.
+     *
+     * @see AngleFormat#parse(String)
+     */
+    public Angle(final String string) throws NumberFormatException {
+        final Object angle;
+        try {
+            synchronized (Angle.class) {
+                angle = getAngleFormat().parseObject(string);
+            }
+        } catch (ParseException exception) {
+            NumberFormatException e = new NumberFormatException(exception.getLocalizedMessage());
+            e.initCause(exception);
+            throw e;
+        }
+        if (getClass().isAssignableFrom(angle.getClass())) {
+            this.θ = ((Angle) angle).θ;
+        } else {
+            throw new NumberFormatException(string);
+        }
+    }
+
+    /**
+     * Returns the angle value in decimal degrees.
+     *
+     * @return The angle value in decimal degrees.
+     */
+    public double degrees() {
+        return θ;
+    }
+
+    /**
+     * Returns the angle value in radians.
+     *
+     * @return The angle value in radians.
+     */
+    public double radians() {
+        return Math.toRadians(θ);
+    }
+
+    /**
+     * Returns a hash code for this {@code Angle} object.
+     */
+    @Override
+    public int hashCode() {
+        final long code = Double.doubleToLongBits(θ);
+        return (int) code ^ (int) (code >>> 32) ^ (int) serialVersionUID;
+    }
+
+    /**
+     * Compares the specified object with this angle for equality.
+     *
+     * @param object The object to compare with this angle for equality.
+     * @return {@code true} if the given object is equal to this angle.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object != null && getClass() == object.getClass()) {
+            return Utilities.equals(θ, ((Angle) object).θ);
+        }
+        return false;
+    }
+
+    /**
+     * Compares two {@code Angle} objects numerically. The comparison
+     * is done as if by the {@link Double#compare(double, double)} method.
+     *
+     * @param that The angle to compare with this object for order.
+     * @return -1 if this angle is smaller than the given one, +1 if greater or 0 if equals.
+     */
+    @Override
+    public int compareTo(final Angle that) {
+        return Double.compare(this.θ, that.θ);
+    }
+
+    /**
+     * Returns a string representation of this {@code Angle} object.
+     * This is a convenience method mostly for debugging purpose, since it uses a fixed locale.
+     * Developers should consider using {@link AngleFormat} for end-user applications instead
+     * than this method.
+     *
+     * @see AngleFormat#format(Angle)
+     */
+    @Override
+    public String toString() {
+        StringBuffer buffer = new StringBuffer(16);
+        synchronized (Angle.class) {
+            buffer = getAngleFormat().format(this, buffer, null);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Returns a shared instance of {@link AngleFormat}. The return type is
+     * {@link Format} in order to avoid class loading before necessary.
+     *
+     * <p>This method must be invoked in a {@code synchronized(Angle.class)} block. We use
+     * synchronization instead than static class initialization because {@code AngleFormat}
+     * is not thread-safe, so it needs to be used in a synchronized block anyway. We could
+     * avoid synchronization by using {@link ThreadLocal}, but this brings other issues in
+     * OSGi context. Given that our Javadoc said that {@link #Angle(String)} and {@link #toString()}
+     * should be used mostly for debugging purpose, we consider not worth to ensure high
+     * concurrency capability here.</p>
+     */
+    private static Format getAngleFormat() {
+        assert Thread.holdsLock(Angle.class);
+        if (format == null) {
+            format = AngleFormat.getInstance(Locale.CANADA);
+            // Canada locale is closer to ISO standards than US locale.
+        }
+        return format;
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Angle.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1401236&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java Tue Oct 23 10:11:34 2012
@@ -0,0 +1,1300 @@
+/*
+ * 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.Locale;
+import java.text.Format;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.ParseException;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import net.jcip.annotations.NotThreadSafe;
+
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.Localized;
+import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.rint;
+import static java.lang.Double.NaN;
+import static java.lang.Double.isNaN;
+import static java.lang.Double.isInfinite;
+import static org.apache.sis.math.MathFunctions.pow10;
+import static org.apache.sis.math.MathFunctions.truncate;
+import static org.apache.sis.math.MathFunctions.isNegative;
+
+// Related to JDK7
+import java.util.Objects;
+
+
+/**
+ * Parses and formats angles according a specified pattern. The pattern is a string
+ * containing any characters, with a special meaning for the following characters:
+ *
+ * <blockquote><table class="compact">
+ *   <tr><td>{@code D}</td><td>The integer part of degrees</td></tr>
+ *   <tr><td>{@code d}</td><td>The fractional part of degrees</td></tr>
+ *   <tr><td>{@code M}</td><td>The integer part of minutes</td></tr>
+ *   <tr><td>{@code m}</td><td>The fractional part of minutes</td></tr>
+ *   <tr><td>{@code S}</td><td>The integer part of seconds</td></tr>
+ *   <tr><td>{@code s}</td><td>The fractional part of seconds</td></tr>
+ *   <tr><td>{@code .}</td><td>The decimal separator</td></tr>
+ * </table></blockquote>
+ *
+ * Upper-case letters {@code D}, {@code M} and {@code S} stand for the integer parts of degrees,
+ * minutes and seconds respectively. They shall appear in this order. For example {@code M'D} is
+ * illegal because "M" and "S" are in reverse order; {@code D°S} is illegal too because "M" is
+ * missing between "D" and "S".
+ *
+ * <p>Lower-case letters {@code d}, {@code m} and {@code s} stand for fractional parts of degrees,
+ * minutes and seconds respectively. Only one of those may appears in a pattern, and it must be
+ * the last special symbol. For example {@code D.dd°MM'} is illegal because "d" is followed by
+ * "M"; {@code D.mm} is illegal because "m" is not the fractional part of "D".</p>
+ *
+ * <p>The number of occurrence of {@code D}, {@code M}, {@code S} and their lower-case counterpart
+ * is the number of digits to format. For example, {@code DD.ddd} will format angles with two digits
+ * for the integer part and three digits for the fractional part (e.g. 4.4578 will be formatted as
+ * "04.458").</p>
+ *
+ * <p>Separator characters like {@code °}, {@code ′} and {@code ″} are inserted "as-is" in the
+ * formatted string, except the decimal separator dot ({@code .}) which is replaced by the
+ * local-dependent decimal separator. Separator characters may be completely omitted;
+ * {@code AngleFormat} will still differentiate degrees, minutes and seconds fields according
+ * the pattern. For example, "{@code 0480439}" with the pattern {@code DDDMMmm} will be parsed
+ * as 48°04.39'.</p>
+ *
+ * <p>The following table gives some pattern examples:</p>
+ *
+ * <blockquote><table class="compact">
+ *   <tr><th>Pattern           </th>  <th>Example   </th></tr>
+ *   <tr><td>{@code DD°MM′SS″ }</td>  <td>48°30′00″ </td></tr>
+ *   <tr><td>{@code DD°MM′    }</td>  <td>48°30′    </td></tr>
+ *   <tr><td>{@code DD.ddd    }</td>  <td>48.500    </td></tr>
+ *   <tr><td>{@code DDMM      }</td>  <td>4830      </td></tr>
+ *   <tr><td>{@code DDMMSS    }</td>  <td>483000    </td></tr>
+ * </table></blockquote>
+ *
+ * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
+ * @since   0.3 (derived from geotk-1.0)
+ * @version 0.3
+ * @module
+ *
+ * @see Angle
+ * @see Latitude
+ * @see Longitude
+ */
+@NotThreadSafe
+public class AngleFormat extends Format implements Localized {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 4320403817210439764L;
+
+    /**
+     * Hemisphere symbols. Must be upper-case.
+     */
+    private static final char NORTH='N', SOUTH='S', EAST='E', WEST='W';
+
+    /**
+     * A constant for the symbol to appears before the degrees fields. used in {@code switch}
+     * statements. Fields PREFIX, DEGREES, MINUTES and SECONDS must have increasing values.
+     */
+    private static final int PREFIX_FIELD = -1;
+
+    /**
+     * Constant for degrees field. When formatting a string, this value may be specified to the
+     * {@link FieldPosition} constructor in order to get the bounding index where degrees have
+     * been written.
+     */
+    public static final int DEGREES_FIELD = 0;
+
+    /**
+     * Constant for minutes field. When formatting a string, this value may be specified to the
+     * {@link FieldPosition} constructor in order to get the bounding index where minutes have
+     * been written.
+     */
+    public static final int MINUTES_FIELD = 1;
+
+    /**
+     * Constant for seconds field. When formatting a string, this value may be specified to the
+     * {@link FieldPosition} constructor in order to get the bounding index where seconds have
+     * been written.
+     */
+    public static final int SECONDS_FIELD = 2;
+
+    /**
+     * Constant for hemisphere field. When formatting a string, this value may be specified to the
+     * {@link FieldPosition} constructor in order to get the bounding index where the hemisphere
+     * symbol has been written.
+     */
+    public static final int HEMISPHERE_FIELD = 3;
+
+    /**
+     * Symbols for degrees (0), minutes (1) and seconds (2).
+     */
+    private static final char[] SYMBOLS = {'D', 'M', 'S'};
+
+    /**
+     * The locale specified at construction time.
+     */
+    private final Locale locale;
+
+    /**
+     * Minimal amount of spaces to be used by the degrees, minutes and seconds fields,
+     * and by the decimal digits. A value of 0 means that the field is not formatted.
+     * {@code fractionFieldWidth} applies to the last non-zero field.
+     */
+    private int degreesFieldWidth,
+                minutesFieldWidth,
+                secondsFieldWidth,
+                fractionFieldWidth;
+
+    /**
+     * Characters to insert before the text to format, and after each field.
+     * A {@code null} value means that there is nothing to insert.
+     */
+    private String prefix,
+                   degreesSuffix,
+                   minutesSuffix,
+                   secondsSuffix;
+
+    /**
+     * {@code true} if the {@link #parse(String, ParsePosition)} method is allowed to fallback
+     * on the build-in default symbols if the string to parse doesn't match the pattern.
+     *
+     * @see #isFallbackAllowed()
+     * @see #setFallbackAllowed(boolean)
+     */
+    private boolean isFallbackAllowed;
+
+    /**
+     * Specifies whatever the decimal separator shall be inserted between the integer part
+     * and the fraction part of the last field. A {@code false} value formats the integer
+     * and fractional part without separation, e.g. "34867" for 34.867.
+     */
+    private boolean useDecimalSeparator;
+
+    /**
+     * Formats to use for writing numbers (degrees, minutes or seconds) when formatting an angle.
+     * The pattern given to this {@code DecimalFormat} shall NOT accept exponential notation,
+     * because "E" of "Exponent" would be confused with "E" of "East".
+     */
+    private final DecimalFormat numberFormat;
+
+    /**
+     * Object to give to {@code DecimalFormat.format} methods,
+     * cached in order to avoid recreating this object too often.
+     *
+     * @see #dummyFieldPosition()
+     */
+    private transient FieldPosition dummy;
+
+    /**
+     * Returns the width of the specified field.
+     */
+    private int getWidth(final int index) {
+        switch (index) {
+            case DEGREES_FIELD: return degreesFieldWidth;
+            case MINUTES_FIELD: return minutesFieldWidth;
+            case SECONDS_FIELD: return secondsFieldWidth;
+            default:            return 0; // Must be 0 (important!)
+        }
+    }
+
+    /**
+     * Sets the width for the specified field.
+     * All following fields will be set to 0.
+     */
+    @SuppressWarnings("fallthrough")
+    private void setWidth(final int index, int width) {
+        switch (index) {
+            case DEGREES_FIELD: degreesFieldWidth = width; width = 0; // fall through
+            case MINUTES_FIELD: minutesFieldWidth = width; width = 0; // fall through
+            case SECONDS_FIELD: secondsFieldWidth = width;
+        }
+    }
+
+    /**
+     * Returns the suffix for the specified field.
+     */
+    private String getSuffix(final int index) {
+        switch (index) {
+            case  PREFIX_FIELD: return prefix;
+            case DEGREES_FIELD: return degreesSuffix;
+            case MINUTES_FIELD: return minutesSuffix;
+            case SECONDS_FIELD: return secondsSuffix;
+            default:            return null;
+        }
+    }
+
+    /**
+     * Sets the suffix for the specified field. Suffix for all
+     * following fields will be set to their default value.
+     */
+    @SuppressWarnings("fallthrough")
+    private void setSuffix(final int field, String suffix) {
+        switch (field) {
+            case  PREFIX_FIELD: prefix        = suffix; suffix = "°";  // fall through
+            case DEGREES_FIELD: degreesSuffix = suffix; suffix = "′";  // fall through
+            case MINUTES_FIELD: minutesSuffix = suffix; suffix = "″";  // fall through
+            case SECONDS_FIELD: secondsSuffix = suffix;
+        }
+    }
+
+    /**
+     * Returns the dummy field position.
+     */
+    private FieldPosition dummyFieldPosition() {
+        if (dummy == null) {
+            dummy = new FieldPosition(0);
+        }
+        return dummy;
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} for the default pattern and the current default locale.
+     *
+     * @return An angle format for the current default locale.
+     */
+    public static AngleFormat getInstance() {
+        return new AngleFormat();
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} for the default pattern and the specified locale.
+     *
+     * @param  locale The locale to use.
+     * @return An angle format for the given locale.
+     */
+    public static AngleFormat getInstance(final Locale locale) {
+        return new AngleFormat(locale);
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} for the default pattern and the current default locale.
+     */
+    public AngleFormat() {
+        this(Locale.getDefault(Locale.Category.FORMAT), new DecimalFormat("#0"));
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} for the default pattern and the specified locale.
+     *
+     * @param  locale The locale to use.
+     */
+    public AngleFormat(final Locale locale) {
+        this(locale, new DecimalFormat("#0", DecimalFormatSymbols.getInstance(locale)));
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} for the specified pattern and the current default locale.
+     *
+     * @param  pattern Pattern to use for parsing and formatting angles.
+     *         See class description for an explanation of pattern syntax.
+     * @throws IllegalArgumentException If the specified pattern is illegal.
+     */
+    public AngleFormat(final String pattern) throws IllegalArgumentException {
+        this(pattern, Locale.getDefault(Locale.Category.FORMAT), new DecimalFormat("#0"));
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} using the specified pattern and locale.
+     *
+     * @param  pattern Pattern to use for parsing and formatting angles.
+     *         See class description for an explanation of pattern syntax.
+     * @param  locale Locale to use.
+     * @throws IllegalArgumentException If the specified pattern is illegal.
+     */
+    public AngleFormat(final String pattern, final Locale locale) throws IllegalArgumentException {
+        this(pattern, locale, new DecimalFormat("#0", DecimalFormatSymbols.getInstance(locale)));
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} using the default pattern and the specified decimal
+     * format.
+     *
+     * @param  locale  The locale specified by the user, or the default locale otherwise.
+     * @param  symbols The symbols to use for parsing and formatting numbers.
+     * @throws IllegalArgumentException If the specified pattern is illegal.
+     */
+    private AngleFormat(final Locale locale, final DecimalFormat decimalFormat) {
+        this.locale = locale;
+        degreesFieldWidth   = 1;
+        minutesFieldWidth   = 2;
+        degreesSuffix       = "°";
+        minutesSuffix       = "′";
+        secondsSuffix       = "″";
+        isFallbackAllowed = true;
+        useDecimalSeparator = true;
+        numberFormat        = decimalFormat;
+    }
+
+    /**
+     * Constructs a new {@code AngleFormat} using the specified pattern and decimal symbols.
+     *
+     * @param  pattern Pattern to use for parsing and formatting angles.
+     *         See class description for an explanation of pattern syntax.
+     * @param  locale  The locale specified by the user, or the default locale otherwise.
+     * @param  symbols The symbols to use for parsing and formatting numbers.
+     * @throws IllegalArgumentException If the specified pattern is illegal.
+     */
+    private AngleFormat(final String pattern, final Locale locale, final DecimalFormat decimalFormat) {
+        this.locale = locale;
+        numberFormat = decimalFormat;
+        isFallbackAllowed = true;
+        applyPattern(pattern, SYMBOLS, '.');
+    }
+
+    /**
+     * Sets the pattern to use for parsing and formatting angles.
+     * See class description for a description of pattern syntax.
+     *
+     * @param  pattern Pattern to use for parsing and formatting angle.
+     * @throws IllegalArgumentException If the specified pattern is not legal.
+     *
+     * @see #setFallbackAllowed(boolean)
+     */
+    public void applyPattern(final String pattern) throws IllegalArgumentException {
+        applyPattern(pattern, SYMBOLS, '.');
+    }
+
+    /**
+     * Actual implementation of {@link #applyPattern(String)},
+     * as a private method for use by the constructor.
+     *
+     * @param symbols An array of 3 characters containing the reserved symbols as upper-case letters.
+     *        This is always the {@link #SYMBOLS} array, unless we apply localized patterns.
+     * @param decimalSeparator The code point which represent decimal separator in the pattern.
+     */
+    @SuppressWarnings("fallthrough")
+    private void applyPattern(final String pattern, final char[] symbols, final int decimalSeparator) {
+        fractionFieldWidth = 0;
+        useDecimalSeparator = true;
+        int startPrefix = 0;
+        int symbolIndex = 0;
+        boolean parseFinished = false;
+        final int length = pattern.length();
+scan:   for (int i=0; i<length;) {
+            /*
+             * Examine all character pattern, skipping the non-reserved ones
+             * ("D", "M", "S", "d", "m", "s"). Non-reserved characters will
+             * be stored as suffix later.
+             */
+            final int c          = pattern.codePointAt(i);
+            final int charCount  = Character.charCount(c);
+            final int upperCaseC = Character.toUpperCase(c);
+            for (int field=DEGREES_FIELD; field<=SECONDS_FIELD; field++) {
+                if (upperCaseC == symbols[field]) {
+                    /*
+                     * A reserved character has been found.  Ensure that it appears in a legal
+                     * location. For example "MM.mm" is illegal because there is no 'D' before
+                     * 'M', and "DD.mm" is illegal because the integer part is not 'M'.
+                     */
+                    if (c == upperCaseC) {
+                        symbolIndex++;
+                    }
+                    if (field != symbolIndex-1 || parseFinished) {
+                        /*
+                         * Illegal pattern. Before to throw an exception, reset this format
+                         * to the default pattern in order to avoid leaving the object in a
+                         * random state.
+                         */
+                        setWidth(DEGREES_FIELD, 1);
+                        setSuffix(PREFIX_FIELD, null);
+                        fractionFieldWidth   = 0;
+                        useDecimalSeparator = true;
+                        throw new IllegalArgumentException(Errors.format(
+                                Errors.Keys.IllegalFormatPatternForClass_2, pattern, Angle.class));
+                    }
+                    if (c == upperCaseC) {
+                        /*
+                         * Memorize the characters prior the reserved letter as the suffix of
+                         * the previous field. Then count the number of occurrences of that
+                         * reserved letter. This number will be the field width.
+                         */
+                        setSuffix(field-1, (i > startPrefix) ? pattern.substring(startPrefix, i) : null);
+                        int w = 1;
+                        while ((i += charCount) < length && pattern.codePointAt(i) == c) {
+                            w++;
+                        }
+                        setWidth(field, w);
+                    } else {
+                        /*
+                         * If the reserved letter is lower-case, the part before that letter will
+                         * be the decimal separator rather than the suffix of previous field.
+                         * The count the number of occurrences of the lower-case letter; this
+                         * will be the precision of the fraction part.
+                         */
+                        if (i == startPrefix) {
+                            useDecimalSeparator = false;
+                        } else {
+                            final int b = pattern.codePointAt(startPrefix);
+                            if (b != decimalSeparator || startPrefix + Character.charCount(b) != i) {
+                                throw new IllegalArgumentException(Errors.format(
+                                        Errors.Keys.IllegalFormatPatternForClass_2, pattern, Angle.class));
+                            }
+                        }
+                        int w = 1;
+                        while ((i += charCount) < length && pattern.codePointAt(i) == c) {
+                            w++;
+                        }
+                        fractionFieldWidth = w;
+                        parseFinished = true;
+                    }
+                    startPrefix = i;
+                    continue scan;
+                }
+            }
+            i += charCount;
+        }
+        setSuffix(symbolIndex-1, (startPrefix < length) ? pattern.substring(startPrefix) : null);
+    }
+
+    /**
+     * Returns the pattern used for parsing and formatting angles.
+     * See class description for an explanation of how patterns work.
+     *
+     * @return The formatting pattern.
+     */
+    public String toPattern() {
+        return toPattern(SYMBOLS, '.');
+    }
+
+    /**
+     * Actual implementation of {@link #toPattern()}.
+     *
+     * @param symbols An array of 3 characters containing the reserved symbols as upper-case letters.
+     *        This is always the {@link #SYMBOLS} array, unless we apply localized patterns.
+     * @param decimalSeparator The code point which represent decimal separator in the pattern.
+     *
+     * @see #isFallbackAllowed()
+     */
+    private String toPattern(final char[] symbols, final int decimalSeparator) {
+        char symbol = '#';
+        final StringBuilder buffer = new StringBuilder(12);
+        for (int field=DEGREES_FIELD; field<=symbols.length; field++) {
+            final String previousSuffix = getSuffix(field-1);
+            int w = getWidth(field);
+            if (w > 0) {
+                /*
+                 * Write the integer parts of degrees, minutes and seconds.
+                 * The suffix of previous field will be written first.
+                 */
+                if (previousSuffix != null) {
+                    buffer.append(previousSuffix);
+                }
+                symbol = symbols[field];
+                do buffer.append(symbol);
+                while (--w > 0);
+            } else {
+                /*
+                 * Write the fractional part of degrees, minutes or seconds,
+                 * then the suffix of the last field.
+                 */
+                w = fractionFieldWidth;
+                if (w > 0) {
+                    if (useDecimalSeparator) {
+                        buffer.appendCodePoint(decimalSeparator);
+                    }
+                    symbol = Character.toLowerCase(symbol);
+                    do buffer.append(symbol);
+                    while (--w > 0);
+                }
+                if (previousSuffix != null) {
+                    buffer.append(previousSuffix);
+                }
+                break;
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Formats an angle. The angle will be formatted according the pattern given to the last call
+     * of {@link #applyPattern(String)}.
+     *
+     * @param  angle Angle to format, in decimal degrees.
+     * @return The formatted string.
+     */
+    public final String format(final double angle) {
+        return format(angle, new StringBuffer(20), null).toString();
+    }
+
+    /**
+     * Formats an angle in the given buffer. The angle will be formatted according
+     * the pattern given to the last call of {@link #applyPattern(String)}.
+     *
+     * @param angle
+     *          Angle to format, in decimal degrees.
+     * @param toAppendTo
+     *          The buffer where to append the formatted angle.
+     * @param pos
+     *          An optional object where to store the position of the field in the formatted
+     *          text, or {@code null} if this information is not wanted. This field position
+     *          shall be created with one of the following constants: {@link #DEGREES_FIELD},
+     *          {@link #MINUTES_FIELD}, {@link #SECONDS_FIELD} or {@link #HEMISPHERE_FIELD}.
+     *
+     * @return The {@code toAppendTo} buffer, returned for method calls chaining.
+     */
+    public StringBuffer format(final double angle, StringBuffer toAppendTo, final FieldPosition pos) {
+        if (isNaN(angle) || isInfinite(angle)) {
+            return numberFormat.format(angle, toAppendTo,
+                    (pos != null) ? pos : new FieldPosition(DecimalFormat.INTEGER_FIELD));
+        }
+        double degrees = angle;
+        /*
+         * Computes the numerical values of minutes and seconds fields.
+         * If those fiels are not written, then store NaN.
+         */
+        double minutes = NaN;
+        double seconds = NaN;
+        if (minutesFieldWidth != 0 && !isNaN(angle)) {
+            minutes = abs(degrees - (degrees = truncate(degrees))) * 60;
+            if (secondsFieldWidth != 0) {
+                seconds = (minutes - (minutes = truncate(minutes))) * 60;
+                /*
+                 * Correction for rounding errors.
+                 */
+                final double puissance = pow10(fractionFieldWidth);
+                seconds = rint(seconds * puissance) / puissance;
+                final double correction = truncate(seconds / 60);
+                seconds -= correction * 60;
+                minutes += correction;
+            } else {
+                final double puissance = pow10(fractionFieldWidth);
+                minutes = rint(minutes * puissance) / puissance;
+            }
+            final double correction = truncate(minutes / 60);
+            minutes -= correction * 60;
+            degrees += correction;
+        }
+        /*
+         * At this point, 'degrees', 'minutes' and 'seconds'
+         * contain the final values to format.
+         */
+        if (prefix != null) {
+            toAppendTo.append(prefix);
+        }
+        final int field;
+        if (pos != null) {
+            field = pos.getField();
+            pos.setBeginIndex(0);
+            pos.setEndIndex(0);
+        } else {
+            field = PREFIX_FIELD;
+        }
+        toAppendTo = formatField(degrees, toAppendTo, (field == DEGREES_FIELD) ? pos : null,
+                degreesFieldWidth, (minutesFieldWidth == 0), degreesSuffix);
+        if (!isNaN(minutes)) {
+            toAppendTo = formatField(minutes, toAppendTo, (field == MINUTES_FIELD) ? pos : null,
+                    minutesFieldWidth, (secondsFieldWidth == 0), minutesSuffix);
+        }
+        if (!isNaN(seconds)) {
+            toAppendTo = formatField(seconds, toAppendTo, (field == SECONDS_FIELD) ? pos : null,
+                    secondsFieldWidth, true, secondsSuffix);
+        }
+        return toAppendTo;
+    }
+
+    /**
+     * Formats a single field value.
+     *
+     * @param value      The field value.
+     * @param toAppendTo The buffer where to append the formatted angle.
+     * @param pos        An optional object where to store the position of a field in the formatted text.
+     * @param width      The field width.
+     * @param decimal    {@code true} for formatting the decimal digits (last field only).
+     * @param suffix     Suffix to append, or {@code null} if none.
+     */
+    private StringBuffer formatField(double value, StringBuffer toAppendTo, final FieldPosition pos,
+                                     final int width, final boolean decimal, final String suffix)
+    {
+        final int startPosition = toAppendTo.length();
+        if (!decimal) {
+            numberFormat.setMinimumIntegerDigits(width);
+            numberFormat.setMaximumFractionDigits(0);
+            toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
+        } else if (useDecimalSeparator) {
+            numberFormat.setMinimumIntegerDigits(width);
+            numberFormat.setMinimumFractionDigits(fractionFieldWidth);
+            numberFormat.setMaximumFractionDigits(fractionFieldWidth);
+            toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
+        } else {
+            value *= pow10(fractionFieldWidth);
+            numberFormat.setMaximumFractionDigits(0);
+            numberFormat.setMinimumIntegerDigits(width + fractionFieldWidth);
+            toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
+        }
+        if (suffix != null) {
+            toAppendTo.append(suffix);
+        }
+        if (pos != null) {
+            pos.setBeginIndex(startPosition);
+            pos.setEndIndex(toAppendTo.length() - 1);
+        }
+        return toAppendTo;
+    }
+
+    /**
+     * Formats an angle, latitude or longitude value in the given buffer.
+     * The angular values will be formatted according the pattern given to the
+     * last call of {@link #applyPattern(String)}, with some variations that
+     * depend on the {@code value} class:
+     *
+     * <ul>
+     *   <li>If {@code value} is a {@link Latitude} instance, then the value is formatted as a
+     *       positive angle followed by the "N" (positive value) or "S" (negative value) symbol.</li>
+     *   <li>If {@code value} is a {@link Longitude} instance, then the value is formatted as a
+     *       positive angle followed by the "E" (positive value) or "W" (negative value) symbol.</li>
+     *   <li>If {@code value} is any {@link Angle} other than a {@code Latitude} or {@code Longitude},
+     *       then it is formatted as by the {@link #format(double, StringBuffer, FieldPosition)}
+     *       method.</li>
+     * </ul>
+     *
+     * @param value
+     *          {@link Angle} object to format.
+     * @param toAppendTo
+     *          The buffer where to append the formatted angle.
+     * @param pos
+     *          An optional object where to store the position of the field in the formatted
+     *          text, or {@code null} if this information is not wanted. This field position
+     *          shall be created with one of the following constants: {@link #DEGREES_FIELD},
+     *          {@link #MINUTES_FIELD}, {@link #SECONDS_FIELD} or {@link #HEMISPHERE_FIELD}.
+     *
+     * @return The {@code toAppendTo} buffer, returned for method calls chaining.
+     * @throws IllegalArgumentException if {@code value} if not an instance of {@link Angle}.
+     */
+    @Override
+    public StringBuffer format(final Object value, StringBuffer toAppendTo, final FieldPosition pos)
+            throws IllegalArgumentException
+    {
+        if (value instanceof Latitude) {
+            return format(((Latitude) value).degrees(), toAppendTo, pos, NORTH, SOUTH);
+        }
+        if (value instanceof Longitude) {
+            return format(((Longitude) value).degrees(), toAppendTo, pos, EAST, WEST);
+        }
+        if (value instanceof Angle) {
+            return format(((Angle) value).degrees(), toAppendTo, pos);
+        }
+        ArgumentChecks.ensureNonNull("value", value);
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentClass_3,
+                "value", value.getClass(), Angle.class));
+    }
+
+    /**
+     * Formats a latitude or longitude value in the given buffer. The magnitude of the
+     * angular value will be formatted according the pattern given to the last call of
+     * {@link #applyPattern(String)}, and one of the given suffix will be appended
+     * according the angle sign.
+     */
+    private StringBuffer format(final double angle, StringBuffer toAppendTo,
+            final FieldPosition pos, final char positiveSuffix, final char negativeSuffix)
+    {
+        toAppendTo = format(abs(angle), toAppendTo, pos);
+        final int start = toAppendTo.length();
+        toAppendTo.append(isNegative(angle) ? negativeSuffix : positiveSuffix);
+        if (pos != null && pos.getField() == HEMISPHERE_FIELD) {
+            pos.setBeginIndex(start);
+            pos.setEndIndex(toAppendTo.length()-1);
+        }
+        return toAppendTo;
+    }
+
+    /**
+     * Ignores a field suffix, then returns the identifier of the suffix just skipped.
+     * This method is invoked by {@link #parse(String, ParsePosition)} for determining
+     * what was the field it just parsed. For example if we just parsed "48°12'", then
+     * this method will skip the "°" part and returns {@link #DEGREES_FIELD}.
+     *
+     * <p>This method skips whitespaces before the suffix, then compares the characters
+     * with the suffix specified to {@link #applyPattern(String)}. If the suffix has not
+     * been recognized, then this method will compares against the standard ', ° and "
+     * ASCII symbols.</p>
+     *
+     * @param source
+     *          The string being parsed.
+     * @param pos
+     *          On input, index of the first {@code source} character to read.
+     *          On output, index after the last suffix character.
+     * @param expectedField
+     *          First field to verify. For example a value of {@link #MINUTES_FIELD} means that
+     *          the suffix for minute and seconds shall be verified before degrees.
+     * @return The {@code *_FIELD} constant for the suffix which has been found, or a value
+     *         outside those constants if no suffix matched.
+     */
+    private int skipSuffix(final String source, final ParsePosition pos, final int expectedField) {
+        int field = expectedField;
+        int start = pos.getIndex();
+        final int length = source.length();
+        assert field >= PREFIX_FIELD && field <= SECONDS_FIELD : field;
+        do {
+            int index = start;
+            final String toSkip = getSuffix(field);
+            if (toSkip != null) {
+                final int toSkipLength = toSkip.length();
+                int c;
+                do {
+                    if (source.regionMatches(index, toSkip, 0, toSkipLength)) {
+                        pos.setIndex(index + toSkipLength);
+                        return field;
+                    }
+                    if (index >= length) break;
+                    c = source.codePointAt(index);
+                    index += Character.charCount(c);
+                }
+                while (Character.isSpaceChar(c));
+            }
+            if (++field > SECONDS_FIELD) {
+                field = PREFIX_FIELD;
+            }
+        } while (field != expectedField);
+        /*
+         * No suffix from the pattern has been found in the supplied text.
+         * Check for the usual symbols, if we were allowe to.
+         */
+        if (isFallbackAllowed) {
+            int c;
+            do {
+                if (start >= length) {
+                    return Integer.MIN_VALUE;
+                }
+                c = source.codePointAt(start);
+                start += Character.charCount(c);
+            }
+            while (Character.isSpaceChar(c));
+            switch (c) {
+                case '°' :            pos.setIndex(start); return DEGREES_FIELD;
+                case '′' : case '\'': pos.setIndex(start); return MINUTES_FIELD;
+                case '″' : case '"' : pos.setIndex(start); return SECONDS_FIELD;
+            }
+        }
+        return Integer.MIN_VALUE; // Unknown field.
+    }
+
+    /**
+     * Returns the index of the first non-space character in the given string.
+     *
+     * @param  source The string being parsed.
+     * @param  index  Index of the first {@code source} character to read.
+     * @return Index of the first non-space character, or the end of string if none.
+     */
+    private static int skipSpaces(final String source, int index) {
+        final int length = source.length();
+        while (index < length) {
+            final int c = source.codePointAt(index);
+            if (!Character.isSpaceChar(c)) break;
+            index += Character.charCount(c);
+        }
+        return index;
+    }
+
+    /**
+     * Parses the given string as an angle. This method can parse the string even if it is not
+     * strictly compliant to the expected pattern. For example if {@link #isFallbackAllowed()}
+     * is {@code true}, then this method will parse "{@code 48°12.34'}" correctly even if the
+     * expected pattern was "{@code DDMM.mm}" (i.e. the string should have been "{@code 4812.34}").
+     *
+     * <p>If the given string ends with a "N" or "S" hemisphere symbol, then this method returns
+     * an instance of {@link Latitude}. Otherwise if the string ends with a "E" or "W" symbol,
+     * then this method returns an instance of {@link Longitude}. Otherwise this method returns
+     * an instance of {@link Angle}.</p>
+     *
+     * <p>This method is stricter than the {@link #parse(String)} method regarding whitespaces
+     * between the degrees, minutes and seconds fields. This is because whitespaces could be
+     * used as a separator for other kinds of values. If the string is known to contain only
+     * an angle value, use {@code parse(String)} instead.</p>
+     *
+     * @param  source The string to parse.
+     * @param  pos    On input, index of the first {@code source} character to read.
+     *                On output, index after the last parsed character.
+     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     */
+    public Angle parse(final String source, final ParsePosition pos) {
+        return parse(source, pos, false);
+    }
+
+    /**
+     * Parses the given string as an angle. The {@code spaceAsSeparator} additional argument
+     * specifies if spaces can be accepted as a field separator. For example if {@code true},
+     * then "45 30" will be parsed as "45°30".
+     */
+    @SuppressWarnings("fallthrough")
+    private Angle parse(final String source, final ParsePosition pos, final boolean spaceAsSeparator) {
+        double degrees;
+        double minutes   = NaN;
+        double seconds   = NaN;
+        final int length = source.length();
+        ///////////////////////////////////////////////////////////////////////////////
+        // BLOCK A: Assign values to 'degrees', 'minutes' and 'seconds' variables.   //
+        //          This block does not take the hemisphere field in account, and    //
+        //          values will need adjustment if decimal separator is missing.     //
+        //          The { } block is for restricting the scope of local variables.   //
+        ///////////////////////////////////////////////////////////////////////////////
+        {
+            /*
+             * Extract the prefix, if any. If we find a degrees, minutes or seconds suffix
+             * before to have meet any number, we will consider that as a parsing failure.
+             */
+            final int indexStart = pos.getIndex();
+            int index = skipSuffix(source, pos, PREFIX_FIELD);
+            if (index >= DEGREES_FIELD && index <= SECONDS_FIELD) {
+                pos.setErrorIndex(indexStart);
+                pos.setIndex(indexStart);
+                return null;
+            }
+            index = skipSpaces(source, pos.getIndex());
+            pos.setIndex(index);
+            /*
+             * Parse the degrees field. If there is no separator between degrees, minutes
+             * and seconds, then the parsed number may actually include many fields (e.g.
+             * "DDDMMmmm"). The separation will be done later.
+             */
+            Number fieldObject = numberFormat.parse(source, pos);
+            if (fieldObject == null) {
+                pos.setIndex(indexStart);
+                if (pos.getErrorIndex() < indexStart) {
+                    pos.setErrorIndex(index);
+                }
+                return null;
+            }
+            degrees = fieldObject.doubleValue();
+            int indexEndField = pos.getIndex();
+            boolean missingDegrees = true;
+BigBoss:    switch (skipSuffix(source, pos, DEGREES_FIELD)) {
+                /* ------------------------------------------
+                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
+                 * ------------------------------------------
+                 * The degrees value is followed by the prefix for angles.
+                 * Stop parsing, since the remaining characters are for an other angle.
+                 */
+                case PREFIX_FIELD: {
+                    pos.setIndex(indexEndField);
+                    break BigBoss;
+                }
+                /* ------------------------------------------
+                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
+                 * ------------------------------------------
+                 * Found the seconds suffix instead then the degrees suffix. Move 'degrees'
+                 * value to 'seconds' and stop parsing, since seconds are the last field.
+                 */
+                case SECONDS_FIELD: {
+                    seconds = degrees;
+                    degrees = NaN;
+                    break BigBoss;
+                }
+                /* ------------------------------------------
+                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
+                 * ------------------------------------------
+                 * No recognized suffix after degrees. If "spaces as separator" are allowed and
+                 * a minutes field is expected after the degrees field, we will pretent that we
+                 * found the minutes suffix. Otherwise stop parsing.
+                 */
+                default: {
+                    if (!spaceAsSeparator || !isFallbackAllowed || minutesFieldWidth == 0) {
+                        break BigBoss;
+                    }
+                    // Fall through for parsing minutes.
+                }
+                /* ------------------------------------------
+                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
+                 * ------------------------------------------
+                 * After the degrees field, check if there is a minute field.
+                 * We proceed as for degrees (parse a number, skip the suffix).
+                 */
+                case DEGREES_FIELD: {
+                    final int indexStartField = pos.getIndex();
+                    index = skipSpaces(source, indexStartField);
+                    if (!spaceAsSeparator && index != indexStartField) {
+                        break BigBoss;
+                    }
+                    pos.setIndex(index);
+                    fieldObject = numberFormat.parse(source, pos);
+                    if (fieldObject == null) {
+                        pos.setIndex(indexStartField);
+                        break BigBoss;
+                    }
+                    indexEndField = pos.getIndex();
+                    minutes = fieldObject.doubleValue();
+                    switch (skipSuffix(source, pos, (minutesFieldWidth != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
+                         * ------------------------------------------
+                         * Found the expected suffix, nothing special to do.
+                         * Continue the outer switch for parsing seconds.
+                         */
+                        case MINUTES_FIELD: {
+                            break; // Continue outer switch for parsing seconds.
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
+                         * ------------------------------------------
+                         * Found the seconds suffix instead then the minutes suffix. Move 'minutes'
+                         * value to 'seconds' and stop parsing, since seconds are the last field.
+                         */
+                        case SECONDS_FIELD: {
+                            seconds = minutes;
+                            minutes = NaN;
+                            break BigBoss;
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
+                         * ------------------------------------------
+                         * No suffix has been found. This is normal if the pattern doesn't specify
+                         * a minutes field, in which case we reject the number that we just parsed.
+                         * However if minutes were expected and space separators are allowed, then
+                         * check for seconds.
+                         */
+                        default: {
+                            if (spaceAsSeparator && isFallbackAllowed && minutesFieldWidth != 0) {
+                                break; // Continue outer switch for parsing seconds.
+                            }
+                            // Fall through for rejecting the minutes.
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
+                         * ------------------------------------------
+                         * Found the degrees suffix instead than the minutes suffix.
+                         * This means that the number we have just read belong to an
+                         * other angle. Stop the parsing before that number.
+                         */
+                        case DEGREES_FIELD: {
+                            pos.setIndex(indexStartField);
+                            minutes = NaN;
+                            break BigBoss;
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
+                         * ------------------------------------------
+                         * Found the prefix of an other angle. Accept the number that
+                         * we have just parsed despite the missing minutes suffix, and
+                         * stop parsing before the prefix.
+                         */
+                        case PREFIX_FIELD: {
+                            pos.setIndex(indexEndField);
+                            break BigBoss;
+                        }
+                    }
+                    missingDegrees = false;
+                    // Fall through for parsing the seconds.
+                }
+                /* -----------------------------------------------------
+                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES OR MINUTES
+                 * -----------------------------------------------------
+                 * If a minutes field was found without degrees, move the 'degrees'
+                 * value to 'minutes'. Then try to parse the next number as seconds.
+                 */
+                case MINUTES_FIELD: {
+                    if (missingDegrees) {
+                        minutes = degrees;
+                        degrees = NaN;
+                    }
+                    final int indexStartField = pos.getIndex();
+                    index = skipSpaces(source, indexStartField);
+                    if (!spaceAsSeparator && index != indexStartField) {
+                        break BigBoss;
+                    }
+                    pos.setIndex(index);
+                    fieldObject = numberFormat.parse(source, pos);
+                    if (fieldObject == null) {
+                        pos.setIndex(indexStartField);
+                        break;
+                    }
+                    indexEndField = pos.getIndex();
+                    seconds = fieldObject.doubleValue();
+                    switch (skipSuffix(source, pos, (secondsFieldWidth != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
+                         * ------------------------------------------
+                         * Found the expected second suffix. We are done.
+                         */
+                        case SECONDS_FIELD: {
+                            break;
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
+                         * ------------------------------------------
+                         * No suffix has been found. This is normal if the pattern doesn't specify
+                         * a seconds field, in which case we reject the number that we just parsed.
+                         * However if seconds were expected and space separators are allowed, then
+                         * accept the value.
+                         */
+                        default: {
+                            if (isFallbackAllowed && secondsFieldWidth != 0) {
+                                break;
+                            }
+                            // Fall through for rejecting the seconds.
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
+                         * ------------------------------------------
+                         * Found the degrees or minutes suffix instead than the seconds suffix.
+                         * This means that the number we have just read belong to an other angle.
+                         * Stop the parsing before that number.
+                         */
+                        case MINUTES_FIELD:
+                        case DEGREES_FIELD: {
+                            pos.setIndex(indexStartField);
+                            seconds = NaN;
+                            break;
+                        }
+                        /* ------------------------------------------
+                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
+                         * ------------------------------------------
+                         * Found the prefix of an other angle. Accept the number that
+                         * we have just parsed despite the missing seconds suffix, and
+                         * stop parsing before the prefix.
+                         */
+                        case PREFIX_FIELD: {
+                            pos.setIndex(indexEndField);
+                            break BigBoss;
+                        }
+                    }
+                }
+            }
+        }
+        ////////////////////////////////////////////////////////////////////
+        // BLOCK B: Handle the case when there is no decimal separator.   //
+        //          Then combine the fields into a decimal degrees value. //
+        ////////////////////////////////////////////////////////////////////
+        if (isNegative(minutes)) {
+            seconds = -seconds;
+        }
+        if (isNegative(degrees)) {
+            minutes = -minutes;
+            seconds = -seconds;
+        }
+        if (!useDecimalSeparator) {
+            final double facteur = pow10(fractionFieldWidth);
+            if (secondsFieldWidth != 0) {
+                if (minutesSuffix == null && isNaN(seconds)) {
+                    if (degreesSuffix == null && isNaN(minutes)) {
+                        degrees /= facteur;
+                    } else {
+                        minutes /= facteur;
+                    }
+                } else {
+                    seconds /= facteur;
+                }
+            } else if (isNaN(seconds)) {
+                if (minutesFieldWidth != 0) {
+                    if (degreesSuffix == null && isNaN(minutes)) {
+                        degrees /= facteur;
+                    } else {
+                        minutes /= facteur;
+                    }
+                } else if (isNaN(minutes)) {
+                    degrees /= facteur;
+                }
+            }
+        }
+        /*
+         * If there is no separation between degrees and minutes fields (e.g. if the pattern
+         * is "DDDMMmmm"), then the 'degrees' variable contains degrees, minutes and seconds
+         * in sexagesimal units. We need to convert to decimal units.
+         */
+        if (minutesSuffix == null && secondsFieldWidth != 0 && isNaN(seconds)) {
+            double facteur = pow10(secondsFieldWidth);
+            if (degreesSuffix == null && minutesFieldWidth != 0 && isNaN(minutes)) {
+                ///////////////////
+                //// DDDMMSS.s ////
+                ///////////////////
+                seconds  = degrees;
+                minutes  = truncate(degrees / facteur);
+                seconds -= minutes * facteur;
+                facteur  = pow10(minutesFieldWidth);
+                degrees  = truncate(minutes / facteur);
+                minutes  -= degrees * facteur;
+            } else {
+                ////////////////////
+                //// DDD°MMSS.s ////
+                ////////////////////
+                seconds  = minutes;
+                minutes  = truncate(minutes / facteur);
+                seconds -= minutes*facteur;
+            }
+        } else if (degreesSuffix == null && minutesFieldWidth != 0 && isNaN(minutes)) {
+            /////////////////
+            //// DDDMM.m ////
+            /////////////////
+            final double facteur = pow10(minutesFieldWidth);
+            minutes  = degrees;
+            degrees  = truncate(degrees / facteur);
+            minutes -= degrees * facteur;
+        }
+        pos.setErrorIndex(-1);
+        if ( isNaN(degrees)) degrees  = 0;
+        if (!isNaN(minutes)) degrees += minutes /   60;
+        if (!isNaN(seconds)) degrees += seconds / 3600;
+        /////////////////////////////////////////////////////////
+        // BLOCK C: Check for hemisphere suffix (N, S, E or W) //
+        //          after the angle string representation.     //
+        /////////////////////////////////////////////////////////
+        for (int index = pos.getIndex(); index < length;) {
+            final int c = source.codePointAt(index);
+            index += Character.charCount(c);
+            switch (Character.toUpperCase(c)) {
+                case NORTH: pos.setIndex(index); return new Latitude ( degrees);
+                case SOUTH: pos.setIndex(index); return new Latitude (-degrees);
+                case EAST : pos.setIndex(index); return new Longitude( degrees);
+                case WEST : pos.setIndex(index); return new Longitude(-degrees);
+            }
+            if (!Character.isSpaceChar(c)) {
+                break;
+            }
+        }
+        return new Angle(degrees);
+    }
+
+    /**
+     * Parses the given string as an angle. This full string is expected to represents an
+     * angle value. This assumption allows {@code parse(String)} to be more tolerant than
+     * {@link #parse(String, ParsePosition)} regarding white spaces between degrees, minutes
+     * and seconds fields.
+     *
+     * @param  source The string to parse.
+     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     * @throws ParseException If the string can not be fully parsed.
+     */
+    public Angle parse(final String source) throws ParseException {
+        final ParsePosition pos = new ParsePosition(0);
+        final Angle angle = parse(source, pos, true);
+        if (skipSpaces(source, pos.getIndex()) != source.length()) {
+            throw Exceptions.createParseException(locale, Angle.class, source, pos);
+        }
+        return angle;
+    }
+
+    /**
+     * Parses a substring as an object.
+     * The default implementation delegates to {@link #parse(String, ParsePosition)}.
+     *
+     * @param  source The string to parse.
+     * @param  pos The position where to start parsing.
+     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     */
+    @Override
+    public Object parseObject(final String source, final ParsePosition pos) {
+        return parse(source, pos);
+    }
+
+    /**
+     * Parses the given string as an object.
+     * The default implementation delegates to {@link #parse(String)}.
+     *
+     * @param  source The string to parse.
+     * @return The parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
+     * @throws ParseException If the string can not been fully parsed.
+     */
+    @Override
+    public Object parseObject(final String source) throws ParseException {
+        return parse(source);
+    }
+
+    /**
+     * Returns {@code true} if the {@link #parse(String, ParsePosition) parse} methods are allowed
+     * to fallback on the build-in default symbols if the string to parse doesn't match the
+     * {@linkplain #applyPattern(String) applied pattern}.
+     *
+     * @return {@code true} if the ASCII quote characters are allowed at parsing time.
+     */
+    public boolean isFallbackAllowed() {
+        return isFallbackAllowed;
+    }
+
+    /**
+     * Sets whether the {@link #parse(String, ParsePosition) parse} methods are allowed to
+     * fallback on the build-in default symbols if the string to parse doesn't match the
+     * {@linkplain #applyPattern(String) applied pattern}. The build-in fallback is:
+     *
+     * <ul>
+     *   <li>{@code °} (an extended-ASCII character) or space (in {@link #parse(String)} method only) for degrees.</li>
+     *   <li>{@code '} (an ASCII character) or {@code ′} (the default Unicode character) for minutes.</li>
+     *   <li>{@code "} (an ASCII character) or {@code ″} (the default Unicode character) for seconds.</li>
+     * </ul>
+     *
+     * The default value is {@code true}, because many end-users will not enter the Unicode
+     * {@code ′} and {@code ″} symbols. However developers may need to set this flag to
+     * {@code false} if those ASCII symbols are used in a wider context (for example the
+     * {@code "} character for quoting strings).
+     *
+     * @param allowed {@code true} if the ASCII quote characters are allowed at parsing time.
+     */
+    public void setFallbackAllowed(final boolean allowed) {
+        isFallbackAllowed = allowed;
+    }
+
+    /**
+     * Returns this formatter locale. This is the locale specified at construction time if any,
+     * or the default locale at construction time otherwise.
+     *
+     * @return This formatter locale.
+     */
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    /**
+     * Returns a "hash value" for this object.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(degreesFieldWidth, minutesFieldWidth, secondsFieldWidth, fractionFieldWidth,
+                prefix, degreesSuffix, minutesSuffix, secondsSuffix, useDecimalSeparator) ^ (int) serialVersionUID;
+    }
+
+    /**
+     * Compares this format with the specified object for equality.
+     *
+     * @param object The object to compare with this angle format for equality.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object != null && getClass() == object.getClass()) {
+            final  AngleFormat cast = (AngleFormat) object;
+            return degreesFieldWidth   == cast.degreesFieldWidth     &&
+                   minutesFieldWidth   == cast.minutesFieldWidth     &&
+                   secondsFieldWidth   == cast.secondsFieldWidth     &&
+                   fractionFieldWidth  == cast.fractionFieldWidth    &&
+                   useDecimalSeparator == cast.useDecimalSeparator   &&
+                   Objects.equals(prefix,        cast.prefix )       &&
+                   Objects.equals(degreesSuffix, cast.degreesSuffix) &&
+                   Objects.equals(minutesSuffix, cast.minutesSuffix) &&
+                   Objects.equals(secondsSuffix, cast.secondsSuffix) &&
+                   Objects.equals(numberFormat.getDecimalFormatSymbols(),
+                             cast.numberFormat.getDecimalFormatSymbols());
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns a string representation of this object for debugging purpose.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + '[' + toPattern() + ']';
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java?rev=1401236&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java Tue Oct 23 10:11:34 2012
@@ -0,0 +1,83 @@
+/*
+ * 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 net.jcip.annotations.Immutable;
+
+
+/**
+ * A latitude angle in decimal degrees.
+ * Positive latitudes are North, while negative latitudes are South.
+ *
+ * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
+ * @since   0.3 (derived from geotk-1.0)
+ * @version 0.3
+ * @module
+ *
+ * @see Longitude
+ * @see AngleFormat
+ */
+@Immutable
+public final class Latitude extends Angle {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -4496748683919618976L;
+
+    /**
+     * Minimum usual value for latitude ({@value}°).
+     *
+     * @see org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getMinimumValue()
+     */
+    public static final double MIN_VALUE = -90;
+
+    /**
+     * Maximum usual value for latitude (+{@value}°).
+     *
+     * @see org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getMaximumValue()
+     */
+    public static final double MAX_VALUE = +90;
+
+    /**
+     * Construct a new latitude with the specified angular value.
+     *
+     * @param θ Angle in decimal degrees.
+     */
+    public Latitude(final double θ) {
+        super(θ);
+    }
+
+    /**
+     * Constructs a newly allocated {@code Latitude} object that contain the angular value
+     * represented by the string. The string should represent an angle in either fractional
+     * degrees (e.g. 45.5°) or degrees with minutes and seconds (e.g. 45°30').
+     * The hemisphere (N or S) is optional (default to North).
+     *
+     * <p>This is a convenience constructor mostly for testing purpose, since it uses a fixed
+     * locale. Developers should consider using {@link AngleFormat} for end-user applications
+     * instead than this constructor.</p>
+     *
+     * @param  string A string to be converted to a {@code Latitude}.
+     * @throws NumberFormatException if the string does not contain a parsable angle,
+     *         or represents a longitude angle.
+     *
+     * @see AngleFormat#parse(String)
+     */
+    public Latitude(final String string) throws NumberFormatException {
+        super(string);
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java?rev=1401236&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java Tue Oct 23 10:11:34 2012
@@ -0,0 +1,83 @@
+/*
+ * 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 net.jcip.annotations.Immutable;
+
+
+/**
+ * A longitude angle in decimal degrees.
+ * Positive longitudes are East, while negative longitudes are West.
+ *
+ * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
+ * @since   0.3 (derived from geotk-1.0)
+ * @version 0.3
+ * @module
+ *
+ * @see Latitude
+ * @see AngleFormat
+ */
+@Immutable
+public final class Longitude extends Angle {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = -8614900608052762636L;
+
+    /**
+     * Minimum usual value for longitude ({@value}°).
+     *
+     * @see org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getMinimumValue()
+     */
+    public static final double MIN_VALUE = -180;
+
+    /**
+     * Maximum usual value for longitude (+{@value}°).
+     *
+     * @see org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getMaximumValue()
+     */
+    public static final double MAX_VALUE = +180;
+
+    /**
+     * Construct a new longitude with the specified angular value.
+     *
+     * @param θ Angle in decimal degrees.
+     */
+    public Longitude(final double θ) {
+        super(θ);
+    }
+
+    /**
+     * Constructs a newly allocated {@code Longitude} object that contain the angular value
+     * represented by the string. The string should represent an angle in either fractional
+     * degrees (e.g. 45.5°) or degrees with minutes and seconds (e.g. 45°30').
+     * The hemisphere (E or W) is optional (default to East).
+     *
+     * <p>This is a convenience constructor mostly for testing purpose, since it uses a fixed
+     * locale. Developers should consider using {@link AngleFormat} for end-user applications
+     * instead than this constructor.</p>
+     *
+     * @param  string A string to be converted to a {@code Longitude}.
+     * @throws NumberFormatException if the string does not contain a parsable angle,
+     *         or represents a latitude angle.
+     *
+     * @see AngleFormat#parse(String)
+     */
+    public Longitude(final String string) throws NumberFormatException {
+        super(string);
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java?rev=1401236&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java Tue Oct 23 10:11:34 2012
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+/**
+ * Measures (like {@link org.apache.sis.measure.Angle} and
+ * {@link org.apache.sis.measure.Range}) and their formatters.
+ * This package defines:
+ *
+ * <ul>
+ *   <li>{@link org.apache.sis.measure.Angle} and its subclasses
+ *      ({@link org.apache.sis.measure.Longitude},
+ *       {@link org.apache.sis.measure.Latitude})</li>
+ *   <li>Formatters
+ *      ({@link org.apache.sis.measure.AngleFormat},
+ *       {@link org.apache.sis.measure.CoordinateFormat},
+ *       {@link org.apache.sis.measure.RangeFormat})</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
+ * @since   0.3 (derived from geotk-2.0)
+ * @version 0.3
+ * @module
+ */
+package org.apache.sis.measure;

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/package-info.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java?rev=1401236&r1=1401235&r2=1401236&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/ArgumentChecks.java Tue Oct 23 10:11:34 2012
@@ -128,7 +128,6 @@ public final class ArgumentChecks extend
      * If this method does not thrown an exception, then the value can be casted to the class
      * represented by {@code expectedType} without throwing a {@link ClassCastException}.
      *
-     * @param  <T> The compile-time type of the value.
      * @param  name The name of the argument to be checked, used only if an exception is thrown.
      *         Can be {@code null} if the name is unknown.
      * @param  expectedType the expected type (class or interface).
@@ -136,7 +135,7 @@ public final class ArgumentChecks extend
      * @throws IllegalArgumentException if {@code value} is non-null and is not assignable
      *         to the given type.
      */
-    public static <T> void ensureCanCast(final String name, final Class<? extends T> expectedType, final T value)
+    public static void ensureCanCast(final String name, final Class<?> expectedType, final Object value)
             throws IllegalArgumentException
     {
         if (value != null) {

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java?rev=1401236&r1=1401235&r2=1401236&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java Tue Oct 23 10:11:34 2012
@@ -16,9 +16,12 @@
  */
 package org.apache.sis.util;
 
-import java.sql.SQLException;
 import java.util.Set;
 import java.util.HashSet;
+import java.util.Locale;
+import java.sql.SQLException;
+import java.text.ParsePosition;
+import java.text.ParseException;
 
 
 /**
@@ -37,6 +40,56 @@ public final class Exceptions extends St
     }
 
     /**
+     * Creates a {@link ParseException} with a localized message built from the given parsing
+     * information. The exception returned by this method contains the error message in two
+     * languages:
+     *
+     * <ul>
+     *   <li>{@link ParseException#getMessage()} returns the message in the default locale.</li>
+     *   <li>{@link ParseException#getLocalizedMessage() getLocalizedMessage()} returns the
+     *       message in the locale given in argument to this method. This is usually the
+     *       {@link java.text.Format} locale, which is presumed to be the end-user locale.</li>
+     * </ul>
+     *
+     * @param  locale The locale for {@link ParseException#getLocalizedMessage()}.
+     * @param  type   The type of objects parsed by the {@link java.text.Format}.
+     * @param  text   The text that {@code Format} failed to parse.
+     * @param  pos    Index of the {@linkplain ParsePosition#getIndex() first parsed character},
+     *                together with the {@linkplain ParsePosition#getErrorIndex() error index}.
+     * @return The localized exception.
+     */
+    public static ParseException createParseException(final Locale locale, final Class<?> type,
+            final String text, final ParsePosition pos)
+    {
+        final int offset = pos.getIndex();
+        final int errorOffset = Math.max(offset, pos.getErrorIndex());
+        return new LocalizedParseException(locale,
+                LocalizedParseException.arguments(type, text, offset, errorOffset), errorOffset);
+    }
+
+    /**
+     * Returns the message of the given exception, localized in the given locale if possible.
+     * Some exceptions created by SIS can format a message in different locales. This method
+     * will return such localized message if possible, or fallback on the standard
+     * {@link Throwable#getLocalizedMessage()} method otherwise. Note that by default,
+     * {@code getLocalizedMessage()} itself fallback on {@link Throwable#getMessage()}.
+     *
+     * @param  exception The exception from which to get the localize message, or {@code null}.
+     * @param  locale    The locale for the message, or {@code null} for the default locale.
+     * @return The message in the given locale if possible, or {@code null} if the {@code exception}
+     *         argument was {@code null} or the exception does not contain a message.
+     */
+    public static String getMessage(final Throwable exception, final Locale locale) {
+        if (exception == null) {
+            return null;
+        }
+        if (locale != null && exception instanceof LocalizedException) {
+            return ((LocalizedException) exception).getMessage(locale);
+        }
+        return exception.getLocalizedMessage();
+    }
+
+    /**
      * Returns an exception of the same kind and with the same stack trace than the given
      * exception, but with a different message. This method simulates the functionality
      * that we would have if {@link Throwable} defined a {@code setMessage(String)} method.
@@ -91,17 +144,18 @@ public final class Exceptions extends St
      *
      * <p>This method does not format the stack trace.</p>
      *
+     * @param  locale The preferred locale for the exception message, or {@code null}.
      * @param  header The message to insert on the first line, or {@code null} if none.
      * @param  cause  The exception, or {@code null} if none.
      * @return The formatted message, or {@code null} if both the header was {@code null}
      *         and no exception provide a message.
      */
-    public static String formatChainedMessages(String header, Throwable cause) {
+    public static String formatChainedMessages(final Locale locale, String header, Throwable cause) {
         Set<String> done = null;
         String lineSeparator = null;
         StringBuilder buffer = null;
         while (cause != null) {
-            String message = cause.getLocalizedMessage();
+            String message = getMessage(cause, locale);
             if (message != null && !(message = message.trim()).isEmpty()) {
                 if (buffer == null) {
                     done = new HashSet<>();



Mime
View raw message