/** * Copyright 2011-2017 Asakusa Framework Team. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.asakusafw.runtime.io.text.value; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.FieldPosition; import java.text.MessageFormat; import java.text.ParsePosition; import java.util.Optional; import com.asakusafw.runtime.io.text.TextUtil; import com.asakusafw.runtime.io.text.driver.FieldAdapter; import com.asakusafw.runtime.value.ValueOption; /** * An abstract implementation of {@link ValueOptionFieldAdapter} for numeric types. * @param <T> the property type * @since 0.9.1 */ public abstract class NumericOptionFieldAdapter<T extends ValueOption<T>> extends ValueOptionFieldAdapter<T> { private final DecimalFormat numberFormat; private final StringBuffer emitBuffer = new StringBuffer(); private final ParsePosition parsePosition = new ParsePosition(0); private final FieldPosition fieldPosition = new FieldPosition(0); NumericOptionFieldAdapter(String nullFormat, DecimalFormat numberFormat) { super(nullFormat); this.numberFormat = numberFormat; } @Override protected final void doParse(CharSequence contents, T property) { if (numberFormat == null) { doParseDefault(contents, property); } else { ParsePosition position = parsePosition; position.setIndex(0); position.setErrorIndex(-1); Number number = numberFormat.parse(contents.toString(), position); if (position.getIndex() == 0 || position.getIndex() != contents.length()) { throw new NumberFormatException(MessageFormat.format( "invalid number {0}", TextUtil.quote(contents))); } else if (number instanceof BigDecimal) { set((BigDecimal) number, property); } else { setSpecial(contents, number, property); } } } @Override protected final void doEmit(T property, StringBuilder output) { if (numberFormat == null) { doEmitDefault(property, output); } else { fieldPosition.setBeginIndex(0); fieldPosition.setEndIndex(0); emitBuffer.setLength(0); Number value = get(property); numberFormat.format(value, emitBuffer, fieldPosition); output.append(emitBuffer); } } /** * Parses the given non-null character sequence and set the parsed value into property. * @param contents the contents, never {@code null} * @param property the destination property * @throws IllegalArgumentException if the character sequence is malformed for this field */ protected abstract void doParseDefault(CharSequence contents, T property); /** * Emits the given non-null property value into the string builder. * @param property the property value, never {@code null} nor represents {@code null} * @param output the destination buffer */ protected abstract void doEmitDefault(T property, StringBuilder output); /** * Returns the property value. * @param property the property * @return the property value */ protected abstract Number get(T property); /** * Sets the given number into the property. * @param value the value * @param property the property */ protected abstract void set(BigDecimal value, T property); /** * Sets the given special number into the property. * @param contents the original contents * @param value the value * @param property the property */ protected void setSpecial(CharSequence contents, Number value, T property) { throw new NumberFormatException(MessageFormat.format( "invalid number {0}", TextUtil.quote(contents))); } /** * A basic implementation of builder for {@link NumericOptionFieldAdapter}. * @param <S> this builder type * @param <T> the build target type * @since 0.9.1 */ protected abstract static class NumericBuilderBase<S extends NumericBuilderBase<S, T>, T extends FieldAdapter<?>> extends BuilderBase<S, T> { private String numberFormat; private DecimalFormatSymbols decimalFormatSymbols; /** * Sets the decimal format. * @param newValue the format string * @return this */ public S withNumberFormat(String newValue) { this.numberFormat = newValue; return self(); } /** * Sets a decimal format symbols. * @param newValue the format symbols * @return this */ public S withDecimalFormatSymbols(DecimalFormatSymbols newValue) { this.decimalFormatSymbols = Optional.ofNullable(newValue) .map(v -> (DecimalFormatSymbols) v.clone()) .orElse(null); return self(); } /** * Returns the number format. * @return the number format */ protected DecimalFormat getDecimalFormat() { return Optional.ofNullable(numberFormat) .map(s -> { DecimalFormat f = new DecimalFormat(s, Optional.ofNullable(decimalFormatSymbols) .orElseGet(DecimalFormatSymbols::getInstance)); f.setParseBigDecimal(true); return f; }) .orElse(null); } } }