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  */
25 package jdk.dynalink.beans.test;
26 
27 import static jdk.dynalink.StandardNamespace.ELEMENT;
28 import static jdk.dynalink.StandardNamespace.METHOD;
29 import static jdk.dynalink.StandardNamespace.PROPERTY;
30 import static jdk.dynalink.StandardOperation.CALL;
31 import static jdk.dynalink.StandardOperation.GET;
32 import static jdk.dynalink.StandardOperation.SET;
33 
34 import java.lang.invoke.MethodHandles;
35 import java.lang.invoke.MethodType;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.Map;
40 import java.util.function.Consumer;
41 import java.util.function.Predicate;
42 import java.util.regex.Pattern;
43 import java.util.stream.Stream;
44 import jdk.dynalink.CallSiteDescriptor;
45 import jdk.dynalink.DynamicLinkerFactory;
46 import jdk.dynalink.Namespace;
47 import jdk.dynalink.NamespaceOperation;
48 import jdk.dynalink.NoSuchDynamicMethodException;
49 import jdk.dynalink.Operation;
50 import jdk.dynalink.beans.StaticClass;
51 import jdk.dynalink.support.SimpleRelinkableCallSite;
52 import org.testng.Assert;
53 import org.testng.annotations.Test;
54 
55 public class BeansLinkerTest {
56     public static class Bean1 {
57         public final int answer = 42;
58 
getName()59         public String getName() {
60             return "bean1";
61         }
62 
someMethod(final String x)63         public String someMethod(final String x) {
64             return x + "-foo";
65         }
66     }
67 
68     @Test
testPublicFieldPropertyUnnamedGetter()69     public static void testPublicFieldPropertyUnnamedGetter() {
70         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals(42, call(op, new Bean1(), "answer")));
71     }
72 
73     @Test
testPublicFieldPropertyNamedGetter()74     public static void testPublicFieldPropertyNamedGetter() {
75         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals(42, call(op.named("answer"), new Bean1())));
76     }
77 
78     @Test
testGetterPropertyUnnamedGetter()79     public static void testGetterPropertyUnnamedGetter() {
80         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals("bean1", call(op, new Bean1(), "name")));
81     }
82 
83     @Test
testGetterPropertyNamedGetter()84     public static void testGetterPropertyNamedGetter() {
85         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals("bean1", call(op.named("name"), new Bean1())));
86     }
87 
88     @Test
testMethodUnnamedGetter()89     public static void testMethodUnnamedGetter() {
90         testGetterPermutations(METHOD, (op) -> Assert.assertEquals("bar-foo", call(call(op, new Bean1(), "someMethod"), new Bean1(), "bar")));
91     }
92 
93     @Test
testMethodNamedGetter()94     public static void testMethodNamedGetter() {
95         testGetterPermutations(METHOD, (op) -> Assert.assertEquals("bar-foo", call(call(op.named("someMethod"), new Bean1()), new Bean1(), "bar")));
96     }
97 
98     private static final Map<String, String> MAP1 = new HashMap<>();
99     static {
100         MAP1.put("foo", "bar");
101     }
102 
103     @Test
testElementUnnamedGetter()104     public static void testElementUnnamedGetter() {
105         testGetterPermutations(ELEMENT, (op) -> Assert.assertEquals("bar", call(op, MAP1, "foo")));
106     }
107 
108     @Test
testElementNamedGetter()109     public static void testElementNamedGetter() {
110         testGetterPermutations(ELEMENT, (op) -> Assert.assertEquals("bar", call(op.named("foo"), MAP1)));
111     }
112 
113     public static class Bean2 {
114         public int answer;
115         private String name;
116 
setName(final String name)117         public void setName(final String name) {
118             this.name = name;
119         }
120     }
121 
122     @Test
testUnnamedFieldSetter()123     public static void testUnnamedFieldSetter() {
124         testSetterPermutations(PROPERTY, (op) -> {
125             final Bean2 bean2 = new Bean2();
126             call(op, bean2, "answer", 12);
127             Assert.assertEquals(bean2.answer, 12);
128         });
129     }
130 
131     @Test
testNamedFieldSetter()132     public static void testNamedFieldSetter() {
133         testSetterPermutations(PROPERTY, (op) -> {
134             final Bean2 bean2 = new Bean2();
135             call(op.named("answer"), bean2, 14);
136             Assert.assertEquals(bean2.answer, 14);
137         });
138     }
139 
140     @Test
testUnnamedPropertySetter()141     public static void testUnnamedPropertySetter() {
142         testSetterPermutations(PROPERTY, (op) -> {
143             final Bean2 bean2 = new Bean2();
144             call(op, bean2, "name", "boo");
145             Assert.assertEquals(bean2.name, "boo");
146         });
147     }
148 
149     @Test
testNamedPropertySetter()150     public static void testNamedPropertySetter() {
151         testSetterPermutations(PROPERTY, (op) -> {
152             final Bean2 bean2 = new Bean2();
153             call(op.named("name"), bean2, "blah");
154             Assert.assertEquals(bean2.name, "blah");
155         });
156     }
157 
158     private static final Pattern GET_ELEMENT_THEN_PROPERTY_PATTERN = Pattern.compile(".*ELEMENT.*PROPERTY.*");
159 
160     @Test
testUnnamedElementAndPropertyGetter()161     public static void testUnnamedElementAndPropertyGetter() {
162         final Map<String, Object> map = new HashMap<>();
163         map.put("empty", true);
164         testGetterPermutations(GET_ELEMENT_THEN_PROPERTY_PATTERN, 4, (op) -> Assert.assertEquals(true, call(op, map, "empty")));
165     }
166 
167     @Test
testNamedElementAndPropertyGetter()168     public static void testNamedElementAndPropertyGetter() {
169         final Map<String, Object> map = new HashMap<>();
170         map.put("empty", true);
171         testGetterPermutations(GET_ELEMENT_THEN_PROPERTY_PATTERN, 4, (op) -> Assert.assertEquals(true, call(op.named("empty"), map)));
172     }
173 
174     private static final Pattern GET_PROPERTY_THEN_ELEMENT_PATTERN = Pattern.compile(".*PROPERTY.*ELEMENT.*");
175 
176     @Test
testUnnamedPropertyAndElementGetter()177     public static void testUnnamedPropertyAndElementGetter() {
178         final Map<String, Object> map = new HashMap<>();
179         map.put("empty", true);
180         testGetterPermutations(GET_PROPERTY_THEN_ELEMENT_PATTERN, 4, (op) -> Assert.assertEquals(false, call(op, map, "empty")));
181     }
182 
183     @Test
testNamedPropertyAndElementGetter()184     public static void testNamedPropertyAndElementGetter() {
185         final Map<String, Object> map = new HashMap<>();
186         map.put("empty", true);
187         testGetterPermutations(GET_PROPERTY_THEN_ELEMENT_PATTERN, 4, (op) -> Assert.assertEquals(false, call(op.named("empty"), map)));
188     }
189 
190     public static class MapWithProperty extends HashMap<String, Object> {
191         private String name;
192 
setName(final String name)193         public void setName(final String name) {
194             this.name = name;
195         }
196     }
197 
198     @Test
testUnnamedPropertyAndElementSetter()199     public static void testUnnamedPropertyAndElementSetter() {
200         final MapWithProperty map = new MapWithProperty();
201         map.put("name", "element");
202 
203         call(SET.withNamespaces(PROPERTY, ELEMENT), map, "name", "property");
204         Assert.assertEquals("property", map.name);
205         Assert.assertEquals("element", map.get("name"));
206 
207         call(SET.withNamespaces(ELEMENT, PROPERTY), map, "name", "element2");
208         Assert.assertEquals("property", map.name);
209         Assert.assertEquals("element2", map.get("name"));
210     }
211 
212     @Test
testMissingMembersAtLinkTime()213     public static void testMissingMembersAtLinkTime() {
214         testPermutations(GETTER_PERMUTATIONS, (op) -> expectNoSuchDynamicMethodException(()-> call(op.named("foo"), new Object())));
215         testPermutations(SETTER_PERMUTATIONS, (op) -> expectNoSuchDynamicMethodException(()-> call(op.named("foo"), new Object(), "newValue")));
216     }
217 
218     @Test
testMissingMembersAtRunTime()219     public static void testMissingMembersAtRunTime() {
220         call(GET.withNamespace(ELEMENT), new ArrayList<>(), "foo");
221         Stream.of(new HashMap(), new ArrayList(), new Object[0]).forEach((receiver) -> {
222             testPermutations(GETTER_PERMUTATIONS, (op) -> { System.err.println(op + " " + receiver.getClass().getName()); Assert.assertNull(call(op, receiver, "foo"));});
223             // No assertion for the setter; we just expect it to silently succeed
224             testPermutations(SETTER_PERMUTATIONS, (op) -> call(op, receiver, "foo", "newValue"));
225         });
226     }
227 
228     public static class A {
229         public static class Inner {}
230     }
231 
232     public static class B extends A {
233         public static class Inner {}
234     }
235 
236     @Test
testInnerClassGetter()237     public static void testInnerClassGetter() {
238         Object inner1 = call(GET.withNamespace(PROPERTY), StaticClass.forClass(A.class), "Inner");
239         Assert.assertTrue(inner1 instanceof StaticClass);
240         Assert.assertEquals(A.Inner.class, ((StaticClass) inner1).getRepresentedClass());
241 
242         Object inner2 = call(GET.withNamespace(PROPERTY), StaticClass.forClass(B.class), "Inner");
243         Assert.assertTrue(inner2 instanceof StaticClass);
244         Assert.assertEquals(B.Inner.class, ((StaticClass) inner2).getRepresentedClass());
245     }
246 
expectNoSuchDynamicMethodException(final Runnable r)247     private static void expectNoSuchDynamicMethodException(final Runnable r) {
248         try {
249             r.run();
250             Assert.fail("Should've thrown NoSuchDynamicMethodException");
251         } catch(final NoSuchDynamicMethodException e) {
252         }
253     }
254 
255     private static NamespaceOperation[] GETTER_PERMUTATIONS = new NamespaceOperation[] {
256         GET.withNamespaces(PROPERTY),
257         GET.withNamespaces(METHOD),
258         GET.withNamespaces(ELEMENT),
259         GET.withNamespaces(PROPERTY, ELEMENT),
260         GET.withNamespaces(PROPERTY, METHOD),
261         GET.withNamespaces(ELEMENT,  PROPERTY),
262         GET.withNamespaces(ELEMENT,  METHOD),
263         GET.withNamespaces(METHOD,   PROPERTY),
264         GET.withNamespaces(METHOD,   ELEMENT),
265         GET.withNamespaces(PROPERTY, ELEMENT,  METHOD),
266         GET.withNamespaces(PROPERTY, METHOD,   ELEMENT),
267         GET.withNamespaces(ELEMENT,  PROPERTY, METHOD),
268         GET.withNamespaces(ELEMENT,  METHOD,   PROPERTY),
269         GET.withNamespaces(METHOD,   PROPERTY, ELEMENT),
270         GET.withNamespaces(METHOD,   ELEMENT,  PROPERTY)
271     };
272 
273     private static NamespaceOperation[] SETTER_PERMUTATIONS = new NamespaceOperation[] {
274         SET.withNamespaces(PROPERTY),
275         SET.withNamespaces(ELEMENT),
276         SET.withNamespaces(PROPERTY, ELEMENT),
277         SET.withNamespaces(ELEMENT, PROPERTY)
278     };
279 
testPermutations(final NamespaceOperation[] ops, final Operation requiredOp, final Namespace requiredNamespace, final int expectedCount, final Consumer<NamespaceOperation> test)280     private static void testPermutations(final NamespaceOperation[] ops, final Operation requiredOp, final Namespace requiredNamespace, final int expectedCount, final Consumer<NamespaceOperation> test) {
281         testPermutationsWithFilter(ops, (op)->NamespaceOperation.contains(op, requiredOp, requiredNamespace), expectedCount, test);
282     }
283 
testPermutations(final NamespaceOperation[] ops, final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test)284     private static void testPermutations(final NamespaceOperation[] ops, final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test) {
285         testPermutationsWithFilter(ops, (op)->regex.matcher(op.toString()).matches(), expectedCount, test);
286     }
287 
testPermutations(final NamespaceOperation[] ops, final Consumer<NamespaceOperation> test)288     private static void testPermutations(final NamespaceOperation[] ops, final Consumer<NamespaceOperation> test) {
289         testPermutationsWithFilter(ops, (op)->true, ops.length, test);
290     }
291 
testPermutationsWithFilter(final NamespaceOperation[] ops, final Predicate<NamespaceOperation> filter, final int expectedCount, final Consumer<NamespaceOperation> test)292     private static void testPermutationsWithFilter(final NamespaceOperation[] ops, final Predicate<NamespaceOperation> filter, final int expectedCount, final Consumer<NamespaceOperation> test) {
293         final int[] counter = new int[1];
294         Stream.of(ops).filter(filter).forEach((op)-> { counter[0]++; test.accept(op); });
295         Assert.assertEquals(counter[0], expectedCount);
296     }
297 
testGetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test)298     private static void testGetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test) {
299         testPermutations(GETTER_PERMUTATIONS, GET, requiredNamespace, 11, test);
300     }
301 
testGetterPermutations(final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test)302     private static void testGetterPermutations(final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test) {
303         testPermutations(GETTER_PERMUTATIONS, regex, expectedCount, test);
304     }
305 
testSetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test)306     private static void testSetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test) {
307         testPermutations(SETTER_PERMUTATIONS, SET, requiredNamespace, 3, test);
308     }
309 
call(final Operation op, final Object... args)310     private static Object call(final Operation op, final Object... args) {
311         try {
312             return new DynamicLinkerFactory().createLinker().link(
313                     new SimpleRelinkableCallSite(new CallSiteDescriptor(
314                             MethodHandles.publicLookup(), op, t(args.length))))
315                             .dynamicInvoker().invokeWithArguments(args);
316         } catch (final Error|RuntimeException e) {
317             throw e;
318         } catch (final Throwable t) {
319             throw new RuntimeException(t);
320         }
321     }
322 
call(final Object... args)323     private static Object call(final Object... args) {
324         return call(CALL, args);
325     }
326 
t(final int argCount)327     private static MethodType t(final int argCount) {
328         return MethodType.methodType(Object.class, Collections.nCopies(argCount, Object.class));
329     }
330 }
331