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

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

/**
 * 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.lyncode.jtwig.render.stream;

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

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;

public class RenderStream {

    private static ExecutorService sExecutor = null;

    private static void initExecutorService(RenderThreadingConfig renderConfiguration) {
        if (sExecutor == null) {
            sExecutor = new ThreadPoolExecutor(renderConfiguration.minThreads(), renderConfiguration.maxThreads(),
                                               renderConfiguration.keepAliveTime(), TimeUnit.SECONDS,
                                               new SynchronousQueue());
        }
    }

    private final OutputStream mRootOutputStream;
    private final MultiOuputStream mMultiStream;
    private final RenderControl mControl;
    private final RenderThreadingConfig mRenderConfiguration;
    private RenderIndex mIndex;

    private RenderStream(MultiOuputStream multiStream, OutputStream stream, RenderIndex dIndex,
                         RenderControl renderControl, RenderThreadingConfig renderConfiguration) {
        mMultiStream = multiStream;
        mRootOutputStream = stream;
        mIndex = dIndex;
        mControl = renderControl;
        mRenderConfiguration = renderConfiguration;
        initExecutorService(mRenderConfiguration);

        if (mIndex != null) {
            SingleOuputStream.Builder builder = SingleOuputStream.builder().withInheritedStream(true);
            if (mIndex.isMostLeft()) {
                builder.withByteStream(false);
                builder.withStream(stream);
            } else if (mIndex.isLeft()) {
                builder.withByteStream(true);
                builder.withStream(multiStream.get(mIndex.previous()).getStream());
            }
            multiStream.addStream(mIndex, builder.build());
        }
    }

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

    public RenderStream renderConcurrent(final Renderable content, final RenderContext context) {
        try {
            mControl.push();
            sExecutor.execute(new RenderTask(content, context));
        } catch (OutOfMemoryError e) {
            sExecutor.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 {
        mControl.lockWrite();
        getOuputStream().write(bytes);
        mControl.unlockWrite();
        return this;
    }

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

    public RenderStream fork() throws IOException {
        mControl.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.addStream(mIndex);

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

    public RenderStream merge() throws IOException {
        mControl.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())) {
                    mControl.unlockChange();
                    return this;
                }
                index = previous;
                previous = index.previous();
            }
            getRootOutputStream().write(mMultiStream.get(toMerge).toByteArray());
            mMultiStream.merged(toMerge);
        }
        mControl.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;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy