/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (CustomizedProcessor.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.Moment; import net.time4j.ZonalDateTime; import net.time4j.engine.AttributeQuery; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoFunction; import net.time4j.engine.Chronology; import java.io.IOException; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; /** * <p>Repräsentiert eine benutzerdefinierte Formatroutine. </p> * * @param <V> generic type of element values * @author Meno Hochschild * @since 3.0 */ final class CustomizedProcessor<V> implements FormatProcessor<V> { //~ Statische Felder/Initialisierungen -------------------------------- private static final ChronoFunction<ChronoDisplay, Void> NO_RESULT = (context -> null); //~ Instanzvariablen -------------------------------------------------- private final ChronoElement<V> element; private final ChronoPrinter<V> printer; private final ChronoParser<V> parser; private final boolean passThroughZDT; // quick path optimization private boolean optPrinter; private boolean optParser; private boolean singleStepMode; //~ Konstruktoren ----------------------------------------------------- /** * <p>Konstruiert eine neue benutzerdefinierte Formatverarbeitung. </p> * * @param element chronological element to be formatted * @param printer helper object for text output * @param parser helper object for parsing */ CustomizedProcessor( ChronoElement<V> element, ChronoPrinter<V> printer, ChronoParser<V> parser ) { this(element, printer, parser, false, false, false); } private CustomizedProcessor( ChronoElement<V> element, ChronoPrinter<V> printer, ChronoParser<V> parser, boolean optPrinter, boolean optParser, boolean singleStepMode ) { super(); if (element == null) { throw new NullPointerException("Missing element."); } else if (printer == null) { throw new NullPointerException("Missing printer."); } else if (parser == null) { throw new NullPointerException("Missing parser."); } this.element = element; this.printer = printer; this.parser = parser; this.passThroughZDT = ((printer instanceof ChronoFormatter) && (element.getType() == Moment.class)); this.optPrinter = optPrinter; this.optParser = optParser; this.singleStepMode = singleStepMode; } //~ Methoden ---------------------------------------------------------- @Override public void print( ChronoDisplay formattable, Appendable buffer, AttributeQuery attributes, Set<ElementPosition> positions, // optional boolean quickPath ) throws IOException { if (quickPath && this.optPrinter) { attributes = ChronoFormatter.class.cast(this.printer).getAttributes(); } // special optimization avoiding double conversion from Moment to ZonalDateTime if (this.passThroughZDT && (formattable instanceof ZonalDateTime) && (positions == null)) { ChronoFormatter<?> cf = (ChronoFormatter<?>) this.printer; cf.print(formattable, buffer, attributes, false); return; } V value = formattable.get(this.element); StringBuilder collector = new StringBuilder(); if ((positions != null) && (buffer instanceof CharSequence)) { int offset = ((CharSequence) buffer).length(); if (this.printer instanceof ChronoFormatter) { ChronoFormatter<?> cf = ChronoFormatter.class.cast(this.printer); Set<ElementPosition> result = print(cf, value, collector, attributes); Set<ElementPosition> set = new LinkedHashSet<>(); for (ElementPosition ep : result) { set.add( new ElementPosition( ep.getElement(), offset + ep.getStartIndex(), offset + ep.getEndIndex())); } positions.addAll(set); } else { this.printer.print(value, collector, attributes, NO_RESULT); positions.add( new ElementPosition( this.element, offset, offset + collector.length())); } } else { this.printer.print(value, collector, attributes, NO_RESULT); } buffer.append(collector); } @Override public void parse( CharSequence text, ParseLog status, AttributeQuery attributes, ParsedEntity<?> parsedResult, boolean quickPath ) { int offset = status.getPosition(); try { if (quickPath && this.optParser) { attributes = ChronoFormatter.class.cast(this.parser).getAttributes(); } V value = this.parser.parse(text, status, attributes); if (value == null) { status.setError(offset, status.getErrorMessage()); } else if (this.singleStepMode && (parsedResult instanceof ParsedValue)) { parsedResult.setResult(value); } else { ChronoEntity<?> raw = status.getRawValues(); for (ChronoElement<?> e : raw.getRegisteredElements()) { if (e.getType() == Integer.class){ @SuppressWarnings("unchecked") // avoids autoboxing ChronoElement<Integer> ie = (ChronoElement<Integer>) e; parsedResult.put(ie, raw.getInt(ie)); } else { parsedResult.put(e, raw.get(e)); } } parsedResult.put(this.element, value); } } catch (IndexOutOfBoundsException ex) { status.setError(offset, ex.getMessage()); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CustomizedProcessor) { CustomizedProcessor<?> that = (CustomizedProcessor) obj; return ( (this.element.equals(that.element)) && this.printer.equals(that.printer) && this.parser.equals(that.parser) ); } else { return false; } } @Override public int hashCode() { return ( 7 * this.element.hashCode() + 31 * this.printer.hashCode() + 37 * this.parser.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(", printer="); sb.append(this.printer); sb.append(", parser="); sb.append(this.parser); sb.append(']'); return sb.toString(); } @Override public ChronoElement<V> getElement() { return this.element; } @Override public FormatProcessor<V> withElement(ChronoElement<V> element) { if (this.element == element) { return this; } return new CustomizedProcessor<>(element, this.printer, this.parser); } @Override public boolean isNumerical() { return false; } @Override @SuppressWarnings("unchecked") public FormatProcessor<V> quickPath( ChronoFormatter<?> formatter, AttributeQuery attributes, int reserved ) { boolean singleStepMode = formatter.isSingleStepOptimizationPossible() && this.element.getType().equals(formatter.getChronology().getChronoType()); if (attributes instanceof AttributeSet) { // should always happen, see implementation of FormatStep ChronoPrinter<V> effectivePrinter = this.printer; ChronoParser<V> effectiveParser = this.parser; boolean effectiveOptPrinter = false; boolean effectiveOptParser = false; Map<ChronoElement<?>, Object> defaultMap = formatter.getDefaults(); AttributeSet outer = (AttributeSet) attributes; if (this.printer instanceof ChronoFormatter) { ChronoFormatter<?> qPrinter = ChronoFormatter.class.cast(this.printer); effectivePrinter = (ChronoPrinter<V>) qPrinter.with(adjust(defaultMap, qPrinter), outer); effectiveOptPrinter = true; } if (this.parser instanceof ChronoFormatter) { ChronoFormatter<?> qParser = ChronoFormatter.class.cast(this.parser); effectiveParser = (ChronoParser<V>) qParser.with(adjust(defaultMap, qParser), outer); effectiveOptParser = true; } return new CustomizedProcessor<>( this.element, effectivePrinter, effectiveParser, effectiveOptPrinter, effectiveOptParser, singleStepMode); } else if (this.optPrinter || this.optParser) { return new CustomizedProcessor<>(this.element, this.printer, this.parser); } else { return this; } } boolean isSingleStepMode() { return this.singleStepMode; } private static Map<ChronoElement<?>, Object> adjust( Map<ChronoElement<?>, Object> outerDefaults, ChronoFormatter<?> inner ) { Chronology<?> chrono = inner.getChronology(); Map<ChronoElement<?>, Object> result = new HashMap<>(); for (ChronoElement<?> element : outerDefaults.keySet()) { if (chrono.isSupported(element)) { result.put(element, outerDefaults.get(element)); } } return result; } // wildcard capture private static <T> Set<ElementPosition> print( ChronoFormatter<T> formatter, Object value, StringBuilder collector, AttributeQuery attributes ) throws IOException { return formatter.print( formatter.getChronology().getChronoType().cast(value), collector, attributes); } }