8025828: Late binding of Chronology to appendValueReduced
authorrriggs
Fri, 18 Oct 2013 16:37:58 -0400
changeset 21296 de1c1faa6f77
parent 21295 5c73446feb1f
child 21297 813b7d784410
8025828: Late binding of Chronology to appendValueReduced Summary: Add a listener to the parseContext called when the Chronology changes Reviewed-by: sherman
jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java
jdk/src/share/classes/java/time/format/DateTimeParseContext.java
jdk/test/java/time/test/java/time/format/TestReducedParser.java
--- a/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java	Sat Oct 19 00:05:42 2013 +0400
+++ b/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java	Fri Oct 18 16:37:58 2013 -0400
@@ -557,12 +557,13 @@
      * a two digit year parse will be in the range 1950-01-01 to 2049-12-31.
      * Only the year would be extracted from the date, thus a base date of
      * 1950-08-25 would also parse to the range 1950-01-01 to 2049-12-31.
-     * This behaviour is necessary to support fields such as week-based-year
+     * This behavior is necessary to support fields such as week-based-year
      * or other calendar systems where the parsed value does not align with
      * standard ISO years.
      * <p>
      * The exact behavior is as follows. Parse the full set of fields and
-     * determine the effective chronology. Then convert the base date to the
+     * determine the effective chronology using the last chronology if
+     * it appears more than once. Then convert the base date to the
      * effective chronology. Then extract the specified field from the
      * chronology-specific base date and use it to determine the
      * {@code baseValue} used below.
@@ -2809,9 +2810,19 @@
         int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
             int baseValue = this.baseValue;
             if (baseDate != null) {
-                // TODO: effective chrono is inaccurate at this point
                 Chronology chrono = context.getEffectiveChronology();
                 baseValue = chrono.date(baseDate).get(field);
+
+                // In case the Chronology is changed later, add a callback when/if it changes
+                final long initialValue = value;
+                context.addChronoChangedListener(
+                        (_unused) ->  {
+                            /* Repeat the set of the field using the current Chronology
+                             * The success/error position is ignored because the value is
+                             * intentionally being overwritten.
+                             */
+                            setValue(context, initialValue, errorPos, successPos);
+                        });
             }
             int parseLen = successPos - errorPos;
             if (parseLen == minWidth && value >= 0) {
--- a/jdk/src/share/classes/java/time/format/DateTimeParseContext.java	Sat Oct 19 00:05:42 2013 +0400
+++ b/jdk/src/share/classes/java/time/format/DateTimeParseContext.java	Fri Oct 18 16:37:58 2013 -0400
@@ -61,7 +61,6 @@
  */
 package java.time.format;
 
-import java.time.Duration;
 import java.time.ZoneId;
 import java.time.chrono.Chronology;
 import java.time.chrono.IsoChronology;
@@ -69,6 +68,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Context object used during date and time parsing.
@@ -105,6 +105,10 @@
      * The list of parsed data.
      */
     private final ArrayList<Parsed> parsed = new ArrayList<>();
+    /**
+     * List of Consumers<Chronology> to be notified if the Chronology changes.
+     */
+    private ArrayList<Consumer<Chronology>> chronoListeners = null;
 
     /**
      * Creates a new instance of the context.
@@ -354,12 +358,36 @@
      * <p>
      * This stores the chronology that has been parsed.
      * No validation is performed other than ensuring it is not null.
+     * <p>
+     * The list of listeners is copied and cleared so that each
+     * listener is called only once.  A listener can add itself again
+     * if it needs to be notified of future changes.
      *
      * @param chrono  the parsed chronology, not null
      */
     void setParsed(Chronology chrono) {
         Objects.requireNonNull(chrono, "chrono");
         currentParsed().chrono = chrono;
+        if (chronoListeners != null && !chronoListeners.isEmpty()) {
+            Consumer[] tmp = new Consumer[1];
+            Consumer<Chronology>[] listeners = chronoListeners.toArray(tmp);
+            chronoListeners.clear();
+            for (Consumer<Chronology> l : listeners) {
+                l.accept(chrono);
+            }
+        }
+    }
+
+    /**
+     * Adds a Consumer<Chronology> to the list of listeners to be notified
+     * if the Chronology changes.
+     * @param listener a Consumer<Chronology> to be called when Chronology changes
+     */
+    void addChronoChangedListener(Consumer<Chronology> listener) {
+        if (chronoListeners == null) {
+            chronoListeners = new ArrayList<Consumer<Chronology>>();
+        }
+        chronoListeners.add(listener);
     }
 
     /**
--- a/jdk/test/java/time/test/java/time/format/TestReducedParser.java	Sat Oct 19 00:05:42 2013 +0400
+++ b/jdk/test/java/time/test/java/time/format/TestReducedParser.java	Fri Oct 18 16:37:58 2013 -0400
@@ -78,10 +78,12 @@
 import java.time.chrono.JapaneseChronology;
 import java.time.chrono.MinguoChronology;
 import java.time.chrono.ThaiBuddhistChronology;
+import java.time.chrono.ThaiBuddhistDate;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQueries;
 
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -443,6 +445,52 @@
 
     }
 
+    @Test
+    public void test_reducedWithLateChronoChange() {
+        ThaiBuddhistDate date = ThaiBuddhistDate.of(2543, 1, 1);
+        DateTimeFormatter df
+                = new DateTimeFormatterBuilder()
+                        .appendValueReduced(YEAR, 2, 2, LocalDate.of(2000, 1, 1))
+                        .appendLiteral(" ")
+                        .appendChronologyId()
+                .toFormatter();
+        int expected = date.get(YEAR);
+        String input = df.format(date);
+
+        ParsePosition pos = new ParsePosition(0);
+        TemporalAccessor parsed = df.parseUnresolved(input, pos);
+        assertEquals(pos.getIndex(), input.length(), "Input not parsed completely");
+        assertEquals(pos.getErrorIndex(), -1, "Error index should be -1 (no-error)");
+        int actual = parsed.get(YEAR);
+        assertEquals(actual, expected,
+                String.format("Wrong date parsed, chrono: %s, input: %s",
+                parsed.query(TemporalQueries.chronology()), input));
+
+    }
+
+    @Test
+    public void test_reducedWithLateChronoChangeTwice() {
+        DateTimeFormatter df
+                = new DateTimeFormatterBuilder()
+                        .appendValueReduced(YEAR, 2, 2, LocalDate.of(2000, 1, 1))
+                        .appendLiteral(" ")
+                        .appendChronologyId()
+                        .appendLiteral(" ")
+                        .appendChronologyId()
+                .toFormatter();
+        int expected = 2044;
+        String input = "44 ThaiBuddhist ISO";
+        ParsePosition pos = new ParsePosition(0);
+        TemporalAccessor parsed = df.parseUnresolved(input, pos);
+        assertEquals(pos.getIndex(), input.length(), "Input not parsed completely: " + pos);
+        assertEquals(pos.getErrorIndex(), -1, "Error index should be -1 (no-error)");
+        int actual = parsed.get(YEAR);
+        assertEquals(actual, expected,
+                String.format("Wrong date parsed, chrono: %s, input: %s",
+                parsed.query(TemporalQueries.chronology()), input));
+
+    }
+
     //-----------------------------------------------------------------------
     // Class to structure the test data
     //-----------------------------------------------------------------------