1 /*
   2  * Copyright (c) 2013, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @summary Test locating and invoking default/static method that defined
  27  *          in interfaces and/or in inheritance
  28  * @bug 7184826
  29  * @build helper.Mod helper.Declared DefaultStaticTestData
  30  * @run testng DefaultStaticInvokeTest
  31  * @author Yong Lu
  32  */
  33 
  34 import java.lang.invoke.MethodHandle;
  35 import java.lang.invoke.MethodHandles;
  36 import java.lang.invoke.MethodType;
  37 import java.lang.reflect.Method;
  38 import java.lang.reflect.Modifier;
  39 import java.util.Arrays;
  40 import java.util.HashMap;
  41 import java.util.HashSet;
  42 
  43 import static org.testng.Assert.assertEquals;
  44 import static org.testng.Assert.assertTrue;
  45 import static org.testng.Assert.assertFalse;
  46 import static org.testng.Assert.assertNotNull;
  47 import org.testng.annotations.Test;
  48 
  49 import static helper.Mod.*;
  50 import static helper.Declared.*;
  51 import helper.Mod;
  52 
  53 public class DefaultStaticInvokeTest {
  54 
  55     @Test(dataProvider = "testCasesAll",
  56             dataProviderClass = DefaultStaticTestData.class)
  57     public void testGetMethods(String testTarget, Object param)
  58             throws Exception {
  59         // test the methods retrieved by getMethods()
  60         testMethods(ALL_METHODS, testTarget, param);
  61     }
  62 
  63     @Test(dataProvider = "testCasesAll",
  64             dataProviderClass = DefaultStaticTestData.class)
  65     public void testGetDeclaredMethods(String testTarget, Object param)
  66             throws Exception {
  67         // test the methods retrieved by getDeclaredMethods()
  68         testMethods(DECLARED_ONLY, testTarget, param);
  69     }
  70 
  71     @Test(dataProvider = "testCasesAll",
  72             dataProviderClass = DefaultStaticTestData.class)
  73     public void testMethodInvoke(String testTarget, Object param)
  74             throws Exception {
  75         Class<?> typeUnderTest = Class.forName(testTarget);
  76         MethodDesc[] expectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class);
  77 
  78         // test the method retrieved by Class.getMethod(String, Object[])
  79         for (MethodDesc toTest : expectedMethods) {
  80             String name = toTest.name();
  81             Method m = getTestMethod(typeUnderTest, name, param);
  82             testThisMethod(toTest, m, typeUnderTest, param);
  83         }
  84     }
  85 
  86     @Test(dataProvider = "testCasesAll",
  87             dataProviderClass = DefaultStaticTestData.class)
  88     public void testMethodHandleInvoke(String testTarget, Object param)
  89             throws Throwable {
  90         Class<?> typeUnderTest = Class.forName(testTarget);
  91         MethodDesc[] expectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class);
  92 
  93         for (MethodDesc toTest : expectedMethods) {
  94             String mName = toTest.name();
  95             Mod mod = toTest.mod();
  96             if (mod != STATIC && typeUnderTest.isInterface()) {
  97                 return;
  98             }
  99 
 100             String result = null;
 101             String expectedReturn = toTest.retval();
 102 
 103             MethodHandle methodHandle = getTestMH(typeUnderTest, mName, param);
 104             if (mName.equals("staticMethod")) {
 105                 result = (param == null)
 106                         ? (String) methodHandle.invoke()
 107                         : (String) methodHandle.invoke(param);
 108             } else {
 109                 result = (param == null)
 110                         ? (String) methodHandle.invoke(typeUnderTest.newInstance())
 111                         : (String) methodHandle.invoke(typeUnderTest.newInstance(), param);
 112             }
 113 
 114             assertEquals(result, expectedReturn);
 115         }
 116 
 117     }
 118 
 119     @Test(dataProvider = "testClasses",
 120             dataProviderClass = DefaultStaticTestData.class)
 121     public void testIAE(String testTarget, Object param)
 122             throws ClassNotFoundException {
 123 
 124         Class<?> typeUnderTest = Class.forName(testTarget);
 125         MethodDesc[] expectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class);
 126 
 127         for (MethodDesc toTest : expectedMethods) {
 128             String mName = toTest.name();
 129             Mod mod = toTest.mod();
 130             if (mod != STATIC && typeUnderTest.isInterface()) {
 131                 return;
 132             }
 133             Exception caught = null;
 134             try {
 135                 getTestMH(typeUnderTest, mName, param, true);
 136             } catch (Exception e) {
 137                 caught = e;
 138             }
 139             assertTrue(caught != null);
 140             assertEquals(caught.getClass(), IllegalAccessException.class);
 141         }
 142     }
 143     private static final String[] OBJECT_METHOD_NAMES = {
 144         "equals",
 145         "hashCode",
 146         "getClass",
 147         "notify",
 148         "notifyAll",
 149         "toString",
 150         "wait",
 151         "wait",
 152         "wait",};
 153     private static final String LAMBDA_METHOD_NAMES = "lambda$";
 154     private static final HashSet<String> OBJECT_NAMES = new HashSet<>(Arrays.asList(OBJECT_METHOD_NAMES));
 155     private static final boolean DECLARED_ONLY = true;
 156     private static final boolean ALL_METHODS = false;
 157 
 158     private void testMethods(boolean declaredOnly, String testTarget, Object param)
 159             throws Exception {
 160         Class<?> typeUnderTest = Class.forName(testTarget);
 161         Method[] methods = declaredOnly
 162                 ? typeUnderTest.getDeclaredMethods()
 163                 : typeUnderTest.getMethods();
 164 
 165         MethodDesc[] baseExpectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class);
 166         MethodDesc[] expectedMethods;
 167 
 168         // If only declared filter out non-declared from expected result
 169         if (declaredOnly) {
 170             int nonDeclared = 0;
 171             for (MethodDesc desc : baseExpectedMethods) {
 172                 if (desc.declared() == NO) {
 173                     nonDeclared++;
 174                 }
 175             }
 176             expectedMethods = new MethodDesc[baseExpectedMethods.length - nonDeclared];
 177             int i = 0;
 178             for (MethodDesc desc : baseExpectedMethods) {
 179                 if (desc.declared() == YES) {
 180                     expectedMethods[i++] = desc;
 181                 }
 182             }
 183         } else {
 184             expectedMethods = baseExpectedMethods;
 185         }
 186 
 187         HashMap<String, Method> myMethods = new HashMap<>(methods.length);
 188         for (Method m : methods) {
 189             String mName = m.getName();
 190             // don't add Object methods and method created from lambda expression
 191             if ((!OBJECT_NAMES.contains(mName)) && (!mName.contains(LAMBDA_METHOD_NAMES))) {
 192                 myMethods.put(mName, m);
 193             }
 194         }
 195         assertEquals(expectedMethods.length, myMethods.size());
 196 
 197         for (MethodDesc toTest : expectedMethods) {
 198 
 199             String name = toTest.name();
 200             Method candidate = myMethods.get(name);
 201 
 202             assertNotNull(candidate);
 203             myMethods.remove(name);
 204 
 205             testThisMethod(toTest, candidate, typeUnderTest, param);
 206 
 207         }
 208 
 209         // Should be no methods left since we remove all we expect to see
 210         assertTrue(myMethods.isEmpty());
 211     }
 212 
 213     private void testThisMethod(MethodDesc toTest, Method method,
 214             Class<?> typeUnderTest, Object param) throws Exception {
 215         // Test modifiers, and invoke
 216         Mod mod = toTest.mod();
 217         String expectedReturn = toTest.retval();
 218         switch (mod) {
 219             case STATIC:
 220                 //assert candidate is static
 221                 assertTrue(Modifier.isStatic(method.getModifiers()));
 222                 assertFalse(method.isDefault());
 223 
 224                 // Test invoke it
 225                 assertEquals(tryInvoke(method, null, param), expectedReturn);
 226                 break;
 227             case DEFAULT:
 228                 // if typeUnderTest is a class then instantiate and invoke
 229                 if (!typeUnderTest.isInterface()) {
 230                     assertEquals(tryInvoke(
 231                             method,
 232                             typeUnderTest,
 233                             param),
 234                             expectedReturn);
 235                 }
 236 
 237                 //assert candidate is default
 238                 assertFalse(Modifier.isStatic(method.getModifiers()));
 239                 assertTrue(method.isDefault());
 240                 break;
 241             case REGULAR:
 242                 // if typeUnderTest must be a class
 243                 assertEquals(tryInvoke(
 244                         method,
 245                         typeUnderTest,
 246                         param),
 247                         expectedReturn);
 248 
 249                 //assert candidate is neither default nor static
 250                 assertFalse(Modifier.isStatic(method.getModifiers()));
 251                 assertFalse(method.isDefault());
 252                 break;
 253             case ABSTRACT:
 254                 //assert candidate is neither default nor static
 255                 assertFalse(Modifier.isStatic(method.getModifiers()));
 256                 assertFalse(method.isDefault());
 257                 break;
 258             default:
 259                 assertFalse(true); //this should never happen
 260                 break;
 261         }
 262 
 263     }
 264 
 265     private Object tryInvoke(Method m, Class<?> receiverType, Object param)
 266             throws Exception {
 267         Object receiver = receiverType == null ? null : receiverType.newInstance();
 268         Object result = null;
 269         if (param == null) {
 270             result = m.invoke(receiver);
 271         } else {
 272             result = m.invoke(receiver, param);
 273         }
 274         return result;
 275     }
 276 
 277     private Method getTestMethod(Class clazz, String methodName, Object param)
 278             throws NoSuchMethodException {
 279         Class[] paramsType = (param != null)
 280                 ? new Class[]{Object.class}
 281                 : new Class[]{};
 282         return clazz.getMethod(methodName, paramsType);
 283     }
 284 
 285     private MethodHandle getTestMH(Class clazz, String methodName, Object param)
 286             throws Exception {
 287         return getTestMH(clazz, methodName, param, false);
 288     }
 289 
 290     private MethodHandle getTestMH(Class clazz, String methodName,
 291             Object param, boolean isNegativeTest)
 292             throws Exception {
 293         MethodType mType = (param != null)
 294                 ? MethodType.genericMethodType(1)
 295                 : MethodType.methodType(String.class);
 296         MethodHandles.Lookup lookup = MethodHandles.lookup();
 297         if (!isNegativeTest) {
 298             return methodName.equals("staticMethod")
 299                     ? lookup.findStatic(clazz, methodName, mType)
 300                     : lookup.findVirtual(clazz, methodName, mType);
 301         } else {
 302             return methodName.equals("staticMethod")
 303                     ? lookup.findVirtual(clazz, methodName, mType)
 304                     : lookup.findStatic(clazz, methodName, mType);
 305         }
 306     }
 307 }