/*
 * 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.common.operators;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.apache.commons.lang3.ArrayUtils;

import org.apache.flink.api.common.InvalidProgramException;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.common.operators.Keys.ExpressionKeys;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.api.java.tuple.Tuple7;
import org.apache.flink.api.java.typeutils.PojoTypeExtractionTest.ComplexNestedClass;
import org.apache.flink.api.java.typeutils.GenericTypeInfo;
import org.apache.flink.api.java.typeutils.TupleTypeInfo;
import org.apache.flink.api.java.typeutils.TypeExtractor;
import org.apache.flink.api.common.operators.SelectorFunctionKeysTest.KeySelector1;
import org.apache.flink.api.common.operators.SelectorFunctionKeysTest.KeySelector3;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

@SuppressWarnings("unused")
@RunWith(PowerMockRunner.class)
public class ExpressionKeysTest {

	@Test
	public void testBasicType() {

		TypeInformation<Long> longType = BasicTypeInfo.LONG_TYPE_INFO;
		ExpressionKeys<Long> ek = new ExpressionKeys<>("*", longType);

		Assert.assertArrayEquals(new int[] {0}, ek.computeLogicalKeyPositions());
	}

	@Test(expected = InvalidProgramException.class)
	public void testGenericNonKeyType() {
		// Fail: GenericType cannot be used as key
		TypeInformation<GenericNonKeyType> genericType = new GenericTypeInfo<>(GenericNonKeyType.class);
		new ExpressionKeys<>("*", genericType);
	}

	@Test
	public void testKeyGenericType() {

		TypeInformation<GenericKeyType> genericType = new GenericTypeInfo<>(GenericKeyType.class);
		ExpressionKeys<GenericKeyType> ek = new ExpressionKeys<>("*", genericType);

		Assert.assertArrayEquals(new int[] {0}, ek.computeLogicalKeyPositions());
	}

	@Test
	public void testTupleRangeCheck() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		
		// test private static final int[] rangeCheckFields(int[] fields, int maxAllowedField)
		Method rangeCheckFieldsMethod = Whitebox.getMethod(Keys.class, "rangeCheckFields", int[].class, int.class);

		// valid indexes
		rangeCheckFieldsMethod.invoke(null, new int[]{1, 2, 3, 4}, 4);

		// corner case tests
		rangeCheckFieldsMethod.invoke(null, new int[] {0}, 0);

		Throwable ex = null;
		try {
			// throws illegal argument.
			rangeCheckFieldsMethod.invoke(null, new int[] {5}, 0);
		} catch(Throwable iae) {
			ex = iae;
		}
		Assert.assertNotNull(ex);
	}
	
	@Test
	public void testStandardTupleKeys() {
		TupleTypeInfo<Tuple7<String, String, String, String, String, String, String>> typeInfo = new TupleTypeInfo<>(
				BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,
				BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO);
		
		ExpressionKeys<Tuple7<String, String, String, String, String, String, String>> ek;
		
		for( int i = 1; i < 8; i++) {
			int[] ints = new int[i];
			for( int j = 0; j < i; j++) {
				ints[j] = j;
			}
			int[] inInts = Arrays.copyOf(ints, ints.length); // copy, just to make sure that the code is not cheating by changing the ints.
			ek = new ExpressionKeys<>(inInts, typeInfo);
			Assert.assertArrayEquals(ints, ek.computeLogicalKeyPositions());
			Assert.assertEquals(ints.length, ek.computeLogicalKeyPositions().length);
			
			ArrayUtils.reverse(ints);
			inInts = Arrays.copyOf(ints, ints.length);
			ek = new ExpressionKeys<>(inInts, typeInfo);
			Assert.assertArrayEquals(ints, ek.computeLogicalKeyPositions());
			Assert.assertEquals(ints.length, ek.computeLogicalKeyPositions().length);
		}
	}
	
	@Test 
	public void testInvalidTuple() throws Throwable {
		TupleTypeInfo<Tuple3<String, Tuple3<String, String, String>, String>> typeInfo = new TupleTypeInfo<>(
				BasicTypeInfo.STRING_TYPE_INFO,
				new TupleTypeInfo<Tuple3<String, String, String>>(BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO),
				BasicTypeInfo.STRING_TYPE_INFO);
		
		String[][] tests = new String[][] {
				new String[] {"f0.f1"}, // nesting into unnested
				new String[] {"f11"},
				new String[] {"f-35"},
				new String[] {"f0.f33"},
				new String[] {"f1.f33"}
		};
		for (String[] test : tests) {
			Throwable e = null;
			try {
				new ExpressionKeys<>(test, typeInfo);
			} catch (Throwable t) {
				e = t;
			}
			Assert.assertNotNull(e);
		}
	}

	@Test(expected = InvalidProgramException.class)
	public void testTupleNonKeyField() {
		// selected field is not a key type
		TypeInformation<Tuple3<String, Long, GenericNonKeyType>> ti = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO,
			TypeExtractor.getForClass(GenericNonKeyType.class)
		);

		new ExpressionKeys<>(2, ti);
	}
	
	@Test
	public void testTupleKeyExpansion() {
		TupleTypeInfo<Tuple3<String, Tuple3<String, String, String>, String>> typeInfo = new TupleTypeInfo<>(
				BasicTypeInfo.STRING_TYPE_INFO,
				new TupleTypeInfo<Tuple3<String, String, String>>(BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO),
				BasicTypeInfo.STRING_TYPE_INFO);
		ExpressionKeys<Tuple3<String, Tuple3<String, String, String>, String>> fpk = 
				new ExpressionKeys<>(0, typeInfo);
		Assert.assertArrayEquals(new int[] {0}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(1, typeInfo);
		Assert.assertArrayEquals(new int[] {1,2,3}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(2, typeInfo);
		Assert.assertArrayEquals(new int[] {4}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(new int[] {0,1,2}, typeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(null, typeInfo, true); // empty case
		Assert.assertArrayEquals(new int[] {0,1,2,3,4}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>("*", typeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4}, fpk.computeLogicalKeyPositions());
		
		// scala style "select all"
		fpk = new ExpressionKeys<>("_", typeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4}, fpk.computeLogicalKeyPositions());
		
		// this was a bug:
		fpk = new ExpressionKeys<>("f2", typeInfo);
		Assert.assertArrayEquals(new int[] {4}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(new String[] {"f0","f1.f0","f1.f1", "f1.f2", "f2"}, typeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(new String[] {"f0","f1.f0","f1.f1", "f2"}, typeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,4}, fpk.computeLogicalKeyPositions());
		
		fpk = new ExpressionKeys<>(new String[] {"f2", "f0"}, typeInfo);
		Assert.assertArrayEquals(new int[] {4,0}, fpk.computeLogicalKeyPositions());
		

		TupleTypeInfo<Tuple3<String, Tuple3<Tuple3<String, String, String>, String, String>, String>> complexTypeInfo = new TupleTypeInfo<>(
				BasicTypeInfo.STRING_TYPE_INFO,
				new TupleTypeInfo<Tuple3<Tuple3<String, String, String>, String, String>>(new TupleTypeInfo<Tuple3<String, String, String>>(BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO),BasicTypeInfo.STRING_TYPE_INFO,BasicTypeInfo.STRING_TYPE_INFO),
				BasicTypeInfo.STRING_TYPE_INFO);
		
		ExpressionKeys<Tuple3<String, Tuple3<Tuple3<String, String, String>, String, String>, String>> complexFpk = 
		new ExpressionKeys<>(0, complexTypeInfo);
		Assert.assertArrayEquals(new int[] {0}, complexFpk.computeLogicalKeyPositions());
		
		complexFpk = new ExpressionKeys<>(new int[] {0,1,2}, complexTypeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4,5,6}, complexFpk.computeLogicalKeyPositions());
		
		complexFpk = new ExpressionKeys<>("*", complexTypeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4,5,6}, complexFpk.computeLogicalKeyPositions());
		
		// scala style select all
		complexFpk = new ExpressionKeys<>("_", complexTypeInfo);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4,5,6}, complexFpk.computeLogicalKeyPositions());
		
		complexFpk = new ExpressionKeys<>("f1.f0.*", complexTypeInfo);
		Assert.assertArrayEquals(new int[] {1,2,3}, complexFpk.computeLogicalKeyPositions());

		complexFpk = new ExpressionKeys<>("f1.f0", complexTypeInfo);
		Assert.assertArrayEquals(new int[] {1,2,3}, complexFpk.computeLogicalKeyPositions());
		
		complexFpk = new ExpressionKeys<>("f2", complexTypeInfo);
		Assert.assertArrayEquals(new int[] {6}, complexFpk.computeLogicalKeyPositions());
	}

	@Test
	public void testPojoKeys() {
		TypeInformation<PojoWithMultiplePojos> ti = TypeExtractor.getForClass(PojoWithMultiplePojos.class);
		ExpressionKeys<PojoWithMultiplePojos> ek;
		ek = new ExpressionKeys<>("*", ti);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("p1.*", ti);
		Assert.assertArrayEquals(new int[] {1,2}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("p2.*", ti);
		Assert.assertArrayEquals(new int[] {3,4}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("p1", ti);
		Assert.assertArrayEquals(new int[] {1,2}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("p2", ti);
		Assert.assertArrayEquals(new int[] {3,4}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("i0", ti);
		Assert.assertArrayEquals(new int[] {0}, ek.computeLogicalKeyPositions());
	}

	@Test
	public void testTupleWithNestedPojo() {

		TypeInformation<Tuple3<Integer, Pojo1, PojoWithMultiplePojos>> ti =
				new TupleTypeInfo<>(
					BasicTypeInfo.INT_TYPE_INFO,
					TypeExtractor.getForClass(Pojo1.class),
					TypeExtractor.getForClass(PojoWithMultiplePojos.class)
				);

		ExpressionKeys<Tuple3<Integer, Pojo1, PojoWithMultiplePojos>> ek;

		ek = new ExpressionKeys<>(0, ti);
		Assert.assertArrayEquals(new int[] {0}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>(1, ti);
		Assert.assertArrayEquals(new int[] {1,2}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>(2, ti);
		Assert.assertArrayEquals(new int[] {3,4,5,6,7}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>(new int[]{}, ti, true);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4,5,6,7}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("*", ti);
		Assert.assertArrayEquals(new int[] {0,1,2,3,4,5,6,7}, ek.computeLogicalKeyPositions());

		ek = new ExpressionKeys<>("f2.p1.*", ti);
		Assert.assertArrayEquals(new int[] {4,5}, ek.computeLogicalKeyPositions());
	}

	@Test
	public void testOriginalTypes() {

		TypeInformation<Tuple3<Integer, Pojo1, PojoWithMultiplePojos>> ti =
				new TupleTypeInfo<>(
						BasicTypeInfo.INT_TYPE_INFO,
						TypeExtractor.getForClass(Pojo1.class),
						TypeExtractor.getForClass(PojoWithMultiplePojos.class)
				);

		ExpressionKeys<Tuple3<Integer, Pojo1, PojoWithMultiplePojos>> ek;

		ek = new ExpressionKeys<>(0, ti);
		Assert.assertArrayEquals(new TypeInformation[] {BasicTypeInfo.INT_TYPE_INFO}, ek.getOriginalKeyFieldTypes());

		ek = new ExpressionKeys<>(1, ti);
		Assert.assertArrayEquals(new TypeInformation[] {TypeExtractor.getForClass(Pojo1.class)}, ek.getOriginalKeyFieldTypes());

		ek = new ExpressionKeys<>(2, ti);
		Assert.assertArrayEquals(new TypeInformation[] {TypeExtractor.getForClass(PojoWithMultiplePojos.class)}, ek.getOriginalKeyFieldTypes());

		ek = new ExpressionKeys<>(new int[]{}, ti, true);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { BasicTypeInfo.INT_TYPE_INFO,
						TypeExtractor.getForClass(Pojo1.class),
						TypeExtractor.getForClass(PojoWithMultiplePojos.class) },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("*", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { ti },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("f1", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { TypeExtractor.getForClass(Pojo1.class) },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("f1.*", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { TypeExtractor.getForClass(Pojo1.class) },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("f2.*", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { TypeExtractor.getForClass(PojoWithMultiplePojos.class) },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("f2.p2", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { TypeExtractor.getForClass(Pojo2.class) },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("f2.p2.*", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { TypeExtractor.getForClass(Pojo2.class) },
				ek.getOriginalKeyFieldTypes()
		);

		ek = new ExpressionKeys<>("f2.p2._", ti);
		Assert.assertArrayEquals(
				new TypeInformation<?>[] { TypeExtractor.getForClass(Pojo2.class) },
				ek.getOriginalKeyFieldTypes()
		);

	}

	@Test(expected = InvalidProgramException.class)
	public void testNonKeyPojoField() {
		// selected field is not a key type
		TypeInformation<PojoWithNonKeyField> ti = TypeExtractor.getForClass(PojoWithNonKeyField.class);
		new ExpressionKeys<>("b", ti);
	}

	@Test
	public void testInvalidPojo() throws Throwable {
		TypeInformation<ComplexNestedClass> ti = TypeExtractor.getForClass(ComplexNestedClass.class);

		String[][] tests = new String[][] {
			new String[] {"nonexistent"},
			new String[] {"date.abc"} // nesting into unnested
		};
		for (String[] test : tests) {
			Throwable e = null;
			try {
				new ExpressionKeys<>(test, ti);
			} catch (Throwable t) {
				e = t;
			}
			Assert.assertNotNull(e);
		}
	}

	@Test
	public void testAreCompatible1() throws Keys.IncompatibleKeysException {
		TypeInformation<Pojo1> t1 = TypeExtractor.getForClass(Pojo1.class);

		ExpressionKeys<Pojo1> ek1 = new ExpressionKeys<>("a", t1);
		ExpressionKeys<Pojo1> ek2 = new ExpressionKeys<>("b", t1);

		Assert.assertTrue(ek1.areCompatible(ek2));
		Assert.assertTrue(ek2.areCompatible(ek1));
	}

	@Test
	public void testAreCompatible2() throws Keys.IncompatibleKeysException {
		TypeInformation<Pojo1> t1 = TypeExtractor.getForClass(Pojo1.class);
		TypeInformation<Tuple2<String, Long>> t2 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO
		);

		ExpressionKeys<Pojo1> ek1 = new ExpressionKeys<>("a", t1);
		ExpressionKeys<Tuple2<String, Long>> ek2 = new ExpressionKeys<>(0, t2);

		Assert.assertTrue(ek1.areCompatible(ek2));
		Assert.assertTrue(ek2.areCompatible(ek1));
	}

	@Test
	public void testAreCompatible3() throws Keys.IncompatibleKeysException {
		TypeInformation<String> t1 = BasicTypeInfo.STRING_TYPE_INFO;
		TypeInformation<Tuple2<String, Long>> t2 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO
		);

		ExpressionKeys<String> ek1 = new ExpressionKeys<>("*", t1);
		ExpressionKeys<Tuple2<String, Long>> ek2 = new ExpressionKeys<>(0, t2);

		Assert.assertTrue(ek1.areCompatible(ek2));
		Assert.assertTrue(ek2.areCompatible(ek1));
	}

	@Test
	public void testAreCompatible4() throws Keys.IncompatibleKeysException {
		TypeInformation<PojoWithMultiplePojos> t1 = TypeExtractor.getForClass(PojoWithMultiplePojos.class);
		TypeInformation<Tuple3<String, Long, Integer>> t2 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO,
			BasicTypeInfo.INT_TYPE_INFO
		);

		ExpressionKeys<PojoWithMultiplePojos> ek1 = new ExpressionKeys<>(new String[]{"p1", "i0"}, t1);
		ExpressionKeys<Tuple3<String, Long, Integer>> ek2 = new ExpressionKeys<>(new int[]{0, 0, 2}, t2);

		Assert.assertTrue(ek1.areCompatible(ek2));
		Assert.assertTrue(ek2.areCompatible(ek1));
	}

	@Test
	public void testAreCompatible5() throws Keys.IncompatibleKeysException {
		TypeInformation<PojoWithMultiplePojos> t1 = TypeExtractor.getForClass(PojoWithMultiplePojos.class);
		TypeInformation<Tuple2<String, String>> t2 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.STRING_TYPE_INFO
		);

		ExpressionKeys<PojoWithMultiplePojos> ek1 = new ExpressionKeys<>(new String[]{"p1.b", "p2.a2"}, t1);
		ExpressionKeys<Tuple2<String, String>> ek2 = new ExpressionKeys<>("*", t2);

		Assert.assertTrue(ek1.areCompatible(ek2));
		Assert.assertTrue(ek2.areCompatible(ek1));
	}

	@Test(expected = Keys.IncompatibleKeysException.class)
	public void testAreCompatible6() throws Keys.IncompatibleKeysException {
		TypeInformation<Pojo1> t1 = TypeExtractor.getForClass(Pojo1.class);
		TypeInformation<Tuple2<String, Long>> t2 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO
		);

		ExpressionKeys<Pojo1> ek1 = new ExpressionKeys<>("a", t1);
		ExpressionKeys<Tuple2<String, Long>> ek2 = new ExpressionKeys<>(1, t2);

		ek1.areCompatible(ek2);
	}

	@Test(expected = Keys.IncompatibleKeysException.class)
	public void testAreCompatible7() throws Keys.IncompatibleKeysException {
		TypeInformation<Pojo1> t1 = TypeExtractor.getForClass(Pojo1.class);
		TypeInformation<Tuple2<String, Long>> t2 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO
		);

		ExpressionKeys<Pojo1> ek1 = new ExpressionKeys<>(new String[]{"a", "b"}, t1);
		ExpressionKeys<Tuple2<String, Long>> ek2 = new ExpressionKeys<>(0, t2);

		ek1.areCompatible(ek2);
	}

	@Test
	public void testAreCompatible8() throws Keys.IncompatibleKeysException {
		TypeInformation<String> t1 = BasicTypeInfo.STRING_TYPE_INFO;
		TypeInformation<Pojo2> t2 = TypeExtractor.getForClass(Pojo2.class);

		ExpressionKeys<String> ek1 = new ExpressionKeys<>("*", t1);
		Keys<Pojo2> ek2 = new Keys.SelectorFunctionKeys<>(
				new KeySelector1(),
				t2,
				BasicTypeInfo.STRING_TYPE_INFO
			);

		Assert.assertTrue(ek1.areCompatible(ek2));
	}

	@Test
	public void testAreCompatible9() throws Keys.IncompatibleKeysException {
		TypeInformation<Tuple3<String, Long, Integer>> t1 = new TupleTypeInfo<>(
			BasicTypeInfo.STRING_TYPE_INFO,
			BasicTypeInfo.LONG_TYPE_INFO,
			BasicTypeInfo.INT_TYPE_INFO
		);
		TypeInformation<PojoWithMultiplePojos> t2 = TypeExtractor.getForClass(PojoWithMultiplePojos.class);

		ExpressionKeys<Tuple3<String, Long, Integer>> ek1 = new ExpressionKeys<>(new int[]{2,0}, t1);
		Keys<PojoWithMultiplePojos> ek2 = new Keys.SelectorFunctionKeys<>(
			new KeySelector3(),
			t2,
			new TupleTypeInfo<Tuple2<Integer, String>>(BasicTypeInfo.INT_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO)
		);

		Assert.assertTrue(ek1.areCompatible(ek2));
	}

	public static class Pojo1 {
		public String a;
		public String b;
	}

	public static class Pojo2 {
		public String a2;
		public String b2;
	}

	public static class PojoWithMultiplePojos {
		public Pojo1 p1;
		public Pojo2 p2;
		public Integer i0;
	}

	public static class PojoWithNonKeyField {
		public String a;
		public GenericNonKeyType b;
	}

	public static class GenericNonKeyType {
		private String a;
		private String b;
	}

	public static class GenericKeyType implements Comparable<GenericNonKeyType> {
		private String a;
		private String b;

		@Override
		public int compareTo(GenericNonKeyType o) {
			return 0;
		}
	}
}