Skip to content

Commit b6aceec

Browse files
authored
3.x: Verify the use of base interfaces in operator inputs & lambdas (#6858)
* 3.x: Verify the use of base interfaces in operator inputs & lambdas * Add @nonnull annotations too.
1 parent 13ffa18 commit b6aceec

File tree

2 files changed

+191
-2
lines changed

2 files changed

+191
-2
lines changed

src/main/java/io/reactivex/rxjava3/core/Flowable.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -6790,7 +6790,7 @@ public final Flowable<List<T>> buffer(long timespan, @NonNull TimeUnit unit, @No
67906790
@SchedulerSupport(SchedulerSupport.NONE)
67916791
@NonNull
67926792
public final <TOpening, TClosing> Flowable<List<T>> buffer(
6793-
@NonNull Flowable<? extends TOpening> openingIndicator,
6793+
@NonNull Publisher<@NonNull ? extends TOpening> openingIndicator,
67946794
@NonNull Function<? super TOpening, ? extends Publisher<@NonNull ? extends TClosing>> closingIndicator) {
67956795
return buffer(openingIndicator, closingIndicator, ArrayListSupplier.asSupplier());
67966796
}
@@ -6831,7 +6831,7 @@ public final <TOpening, TClosing> Flowable<List<T>> buffer(
68316831
@SchedulerSupport(SchedulerSupport.NONE)
68326832
@NonNull
68336833
public final <TOpening, TClosing, U extends Collection<? super T>> Flowable<U> buffer(
6834-
@NonNull Flowable<? extends TOpening> openingIndicator,
6834+
@NonNull Publisher<@NonNull ? extends TOpening> openingIndicator,
68356835
@NonNull Function<? super TOpening, ? extends Publisher<@NonNull ? extends TClosing>> closingIndicator,
68366836
@NonNull Supplier<U> bufferSupplier) {
68376837
Objects.requireNonNull(openingIndicator, "openingIndicator is null");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/**
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava3.validators;
15+
16+
import static org.junit.Assert.*;
17+
18+
import java.lang.reflect.*;
19+
import java.util.*;
20+
import java.util.Observable;
21+
import java.util.concurrent.Callable;
22+
23+
import org.junit.Test;
24+
import org.reactivestreams.Publisher;
25+
26+
import io.reactivex.rxjava3.core.*;
27+
import io.reactivex.rxjava3.functions.*;
28+
import io.reactivex.rxjava3.parallel.ParallelFlowable;
29+
30+
/**
31+
* Verify that an operator method uses base interfaces as its direct input or
32+
* has lambdas returning base interfaces.
33+
*/
34+
public class OperatorsUseInterfaces {
35+
36+
@Test
37+
public void checkFlowable() {
38+
checkClass(Flowable.class);
39+
}
40+
41+
@Test
42+
public void checkObservable() {
43+
checkClass(Observable.class);
44+
}
45+
46+
@Test
47+
public void checkMaybe() {
48+
checkClass(Maybe.class);
49+
}
50+
51+
@Test
52+
public void checkSingle() {
53+
checkClass(Single.class);
54+
}
55+
56+
@Test
57+
public void checkCompletable() {
58+
checkClass(Completable.class);
59+
}
60+
61+
@Test
62+
public void checkParallelFlowable() {
63+
checkClass(ParallelFlowable.class);
64+
}
65+
66+
void checkClass(Class<?> clazz) {
67+
StringBuilder error = new StringBuilder();
68+
int errors = 0;
69+
70+
for (Method method : clazz.getMethods()) {
71+
if (method.getDeclaringClass() == clazz) {
72+
int pidx = 1;
73+
for (Parameter param : method.getParameters()) {
74+
Class<?> type = param.getType();
75+
if (type.isArray()) {
76+
type = type.getComponentType();
77+
}
78+
if (CLASSES.contains(type)) {
79+
errors++;
80+
error.append("Non-interface input parameter #")
81+
.append(pidx)
82+
.append(": ")
83+
.append(type)
84+
.append("\r\n")
85+
.append(" ")
86+
.append(method)
87+
.append("\r\n")
88+
;
89+
}
90+
if (CAN_RETURN.contains(type)) {
91+
Type gtype = method.getGenericParameterTypes()[pidx - 1];
92+
if (gtype instanceof GenericArrayType) {
93+
gtype = ((GenericArrayType)gtype).getGenericComponentType();
94+
}
95+
ParameterizedType ptype = (ParameterizedType)gtype;
96+
for (;;) {
97+
Type[] parameterArgTypes = ptype.getActualTypeArguments();
98+
Type argType = parameterArgTypes[parameterArgTypes.length - 1];
99+
if (argType instanceof GenericArrayType) {
100+
argType = ((GenericArrayType)argType).getGenericComponentType();
101+
}
102+
if (argType instanceof ParameterizedType) {
103+
ParameterizedType lastArg = (ParameterizedType)argType;
104+
105+
if (CLASSES.contains(lastArg.getRawType())) {
106+
errors++;
107+
error.append("Non-interface lambda return #")
108+
.append(pidx)
109+
.append(": ")
110+
.append(type)
111+
.append("\r\n")
112+
.append(" ")
113+
.append(method)
114+
.append("\r\n")
115+
;
116+
}
117+
118+
if (CAN_RETURN.contains(lastArg.getRawType())) {
119+
ptype = lastArg;
120+
continue;
121+
}
122+
}
123+
break;
124+
}
125+
}
126+
pidx++;
127+
}
128+
}
129+
}
130+
131+
if (errors != 0) {
132+
error.insert(0, "Found " + errors + " issues\r\n");
133+
fail(error.toString());
134+
}
135+
}
136+
137+
public void method1(Flowable<?> f) {
138+
// self-test
139+
}
140+
141+
public void method2(Callable<Flowable<?>> c) {
142+
// self-test
143+
}
144+
145+
public void method3(Supplier<Publisher<Flowable<?>>> c) {
146+
// self-test
147+
}
148+
149+
public void method4(Flowable<?>[] array) {
150+
// self-test
151+
}
152+
153+
public void method5(Callable<Flowable<?>[]> c) {
154+
// self-test
155+
}
156+
157+
public void method6(Callable<Publisher<Flowable<?>[]>> c) {
158+
// self-test
159+
}
160+
161+
@Test
162+
public void checkSelf() {
163+
try {
164+
checkClass(OperatorsUseInterfaces.class);
165+
throw new RuntimeException("Should have failed");
166+
} catch (AssertionError expected) {
167+
assertTrue(expected.toString(), expected.toString().contains("method1"));
168+
assertTrue(expected.toString(), expected.toString().contains("method2"));
169+
assertTrue(expected.toString(), expected.toString().contains("method3"));
170+
assertTrue(expected.toString(), expected.toString().contains("method4"));
171+
assertTrue(expected.toString(), expected.toString().contains("method5"));
172+
assertTrue(expected.toString(), expected.toString().contains("method6"));
173+
}
174+
}
175+
176+
static final Set<Class<?>> CLASSES = new HashSet<>(Arrays.asList(
177+
Flowable.class, Observable.class,
178+
Maybe.class, Single.class,
179+
Completable.class
180+
));
181+
182+
static final Set<Class<?>> CAN_RETURN = new HashSet<>(Arrays.asList(
183+
Callable.class, Supplier.class,
184+
Function.class, BiFunction.class, Function3.class, Function4.class,
185+
Function5.class, Function6.class, Function7.class, Function8.class,
186+
Function9.class,
187+
Publisher.class, ObservableSource.class, MaybeSource.class, SingleSource.class
188+
));
189+
}

0 commit comments

Comments
 (0)