content: 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.ExpectBuilder.validateDuration;
  import static net.sf.expectit.Utils.toDebugString;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.nio.charset.Charset;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.TimeUnit;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import net.sf.expectit.interact.InteractBuilder;
  import net.sf.expectit.matcher.Matcher;
  
  /**
   * An implementation of the Expect interface which delegates actual work to SingleInputExpect
   * objects.
   */
  class ExpectImpl extends AbstractExpectImpl {
      private static final Logger LOG = Logger.getLogger(ExpectImpl.class.getName());
  
      static final int INFINITE_TIMEOUT = -1; // value representing infinite timeout
  
      private final OutputStream output;
      private final SingleInputExpect[] inputs;
      private final Charset charset;
      private final Appendable echoOutput;
      @Deprecated
      private final boolean errorOnTimeout;
      private final ExecutorService executor;
      private final String lineSeparator;
      private final boolean exceptionOnFailure;
      private final boolean autoFlushEcho;
      private final boolean useInternalExecutor;
  
      ExpectImpl(
              final long timeout,
              final OutputStream output,
              final SingleInputExpect[] inputs,
              final Charset charset,
              final Appendable echoOutput,
              final boolean errorOnTimeoutOld,
              final String lineSeparator,
              final boolean exceptionOnFailure,
              final boolean autoFlushEcho,
              final ExecutorService executor) {
          super(timeout);
          this.output = output;
          this.inputs = inputs;
          this.charset = charset;
          this.echoOutput = echoOutput;
          this.errorOnTimeout = errorOnTimeoutOld;
          this.exceptionOnFailure = exceptionOnFailure;
          this.lineSeparator = lineSeparator;
          this.autoFlushEcho = autoFlushEcho;
          this.executor = executor == null
                  ? Executors.newFixedThreadPool(
                          inputs.length,
                          new NamedExecutorThreadFactory("expect-"))
                  : executor;
          this.useInternalExecutor = executor == null;
      }
  
      void start() {
          for (SingleInputExpect input : inputs) {
              input.start(executor);
          }
      }
  
      @Override
      public <R extends Result> R expectIn(int input, long timeoutMs, Matcher<R> matcher)
              throws IOException {
          if (LOG.isLoggable(Level.FINE)) {
              LOG.fine(
                      String.format(
                              "Expect matcher '%s' with timeout %d (ms) in input #%d",
                              toDebugString(matcher),
                              timeoutMs,
                              input));
          }
          R result = inputs[input].expect(timeoutMs, matcher);
          if (exceptionOnFailure && !result.isSuccessful()) {
              final String inputBuffer = inputs[input].getBuffer().toString();
              throw new ExpectIOException(
                      "Expect operation fails (timeout: "
                              + timeoutMs + " ms) for matcher: " + matcher, inputBuffer);
          }
          if (errorOnTimeout && !result.isSuccessful()) {
              throw new AssertionError(
                      "Expect timeout (" + timeoutMs + " ms) for matcher: " + matcher);
          }
          return result;
      }
  
      @Override
      public Expect withTimeout(long duration, TimeUnit unit) {
          validateDuration(duration);
          return new ExpectTimeoutAdapter(this, unit.toMillis(duration));
      }
  
      @Deprecated
      @Override
      public Expect withInfiniteTimeout() {
          return new ExpectTimeoutAdapter(this, -1);
      }
  
      @Override
      public Expect send(String string) throws IOException {
          byte[] bytes = string.getBytes(charset);
          writeBytes(bytes);
          echoString(string);
          return this;
      }
  
      @Override
      public Expect sendLine() throws IOException {
          return sendLine("");
      }
  
      @Override
      public Expect sendLine(String string) throws IOException {
          return send(string + lineSeparator);
      }
  
      @Override
      public Expect sendBytes(byte[] bytes) throws IOException {
          writeBytes(bytes);
          echoString(new String(bytes, charset));
          return this;
      }
  
      private void writeBytes(byte[] bytes) throws IOException {
          if (LOG.isLoggable(Level.FINE)) {
              LOG.fine("Writing bytes: " + toDebugString(bytes, bytes.length, charset));
          }
          output.write(bytes);
          output.flush();
      }
  
      private void echoString(String string) throws IOException {
          if (echoOutput != null) {
              echoOutput.append(string);
              if (autoFlushEcho) {
                  Utils.flushAppendable(echoOutput);
              }
          }
      }
  
      @Override
      public void close() throws IOException {
          for (SingleInputExpect input : inputs) {
              input.stop();
          }
  
          if (useInternalExecutor) {
              executor.shutdown();
          }
          if (autoFlushEcho) {
              Utils.flushAppendable(echoOutput);
          }
      }
  
      @Override
      public InteractBuilder interact() {
          return interactWith(0);
      }
  
      @Override
      public InteractBuilder interactWith(final int input) {
          return interactWithInternal(this, input);
      }
  
      InteractBuilder interactWithInternal(final AbstractExpectImpl expect, final int input) {
          if (input >= inputs.length || input < 0) {
              throw new IllegalArgumentException("Input index is out of bounds: " + input);
          }
          return new InteractBuilderImpl(expect, input);
      }
  
      @Override
      SingleInputExpect[] getInputs() { return inputs; }
  }
  