/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (IntervalParser.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.range; import net.time4j.engine.AttributeQuery; import net.time4j.engine.Temporal; import net.time4j.engine.TimeLine; import net.time4j.format.expert.ChronoParser; import net.time4j.format.expert.ParseLog; import java.text.ParseException; import static net.time4j.format.Attributes.TRAILING_CHARACTERS; import static net.time4j.range.IntervalEdge.CLOSED; import static net.time4j.range.IntervalEdge.OPEN; /** * <p>Interpretiert Intervalle basierend auf Zeitformatierern, die auf die * Start- oder Endkomponenten angewandt werden. </p> * * @author Meno Hochschild * @param <T> generic temporal type * @param <I> generic interval type * @since 2.0 */ class IntervalParser<T extends Temporal<? super T>, I extends IsoInterval<T, I>> implements ChronoParser<I> { //~ Instanzvariablen -------------------------------------------------- private final IntervalFactory<T, I> factory; private final ChronoParser<T> startFormat; private final ChronoParser<T> endFormat; private final BracketPolicy policy; private final Character separator; //~ Konstruktoren ----------------------------------------------------- IntervalParser( IntervalFactory<T, I> factory, ChronoParser<T> startFormat, ChronoParser<T> endFormat, // optional BracketPolicy policy, Character separator // optional ) { super(); if (policy == null) { throw new NullPointerException("Missing bracket policy."); } this.factory = factory; this.startFormat = startFormat; this.endFormat = endFormat; this.policy = policy; this.separator = separator; } //~ Methoden ---------------------------------------------------------- /** * <p>Factory method with either solidus or hyphen as separation char. </p> * * @param <T> generic temporal type * @param <I> generic interval type * @param factory interval factory * @param parser boundary parser * @param policy bracket policy * @return new interval parser * @since 2.0 */ static <T extends Temporal<? super T>, I extends IsoInterval<T, I>> IntervalParser<T, I> of( IntervalFactory<T, I> factory, ChronoParser<T> parser, BracketPolicy policy ) { if (parser == null) { throw new NullPointerException("Missing boundary parser."); } return new IntervalParser<>(factory, parser, parser, policy, null); } /** * <p>General factory method. </p> * * @param <T> generic temporal type * @param <I> generic interval type * @param factory interval factory * @param startFormat formatter for lower interval boundary * @param endFormat formatter for upper interval boundary * @param policy bracket policy * @param separator separation char between start and end component * @return new interval parser * @since 3.9/4.6 */ static <T extends Temporal<? super T>, I extends IsoInterval<T, I>> IntervalParser<T, I> of( IntervalFactory<T, I> factory, ChronoParser<T> startFormat, ChronoParser<T> endFormat, BracketPolicy policy, char separator ) { if (startFormat == null) { throw new NullPointerException("Missing start boundary parser."); } else if (endFormat == null) { throw new NullPointerException("Missing end boundary parser."); } return new IntervalParser<>( factory, startFormat, endFormat, policy, Character.valueOf(separator)); } /** * <p>Interpretiert den angegebenen Text als chronologisches Intervall. </p> * * @param text text to be parsed * @return parse result * @throws IndexOutOfBoundsException if the text is empty * @throws ParseException if the text is not parseable */ I parse(String text) throws ParseException { ParseLog plog = new ParseLog(); AttributeQuery attrs = this.startFormat.getAttributes(); I ret = this.parse(text, plog, attrs); int pos = plog.getPosition(); int len = text.length(); if ((ret == null) || plog.isError()) { throw new ParseException( plog.getErrorMessage(), plog.getErrorIndex()); } else if ( (pos < len) && !attrs.get(TRAILING_CHARACTERS, Boolean.FALSE).booleanValue() ) { String suffix; if (len - pos <= 10) { suffix = text.subSequence(pos, len).toString(); } else { suffix = text.subSequence(pos, pos + 10).toString() + "..."; } throw new ParseException("Unparsed trailing characters: " + suffix, pos); } return ret; } @Override public I parse( CharSequence text, ParseLog status, AttributeQuery attributes ) { // initialization phase int start = status.getPosition(); int len = text.length(); int pos = start; if (pos >= len) { throw new IndexOutOfBoundsException("[" + pos + "]: " + text.toString()); } IntervalEdge left = CLOSED; IntervalEdge right = this.factory.isCalendrical() ? CLOSED : OPEN; T t1 = null; T t2 = null; Boundary<T> lower = null; Boundary<T> upper = null; int posLower = -1; int posUpper = -1; ParseLog lowerLog = null; ParseLog upperLog = null; String period = null; int symbol = 0; // starting boundary char c = text.charAt(pos); boolean leftVisible = ((c == '[') || (c == '(')); boolean rightVisible = false; if (leftVisible) { if (this.policy == BracketPolicy.SHOW_NEVER) { status.setError( pos, "Illegal start boundary due to bracket policy " + this.policy + ": " + c); } else if (c == '(') { left = OPEN; } pos++; } else if (this.policy == BracketPolicy.SHOW_ALWAYS) { status.setError(pos, "Missing start boundary bracket."); } if (status.isError()) { return null; } else if (pos >= len) { status.setError( pos, "Missing interval start component, end of text reached."); return null; } // start component and solidus c = text.charAt(pos); if (c == 'P') { posLower = pos; int index = pos; int solidus = -1; // here assuming iso mode hence searching for solidus only while (++index < len) { if (text.charAt(index) == '/') { solidus = index; break; } } if (solidus == -1) { status.setError( pos, "Solidus char separating start and end boundaries expected."); return null; } period = text.subSequence(pos, solidus).toString(); pos = solidus + 1; } else if ( (c == '-') && (pos + 1 < len) && this.isExpectedSeparator(text.charAt(pos + 1)) ) { if ((left == CLOSED) && leftVisible) { status.setError(pos - 1, "Open boundary expected."); return null; } left = OPEN; lower = Boundary.infinitePast(); symbol = 1; pos += 2; } else if ( (c == '-') && (pos + 1 < len) && (text.charAt(pos + 1) == '\u221E') ) { if ((left == CLOSED) && leftVisible) { status.setError(pos - 1, "Open boundary expected."); return null; } left = OPEN; lower = Boundary.infinitePast(); pos += 2; this.checkSeparatorChar(text, status, pos, len); if (status.isError()) { return null; } symbol = 2; pos++; } else { lowerLog = new ParseLog(pos); t1 = this.startFormat.parse(text, lowerLog, attributes); if (t1 == null || lowerLog.isError()) { status.setError(pos, lowerLog.getErrorMessage()); return null; } lower = Boundary.of(left, t1); pos = lowerLog.getPosition(); this.checkSeparatorChar(text, status, pos, len); if (status.isError()) { return null; } pos++; } // end component after separator char if (pos >= len) { status.setError( pos, "Missing interval end component, end of text reached."); return null; } c = text.charAt(pos); if (c == 'P') { if (t1 == null) { status.setError( pos, "Cannot process end period without start time."); return null; } posUpper = pos; int endIndex = len; char test = text.charAt(endIndex - 1); if ((test == ']') || (test == ')')) { endIndex--; } period = text.subSequence(pos, endIndex).toString(); pos = endIndex; } else if ( (c == '-') && ((pos + 1 >= len) || (text.charAt(pos + 1) == ')')) ) { if (symbol == 2) { status.setError(pos, "Mixed infinity symbols not allowed."); return null; } right = OPEN; upper = Boundary.infiniteFuture(); symbol = 1; pos++; } else if ( (c == '+') && (pos + 1 < len) && (text.charAt(pos + 1) == '\u221E') ) { if ( (pos + 2 < len) && (text.charAt(pos + 2) == ']') && (this.policy != BracketPolicy.SHOW_NEVER) ) { status.setError(pos + 2, "Open boundary expected."); return null; } else if (symbol == 1) { status.setError(pos, "Mixed infinity symbols not allowed."); return null; } right = OPEN; upper = Boundary.infiniteFuture(); symbol = 2; pos += 2; } else { upperLog = new ParseLog(pos); if (lowerLog == null) { t2 = this.startFormat.parse(text, upperLog, attributes); } else if (this.endFormat == null) { t2 = this.parseReducedEnd(text, t1, lowerLog, upperLog, attributes); } else { t2 = this.endFormat.parse(text, upperLog, attributes); } if (t2 == null || upperLog.isError()) { status.setError(pos, upperLog.getErrorMessage()); return null; } upper = Boundary.of(right, t2); pos = upperLog.getPosition(); } // ending boundary if (pos >= len) { if (this.policy == BracketPolicy.SHOW_ALWAYS) { status.setError(pos, "Missing end boundary bracket."); } } else { c = text.charAt(pos); if ((c == ']') || (c == ')')) { if (this.policy == BracketPolicy.SHOW_NEVER) { status.setError( pos, "Illegal end boundary due to bracket policy " + this.policy + ": " + c); } else { rightVisible = true; right = ((c == ']') ? CLOSED : OPEN); if (t2 != null) { upper = Boundary.of(right, t2); } pos++; } } else if (this.policy == BracketPolicy.SHOW_ALWAYS) { status.setError(pos, "Missing end boundary bracket."); } } if (status.isError()) { return null; } // special case for P-strings (ISO-support) if (period != null) { IntervalFactory<T, I> iif = this.factory; if (lower == null) { if (t2 == null) { status.setError( posLower, "Cannot process start period without end time."); return null; } t1 = iif.minusPeriod(t2, period, upperLog, attributes); if (t1 == null) { status.setError(posLower, "Wrong period: " + period); return null; } lower = Boundary.of(left, t1); } if (upper == null) { t2 = iif.plusPeriod(t1, period, lowerLog, attributes); if (t2 == null) { status.setError(posUpper, "Wrong period: " + period); return null; } upper = Boundary.of(right, t2); } } if ((symbol == 0) && this.factory.supportsInfinity()) { lower = this.resolveInfinity(leftVisible, lower); upper = this.resolveInfinity(rightVisible, upper); } // create and return interval try { I interval = this.factory.between(lower, upper); if (this.policy == BracketPolicy.SHOW_WHEN_NON_STANDARD) { boolean visible = this.policy.display(interval); if (visible && (!leftVisible || !rightVisible)) { int index = (!rightVisible ? pos : start); status.setError(index, "Missing boundary."); return null; } else if (!visible && (leftVisible || rightVisible)) { int index = (rightVisible ? pos : start); status.setError( index, "Standard boundary not allowed due to bracket policy: " + this.policy); return null; } } status.setPosition(pos); return interval; } catch (IllegalArgumentException iae) { status.setError(pos, iae.getMessage()); return null; } } /** * <p>Custom parsing using any kind of interval pattern (possibly with or-logic). </p> * * @param text text to be parsed * @param factory interval factory * @param parser parser used for parsing either start or end component * @param pattern interval pattern containing the placeholders {0} or {1} * @return parsed interval * @throws ParseException if parsing fails * @since 4.18 */ static <T, I extends ChronoInterval<T>> I parsePattern( CharSequence text, IntervalCreator<T, I> factory, ChronoParser<T> parser, String pattern ) throws ParseException { ParseLog plog = new ParseLog(); String[] components = pattern.split("\\|"); for (String component : components) { plog.reset(); I interval = IntervalParser.parseComponent(text, factory, parser, component, plog); if ((interval != null) && !plog.isError()) { return interval; } } if (plog.isError()) { throw new ParseException(plog.getErrorMessage(), plog.getErrorIndex()); } else { throw new ParseException("Parsing of interval failed: " + text, plog.getPosition()); } } private static <T, I extends ChronoInterval<T>> I parseComponent( CharSequence text, IntervalCreator<T, I> factory, ChronoParser<T> parser, String pattern, ParseLog plog ){ int pos = plog.getPosition(); int len = text.length(); T start = null; T end = null; int i = 0; int n = pattern.length(); boolean startWasSet = false; boolean endWasSet = false; AttributeQuery attrs = parser.getAttributes(); while (i < n) { char c = pattern.charAt(i); if ((c == '{') && (i + 2 < n) && (pattern.charAt(i + 2) == '}')) { char next = pattern.charAt(i + 1); if (next == '0') { if (startWasSet) { plog.setError(pos, "Cannot parse start component more than once."); return null; } plog.setPosition(pos); if ((pos + 1 < len) && (text.charAt(pos) == '-') && (text.charAt(pos + 1) == '\u221E')) { // start = null; pos += 2; } else { start = parser.parse(text, plog, attrs); if ((start == null) || plog.isError()) { return null; } else { pos = plog.getPosition(); } } startWasSet = true; i += 3; continue; } else if (next == '1') { if (endWasSet) { plog.setError(pos, "Cannot parse end component more than once."); return null; } plog.setPosition(pos); if ((pos + 1 < len) && (text.charAt(pos) == '+') && (text.charAt(pos + 1) == '\u221E')) { // end = null; pos += 2; } else { end = parser.parse(text, plog, attrs); if ((end == null) || plog.isError()) { return null; } else { pos = plog.getPosition(); } } endWasSet = true; i += 3; continue; } } else if (c == '[' || c == ']' || c == '(' || c == ')') { plog.setError(pos, "Brackets representing interval boundaries cannot be parsed: " + text); return null; } if (pos >= len) { plog.setError(pos, "End of text reached."); return null; } else if (c != text.charAt(pos)) { plog.setError(pos, "Literal mismatched: " + text.toString() + " (expected=" + pattern + ")"); return null; } i++; pos++; } if ((pos < len) && !attrs.get(TRAILING_CHARACTERS, Boolean.FALSE).booleanValue()) { plog.setError(pos, "Trailing characters found: " + text); return null; } Boundary<T> s = Boundary.infinitePast(); Boundary<T> e = Boundary.infiniteFuture(); if (start != null) { s = Boundary.ofClosed(start); } if (end != null) { if (factory.isCalendrical()) { e = Boundary.ofClosed(end); } else { e = Boundary.ofOpen(end); } } try { return factory.between(s, e); } catch (IllegalArgumentException iae) { plog.setError(pos, iae.getMessage()); return null; } } protected T parseReducedEnd( CharSequence text, T t1, ParseLog lowerLog, ParseLog upperLog, AttributeQuery attrs ) { return null; } private Boundary<T> resolveInfinity( boolean visible, Boundary<T> boundary ) { if (!boundary.isInfinite() && (!visible || boundary.isOpen())) { TimeLine<T> timeLine = this.factory.getTimeLine(); T test = boundary.getTemporal(); if (test.equals(timeLine.getMinimum())) { return Boundary.infinitePast(); } else if (test.equals(timeLine.getMaximum())) { return Boundary.infiniteFuture(); } } return boundary; } private void checkSeparatorChar( CharSequence text, ParseLog status, int pos, int len ) { if (pos >= len) { status.setError( pos, "Reached end of text, but not found any separation char."); return; } if (!this.isExpectedSeparator(text.charAt(pos))) { status.setError( pos, "Missing or misplaced separation char between start and end boundaries: " + ((this.separator == null) ? "/ or -" : this.separator.toString())); } } private boolean isExpectedSeparator(char test) { if (this.separator == null) { return ((test == '/') || (test == '-')); } else { return (test == this.separator.charValue()); } } }