content: /**
   * Copyright (c) 2016-present, RxJava 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.
   */
  package io.reactivex.common.exceptions;
  
  import java.io.*;
  import java.util.*;
  
  import io.reactivex.common.annotations.NonNull;
  
  /**
   * Represents an exception that is a composite of one or more other exceptions. A {@code CompositeException}
   * does not modify the structure of any exception it wraps, but at print-time it iterates through the list of
   * Throwables contained in the composite in order to print them all.
   *
   * Its invariant is to contain an immutable, ordered (by insertion order), unique list of non-composite
   * exceptions. You can retrieve individual exceptions in this list with {@link #getExceptions()}.
   *
   * The {@link #printStackTrace()} implementation handles the StackTrace in a customized way instead of using
   * {@code getCause()} so that it can avoid circular references.
   *
   * If you invoke {@link #getCause()}, it will lazily create the causal chain but will stop if it finds any
   * Throwable in the chain that it has already seen.
   */
  public final class CompositeException extends RuntimeException {
  
      private static final long serialVersionUID = 3026362227162912146L;
  
      private final List<Throwable> exceptions;
      private final String message;
      private Throwable cause;
  
      /**
       * Constructs a CompositeException with the given array of Throwables as the
       * list of suppressed exceptions.
       * @param exceptions the Throwables to have as initially suppressed exceptions
       *
       * @throws IllegalArgumentException if <code>exceptions</code> is empty.
       */
      public CompositeException(@NonNull Throwable... exceptions) {
          this(exceptions == null ?
                  Collections.singletonList(new NullPointerException("exceptions was null")) : Arrays.asList(exceptions));
      }
  
      /**
       * Constructs a CompositeException with the given array of Throwables as the
       * list of suppressed exceptions.
       * @param errors the Throwables to have as initially suppressed exceptions
       *
       * @throws IllegalArgumentException if <code>errors</code> is empty.
       */
      public CompositeException(@NonNull Iterable<? extends Throwable> errors) {
          Set<Throwable> deDupedExceptions = new LinkedHashSet<Throwable>();
          List<Throwable> localExceptions = new ArrayList<Throwable>();
          if (errors != null) {
              for (Throwable ex : errors) {
                  if (ex instanceof CompositeException) {
                      deDupedExceptions.addAll(((CompositeException) ex).getExceptions());
                  } else
                  if (ex != null) {
                      deDupedExceptions.add(ex);
                  } else {
                      deDupedExceptions.add(new NullPointerException("Throwable was null!"));
                  }
              }
          } else {
              deDupedExceptions.add(new NullPointerException("errors was null"));
          }
          if (deDupedExceptions.isEmpty()) {
              throw new IllegalArgumentException("errors is empty");
          }
          localExceptions.addAll(deDupedExceptions);
          this.exceptions = Collections.unmodifiableList(localExceptions);
          this.message = exceptions.size() + " exceptions occurred. ";
      }
  
      /**
       * Retrieves the list of exceptions that make up the {@code CompositeException}.
       *
       * @return the exceptions that make up the {@code CompositeException}, as a {@link List} of {@link Throwable}s
       */
      @NonNull
      public List<Throwable> getExceptions() {
          return exceptions;
      }
  
      @Override
      @NonNull
      public String getMessage() {
          return message;
      }
  
      @Override
      @NonNull
      public synchronized Throwable getCause() { // NOPMD
          if (cause == null) {
              // we lazily generate this causal chain if this is called
              CompositeExceptionCausalChain localCause = new CompositeExceptionCausalChain();
              Set<Throwable> seenCauses = new HashSet<Throwable>();
  
              Throwable chain = localCause;
              for (Throwable e : exceptions) {
                  if (seenCauses.contains(e)) {
                      // already seen this outer Throwable so skip
                      continue;
                  }
                  seenCauses.add(e);
  
                  List<Throwable> listOfCauses = getListOfCauses(e);
                  // check if any of them have been seen before
                  for (Throwable child : listOfCauses) {
                      if (seenCauses.contains(child)) {
                          // already seen this outer Throwable so skip
                          e = new RuntimeException("Duplicate found in causal chain so cropping to prevent loop ...");
                          continue;
                      }
                      seenCauses.add(child);
                  }
  
                  // we now have 'e' as the last in the chain
                  try {
                      chain.initCause(e);
                  } catch (Throwable t) { // NOPMD
                      // ignore
                      // the JavaDocs say that some Throwables (depending on how they're made) will never
                      // let me call initCause without blowing up even if it returns null
                  }
                  chain = getRootCause(chain);
              }
              cause = localCause;
          }
          return cause;
      }
  
      /**
       * All of the following {@code printStackTrace} functionality is derived from JDK {@link Throwable}
       * {@code printStackTrace}. In particular, the {@code PrintStreamOrWriter} abstraction is copied wholesale.
       *
       * Changes from the official JDK implementation:<ul>
       * <li>no infinite loop detection</li>
       * <li>smaller critical section holding {@link PrintStream} lock</li>
       * <li>explicit knowledge about the exceptions {@link List} that this loops through</li>
       * </ul>
       */
      @Override
      public void printStackTrace() {
          printStackTrace(System.err);
      }
  
      @Override
      public void printStackTrace(PrintStream s) {
          printStackTrace(new WrappedPrintStream(s));
      }
  
      @Override
      public void printStackTrace(PrintWriter s) {
          printStackTrace(new WrappedPrintWriter(s));
      }
  
      /**
       * Special handling for printing out a {@code CompositeException}.
       * Loops through all inner exceptions and prints them out.
       *
       * @param s
       *            stream to print to
       */
      private void printStackTrace(PrintStreamOrWriter s) {
          StringBuilder b = new StringBuilder(128);
          b.append(this).append('\n');
          for (StackTraceElement myStackElement : getStackTrace()) {
              b.append("\tat ").append(myStackElement).append('\n');
          }
          int i = 1;
          for (Throwable ex : exceptions) {
              b.append("  ComposedException ").append(i).append(" :\n");
              appendStackTrace(b, ex, "\t");
              i++;
          }
          s.println(b.toString());
      }
  
      private void appendStackTrace(StringBuilder b, Throwable ex, String prefix) {
          b.append(prefix).append(ex).append('\n');
          for (StackTraceElement stackElement : ex.getStackTrace()) {
              b.append("\t\tat ").append(stackElement).append('\n');
          }
          if (ex.getCause() != null) {
              b.append("\tCaused by: ");
              appendStackTrace(b, ex.getCause(), "");
          }
      }
  
      abstract static class PrintStreamOrWriter {
          /** Prints the specified string as a line on this StreamOrWriter. */
          abstract void println(Object o);
      }
  
      /**
       * Same abstraction and implementation as in JDK to allow PrintStream and PrintWriter to share implementation.
       */
      static final class WrappedPrintStream extends PrintStreamOrWriter {
          private final PrintStream printStream;
  
          WrappedPrintStream(PrintStream printStream) {
              this.printStream = printStream;
          }
  
          @Override
          void println(Object o) {
              printStream.println(o);
          }
      }
  
      static final class WrappedPrintWriter extends PrintStreamOrWriter {
          private final PrintWriter printWriter;
  
          WrappedPrintWriter(PrintWriter printWriter) {
              this.printWriter = printWriter;
          }
  
          @Override
          void println(Object o) {
              printWriter.println(o);
          }
      }
  
      static final class CompositeExceptionCausalChain extends RuntimeException {
          private static final long serialVersionUID = 3875212506787802066L;
          /* package-private */static final String MESSAGE = "Chain of Causes for CompositeException In Order Received =>";
  
          @Override
          public String getMessage() {
              return MESSAGE;
          }
      }
  
      private List<Throwable> getListOfCauses(Throwable ex) {
          List<Throwable> list = new ArrayList<Throwable>();
          Throwable root = ex.getCause();
          if (root == null || root == ex) {
              return list;
          } else {
              while (true) {
                  list.add(root);
                  Throwable cause = root.getCause();
                  if (cause == null || cause == root) {
                      return list;
                  } else {
                      root = cause;
                  }
              }
          }
      }
  
      /**
       * Returns the number of suppressed exceptions.
       * @return the number of suppressed exceptions
       */
      public int size() {
          return exceptions.size();
      }
  
      /**
       * Returns the root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself.
       *
       * @param e the {@link Throwable} {@code e}.
       * @return The root cause of {@code e}. If {@code e.getCause()} returns {@code null} or {@code e}, just return {@code e} itself.
       */
      private Throwable getRootCause(Throwable e) {
          Throwable root = e.getCause();
          if (root == null || cause == root) {
              return e;
          }
          while (true) {
              Throwable cause = root.getCause();
              if (cause == null || cause == root) {
                  return root;
              }
              root = cause;
          }
      }
  }
  