src/share/classes/java/time/format/DateTimeParseContext.java
Print this page
*** 59,72 ****
* 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 java.time.temporal.TemporalField;
import java.util.ArrayList;
! import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Context object used during date and time parsing.
* <p>
--- 59,81 ----
* 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 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.util.ArrayList;
! import java.util.HashMap;
import java.util.Locale;
+ import java.util.Map;
import java.util.Objects;
/**
* Context object used during date and time parsing.
* <p>
*** 82,92 ****
* 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
*/
! final class DateTimeParseContext {
/**
* The formatter, not null.
*/
private DateTimeFormatter formatter;
--- 91,101 ----
* 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
*/
! final class DateTimeParseContext implements TemporalAccessor {
/**
* The formatter, not null.
*/
private DateTimeFormatter formatter;
*** 128,171 ****
* This locale is used to control localization in the parse except
* where localization is controlled by the symbols.
*
* @return the locale, not null
*/
! public Locale getLocale() {
return formatter.getLocale();
}
/**
* Gets the formatting symbols.
* <p>
* The symbols control the localization of numeric parsing.
*
* @return the formatting symbols, not null
*/
! public DateTimeFormatSymbols getSymbols() {
return formatter.getSymbols();
}
//-----------------------------------------------------------------------
/**
* Checks if parsing is case sensitive.
*
* @return true if parsing is case sensitive, false if case insensitive
*/
! public boolean isCaseSensitive() {
return caseSensitive;
}
/**
* Sets whether the parsing is case sensitive or not.
*
* @param caseSensitive changes the parsing to be case sensitive or not from now on
*/
! public void setCaseSensitive(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
/**
* Helper to compare two {@code CharSequence} instances.
* This uses {@link #isCaseSensitive()}.
*
* @param cs1 the first character sequence, not null
--- 137,197 ----
* This locale is used to control localization in the parse except
* where localization is controlled by the symbols.
*
* @return the locale, not null
*/
! Locale getLocale() {
return formatter.getLocale();
}
/**
* Gets the formatting symbols.
* <p>
* The symbols control the localization of numeric parsing.
*
* @return the formatting symbols, not null
*/
! DateTimeFormatSymbols getSymbols() {
return formatter.getSymbols();
}
+ /**
+ * Gets the effective chronology during parsing.
+ *
+ * @return the effective parsing chronology, not null
+ */
+ Chronology getEffectiveChronology() {
+ Chronology chrono = currentParsed().chrono;
+ if (chrono == null) {
+ chrono = formatter.getChronology();
+ if (chrono == null) {
+ chrono = IsoChronology.INSTANCE;
+ }
+ }
+ return chrono;
+ }
+
//-----------------------------------------------------------------------
/**
* Checks if parsing is case sensitive.
*
* @return true if parsing is case sensitive, false if case insensitive
*/
! boolean isCaseSensitive() {
return caseSensitive;
}
/**
* Sets whether the parsing is case sensitive or not.
*
* @param caseSensitive changes the parsing to be case sensitive or not from now on
*/
! void setCaseSensitive(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
+ //-----------------------------------------------------------------------
/**
* Helper to compare two {@code CharSequence} instances.
* This uses {@link #isCaseSensitive()}.
*
* @param cs1 the first character sequence, not null
*** 173,183 ****
* @param cs2 the second character sequence, not null
* @param offset2 the offset into the second sequence, valid
* @param length the length to check, valid
* @return true if equal
*/
! public boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) {
if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) {
return false;
}
if (isCaseSensitive()) {
for (int i = 0; i < length; i++) {
--- 199,209 ----
* @param cs2 the second character sequence, not null
* @param offset2 the offset into the second sequence, valid
* @param length the length to check, valid
* @return true if equal
*/
! boolean subSequenceEquals(CharSequence cs1, int offset1, CharSequence cs2, int offset2, int length) {
if (offset1 + length > cs1.length() || offset2 + length > cs2.length()) {
return false;
}
if (isCaseSensitive()) {
for (int i = 0; i < length; i++) {
*** 198,225 ****
}
}
return true;
}
//-----------------------------------------------------------------------
/**
* Checks if parsing is strict.
* <p>
* Strict parsing requires exact matching of the text and sign styles.
*
* @return true if parsing is strict, false if lenient
*/
! public boolean isStrict() {
return strict;
}
/**
* Sets whether parsing is strict or lenient.
*
* @param strict changes the parsing to be strict or lenient from now on
*/
! public void setStrict(boolean strict) {
this.strict = strict;
}
//-----------------------------------------------------------------------
/**
--- 224,279 ----
}
}
return true;
}
+ /**
+ * Helper to compare two {@code char}.
+ * This uses {@link #isCaseSensitive()}.
+ *
+ * @param ch1 the first character
+ * @param ch2 the second character
+ * @return true if equal
+ */
+ boolean charEquals(char ch1, char ch2) {
+ if (isCaseSensitive()) {
+ return ch1 == ch2;
+ }
+ return charEqualsIgnoreCase(ch1, ch2);
+ }
+
+ /**
+ * Compares two characters ignoring case.
+ *
+ * @param c1 the first
+ * @param c2 the second
+ * @return true if equal
+ */
+ static boolean charEqualsIgnoreCase(char c1, char c2) {
+ return c1 == c2 ||
+ Character.toUpperCase(c1) == Character.toUpperCase(c2) ||
+ Character.toLowerCase(c1) == Character.toLowerCase(c2);
+ }
+
//-----------------------------------------------------------------------
/**
* Checks if parsing is strict.
* <p>
* Strict parsing requires exact matching of the text and sign styles.
*
* @return true if parsing is strict, false if lenient
*/
! boolean isStrict() {
return strict;
}
/**
* Sets whether parsing is strict or lenient.
*
* @param strict changes the parsing to be strict or lenient from now on
*/
! void setStrict(boolean strict) {
this.strict = strict;
}
//-----------------------------------------------------------------------
/**
*** 262,337 ****
* For example, the day-of-month might be set to 50, or the hour to 1000.
*
* @param field the field to query from the map, null returns null
* @return the value mapped to the specified field, null if field was not parsed
*/
! public Long getParsed(TemporalField field) {
! for (Object obj : currentParsed().parsed) {
! if (obj instanceof FieldValue) {
! FieldValue fv = (FieldValue) obj;
! if (fv.field.equals(field)) {
! return fv.value;
! }
! }
! }
! return null;
! }
!
! /**
! * Gets the first value that was parsed for the specified type.
! * <p>
! * This searches the results of the parse, returning the first date-time found
! * of the specified type. No attempt is made to derive a value.
! *
! * @param clazz the type to query from the map, not null
! * @return the temporal object, null if it was not parsed
! */
! @SuppressWarnings("unchecked")
! public <T> T getParsed(Class<T> clazz) {
! for (Object obj : currentParsed().parsed) {
! if (clazz.isInstance(obj)) {
! return (T) obj;
! }
! }
! return null;
! }
!
! /**
! * Gets the list of parsed temporal information.
! *
! * @return the list of parsed temporal objects, not null, no nulls
! */
! List<Object> getParsed() {
! // package scoped for testing
! return currentParsed().parsed;
}
/**
* Stores the parsed field.
* <p>
* This stores a field-value pair that has been parsed.
* The value stored may be out of range for the field - no checks are performed.
*
* @param field the field to set in the field-value map, not null
* @param value the value to set in the field-value map
*/
! public void setParsedField(TemporalField field, long value) {
Objects.requireNonNull(field, "field");
! currentParsed().parsed.add(new FieldValue(field, value));
}
/**
! * Stores the parsed complete object.
* <p>
! * This stores a complete object that has been parsed.
! * No validation is performed on the date-time other than ensuring it is not null.
*
! * @param object the parsed object, not null
*/
! public <T> void setParsed(Object object) {
! Objects.requireNonNull(object, "object");
! currentParsed().parsed.add(object);
}
//-----------------------------------------------------------------------
/**
* Returns a {@code DateTimeBuilder} that can be used to interpret
--- 316,371 ----
* For example, the day-of-month might be set to 50, or the hour to 1000.
*
* @param field the field to query from the map, null returns null
* @return the value mapped to the specified field, null if field was not parsed
*/
! Long getParsed(TemporalField field) {
! return currentParsed().fieldValues.get(field);
}
/**
* Stores the parsed field.
* <p>
* This stores a field-value pair that has been parsed.
* The value stored may be out of range for the field - no checks are performed.
*
* @param field the field to set in the field-value map, not null
* @param value the value to set in the field-value map
+ * @param errorPos the position of the field being parsed
+ * @param successPos the position after the field being parsed
+ * @return the new position
*/
! int setParsedField(TemporalField field, long value, int errorPos, int successPos) {
Objects.requireNonNull(field, "field");
! Long old = currentParsed().fieldValues.put(field, value);
! return (old != null && old.longValue() != value) ? ~errorPos : successPos;
}
/**
! * Stores the parsed chronology.
* <p>
! * This stores the chronology that has been parsed.
! * No validation is performed other than ensuring it is not null.
*
! * @param chrono the parsed chronology, not null
*/
! void setParsed(Chronology chrono) {
! Objects.requireNonNull(chrono, "chrono");
! currentParsed().chrono = chrono;
! }
!
! /**
! * Stores the parsed zone.
! * <p>
! * This stores the zone that has been parsed.
! * No validation is performed other than ensuring it is not null.
! *
! * @param zone the parsed zone, not null
! */
! void setParsed(ZoneId zone) {
! Objects.requireNonNull(zone, "zone");
! currentParsed().zone = zone;
}
//-----------------------------------------------------------------------
/**
* Returns a {@code DateTimeBuilder} that can be used to interpret
*** 343,366 ****
* 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
*/
! public DateTimeBuilder toBuilder() {
! List<Object> cals = currentParsed().parsed;
DateTimeBuilder builder = new DateTimeBuilder();
! for (Object obj : cals) {
! if (obj instanceof FieldValue) {
! FieldValue fv = (FieldValue) obj;
! builder.addFieldValue(fv.field, fv.value);
! } else {
! builder.addCalendrical(obj);
}
}
return builder;
}
//-----------------------------------------------------------------------
/**
* Returns a string version of the context for debugging.
*
* @return a string representation of the context data, not null
--- 377,478 ----
* 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
*/
! 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
+ */
+ DateTimeParseContext resolveFields() {
+ Parsed data = currentParsed();
+ 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 this;
+ }
+
+ 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) {
+ Long old = currentParsed().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);
+ }
+ } else {
+ data.fieldValues.remove(changeField);
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ // 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 DateTimeException("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
*** 373,409 ****
//-----------------------------------------------------------------------
/**
* Temporary store of parsed data.
*/
private static final class Parsed {
! final List<Object> parsed = new ArrayList<>();
private Parsed() {
}
protected Parsed copy() {
Parsed cloned = new Parsed();
! cloned.parsed.addAll(this.parsed);
return cloned;
}
@Override
public String toString() {
! return parsed.toString();
! }
! }
!
! //-----------------------------------------------------------------------
! /**
! * Temporary store of a field-value pair.
! */
! private static final class FieldValue {
! final TemporalField field;
! final long value;
! private FieldValue(TemporalField field, long value) {
! this.field = field;
! this.value = value;
! }
! @Override
! public String toString() {
! return field.getName() + ' ' + value;
}
}
}
--- 485,508 ----
//-----------------------------------------------------------------------
/**
* 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;
}
}
}