You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2012/10/23 12:11:35 UTC
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...
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<>();