1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package jdk.dynalink.beans.test;
26
27import static jdk.dynalink.StandardNamespace.ELEMENT;
28import static jdk.dynalink.StandardNamespace.METHOD;
29import static jdk.dynalink.StandardNamespace.PROPERTY;
30import static jdk.dynalink.StandardOperation.CALL;
31import static jdk.dynalink.StandardOperation.GET;
32import static jdk.dynalink.StandardOperation.SET;
33
34import java.lang.invoke.MethodHandles;
35import java.lang.invoke.MethodType;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.HashMap;
39import java.util.Map;
40import java.util.function.Consumer;
41import java.util.function.Predicate;
42import java.util.regex.Pattern;
43import java.util.stream.Stream;
44import jdk.dynalink.CallSiteDescriptor;
45import jdk.dynalink.DynamicLinkerFactory;
46import jdk.dynalink.Namespace;
47import jdk.dynalink.NamespaceOperation;
48import jdk.dynalink.NoSuchDynamicMethodException;
49import jdk.dynalink.Operation;
50import jdk.dynalink.support.SimpleRelinkableCallSite;
51import org.testng.Assert;
52import org.testng.annotations.Test;
53
54public class BeansLinkerTest {
55    public static class Bean1 {
56        public final int answer = 42;
57
58        public String getName() {
59            return "bean1";
60        }
61
62        public String someMethod(final String x) {
63            return x + "-foo";
64        }
65    }
66
67    @Test
68    public static void testPublicFieldPropertyUnnamedGetter() {
69        testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals(42, call(op, new Bean1(), "answer")));
70    }
71
72    @Test
73    public static void testPublicFieldPropertyNamedGetter() {
74        testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals(42, call(op.named("answer"), new Bean1())));
75    }
76
77    @Test
78    public static void testGetterPropertyUnnamedGetter() {
79        testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals("bean1", call(op, new Bean1(), "name")));
80    }
81
82    @Test
83    public static void testGetterPropertyNamedGetter() {
84        testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals("bean1", call(op.named("name"), new Bean1())));
85    }
86
87    @Test
88    public static void testMethodUnnamedGetter() {
89        testGetterPermutations(METHOD, (op) -> Assert.assertEquals("bar-foo", call(call(op, new Bean1(), "someMethod"), new Bean1(), "bar")));
90    }
91
92    @Test
93    public static void testMethodNamedGetter() {
94        testGetterPermutations(METHOD, (op) -> Assert.assertEquals("bar-foo", call(call(op.named("someMethod"), new Bean1()), new Bean1(), "bar")));
95    }
96
97    private static final Map<String, String> MAP1 = new HashMap<>();
98    static {
99        MAP1.put("foo", "bar");
100    }
101
102    @Test
103    public static void testElementUnnamedGetter() {
104        testGetterPermutations(ELEMENT, (op) -> Assert.assertEquals("bar", call(op, MAP1, "foo")));
105    }
106
107    @Test
108    public static void testElementNamedGetter() {
109        testGetterPermutations(ELEMENT, (op) -> Assert.assertEquals("bar", call(op.named("foo"), MAP1)));
110    }
111
112    public static class Bean2 {
113        public int answer;
114        private String name;
115
116        public void setName(final String name) {
117            this.name = name;
118        }
119    }
120
121    @Test
122    public static void testUnnamedFieldSetter() {
123        testSetterPermutations(PROPERTY, (op) -> {
124            final Bean2 bean2 = new Bean2();
125            call(op, bean2, "answer", 12);
126            Assert.assertEquals(bean2.answer, 12);
127        });
128    }
129
130    @Test
131    public static void testNamedFieldSetter() {
132        testSetterPermutations(PROPERTY, (op) -> {
133            final Bean2 bean2 = new Bean2();
134            call(op.named("answer"), bean2, 14);
135            Assert.assertEquals(bean2.answer, 14);
136        });
137    }
138
139    @Test
140    public static void testUnnamedPropertySetter() {
141        testSetterPermutations(PROPERTY, (op) -> {
142            final Bean2 bean2 = new Bean2();
143            call(op, bean2, "name", "boo");
144            Assert.assertEquals(bean2.name, "boo");
145        });
146    }
147
148    @Test
149    public static void testNamedPropertySetter() {
150        testSetterPermutations(PROPERTY, (op) -> {
151            final Bean2 bean2 = new Bean2();
152            call(op.named("name"), bean2, "blah");
153            Assert.assertEquals(bean2.name, "blah");
154        });
155    }
156
157    private static final Pattern GET_ELEMENT_THEN_PROPERTY_PATTERN = Pattern.compile(".*ELEMENT.*PROPERTY.*");
158
159    @Test
160    public static void testUnnamedElementAndPropertyGetter() {
161        final Map<String, Object> map = new HashMap<>();
162        map.put("empty", true);
163        testGetterPermutations(GET_ELEMENT_THEN_PROPERTY_PATTERN, 4, (op) -> Assert.assertEquals(true, call(op, map, "empty")));
164    }
165
166    @Test
167    public static void testNamedElementAndPropertyGetter() {
168        final Map<String, Object> map = new HashMap<>();
169        map.put("empty", true);
170        testGetterPermutations(GET_ELEMENT_THEN_PROPERTY_PATTERN, 4, (op) -> Assert.assertEquals(true, call(op.named("empty"), map)));
171    }
172
173    private static final Pattern GET_PROPERTY_THEN_ELEMENT_PATTERN = Pattern.compile(".*PROPERTY.*ELEMENT.*");
174
175    @Test
176    public static void testUnnamedPropertyAndElementGetter() {
177        final Map<String, Object> map = new HashMap<>();
178        map.put("empty", true);
179        testGetterPermutations(GET_PROPERTY_THEN_ELEMENT_PATTERN, 4, (op) -> Assert.assertEquals(false, call(op, map, "empty")));
180    }
181
182    @Test
183    public static void testNamedPropertyAndElementGetter() {
184        final Map<String, Object> map = new HashMap<>();
185        map.put("empty", true);
186        testGetterPermutations(GET_PROPERTY_THEN_ELEMENT_PATTERN, 4, (op) -> Assert.assertEquals(false, call(op.named("empty"), map)));
187    }
188
189    public static class MapWithProperty extends HashMap<String, Object> {
190        private String name;
191
192        public void setName(final String name) {
193            this.name = name;
194        }
195    }
196
197    @Test
198    public static void testUnnamedPropertyAndElementSetter() {
199        final MapWithProperty map = new MapWithProperty();
200        map.put("name", "element");
201
202        call(SET.withNamespaces(PROPERTY, ELEMENT), map, "name", "property");
203        Assert.assertEquals("property", map.name);
204        Assert.assertEquals("element", map.get("name"));
205
206        call(SET.withNamespaces(ELEMENT, PROPERTY), map, "name", "element2");
207        Assert.assertEquals("property", map.name);
208        Assert.assertEquals("element2", map.get("name"));
209    }
210
211    @Test
212    public static void testMissingMembersAtLinkTime() {
213        testPermutations(GETTER_PERMUTATIONS, (op) -> expectNoSuchDynamicMethodException(()-> call(op.named("foo"), new Object())));
214        testPermutations(SETTER_PERMUTATIONS, (op) -> expectNoSuchDynamicMethodException(()-> call(op.named("foo"), new Object(), "newValue")));
215    }
216
217    @Test
218    public static void testMissingMembersAtRunTime() {
219        call(GET.withNamespace(ELEMENT), new ArrayList<>(), "foo");
220        Stream.of(new HashMap(), new ArrayList(), new Object[0]).forEach((receiver) -> {
221            testPermutations(GETTER_PERMUTATIONS, (op) -> { System.err.println(op + " " + receiver.getClass().getName()); Assert.assertNull(call(op, receiver, "foo"));});
222            // No assertion for the setter; we just expect it to silently succeed
223            testPermutations(SETTER_PERMUTATIONS, (op) -> call(op, receiver, "foo", "newValue"));
224        });
225    }
226
227    private static void expectNoSuchDynamicMethodException(final Runnable r) {
228        try {
229            r.run();
230            Assert.fail("Should've thrown NoSuchDynamicMethodException");
231        } catch(final NoSuchDynamicMethodException e) {
232        }
233    }
234
235    private static NamespaceOperation[] GETTER_PERMUTATIONS = new NamespaceOperation[] {
236        GET.withNamespaces(PROPERTY),
237        GET.withNamespaces(METHOD),
238        GET.withNamespaces(ELEMENT),
239        GET.withNamespaces(PROPERTY, ELEMENT),
240        GET.withNamespaces(PROPERTY, METHOD),
241        GET.withNamespaces(ELEMENT,  PROPERTY),
242        GET.withNamespaces(ELEMENT,  METHOD),
243        GET.withNamespaces(METHOD,   PROPERTY),
244        GET.withNamespaces(METHOD,   ELEMENT),
245        GET.withNamespaces(PROPERTY, ELEMENT,  METHOD),
246        GET.withNamespaces(PROPERTY, METHOD,   ELEMENT),
247        GET.withNamespaces(ELEMENT,  PROPERTY, METHOD),
248        GET.withNamespaces(ELEMENT,  METHOD,   PROPERTY),
249        GET.withNamespaces(METHOD,   PROPERTY, ELEMENT),
250        GET.withNamespaces(METHOD,   ELEMENT,  PROPERTY)
251    };
252
253    private static NamespaceOperation[] SETTER_PERMUTATIONS = new NamespaceOperation[] {
254        SET.withNamespaces(PROPERTY),
255        SET.withNamespaces(ELEMENT),
256        SET.withNamespaces(PROPERTY, ELEMENT),
257        SET.withNamespaces(ELEMENT, PROPERTY)
258    };
259
260    private static void testPermutations(final NamespaceOperation[] ops, final Operation requiredOp, final Namespace requiredNamespace, final int expectedCount, final Consumer<NamespaceOperation> test) {
261        testPermutationsWithFilter(ops, (op)->NamespaceOperation.contains(op, requiredOp, requiredNamespace), expectedCount, test);
262    }
263
264    private static void testPermutations(final NamespaceOperation[] ops, final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test) {
265        testPermutationsWithFilter(ops, (op)->regex.matcher(op.toString()).matches(), expectedCount, test);
266    }
267
268    private static void testPermutations(final NamespaceOperation[] ops, final Consumer<NamespaceOperation> test) {
269        testPermutationsWithFilter(ops, (op)->true, ops.length, test);
270    }
271
272    private static void testPermutationsWithFilter(final NamespaceOperation[] ops, final Predicate<NamespaceOperation> filter, final int expectedCount, final Consumer<NamespaceOperation> test) {
273        final int[] counter = new int[1];
274        Stream.of(ops).filter(filter).forEach((op)-> { counter[0]++; test.accept(op); });
275        Assert.assertEquals(counter[0], expectedCount);
276    }
277
278    private static void testGetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test) {
279        testPermutations(GETTER_PERMUTATIONS, GET, requiredNamespace, 11, test);
280    }
281
282    private static void testGetterPermutations(final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test) {
283        testPermutations(GETTER_PERMUTATIONS, regex, expectedCount, test);
284    }
285
286    private static void testSetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test) {
287        testPermutations(SETTER_PERMUTATIONS, SET, requiredNamespace, 3, test);
288    }
289
290    private static Object call(final Operation op, final Object... args) {
291        try {
292            return new DynamicLinkerFactory().createLinker().link(
293                    new SimpleRelinkableCallSite(new CallSiteDescriptor(
294                            MethodHandles.publicLookup(), op, t(args.length))))
295                            .dynamicInvoker().invokeWithArguments(args);
296        } catch (final Error|RuntimeException e) {
297            throw e;
298        } catch (final Throwable t) {
299            throw new RuntimeException(t);
300        }
301    }
302
303    private static Object call(final Object... args) {
304        return call(CALL, args);
305    }
306
307    private static MethodType t(final int argCount) {
308        return MethodType.methodType(Object.class, Collections.nCopies(argCount, Object.class));
309    }
310}
311