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.interop;
  
  import static org.junit.Assert.fail;
  
  import java.io.*;
  
  import org.junit.Test;
  
  import io.reactivex.flowable.Flowable;
  import io.reactivex.observable.*;
  
  /**
   * Checks the source code of the base reactive types and locates missing
   * mention of {@code Backpressure:} and {@code Scheduler:} of methods.
   */
  public class JavadocForAnnotations {
  
      static void checkSource(String project, String baseClassName, boolean scheduler) throws Exception {
          File f = MaybeNo2Dot0Since.findSource(baseClassName, project);
          if (f == null) {
              return;
          }
  
          StringBuilder b = readFile(f);
  
          StringBuilder e = new StringBuilder();
  
          if (scheduler) {
              scanFor(b, "@SchedulerSupport", "Scheduler:", e, baseClassName, project);
          } else {
              scanFor(b, "@BackpressureSupport", "Backpressure:", e, baseClassName, project);
          }
  
          if (e.length() != 0) {
              System.out.println(e);
  
              fail(e.toString());
          }
      }
  
      public static StringBuilder readFile(File f) throws Exception {
          StringBuilder b = new StringBuilder();
  
          BufferedReader in = new BufferedReader(new FileReader(f));
          try {
              for (;;) {
                  String line = in.readLine();
  
                  if (line == null) {
                      break;
                  }
  
                  b.append(line).append('\n');
              }
          } finally {
              in.close();
          }
  
          return b;
      }
  
      static final void scanFor(StringBuilder sourceCode, String annotation, String inDoc,
              StringBuilder e, String baseClassName, String project) {
          int index = 0;
          for (;;) {
              int idx = sourceCode.indexOf(annotation, index);
  
              if (idx < 0) {
                  break;
              }
  
              int j = sourceCode.lastIndexOf("/**", idx);
  
              // see if the last /** is not before the index (last time the annotation was found
              // indicating an uncommented method like subscribe()
              if (j > index) {
                  int k = sourceCode.indexOf(inDoc, j);
  
                  if (k < 0 || k > idx) {
                      // when printed on the console, IDEs will create a clickable link to help navigate to the offending point
                      e.append("java.lang.RuntimeException: missing ").append(inDoc).append(" section\r\n")
                      ;
                      int lc = lineNumber(sourceCode, idx);
  
                      e.append(" at io.reactivex.").append(project).append('.').append(baseClassName)
                      .append(" (").append(baseClassName).append(".java:")
                      .append(lc).append(")").append("\r\n\r\n");
                  }
              }
  
              index = idx + annotation.length();
          }
      }
  
  
      static final void scanForBadMethod(StringBuilder sourceCode, String annotation, String inDoc,
              StringBuilder e, String baseClassName, String project) {
          int index = 0;
          for (;;) {
              int idx = sourceCode.indexOf(annotation, index);
  
              if (idx < 0) {
                  break;
              }
  
              int j = sourceCode.lastIndexOf("/**", idx);
  
              // see if the last /** is not before the index (last time the annotation was found
              // indicating an uncommented method like subscribe()
              if (j > index) {
                  int k = sourceCode.indexOf(inDoc, j);
  
                  if (k >= 0 && k <= idx) {
  
                      int ll = sourceCode.indexOf("You specify", k);
                      int lm = sourceCode.indexOf("This operator", k);
                      if ((ll < 0 || ll > idx) && (lm < 0 || lm > idx)) {
  
                          int n = sourceCode.indexOf("{@code ", k);
  
                          if (n < idx) {
                              int m = sourceCode.indexOf("}", n);
  
                              if (m < idx) {
                                  String mname = sourceCode.substring(n + 7, m);
  
                                  int q = sourceCode.indexOf("@SuppressWarnings({", idx);
  
                                  int o = sourceCode.indexOf("{", idx);
  
                                  if (q + 18 == o) {
                                      o = sourceCode.indexOf("{", q + 20);
                                  }
  
                                  if (o >= 0) {
  
                                      int p = sourceCode.indexOf(" " + mname + "(", idx);
  
                                      if (p < 0 || p > o) {
                                          // when printed on the console, IDEs will create a clickable link to help navigate to the offending point
                                          e.append("java.lang.RuntimeException: wrong method name in description of ").append(inDoc).append(" '").append(mname).append("'\r\n")
                                          ;
                                          int lc = lineNumber(sourceCode, idx);
  
                                          e.append(" at io.reactivex.").append(project).append('.').append(baseClassName)
                                          .append(" (").append(baseClassName).append(".java:")
                                          .append(lc).append(")").append("\r\n\r\n");
                                      }
                                  }
                              }
  
                          }
  
                      }
                  }
              }
  
              index = idx + annotation.length();
          }
      }
  
      static void checkSchedulerBadMethod(String project, String baseClassName) throws Exception {
          File f = MaybeNo2Dot0Since.findSource(baseClassName, project);
          if (f == null) {
              return;
          }
  
          StringBuilder b = readFile(f);
  
          StringBuilder e = new StringBuilder();
  
          scanForBadMethod(b, "@SchedulerSupport", "Scheduler:", e, baseClassName, project);
  
          if (e.length() != 0) {
              System.out.println(e);
  
              fail(e.toString());
          }
      }
  
      public static int lineNumber(StringBuilder s, int index) {
          int cnt = 1;
          for (int i = 0; i < index; i++) {
              if (s.charAt(i) == '\n') {
                  cnt++;
              }
          }
          return cnt;
      }
  
      @Test
      public void checkFlowableBackpressure() throws Exception {
          checkSource("flowable", Flowable.class.getSimpleName(), false);
      }
  
      @Test
      public void checkFlowableScheduler() throws Exception {
          checkSource("flowable", Flowable.class.getSimpleName(), true);
      }
  
      @Test
      public void checkObservableBackpressure() throws Exception {
          checkSource("flowable", Observable.class.getSimpleName(), false);
      }
  
      @Test
      public void checkObservableScheduler() throws Exception {
          checkSource("observable", Observable.class.getSimpleName(), true);
      }
  
      @Test
      public void checkSingleBackpressure() throws Exception {
          checkSource("observable", Single.class.getSimpleName(), false);
      }
  
      @Test
      public void checkSingleScheduler() throws Exception {
          checkSource("observable", Single.class.getSimpleName(), true);
      }
  
      @Test
      public void checkCompletableBackpressure() throws Exception {
          checkSource("observable", Completable.class.getSimpleName(), false);
      }
  
      @Test
      public void checkCompletableScheduler() throws Exception {
          checkSource("observable", Completable.class.getSimpleName(), true);
      }
  
      @Test
      public void checkMaybeBackpressure() throws Exception {
          checkSource("observable", Maybe.class.getSimpleName(), false);
      }
  
      @Test
      public void checkMaybeScheduler() throws Exception {
          checkSource("observable", Maybe.class.getSimpleName(), true);
      }
  
      @Test
      public void checkFlowableSchedulerDoc() throws Exception {
          checkSchedulerBadMethod("observable", Flowable.class.getSimpleName());
      }
  
      @Test
      public void checkObservableSchedulerDoc() throws Exception {
          checkSchedulerBadMethod("observable", Observable.class.getSimpleName());
      }
  
      @Test
      public void checkSingleSchedulerDoc() throws Exception {
          checkSchedulerBadMethod("observable", Single.class.getSimpleName());
      }
  
      @Test
      public void checkCompletableSchedulerDoc() throws Exception {
          checkSchedulerBadMethod("observable", Completable.class.getSimpleName());
      }
  
      @Test
      public void checkMaybeSchedulerDoc() throws Exception {
          checkSchedulerBadMethod("observable", Maybe.class.getSimpleName());
      }
  }
  