/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.painless;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.lang.invoke.LambdaConversionException;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
public class FunctionRefTests extends ScriptTestCase {
public void testStaticMethodReference() {
assertEquals(1, exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(Integer::compare); return l.get(0);"));
}
public void testStaticMethodReferenceDef() {
assertEquals(1, exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(Integer::compare); return l.get(0);"));
}
public void testVirtualMethodReference() {
assertEquals(2, exec("List l = new ArrayList(); l.add(1); l.add(1); return l.stream().mapToInt(Integer::intValue).sum();"));
}
public void testVirtualMethodReferenceDef() {
assertEquals(2, exec("def l = new ArrayList(); l.add(1); l.add(1); return l.stream().mapToInt(Integer::intValue).sum();"));
}
public void testQualifiedStaticMethodReference() {
assertEquals(true,
exec("List l = [true]; l.stream().map(org.elasticsearch.painless.FeatureTest::overloadedStatic).findFirst().get()"));
}
public void testQualifiedStaticMethodReferenceDef() {
assertEquals(true,
exec("def l = [true]; l.stream().map(org.elasticsearch.painless.FeatureTest::overloadedStatic).findFirst().get()"));
}
public void testQualifiedVirtualMethodReference() {
long instant = randomLong();
assertEquals(instant, exec(
"List l = [params.d]; return l.stream().mapToLong(org.joda.time.ReadableDateTime::getMillis).sum()",
singletonMap("d", new DateTime(instant, DateTimeZone.UTC)), true));
}
public void testQualifiedVirtualMethodReferenceDef() {
long instant = randomLong();
assertEquals(instant, exec(
"def l = [params.d]; return l.stream().mapToLong(org.joda.time.ReadableDateTime::getMillis).sum()",
singletonMap("d", new DateTime(instant, DateTimeZone.UTC)), true));
}
public void testCtorMethodReference() {
assertEquals(3.0D,
exec("List l = new ArrayList(); l.add(1.0); l.add(2.0); " +
"DoubleStream doubleStream = l.stream().mapToDouble(Double::doubleValue);" +
"DoubleSummaryStatistics stats = doubleStream.collect(DoubleSummaryStatistics::new, " +
"DoubleSummaryStatistics::accept, " +
"DoubleSummaryStatistics::combine); " +
"return stats.getSum()"));
}
public void testCtorMethodReferenceDef() {
assertEquals(3.0D,
exec("def l = new ArrayList(); l.add(1.0); l.add(2.0); " +
"def doubleStream = l.stream().mapToDouble(Double::doubleValue);" +
"def stats = doubleStream.collect(DoubleSummaryStatistics::new, " +
"DoubleSummaryStatistics::accept, " +
"DoubleSummaryStatistics::combine); " +
"return stats.getSum()"));
}
public void testCtorWithParams() {
assertArrayEquals(new Object[] { "foo", "bar" },
(Object[]) exec("List l = new ArrayList(); l.add('foo'); l.add('bar'); " +
"Stream stream = l.stream().map(StringBuilder::new);" +
"return stream.map(Object::toString).toArray()"));
}
public void testArrayCtorMethodRef() {
assertEquals(1.0D,
exec("List l = new ArrayList(); l.add(1.0); l.add(2.0); " +
"def[] array = l.stream().toArray(Double[]::new);" +
"return array[0];"));
}
public void testArrayCtorMethodRefDef() {
assertEquals(1.0D,
exec("def l = new ArrayList(); l.add(1.0); l.add(2.0); " +
"def[] array = l.stream().toArray(Double[]::new);" +
"return array[0];"));
}
public void testCapturingMethodReference() {
assertEquals("5", exec("Integer x = Integer.valueOf(5); return Optional.empty().orElseGet(x::toString);"));
assertEquals("[]", exec("List l = new ArrayList(); return Optional.empty().orElseGet(l::toString);"));
}
public void testCapturingMethodReferenceDefImpl() {
assertEquals("5", exec("def x = Integer.valueOf(5); return Optional.empty().orElseGet(x::toString);"));
assertEquals("[]", exec("def l = new ArrayList(); return Optional.empty().orElseGet(l::toString);"));
}
public void testCapturingMethodReferenceDefInterface() {
assertEquals("5", exec("Integer x = Integer.valueOf(5); def opt = Optional.empty(); return opt.orElseGet(x::toString);"));
assertEquals("[]", exec("List l = new ArrayList(); def opt = Optional.empty(); return opt.orElseGet(l::toString);"));
}
public void testCapturingMethodReferenceDefEverywhere() {
assertEquals("5", exec("def x = Integer.valueOf(5); def opt = Optional.empty(); return opt.orElseGet(x::toString);"));
assertEquals("[]", exec("def l = new ArrayList(); def opt = Optional.empty(); return opt.orElseGet(l::toString);"));
}
public void testCapturingMethodReferenceMultipleLambdas() {
assertEquals("testingcdefg", exec(
"String x = 'testing';" +
"String y = 'abcdefg';" +
"org.elasticsearch.painless.FeatureTest test = new org.elasticsearch.painless.FeatureTest(2,3);" +
"return test.twoFunctionsOfX(x::concat, y::substring);"));
}
public void testCapturingMethodReferenceMultipleLambdasDefImpls() {
assertEquals("testingcdefg", exec(
"def x = 'testing';" +
"def y = 'abcdefg';" +
"org.elasticsearch.painless.FeatureTest test = new org.elasticsearch.painless.FeatureTest(2,3);" +
"return test.twoFunctionsOfX(x::concat, y::substring);"));
}
public void testCapturingMethodReferenceMultipleLambdasDefInterface() {
assertEquals("testingcdefg", exec(
"String x = 'testing';" +
"String y = 'abcdefg';" +
"def test = new org.elasticsearch.painless.FeatureTest(2,3);" +
"return test.twoFunctionsOfX(x::concat, y::substring);"));
}
public void testCapturingMethodReferenceMultipleLambdasDefEverywhere() {
assertEquals("testingcdefg", exec(
"def x = 'testing';" +
"def y = 'abcdefg';" +
"def test = new org.elasticsearch.painless.FeatureTest(2,3);" +
"return test.twoFunctionsOfX(x::concat, y::substring);"));
}
public void testOwnStaticMethodReference() {
assertEquals(2, exec("int mycompare(int i, int j) { j - i } " +
"List l = new ArrayList(); l.add(2); l.add(1); l.sort(this::mycompare); return l.get(0);"));
}
public void testOwnStaticMethodReferenceDef() {
assertEquals(2, exec("int mycompare(int i, int j) { j - i } " +
"def l = new ArrayList(); l.add(2); l.add(1); l.sort(this::mycompare); return l.get(0);"));
}
public void testInterfaceDefaultMethod() {
assertEquals("bar", exec("String f(BiFunction function) { function.apply('foo', 'bar') }" +
"Map map = new HashMap(); f(map::getOrDefault)"));
}
public void testInterfaceDefaultMethodDef() {
assertEquals("bar", exec("String f(BiFunction function) { function.apply('foo', 'bar') }" +
"def map = new HashMap(); f(map::getOrDefault)"));
}
public void testMethodMissing() {
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);");
});
assertThat(e.getMessage(), startsWith("Unknown reference"));
}
public void testQualifiedMethodMissing() {
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false);
});
assertThat(e.getMessage(), startsWith("Unknown reference"));
}
public void testClassMissing() {
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = [2, 1]; l.sort(Bogus::bogus); return l.get(0);", false);
});
assertThat(e.getMessage(), endsWith("Variable [Bogus] is not defined."));
}
public void testQualifiedClassMissing() {
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = [2, 1]; l.sort(org.joda.time.BogusDateTime::bogus); return l.get(0);", false);
});
/* Because the type isn't known and we use the lexer hack this fails to parse. I find this error message confusing but it is the one
* we have... */
assertEquals("invalid sequence of tokens near ['::'].", e.getMessage());
}
public void testNotFunctionalInterface() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);");
});
assertThat(expected.getMessage(), containsString("Cannot convert function reference"));
}
public void testIncompatible() {
expectScriptThrows(BootstrapMethodError.class, () -> {
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
});
}
public void testWrongArity() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("Optional.empty().orElseGet(String::startsWith);");
});
assertThat(expected.getMessage(), containsString("Unknown reference"));
}
public void testWrongArityNotEnough() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
});
assertTrue(expected.getMessage().contains("Unknown reference"));
}
public void testWrongArityDef() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
});
assertThat(expected.getMessage(), containsString("Unknown reference"));
}
public void testWrongArityNotEnoughDef() {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
});
assertThat(expected.getMessage(), containsString("Unknown reference"));
}
public void testReturnVoid() {
Throwable expected = expectScriptThrows(ClassCastException.class, () -> {
exec("StringBuilder b = new StringBuilder(); List l = [1, 2]; l.stream().mapToLong(b::setLength).sum();");
});
assertThat(expected.getMessage(), containsString("Cannot cast from [void] to [long]."));
}
public void testReturnVoidDef() {
Exception expected = expectScriptThrows(LambdaConversionException.class, () -> {
exec("StringBuilder b = new StringBuilder(); def l = [1, 2]; l.stream().mapToLong(b::setLength);");
});
assertThat(expected.getMessage(), containsString("lambda expects return type [long], but found return type [void]"));
expected = expectScriptThrows(LambdaConversionException.class, () -> {
exec("def b = new StringBuilder(); def l = [1, 2]; l.stream().mapToLong(b::setLength);");
});
assertThat(expected.getMessage(), containsString("lambda expects return type [long], but found return type [void]"));
expected = expectScriptThrows(LambdaConversionException.class, () -> {
exec("def b = new StringBuilder(); List l = [1, 2]; l.stream().mapToLong(b::setLength);");
});
assertThat(expected.getMessage(), containsString("lambda expects return type [long], but found return type [void]"));
}
}