Package com.asakusafw.runtime.io

Source Code of com.asakusafw.runtime.io.TsvEmitter

/**
* Copyright 2011-2014 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;

import static com.asakusafw.runtime.io.TsvConstants.*;

import java.io.IOException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.text.MessageFormat;

import org.apache.hadoop.io.Text;

import com.asakusafw.runtime.value.BooleanOption;
import com.asakusafw.runtime.value.ByteOption;
import com.asakusafw.runtime.value.DateOption;
import com.asakusafw.runtime.value.DateTimeOption;
import com.asakusafw.runtime.value.DateUtil;
import com.asakusafw.runtime.value.DecimalOption;
import com.asakusafw.runtime.value.DoubleOption;
import com.asakusafw.runtime.value.FloatOption;
import com.asakusafw.runtime.value.IntOption;
import com.asakusafw.runtime.value.LongOption;
import com.asakusafw.runtime.value.ShortOption;
import com.asakusafw.runtime.value.StringOption;
import com.asakusafw.runtime.value.ValueOption;

/**
* {@link ValueOption}の内容をTSV形式で出力する。
* <p>
* 次のように利用する。
* </p>
<pre><code>
Writer writer = ...;
Iterator&lt;SomeModel&gt; models = ...;
try {
    TsvEmitter emitter = new TsvEmitter(writer);
    while (models.hasNext();) {
        SomeModel model = models.next();
        emitter.emit(model.getHogeOption());
        emitter.emit(model.getFooOption());
        emitter.emit(model.getBarOption());
        emitter.endRecord();
    }
    emitter.close();
}
finally {
    writer.close();
}
</code></pre>
* <p>
* 特に指定がない限り、このクラスのメソッドの引数に{@code null}を指定した場合には
* {@link NullPointerException}がスローされる。
* </p>
*/
public class TsvEmitter implements RecordEmitter {

    private static final Charset TEXT_ENCODE = Charset.forName("UTF-8");

    private static final int BUFFER_SIZE = 2048;

    private final Writer writer;

    private final CharsetDecoder decoder;

    private final StringBuilder lineBuffer;

    private final char[] writeBuffer;

    private boolean headOfLine;

    private final CharBuffer decodeBuffer;

    // MEMO: 全体的に throws IOException は残しておく
    // これは拡張時に互換性を保つため。

    /**
     * インスタンスを生成する。
     * @param writer 出力先のライター
     * @throws IOException 初期化に失敗した場合
     * @throws IllegalArgumentException 引数に{@code null}が指定された場合
     */
    public TsvEmitter(Writer writer) throws IOException {
        if (writer == null) {
            throw new IllegalArgumentException("writer must not be null"); //$NON-NLS-1$
        }
        this.writer = writer;
        this.decoder = TEXT_ENCODE.newDecoder()
            .onMalformedInput(CodingErrorAction.REPORT)
            .onUnmappableCharacter(CodingErrorAction.REPORT);
        this.lineBuffer = new StringBuilder();
        this.writeBuffer = new char[BUFFER_SIZE];
        this.headOfLine = true;
        this.decodeBuffer = CharBuffer.wrap(writeBuffer);
    }

    @Override
    public void endRecord() throws IOException {
        flushLineBuffer();
        writer.write(RECORD_SEPARATOR);
        headOfLine = true;
    }

    private void flushLineBuffer() throws IOException {
        int rest = lineBuffer.length();
        int cursor = 0;
        while (rest > 0) {
            int chunkSize = Math.min(rest, writeBuffer.length);
            lineBuffer.getChars(cursor, cursor + chunkSize, writeBuffer, 0);
            writer.write(writeBuffer, 0, chunkSize);
            rest -= chunkSize;
            cursor += chunkSize;
        }
        lineBuffer.setLength(0);
    }

    private void startCell() {
        if (headOfLine == false) {
            lineBuffer.append(CELL_SEPARATOR);
        }
        headOfLine = false;
    }

    @Override
    public void emit(BooleanOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get() ? BOOLEAN_TRUE : BOOLEAN_FALSE);
    }

    @Override
    public void emit(ByteOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(ShortOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(IntOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(LongOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(FloatOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(DoubleOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(DecimalOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        lineBuffer.append(option.get());
    }

    @Override
    public void emit(StringOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        Text text = option.get();
        if (text.getLength() == 0) {
            return;
        }

        byte[] bytes = text.getBytes();
        ByteBuffer source = ByteBuffer.wrap(bytes, 0, text.getLength());
        decoder.reset();
        decodeBuffer.clear();
        while (true) {
            CoderResult result = decoder.decode(source, decodeBuffer, true);
            if (result.isError()) {
                throw new RecordFormatException(MessageFormat.format(
                        "Cannot process a character string (\"{0}\")",
                        result));
            }
            if (result.isUnderflow()) {
                consumeDecoded();
                break;
            }
            if (result.isOverflow()) {
                consumeDecoded();
            }
        }
        while (true) {
            CoderResult result = decoder.flush(decodeBuffer);
            if (result.isError()) {
                throw new RecordFormatException(MessageFormat.format(
                        "Cannot process a character string (\"{0}\")",
                        result));
            }
            if (result.isUnderflow()) {
                consumeDecoded();
                break;
            }
            if (result.isOverflow()) {
                consumeDecoded();
            }
        }
    }

    private void consumeDecoded() {
        decodeBuffer.flip();
        if (decodeBuffer.hasRemaining()) {
            char[] array = decodeBuffer.array();
            for (int i = decodeBuffer.position(), n = decodeBuffer.limit(); i < n; i++) {
                char c = array[i];
                if (c == '\t') {
                    lineBuffer.append(ESCAPE_CHAR);
                    lineBuffer.append(ESCAPE_HT);
                } else if (c == '\n') {
                    lineBuffer.append(ESCAPE_CHAR);
                    lineBuffer.append(ESCAPE_LF);
                } else if (c == '\\') {
                    lineBuffer.append(ESCAPE_CHAR);
                    lineBuffer.append(ESCAPE_CHAR);
                } else {
                    lineBuffer.append(c);
                }
            }
        }
        decodeBuffer.clear();
    }

    @Override
    public void emit(DateOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        int days = option.get().getElapsedDays();
        emitDate(days);
    }

    @Override
    public void emit(DateTimeOption option) throws IOException {
        startCell();
        if (emitNull(option)) {
            return;
        }
        long seconds = option.get().getElapsedSeconds();
        int days = DateUtil.getDayFromSeconds(seconds);
        emitDate(days);

        lineBuffer.append(DATE_TIME_SEPARATOR);

        int sec = DateUtil.getSecondOfDay(seconds);
        emitTime(sec);
    }

    private void emitDate(int days) {
        int year = DateUtil.getYearFromDay(days);
        int daysInYear = days - DateUtil.getDayFromYear(year);
        boolean leap = DateUtil.isLeap(year);
        int month = DateUtil.getMonthOfYear(daysInYear, leap);
        int day = DateUtil.getDayOfMonth(daysInYear, leap);

        fill('0', YEAR_FIELD_LENGTH, year);
        lineBuffer.append(DATE_FIELD_SEPARATOR);
        fill('0', MONTH_FIELD_LENGTH, month);
        lineBuffer.append(DATE_FIELD_SEPARATOR);
        fill('0', DATE_FIELD_LENGTH, day);
    }

    private void emitTime(int sec) {
        fill('0', HOUR_FIELD_LENGTH, sec / (60 * 60));
        lineBuffer.append(TIME_FIELD_SEPARATOR);
        fill('0', MINUTE_FIELD_LENGTH, sec / 60 % 60);
        lineBuffer.append(TIME_FIELD_SEPARATOR);
        fill('0', SECOND_FIELD_LENGTH, sec % 60);
    }

    private void fill(char filler, int columns, int value) {
        for (int i = 0, n = countToFill(columns, value); i < n; i++) {
            lineBuffer.append(filler);
        }
        lineBuffer.append(value);
    }

    private int countToFill(int columns, int value) {
        if (value < 0) {
            return 0;
        }
        for (int count = columns - 1, figure = 10; count >= 0; count--, figure *= 10) {
            if (value < figure) {
                return count;
            }
        }
        return 0;
    }

    private boolean emitNull(ValueOption<?> option) {
        if (option.isNull()) {
            lineBuffer.append(ESCAPE_CHAR);
            lineBuffer.append(ESCAPE_NULL_COLUMN);
            return true;
        }
        return false;
    }

    @Override
    public void flush() throws IOException {
        flushLineBuffer();
        writer.flush();
    }

    @Override
    public void close() throws IOException {
        if (headOfLine == false) {
            endRecord();
        }
        writer.close();
    }
}
TOP

Related Classes of com.asakusafw.runtime.io.TsvEmitter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.