Created
January 31, 2013 21:15
-
-
Save jodastephen/4686522 to your computer and use it in GitHub Desktop.
REJECTED: Manipulator. Patch to discuss ThreeTen issue #115
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java | |
--- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java | |
+++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java | |
@@ -110,6 +110,7 @@ | |
import java.util.Set; | |
import java.util.TimeZone; | |
import java.util.concurrent.ConcurrentHashMap; | |
+import java.util.function.Function; | |
import sun.util.locale.provider.TimeZoneNameUtility; | |
@@ -1449,6 +1450,54 @@ | |
//----------------------------------------------------------------------- | |
/** | |
+ * Appends a manipulator to the builder which has the ability to manipulate | |
+ * the temporal being formatted or parsed. | |
+ * <p> | |
+ * The manipulator function is invoked during parsing and can alter the | |
+ * map of parsed fields, such as adding or removing fields. | |
+ * This is very powerful behavior, which can be used is many different ways. | |
+ * <p> | |
+ * For example, consider a formatter that parses the year, month, day-of-month | |
+ * and day-of-week. In this case, the day-of-week is additional information | |
+ * and not essential to construct a date. But if the day-of-week is not the | |
+ * correct day for the date produced by the year, month and day, then an exception | |
+ * will be thrown. The manipulator could be used to remove the day-of-week, | |
+ * effectively ignoring the parsed value, and thus creating a more lenient parse. | |
+ * <p> | |
+ * Another example, consider a formatter that parses the year, month and day-of-month. | |
+ * Users may enter a day-of-month that is invalid for the year and month. | |
+ * A manipulator could be used to check the year and month and change the day-of-month | |
+ * to the last valid day of the month, effectively converting "June 31st" to "Jun 30th". | |
+ * <p> | |
+ * Using this method can make the formatter thread-unsafe. | |
+ * If the manipulator is thread-safe, then the formatter will remain thread-safe. | |
+ * If the manipulator is not thread-safe, then the formatter must not be shared. | |
+ * <p> | |
+ * The function receives a temporal representing the current state of the parse. | |
+ * The temporal must be queried using the methods of {@code TemporalAccessor}, | |
+ * not using the {@code getFrom}, {@code isSupportedBy} or {@code rangeRefinedBy} | |
+ * methods on {@code TemporalField}. Any exception will terminate the parse. | |
+ * Thus, before querying a field, function implementations should ensure it is supported. | |
+ * <p> | |
+ * The function returns a map. If the map is null, the parse is terminated. | |
+ * If the map is empty, no changes are made. | |
+ * If the map is non-empty, then each entry in the map is processed. | |
+ * If the key is null, a {@code NullPointerException} is thrown. | |
+ * If the value is non-null, then the state of the parser is altered as though | |
+ * using {@code Map.put}. If the value is null, then the state of the parser | |
+ * is altered as though using {@code Map.remove}. | |
+ * | |
+ * @param manipulator the manipulator to add, not null | |
+ * @return this, for chaining, not null | |
+ */ | |
+ public DateTimeFormatterBuilder manipulateParse(Function<TemporalAccessor, Map<TemporalField, Long>> manipulator) { | |
+ Objects.requireNonNull(manipulator, "manipulator"); | |
+ appendInternal(new Manipulator(manipulator)); | |
+ return this; | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
* Appends a printer and/or parser to the internal list handling padding. | |
* | |
* @param pp the printer-parser to add, not null | |
@@ -3444,6 +3493,27 @@ | |
} | |
} | |
+ //------------------------------------------------------------------------- | |
+ /** | |
+ * Manipulator of temporals. | |
+ */ | |
+ static class Manipulator implements DateTimePrinterParser { | |
+ private Function<TemporalAccessor, Map<TemporalField, Long>> manipulator; | |
+ | |
+ Manipulator(Function<TemporalAccessor, Map<TemporalField, Long>> manipulator) { | |
+ this.manipulator = manipulator; | |
+ } | |
+ | |
+ @Override | |
+ public boolean format(DateTimePrintContext context, StringBuilder buf) { | |
+ return true; | |
+ } | |
+ | |
+ @Override | |
+ public int parse(DateTimeParseContext context, CharSequence text, int position) { | |
+ return (context.manipulate(manipulator) ? position : ~position); | |
+ } | |
+ } | |
//------------------------------------------------------------------------- | |
/** | |
diff --git a/src/share/classes/java/time/format/DateTimeParseContext.java b/src/share/classes/java/time/format/DateTimeParseContext.java | |
--- a/src/share/classes/java/time/format/DateTimeParseContext.java | |
+++ b/src/share/classes/java/time/format/DateTimeParseContext.java | |
@@ -74,6 +74,7 @@ | |
import java.util.Locale; | |
import java.util.Map; | |
import java.util.Objects; | |
+import java.util.function.Function; | |
/** | |
* Context object used during date and time parsing. | |
@@ -322,6 +323,31 @@ | |
//----------------------------------------------------------------------- | |
/** | |
+ * Manipulates the parsed temporal. | |
+ * | |
+ * @param manipulator the manipulator of temporals, not null | |
+ * @return true if successful, false if fails | |
+ */ | |
+ boolean manipulate(Function<TemporalAccessor, Map<TemporalField, Long>> manipulator) { | |
+ Parsed data = currentParsed(); | |
+ Map<TemporalField, Long> changes = manipulator.apply(this); | |
+ if (changes == null) { | |
+ return false; | |
+ } | |
+ for (Map.Entry<TemporalField, Long> change : changes.entrySet()) { | |
+ TemporalField changeField = Objects.requireNonNull(change.getKey(), "changeField"); | |
+ Long changeValue = change.getValue(); | |
+ if (changeValue != null) { | |
+ data.fieldValues.put(changeField, changeValue); | |
+ } else { | |
+ data.fieldValues.remove(changeField); | |
+ } | |
+ } | |
+ return true; | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ /** | |
* Returns a {@code DateTimeBuilder} that can be used to interpret | |
* the results of the parse. | |
* <p> | |
diff --git a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java | |
--- a/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java | |
+++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatterBuilder.java | |
@@ -68,6 +68,7 @@ | |
import java.text.ParsePosition; | |
import java.time.LocalDate; | |
+import java.time.YearMonth; | |
import java.time.ZoneOffset; | |
import java.time.format.DateTimeFormatter; | |
import java.time.format.DateTimeFormatterBuilder; | |
@@ -75,6 +76,8 @@ | |
import java.time.format.TextStyle; | |
import java.time.temporal.Temporal; | |
import java.time.temporal.TemporalAccessor; | |
+import java.time.temporal.TemporalField; | |
+import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.Locale; | |
import java.util.Map; | |
@@ -522,6 +525,86 @@ | |
//----------------------------------------------------------------------- | |
//----------------------------------------------------------------------- | |
//----------------------------------------------------------------------- | |
+ @Test | |
+ public void test_appendManipulator_removeOne() { | |
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-') | |
+ .appendValue(DAY_OF_MONTH) | |
+ .manipulateParse(t -> Collections.<TemporalField, Long>singletonMap(MONTH_OF_YEAR, null)); | |
+ DateTimeFormatter f = builder.toFormatter(); | |
+ TemporalAccessor parsed = f.parse("6-8"); | |
+ assertEquals(parsed.isSupported(MONTH_OF_YEAR), false); | |
+ assertEquals(parsed.isSupported(DAY_OF_MONTH), true); | |
+ assertEquals(parsed.getLong(DAY_OF_MONTH), 8); | |
+ } | |
+ | |
+ @Test | |
+ public void test_appendManipulator_addOne() { | |
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-') | |
+ .appendValue(DAY_OF_MONTH) | |
+ .manipulateParse(t -> Collections.<TemporalField, Long>singletonMap(YEAR, 2012L)); | |
+ DateTimeFormatter f = builder.toFormatter(); | |
+ TemporalAccessor parsed = f.parse("6-8"); | |
+ assertEquals(parsed.isSupported(MONTH_OF_YEAR), true); | |
+ assertEquals(parsed.isSupported(DAY_OF_MONTH), true); | |
+ assertEquals(parsed.isSupported(YEAR), true); | |
+ assertEquals(parsed.getLong(MONTH_OF_YEAR), 6); | |
+ assertEquals(parsed.getLong(DAY_OF_MONTH), 8); | |
+ assertEquals(parsed.getLong(YEAR), 2012); | |
+ } | |
+ | |
+ @Test | |
+ public void test_appendManipulator_emptyMapNoChange() { | |
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-') | |
+ .appendValue(DAY_OF_MONTH) | |
+ .manipulateParse(t -> Collections.emptyMap()); | |
+ DateTimeFormatter f = builder.toFormatter(); | |
+ TemporalAccessor parsed = f.parse("6-8"); | |
+ assertEquals(parsed.isSupported(MONTH_OF_YEAR), true); | |
+ assertEquals(parsed.isSupported(DAY_OF_MONTH), true); | |
+ assertEquals(parsed.getLong(MONTH_OF_YEAR), 6); | |
+ assertEquals(parsed.getLong(DAY_OF_MONTH), 8); | |
+ } | |
+ | |
+ @Test | |
+ public void test_appendManipulator_nullMapEndParse() { | |
+ builder.appendValue(MONTH_OF_YEAR).appendLiteral('-') | |
+ .appendValue(DAY_OF_MONTH) | |
+ .manipulateParse(t -> null); | |
+ DateTimeFormatter f = builder.toFormatter(); | |
+ ParsePosition pos = new ParsePosition(0); | |
+ TemporalAccessor parsed = f.parseUnresolved("6-8", pos); | |
+ assertEquals(parsed, null); | |
+ assertEquals(pos.getErrorIndex(), 3); | |
+ } | |
+ | |
+ @Test | |
+ public void test_appendManipulator_checkAndAct() { | |
+ builder.appendValue(YEAR).appendLiteral('-') | |
+ .appendValue(MONTH_OF_YEAR).appendLiteral('-') | |
+ .appendValue(DAY_OF_MONTH) | |
+ .manipulateParse(t -> { | |
+ YearMonth yearMonth = YearMonth.from(t); | |
+ if (yearMonth.isValidDay(t.get(DAY_OF_MONTH))) { | |
+ return Collections.emptyMap(); | |
+ } else { | |
+ return Collections.<TemporalField, Long>singletonMap(DAY_OF_MONTH, (long) yearMonth.lengthOfMonth()); | |
+ } | |
+ }); | |
+ DateTimeFormatter f = builder.toFormatter(); | |
+ assertEquals(LocalDate.parse("2012-2-15", f), LocalDate.of(2012, 2, 15)); | |
+ assertEquals(LocalDate.parse("2012-2-31", f), LocalDate.of(2012, 2, 29)); | |
+ assertEquals(LocalDate.parse("2012-2-28", f), LocalDate.of(2012, 2, 28)); | |
+ assertEquals(LocalDate.parse("2012-6-31", f), LocalDate.of(2012, 6, 30)); | |
+ } | |
+ | |
+ @Test(expectedExceptions=NullPointerException.class) | |
+ public void test_appendManipulator_null() throws Exception { | |
+ builder.manipulateParse(null); | |
+ } | |
+ | |
+ //----------------------------------------------------------------------- | |
+ //----------------------------------------------------------------------- | |
+ //----------------------------------------------------------------------- | |
@Test(groups={"tck"}) | |
public void test_padNext_1arg() throws Exception { | |
builder.appendValue(MONTH_OF_YEAR).padNext(2).appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment