Package com.asakusafw.runtime.stage.temporary

Source Code of com.asakusafw.runtime.stage.temporary.TemporaryFileOutputHelper

/**
* 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.stage.temporary;

import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xerial.snappy.Snappy;

import com.asakusafw.runtime.io.util.DataBuffer;

class TemporaryFileOutputHelper implements Closeable {

    static final Log LOG = LogFactory.getLog(TemporaryFileOutputHelper.class);

    static final AtomicInteger THREAD_COUNTER = new AtomicInteger();

    private static final ThreadFactory DAEMON_THREAD_FACTORY = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            t.setName(String.format("TemporaryFileOutput-%d", THREAD_COUNTER.incrementAndGet()));
            return t;
        }
    };

    private final OutputStream output;

    private final ExecutorService executor = Executors.newFixedThreadPool(1, DAEMON_THREAD_FACTORY);

    private final LinkedList<DataBuffer> available = new LinkedList<DataBuffer>();

    private Future<Result> running;

    private final String dataTypeName;

    private int positionInBlock;

    public TemporaryFileOutputHelper(OutputStream output, String dataTypeName) {
        this.output = output;
        this.dataTypeName = dataTypeName;
    }

    public synchronized void initialize(int initialBufferSize) {
        available.addFirst(new DataBuffer(initialBufferSize));
        available.addFirst(new DataBuffer(initialBufferSize));
    }

    public synchronized DataBuffer acquireBuffer() throws IOException, InterruptedException {
        // if no available buffers, first we try to flush the active buffer
        if (available.isEmpty()) {
            flushBuffer();
            if (available.isEmpty()) {
                throw new IllegalStateException();
            }
        }
        DataBuffer first = available.removeFirst();
        first.reset(0, 0);
        return first;
    }

    public synchronized void putNextPage(DataBuffer buffer) throws IOException, InterruptedException {
        // wait for the current active task
        flushBuffer();

        // submit task for writing contents in the buffer
        assert running == null;
        this.running = executor.submit(new Task(output, buffer, dataTypeName, positionInBlock));
    }

    private void flushBuffer() throws IOException, InterruptedException {
        if (running == null) {
            return;
        }
        Result result;
        try {
            result = running.get();
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof IOException) {
                throw new IOException("Exception occurred while writing contents", cause);
            } else if (cause instanceof InterruptedException) {
                throw (InterruptedException) cause;
            } else if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new IllegalStateException(cause);
        } finally {
            running = null;
        }
        this.positionInBlock = result.positionInBlock;

        // releases the written buffer
        this.available.addFirst(result.buffer);
    }

    @Override
    public synchronized void close() throws IOException {
        try {
            try {
                flushBuffer();
            } catch (InterruptedException e) {
                throw (IOException) new InterruptedIOException().initCause(e);
            }
        } finally {
            executor.shutdownNow();
            output.close();
        }
    }

    private static final class Result {

        final DataBuffer buffer;

        final int positionInBlock;

        public Result(DataBuffer buffer, int positionInBlock) {
            this.buffer = buffer;
            this.positionInBlock = positionInBlock;
        }
    }

    private static final class Task implements Callable<Result> {

        private static final byte[] ZEROS = new byte[64 * 1024];

        private final OutputStream output;

        private final DataBuffer buffer;

        private final String dataTypeName;

        private int positionInBlock;

        public Task(OutputStream output, DataBuffer buffer, String dataTypeName, int positionInBlock) {
            this.output = output;
            this.buffer = buffer;
            this.dataTypeName = dataTypeName;
            this.positionInBlock = positionInBlock;
        }

        @Override
        public Result call() throws Exception {
            flush();
            return new Result(buffer, positionInBlock);
        }

        private void flush() throws IOException {
            if (positionInBlock == 0) {
                positionInBlock += TemporaryFile.writeBlockHeader(output);
                positionInBlock += TemporaryFile.writeString(output, dataTypeName);
            }
            int length = buffer.getWritePosition();
            if (length <= 0) {
                return;
            }
            byte[] buf = TemporaryFile.getInstantBuffer(Snappy.maxCompressedLength(length));
            int compressed = Snappy.compress(buffer.getData(), 0, length, buf, 0);
            writeContentPage(buf, compressed);
            buffer.reset(0, 0);
        }

        private void writeContentPage(byte[] contents, int length) throws IOException {
            if (TemporaryFile.canWritePage(positionInBlock, length) == false) {
                if (TemporaryFile.canWritePage(0, length) == false) {
                    throw new IOException(MessageFormat.format(
                            "Page size is too large: {1} (> {0})",
                            TemporaryFile.BLOCK_SIZE,
                            length));
                }
                writeEndOfPage();
                positionInBlock = 0;
                positionInBlock += TemporaryFile.writeBlockHeader(output);
                positionInBlock += TemporaryFile.writeString(output, dataTypeName);
            }
            TemporaryFile.writeContentPageMark(output, length);
            output.write(contents, 0, length);
            positionInBlock += TemporaryFile.PAGE_HEADER_SIZE + length;
        }

        private void writeEndOfPage() throws IOException {
            TemporaryFile.writeEndOfBlockMark(output);
            positionInBlock += TemporaryFile.PAGE_HEADER_SIZE;
            int rest = TemporaryFile.BLOCK_SIZE - positionInBlock;
            assert rest >= 0;
            byte[] zeros = ZEROS;
            while (rest > 0) {
                int size = Math.min(rest, zeros.length);
                output.write(zeros, 0, size);
                rest -= size;
            }
            positionInBlock = TemporaryFile.BLOCK_SIZE;
        }
    }
}
TOP

Related Classes of com.asakusafw.runtime.stage.temporary.TemporaryFileOutputHelper

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.