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 java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.Pipe; import java.nio.charset.Charset; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import net.sf.expectit.echo.EchoOutput; import net.sf.expectit.filter.Filter; import net.sf.expectit.filter.Filters; /** * A class used to construct {@link Expect} instances. */ public class ExpectBuilder { /** * The default timeout value of 30000 milliseconds. */ public static final int DEFAULT_TIMEOUT_MS = 30000; /** * The default buffer size in bytes. */ public static final int DEFAULT_BUFFER_SIZE = 1024; private InputStream[] inputs; private Filter filter; private OutputStream output; private long timeout = DEFAULT_TIMEOUT_MS; private EchoOutput echoOutputOld; private Charset charset = Charset.defaultCharset(); @Deprecated private boolean errorOnTimeout; private String lineSeparator = "\n"; private Appendable echoOutput; private Appendable echoInput; private Appendable[] echoInputs; private int bufferSize = DEFAULT_BUFFER_SIZE; private boolean exceptionOnFailure; private boolean autoFlushEcho; private boolean combineInputs; private ExecutorService executor; /** * Default constructor. */ public ExpectBuilder() { } /** * Sets the output stream where {@link Expect} sends command to. Optional, * if not set then all the * send method are expected to throw {@link NullPointerException}. * * @param output the output stream * @return this */ public final ExpectBuilder withOutput(OutputStream output) { this.output = output; return this; } /** * Sets the input streams for expect operations. Required. * * @param inputs the input stream * @return this */ public final ExpectBuilder withInputs(InputStream... inputs) { this.inputs = inputs; return this; } /** * Sets the default timeout in the given unit for expect operations. Optional, * the default value is 30 seconds. * * @param duration the timeout value * @param unit the time unit * @return this * @throws java.lang.IllegalArgumentException if the timeout {@code <= 0} */ public final ExpectBuilder withTimeout(long duration, TimeUnit unit) { validateDuration(duration); this.timeout = unit.toMillis(duration); return this; } static void validateDuration(long duration) { if (duration <= 0) { throw new IllegalArgumentException("Duration <= 0"); } } /** * Sets the default timeout to infinity. * * @return this */ public final ExpectBuilder withInfiniteTimeout() { this.timeout = -1; return this; } /** * Sets the echo output to print all the sent and received data. Useful for debugging to * monitor I/O activity. * Optional, by default is unset. * * @param echoOutput an instance of echo output. Refer to {@link net.sf.expectit.echo * .EchoAdapters} for adapter * static methods for common use cases. * @return this */ @Deprecated public final ExpectBuilder withEchoOutput(EchoOutput echoOutput) { this.echoOutputOld = echoOutput; return this; } /** * Enables printing of all the sent data. Useful for debugging to monitor I/O activity. * Optional, by default is unset. * * @param echoOutput where to echo the sent data. * @return this */ public final ExpectBuilder withEchoOutput(Appendable echoOutput) { this.echoOutput = echoOutput; return this; } /** * Enables printing of all the received data. Useful for debugging to monitor I/O activity. * Optional, by default is unset. * <p/> * If the only {@code firstInput} is specified then the data received from all the inputs is * echoed to it. * <p/> * The build method throws an {@link java.lang.IllegalArgumentException} if the number of the * {@code otherInputs} * parameters does not correspond to the number of the input streams. That is, * if {@code otherInputs} * is specified, the number of them must be equal to the number of inputs minus 1. * <p/> * The byte data received from the input streams is converted to strings using the * builder's charset. See {@link ExpectBuilder#withCharset(java.nio.charset.Charset)} for * more information. * * @param firstInput where to echo the received data for the first input. If {@code * otherInputs} are empty, then * all the data will be echoed to it. * @param otherInputs where to echo the received data for other inputs. * @return this */ public final ExpectBuilder withEchoInput(Appendable firstInput, Appendable... otherInputs) { this.echoInput = firstInput; this.echoInputs = otherInputs; return this; } /** * Sets the character encoding used to covert bytes when working with byte streams. Optional, * the default is * {@link Charset#defaultCharset}. * * @param charset the charset * @return this */ public final ExpectBuilder withCharset(Charset charset) { this.charset = charset; return this; } /** * Sets a filter for the input. Optional, by default no filter is applied. * <p/> * Filters can be used to modify the input before performing expect operations. For example, * to remove * non-printable characters. Filters can be switched on and off while working with the expect * instance. * * @param filter the filter * @param moreFilters more filter to apply. if specified then all the filters are combined * using the * {@link Filters#chain(Filter...)} method.s * @return this */ public final ExpectBuilder withInputFilters(Filter filter, Filter... moreFilters) { if (moreFilters.length == 0) { this.filter = filter; } else { Filter[] filters = new Filter[moreFilters.length + 1]; filters[0] = filter; System.arraycopy(moreFilters, 0, filters, 1, moreFilters.length); this.filter = Filters.chain(filters); } return this; } /** * Enables throwing an {@link java.lang.AssertionError} if any expect operation times out. * Optional, disabled by default. * * @param errorOnTimeout the error flag * @return this * @deprecated This method is deprecated and will be removed. Use * {@link #withExceptionOnFailure()} instead. */ @Deprecated public final ExpectBuilder withErrorOnTimeout(boolean errorOnTimeout) { this.errorOnTimeout = errorOnTimeout; return this; } /** * Enables throwing an {@link ExpectIOException} if an expect operation * was not successful. Optional, disabled by default. * * @return this */ public final ExpectBuilder withExceptionOnFailure() { this.exceptionOnFailure = true; return this; } /** * Sets the line separator used by the {@link Expect#sendLine()} method. Optional, default * is '\n'. * <p> * The default line separator was intentionally made non-system specific to avoid issues like * when the tool is running on Windows and sending {@code CRLF} to a Unix system which threats * them as two new lines. * </p> * * @param lineSeparator the line separator * @return this */ public final ExpectBuilder withLineSeparator(String lineSeparator) { this.lineSeparator = lineSeparator; return this; } /** * Sets the size of the input buffer in bytes. Optional, default is 1024. * * @param bufferSize the buffer size * @return this * @throws java.lang.IllegalArgumentException if {@code bufferSize} is <= 0 */ public final ExpectBuilder withBufferSize(int bufferSize) { if (bufferSize <= 0) { throw new IllegalArgumentException("bufferSize must be > 0"); } this.bufferSize = bufferSize; return this; } /** * Enables automatic flushing of the echo input and output after each match operation * if the given echo object(s) implements {@link java.io.Flushable} interface. * * @param autoFlushEcho the auto flushing flag. * @return this */ public final ExpectBuilder withAutoFlushEcho(boolean autoFlushEcho) { this.autoFlushEcho = autoFlushEcho; return this; } /** * Combines input streaming data into one input. Data from all the inputs can be expected in * the first input. Useful if you want to perform match operation on data that is coming from * two input streams. * * @param combineInputs the combine inputs flag. * @return this. */ public final ExpectBuilder withCombineInputs(boolean combineInputs) { this.combineInputs = combineInputs; return this; } /** * Set the executor used to create internal thread. If not specified, an executor with the * number of threads corresponding to the number of inputs will be created internally.<br/> * <b>IMPORTANT!</b> Make sure that the given executor has the number of threads greater or * equal than the number of input streams. <br/> * If the executor is passed form outside it won't be shutdown when the Expect instance is * closed. * * @param executor the executor service to use. * @return this. */ public final ExpectBuilder withExecutor(ExecutorService executor) { this.executor = executor; return this; } /** * Creates a ready to use {@link Expect} instance. * <p/> * This method creates an instance and starts background threads that receive input data * through NIO pipes. The * created instance must be disposed after use by calling the {@link net.sf.expectit * .Expect#close()} method, * <p/> * The instance is not thread safe and intended to be used in a single thread. * * @return the instance * @throws java.io.IOException if I/O error occurs * @throws java.lang.IllegalStateException if the {@code inputs} are incorrect */ public final Expect build() throws IOException { if (inputs == null || inputs.length == 0) { throw new IllegalStateException("Inputs are null or empty"); } if (echoInputs != null && echoInputs.length != 0 && echoInputs.length != inputs.length - 1) { throw new IllegalArgumentException( "The number of echo input does not correspond to the total " + "number of the input streams"); } SingleInputExpect[] inputs = new SingleInputExpect[this.inputs.length]; Pipe pipe = null; Pipe.SourceChannel source = null; Pipe.SinkChannel sink = null; for (int i = 0; i < inputs.length; i++) { if (!combineInputs || pipe == null) { pipe = Pipe.open(); source = pipe.source(); sink = pipe.sink(); } inputs[i] = new SingleInputExpect( source, sink, this.inputs[i], charset, getEchoInputForIndex(i), filter, bufferSize, autoFlushEcho); } if (echoOutputOld != null) { echoOutput = new AppendableAdapter() { @Override public Appendable append(CharSequence csq) throws IOException { echoOutputOld.onSend(csq.toString()); return this; } }; } ExpectImpl instance = new ExpectImpl( timeout, output, inputs, charset, echoOutput, errorOnTimeout, lineSeparator, exceptionOnFailure, autoFlushEcho, executor); instance.start(); return instance; } private Appendable getEchoInputForIndex(final int i) { if (echoOutputOld != null) { return new AppendableAdapter() { @Override public Appendable append(CharSequence csq) throws IOException { echoOutputOld.onReceive(i, csq.toString()); return this; } }; } if (echoInput == null) { return null; } else if (echoInputs.length == 0 || i == 0) { return echoInput; } else { return echoInputs[i - 1]; } } }