sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1514390 [2/2] - in /sis/branches/JDK6: ./ application/sis-console/ application/sis-console/src/main/artifact/ core/sis-metadata/ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/src/main/java/org/apache/sis/metada...
Date Thu, 15 Aug 2013 17:33:24 GMT
Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -59,45 +59,64 @@ import org.apache.sis.internal.jdk7.Obje
  *   <tr><td>{@code s}</td><td>The fractional part of seconds</td></tr>
  *   <tr><td>{@code #}</td><td>Fraction digits shown only if non-zero</td></tr>
  *   <tr><td>{@code .}</td><td>The decimal separator</td></tr>
+ *   <tr><td>{@code ?}</td><td>Omit the preceding field if zero</td></tr>
  * </table>
  *
  * 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".
+ * minutes and seconds respectively. If present, they shall appear in that order.
  *
- * <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>
+ * {@example "<code>M′D</code>" is illegal because "<code>M</code>" and "<code>S</code>" are in reverse order.
+ *           "<code>D°S</code>" is also illegal because "<code>M</code>" is missing between "<code>D</code>" and
+ *           "<code>S</code>".}
  *
- * <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>
+ * Lower-case letters {@code d}, {@code m} and {@code s} stand for fractional parts of degrees, minutes and
+ * seconds respectively. Only one of those can appear in a pattern. If present, they must be in the last field.
  *
- * <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>
+ * {@example "<code>D.dd°MM′</code>" is illegal because "<code>d</code>" is followed by "<code>M</code>".
+ *           "<code>D.mm</code>" is also illegal because "<code>m</code>" is not the fractional part of
+ *           "<code>D</code>".}
  *
- * <p>The following table gives some pattern examples:</p>
+ * The number of occurrences of {@code D}, {@code M}, {@code S} and their lower-case counterpart is the number
+ * of digits to format.
  *
+ * {@example "<code>DD.ddd</code>" will format angles with two digits for the integer part and three digits
+ *           for the fractional part (e.g. <code>4.4578</code> will be formatted as <code>"04.458"</code>).}
+ *
+ * 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.
+ *
+ * {@example "<code>0480439</code>" with the "<code>DDDMMmm</code>" pattern will be parsed as 48°04.39′.}
+ *
+ * The {@code ?} modifier specifies that the preceding field can be omitted if its value is zero.
+ * Any field can be omitted for {@link Angle} object, but only trailing fields are omitted for
+ * {@li{@link Longitude} and {@link Latitude}.
+ *
+ * {@example "<code>DD°MM′?SS″?</code>" will format an angle of 12.01° as <code>12°36″</code>,
+ *           but a longitude of 12.01°N as <code>12°00′36″N</code> (not <code>12°36″N</code>).}
+ *
+ * The above special case exists because some kind of angles are expected to be very small (e.g. rotation angles in
+ * {@linkplain org.apache.sis.referencing.datum.BursaWolfParameters Bursa-Wolf parameters} are given in arc-seconds),
+ * while longitude and latitude values are usually distributed over their full ±180° or ±90° range. Since longitude
+ * or latitude values without the degrees field are unusual, omitting that field is likely to increase the
+ * risk of confusion in those cases.
+ *
+ * {@section Examples}
  * <table class="sis">
- *   <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 DD.###    }</td>  <td>48.5      </td></tr>
- *   <tr><td>{@code DDMM      }</td>  <td>4830      </td></tr>
- *   <tr><td>{@code DDMMSS    }</td>  <td>483000    </td></tr>
+ *   <tr><th>Pattern               </th>  <th>48.5      </th> <th>-12.53125    </th></tr>
+ *   <tr><td>{@code DD°MM′SS.#″}   </td>  <td>48°30′00″ </td> <td>-12°31′52.5″ </td></tr>
+ *   <tr><td>{@code DD°MM′}        </td>  <td>48°30′    </td> <td>-12°32′      </td></tr>
+ *   <tr><td>{@code DD.ddd}        </td>  <td>48.500    </td> <td>-12.531      </td></tr>
+ *   <tr><td>{@code DD.###}        </td>  <td>48.5      </td> <td>-12.531      </td></tr>
+ *   <tr><td>{@code DDMM}          </td>  <td>4830      </td> <td>-1232        </td></tr>
+ *   <tr><td>{@code DDMMSSs}       </td>  <td>4830000   </td> <td>-1231525     </td></tr>
+ *   <tr><td>{@code DD°MM′?SS.s″?} </td>  <td>48°30′    </td> <td>-12°31′52.5″ </td></tr>
  * </table>
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3 (derived from geotk-1.0)
- * @version 0.3
+ * @version 0.4
  * @module
  *
  * @see Angle
@@ -165,10 +184,15 @@ public class AngleFormat extends Format 
     static final int HEMISPHERE_FIELD = 4;
 
     /**
+     * Index for the {@link #SYMBOLS} character which stands for optional field.
+     */
+    private static final int OPTIONAL_FIELD = 4;
+
+    /**
      * Symbols for degrees (0), minutes (1), seconds (2) and optional fraction digits (3).
      * The index of each symbol shall be equal to the corresponding {@code *_FIELD} constant.
      */
-    private static final char[] SYMBOLS = {'D', 'M', 'S', '#'};
+    private static final int[] SYMBOLS = {'D', 'M', 'S', '#', '?'};
 
     /**
      * Defines constants that are used as attribute keys in the iterator returned from
@@ -254,6 +278,13 @@ public class AngleFormat extends Format 
                  maximumTotalWidth;
 
     /**
+     * A bitmask of optional fields. Optional fields are formatted only if their value is different than zero.
+     * The bit position is given by a {@code *_FIELD} constant, and the actual bitmask is computed by
+     * {@code 1 << *_FIELD}. A value of zero means that no field is optional.
+     */
+    private byte optionalFields;
+
+    /**
      * Characters to insert before the text to format, and after each field.
      * A {@code null} value means that there is nothing to insert.
      */
@@ -282,6 +313,16 @@ public class AngleFormat extends Format 
     private boolean useDecimalSeparator;
 
     /**
+     * If {@code true}, {@link #optionalFields} never apply to fields to leading fields.
+     * If the minutes field is declared optional but the degrees and seconds are formatted,
+     * then minutes will be formatted too un order to reduce the risk of confusion
+     *
+     * {@example Value 12.01 is formatted as <code>12°00′36″</code> if <code>true</code>
+     *           and as <code>12°36″</code> if <code>false</code>.}
+     */
+    private transient boolean showLeadingFields;
+
+    /**
      * Format 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".
@@ -360,14 +401,15 @@ public class AngleFormat extends Format 
     public AngleFormat(final Locale locale) {
         ArgumentChecks.ensureNonNull("locale", locale);
         this.locale = locale;
-        degreesFieldWidth     = 1;
-        minutesFieldWidth     = 2;
-        secondsFieldWidth     = 2;
-        fractionFieldWidth    = 16;  // Number of digits for accurate representation of 1″ ULP.
-        degreesSuffix         = "°";
-        minutesSuffix         = "′";
-        secondsSuffix         = "″";
-        useDecimalSeparator   = true;
+        degreesFieldWidth   = 1;
+        minutesFieldWidth   = 2;
+        secondsFieldWidth   = 2;
+        fractionFieldWidth  = 16;  // Number of digits for accurate representation of 1″ ULP.
+        optionalFields      = (1 << DEGREES_FIELD) | (1 << MINUTES_FIELD) | (1 << SECONDS_FIELD);
+        degreesSuffix       = "°";
+        minutesSuffix       = "′";
+        secondsSuffix       = "″";
+        useDecimalSeparator = true;
     }
 
     /**
@@ -390,6 +432,7 @@ public class AngleFormat extends Format 
      * @throws IllegalArgumentException If the specified pattern is illegal.
      */
     public AngleFormat(final String pattern, final Locale locale) throws IllegalArgumentException {
+        ArgumentChecks.ensureNonEmpty("pattern", pattern);
         ArgumentChecks.ensureNonNull("locale", locale);
         this.locale = locale;
         applyPattern(pattern, SYMBOLS, '.');
@@ -406,137 +449,182 @@ public class AngleFormat extends Format 
      * @see #setMaximumFractionDigits(int)
      */
     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) {
         ArgumentChecks.ensureNonEmpty("pattern", pattern);
-        degreesFieldWidth     = 1;
+        degreesFieldWidth     = 0;
         minutesFieldWidth     = 0;
         secondsFieldWidth     = 0;
         fractionFieldWidth    = 0;
         minimumFractionDigits = 0;
         maximumTotalWidth     = 0;
+        optionalFields        = 0;
         prefix                = null;
         degreesSuffix         = null;
         minutesSuffix         = null;
         secondsSuffix         = null;
+        useDecimalSeparator   = false;
+        applyPattern(pattern, SYMBOLS, '.');
+    }
+
+    /**
+     * Actual implementation of {@link #applyPattern(String)}, as a private method for use by the constructor.
+     * All fields related to the pattern shall be set to 0 or null before this method call.
+     *
+     * @param symbols An array of code points 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 int[] symbols, final int decimalSeparator) {
+        degreesFieldWidth     = 1;
         useDecimalSeparator   = true;
         int expectedField     = PREFIX_FIELD;
         int endPreviousField  = 0;
         boolean parseFinished = false;
         final int length = pattern.length();
-scan:   for (int i=0; i<length;) {
+        for (int i=0; i<length;) {
             /*
              * Examine the first characters in the pattern, skipping the non-reserved ones
              * ("D", "M", "S", "d", "m", "s", "#"). Non-reserved characters will be stored
-             * as suffix later.
+             * as prefix or suffix later.
              */
-            int c          = pattern.codePointAt(i);
-            int charCount  = Character.charCount(c);
-            int upperCaseC = Character.toUpperCase(c);
-            for (int field=DEGREES_FIELD; field<=FRACTION_FIELD; field++) {
-                if (upperCaseC != symbols[field]) {
-                    continue;
-                }
+            int c           = pattern.codePointAt(i);
+            int charCount   = Character.charCount(c);
+            int upperCaseC  = Character.toUpperCase(c);
+            final int field = fieldForSymbol(symbols, upperCaseC);
+            if (field < 0) { // If not a reserved character, continue the search.
+                i += charCount;
+                continue;
+            }
+            /*
+             * 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'. The legal location is 'expectedField'.
+             */
+            final boolean isIntegerField = (c == upperCaseC) && (field != FRACTION_FIELD);
+            if (isIntegerField) {
+                expectedField++;
+            }
+            if (parseFinished || (field != expectedField && field != FRACTION_FIELD)) {
+                throw illegalPattern(pattern);
+            }
+            if (isIntegerField) {
                 /*
-                 * 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 the reserved letter is upper-case, then we found the integer part of a field.
+                 * 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.
                  */
-                final boolean isIntegerField = (c == upperCaseC) && (field != FRACTION_FIELD);
-                if (isIntegerField) {
-                    expectedField++;
-                }
-                if (parseFinished || (field != expectedField && field != FRACTION_FIELD)) {
-                    throw new IllegalArgumentException(Errors.format(
-                            Errors.Keys.IllegalFormatPatternForClass_2, Angle.class, pattern));
-                }
-                if (isIntegerField) {
-                    /*
-                     * 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.
-                     */
-                    final String previousSuffix = (i > endPreviousField) ? pattern.substring(endPreviousField, i) : null;
-                    int width = 1;
-                    while ((i += charCount) < length && pattern.codePointAt(i) == c) {
-                        width++;
-                    }
-                    final byte wb = toByte(width);
-                    switch (field) {
-                        case DEGREES_FIELD: prefix        = previousSuffix; degreesFieldWidth = wb; break;
-                        case MINUTES_FIELD: degreesSuffix = previousSuffix; minutesFieldWidth = wb; break;
-                        case SECONDS_FIELD: minutesSuffix = previousSuffix; secondsFieldWidth = wb; break;
-                        default: throw new AssertionError(field);
-                    }
-                } else {
-                    /*
-                     * If the reserved letter is lower-case or the symbol for optional fraction
-                     * digit, 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 == endPreviousField) {
-                        useDecimalSeparator = false;
-                    } else {
-                        final int b = pattern.codePointAt(endPreviousField);
-                        if (b != decimalSeparator || endPreviousField + Character.charCount(b) != i) {
-                            throw new IllegalArgumentException(Errors.format(
-                                    Errors.Keys.IllegalFormatPatternForClass_2, Angle.class, pattern));
-                        }
-                    }
-                    int width = 1;
-                    while ((i += charCount) < length) {
-                        final int fc = pattern.codePointAt(i);
-                        if (fc != c) {
-                            if (fc != symbols[FRACTION_FIELD]) break;
-                            // Switch the search from mandatory to optional digits.
-                            minimumFractionDigits = toByte(width);
-                            charCount = Character.charCount(c = fc);
+                String previousSuffix = null;
+                if (endPreviousField < i) {
+                    int endPreviousSuffix = i;
+                    if (pattern.codePointBefore(endPreviousSuffix) == symbols[OPTIONAL_FIELD]) {
+                        // If we find the '?' character, then the previous field is optional.
+                        if (--endPreviousSuffix == endPreviousField) {
+                            throw illegalPattern(pattern);
                         }
-                        width++;
+                        optionalFields |= (1 << (field - 1));
                     }
-                    fractionFieldWidth = toByte(width);
-                    if (c != symbols[FRACTION_FIELD]) {
-                        // The pattern contains only mandatory digits.
-                        minimumFractionDigits = fractionFieldWidth;
-                    } else if (!useDecimalSeparator) {
-                        // Variable number of digits not allowed if there is no decimal separator.
-                        throw new IllegalArgumentException(Errors.format(Errors.Keys.RequireDecimalSeparator));
-                    }
-                    parseFinished = true;
+                    previousSuffix = pattern.substring(endPreviousField, endPreviousSuffix);
+                }
+                int width = 1;
+                while ((i += charCount) < length && pattern.codePointAt(i) == c) {
+                    width++;
+                }
+                final byte wb = toByte(width);
+                switch (field) {
+                    case DEGREES_FIELD: prefix        = previousSuffix; degreesFieldWidth = wb; break;
+                    case MINUTES_FIELD: degreesSuffix = previousSuffix; minutesFieldWidth = wb; break;
+                    case SECONDS_FIELD: minutesSuffix = previousSuffix; secondsFieldWidth = wb; break;
+                    default: throw new AssertionError(field);
+                }
+            } else {
+                /*
+                 * If the reserved letter is lower-case or the symbol for optional fraction digit,
+                 * then the part before that letter will be the decimal separator rather than the
+                 * suffix of previous field. The number of occurrences of the lower-case letter will
+                 * be the precision of the fraction part.
+                 */
+                if (i == endPreviousField) {
+                    useDecimalSeparator = false;
+                } else {
+                    final int b = pattern.codePointAt(endPreviousField);
+                    if (b != decimalSeparator || endPreviousField + Character.charCount(b) != i) {
+                        throw illegalPattern(pattern);
+                    }
+                }
+                int width = 1;
+                while ((i += charCount) < length) {
+                    final int fc = pattern.codePointAt(i);
+                    if (fc != c) {
+                        if (fc != symbols[FRACTION_FIELD]) break;
+                        // Switch the search from mandatory to optional digits.
+                        minimumFractionDigits = toByte(width);
+                        charCount = Character.charCount(c = fc);
+                    }
+                    width++;
+                }
+                fractionFieldWidth = toByte(width);
+                if (c != symbols[FRACTION_FIELD]) {
+                    // The pattern contains only mandatory digits.
+                    minimumFractionDigits = fractionFieldWidth;
+                } else if (!useDecimalSeparator) {
+                    // Variable number of digits not allowed if there is no decimal separator.
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.RequireDecimalSeparator));
                 }
-                endPreviousField = i;
-                continue scan;
+                parseFinished = true;
             }
-            i += charCount;
+            endPreviousField = i;
         }
+        /*
+         * At this point, we finished parsing the pattern. We may have some trailing characters which have not
+         * been processed by the main loop. Those trailing characters will be the suffix of the last field.
+         */
         if (endPreviousField < length) {
-            final String suffix = pattern.substring(endPreviousField);
+            int endPreviousSuffix = length;
+            if (pattern.codePointBefore(endPreviousSuffix) == symbols[OPTIONAL_FIELD]) {
+                if (--endPreviousSuffix == endPreviousField) {
+                    throw illegalPattern(pattern);
+                }
+                optionalFields |= (1 << expectedField);
+            }
+            final String suffix = pattern.substring(endPreviousField, endPreviousSuffix);
             switch (expectedField) {
                 case DEGREES_FIELD: degreesSuffix = suffix; break;
                 case MINUTES_FIELD: minutesSuffix = suffix; break;
                 case SECONDS_FIELD: secondsSuffix = suffix; break;
                 default: {
                     // Happen if no symbol has been recognized in the pattern.
-                    throw new IllegalArgumentException(Errors.format(
-                            Errors.Keys.IllegalFormatPatternForClass_2, Angle.class, pattern));
+                    throw illegalPattern(pattern);
                 }
             }
         }
     }
 
     /**
+     * Returns the field index for the given upper case character, or -1 if none.
+     *
+     * @param  symbols An array of code points containing the reserved symbols as upper-case letters.
+     * @param  c The symbol to search, as an upper-case character (code point actually).
+     * @return The index of the given character, or -1 if not found.
+     */
+    private static int fieldForSymbol(final int[] symbols, final int c) {
+        for (int field=DEGREES_FIELD; field<=FRACTION_FIELD; field++) {
+            if (c == symbols[field]) {
+                return field;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns an exception for an illegal pattern.
+     */
+    private static IllegalArgumentException illegalPattern(final String pattern) {
+        return new IllegalArgumentException(Errors.format(
+                Errors.Keys.IllegalFormatPatternForClass_2, Angle.class, pattern));
+    }
+
+    /**
      * Returns the pattern used for parsing and formatting angles.
      * See class description for an explanation of how patterns work.
      *
@@ -550,14 +638,16 @@ scan:   for (int i=0; i<length;) {
     }
 
     /**
-     * Actual implementation of {@link #toPattern()}.
+     * Actual implementation of {@link #toPattern()} and {@code toLocalizedPattern()}
+     * (the later method may be provided in a future SIS version).
      *
-     * @param symbols An array of 3 characters containing the reserved symbols as upper-case letters.
+     * @param symbols An array of code points 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.
      */
-    private String toPattern(final char[] symbols, final int decimalSeparator) {
-        char symbol = 0;
+    private String toPattern(final int[] symbols, final int decimalSeparator) {
+        int symbol = 0;
+        boolean previousWasOptional = false;
         final StringBuilder buffer = new StringBuilder();
         for (int field=DEGREES_FIELD; field<=FRACTION_FIELD; field++) {
             final String previousSuffix;
@@ -587,26 +677,33 @@ scan:   for (int i=0; i<length;) {
                         if (width == optional) {
                             symbol = symbols[FRACTION_FIELD];
                         }
-                        buffer.append(symbol);
+                        buffer.appendCodePoint(symbol);
                     }
                     while (--width > 0);
                 }
-                if (previousSuffix != null) {
-                    buffer.append(previousSuffix);
-                }
-                break; // We are done.
+                /*
+                 * The code for writing the suffix is common to this "if" case (the fraction part of
+                 * the pattern) and the "normal" case below. So we write the suffix outside the "if"
+                 * block and will exit the main loop immediately after that.
+                 */
             }
-            /*
-             * This is the normal part of the loop, before the final fractional part handled
-             * in the above block. Write the suffix of the previous field, then the pattern
-             * for the integer part of degrees, minutes or second field.
-             */
             if (previousSuffix != null) {
                 buffer.append(previousSuffix);
             }
+            if (previousWasOptional) {
+                buffer.appendCodePoint(symbols[OPTIONAL_FIELD]);
+            }
+            if (width <= 0) {
+                break; // The "if" case above has been executed for writing the fractional part, so we are done.
+            }
+            /*
+             * This is the main part of the loop, before the final fractional part handled in the above "if" case.
+             * Write the pattern for the integer part of degrees, minutes or second field.
+             */
             symbol = symbols[field];
-            do buffer.append(symbol);
+            do buffer.appendCodePoint(symbol);
             while (--width > 0);
+            previousWasOptional = (optionalFields & (1 << field)) != 0;
         }
         return buffer.toString();
     }
@@ -809,32 +906,44 @@ scan:   for (int i=0; i<length;) {
             }
             return toAppendTo;
         }
-        double degrees = angle;
         /*
          * Computes the numerical values of minutes and seconds fields.
          * If those fiels are not written, then store NaN.
          */
+        double degrees = angle;
         double minutes = NaN;
         double seconds = NaN;
         if (minutesFieldWidth != 0 && !isNaN(angle)) {
             minutes = abs(degrees - (degrees = truncate(degrees))) * 60;
+            final double p = pow10(fractionFieldWidth);
             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;
+                seconds = rint(seconds * p) / p; // Correction for rounding errors.
+                if (seconds >= 60) { // We do not expect > 60 (only == 60), but let be safe.
+                    seconds = 0;
+                    minutes++;
+                }
             } else {
-                final double puissance = pow10(fractionFieldWidth);
-                minutes = rint(minutes * puissance) / puissance;
+                minutes = rint(minutes * p) / p; // Correction for rounding errors.
+            }
+            if (minutes >= 60) { // We do not expect > 60 (only == 60), but let be safe.
+                minutes = 0;
+                degrees += Math.signum(angle);
+            }
+            // Note: a previous version was doing a unconditional addition to the 'degrees' variable,
+            // in the form 'degrees += correction'. However -0.0 + 0 == +0.0, while we really need to
+            // preserve the sign of negative zero. See [SIS-120].
+        }
+        /*
+         * Avoid formatting values like 12.01°N as 12°36″N because of the risk of confusion.
+         * In such cases, force the formatting of minutes field as in 12°00′36″.
+         */
+        byte effectiveOptionalFields = optionalFields;
+        if (showLeadingFields) {
+            effectiveOptionalFields &= ~(1 << DEGREES_FIELD);
+            if (minutes == 0 && ((effectiveOptionalFields & (1 << SECONDS_FIELD)) == 0 || seconds != 0)) {
+                effectiveOptionalFields &= ~(1 << MINUTES_FIELD);
             }
-            final double correction = truncate(minutes / 60);
-            minutes -= correction * 60;
-            degrees += correction;
         }
         /*
          * At this point the 'degrees', 'minutes' and 'seconds' variables contain the final values
@@ -853,11 +962,10 @@ scan:   for (int i=0; i<length;) {
             }
         }
         /*
-         * Formats fields in a loop from DEGREES_FIELD to SECONDS_FIELD inclusive.
-         * The first part of the loop will configure the NumberFormat, but without
-         * writing anything yet (ignoring the prefix written before the loop).
+         * The following loop will format fields from DEGREES_FIELD to SECONDS_FIELD inclusive.
+         * The NumberFormat will be reconfigured at each iteration.
          */
-        int field = DEGREES_FIELD;
+        int field = PREFIX_FIELD;
         if (prefix != null) {
             toAppendTo.append(prefix);
         }
@@ -867,12 +975,26 @@ scan:   for (int i=0; i<length;) {
             int    width;
             double value;
             String suffix;
-            switch (field) {
+            switch (++field) {
                 case DEGREES_FIELD: value=degrees; width=degreesFieldWidth; suffix=degreesSuffix; hasMore=(minutesFieldWidth != 0); break;
                 case MINUTES_FIELD: value=minutes; width=minutesFieldWidth; suffix=minutesSuffix; hasMore=(secondsFieldWidth != 0); break;
                 case SECONDS_FIELD: value=seconds; width=secondsFieldWidth; suffix=secondsSuffix; hasMore=false; break;
                 default: throw new AssertionError(field);
             }
+            /*
+             * If the value is zero and the field is optional, propagate the sign to the next field
+             * and skip the whole field. Otherwise process to the formatting of current field.
+             */
+            if (value == 0 && (effectiveOptionalFields & (1 << field)) != 0) {
+                switch (field) {
+                    case DEGREES_FIELD: minutes = Math.copySign(minutes, degrees); break;
+                    case MINUTES_FIELD: seconds = Math.copySign(seconds, minutes); break;
+                }
+                continue;
+            }
+            /*
+             * Configure the NumberFormat for the number of digits to write, but do not write anything yet.
+             */
             if (hasMore) {
                 numberFormat.setMinimumIntegerDigits(width);
                 numberFormat.setMaximumFractionDigits(0);
@@ -883,9 +1005,10 @@ scan:   for (int i=0; i<length;) {
                      * If we are required to fit the formatted angle in some maximal total width
                      * (i.e. the user called the setMaximumWidth(int) method), compute the space
                      * available for fraction digits after we removed the space for the integer
-                     * digits, the decimal separator (this is the -1 below) and the suffix.
+                     * digits, the decimal separator (this is the +1 below) and the suffix.
                      */
-                    int available = maximumTotalWidth - toAppendTo.codePointCount(offset, toAppendTo.length()) - width - 1;
+                    int available = maximumTotalWidth - toAppendTo.codePointCount(offset, toAppendTo.length());
+                    available -= (width + 1); // Remove the amount of code points that we plan to write.
                     if (suffix != null) {
                         width -= suffix.length();
                     }
@@ -932,7 +1055,6 @@ scan:   for (int i=0; i<length;) {
                 pos.setBeginIndex(startPosition);
                 pos.setEndIndex(toAppendTo.length());
             }
-            field++;
         } while (hasMore);
         return toAppendTo;
     }
@@ -992,7 +1114,12 @@ scan:   for (int i=0; i<length;) {
     private StringBuffer format(final double angle, StringBuffer toAppendTo,
             final FieldPosition pos, final char positiveSuffix, final char negativeSuffix)
     {
-        toAppendTo = format(abs(angle), toAppendTo, pos);
+        try {
+            showLeadingFields = true;
+            toAppendTo = format(abs(angle), toAppendTo, pos);
+        } finally {
+            showLeadingFields = false;
+        }
         final int startPosition = toAppendTo.length();
         final char suffix = isNegative(angle) ? negativeSuffix : positiveSuffix;
         toAppendTo.append(suffix);
@@ -1633,9 +1760,9 @@ BigBoss:    switch (skipSuffix(source, p
      */
     @Override
     public int hashCode() {
-        return Objects.hash(degreesFieldWidth, minutesFieldWidth, secondsFieldWidth,
-                fractionFieldWidth, minimumFractionDigits, useDecimalSeparator, isFallbackAllowed,
-                locale, prefix, degreesSuffix, minutesSuffix, secondsSuffix) ^ (int) serialVersionUID;
+        return Objects.hash(degreesFieldWidth, minutesFieldWidth, secondsFieldWidth, fractionFieldWidth,
+                minimumFractionDigits, useDecimalSeparator, isFallbackAllowed, optionalFields, locale,
+                prefix, degreesSuffix, minutesSuffix, secondsSuffix) ^ (int) serialVersionUID;
     }
 
     /**
@@ -1657,14 +1784,14 @@ BigBoss:    switch (skipSuffix(source, p
                    minimumFractionDigits == cast.minimumFractionDigits &&
                    useDecimalSeparator   == cast.useDecimalSeparator   &&
                    isFallbackAllowed     == cast.isFallbackAllowed     &&
+                   optionalFields        == cast.optionalFields        &&
                    Objects.equals(locale,        cast.locale)          &&
                    Objects.equals(prefix,        cast.prefix)          &&
                    Objects.equals(degreesSuffix, cast.degreesSuffix)   &&
                    Objects.equals(minutesSuffix, cast.minutesSuffix)   &&
                    Objects.equals(secondsSuffix, cast.secondsSuffix);
-        } else {
-            return false;
         }
+        return false;
     }
 
     /**

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -483,15 +483,16 @@ public final class Numbers extends Stati
     }
 
     /**
-     * Casts a number to the specified class. The class must by one of {@link Byte},
+     * Casts a number to the specified type. The target type can be one of {@link Byte},
      * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double},
      * {@link BigInteger} or {@link BigDecimal}.
      * This method makes the following choice:
      *
      * <ul>
-     *   <li>If the given type is {@code Double.class}, then this method returns
+     *   <li>If the given value is {@code null} or an instance of the given type, then it is returned unchanged.</li>
+     *   <li>Otherwise if the given type is {@code Double.class}, then this method returns
      *       <code>{@linkplain Double#valueOf(double) Double.valueOf}(number.doubleValue())</code>;</li>
-     *   <li>If the given type is {@code Float.class}, then this method returns
+     *   <li>Otherwise if the given type is {@code Float.class}, then this method returns
      *       <code>{@linkplain Float#valueOf(float) Float.valueOf}(number.floatValue())</code>;</li>
      *   <li>And likewise for all remaining known types.</li>
      * </ul>

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/Pooled.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -326,6 +326,10 @@ abstract class Pooled {
                                 mask |= Context.SUBSTITUTE_LANGUAGE;
                             } else if (CharSequences.equalsIgnoreCase(substitute, "country")) {
                                 mask |= Context.SUBSTITUTE_COUNTRY;
+                            } else if (CharSequences.equalsIgnoreCase(substitute, "filename")) {
+                                mask |= Context.SUBSTITUTE_FILENAME;
+                            } else if (CharSequences.equalsIgnoreCase(substitute, "mimetype")) {
+                                mask |= Context.SUBSTITUTE_MIMETYPE;
                             }
                         }
                     }
@@ -369,9 +373,11 @@ abstract class Pooled {
             if (name.equals(XML.WARNING_LISTENER)) return warningListener;
             if (name.equals(XML.STRING_SUBSTITUTES)) {
                 int n = 0;
-                final String[] substitutes = new String[2];
+                final String[] substitutes = new String[4];
                 if ((bitMasks & Context.SUBSTITUTE_LANGUAGE) != 0) substitutes[n++] = "language";
                 if ((bitMasks & Context.SUBSTITUTE_COUNTRY)  != 0) substitutes[n++] = "country";
+                if ((bitMasks & Context.SUBSTITUTE_FILENAME) != 0) substitutes[n++] = "filename";
+                if ((bitMasks & Context.SUBSTITUTE_MIMETYPE) != 0) substitutes[n++] = "mimetype";
                 return (n != 0) ? ArraysExt.resize(substitutes, n) : null;
             }
             else {

Modified: sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/main/java/org/apache/sis/xml/XML.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -237,8 +237,10 @@ public final class XML extends Static {
      * The value for this property shall be a {@code String[]} array of any of the following values:
      *
      * <ul>
-     *   <li>"{@code language}"</li>
-     *   <li>"{@code country}"</li>
+     *   <li>"{@code language}" for substituting {@code <gmd:LanguageCode>} elements</li>
+     *   <li>"{@code country}"  for substituting {@code <gmd:Country>} elements</li>
+     *   <li>"{@code filename}" for substituting {@code <gmx:FileName>} elements</li>
+     *   <li>"{@code mimetype}" for substituting {@code <gmx:MimeFileType>} elements</li>
      * </ul>
      *
      * {@section Example}

Modified: sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/internal/converter/StringConverterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/internal/converter/StringConverterTest.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/internal/converter/StringConverterTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/internal/converter/StringConverterTest.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -187,7 +187,8 @@ public final strictfp class StringConver
     @Test
     public void testAngle() {
         final ObjectConverter<String,Angle> c = new StringConverter.Angle();
-        runInvertibleConversion(c, "42°30′00″", new Angle(42.5));
+        runInvertibleConversion(c, "42°30′",       new Angle(42.5));
+        runInvertibleConversion(c, "42°30′56.25″", new Angle(42.515625));
         tryUnconvertibleValue(c);
         assertSerializedEquals(c);
     }

Modified: sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -56,8 +56,10 @@ public final strictfp class MathFunction
      */
     @Test
     public void testTruncate() {
-        assertEquals(+4, truncate(+4.9), 0);
-        assertEquals(-4, truncate(-4.9), 0);
+        assertEquals(+4.0, truncate(+4.9), 0);
+        assertEquals(-4.0, truncate(-4.9), 0);
+        assertEquals(+0.0, truncate(+0.1), 0);
+        assertEquals(-0.0, truncate(-0.1), 0);
         assertEquals("Positive zero",
                 Double.doubleToLongBits(+0.0),
                 Double.doubleToLongBits(truncate(+0.5)));

Modified: sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -19,6 +19,7 @@ package org.apache.sis.measure;
 import java.util.Locale;
 import java.text.FieldPosition;
 import java.text.AttributedCharacterIterator;
+import java.text.ParseException;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
@@ -33,7 +34,7 @@ import static org.apache.sis.test.TestUt
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3 (derived from geotk-2.0)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 @DependsOn({
@@ -60,6 +61,24 @@ public final strictfp class AngleFormatT
     }
 
     /**
+     * Tests a {@code '?'} symbol without suffix.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testIllegalOptionalField() {
+        final AngleFormat f = new AngleFormat(Locale.CANADA);
+        f.applyPattern("DD°MM?SS.m″");
+    }
+
+    /**
+     * Tests a {@code '?'} symbol without suffix.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testIllegalOptionalLastField() {
+        final AngleFormat f = new AngleFormat(Locale.CANADA);
+        f.applyPattern("DD°MM?");
+    }
+
+    /**
      * Tests using {@link Locale#CANADA}.
      */
     @Test
@@ -73,6 +92,7 @@ public final strictfp class AngleFormatT
         assertEquals("-12.247°",  formatAndParse(f, new Angle   (-12.247)));
         assertEquals( "13.214°N", formatAndParse(f, new Latitude( 13.214)));
         assertEquals( "12.782°S", formatAndParse(f, new Latitude(-12.782)));
+        assertEquals("-00.010°",  formatAndParse(f, new Angle   (-0.01)));
     }
 
     /**
@@ -83,9 +103,10 @@ public final strictfp class AngleFormatT
         final AngleFormat f = new AngleFormat("DD.ddd°", Locale.FRANCE);
         assertEquals(3, f.getMinimumFractionDigits());
         assertEquals(3, f.getMaximumFractionDigits());
-        assertEquals( "DD.ddd°", f.toPattern());
-        assertEquals("19,457°E", formatAndParse(f, new Longitude( 19.457)));
-        assertEquals("78,124°S", formatAndParse(f, new Latitude (-78.124)));
+        assertEquals( "DD.ddd°",  f.toPattern());
+        assertEquals( "19,457°E", formatAndParse(f, new Longitude( 19.457)));
+        assertEquals( "78,124°S", formatAndParse(f, new Latitude (-78.124)));
+        assertEquals("-00,010°",  formatAndParse(f, new Angle    (-0.01)));
     }
 
     /**
@@ -96,9 +117,10 @@ public final strictfp class AngleFormatT
         final AngleFormat f = new AngleFormat("DDddd", Locale.CANADA);
         assertEquals(3, f.getMinimumFractionDigits());
         assertEquals(3, f.getMaximumFractionDigits());
-        assertEquals("DDddd",  f.toPattern());
-        assertEquals("19457E", formatAndParse(f, new Longitude( 19.457)));
-        assertEquals("78124S", formatAndParse(f, new Latitude (-78.124)));
+        assertEquals( "DDddd",  f.toPattern());
+        assertEquals( "19457E", formatAndParse(f, new Longitude( 19.457)));
+        assertEquals( "78124S", formatAndParse(f, new Latitude (-78.124)));
+        assertEquals("-00010",  formatAndParse(f, new Angle    (-0.01)));
     }
 
     /**
@@ -113,6 +135,7 @@ public final strictfp class AngleFormatT
         assertEquals( "DD°MM.m", f.toPattern());
         assertEquals( "12°30.0", formatAndParse(f, new Angle( 12.50)));
         assertEquals("-10°15.0", formatAndParse(f, new Angle(-10.25)));
+        assertEquals("-00°00.6", formatAndParse(f, new Angle( -0.01)));
     }
 
     /**
@@ -127,6 +150,74 @@ public final strictfp class AngleFormatT
         assertEquals( "DD°MM′SS.sss″", f.toPattern());
         assertEquals( "12°30′56.250″", formatAndParse(f, new Angle( 12.515625)));
         assertEquals("-12°30′56.250″", formatAndParse(f, new Angle(-12.515625)));
+        assertEquals("-00°00′36.000″", formatAndParse(f, new Angle( -0.01)));
+    }
+
+    /**
+     * Tests values that have to be rounded, especially the values near zero.
+     */
+    @Test
+    @DependsOnMethod("testDegreeMinutesSeconds")
+    public void testRounding() {
+        final AngleFormat f = new AngleFormat("DD°MM′SS.sss″", Locale.CANADA);
+        assertEquals( "01°00′00.000″", f.format(new Angle(+(59 + (59.9999 / 60)) / 60)));
+        assertEquals("-01°00′00.000″", f.format(new Angle(-(59 + (59.9999 / 60)) / 60)));
+        assertEquals("-00°59′59.999″", f.format(new Angle(-(59 + (59.9988 / 60)) / 60)));
+    }
+
+    /**
+     * Tests with optional minutes and seconds fields.
+     */
+    @Test
+    @DependsOnMethod("testDegreeMinutesSeconds")
+    public void testOptionalFields() {
+        final AngleFormat f = new AngleFormat(Locale.CANADA);
+        assertEquals("D°?MM′?SS.################″?", f.toPattern());
+        assertEquals("12°",          formatAndParse(f, new Angle(12)));
+        assertEquals("12°30′",       formatAndParse(f, new Angle(12.5)));
+        assertEquals("12°36″",       formatAndParse(f, new Angle(12.01)));
+        assertEquals("12°00′36″N",   formatAndParse(f, new Latitude(12.01)));
+        assertEquals("12°30′56.25″", formatAndParse(f, new Angle(12.515625)));
+        assertEquals("-36″",         formatAndParse(f, new Angle(-0.01)));
+        assertEquals("0°00′36″S",    formatAndParse(f, new Latitude(-0.01)));
+    }
+
+    /**
+     * Tests the example provided in the {@link AngleFormat} javadoc.
+     *
+     * @throws ParseException If a string can not be parsed.
+     */
+    @Test
+    @DependsOnMethod("testOptionalFields")
+    public void testJavadocExamples() throws ParseException {
+        final AngleFormat f = new AngleFormat(Locale.CANADA);
+        testExample(f, "DD°MM′SS.#″",   "48°30′00″", "-12°31′52.5″", 0.000);
+        testExample(f, "DD°MM′",        "48°30′",    "-12°32′",      0.003);
+        testExample(f, "DD.ddd",        "48.500",    "-12.531",      2.500);
+        testExample(f, "DD.###",        "48.5",      "-12.531",      2.500);
+        testExample(f, "DDMM",          "4830",      "-1232",        0.003);
+        testExample(f, "DDMMSSs",       "4830000",   "-1231525",     0.000);
+        testExample(f, "DD°MM′?SS.s″?", "48°30′",    "-12°31′52.5″", 0.000);
+    }
+
+    /**
+     * Tests a single line of Javadoc examples.
+     *
+     * @param f       The angle format to test.
+     * @param pattern The pattern to apply for the test.
+     * @param e1      The expected string value of 48.5.
+     * @param e2      The expected string value of -12.53125.
+     * @param eps     The tolerance for comparing the parsed value of {@code e2}.
+     */
+    private static void testExample(final AngleFormat f, final String pattern, final String e1, final String e2,
+            final double eps) throws ParseException
+    {
+        f.applyPattern(pattern);
+        assertEquals("toPattern()", pattern, f.toPattern());
+        assertEquals("format(double)", e1,       f.format(48.5));
+        assertEquals("format(double)", e2,       f.format(-12.53125));
+        assertEquals("parse(String)",  48.5,     f.parse(e1).degrees(), 0.0);
+        assertEquals("parse(String)", -12.53125, f.parse(e2).degrees(), eps);
     }
 
     /**
@@ -160,7 +251,7 @@ public final strictfp class AngleFormatT
     @Test
     @DependsOnMethod("testOptionalFractionDigits")
     public void testSetMaximumWidth() {
-        final AngleFormat f = new AngleFormat(Locale.CANADA);
+        final AngleFormat f = new AngleFormat("D°MM′SS.################″", Locale.CANADA);
         assertEquals("D°MM′SS.################″", f.toPattern());
 
         f.setMaximumWidth(12);

Modified: sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java [UTF-8] (original)
+++ sis/branches/JDK6/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -29,7 +29,7 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3 (derived from geotk-2.0)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 @DependsOn(AngleFormatTest.class)
@@ -39,9 +39,11 @@ public final strictfp class AngleTest ex
      */
     @Test
     public void testToString() {
-        assertEquals("45°30′00″",  new Angle    (45.5).toString());
-        assertEquals("45°30′00″N", new Latitude (45.5).toString());
-        assertEquals("45°30′00″E", new Longitude(45.5).toString());
+        assertEquals("45°",           new Angle    (45  ).toString());
+        assertEquals("45°30′",        new Angle    (45.5).toString());
+        assertEquals("45°30′N",       new Latitude (45.5).toString());
+        assertEquals("45°30′E",       new Longitude(45.5).toString());
+        assertEquals("45°30′56.25″E", new Longitude(45.515625).toString());
 
         // Angle out of expected range.
         assertEquals( "720.0°E", new Longitude( 720).toString());
@@ -59,6 +61,7 @@ public final strictfp class AngleTest ex
      */
     @Test
     public void testParse() {
+        assertEquals(new Angle    (45.5), new Angle    ("45°30′"));
         assertEquals(new Angle    (45.5), new Angle    ("45°30′00″"));
         assertEquals(new Latitude (45.5), new Latitude ("45°30′00″N"));
         assertEquals(new Longitude(45.5), new Longitude("45°30′00″E"));
@@ -75,13 +78,13 @@ public final strictfp class AngleTest ex
      */
     @Test
     public void testFormatTo() {
-        assertEquals("5°30′00″",  String.format(Locale.CANADA,  "%s",    new Angle   (5.5)));
-        assertEquals("5°30′00″N", String.format(Locale.CANADA,  "%s",    new Latitude(5.5)));
-        assertEquals("  5°30′",   String.format(Locale.CANADA,  "%7.5s", new Angle   (5.5)));
-        assertEquals("  5.5°N",   String.format(Locale.CANADA,  "%7.5s", new Latitude(5.5)));
-        assertEquals("  5,5°N",   String.format(Locale.FRANCE,  "%7.5s", new Latitude(5.5)));
-        assertEquals("5,5°N  ",   String.format(Locale.FRANCE, "%-7.5s", new Latitude(5.5)));
-        assertEquals("N",         String.format(Locale.FRANCE,  "%1.1s", new Latitude(5.5)));
-        assertEquals(" ",         String.format(Locale.FRANCE,  "%1.0s", new Latitude(5.5)));
+        assertEquals("5°30′36″",  String.format(Locale.CANADA,  "%s",    new Angle   (5.51)));
+        assertEquals("5°30′36″N", String.format(Locale.CANADA,  "%s",    new Latitude(5.51)));
+        assertEquals("  5°31′",   String.format(Locale.CANADA,  "%7.5s", new Angle   (5.51)));
+        assertEquals("  5.5°N",   String.format(Locale.CANADA,  "%7.5s", new Latitude(5.51)));
+        assertEquals("  5,5°N",   String.format(Locale.FRANCE,  "%7.5s", new Latitude(5.51)));
+        assertEquals("5,5°N  ",   String.format(Locale.FRANCE, "%-7.5s", new Latitude(5.51)));
+        assertEquals("N",         String.format(Locale.FRANCE,  "%1.1s", new Latitude(5.51)));
+        assertEquals(" ",         String.format(Locale.FRANCE,  "%1.0s", new Latitude(5.51)));
     }
 }

Modified: sis/branches/JDK6/ide-project/NetBeans/nbproject/project.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/ide-project/NetBeans/nbproject/project.properties?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/ide-project/NetBeans/nbproject/project.properties [ISO-8859-1] (original)
+++ sis/branches/JDK6/ide-project/NetBeans/nbproject/project.properties [ISO-8859-1] Thu Aug 15 17:33:23 2013
@@ -72,7 +72,7 @@ jdom1.version        = 1.0
 jdom2.version        = 2.0.4
 jee.version          = 6.0
 osgi.version         = 5.0.0
-netcdf.version       = 4.3.17
+netcdf.version       = 4.3.18
 joda-time.version    = 2.0
 httpclient.version   = 3.1
 slf4j.version        = 1.6.4

Modified: sis/branches/JDK6/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/pom.xml?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/pom.xml (original)
+++ sis/branches/JDK6/pom.xml Thu Aug 15 17:33:23 2013
@@ -374,7 +374,7 @@ Apache SIS is a free software, Java lang
        =================================================================== -->
   <properties>
     <geoapi.version>3.1-M04</geoapi.version>
-    <netcdf.version>4.3.17</netcdf.version>
+    <netcdf.version>4.3.18</netcdf.version>
     <findbugs.version>2.5.2</findbugs.version>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <website.encoding>UTF-8</website.encoding>

Modified: sis/branches/JDK6/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK6/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java?rev=1514390&r1=1514389&r2=1514390&view=diff
==============================================================================
--- sis/branches/JDK6/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java [UTF-8] (original)
+++ sis/branches/JDK6/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java [UTF-8] Thu Aug 15 17:33:23 2013
@@ -113,10 +113,10 @@ public final strictfp class MetadataRead
             "  │   ├─Spatial representation type……………………………… Grid\n" +
             "  │   ├─Extent\n" +
             "  │   │   ├─Geographic element\n" +
-            "  │   │   │   ├─West bound longitude…………………………… -180.0\n" +
-            "  │   │   │   ├─East bound longitude…………………………… 180.0\n" +
-            "  │   │   │   ├─South bound latitude…………………………… -90.0\n" +
-            "  │   │   │   ├─North bound latitude…………………………… 90.0\n" +
+            "  │   │   │   ├─West bound longitude…………………………… 180°W\n" +
+            "  │   │   │   ├─East bound longitude…………………………… 180°E\n" +
+            "  │   │   │   ├─South bound latitude…………………………… 90°S\n" +
+            "  │   │   │   ├─North bound latitude…………………………… 90°N\n" +
             "  │   │   │   └─Extent type code……………………………………… true\n" +
             "  │   │   └─Vertical element\n" +
             "  │   │       ├─Minimum value……………………………………………… 0.0\n" +



Mime
View raw message