0
# API Completeness
1
2
Framework for verifying API parity between Java and Scala implementations using reflection-based method comparison. Essential for ensuring consistent API coverage across language bindings.
3
4
## Core Base Classes
5
6
### ScalaAPICompletenessTestBase
7
8
Abstract base class providing infrastructure for comparing Java and Scala API implementations using reflection.
9
10
```java { .api }
11
public abstract class ScalaAPICompletenessTestBase {
12
protected static final Logger LOG = LoggerFactory.getLogger(ScalaAPICompletenessTestBase.class);
13
14
// Core comparison methods
15
protected void compareApis(Class<?> javaClass, Class<?> scalaClass);
16
protected void compareApis(Class<?> javaClass, Class<?> scalaClass, Set<String> excludedMethods);
17
18
// Method filtering and exclusion
19
protected boolean isExcluded(Method method);
20
protected boolean isExcluded(String methodName);
21
protected Set<String> getExcludedMethods();
22
23
// Comparison utilities
24
protected boolean methodsMatch(Method javaMethod, Method scalaMethod);
25
protected String getMethodSignature(Method method);
26
protected void reportMissingMethods(Set<String> missingMethods, String apiType);
27
}
28
```
29
30
### TupleComparatorTestBase
31
32
Abstract base class for testing tuple comparator implementations between Java and Scala.
33
34
```java { .api }
35
public abstract class TupleComparatorTestBase<T> {
36
protected TypeComparator<T> javaComparator;
37
protected TypeComparator<T> scalaComparator;
38
39
@Before
40
public abstract void setup();
41
42
// Comparison testing methods
43
protected void testComparatorEquality(T value1, T value2);
44
protected void testComparatorSerialization();
45
protected void testComparatorNormalization();
46
47
// Utility methods
48
protected abstract T[] getTestData();
49
protected abstract TypeComparator<T> createJavaComparator();
50
protected abstract TypeComparator<T> createScalaComparator();
51
}
52
```
53
54
### PairComparatorTestBase
55
56
Abstract base class for testing pair comparator implementations.
57
58
```java { .api }
59
public abstract class PairComparatorTestBase<T1, T2> {
60
protected TypePairComparator<T1, T2> javaPairComparator;
61
protected TypePairComparator<T1, T2> scalaPairComparator;
62
63
@Before
64
public abstract void setup();
65
66
// Pair comparison testing
67
protected void testPairComparatorEquality(T1 first1, T2 second1, T1 first2, T2 second2);
68
protected void testPairComparatorSerialization();
69
70
// Abstract methods for test data
71
protected abstract T1[] getFirstTypeTestData();
72
protected abstract T2[] getSecondTypeTestData();
73
protected abstract TypePairComparator<T1, T2> createJavaPairComparator();
74
protected abstract TypePairComparator<T1, T2> createScalaPairComparator();
75
}
76
```
77
78
## API Comparison Utilities
79
80
### MethodSignatureComparator
81
82
Utility for comparing method signatures between Java and Scala implementations.
83
84
```java { .api }
85
public class MethodSignatureComparator {
86
public static boolean signaturesMatch(Method javaMethod, Method scalaMethod);
87
public static String normalizeSignature(Method method);
88
public static boolean returnTypesCompatible(Class<?> javaReturn, Class<?> scalaReturn);
89
public static boolean parametersCompatible(Class<?>[] javaParams, Class<?>[] scalaParams);
90
}
91
```
92
93
### ReflectionTestUtils
94
95
Utilities for reflection-based testing and method analysis.
96
97
```java { .api }
98
public class ReflectionTestUtils {
99
public static Set<Method> getPublicMethods(Class<?> clazz);
100
public static Set<Method> getPublicMethods(Class<?> clazz, Set<String> excludedNames);
101
public static boolean isMethodExcluded(Method method, Set<String> exclusions);
102
public static String getSimpleMethodSignature(Method method);
103
}
104
```
105
106
## Usage Examples
107
108
### Basic API Completeness Test
109
110
```java
111
public class DataSetAPICompletenessTest extends ScalaAPICompletenessTestBase {
112
113
@Test
114
public void testDataSetAPI() {
115
Class<?> javaDataSet = org.apache.flink.api.java.DataSet.class;
116
Class<?> scalaDataSet = org.apache.flink.api.scala.DataSet.class;
117
118
// Compare APIs with standard exclusions
119
compareApis(javaDataSet, scalaDataSet);
120
}
121
122
@Override
123
protected Set<String> getExcludedMethods() {
124
Set<String> excluded = new HashSet<>();
125
126
// Exclude Scala-specific methods
127
excluded.add("$plus$plus"); // Scala ++ operator
128
excluded.add("$colon$colon"); // Scala :: operator
129
130
// Exclude internal methods
131
excluded.add("clean");
132
excluded.add("getExecutionEnvironment");
133
134
return excluded;
135
}
136
137
@Override
138
protected boolean isExcluded(Method method) {
139
// Additional custom exclusion logic
140
String methodName = method.getName();
141
142
// Exclude synthetic methods
143
if (method.isSynthetic()) {
144
return true;
145
}
146
147
// Exclude bridge methods
148
if (method.isBridge()) {
149
return true;
150
}
151
152
// Exclude Scala-specific naming patterns
153
if (methodName.contains("$")) {
154
return true;
155
}
156
157
return super.isExcluded(method);
158
}
159
}
160
```
161
162
### Tuple Comparator Testing
163
164
```java
165
public class Tuple2ComparatorTest extends TupleComparatorTestBase<Tuple2<String, Integer>> {
166
167
@Override
168
public void setup() {
169
javaComparator = createJavaComparator();
170
scalaComparator = createScalaComparator();
171
}
172
173
@Test
174
public void testTuple2Comparison() {
175
Tuple2<String, Integer> tuple1 = new Tuple2<>("hello", 42);
176
Tuple2<String, Integer> tuple2 = new Tuple2<>("world", 24);
177
178
testComparatorEquality(tuple1, tuple2);
179
testComparatorSerialization();
180
testComparatorNormalization();
181
}
182
183
@Override
184
protected Tuple2<String, Integer>[] getTestData() {
185
return new Tuple2[]{
186
new Tuple2<>("a", 1),
187
new Tuple2<>("b", 2),
188
new Tuple2<>("c", 3),
189
new Tuple2<>("a", 1), // Duplicate for equality testing
190
};
191
}
192
193
@Override
194
protected TypeComparator<Tuple2<String, Integer>> createJavaComparator() {
195
return new TupleComparator<>(
196
new int[]{0, 1}, // Key positions
197
new TypeComparator[]{
198
new StringComparator(true),
199
new IntComparator(true)
200
},
201
new TypeSerializer[]{
202
StringSerializer.INSTANCE,
203
IntSerializer.INSTANCE
204
}
205
);
206
}
207
208
@Override
209
protected TypeComparator<Tuple2<String, Integer>> createScalaComparator() {
210
// Create Scala equivalent comparator
211
return new ScalaTupleComparator<>(
212
new int[]{0, 1},
213
new TypeComparator[]{
214
new StringComparator(true),
215
new IntComparator(true)
216
}
217
);
218
}
219
}
220
```
221
222
### Custom API Comparison with Detailed Reporting
223
224
```java
225
public class CustomAPICompletenessTest extends ScalaAPICompletenessTestBase {
226
227
@Test
228
public void testStreamingAPICompleteness() {
229
Class<?> javaDataStream = org.apache.flink.streaming.api.datastream.DataStream.class;
230
Class<?> scalaDataStream = org.apache.flink.streaming.api.scala.DataStream.class;
231
232
Set<String> customExclusions = new HashSet<>();
233
customExclusions.add("javaStream"); // Scala-specific bridge method
234
customExclusions.add("scalaStream"); // Java-specific bridge method
235
236
compareApis(javaDataStream, scalaDataStream, customExclusions);
237
}
238
239
@Override
240
protected void compareApis(Class<?> javaClass, Class<?> scalaClass, Set<String> excludedMethods) {
241
LOG.info("Comparing {} with {}", javaClass.getSimpleName(), scalaClass.getSimpleName());
242
243
Set<Method> javaMethods = getFilteredMethods(javaClass, excludedMethods);
244
Set<Method> scalaMethods = getFilteredMethods(scalaClass, excludedMethods);
245
246
// Find methods in Java but not in Scala
247
Set<String> javaSignatures = javaMethods.stream()
248
.map(this::getMethodSignature)
249
.collect(Collectors.toSet());
250
251
Set<String> scalaSignatures = scalaMethods.stream()
252
.map(this::getMethodSignature)
253
.collect(Collectors.toSet());
254
255
Set<String> missingInScala = new HashSet<>(javaSignatures);
256
missingInScala.removeAll(scalaSignatures);
257
258
Set<String> missingInJava = new HashSet<>(scalaSignatures);
259
missingInJava.removeAll(javaSignatures);
260
261
// Report findings
262
if (!missingInScala.isEmpty()) {
263
reportMissingMethods(missingInScala, "Scala API");
264
}
265
266
if (!missingInJava.isEmpty()) {
267
reportMissingMethods(missingInJava, "Java API");
268
}
269
270
// Assertions
271
assertTrue("Scala API missing methods: " + missingInScala, missingInScala.isEmpty());
272
assertTrue("Java API has extra methods: " + missingInJava, missingInJava.isEmpty());
273
274
LOG.info("API comparison successful: {} methods verified", javaSignatures.size());
275
}
276
277
private Set<Method> getFilteredMethods(Class<?> clazz, Set<String> excludedMethods) {
278
return ReflectionTestUtils.getPublicMethods(clazz, excludedMethods);
279
}
280
}
281
```
282
283
### Comprehensive Operator API Testing
284
285
```java
286
public class OperatorAPICompletenessTest extends ScalaAPICompletenessTestBase {
287
288
@Test
289
public void testAllOperatorAPIs() {
290
// Test core transformation operators
291
testOperatorAPI("DataSet",
292
org.apache.flink.api.java.DataSet.class,
293
org.apache.flink.api.scala.DataSet.class);
294
295
testOperatorAPI("DataStream",
296
org.apache.flink.streaming.api.datastream.DataStream.class,
297
org.apache.flink.streaming.api.scala.DataStream.class);
298
299
testOperatorAPI("KeyedStream",
300
org.apache.flink.streaming.api.datastream.KeyedStream.class,
301
org.apache.flink.streaming.api.scala.KeyedStream.class);
302
}
303
304
private void testOperatorAPI(String apiName, Class<?> javaClass, Class<?> scalaClass) {
305
LOG.info("Testing {} API completeness", apiName);
306
307
try {
308
compareApis(javaClass, scalaClass);
309
LOG.info("{} API comparison successful", apiName);
310
} catch (AssertionError e) {
311
LOG.error("{} API comparison failed: {}", apiName, e.getMessage());
312
throw new AssertionError(apiName + " API completeness test failed", e);
313
}
314
}
315
316
@Override
317
protected Set<String> getExcludedMethods() {
318
Set<String> excluded = super.getExcludedMethods();
319
320
// Common exclusions across all operator APIs
321
excluded.add("returns"); // Type hint methods
322
excluded.add("getType");
323
excluded.add("getTransformation");
324
325
// Scala collection interop methods
326
excluded.add("asScala");
327
excluded.add("asJava");
328
329
return excluded;
330
}
331
}
332
```
333
334
### Serialization Compatibility Testing
335
336
```java
337
public class SerializationAPICompletenessTest extends ScalaAPICompletenessTestBase {
338
339
@Test
340
public void testSerializerAPIs() {
341
compareSerializerAPIs(
342
"Tuple2Serializer",
343
org.apache.flink.api.common.typeutils.base.TupleSerializer.class,
344
org.apache.flink.api.scala.typeutils.ScalaTupleSerializer.class
345
);
346
}
347
348
private void compareSerializerAPIs(String serializerName, Class<?> javaClass, Class<?> scalaClass) {
349
LOG.info("Comparing {} implementations", serializerName);
350
351
// Get serialization-specific methods
352
Set<Method> javaMethods = Arrays.stream(javaClass.getMethods())
353
.filter(m -> isSerializationMethod(m))
354
.collect(Collectors.toSet());
355
356
Set<Method> scalaMethods = Arrays.stream(scalaClass.getMethods())
357
.filter(m -> isSerializationMethod(m))
358
.collect(Collectors.toSet());
359
360
// Compare method signatures
361
compareMethodSets(javaMethods, scalaMethods, serializerName);
362
}
363
364
private boolean isSerializationMethod(Method method) {
365
String name = method.getName();
366
return name.equals("serialize") ||
367
name.equals("deserialize") ||
368
name.equals("getLength") ||
369
name.equals("copy");
370
}
371
372
private void compareMethodSets(Set<Method> javaMethods, Set<Method> scalaMethods, String context) {
373
Set<String> javaSignatures = javaMethods.stream()
374
.map(this::getMethodSignature)
375
.collect(Collectors.toSet());
376
377
Set<String> scalaSignatures = scalaMethods.stream()
378
.map(this::getMethodSignature)
379
.collect(Collectors.toSet());
380
381
assertEquals(context + " method signatures should match", javaSignatures, scalaSignatures);
382
}
383
}