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

net.sf.expectit.SingleInputExpect Maven / Gradle / Ivy

package net.sf.expectit;

/*
 * #%L
 * ExpectIt
 * %%
 * Copyright (C) 2014 Alexey Gavrilov and contributors
 * %%
 * 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.
 * #L%
 */

import static net.sf.expectit.Utils.toDebugString;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.expectit.filter.Filter;
import net.sf.expectit.matcher.Matcher;

/**
 * Represents a single inputs.
 */
class SingleInputExpect {
    private static final Logger LOG = Logger.getLogger(SingleInputExpect.class.getName());

    public static final int BUFFER_SIZE = 1024;

    private final InputStream input;
    private final StringBuilder buffer;
    private final Charset charset;
    private final Appendable echoInput;
    private final Filter filter;
    private Future copierFuture;
    private final Pipe.SourceChannel source;
    private final Pipe.SinkChannel sink;
    private final int bufferSize;
    private final boolean autoFlushEcho;

    protected SingleInputExpect(
            final Pipe.SourceChannel source,
            final Pipe.SinkChannel sink,
            final InputStream input,
            final Charset charset,
            final Appendable echoInput,
            final Filter filter,
            final int bufferSize,
            final boolean autoFlushEcho) throws IOException {
        this.input = input;
        this.charset = charset;
        this.echoInput = echoInput;
        this.filter = filter;
        this.bufferSize = bufferSize;
        this.autoFlushEcho = autoFlushEcho;
        this.source = source;
        this.sink = sink;
        source.configureBlocking(false);
        buffer = new StringBuilder();
    }

    public void start(ExecutorService executor) {
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine(
                    String.format(
                            "Starting expect thread: input=%s, charset=%s, echoInput=%s, "
                                    + "filter=%s, bufferSize=%d",
                            input,
                            charset,
                            echoInput,
                            filter,
                            bufferSize
                    )
            );
        }
        copierFuture = executor.submit(
                new InputStreamCopier(
                        sink,
                        input,
                        bufferSize,
                        echoInput,
                        charset,
                        autoFlushEcho));
    }

    public  R expect(long timeoutMs, Matcher matcher) throws IOException {
        if (copierFuture == null) {
            throw new IllegalStateException("Not started");
        }

        final long timeToStop = System.currentTimeMillis() + timeoutMs;
        final boolean isInfiniteTimeout = timeoutMs == ExpectImpl.INFINITE_TIMEOUT;
        long timeElapsed = timeoutMs;
        ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
        Selector selector = Selector.open();

        try {
            source.register(selector, SelectionKey.OP_READ);
            R result = matcher.matches(buffer.toString(), copierFuture.isDone());
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine(
                        String.format(
                                "Initial matcher %s result: %s",
                                toDebugString(matcher),
                                toDebugString(result)));
            }
            while (!(result.isSuccessful() || result.canStopMatching())
                    && (isInfiniteTimeout || timeElapsed > 0)) {
                int keys = isInfiniteTimeout ? selector.select() : selector.select(timeElapsed);
                // if thread was interrupted the selector returns immediately
                // and keep the thread status, so we need to check it
                if (Thread.currentThread().isInterrupted()) {
                    LOG.fine("Thread was interrupted");
                    throw new ClosedByInterruptException();
                }

                if (!isInfiniteTimeout) {
                    timeElapsed = timeToStop - System.currentTimeMillis();
                }

                if (keys == 0) {
                    LOG.fine("Selector returns 0 key");
                    continue;
                }

                selector.selectedKeys().clear();
                int len = source.read(byteBuffer);

                if (len > 0) {
                    String string = new String(byteBuffer.array(), 0, len, charset);
                    processString(string);
                    byteBuffer.clear();
                }

                result = matcher.matches(buffer.toString(), len == -1);
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine(
                            String.format(
                                    "Matcher %s result: %s. Operation time: %d ms",
                                    toDebugString(matcher),
                                    toDebugString(result),
                                    timeoutMs - timeElapsed));
                }
            }
            if (result.isSuccessful()) {
                buffer.delete(0, result.end());
            } else if (copierFuture.isDone() && buffer.length() == 0) {
                throw new EOFException("Input closed");
            }
            return result;
        } finally {
            selector.close();
        }
    }

    private void processString(String string) throws IOException {
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("Processing string: " + toDebugString(string));
        }
        if (filter != null) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("Before append filter: " + toDebugString(filter));
            }
            string = filter.beforeAppend(string, buffer);
        }

        if (string != null) {
            buffer.append(string);
            if (filter != null) {
                LOG.fine("After append filter: " + toDebugString(filter));
                filter.afterAppend(buffer);
            }
        }
    }

    public void stop() throws IOException {
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("Releasing resources for input: " + this.input);
        }
        if (copierFuture != null) {
            copierFuture.cancel(true);
        }
        sink.close();
        source.close();
        if (autoFlushEcho) {
            Utils.flushAppendable(echoInput);
        }
    }

    StringBuilder getBuffer() {
        return buffer;
    }
}