blob: 1d5cf22a5c1aafee465d3e913d03ef61e31bacd8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.flink.api.java.typeutils;
import org.apache.flink.api.common.functions.CoGroupFunction;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.InvalidTypesException;
import org.apache.flink.api.common.functions.JoinFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.MapPartitionFunction;
import org.apache.flink.api.common.functions.Partitioner;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple1;
import org.apache.flink.api.java.tuple.Tuple2;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.apache.flink.api.java.typeutils.TypeExtractionUtils.checkAndExtractLambda;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Tests the type extractor for lambda functions. Many tests only work if the compiler supports
* lambdas properly otherwise a MissingTypeInfo is returned.
*/
public class LambdaExtractionTest {
private static final TypeInformation<Tuple2<Tuple1<Integer>, Boolean>> NESTED_TUPLE_BOOLEAN_TYPE =
new TypeHint<Tuple2<Tuple1<Integer>, Boolean>>(){}.getTypeInfo();
private static final TypeInformation<Tuple2<Tuple1<Integer>, Double>> NESTED_TUPLE_DOUBLE_TYPE =
new TypeHint<Tuple2<Tuple1<Integer>, Double>>(){}.getTypeInfo();
@Test
@SuppressWarnings({"Convert2Lambda", "Anonymous2MethodRef"})
public void testIdentifyLambdas() throws TypeExtractionException {
MapFunction<?, ?> anonymousFromInterface = new MapFunction<String, Integer>() {
@Override
public Integer map(String value) {
return Integer.parseInt(value);
}
};
MapFunction<?, ?> anonymousFromClass = new RichMapFunction<String, Integer>() {
@Override
public Integer map(String value) {
return Integer.parseInt(value);
}
};
MapFunction<?, ?> fromProperClass = new StaticMapper();
MapFunction<?, ?> fromDerived = new ToTuple<Integer>() {
@Override
public Tuple2<Integer, Long> map(Integer value) {
return new Tuple2<>(value, 1L);
}
};
MapFunction<String, Integer> staticLambda = Integer::parseInt;
MapFunction<Integer, String> instanceLambda = Object::toString;
MapFunction<String, Integer> constructorLambda = Integer::new;
assertNull(checkAndExtractLambda(anonymousFromInterface));
assertNull(checkAndExtractLambda(anonymousFromClass));
assertNull(checkAndExtractLambda(fromProperClass));
assertNull(checkAndExtractLambda(fromDerived));
assertNotNull(checkAndExtractLambda(staticLambda));
assertNotNull(checkAndExtractLambda(instanceLambda));
assertNotNull(checkAndExtractLambda(constructorLambda));
assertNotNull(checkAndExtractLambda(STATIC_LAMBDA));
}
private static class StaticMapper implements MapFunction<String, Integer> {
@Override
public Integer map(String value) {
return Integer.parseInt(value);
}
}
private interface ToTuple<T> extends MapFunction<T, Tuple2<T, Long>> {
@Override
Tuple2<T, Long> map(T value) throws Exception;
}
private static final MapFunction<String, Integer> STATIC_LAMBDA = Integer::parseInt;
private static class MyClass {
private String s = "mystring";
public MapFunction<Integer, String> getMapFunction() {
return (i) -> s;
}
}
@Test
public void testLambdaWithMemberVariable() {
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(new MyClass().getMapFunction(), Types.INT);
assertEquals(ti, BasicTypeInfo.STRING_TYPE_INFO);
}
@Test
public void testLambdaWithLocalVariable() {
String s = "mystring";
final int k = 24;
int j = 26;
MapFunction<Integer, String> f = (i) -> s + k + j;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, Types.INT);
assertEquals(ti, BasicTypeInfo.STRING_TYPE_INFO);
}
@Test
public void testLambdaWithNonGenericResultType() {
MapFunction<Tuple2<Tuple1<Integer>, Boolean>, Boolean> f = (i) -> null;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, null, true);
assertTrue(ti instanceof BasicTypeInfo);
assertEquals(BasicTypeInfo.BOOLEAN_TYPE_INFO, ti);
}
@Test
public void testMapLambda() {
MapFunction<Tuple2<Tuple1<Integer>, Boolean>, Tuple2<Tuple1<Integer>, String>> f = (i) -> null;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertTrue(((TupleTypeInfo<?>) ti).getTypeAt(0).isTupleType());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
@Test
public void testFlatMapLambda() {
FlatMapFunction<Tuple2<Tuple1<Integer>, Boolean>, Tuple2<Tuple1<Integer>, String>> f = (i, out) -> out.collect(null);
TypeInformation<?> ti = TypeExtractor.getFlatMapReturnTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertTrue(((TupleTypeInfo<?>) ti).getTypeAt(0).isTupleType());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
@Test
public void testMapPartitionLambda() {
MapPartitionFunction<Tuple2<Tuple1<Integer>, Boolean>, Tuple2<Tuple1<Integer>, String>> f = (i, o) -> {};
TypeInformation<?> ti = TypeExtractor.getMapPartitionReturnTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertTrue(((TupleTypeInfo<?>) ti).getTypeAt(0).isTupleType());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
@Test
public void testJoinLambda() {
JoinFunction<Tuple2<Tuple1<Integer>, Boolean>, Tuple2<Tuple1<Integer>, Double>, Tuple2<Tuple1<Integer>, String>> f = (i1, i2) -> null;
TypeInformation<?> ti = TypeExtractor.getJoinReturnTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, NESTED_TUPLE_DOUBLE_TYPE, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertTrue(((TupleTypeInfo<?>) ti).getTypeAt(0).isTupleType());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
@Test
public void testCoGroupLambda() {
CoGroupFunction<Tuple2<Tuple1<Integer>, Boolean>, Tuple2<Tuple1<Integer>, Double>, Tuple2<Tuple1<Integer>, String>> f = (i1, i2, o) -> {};
TypeInformation<?> ti = TypeExtractor.getCoGroupReturnTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, NESTED_TUPLE_DOUBLE_TYPE, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertTrue(((TupleTypeInfo<?>) ti).getTypeAt(0).isTupleType());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
@Test
public void testKeySelectorLambda() {
KeySelector<Tuple2<Tuple1<Integer>, Boolean>, Tuple2<Tuple1<Integer>, String>> f = (i) -> null;
TypeInformation<?> ti = TypeExtractor.getKeySelectorTypes(f, NESTED_TUPLE_BOOLEAN_TYPE, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertTrue(((TupleTypeInfo<?>) ti).getTypeAt(0).isTupleType());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
@SuppressWarnings("rawtypes")
@Test
public void testLambdaTypeErasure() {
MapFunction<Tuple1<Integer>, Tuple1> f = (i) -> null;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, new TypeHint<Tuple1<Integer>>(){}.getTypeInfo(), null, true);
assertTrue(ti instanceof MissingTypeInfo);
}
@Test
public void testPartitionerLambda() {
Partitioner<Tuple2<Integer, String>> partitioner = (key, numPartitions) -> key.f1.length() % numPartitions;
final TypeInformation<?> ti = TypeExtractor.getPartitionerTypes(partitioner, null, true);
if (!(ti instanceof MissingTypeInfo)) {
assertTrue(ti.isTupleType());
assertEquals(2, ti.getArity());
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(0), BasicTypeInfo.INT_TYPE_INFO);
assertEquals(((TupleTypeInfo<?>) ti).getTypeAt(1), BasicTypeInfo.STRING_TYPE_INFO);
}
}
private static class MyType {
private int key;
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
protected int getKey2() {
return 0;
}
}
@Test
public void testInstanceMethodRefSameType() {
MapFunction<MyType, Integer> f = MyType::getKey;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, TypeExtractor.createTypeInfo(MyType.class));
assertEquals(BasicTypeInfo.INT_TYPE_INFO, ti);
}
@Test
public void testInstanceMethodRefSuperType() {
MapFunction<Integer, String> f = Object::toString;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, BasicTypeInfo.INT_TYPE_INFO);
assertEquals(BasicTypeInfo.STRING_TYPE_INFO, ti);
}
private static class MySubtype extends MyType {
public boolean test;
}
@Test
public void testInstanceMethodRefSuperTypeProtected() {
MapFunction<MySubtype, Integer> f = MyType::getKey2;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, TypeExtractor.createTypeInfo(MySubtype.class));
assertEquals(BasicTypeInfo.INT_TYPE_INFO, ti);
}
@Test
public void testConstructorMethodRef() {
MapFunction<String, Integer> f = Integer::new;
TypeInformation<?> ti = TypeExtractor.getMapReturnTypes(f, BasicTypeInfo.STRING_TYPE_INFO);
assertEquals(BasicTypeInfo.INT_TYPE_INFO, ti);
}
private interface InterfaceWithDefaultMethod {
void samMethod();
default void defaultMethod() {
}
}
@Test
public void testSamMethodExtractionInterfaceWithDefaultMethod() {
final Method sam = TypeExtractionUtils.getSingleAbstractMethod(InterfaceWithDefaultMethod.class);
assertNotNull(sam);
assertEquals("samMethod", sam.getName());
}
private interface InterfaceWithMultipleMethods {
void firstMethod();
void secondMethod();
}
@Test(expected = InvalidTypesException.class)
public void getSingleAbstractMethodMultipleMethods() {
TypeExtractionUtils.getSingleAbstractMethod(InterfaceWithMultipleMethods.class);
}
private interface InterfaceWithoutAbstractMethod {
default void defaultMethod() {
}
}
@Test(expected = InvalidTypesException.class)
public void testSingleAbstractMethodNoAbstractMethods() {
TypeExtractionUtils.getSingleAbstractMethod(InterfaceWithoutAbstractMethod.class);
}
private abstract class AbstractClassWithSingleAbstractMethod {
public abstract void defaultMethod();
}
@Test(expected = InvalidTypesException.class)
public void testSingleAbstractMethodNotAnInterface() {
TypeExtractionUtils.getSingleAbstractMethod(AbstractClassWithSingleAbstractMethod.class);
}
}