All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lyncode.jtwig.render.stream.RenderStream Maven / Gradle / Ivy

package com.lyncode.jtwig.render.stream;

import com.lyncode.jtwig.content.api.Renderable;
import com.lyncode.jtwig.exception.RenderException;
import com.lyncode.jtwig.render.RenderContext;

import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class RenderStream {
    private static int minThreads = 20;
    private static int maxThreads = 100;
    private static ExecutorService sExecutor = null;

    private static ExecutorService executorService() {
        if (sExecutor == null)
            sExecutor = new ThreadPoolExecutor(minThreads, maxThreads, 60L, TimeUnit.SECONDS, new SynchronousQueue());
        return sExecutor;
    }

    public static void withMinThreads (int value) {
        // Should run this method before do any parsing (initialization)
        minThreads = value;
    }
    public static void withMaxThreads (int value) {
        // Should run this method before do any parsing (initialization)
        maxThreads = value;
    }

    private final OutputStream mRootOutputStream;
    private final MultiOuputStream mMultiStream;
    private RenderIndex mIndex;
    private final ReentrantReadWriteLock mLock;
    private final RenderControl mControl;

    private RenderStream(MultiOuputStream multiStream, OutputStream stream, RenderIndex dIndex,
                         ReentrantReadWriteLock rwl, RenderControl renderControl) {
        this.mMultiStream = multiStream;
        this.mRootOutputStream = stream;
        this.mIndex = dIndex;
        this.mControl = renderControl;
        this.mLock = rwl;

        if (mIndex != null) {
            if (mIndex.isMostLeft()) {
                multiStream.newStream(mIndex, new SingleOuputStream(stream, true, false));
            } else if (mIndex.isLeft()) {
                multiStream.newStream(mIndex, new SingleOuputStream(
                        multiStream.get(mIndex.previous()).getStream(), true, true));
            } else {
                multiStream.newStream(mIndex);
            }
        }
    }

    public RenderStream(OutputStream outputStream) {
        this(new MultiOuputStream(), outputStream, null,
             new ReentrantReadWriteLock(),
             new RenderControl());
    }

    public RenderStream renderConcurrent(final Renderable content, final RenderContext context) {
        try {
            mControl.push();
            executorService().execute(new RenderTask(content, context));
        } catch (OutOfMemoryError e) {
            executorService().shutdownNow();
            mControl.cancel();
        }
        return this;
    }

    public RenderStream waitForExecutorCompletion() throws RenderException {
        try {
            mControl.waitFinish();
        } catch (InterruptedException e) {
            throw new RenderException(e);
        }
        return this;
    }

    public RenderStream notifyTaskFinished() {
        mControl.poll();
        return this;
    }

    public OutputStream getOuputStream() {
        if (mIndex != null) {
            return mMultiStream.get(mIndex);
        } else {
            return getRootOutputStream();
        }
    }

    public RenderStream write(byte[] bytes) throws IOException {
        lockWrite();
        getOuputStream().write(bytes);
        unlockWrite();
        return this;
    }

    public RenderStream close() throws IOException {
        lockWrite();
        if (mIndex != null) {
            mMultiStream.close(mIndex);
        }
        unlockWrite();
        return this;
    }

    public RenderStream fork() throws IOException {
        lockChange();
        if (mIndex == null) {
            mIndex = RenderIndex.newIndex(); //create new index for the first time we have a concurrent stream
        }
        mMultiStream.waitOrder(mIndex); // this index will wait for his left children

        RenderIndex forkedIndex = mIndex.left(); // forked will render the left children
        mIndex = mIndex.right(); // this will continue with the right children
        mMultiStream.newStream(mIndex);

        RenderStream forkedStream = new RenderStream(mMultiStream, getRootOutputStream(), forkedIndex,
                                                     mLock, mControl);
        unlockChange();
        return forkedStream;
    }

    public RenderStream merge() throws IOException {
        lockChange();
        if (mIndex != null && mMultiStream.isClosed(mIndex)) {
            RenderIndex index = mIndex.clone();
            RenderIndex previous = index.previous();
            RenderIndex toMerge = index;
            while (!index.isRoot()) {
                if (mMultiStream.isWaitingOrder(
                        previous) && index.isLeft()) {
                    //Close the parent if was waiting for his left children
                    mMultiStream.close(previous);
                }
                if (mMultiStream.isClosed(previous)) {
                    if (toMerge.isRight()) {
                        //only merge if toMerge is a right children, if is the left, is already using the same outputstream
                        mergeStreams(previous, toMerge);
                    }
                    mMultiStream.merged(toMerge);
                    toMerge = previous;
                    // Check if the right child is closed and merge from there
                    if (mMultiStream.isClosed(previous.right())) {
                        mergeStreams(previous, previous.right());
                        mMultiStream.merged(previous.right());
                    }
                } else if (mMultiStream.isOpen(previous) || (mMultiStream.isWaitingOrder(
                        previous) && !index.isLeft())) {
                    unlockChange();
                    return this;
                }
                index = previous;
                previous = index.previous();
            }
            getRootOutputStream().write(mMultiStream.get(toMerge).toByteArray());
            mMultiStream.merged(toMerge);
        }
        unlockChange();
        return this;
    }

    private void mergeStreams(RenderIndex destinationIndex, RenderIndex originIndex) throws IOException {
        SingleOuputStream singleOuputStream = mMultiStream.get(destinationIndex);
        mMultiStream.get(originIndex).writeTo(singleOuputStream.getStream());
    }

    private OutputStream getRootOutputStream() {
        return mRootOutputStream;
    }

    private void lockWrite() {
        this.mLock.readLock().lock();
    }

    private void unlockWrite() {
        this.mLock.readLock().unlock();
    }

    private void lockChange() {
        this.mLock.writeLock().lock();
    }

    private void unlockChange() {
        this.mLock.writeLock().unlock();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy