/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (TextProcessor.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.format.expert; import net.time4j.Month; import net.time4j.PlainDate; import net.time4j.engine.AttributeQuery; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoException; import net.time4j.format.Attributes; import net.time4j.format.Leniency; import net.time4j.format.OutputContext; import net.time4j.format.TextElement; import net.time4j.format.TextWidth; import net.time4j.format.internal.GregorianTextElement; import net.time4j.history.internal.HistorizedElement; import java.io.IOException; import java.util.Locale; import java.util.Set; /** * <p>Text-Formatierung eines chronologischen Elements. </p> * * @param <V> generic type of element values (String or Enum) * @author Meno Hochschild * @since 3.0 */ final class TextProcessor<V> implements FormatProcessor<V> { //~ Instanzvariablen ---------------------------------------------- private final TextElement<V> element; private final boolean protectedMode; // quick path optimization private final GregorianTextElement<V> gte; private final Locale language; private final TextWidth tw; private final OutputContext oc; private final Leniency lenientMode; private final int protectedLength; //~ Konstruktoren ----------------------------------------------------- private TextProcessor( TextElement<V> element, boolean protectedMode, Locale language, TextWidth tw, OutputContext oc, Leniency lenientMode, int protectedLength ) { super(); if (element == null) { throw new NullPointerException("Missing element."); } this.element = element; this.protectedMode = protectedMode; // quick path members this.gte = (element instanceof GregorianTextElement) ? (GregorianTextElement<V>) element : null; this.language = language; this.tw = tw; this.oc = oc; this.lenientMode = lenientMode; this.protectedLength = protectedLength; } //~ Methoden ---------------------------------------------------------- /** * <p>Konstruiert eine neue Instanz. </p> * * @param element element to be formatted * @return new processor instance */ static <V> TextProcessor<V> create(TextElement<V> element) { return new TextProcessor<>( element, false, Locale.ROOT, TextWidth.WIDE, OutputContext.FORMAT, Leniency.SMART, 0); } /** * <p>Konstruiert eine neue Instanz. </p> * * @param element element to be formatted * @return new processor instance whose element cannot be changed */ static <V> TextProcessor<V> createProtected(TextElement<V> element) { return new TextProcessor<>( element, true, Locale.ROOT, TextWidth.WIDE, OutputContext.FORMAT, Leniency.SMART, 0); } @Override public void print( ChronoDisplay formattable, Appendable buffer, AttributeQuery attributes, Set<ElementPosition> positions, // optional boolean quickPath ) throws IOException { try { if (buffer instanceof CharSequence) { CharSequence cs = (CharSequence) buffer; int offset = cs.length(); this.print(formattable, buffer, attributes, quickPath); if (positions != null) { positions.add(new ElementPosition(this.element, offset, cs.length())); } } else { this.print(formattable, buffer, attributes, quickPath); } } catch (ChronoException ce) { throw new IllegalArgumentException(ce); } } @Override public void parse( CharSequence text, ParseLog status, AttributeQuery attributes, ParsedEntity<?> parsedResult, boolean quickPath ) { int start = status.getPosition(); int len = text.length(); int protectedChars = (quickPath ? this.protectedLength : attributes.get(Attributes.PROTECTED_CHARACTERS, 0)); if (protectedChars > 0) { len -= protectedChars; } if (start >= len) { status.setError(start, "Missing chars for: " + this.element.name()); status.setWarning(); return; } Object value; if (quickPath && (this.gte != null) && (this.lenientMode != null)) { value = this.gte.parse(text, status.getPP(), this.language, this.tw, this.oc, this.lenientMode); } else if (this.element instanceof HistorizedElement) { value = ((HistorizedElement) this.element).parse(text, status.getPP(), attributes, parsedResult); } else { value = this.element.parse(text, status.getPP(), attributes); } if (status.isError()) { Class<V> valueType = this.element.getType(); if (valueType.isEnum()) { status.setError(status.getErrorIndex(), "No suitable enum found: " + valueType.getName()); } else { status.setError(status.getErrorIndex(), "Unparseable element: " + this.element.name()); } } else if (value == null) { status.setError(start, "No interpretable value."); } else if (this.element == PlainDate.MONTH_OF_YEAR) { // prevents autoboxing and extra array allocation parsedResult.put(PlainDate.MONTH_AS_NUMBER, Month.class.cast(value).getValue()); } else { parsedResult.put(this.element, value); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof TextProcessor) { TextProcessor<?> that = (TextProcessor) obj; return ( this.element.equals(that.element) && (this.protectedMode == that.protectedMode)); } else { return false; } } @Override public int hashCode() { return this.element.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(64); sb.append(this.getClass().getName()); sb.append("[element="); sb.append(this.element.name()); sb.append(",protected-mode="); sb.append(this.protectedMode); sb.append(']'); return sb.toString(); } @Override public ChronoElement<V> getElement() { return this.element; } @Override public FormatProcessor<V> withElement(ChronoElement<V> element) { if (this.protectedMode || (this.element == element)) { return this; } else if (element instanceof TextElement) { return TextProcessor.create((TextElement<V>) element); } else { throw new IllegalArgumentException( "Text element required: " + element.getClass().getName()); } } @Override public boolean isNumerical() { return false; } @Override public FormatProcessor<V> quickPath( ChronoFormatter<?> formatter, AttributeQuery attributes, int reserved ) { Leniency leniency = attributes.get(Attributes.LENIENCY, Leniency.SMART); boolean multipleContext = attributes.get(Attributes.PARSE_MULTIPLE_CONTEXT, Boolean.TRUE).booleanValue(); boolean caseInsensitive = attributes.get(Attributes.PARSE_CASE_INSENSITIVE, Boolean.TRUE).booleanValue(); boolean partialCompare = attributes.get(Attributes.PARSE_PARTIAL_COMPARE, Boolean.FALSE).booleanValue(); if ((leniency == Leniency.STRICT) && (multipleContext || caseInsensitive || partialCompare)) { leniency = null; } else if ((leniency == Leniency.SMART) && (!multipleContext || !caseInsensitive || partialCompare)) { leniency = null; } else if (!multipleContext || !caseInsensitive || !partialCompare) { // lax mode leniency = null; } return new TextProcessor<>( this.element, this.protectedMode, attributes.get(Attributes.LANGUAGE, Locale.ROOT), attributes.get(Attributes.TEXT_WIDTH, TextWidth.WIDE), attributes.get(Attributes.OUTPUT_CONTEXT, OutputContext.FORMAT), leniency, attributes.get(Attributes.PROTECTED_CHARACTERS, 0) ); } private void print( ChronoDisplay formattable, Appendable buffer, AttributeQuery attributes, boolean quickPath ) throws IOException { if ((this.gte != null) && quickPath) { this.gte.print(formattable, buffer, this.language, this.tw, this.oc); } else { this.element.print(formattable, buffer, attributes); } } }