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 
  59         public String getName() {
  60             return "bean1";
  61         }
  62 
  63         public String someMethod(final String x) {
  64             return x + "-foo";
  65         }
  66     }
  67 
  68     @Test
  69     public static void testPublicFieldPropertyUnnamedGetter() {
  70         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals(42, call(op, new Bean1(), "answer")));
  71     }
  72 
  73     @Test
  74     public static void testPublicFieldPropertyNamedGetter() {
  75         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals(42, call(op.named("answer"), new Bean1())));
  76     }
  77 
  78     @Test
  79     public static void testGetterPropertyUnnamedGetter() {
  80         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals("bean1", call(op, new Bean1(), "name")));
  81     }
  82 
  83     @Test
  84     public static void testGetterPropertyNamedGetter() {
  85         testGetterPermutations(PROPERTY, (op) -> Assert.assertEquals("bean1", call(op.named("name"), new Bean1())));
  86     }
  87 
  88     @Test
  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
  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
 104     public static void testElementUnnamedGetter() {
 105         testGetterPermutations(ELEMENT, (op) -> Assert.assertEquals("bar", call(op, MAP1, "foo")));
 106     }
 107 
 108     @Test
 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 
 117         public void setName(final String name) {
 118             this.name = name;
 119         }
 120     }
 121 
 122     @Test
 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
 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
 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
 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
 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
 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
 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
 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 
 193         public void setName(final String name) {
 194             this.name = name;
 195         }
 196     }
 197 
 198     @Test
 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
 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
 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
 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 
 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 
 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 
 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 
 288     private static void testPermutations(final NamespaceOperation[] ops, final Consumer<NamespaceOperation> test) {
 289         testPermutationsWithFilter(ops, (op)->true, ops.length, test);
 290     }
 291 
 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 
 298     private static void testGetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test) {
 299         testPermutations(GETTER_PERMUTATIONS, GET, requiredNamespace, 11, test);
 300     }
 301 
 302     private static void testGetterPermutations(final Pattern regex, final int expectedCount, final Consumer<NamespaceOperation> test) {
 303         testPermutations(GETTER_PERMUTATIONS, regex, expectedCount, test);
 304     }
 305 
 306     private static void testSetterPermutations(final Namespace requiredNamespace, final Consumer<NamespaceOperation> test) {
 307         testPermutations(SETTER_PERMUTATIONS, SET, requiredNamespace, 3, test);
 308     }
 309 
 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 
 323     private static Object call(final Object... args) {
 324         return call(CALL, args);
 325     }
 326 
 327     private static MethodType t(final int argCount) {
 328         return MethodType.methodType(Object.class, Collections.nCopies(argCount, Object.class));
 329     }
 330 }