package org.enumerable.lambda;
import org.enumerable.lambda.annotation.LambdaParameter;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.NotSerializableException;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import static java.lang.Math.PI;
import static org.enumerable.lambda.Lambda.delegate;
import static org.enumerable.lambda.Lambda.λ;
import static org.enumerable.lambda.Parameters.*;
import static org.enumerable.lambda.primitives.LambdaPrimitives.λ;
import static org.junit.Assert.*;
@SuppressWarnings("serial")
public class ClosureTest extends TestBase implements Serializable {
@Test(expected = ArithmeticException.class)
public void uncheckedExceptionInBlockPropagetsOut() throws Exception {
λ(n, n / 0).call(0);
}
@Test(expected = ClassNotFoundException.class)
public void checkedExceptionInBlockPropagatesOut() throws Exception {
Fn1<String, ? extends Class<?>> classForName = λ(s, Class.forName(s));
assertEquals(String.class, classForName.call(String.class.getName()));
classForName.call("class.not.Found");
}
@Test
public void closeOverLocalPrimitiveVariable() throws Exception {
int i = 0;
λ(n, i += n).call(10);
assertEquals(10, i);
}
@Test
public void closeOverLocalDoubleVariableWithoutModification() throws Exception {
double i = 0;
assertEquals(3.14, λ(d, i + d).call(3.14), 0);
}
@Test
public void closeOverLocalDoubleWithModification() throws Exception {
double i = 0;
λ(d, i = d).call(3.14);
assertEquals(3.14, i, 0);
}
@Test
public void closeOverLocalLongVariableWithoutModification() throws Exception {
long i = 0;
assertEquals(10, (long) λ(l, i + l).call(10L));
}
@Test
public void closeOverLocalLongWithModification() throws Exception {
long i = 0;
λ(l, i += l).call(10L);
assertEquals(10, i);
}
@Test
public void closeOverLocalReferenceVariable() throws Exception {
String hello = "hello";
λ(s, hello += s).call(" world");
assertEquals("hello world", hello);
assertEquals(String.class, hello.getClass());
}
@Test
public void closeOverLocalArrayVariable() throws Exception {
String[] hello = new String[] { "hello" };
String[] original = hello;
λ(s, hello[0] += s).call(" world");
assertEquals("hello world", hello[0]);
assertSame(original, hello);
}
@Test
public void closeOverLocalArrayVariableWhichCanBeSet() throws Exception {
String[] hello = new String[] { "hello" };
String[] original = hello;
λ(s, hello = new String[] { s }).call("world");
assertEquals("world", hello[0]);
assertNotSame(original, hello);
}
@Test
public void closeOverLocalFinalPrimitiveVariable() throws Exception {
final int i = 10;
assertEquals(20, (int) λ(n, i + n).call(10));
}
@Test
public void closeOverLocalFinalReferenceVariable() throws Exception {
final String hello = "hello";
assertEquals("hello world", λ(s, hello + s).call(" world"));
}
@Test
public void closeOverLocalFinalArrayVarible() throws Exception {
final String[] hello = new String[] { "hello" };
λ(s, hello[0] += s).call(" world");
assertEquals("hello world", hello[0]);
}
// This test fails when the CheckClassAdapter is used with 'duplicate class
// definition for name: lambda/ClosureTest'
// @Test
// public void closeOverThis() throws Exception {
// assertSame(this, λ(this).call());
// }
@Test
public void closeOverMethodParameter() throws Exception {
methodCall("Hello");
}
void methodCall(String string) {
assertSame(string, λ(string).call());
λ(string = "world").call();
assertEquals("world", string);
}
@Test
public void closingOverLocalVariableDoesNotChangeIt() throws Exception {
int i = 0;
λ(n, i += n);
assertEquals(0, i);
}
@Test
public void closeOverLocalVariableAndRunInDifferentMethod() throws Exception {
int i = 0;
otherMethod(λ(n, i += n), 10);
assertEquals(10, i);
}
@Test
public void closingOverLocalPrimitveVariableCanStillIncrementOutsideClosure() throws Exception {
int i = 0;
Fn1<Integer, Integer> add = λ(n, i += n);
i += 10;
assertEquals(10, i);
add.call((int) Short.MAX_VALUE);
assertEquals(Short.MAX_VALUE + 10, i);
i += Integer.MAX_VALUE / 2;
assertEquals(Short.MAX_VALUE + 10 + Integer.MAX_VALUE / 2, i);
}
@Test
public void closingOverLocalReferenceVariableWorksLikeNormalOutsideClosure() throws Exception {
String string = "hello";
Fn1<String, String> toUpCase = λ(s, s.toUpperCase());
string += " world";
assertEquals("hello world", string);
string = toUpCase.call(string);
assertEquals("HELLO WORLD", string);
assertEquals(String.class, string.getClass());
}
void otherMethod(Fn1<Integer, Integer> add, int x) {
add.call(x);
}
@Test
public void closingOverLocalVariableSeesChangesToLocalVariable() throws Exception {
int i = 0;
Fn1<Integer, Integer> plus = λ(n, i += n);
plus.call(1);
assertEquals(1, i);
i++;
assertEquals(2, i);
plus.call(1);
assertEquals(3, i);
}
@Test
public void closingOverLocalVariableAfterMethodReturn() throws Exception {
Fn1<Integer, Integer> plus = methodReturn();
assertEquals(2, (int) plus.call(1));
assertEquals(3, (int) plus.call(1));
}
public Fn1<Integer, Integer> methodReturn() throws Exception {
int i = 1;
return λ(n, i += n);
}
// @Test
// public void closeOverForLoopVariable() throws Exception {
// int sum = 0;
// for (int i = 1; i <= 10; i++)
// sum = λ(n, n + i).call(sum);
// assertEquals(55, sum);
// }
// @Test
// @Ignore("This test fail when compiled with javac for some reason. A crazy test anyway")
// public void closeOverForLoopVariableInTestAndForUpdate() throws Exception
// {
// int sum = 0;
// for (int i = 0; λ(n, n < 10).call(i); sum += λ(n, i += n).call(1))
// ;
// assertEquals(55, sum);
// }
@Test
public void closingOverOuterFinalInlineableVariableFromInsideAnonymousClass() throws Exception {
final String string = "final";
assertEquals(string, new Callable<String>() {
public String call() {
return λ(string).call();
}
}.call());
}
@Test
public void closingOverOuterFinalReferenceVariableFromInsideAnonymousClass() throws Exception {
final List<String> list = new ArrayList<String>();
new Runnable() {
public void run() {
λ(list.add("final")).call();
}
}.run();
assertEquals(list("final"), list);
}
@Test
public void callInstanceMethodOnThis() throws Exception {
assertEquals(hello(), λ(hello()).call());
}
public String hello() {
return "hello";
}
private String world = "world";
@Test
public void gettingPrivateFieldOnThis() throws Exception {
assertEquals(world, λ(world).call());
}
@Test
public void settingPrivateFieldOnThis() throws Exception {
assertEquals("hello", λ(world = "hello").call());
assertEquals("hello", world);
}
private static String staticWorld = "world";
@Test
public void gettingPrivateStaticFieldOnThisClass() throws Exception {
assertEquals(staticWorld, λ(staticWorld).call());
}
@Test
public void settingPrivateStaticFieldOnThisClass() throws Exception {
assertEquals("hello", λ(staticWorld = "hello").call());
assertEquals("hello", staticWorld);
}
private long large = 2;
@Test
public void gettingPrivateLongFieldOnThis() throws Exception {
assertEquals(large, (long) λ(large).call());
}
@Test
public void settingPrivateLongFieldOnThis() throws Exception {
assertEquals(4, (long) λ(large = 4).call());
assertEquals(4, large);
}
@Test
public void callPrivateInstanceMethodOnThisTakingNoArguments() throws Exception {
assertEquals(privateHelloNoArguments(), λ(privateHelloNoArguments()).call());
}
@Test
public void callPrivateInstanceMethodOnThisTakingNoArgumentsTwice() throws Exception {
assertEquals(privateHelloNoArguments(), λ(privateHelloNoArguments()).call());
assertEquals(privateHelloNoArguments(), λ(privateHelloNoArguments()).call());
}
@Test
public void callPrivateInstanceMethodOnThisTakingArguments() throws Exception {
assertEquals(privateHello("world", 2), λ(privateHello("world", 2)).call());
}
@Test
public void callPrivateInstanceMethodOnThisReturningPrimitive() throws Exception {
assertEquals(privatePrimitiveHello("world", 2), (int) λ(privatePrimitiveHello("world", 2)).call());
}
@Test
public void callPrivateInstanceMethodOnThisWithExistingAccessMethod() throws Exception {
int result = λ(privatePrimitiveHello("world", 2)).call();
assertEquals(7, result);
assertEquals(new Fn0<Object>() {
public Object call() {
return privatePrimitiveHello("", 2);
}
}.call(), result);
}
@Test
public void callPrivateStaticMethodOnThisClassTakingNoArguments() throws Exception {
assertEquals(staticPrivateHelloNoArguments(), λ(staticPrivateHelloNoArguments()).call());
}
private static String staticPrivateHelloNoArguments() {
return "hello";
}
private String privateHello(String string, int number) {
return "hello" + string + number;
}
private String privateHelloNoArguments() {
return "hello";
}
private int privatePrimitiveHello(String string, int number) {
return "hello".length() + number;
}
@Test
public void callStaticMethod() throws Exception {
assertEquals(ClosureTest.world(), λ(ClosureTest.world()).call());
}
public static String world() {
return "hello";
}
@Test
public void callStaticMethodReturningClosure() throws Exception {
assertEquals("helloworld", OtherClass.helloStatic().call("world"));
}
@Test
public void callClosureCreatedInStaticInit() throws Exception {
assertEquals("!", OtherClass.staticClosure.call());
}
public static class OtherClass {
@LambdaParameter
static String string;
static Fn0<String> staticClosure = λ("!");
public static Fn1<String, String> helloStatic() {
return λ(string, "hello" + string);
}
}
@Test
public void callInstanceMethodOnArgument() throws Exception {
assertEquals("HELLO", λ(s, s.toUpperCase()).call(hello()));
}
@Test
public void callStaticMethodOnDifferentClass() throws Exception {
assertTrue(λ(c, Character.isUpperCase(c)).call('C'));
}
@Test
public void accessingEnclosingMethodArgument() throws Exception {
instanceArgumentMethodCall("Hello");
}
void instanceArgumentMethodCall(String string) {
assertEquals("Hello", string);
assertEquals(string.toUpperCase(), λ(string.toUpperCase()).call());
}
@Test
public void canAccessMethodArgumentInClosureFirst() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
printOnStream(new PrintStream(out));
assertEquals("word: hello\n", out.toString());
}
public void printOnStream(PrintStream out) {
λ(s, out.printf("word: %s\n", s)).call("hello");
}
@Test
public void accessingEnclosingMethodPrimitiveArgument() throws Exception {
primitiveArgumentMethodCall(10);
primitiveArgumentIncMethodCall(10);
}
void primitiveArgumentMethodCall(int x) {
assertEquals(10, x);
assertEquals(x, (int) λ(n, x).call(0));
}
void primitiveArgumentIncMethodCall(int x) {
λ(n, x++).call(0);
}
@Test
public void returnSameArgument() throws Exception {
String hello = hello();
assertSame(hello, λ(s, s).call(hello));
}
static int staticInt = 0;
@Test
public void accessingPrimitiveStaticField() throws Exception {
λ(n, staticInt += n).call(10);
assertEquals(10, staticInt);
}
static String staticString = "world";
@Test
public void accessingStaticField() throws Exception {
λ(s, staticString = s + staticString).call("hello ");
assertEquals("hello world", staticString);
}
double instanceDouble = 0;
@Test
public void accessingPrimitveInstanceField() throws Exception {
λ(d, instanceDouble += d).call(3.14);
assertEquals(3.14, instanceDouble, 0.0);
}
String instanceString = "";
@Test
public void accessingInstanceField() throws Exception {
λ(s, instanceString = s).call("hello world");
assertEquals("hello world", instanceString);
}
@Test
public void accessStaticFieldOnDifferentClass() throws Exception {
assertEquals(PI, λ(d, PI).call(0.0), 0.0);
}
@Test
public void defaultValueCapturedFromLocalVariableForFirstArgument() throws Exception {
int two = 2;
Fn1<Integer, Integer> nTimesN = λ(n = two, n * n);
assertEquals(9, (int) nTimesN.call(3));
assertEquals(4, (int) nTimesN.call());
}
@Test
public void defaultValueCapturedFromLocalVariableForSecondArgument() throws Exception {
int two = 2;
Fn2<Integer, Integer, Integer> nTimesM = λ(n, m = two, n * m);
assertEquals(2, (int) nTimesM.call(2, 1));
assertEquals(4, (int) nTimesM.call(2));
}
int two = 2;
@Test
public void defaultValueCapturedFromInstanceFieldForSecondArgument() throws Exception {
Fn2<Integer, Integer, Integer> nTimesM = λ(n, m = two, n * m);
assertEquals(2, (int) nTimesM.call(2, 1));
assertEquals(4, (int) nTimesM.call(2));
}
@Test
public void defaultValueFromInstanceMethodForSecondArgument() throws Exception {
Fn2<Integer, Integer, Integer> nTimesM = λ(n, m = two(), n * m);
assertEquals(2, (int) nTimesM.call(2, 1));
assertEquals(4, (int) nTimesM.call(2));
}
private int two() {
return 2;
}
@Test
public void defaultValueFromStaticFieldForSecondArgument() throws Exception {
Fn2<Double, Double, Double> nTimesM = λ(x, y = PI, x * y);
assertEquals(2, nTimesM.call(2.0, 1.0), 0.0);
assertEquals(2 * PI, nTimesM.call(2.0), 0.0);
}
@Test
public void readingBindings() throws Exception {
Fn0<String> closure = getClosureWithBindings("world");
Fn0<?>.Binding binding = closure.binding();
assertEquals(this, binding.get("this"));
assertEquals(2, binding.get("two"));
assertEquals("world", binding.get("arg"));
closure.call();
binding = closure.binding();
assertEquals(4, binding.get("two"));
}
public Fn0<String> getClosureWithBindings(String arg) {
@SuppressWarnings("unused")
int two = 2;
return λ((two = 4) + hello() + arg);
}
public void readingVariableNotInBindingReturnsNull() throws Exception {
Fn0<String> closure = λ(hello());
Fn0<?>.Binding binding = closure.binding();
assertNull(binding.get("x"));
}
@Test(expected = IllegalArgumentException.class)
public void changingFinalVariableInBindingThrowsException() throws Exception {
Fn0<String> closure = λ(hello());
Fn0<?>.Binding binding = closure.binding();
binding.set("this", this);
}
@Test(expected = IllegalArgumentException.class)
public void changingVariableNotInBindingThrowsException() throws Exception {
Fn0<String> closure = λ(hello());
Fn0<?>.Binding binding = closure.binding();
binding.set("x", 2);
}
@Test
public void changingMutableVariableInBindingReflectsOnCapturedContext() throws Exception {
int two = 2;
Fn0<?>.Binding binding = λ(two = 2).binding();
binding.set("two", 4);
assertEquals(4, two);
}
@Test
public void changingMutableVariableInCapturedContextReflectsOnBinding() throws Exception {
int two = 2;
Fn0<?>.Binding binding = λ(two = 2).binding();
two = 4;
assertEquals(two, binding.get("two"));
assertEquals(4, binding.get("two"));
}
@Test
public void serializingOfClosure() throws Exception {
int x = 5;
Fn1<Integer, Integer> inc = λ(n, x = n + x);
assertEquals(10, (int) inc.call(5));
assertEquals(10, x);
byte[] bytes = serialze(inc);
Fn1<Integer, Integer> deserializedInc = deserialize(bytes);
assertNotSame(inc, deserializedInc);
assertEquals(15, (int) deserializedInc.call(5));
assertEquals(10, x);
assertEquals(12, (int) inc.call(2));
assertEquals(12, x);
assertEquals(20, (int) deserializedInc.call(5));
}
int fieldOnThis = 5;
@Test
public void serializingWhenClosingOverThis() throws Exception {
Fn1<Integer, Integer> inc = λ(n, this.fieldOnThis = n + this.fieldOnThis);
assertEquals(10, (int) inc.call(5));
assertEquals(10, this.fieldOnThis);
byte[] bytes = serialze(inc);
Fn1<Integer, Integer> deserializedInc = deserialize(bytes);
assertNotSame(inc, deserializedInc);
assertEquals(15, (int) deserializedInc.call(5));
assertEquals(10, this.fieldOnThis);
}
@Test(expected = NotSerializableException.class)
public void lambdaMustBeExplicitlySerializable() throws Exception {
Runnable runnable = delegate(fieldOnThis = 1);
runnable.run();
assertEquals(1, fieldOnThis);
serialze(runnable);
}
}