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