Skip to content

Instantly share code, notes, and snippets.

@jodastephen
Created March 6, 2013 15:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jodastephen/5100331 to your computer and use it in GitHub Desktop.
Save jodastephen/5100331 to your computer and use it in GitHub Desktop.
APPLIED: Merge parsing builder classes and enhance them
# HG changeset patch
# User scolebourne
# Date 1362570460 0
# Node ID c88c08f0effb88814251de6cf980a7d62972ae36
# Parent d35363a560573014ee988db75278887a7fb50c72
Rename instance variable
diff --git a/src/share/classes/java/time/format/DateTimeBuilder.java b/src/share/classes/java/time/format/DateTimeBuilder.java
--- a/src/share/classes/java/time/format/DateTimeBuilder.java
+++ b/src/share/classes/java/time/format/DateTimeBuilder.java
@@ -123,7 +123,7 @@
/**
* The map of date-time fields.
*/
- private final EnumMap<ChronoField, Long> standardFields = new EnumMap<ChronoField, Long>(ChronoField.class);
+ private final EnumMap<ChronoField, Long> chronoFields = new EnumMap<>(ChronoField.class);
/**
* The chronology.
*/
@@ -151,7 +151,7 @@
//-----------------------------------------------------------------------
private Long getFieldValue0(TemporalField field) {
if (field instanceof ChronoField) {
- return standardFields.get(field);
+ return chronoFields.get(field);
} else if (otherFields != null) {
return otherFields.get(field);
}
@@ -183,7 +183,7 @@
private DateTimeBuilder putFieldValue0(TemporalField field, long value) {
if (field instanceof ChronoField) {
- standardFields.put((ChronoField) field, value);
+ chronoFields.put((ChronoField) field, value);
} else {
if (otherFields == null) {
otherFields = new LinkedHashMap<TemporalField, Long>();
@@ -229,21 +229,21 @@
}
private void mergeDate() {
- ChronoLocalDate<?> date = chrono.resolveDate(standardFields);
+ ChronoLocalDate<?> date = chrono.resolveDate(chronoFields);
if (date != null) {
addObject(checkDate(date));
}
}
private ChronoLocalDate<?> checkDate(ChronoLocalDate<?> date) {
- for (ChronoField field : standardFields.keySet()) {
+ for (ChronoField field : chronoFields.keySet()) {
long val1;
try {
val1 = date.getLong(field);
} catch (DateTimeException ex) {
continue;
}
- Long val2 = standardFields.get(field);
+ Long val2 = chronoFields.get(field);
if (val1 != val2) {
throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date);
}
@@ -252,17 +252,17 @@
}
private void mergeTime() {
- if (standardFields.containsKey(CLOCK_HOUR_OF_DAY)) {
- long ch = standardFields.remove(CLOCK_HOUR_OF_DAY);
+ if (chronoFields.containsKey(CLOCK_HOUR_OF_DAY)) {
+ long ch = chronoFields.remove(CLOCK_HOUR_OF_DAY);
addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch);
}
- if (standardFields.containsKey(CLOCK_HOUR_OF_AMPM)) {
- long ch = standardFields.remove(CLOCK_HOUR_OF_AMPM);
+ if (chronoFields.containsKey(CLOCK_HOUR_OF_AMPM)) {
+ long ch = chronoFields.remove(CLOCK_HOUR_OF_AMPM);
addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch);
}
- if (standardFields.containsKey(AMPM_OF_DAY) && standardFields.containsKey(HOUR_OF_AMPM)) {
- long ap = standardFields.remove(AMPM_OF_DAY);
- long hap = standardFields.remove(HOUR_OF_AMPM);
+ if (chronoFields.containsKey(AMPM_OF_DAY) && chronoFields.containsKey(HOUR_OF_AMPM)) {
+ long ap = chronoFields.remove(AMPM_OF_DAY);
+ long hap = chronoFields.remove(HOUR_OF_AMPM);
addFieldValue(HOUR_OF_DAY, ap * 12 + hap);
}
// if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) {
@@ -275,29 +275,29 @@
// long som = timeFields.remove(SECOND_OF_MINUTE);
// addFieldValue(SECOND_OF_DAY, mod * 60 + som);
// }
- if (standardFields.containsKey(NANO_OF_DAY)) {
- long nod = standardFields.remove(NANO_OF_DAY);
+ if (chronoFields.containsKey(NANO_OF_DAY)) {
+ long nod = chronoFields.remove(NANO_OF_DAY);
addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L);
addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
}
- if (standardFields.containsKey(MICRO_OF_DAY)) {
- long cod = standardFields.remove(MICRO_OF_DAY);
+ if (chronoFields.containsKey(MICRO_OF_DAY)) {
+ long cod = chronoFields.remove(MICRO_OF_DAY);
addFieldValue(SECOND_OF_DAY, cod / 1000_000L);
addFieldValue(MICRO_OF_SECOND, cod % 1000_000L);
}
- if (standardFields.containsKey(MILLI_OF_DAY)) {
- long lod = standardFields.remove(MILLI_OF_DAY);
+ if (chronoFields.containsKey(MILLI_OF_DAY)) {
+ long lod = chronoFields.remove(MILLI_OF_DAY);
addFieldValue(SECOND_OF_DAY, lod / 1000);
addFieldValue(MILLI_OF_SECOND, lod % 1000);
}
- if (standardFields.containsKey(SECOND_OF_DAY)) {
- long sod = standardFields.remove(SECOND_OF_DAY);
+ if (chronoFields.containsKey(SECOND_OF_DAY)) {
+ long sod = chronoFields.remove(SECOND_OF_DAY);
addFieldValue(HOUR_OF_DAY, sod / 3600);
addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
addFieldValue(SECOND_OF_MINUTE, sod % 60);
}
- if (standardFields.containsKey(MINUTE_OF_DAY)) {
- long mod = standardFields.remove(MINUTE_OF_DAY);
+ if (chronoFields.containsKey(MINUTE_OF_DAY)) {
+ long mod = chronoFields.remove(MINUTE_OF_DAY);
addFieldValue(HOUR_OF_DAY, mod / 60);
addFieldValue(MINUTE_OF_HOUR, mod % 60);
}
@@ -307,16 +307,16 @@
// addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
// addFieldValue(SECOND_OF_MINUTE, sod % 60);
// addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
- if (standardFields.containsKey(MILLI_OF_SECOND) && standardFields.containsKey(MICRO_OF_SECOND)) {
- long los = standardFields.remove(MILLI_OF_SECOND);
- long cos = standardFields.get(MICRO_OF_SECOND);
+ if (chronoFields.containsKey(MILLI_OF_SECOND) && chronoFields.containsKey(MICRO_OF_SECOND)) {
+ long los = chronoFields.remove(MILLI_OF_SECOND);
+ long cos = chronoFields.get(MICRO_OF_SECOND);
addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000));
}
- Long hod = standardFields.get(HOUR_OF_DAY);
- Long moh = standardFields.get(MINUTE_OF_HOUR);
- Long som = standardFields.get(SECOND_OF_MINUTE);
- Long nos = standardFields.get(NANO_OF_SECOND);
+ Long hod = chronoFields.get(HOUR_OF_DAY);
+ Long moh = chronoFields.get(MINUTE_OF_HOUR);
+ Long som = chronoFields.get(SECOND_OF_MINUTE);
+ Long nos = chronoFields.get(NANO_OF_SECOND);
if (hod != null) {
int hodVal = Math.toIntExact(hod);
if (moh != null) {
@@ -344,7 +344,7 @@
if (field == null) {
return false;
}
- return standardFields.containsKey(field) ||
+ return chronoFields.containsKey(field) ||
(otherFields != null && otherFields.containsKey(field)) ||
(date != null && date.isSupported(field)) ||
(time != null && time.isSupported(field));
@@ -393,7 +393,7 @@
StringBuilder buf = new StringBuilder(128);
buf.append("DateTimeBuilder[");
Map<TemporalField, Long> fields = new HashMap<>();
- fields.putAll(standardFields);
+ fields.putAll(chronoFields);
if (otherFields != null) {
fields.putAll(otherFields);
}
# HG changeset patch
# User scolebourne
# Date 1362572058 0
# Node ID e5c9a73cbf0ff98e9a525f8377782e15b8b5a583
# Parent c88c08f0effb88814251de6cf980a7d62972ae36
Simplify method calls during parse resolution
Hides the builder even more
diff --git a/src/share/classes/java/time/format/DateTimeFormatter.java b/src/share/classes/java/time/format/DateTimeFormatter.java
--- a/src/share/classes/java/time/format/DateTimeFormatter.java
+++ b/src/share/classes/java/time/format/DateTimeFormatter.java
@@ -1322,7 +1322,7 @@
public TemporalAccessor parse(CharSequence text) {
Objects.requireNonNull(text, "text");
try {
- return parseToBuilder(text, null).resolve();
+ return parseResolved0(text, null);
} catch (DateTimeParseException ex) {
throw ex;
} catch (RuntimeException ex) {
@@ -1364,7 +1364,7 @@
Objects.requireNonNull(text, "text");
Objects.requireNonNull(position, "position");
try {
- return parseToBuilder(text, position).resolve();
+ return parseResolved0(text, position);
} catch (DateTimeParseException | IndexOutOfBoundsException ex) {
throw ex;
} catch (RuntimeException ex) {
@@ -1396,8 +1396,7 @@
Objects.requireNonNull(text, "text");
Objects.requireNonNull(query, "query");
try {
- DateTimeBuilder builder = parseToBuilder(text, null).resolve();
- return builder.query(query);
+ return parseResolved0(text, null).query(query);
} catch (DateTimeParseException ex) {
throw ex;
} catch (RuntimeException ex) {
@@ -1443,10 +1442,10 @@
throw new IllegalArgumentException("At least two queries must be specified");
}
try {
- DateTimeBuilder builder = parseToBuilder(text, null).resolve();
+ TemporalAccessor resolved = parseResolved0(text, null);
for (TemporalQuery<?> query : queries) {
try {
- return (TemporalAccessor) builder.query(query);
+ return (TemporalAccessor) resolved.query(query);
} catch (RuntimeException ex) {
// continue
}
@@ -1471,22 +1470,22 @@
//-----------------------------------------------------------------------
/**
- * Parses the text to a builder.
+ * Parses and resolves the specified text.
* <p>
- * This parses to a {@code DateTimeBuilder} ensuring that the text is fully parsed.
- * This method throws {@link DateTimeParseException} if unable to parse, or
- * some other {@code DateTimeException} if another date/time problem occurs.
+ * This parses to a {@code TemporalAccessor} ensuring that the text is fully parsed.
*
* @param text the text to parse, not null
* @param position the position to parse from, updated with length parsed
* and the index of any error, null if parsing whole string
- * @return the engine representing the result of the parse, not null
+ * @return the resolved result of the parse, not null
* @throws DateTimeParseException if the parse fails
+ * @throws DateTimeException if an error occurs while resolving the date or time
+ * @throws IndexOutOfBoundsException if the position is invalid
*/
- private DateTimeBuilder parseToBuilder(final CharSequence text, final ParsePosition position) {
+ private TemporalAccessor parseResolved0(final CharSequence text, final ParsePosition position) {
ParsePosition pos = (position != null ? position : new ParsePosition(0));
- DateTimeParseContext result = parseUnresolved0(text, pos);
- if (result == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) {
+ DateTimeParseContext unresolved = parseUnresolved0(text, pos);
+ if (unresolved == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) {
String abbr = "";
if (text.length() > 64) {
abbr = text.subSequence(0, 64).toString() + "...";
@@ -1501,7 +1500,7 @@
pos.getIndex(), text, pos.getIndex());
}
}
- return result.resolveFields().toBuilder();
+ return unresolved.resolve();
}
/**
@@ -1667,7 +1666,7 @@
Objects.requireNonNull(text, "text");
try {
if (parseType == null) {
- return formatter.parseToBuilder(text, null).resolve();
+ return formatter.parseResolved0(text, null);
}
return formatter.parse(text, parseType);
} catch (DateTimeParseException ex) {
@@ -1695,11 +1694,11 @@
return null;
}
try {
- DateTimeBuilder builder = unresolved.resolveFields().toBuilder().resolve();
+ TemporalAccessor resolved = unresolved.resolve();
if (parseType == null) {
- return builder;
+ return resolved;
}
- return builder.query(parseType);
+ return resolved.query(parseType);
} catch (RuntimeException ex) {
pos.setErrorIndex(0);
return null;
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
@@ -384,7 +384,7 @@
*
* @return a new builder with the results of the parse, not null
*/
- DateTimeBuilder toBuilder() {
+ private DateTimeBuilder toBuilder() {
Parsed parsed = currentParsed();
DateTimeBuilder builder = new DateTimeBuilder();
for (Map.Entry<TemporalField, Long> fv : parsed.fieldValues.entrySet()) {
@@ -404,7 +404,7 @@
* @throws DateTimeException if resolving one field results in a value for
* another field that is in conflict
*/
- DateTimeParseContext resolveFields() {
+ TemporalAccessor resolve() {
Parsed data = currentParsed();
resolveChronoField(data);
outer:
@@ -420,7 +420,7 @@
}
break;
}
- return this;
+ return toBuilder().resolve();
}
private void resolveMakeChanges(Parsed data, TemporalField targetField, Map<TemporalField, Long> changes) {
# HG changeset patch
# User scolebourne
# Date 1362574135 0
# Node ID b60304856d2ef7d298cbcbeb699ce866a65a8e66
# Parent e5c9a73cbf0ff98e9a525f8377782e15b8b5a583
Make Parsed class TemporalAccessor rather than DTParseContext
Change is better OO and avoids repeated calls to currentParsed()
Promote Parsed to top-level as class now larger
Allows DTBuidler to be merged into Parsed
diff --git a/src/share/classes/java/time/format/DateTimeFormatter.java b/src/share/classes/java/time/format/DateTimeFormatter.java
--- a/src/share/classes/java/time/format/DateTimeFormatter.java
+++ b/src/share/classes/java/time/format/DateTimeFormatter.java
@@ -1484,7 +1484,7 @@
*/
private TemporalAccessor parseResolved0(final CharSequence text, final ParsePosition position) {
ParsePosition pos = (position != null ? position : new ParsePosition(0));
- DateTimeParseContext unresolved = parseUnresolved0(text, pos);
+ Parsed unresolved = parseUnresolved0(text, pos);
if (unresolved == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) {
String abbr = "";
if (text.length() > 64) {
@@ -1546,7 +1546,7 @@
return parseUnresolved0(text, position);
}
- private DateTimeParseContext parseUnresolved0(CharSequence text, ParsePosition position) {
+ private Parsed parseUnresolved0(CharSequence text, ParsePosition position) {
Objects.requireNonNull(text, "text");
Objects.requireNonNull(position, "position");
DateTimeParseContext context = new DateTimeParseContext(this);
@@ -1557,7 +1557,7 @@
return null;
}
position.setIndex(pos); // errorIndex not updated from input
- return context;
+ return context.toParsed();
}
//-----------------------------------------------------------------------
@@ -1678,7 +1678,7 @@
@Override
public Object parseObject(String text, ParsePosition pos) {
Objects.requireNonNull(text, "text");
- DateTimeParseContext unresolved;
+ Parsed unresolved;
try {
unresolved = formatter.parseUnresolved0(text, pos);
} catch (IndexOutOfBoundsException ex) {
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
@@ -61,24 +61,12 @@
*/
package java.time.format;
-import static java.time.temporal.ChronoField.ERA;
-import static java.time.temporal.ChronoField.YEAR;
-import static java.time.temporal.ChronoField.YEAR_OF_ERA;
-
-import java.time.DateTimeException;
import java.time.ZoneId;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
-import java.time.temporal.ChronoField;
-import java.time.temporal.Queries;
-import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
-import java.time.temporal.TemporalQuery;
-import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.Locale;
-import java.util.Map;
import java.util.Objects;
/**
@@ -88,8 +76,8 @@
* It has the ability to store and retrieve the parsed values and manage optional segments.
* It also provides key information to the parsing methods.
* <p>
- * Once parsing is complete, the {@link #toBuilder()} is typically used
- * to obtain a builder that can combine the separate parsed fields into meaningful values.
+ * Once parsing is complete, the {@link #toParsed()} is used to obtain the data.
+ * It contains a method to resolve the separate parsed fields into meaningful values.
*
* <h3>Specification for implementors</h3>
* This class is a mutable context intended for use from a single thread.
@@ -98,7 +86,7 @@
*
* @since 1.8
*/
-final class DateTimeParseContext implements TemporalAccessor {
+final class DateTimeParseContext {
/**
* The formatter, not null.
@@ -311,6 +299,17 @@
return parsed.get(parsed.size() - 1);
}
+ /**
+ * Gets the result of the parse.
+ *
+ * @return the result of the parse, not null
+ */
+ Parsed toParsed() {
+ Parsed parsed = currentParsed();
+ parsed.effectiveChrono = getEffectiveChronology();
+ return parsed;
+ }
+
//-----------------------------------------------------------------------
/**
* Gets the first value that was parsed for the specified field.
@@ -373,125 +372,6 @@
//-----------------------------------------------------------------------
/**
- * Returns a {@code DateTimeBuilder} that can be used to interpret
- * the results of the parse.
- * <p>
- * This method is typically used once parsing is complete to obtain the parsed data.
- * Parsing will typically result in separate fields, such as year, month and day.
- * The returned builder can be used to combine the parsed data into meaningful
- * objects such as {@code LocalDate}, potentially applying complex processing
- * to handle invalid parsed data.
- *
- * @return a new builder with the results of the parse, not null
- */
- private DateTimeBuilder toBuilder() {
- Parsed parsed = currentParsed();
- DateTimeBuilder builder = new DateTimeBuilder();
- for (Map.Entry<TemporalField, Long> fv : parsed.fieldValues.entrySet()) {
- builder.addFieldValue(fv.getKey(), fv.getValue());
- }
- builder.addObject(getEffectiveChronology());
- if (parsed.zone != null) {
- builder.addObject(parsed.zone);
- }
- return builder;
- }
-
- /**
- * Resolves the fields in this context.
- *
- * @return this, for method chaining
- * @throws DateTimeException if resolving one field results in a value for
- * another field that is in conflict
- */
- TemporalAccessor resolve() {
- Parsed data = currentParsed();
- resolveChronoField(data);
- outer:
- while (true) {
- for (Map.Entry<TemporalField, Long> entry : data.fieldValues.entrySet()) {
- TemporalField targetField = entry.getKey();
- Map<TemporalField, Long> changes = targetField.resolve(this, entry.getValue());
- if (changes != null) {
- resolveMakeChanges(data, targetField, changes);
- data.fieldValues.remove(targetField); // helps avoid infinite loops
- continue outer; // have to restart to avoid concurrent modification
- }
- }
- break;
- }
- return toBuilder().resolve();
- }
-
- private void resolveMakeChanges(Parsed data, TemporalField targetField, Map<TemporalField, Long> changes) {
- for (Map.Entry<TemporalField, Long> change : changes.entrySet()) {
- TemporalField changeField = change.getKey();
- Long changeValue = change.getValue();
- Objects.requireNonNull(changeField, "changeField");
- if (changeValue != null) {
- updateCheckConflict(data, targetField, changeField, changeValue);
- } else {
- data.fieldValues.remove(changeField);
- }
- }
- }
-
- private void updateCheckConflict(Parsed data, TemporalField targetField, TemporalField changeField, Long changeValue) {
- Long old = data.fieldValues.put(changeField, changeValue);
- if (old != null && old.longValue() != changeValue.longValue()) {
- throw new DateTimeException("Conflict found: " + changeField + " " + old +
- " differs from " + changeField + " " + changeValue +
- " while resolving " + targetField);
- }
- }
-
- private void resolveChronoField(Parsed data) {
- Long yoeVal = data.fieldValues.remove(YEAR_OF_ERA);
- if (yoeVal != null) {
- Long eraVal = data.fieldValues.remove(ERA);
- long year = getEffectiveChronology().resolveYearOfEra(yoeVal, eraVal);
- updateCheckConflict(data, YEAR_OF_ERA, YEAR, year);
- }
- }
-
- //-----------------------------------------------------------------------
- // TemporalAccessor methods
- // should only to be used once parsing is complete
- @Override
- public boolean isSupported(TemporalField field) {
- if (currentParsed().fieldValues.containsKey(field)) {
- return true;
- }
- return (field instanceof ChronoField == false) && field.isSupportedBy(this);
- }
-
- @Override
- public long getLong(TemporalField field) {
- Long value = currentParsed().fieldValues.get(field);
- if (value != null) {
- return value;
- }
- if (field instanceof ChronoField) {
- throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName());
- }
- return field.getFrom(this);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <R> R query(TemporalQuery<R> query) {
- if (query == Queries.chronology()) {
- return (R) currentParsed().chrono;
- } else if (query == Queries.zoneId()) {
- return (R) currentParsed().zone;
- } else if (query == Queries.precision()) {
- return null;
- }
- return query.queryFrom(this);
- }
-
- //-----------------------------------------------------------------------
- /**
* Returns a string version of the context for debugging.
*
* @return a string representation of the context data, not null
@@ -501,27 +381,4 @@
return currentParsed().toString();
}
- //-----------------------------------------------------------------------
- /**
- * Temporary store of parsed data.
- */
- private static final class Parsed {
- Chronology chrono = null;
- ZoneId zone = null;
- final Map<TemporalField, Long> fieldValues = new HashMap<>();
- private Parsed() {
- }
- protected Parsed copy() {
- Parsed cloned = new Parsed();
- cloned.chrono = this.chrono;
- cloned.zone = this.zone;
- cloned.fieldValues.putAll(this.fieldValues);
- return cloned;
- }
- @Override
- public String toString() {
- return fieldValues.toString() + "," + chrono + "," + zone;
- }
- }
-
}
diff --git a/src/share/classes/java/time/format/Parsed.java b/src/share/classes/java/time/format/Parsed.java
new file mode 100644
--- /dev/null
+++ b/src/share/classes/java/time/format/Parsed.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file:
+ *
+ * Copyright (c) 2008-2013, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package java.time.format;
+
+import static java.time.temporal.ChronoField.ERA;
+import static java.time.temporal.ChronoField.YEAR;
+import static java.time.temporal.ChronoField.YEAR_OF_ERA;
+
+import java.time.DateTimeException;
+import java.time.ZoneId;
+import java.time.chrono.Chronology;
+import java.time.temporal.ChronoField;
+import java.time.temporal.Queries;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQuery;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A store of parsed data.
+ * <p>
+ * This class is used during parsing to collect the data. Part of the parsing process
+ * involves handling optional blocks and multiple copies of the data get created to
+ * support the necessary backtracking.
+ * <p>
+ * Once parsing is completed, this class can be used as the resultant {@code TemporalAccessor}.
+ * In most cases, it is only exposed once the fields have been resolved.
+ *
+ * <h3>Specification for implementors</h3>
+ * This class is a mutable context intended for use from a single thread.
+ * Usage of the class is thread-safe within standard parsing as a new instance of this class
+ * is automatically created for each parse and parsing is single-threaded
+ *
+ * @since 1.8
+ */
+public final class Parsed implements TemporalAccessor {
+ // fields are accessed using package scope from DateTimeParseContext
+
+ /**
+ * The parsed chronology.
+ */
+ Chronology chrono;
+ /**
+ * The parsed zone.
+ */
+ ZoneId zone;
+ /**
+ * The parsed fields.
+ */
+ final Map<TemporalField, Long> fieldValues = new HashMap<>();
+ /**
+ * The effective chronology.
+ */
+ Chronology effectiveChrono;
+
+ /**
+ * Creates an instance.
+ */
+ Parsed() {
+ }
+
+ /**
+ * Creates a copy.
+ */
+ Parsed copy() {
+ // no need to copy effective chronology
+ Parsed cloned = new Parsed();
+ cloned.chrono = this.chrono;
+ cloned.zone = this.zone;
+ cloned.fieldValues.putAll(this.fieldValues);
+ return cloned;
+ }
+
+ //-----------------------------------------------------------------------
+ @Override
+ public boolean isSupported(TemporalField field) {
+ if (fieldValues.containsKey(field)) {
+ return true;
+ }
+ return (field instanceof ChronoField == false) && field.isSupportedBy(this);
+ }
+
+ @Override
+ public long getLong(TemporalField field) {
+ Long value = fieldValues.get(field);
+ if (value != null) {
+ return value;
+ }
+ if (field instanceof ChronoField) {
+ throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName());
+ }
+ return field.getFrom(this);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <R> R query(TemporalQuery<R> query) {
+ if (query == Queries.chronology()) {
+ return (R) chrono;
+ } else if (query == Queries.zoneId()) {
+ return (R) zone;
+ } else if (query == Queries.precision()) {
+ return null;
+ }
+ return query.queryFrom(this);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Resolves the fields in this context.
+ *
+ * @return this, for method chaining
+ * @throws DateTimeException if resolving one field results in a value for
+ * another field that is in conflict
+ */
+ TemporalAccessor resolve() {
+ resolveChronoField();
+ outer:
+ while (true) {
+ for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
+ TemporalField targetField = entry.getKey();
+ Map<TemporalField, Long> changes = targetField.resolve(this, entry.getValue());
+ if (changes != null) {
+ resolveMakeChanges(targetField, changes);
+ fieldValues.remove(targetField); // helps avoid infinite loops
+ continue outer; // have to restart to avoid concurrent modification
+ }
+ }
+ break;
+ }
+ return toBuilder().resolve();
+ }
+
+ private void resolveMakeChanges(TemporalField targetField, Map<TemporalField, Long> changes) {
+ for (Map.Entry<TemporalField, Long> change : changes.entrySet()) {
+ TemporalField changeField = change.getKey();
+ Long changeValue = change.getValue();
+ Objects.requireNonNull(changeField, "changeField");
+ if (changeValue != null) {
+ updateCheckConflict(targetField, changeField, changeValue);
+ } else {
+ fieldValues.remove(changeField);
+ }
+ }
+ }
+
+ private void updateCheckConflict(TemporalField targetField, TemporalField changeField, Long changeValue) {
+ Long old = fieldValues.put(changeField, changeValue);
+ if (old != null && old.longValue() != changeValue.longValue()) {
+ throw new DateTimeException("Conflict found: " + changeField + " " + old +
+ " differs from " + changeField + " " + changeValue +
+ " while resolving " + targetField);
+ }
+ }
+
+ private void resolveChronoField() {
+ Long yoeVal = fieldValues.remove(YEAR_OF_ERA);
+ if (yoeVal != null) {
+ Long eraVal = fieldValues.remove(ERA);
+ long year = effectiveChrono.resolveYearOfEra(yoeVal, eraVal);
+ updateCheckConflict(YEAR_OF_ERA, YEAR, year);
+ }
+ }
+
+ /**
+ * Returns a {@code DateTimeBuilder} that can be used to interpret
+ * the results of the parse.
+ * <p>
+ * This method is typically used once parsing is complete to obtain the parsed data.
+ * Parsing will typically result in separate fields, such as year, month and day.
+ * The returned builder can be used to combine the parsed data into meaningful
+ * objects such as {@code LocalDate}, potentially applying complex processing
+ * to handle invalid parsed data.
+ *
+ * @return a new builder with the results of the parse, not null
+ */
+ private DateTimeBuilder toBuilder() {
+ DateTimeBuilder builder = new DateTimeBuilder();
+ for (Map.Entry<TemporalField, Long> fv : fieldValues.entrySet()) {
+ builder.addFieldValue(fv.getKey(), fv.getValue());
+ }
+ builder.addObject(effectiveChrono);
+ if (zone != null) {
+ builder.addObject(zone);
+ }
+ return builder;
+ }
+
+ //-----------------------------------------------------------------------
+ @Override
+ public String toString() {
+ return fieldValues.toString() + "," + chrono + "," + zone;
+ }
+
+}
# HG changeset patch
# User scolebourne
# Date 1362582735 0
# Node ID af26fda8257480017ab499c851d4bd6dde5125cf
# Parent b60304856d2ef7d298cbcbeb699ce866a65a8e66
Move DateTimeBuilder logic into Parsed
Puts main resolving code in one place
diff --git a/src/share/classes/java/time/chrono/Chronology.java b/src/share/classes/java/time/chrono/Chronology.java
--- a/src/share/classes/java/time/chrono/Chronology.java
+++ b/src/share/classes/java/time/chrono/Chronology.java
@@ -98,10 +98,10 @@
import java.time.temporal.TemporalQuery;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;
-import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
@@ -882,7 +882,7 @@
* @throws DateTimeException if the date cannot be resolved, typically
* because of a conflict in the input data
*/
- public ChronoLocalDate<?> resolveDate(EnumMap<ChronoField, Long> fieldValues) {
+ public ChronoLocalDate<?> resolveDate(Map<TemporalField, Long> fieldValues) {
// year-of-era and era resolved to year before this method starts
if (fieldValues.containsKey(EPOCH_DAY)) {
@@ -963,7 +963,7 @@
* @param value the value to add, not null
* @throws DateTimeException if the field is already present with a different value
*/
- void addFieldValue(EnumMap<ChronoField, Long> fieldValues, ChronoField field, long value) {
+ void addFieldValue(Map<TemporalField, Long> fieldValues, ChronoField field, long value) {
Long old = fieldValues.get(field); // check first for better error message
if (old != null && old.longValue() != value) {
throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this);
diff --git a/src/share/classes/java/time/chrono/IsoChronology.java b/src/share/classes/java/time/chrono/IsoChronology.java
--- a/src/share/classes/java/time/chrono/IsoChronology.java
+++ b/src/share/classes/java/time/chrono/IsoChronology.java
@@ -70,11 +70,9 @@
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static java.time.temporal.ChronoField.DAY_OF_YEAR;
import static java.time.temporal.ChronoField.EPOCH_DAY;
-import static java.time.temporal.ChronoField.ERA;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
import static java.time.temporal.ChronoField.YEAR;
-import static java.time.temporal.ChronoField.YEAR_OF_ERA;
import java.io.Serializable;
import java.time.Clock;
@@ -83,17 +81,16 @@
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
import java.time.temporal.ValueRange;
import java.util.Arrays;
-import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
/**
@@ -412,7 +409,7 @@
}
@Override // override for performance
- public LocalDate resolveDate(EnumMap<ChronoField, Long> fieldValues) {
+ public LocalDate resolveDate(Map<TemporalField, Long> fieldValues) {
// year-of-era and era resolved to year before this method starts
if (fieldValues.containsKey(EPOCH_DAY)) {
diff --git a/src/share/classes/java/time/format/DateTimeBuilder.java b/src/share/classes/java/time/format/DateTimeBuilder.java
deleted file mode 100644
--- a/src/share/classes/java/time/format/DateTimeBuilder.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * This file is available under and governed by the GNU General Public
- * License version 2 only, as published by the Free Software Foundation.
- * However, the following notice accompanied the original version of this
- * file:
- *
- * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * * Neither the name of JSR-310 nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package java.time.format;
-
-import static java.time.temporal.ChronoField.AMPM_OF_DAY;
-import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
-import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
-import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
-import static java.time.temporal.ChronoField.HOUR_OF_DAY;
-import static java.time.temporal.ChronoField.MICRO_OF_DAY;
-import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
-import static java.time.temporal.ChronoField.MILLI_OF_DAY;
-import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
-import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
-import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
-import static java.time.temporal.ChronoField.NANO_OF_DAY;
-import static java.time.temporal.ChronoField.NANO_OF_SECOND;
-import static java.time.temporal.ChronoField.SECOND_OF_DAY;
-import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
-
-import java.time.DateTimeException;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.time.chrono.ChronoLocalDate;
-import java.time.chrono.Chronology;
-import java.time.temporal.ChronoField;
-import java.time.temporal.Queries;
-import java.time.temporal.TemporalAccessor;
-import java.time.temporal.TemporalField;
-import java.time.temporal.TemporalQuery;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * Builder that can holds date and time fields and related date and time objects.
- * <p>
- * <b>This class still needs major revision before JDK1.8 ships.</b>
- * <p>
- * The builder is used to hold onto different elements of date and time.
- * It holds two kinds of object:
- * <p><ul>
- * <li>a {@code Map} from {@link TemporalField} to {@code long} value, where the
- * value may be outside the valid range for the field
- * <li>a list of objects, such as {@code Chronology} or {@code ZoneId}
- * </ul><p>
- *
- * <h3>Specification for implementors</h3>
- * This class is mutable and not thread-safe.
- * It should only be used from a single thread.
- *
- * @since 1.8
- */
-final class DateTimeBuilder
- implements TemporalAccessor {
-
- /**
- * The map of other fields.
- */
- private Map<TemporalField, Long> otherFields;
- /**
- * The map of date-time fields.
- */
- private final EnumMap<ChronoField, Long> chronoFields = new EnumMap<>(ChronoField.class);
- /**
- * The chronology.
- */
- private Chronology chrono;
- /**
- * The zone.
- */
- private ZoneId zone;
- /**
- * The date.
- */
- private ChronoLocalDate<?> date;
- /**
- * The time.
- */
- private LocalTime time;
-
- //-----------------------------------------------------------------------
- /**
- * Creates an empty instance of the builder.
- */
- public DateTimeBuilder() {
- }
-
- //-----------------------------------------------------------------------
- private Long getFieldValue0(TemporalField field) {
- if (field instanceof ChronoField) {
- return chronoFields.get(field);
- } else if (otherFields != null) {
- return otherFields.get(field);
- }
- return null;
- }
-
- /**
- * Adds a field-value pair to the builder.
- * <p>
- * This adds a field to the builder.
- * If the field is not already present, then the field-value pair is added to the map.
- * If the field is already present and it has the same value as that specified, no action occurs.
- * If the field is already present and it has a different value to that specified, then
- * an exception is thrown.
- *
- * @param field the field to add, not null
- * @param value the value to add, not null
- * @return {@code this}, for method chaining
- * @throws DateTimeException if the field is already present with a different value
- */
- DateTimeBuilder addFieldValue(TemporalField field, long value) {
- Objects.requireNonNull(field, "field");
- Long old = getFieldValue0(field); // check first for better error message
- if (old != null && old.longValue() != value) {
- throw new DateTimeException("Conflict found: " + field + " " + old + " differs from " + field + " " + value + ": " + this);
- }
- return putFieldValue0(field, value);
- }
-
- private DateTimeBuilder putFieldValue0(TemporalField field, long value) {
- if (field instanceof ChronoField) {
- chronoFields.put((ChronoField) field, value);
- } else {
- if (otherFields == null) {
- otherFields = new LinkedHashMap<TemporalField, Long>();
- }
- otherFields.put(field, value);
- }
- return this;
- }
-
- //-----------------------------------------------------------------------
- void addObject(Chronology chrono) {
- this.chrono = chrono;
- }
-
- void addObject(ZoneId zone) {
- this.zone = zone;
- }
-
- void addObject(ChronoLocalDate<?> date) {
- this.date = date;
- }
-
- void addObject(LocalTime time) {
- this.time = time;
- }
-
- //-----------------------------------------------------------------------
- /**
- * Resolves the builder, evaluating the date and time.
- * <p>
- * This examines the contents of the builder and resolves it to produce the best
- * available date and time, throwing an exception if a problem occurs.
- * Calling this method changes the state of the builder.
- *
- * @return {@code this}, for method chaining
- */
- DateTimeBuilder resolve() {
- // handle standard fields
- mergeDate();
- mergeTime();
- // TODO: cross validate remaining fields?
- return this;
- }
-
- private void mergeDate() {
- ChronoLocalDate<?> date = chrono.resolveDate(chronoFields);
- if (date != null) {
- addObject(checkDate(date));
- }
- }
-
- private ChronoLocalDate<?> checkDate(ChronoLocalDate<?> date) {
- for (ChronoField field : chronoFields.keySet()) {
- long val1;
- try {
- val1 = date.getLong(field);
- } catch (DateTimeException ex) {
- continue;
- }
- Long val2 = chronoFields.get(field);
- if (val1 != val2) {
- throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date);
- }
- }
- return date;
- }
-
- private void mergeTime() {
- if (chronoFields.containsKey(CLOCK_HOUR_OF_DAY)) {
- long ch = chronoFields.remove(CLOCK_HOUR_OF_DAY);
- addFieldValue(HOUR_OF_DAY, ch == 24 ? 0 : ch);
- }
- if (chronoFields.containsKey(CLOCK_HOUR_OF_AMPM)) {
- long ch = chronoFields.remove(CLOCK_HOUR_OF_AMPM);
- addFieldValue(HOUR_OF_AMPM, ch == 12 ? 0 : ch);
- }
- if (chronoFields.containsKey(AMPM_OF_DAY) && chronoFields.containsKey(HOUR_OF_AMPM)) {
- long ap = chronoFields.remove(AMPM_OF_DAY);
- long hap = chronoFields.remove(HOUR_OF_AMPM);
- addFieldValue(HOUR_OF_DAY, ap * 12 + hap);
- }
-// if (timeFields.containsKey(HOUR_OF_DAY) && timeFields.containsKey(MINUTE_OF_HOUR)) {
-// long hod = timeFields.remove(HOUR_OF_DAY);
-// long moh = timeFields.remove(MINUTE_OF_HOUR);
-// addFieldValue(MINUTE_OF_DAY, hod * 60 + moh);
-// }
-// if (timeFields.containsKey(MINUTE_OF_DAY) && timeFields.containsKey(SECOND_OF_MINUTE)) {
-// long mod = timeFields.remove(MINUTE_OF_DAY);
-// long som = timeFields.remove(SECOND_OF_MINUTE);
-// addFieldValue(SECOND_OF_DAY, mod * 60 + som);
-// }
- if (chronoFields.containsKey(NANO_OF_DAY)) {
- long nod = chronoFields.remove(NANO_OF_DAY);
- addFieldValue(SECOND_OF_DAY, nod / 1000_000_000L);
- addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
- }
- if (chronoFields.containsKey(MICRO_OF_DAY)) {
- long cod = chronoFields.remove(MICRO_OF_DAY);
- addFieldValue(SECOND_OF_DAY, cod / 1000_000L);
- addFieldValue(MICRO_OF_SECOND, cod % 1000_000L);
- }
- if (chronoFields.containsKey(MILLI_OF_DAY)) {
- long lod = chronoFields.remove(MILLI_OF_DAY);
- addFieldValue(SECOND_OF_DAY, lod / 1000);
- addFieldValue(MILLI_OF_SECOND, lod % 1000);
- }
- if (chronoFields.containsKey(SECOND_OF_DAY)) {
- long sod = chronoFields.remove(SECOND_OF_DAY);
- addFieldValue(HOUR_OF_DAY, sod / 3600);
- addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
- addFieldValue(SECOND_OF_MINUTE, sod % 60);
- }
- if (chronoFields.containsKey(MINUTE_OF_DAY)) {
- long mod = chronoFields.remove(MINUTE_OF_DAY);
- addFieldValue(HOUR_OF_DAY, mod / 60);
- addFieldValue(MINUTE_OF_HOUR, mod % 60);
- }
-
-// long sod = nod / 1000_000_000L;
-// addFieldValue(HOUR_OF_DAY, sod / 3600);
-// addFieldValue(MINUTE_OF_HOUR, (sod / 60) % 60);
-// addFieldValue(SECOND_OF_MINUTE, sod % 60);
-// addFieldValue(NANO_OF_SECOND, nod % 1000_000_000L);
- if (chronoFields.containsKey(MILLI_OF_SECOND) && chronoFields.containsKey(MICRO_OF_SECOND)) {
- long los = chronoFields.remove(MILLI_OF_SECOND);
- long cos = chronoFields.get(MICRO_OF_SECOND);
- addFieldValue(MICRO_OF_SECOND, los * 1000 + (cos % 1000));
- }
-
- Long hod = chronoFields.get(HOUR_OF_DAY);
- Long moh = chronoFields.get(MINUTE_OF_HOUR);
- Long som = chronoFields.get(SECOND_OF_MINUTE);
- Long nos = chronoFields.get(NANO_OF_SECOND);
- if (hod != null) {
- int hodVal = Math.toIntExact(hod);
- if (moh != null) {
- int mohVal = Math.toIntExact(moh);
- if (som != null) {
- int somVal = Math.toIntExact(som);
- if (nos != null) {
- int nosVal = Math.toIntExact(nos);
- addObject(LocalTime.of(hodVal, mohVal, somVal, nosVal));
- } else {
- addObject(LocalTime.of(hodVal, mohVal, somVal));
- }
- } else {
- addObject(LocalTime.of(hodVal, mohVal));
- }
- } else {
- addObject(LocalTime.of(hodVal, 0));
- }
- }
- }
-
- //-----------------------------------------------------------------------
- @Override
- public boolean isSupported(TemporalField field) {
- if (field == null) {
- return false;
- }
- return chronoFields.containsKey(field) ||
- (otherFields != null && otherFields.containsKey(field)) ||
- (date != null && date.isSupported(field)) ||
- (time != null && time.isSupported(field));
- }
-
- @Override
- public long getLong(TemporalField field) {
- Objects.requireNonNull(field, "field");
- Long value = getFieldValue0(field);
- if (value == null) {
- if (date != null && date.isSupported(field)) {
- return date.getLong(field);
- }
- if (time != null && time.isSupported(field)) {
- return time.getLong(field);
- }
- throw new DateTimeException("Field not found: " + field);
- }
- return value;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <R> R query(TemporalQuery<R> query) {
- if (query == Queries.zoneId()) {
- return (R) zone;
- } else if (query == Queries.chronology()) {
- return (R) chrono;
- } else if (query == Queries.localDate()) {
- return (R) (date != null ? LocalDate.from(date) : null);
- } else if (query == Queries.localTime()) {
- return (R) time;
- } else if (query == Queries.zone() || query == Queries.offset()) {
- return query.queryFrom(this);
- } else if (query == Queries.precision()) {
- return null; // not a complete date/time
- }
- // inline TemporalAccessor.super.query(query) as an optimization
- // non-JDK classes are not permitted to make this optimization
- return query.queryFrom(this);
- }
-
- //-----------------------------------------------------------------------
- @Override
- public String toString() {
- StringBuilder buf = new StringBuilder(128);
- buf.append("DateTimeBuilder[");
- Map<TemporalField, Long> fields = new HashMap<>();
- fields.putAll(chronoFields);
- if (otherFields != null) {
- fields.putAll(otherFields);
- }
- if (fields.size() > 0) {
- buf.append("fields=").append(fields);
- }
- buf.append(", ").append(chrono);
- buf.append(", ").append(zone);
- buf.append(", ").append(date);
- buf.append(", ").append(time);
- buf.append(']');
- return buf.toString();
- }
-
-}
diff --git a/src/share/classes/java/time/format/Parsed.java b/src/share/classes/java/time/format/Parsed.java
--- a/src/share/classes/java/time/format/Parsed.java
+++ b/src/share/classes/java/time/format/Parsed.java
@@ -61,12 +61,30 @@
*/
package java.time.format;
+import static java.time.temporal.ChronoField.AMPM_OF_DAY;
+import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
+import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
import static java.time.temporal.ChronoField.ERA;
+import static java.time.temporal.ChronoField.HOUR_OF_AMPM;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MICRO_OF_DAY;
+import static java.time.temporal.ChronoField.MICRO_OF_SECOND;
+import static java.time.temporal.ChronoField.MILLI_OF_DAY;
+import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
+import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoField.NANO_OF_DAY;
+import static java.time.temporal.ChronoField.NANO_OF_SECOND;
+import static java.time.temporal.ChronoField.SECOND_OF_DAY;
+import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
import static java.time.temporal.ChronoField.YEAR_OF_ERA;
import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.LocalTime;
import java.time.ZoneId;
+import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.temporal.ChronoField;
import java.time.temporal.Queries;
@@ -96,24 +114,32 @@
* @since 1.8
*/
public final class Parsed implements TemporalAccessor {
- // fields are accessed using package scope from DateTimeParseContext
+ // some fields are accessed using package scope from DateTimeParseContext
/**
+ * The parsed fields.
+ */
+ final Map<TemporalField, Long> fieldValues = new HashMap<>();
+ /**
+ * The parsed zone.
+ */
+ ZoneId zone;
+ /**
* The parsed chronology.
*/
Chronology chrono;
/**
- * The parsed zone.
- */
- ZoneId zone;
- /**
- * The parsed fields.
- */
- final Map<TemporalField, Long> fieldValues = new HashMap<>();
- /**
* The effective chronology.
*/
Chronology effectiveChrono;
+ /**
+ * The resolved date.
+ */
+ private ChronoLocalDate<?> date;
+ /**
+ * The resolved time.
+ */
+ private LocalTime time;
/**
* Creates an instance.
@@ -125,29 +151,38 @@
* Creates a copy.
*/
Parsed copy() {
- // no need to copy effective chronology
+ // only copy fields used in parsing stage
Parsed cloned = new Parsed();
+ cloned.fieldValues.putAll(this.fieldValues);
+ cloned.zone = this.zone;
cloned.chrono = this.chrono;
- cloned.zone = this.zone;
- cloned.fieldValues.putAll(this.fieldValues);
return cloned;
}
//-----------------------------------------------------------------------
@Override
public boolean isSupported(TemporalField field) {
- if (fieldValues.containsKey(field)) {
+ if (fieldValues.containsKey(field) ||
+ (date != null && date.isSupported(field)) ||
+ (time != null && time.isSupported(field))) {
return true;
}
- return (field instanceof ChronoField == false) && field.isSupportedBy(this);
+ return field != null && (field instanceof ChronoField == false) && field.isSupportedBy(this);
}
@Override
public long getLong(TemporalField field) {
+ Objects.requireNonNull(field, "field");
Long value = fieldValues.get(field);
if (value != null) {
return value;
}
+ if (date != null && date.isSupported(field)) {
+ return date.getLong(field);
+ }
+ if (time != null && time.isSupported(field)) {
+ return time.getLong(field);
+ }
if (field instanceof ChronoField) {
throw new UnsupportedTemporalTypeException("Unsupported field: " + field.getName());
}
@@ -157,13 +192,21 @@
@SuppressWarnings("unchecked")
@Override
public <R> R query(TemporalQuery<R> query) {
- if (query == Queries.chronology()) {
+ if (query == Queries.zoneId()) {
+ return (R) zone;
+ } else if (query == Queries.chronology()) {
return (R) chrono;
- } else if (query == Queries.zoneId()) {
- return (R) zone;
+ } else if (query == Queries.localDate()) {
+ return (R) (date != null ? LocalDate.from(date) : null);
+ } else if (query == Queries.localTime()) {
+ return (R) time;
+ } else if (query == Queries.zone() || query == Queries.offset()) {
+ return query.queryFrom(this);
} else if (query == Queries.precision()) {
- return null;
+ return null; // not a complete date/time
}
+ // inline TemporalAccessor.super.query(query) as an optimization
+ // non-JDK classes are not permitted to make this optimization
return query.queryFrom(this);
}
@@ -176,24 +219,40 @@
* another field that is in conflict
*/
TemporalAccessor resolve() {
- resolveChronoField();
+ chrono = effectiveChrono;
+ resolveFields();
+ resolveDate();
+ resolveTime();
+ // TODO: cross validate remaining fields?
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ private void resolveFields() {
+ resolveDateFields();
+ resolveTimeFields();
+ boolean changed = false;
outer:
while (true) {
for (Map.Entry<TemporalField, Long> entry : fieldValues.entrySet()) {
TemporalField targetField = entry.getKey();
Map<TemporalField, Long> changes = targetField.resolve(this, entry.getValue());
if (changes != null) {
- resolveMakeChanges(targetField, changes);
+ changed = true;
+ resolveFieldsMakeChanges(targetField, changes);
fieldValues.remove(targetField); // helps avoid infinite loops
continue outer; // have to restart to avoid concurrent modification
}
}
break;
}
- return toBuilder().resolve();
+ if (changed) {
+ resolveDateFields();
+ resolveTimeFields();
+ }
}
- private void resolveMakeChanges(TemporalField targetField, Map<TemporalField, Long> changes) {
+ private void resolveFieldsMakeChanges(TemporalField targetField, Map<TemporalField, Long> changes) {
for (Map.Entry<TemporalField, Long> change : changes.entrySet()) {
TemporalField changeField = change.getKey();
Long changeValue = change.getValue();
@@ -215,43 +274,134 @@
}
}
- private void resolveChronoField() {
+ //-----------------------------------------------------------------------
+ private void resolveDateFields() {
Long yoeVal = fieldValues.remove(YEAR_OF_ERA);
if (yoeVal != null) {
Long eraVal = fieldValues.remove(ERA);
- long year = effectiveChrono.resolveYearOfEra(yoeVal, eraVal);
+ long year = chrono.resolveYearOfEra(yoeVal, eraVal);
updateCheckConflict(YEAR_OF_ERA, YEAR, year);
}
}
- /**
- * Returns a {@code DateTimeBuilder} that can be used to interpret
- * the results of the parse.
- * <p>
- * This method is typically used once parsing is complete to obtain the parsed data.
- * Parsing will typically result in separate fields, such as year, month and day.
- * The returned builder can be used to combine the parsed data into meaningful
- * objects such as {@code LocalDate}, potentially applying complex processing
- * to handle invalid parsed data.
- *
- * @return a new builder with the results of the parse, not null
- */
- private DateTimeBuilder toBuilder() {
- DateTimeBuilder builder = new DateTimeBuilder();
- for (Map.Entry<TemporalField, Long> fv : fieldValues.entrySet()) {
- builder.addFieldValue(fv.getKey(), fv.getValue());
+ private void resolveDate() {
+ ChronoLocalDate<?> resolved = chrono.resolveDate(fieldValues);
+ if (resolved != null) {
+ date = checkDate(resolved);
}
- builder.addObject(effectiveChrono);
- if (zone != null) {
- builder.addObject(zone);
+ }
+
+ private ChronoLocalDate<?> checkDate(ChronoLocalDate<?> date) {
+ for (TemporalField field : fieldValues.keySet()) {
+ long val1;
+ try {
+ val1 = date.getLong(field);
+ } catch (DateTimeException ex) {
+ continue;
+ }
+ Long val2 = fieldValues.get(field);
+ if (val1 != val2) {
+ throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date);
+ }
}
- return builder;
+ return date;
+ }
+
+ //-----------------------------------------------------------------------
+ private void resolveTimeFields() {
+ if (fieldValues.containsKey(CLOCK_HOUR_OF_DAY)) {
+ long ch = fieldValues.remove(CLOCK_HOUR_OF_DAY);
+ updateCheckConflict(CLOCK_HOUR_OF_DAY, HOUR_OF_DAY, ch == 24 ? 0 : ch);
+ }
+ if (fieldValues.containsKey(CLOCK_HOUR_OF_AMPM)) {
+ long ch = fieldValues.remove(CLOCK_HOUR_OF_AMPM);
+ updateCheckConflict(CLOCK_HOUR_OF_AMPM, HOUR_OF_AMPM, ch == 12 ? 0 : ch);
+ }
+ if (fieldValues.containsKey(AMPM_OF_DAY) && fieldValues.containsKey(HOUR_OF_AMPM)) {
+ long ap = fieldValues.remove(AMPM_OF_DAY);
+ long hap = fieldValues.remove(HOUR_OF_AMPM);
+ updateCheckConflict(AMPM_OF_DAY, HOUR_OF_DAY, ap * 12 + hap);
+ }
+ if (fieldValues.containsKey(NANO_OF_DAY)) {
+ long nod = fieldValues.remove(NANO_OF_DAY);
+ updateCheckConflict(NANO_OF_DAY, SECOND_OF_DAY, nod / 1000_000_000L);
+ updateCheckConflict(NANO_OF_DAY, NANO_OF_SECOND, nod % 1000_000_000L);
+ }
+ if (fieldValues.containsKey(MICRO_OF_DAY)) {
+ long cod = fieldValues.remove(MICRO_OF_DAY);
+ updateCheckConflict(MICRO_OF_DAY, SECOND_OF_DAY, cod / 1000_000L);
+ updateCheckConflict(MICRO_OF_DAY, MICRO_OF_SECOND, cod % 1000_000L);
+ }
+ if (fieldValues.containsKey(MILLI_OF_DAY)) {
+ long lod = fieldValues.remove(MILLI_OF_DAY);
+ updateCheckConflict(MILLI_OF_DAY, SECOND_OF_DAY, lod / 1000);
+ updateCheckConflict(MILLI_OF_DAY, MILLI_OF_SECOND, lod % 1000);
+ }
+ if (fieldValues.containsKey(SECOND_OF_DAY)) {
+ long sod = fieldValues.remove(SECOND_OF_DAY);
+ updateCheckConflict(SECOND_OF_DAY, HOUR_OF_DAY, sod / 3600);
+ updateCheckConflict(SECOND_OF_DAY, MINUTE_OF_HOUR, (sod / 60) % 60);
+ updateCheckConflict(SECOND_OF_DAY, SECOND_OF_MINUTE, sod % 60);
+ }
+ if (fieldValues.containsKey(MINUTE_OF_DAY)) {
+ long mod = fieldValues.remove(MINUTE_OF_DAY);
+ updateCheckConflict(MINUTE_OF_DAY, HOUR_OF_DAY, mod / 60);
+ updateCheckConflict(MINUTE_OF_DAY, MINUTE_OF_HOUR, mod % 60);
+ }
+ if (fieldValues.containsKey(MICRO_OF_SECOND)) {
+ long mos = fieldValues.remove(MICRO_OF_SECOND);
+ Long nos = fieldValues.get(NANO_OF_SECOND);
+ if (nos != null) {
+ updateCheckConflict(MICRO_OF_SECOND, NANO_OF_SECOND, mos * 1000 + (nos % 1000));
+ } else {
+ fieldValues.put(NANO_OF_SECOND, mos * 1000);
+ }
+ }
+ if (fieldValues.containsKey(MILLI_OF_SECOND)) {
+ long mos = fieldValues.remove(MILLI_OF_SECOND);
+ Long nos = fieldValues.get(NANO_OF_SECOND);
+ if (nos != null) {
+ updateCheckConflict(MILLI_OF_SECOND, NANO_OF_SECOND, mos * 1000000 + (nos % 1000000));
+ } else {
+ fieldValues.put(NANO_OF_SECOND, mos * 1000000);
+ }
+ }
+ }
+
+ private void resolveTime() {
+ Long hod = fieldValues.get(HOUR_OF_DAY);
+ Long moh = fieldValues.get(MINUTE_OF_HOUR);
+ Long som = fieldValues.get(SECOND_OF_MINUTE);
+ Long nos = fieldValues.get(NANO_OF_SECOND);
+ if (hod != null) {
+ int hodVal = HOUR_OF_DAY.checkValidIntValue(hod);
+ if (moh != null) {
+ int mohVal = MINUTE_OF_HOUR.checkValidIntValue(moh);
+ if (som != null) {
+ int somVal = SECOND_OF_MINUTE.checkValidIntValue(som);
+ if (nos != null) {
+ int nosVal = NANO_OF_SECOND.checkValidIntValue(nos);
+ time = LocalTime.of(hodVal, mohVal, somVal, nosVal);
+ } else {
+ time = LocalTime.of(hodVal, mohVal, somVal);
+ }
+ } else {
+ time = LocalTime.of(hodVal, mohVal);
+ }
+ } else {
+ time = LocalTime.of(hodVal, 0);
+ }
+ }
}
//-----------------------------------------------------------------------
@Override
public String toString() {
- return fieldValues.toString() + "," + chrono + "," + zone;
+ String str = fieldValues.toString() + "," + chrono + "," + zone;
+ if (date != null || time != null) {
+ str += " resolved to " + date + "," + time;
+ }
+ return str;
}
}
# HG changeset patch
# User scolebourne
# Date 1362585339 0
# Node ID b5303ddfd0a1181ff2837a993597a0bcf72c5ec2
# Parent af26fda8257480017ab499c851d4bd6dde5125cf
Add proper cross-check at the end of parsing
diff --git a/src/share/classes/java/time/format/Parsed.java b/src/share/classes/java/time/format/Parsed.java
--- a/src/share/classes/java/time/format/Parsed.java
+++ b/src/share/classes/java/time/format/Parsed.java
@@ -85,6 +85,7 @@
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDate;
+import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.Chronology;
import java.time.temporal.ChronoField;
import java.time.temporal.Queries;
@@ -93,7 +94,9 @@
import java.time.temporal.TemporalQuery;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
/**
@@ -223,7 +226,7 @@
resolveFields();
resolveDate();
resolveTime();
- // TODO: cross validate remaining fields?
+ crossCheck();
return this;
}
@@ -285,26 +288,39 @@
}
private void resolveDate() {
- ChronoLocalDate<?> resolved = chrono.resolveDate(fieldValues);
- if (resolved != null) {
- date = checkDate(resolved);
+ date = chrono.resolveDate(fieldValues);
+ }
+
+ private void crossCheck() {
+ // only cross-check date, time and date-time
+ // avoid object creation if possible
+ if (date != null) {
+ crossCheck(date);
+ }
+ if (time != null) {
+ crossCheck(time);
+ if (date != null && fieldValues.size() > 0) {
+ crossCheck(date.atTime(time));
+ }
}
}
- private ChronoLocalDate<?> checkDate(ChronoLocalDate<?> date) {
- for (TemporalField field : fieldValues.keySet()) {
+ private void crossCheck(TemporalAccessor target) {
+ for (Iterator<Entry<TemporalField, Long>> it = fieldValues.entrySet().iterator(); it.hasNext(); ) {
+ Entry<TemporalField, Long> entry = it.next();
+ TemporalField field = entry.getKey();
long val1;
try {
- val1 = date.getLong(field);
- } catch (DateTimeException ex) {
+ val1 = target.getLong(field);
+ } catch (RuntimeException ex) {
continue;
}
- Long val2 = fieldValues.get(field);
+ long val2 = entry.getValue();
if (val1 != val2) {
throw new DateTimeException("Conflict found: Field " + field + " " + val1 + " differs from " + field + " " + val2 + " derived from " + date);
}
+ it.remove();
}
- return date;
}
//-----------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment