/** * 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; import static org.junit.Assert.fail; import java.io.*; import org.junit.*; /** * 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 baseClassName, boolean scheduler) throws Exception { File f = MaybeNo2Dot0Since.findSource(baseClassName); if (f == null) { return; } StringBuilder b = readFile(f); StringBuilder e = new StringBuilder(); if (scheduler) { scanFor(b, "@SchedulerSupport", "Scheduler:", e, baseClassName); } else { scanFor(b, "@BackpressureSupport", "Backpressure:", e, baseClassName); } 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) { 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(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) { 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(baseClassName) .append(" (").append(baseClassName).append(".java:") .append(lc).append(")").append("\r\n\r\n"); } } } } } } } index = idx + annotation.length(); } } static void checkSchedulerBadMethod(String baseClassName) throws Exception { File f = MaybeNo2Dot0Since.findSource(baseClassName); if (f == null) { return; } StringBuilder b = readFile(f); StringBuilder e = new StringBuilder(); scanForBadMethod(b, "@SchedulerSupport", "Scheduler:", e, baseClassName); 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.class.getSimpleName(), false); } @Test public void checkFlowableScheduler() throws Exception { checkSource(Flowable.class.getSimpleName(), true); } @Test public void checkObservableBackpressure() throws Exception { checkSource(Observable.class.getSimpleName(), false); } @Test public void checkObservableScheduler() throws Exception { checkSource(Observable.class.getSimpleName(), true); } @Test public void checkSingleBackpressure() throws Exception { checkSource(Single.class.getSimpleName(), false); } @Test public void checkSingleScheduler() throws Exception { checkSource(Single.class.getSimpleName(), true); } @Test public void checkCompletableBackpressure() throws Exception { checkSource(Completable.class.getSimpleName(), false); } @Test public void checkCompletableScheduler() throws Exception { checkSource(Completable.class.getSimpleName(), true); } @Test public void checkMaybeBackpressure() throws Exception { checkSource(Maybe.class.getSimpleName(), false); } @Test public void checkMaybeScheduler() throws Exception { checkSource(Maybe.class.getSimpleName(), true); } @Test public void checkFlowableSchedulerDoc() throws Exception { checkSchedulerBadMethod(Flowable.class.getSimpleName()); } @Test public void checkObservableSchedulerDoc() throws Exception { checkSchedulerBadMethod(Observable.class.getSimpleName()); } @Test public void checkSingleSchedulerDoc() throws Exception { checkSchedulerBadMethod(Single.class.getSimpleName()); } @Test public void checkCompletableSchedulerDoc() throws Exception { checkSchedulerBadMethod(Completable.class.getSimpleName()); } @Test public void checkMaybeSchedulerDoc() throws Exception { checkSchedulerBadMethod(Maybe.class.getSimpleName()); } }