1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.lang;
18
19import junit.framework.TestCase;
20
21import java.io.ByteArrayInputStream;
22import java.io.ByteArrayOutputStream;
23import java.io.IOException;
24import java.io.NotSerializableException;
25import java.io.ObjectInputStream;
26import java.io.ObjectOutputStream;
27import java.io.Serializable;
28import java.lang.reflect.Method;
29import java.lang.reflect.Modifier;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.HashSet;
33import java.util.List;
34import java.util.Set;
35import java.util.concurrent.Callable;
36
37public class LambdaImplementationTest extends TestCase {
38
39    private static final String STATIC_METHOD_RESPONSE = "StaticMethodResponse";
40
41    public void testNonCapturingLambda() throws Exception {
42        Callable<String> r1 = () -> "Hello World";
43        assertGeneralLambdaClassCharacteristics(r1);
44        assertLambdaImplementsInterfaces(r1, Callable.class);
45        assertLambdaMethodCharacteristics(r1, Callable.class);
46        assertNonSerializableLambdaCharacteristics(r1);
47        assertCallableBehavior(r1, "Hello World");
48
49        List<Callable<String>> callables = new ArrayList<>();
50        for (int i = 0; i < 2; i++) {
51            callables.add(() -> "Hello World");
52        }
53        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
54        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
55    }
56
57    interface Condition<T> {
58        boolean check(T arg);
59    }
60
61    public void testInstanceMethodReferenceLambda() throws Exception {
62        Condition<String> c = String::isEmpty;
63        Class<?> lambdaClass = c.getClass();
64        assertGeneralLambdaClassCharacteristics(c);
65        assertLambdaImplementsInterfaces(c, Condition.class);
66        assertLambdaMethodCharacteristics(c, Condition.class);
67        assertNonSerializableLambdaCharacteristics(c);
68
69        // Check the behavior of the lambda's method.
70        assertTrue(c.check(""));
71        assertFalse(c.check("notEmpty"));
72
73        Method implCallMethod = lambdaClass.getMethod(
74                "check", Object.class /* type erasure => not String.class */);
75        assertTrue((Boolean) implCallMethod.invoke(c, ""));
76        assertFalse((Boolean) implCallMethod.invoke(c, "notEmpty"));
77
78        Method interfaceCallMethod = Condition.class.getDeclaredMethod(
79                "check", Object.class /* type erasure => not String.class */);
80        assertTrue((Boolean) interfaceCallMethod.invoke(c, ""));
81        assertFalse((Boolean) interfaceCallMethod.invoke(c, "notEmpty"));
82    }
83
84    public void testStaticMethodReferenceLambda() throws Exception {
85        Callable<String> r1 = LambdaImplementationTest::staticMethod;
86        assertGeneralLambdaClassCharacteristics(r1);
87        assertLambdaImplementsInterfaces(r1, Callable.class);
88        assertLambdaMethodCharacteristics(r1, Callable.class);
89        assertNonSerializableLambdaCharacteristics(r1);
90
91        assertCallableBehavior(r1, STATIC_METHOD_RESPONSE);
92
93        List<Callable<String>> callables = new ArrayList<>();
94        for (int i = 0; i < 2; i++) {
95            callables.add(LambdaImplementationTest::staticMethod);
96        }
97        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
98        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
99    }
100
101    public void testObjectMethodReferenceLambda() throws Exception {
102        String msg = "Hello";
103        StringBuilder o = new StringBuilder(msg);
104        Callable<String> r1 = o::toString;
105        assertGeneralLambdaClassCharacteristics(r1);
106        assertLambdaImplementsInterfaces(r1, Callable.class);
107        assertLambdaMethodCharacteristics(r1, Callable.class);
108        assertNonSerializableLambdaCharacteristics(r1);
109
110        assertCallableBehavior(r1, msg);
111
112        List<Callable<String>> callables = new ArrayList<>();
113        for (int i = 0; i < 2; i++) {
114            callables.add(o::toString);
115        }
116        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
117        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
118    }
119
120    public void testArgumentCapturingLambda() throws Exception {
121        checkArgumentCapturingLambda("Argument");
122    }
123
124    private void checkArgumentCapturingLambda(String msg) throws Exception {
125        Callable<String> r1 = () -> msg;
126        assertGeneralLambdaClassCharacteristics(r1);
127        assertLambdaImplementsInterfaces(r1, Callable.class);
128        assertLambdaMethodCharacteristics(r1, Callable.class);
129        assertNonSerializableLambdaCharacteristics(r1);
130
131        assertCallableBehavior(r1, msg);
132
133        List<Callable<String>> callables = new ArrayList<>();
134        for (int i = 0; i < 2; i++) {
135            callables.add(() -> msg);
136        }
137        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
138        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
139    }
140
141    public void testSerializableLambda_withoutState() throws Exception {
142        Callable<String> r1 = (Callable<String> & Serializable) () -> "No State";
143        assertGeneralLambdaClassCharacteristics(r1);
144        assertLambdaImplementsInterfaces(r1, Callable.class, Serializable.class);
145        assertLambdaMethodCharacteristics(r1, Callable.class);
146        assertSerializableLambdaCharacteristics(r1);
147
148        assertCallableBehavior(r1, "No State");
149
150        List<Callable<String>> callables = new ArrayList<>();
151        for (int i = 0; i < 2; i++) {
152            Callable<String> callable = (Callable<String> & Serializable) () -> "No State";
153            assertLambdaImplementsInterfaces(callable, Callable.class, Serializable.class);
154            callables.add(callable);
155        }
156        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
157        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
158    }
159
160    public void testSerializableLambda_withState() throws Exception {
161        final long state = System.currentTimeMillis();
162        Callable<String> r1 = (Callable<String> & Serializable) () -> "State:" + state;
163        assertGeneralLambdaClassCharacteristics(r1);
164        assertLambdaImplementsInterfaces(r1, Callable.class, Serializable.class);
165        assertLambdaMethodCharacteristics(r1, Callable.class);
166        assertSerializableLambdaCharacteristics(r1);
167
168        assertCallableBehavior(r1, "State:" + state);
169
170        Callable<String> deserializedR1 = roundtripSerialization(r1);
171        assertEquals(r1.call(), deserializedR1.call());
172
173        List<Callable<String>> callables = new ArrayList<>();
174        for (int i = 0; i < 2; i++) {
175            Callable<String> callable = (Callable<String> & Serializable) () -> "State:" + state;
176            assertLambdaImplementsInterfaces(callable, Callable.class, Serializable.class);
177            callables.add(callable);
178        }
179        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
180        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
181    }
182
183    public void testBadSerializableLambda() throws Exception {
184        final Object state = new Object(); // Not Serializable
185        Callable<String> r1 = (Callable<String> & Serializable) () -> "Hello world: " + state;
186        assertGeneralLambdaClassCharacteristics(r1);
187        assertLambdaMethodCharacteristics(r1, Callable.class);
188        assertLambdaImplementsInterfaces(r1, Callable.class, Serializable.class);
189
190        try {
191            serializeObject(r1);
192            fail();
193        } catch (NotSerializableException expected) {
194        }
195    }
196
197    public void testMultipleInterfaceLambda() throws Exception {
198        Callable<String> r1 = (Callable<String> & MarkerInterface) () -> "MultipleInterfaces";
199        assertTrue(r1 instanceof MarkerInterface);
200        assertGeneralLambdaClassCharacteristics(r1);
201        assertLambdaMethodCharacteristics(r1, Callable.class);
202        assertLambdaImplementsInterfaces(r1, Callable.class, MarkerInterface.class);
203        assertNonSerializableLambdaCharacteristics(r1);
204
205        assertCallableBehavior(r1, "MultipleInterfaces");
206
207        List<Callable<String>> callables = new ArrayList<>();
208        for (int i = 0; i < 2; i++) {
209            Callable<String> callable =
210                    (Callable<String> & MarkerInterface) () -> "MultipleInterfaces";
211            assertLambdaImplementsInterfaces(callable, Callable.class, MarkerInterface.class);
212            callables.add(callable);
213        }
214        assertLambdaImplementsInterfaces(r1, Callable.class, MarkerInterface.class);
215        assertMultipleDefinitionCharacteristics(r1, callables.get(0));
216        assertMultipleInstanceCharacteristics(callables.get(0), callables.get(1));
217    }
218
219    private static void assertSerializableLambdaCharacteristics(Object r1) throws Exception {
220        assertTrue(r1 instanceof Serializable);
221
222        Object deserializedR1 = roundtripSerialization(r1);
223        assertFalse(deserializedR1.equals(r1));
224        assertNotSame(deserializedR1, r1);
225    }
226
227    @SuppressWarnings("unchecked")
228    private static <T> T roundtripSerialization(T r1) throws Exception {
229        byte[] bytes = serializeObject(r1);
230        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
231        try (ObjectInputStream is = new ObjectInputStream(bais)) {
232            return (T) is.readObject();
233        }
234    }
235
236    private static <T> byte[] serializeObject(T r1) throws IOException {
237        ByteArrayOutputStream baos = new ByteArrayOutputStream();
238        try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
239            os.writeObject(r1);
240            os.flush();
241        }
242        return baos.toByteArray();
243    }
244
245    private static <T> void assertLambdaImplementsInterfaces(T r1, Class<?>... expectedInterfaces)
246            throws Exception {
247        Class<?> lambdaClass = r1.getClass();
248
249        // Check directly implemented interfaces. Ordering is well-defined.
250        Class<?>[] actualInterfaces = lambdaClass.getInterfaces();
251        assertEquals(expectedInterfaces.length, actualInterfaces.length);
252        List<Class<?>> actual = Arrays.asList(actualInterfaces);
253        List<Class<?>> expected = Arrays.asList(expectedInterfaces);
254        assertEquals(expected, actual);
255
256        // Confirm that the only method declared on the lambda's class are those defined by
257        // interfaces it implements. i.e. there's no additional public contract.
258        Set<Method> declaredMethods = new HashSet<>();
259        addNonStaticPublicMethods(lambdaClass, declaredMethods);
260        Set<Method> expectedMethods = new HashSet<>();
261        for (Class<?> interfaceClass : expectedInterfaces) {
262            // Obtain methods declared by super-interfaces too.
263            while (interfaceClass != null) {
264                addNonStaticPublicMethods(interfaceClass, expectedMethods);
265                interfaceClass = interfaceClass.getSuperclass();
266            }
267        }
268        assertEquals(expectedMethods.size(), declaredMethods.size());
269
270        // Check the method signatures are compatible.
271        for (Method expectedMethod : expectedMethods) {
272            Method actualMethod =
273                    lambdaClass.getMethod(expectedMethod.getName(),
274                            expectedMethod.getParameterTypes());
275            assertEquals(expectedMethod.getReturnType(), actualMethod.getReturnType());
276        }
277    }
278
279    private static void addNonStaticPublicMethods(Class<?> clazz, Set<Method> methodSet) {
280        for (Method interfaceMethod : clazz.getDeclaredMethods()) {
281            int modifiers = interfaceMethod.getModifiers();
282            if ((!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
283                methodSet.add(interfaceMethod);
284            }
285        }
286    }
287
288    private static void assertNonSerializableLambdaCharacteristics(Object r1) throws Exception {
289        ByteArrayOutputStream baos = new ByteArrayOutputStream();
290        try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
291            os.writeObject(r1);
292            os.flush();
293            fail();
294        } catch (NotSerializableException expected) {
295        }
296    }
297
298    /**
299     * Asserts that necessary conditions hold when there are two lambdas with separate but identical
300     * definitions.
301     */
302    private static void assertMultipleDefinitionCharacteristics(
303            Callable<String> r1, Callable<String> r2) throws Exception {
304
305        // Sanity check that the lambdas do the same thing.
306        assertEquals(r1.call(), r2.call());
307
308        // Unclear if any of this is *guaranteed* to be true.
309
310        // Check the objects are not the same and do not equal. This could influence collection
311        // behavior.
312        assertNotSame(r1, r2);
313        assertTrue(!r1.equals(r2));
314
315        // Two lambdas from different definitions can share the same class or may not.
316        // See JLS 15.27.4.
317    }
318
319    /**
320     * Asserts that necessary conditions hold when there are two lambdas created from the same
321     * definition.
322     */
323    private static void assertMultipleInstanceCharacteristics(
324            Callable<String> r1, Callable<String> r2) throws Exception {
325
326        // Sanity check that the lambdas do the same thing.
327        assertEquals(r1.call(), r2.call());
328
329        // There doesn't appear to be anything else that is safe to assert here. Two lambdas
330        // created from the same definition can be the same, as can their class, but they can also
331        // be different. See JLS 15.27.4.
332    }
333
334    private static void assertGeneralLambdaClassCharacteristics(Object r1) throws Exception {
335        Class<?> lambdaClass = r1.getClass();
336
337        // Lambda objects have classes that have names.
338        assertNotNull(lambdaClass.getName());
339        assertNotNull(lambdaClass.getSimpleName());
340        assertNotNull(lambdaClass.getCanonicalName());
341
342        // Lambda classes are "synthetic classes" that are not arrays.
343        assertFalse(lambdaClass.isAnnotation());
344        assertFalse(lambdaClass.isInterface());
345        assertFalse(lambdaClass.isArray());
346        assertFalse(lambdaClass.isEnum());
347        assertFalse(lambdaClass.isPrimitive());
348        assertTrue(lambdaClass.isSynthetic());
349        assertNull(lambdaClass.getComponentType());
350
351        // Expected modifiers
352        int classModifiers = lambdaClass.getModifiers();
353        assertTrue(Modifier.isFinal(classModifiers));
354
355        // Unexpected modifiers
356        assertFalse(Modifier.isPrivate(classModifiers));
357        assertFalse(Modifier.isPublic(classModifiers));
358        assertFalse(Modifier.isProtected(classModifiers));
359        assertFalse(Modifier.isStatic(classModifiers));
360        assertFalse(Modifier.isSynchronized(classModifiers));
361        assertFalse(Modifier.isVolatile(classModifiers));
362        assertFalse(Modifier.isTransient(classModifiers));
363        assertFalse(Modifier.isNative(classModifiers));
364        assertFalse(Modifier.isInterface(classModifiers));
365        assertFalse(Modifier.isAbstract(classModifiers));
366        assertFalse(Modifier.isStrict(classModifiers));
367
368        // Check the classloader, inheritance hierarchy and package.
369        assertSame(LambdaImplementationTest.class.getClassLoader(), lambdaClass.getClassLoader());
370        assertSame(Object.class, lambdaClass.getSuperclass());
371        assertSame(Object.class, lambdaClass.getGenericSuperclass());
372        assertEquals(LambdaImplementationTest.class.getPackage(), lambdaClass.getPackage());
373
374        // Check the implementation of the non-final public methods that all Objects possess.
375        assertNotNull(r1.toString());
376        assertTrue(r1.equals(r1));
377        assertEquals(System.identityHashCode(r1), r1.hashCode());
378    }
379
380    private static <T> void assertLambdaMethodCharacteristics(T r1, Class<?> samInterfaceClass)
381            throws Exception {
382        // Find the single abstract method on the interface.
383        Method singleAbstractMethod = null;
384        for (Method method : samInterfaceClass.getDeclaredMethods()) {
385            if (Modifier.isAbstract(method.getModifiers())) {
386                singleAbstractMethod = method;
387                break;
388            }
389        }
390        assertNotNull(singleAbstractMethod);
391
392        // Confirm the lambda implements the method as expected.
393        Method implementationMethod = r1.getClass().getMethod(
394                singleAbstractMethod.getName(), singleAbstractMethod.getParameterTypes());
395        assertSame(singleAbstractMethod.getReturnType(), implementationMethod.getReturnType());
396        assertSame(r1.getClass(), implementationMethod.getDeclaringClass());
397        assertFalse(implementationMethod.isSynthetic());
398        assertFalse(implementationMethod.isBridge());
399        assertFalse(implementationMethod.isDefault());
400    }
401
402    private static String staticMethod() {
403        return STATIC_METHOD_RESPONSE;
404    }
405
406    private interface MarkerInterface {
407    }
408
409    private static <T> void assertCallableBehavior(Callable<T> r1, T expectedResult)
410            throws Exception {
411        assertEquals(expectedResult, r1.call());
412
413        Method implCallMethod = r1.getClass().getDeclaredMethod("call");
414        assertEquals(expectedResult, implCallMethod.invoke(r1));
415
416        Method interfaceCallMethod = Callable.class.getDeclaredMethod("call");
417        assertEquals(expectedResult, interfaceCallMethod.invoke(r1));
418    }
419}
420