1 /*
   2  * Copyright (c) 2019, 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 package jdk.jpackage.test;
  24 
  25 import java.lang.reflect.Constructor;
  26 import java.lang.reflect.InvocationTargetException;
  27 import java.lang.reflect.Method;
  28 import java.lang.reflect.Modifier;
  29 import java.util.Arrays;
  30 import java.util.List;
  31 import java.util.Objects;
  32 import java.util.Optional;
  33 import java.util.function.Supplier;
  34 import java.util.stream.Collectors;
  35 import java.util.stream.Stream;
  36 import jdk.jpackage.test.Functional.ThrowingConsumer;
  37 import jdk.jpackage.test.TestInstance.TestDesc;
  38 
  39 class MethodCall implements ThrowingConsumer {
  40 
  41     MethodCall(Object[] instanceCtorArgs, Method method) {
  42         this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
  43                 DEFAULT_CTOR_ARGS);
  44         this.method = method;
  45         this.methodArgs = new Object[0];
  46     }
  47 
  48     MethodCall(Object[] instanceCtorArgs, Method method, Object arg) {
  49         this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
  50                 DEFAULT_CTOR_ARGS);
  51         this.method = method;
  52         this.methodArgs = new Object[]{arg};
  53     }
  54 
  55     TestDesc createDescription() {
  56         var descBuilder = TestDesc.createBuilder().method(method);
  57         if (methodArgs.length != 0) {
  58             descBuilder.methodArgs(methodArgs);
  59         }
  60 
  61         if (ctorArgs.length != 0) {
  62             descBuilder.ctorArgs(ctorArgs);
  63         }
  64 
  65         return descBuilder.get();
  66     }
  67 
  68     Method getMethod() {
  69         return method;
  70     }
  71 
  72     Object newInstance() throws NoSuchMethodException, InstantiationException,
  73             IllegalAccessException, IllegalArgumentException,
  74             InvocationTargetException {
  75         if ((method.getModifiers() & Modifier.STATIC) != 0) {
  76             return null;
  77         }
  78 
  79         Constructor ctor = findRequiredConstructor(method.getDeclaringClass(),
  80                 ctorArgs);
  81         if (ctor.isVarArgs()) {
  82             // Assume constructor doesn't have fixed, only variable parameters.
  83             return ctor.newInstance(new Object[]{ctorArgs});
  84         }
  85 
  86         return ctor.newInstance(ctorArgs);
  87     }
  88 
  89     void checkRequiredConstructor() throws NoSuchMethodException {
  90         if ((method.getModifiers() & Modifier.STATIC) == 0) {
  91             findRequiredConstructor(method.getDeclaringClass(), ctorArgs);
  92         }
  93     }
  94 
  95     private static Constructor findVarArgConstructor(Class type) {
  96         return Stream.of(type.getConstructors()).filter(
  97                 Constructor::isVarArgs).findFirst().orElse(null);
  98     }
  99 
 100     private Constructor findRequiredConstructor(Class type, Object... ctorArgs)
 101             throws NoSuchMethodException {
 102 
 103         Supplier<NoSuchMethodException> notFoundException = () -> {
 104             return new NoSuchMethodException(String.format(
 105                     "No public contructor in %s for %s arguments", type,
 106                     Arrays.deepToString(ctorArgs)));
 107         };
 108 
 109         if (Stream.of(ctorArgs).allMatch(Objects::nonNull)) {
 110             // No `null` in constructor args, take easy path
 111             try {
 112                 return type.getConstructor(Stream.of(ctorArgs).map(
 113                         Object::getClass).collect(Collectors.toList()).toArray(
 114                         Class[]::new));
 115             } catch (NoSuchMethodException ex) {
 116                 // Failed to find ctor that can take the given arguments.
 117                 Constructor varArgCtor = findVarArgConstructor(type);
 118                 if (varArgCtor != null) {
 119                     // There is one with variable number of arguments. Use it.
 120                     return varArgCtor;
 121                 }
 122                 throw notFoundException.get();
 123             }
 124         }
 125 
 126         List<Constructor> ctors = Stream.of(type.getConstructors())
 127                 .filter(ctor -> ctor.getParameterCount() == ctorArgs.length)
 128                 .collect(Collectors.toList());
 129 
 130         if (ctors.isEmpty()) {
 131             // No public constructors that can handle the given arguments.
 132             throw notFoundException.get();
 133         }
 134 
 135         if (ctors.size() == 1) {
 136             return ctors.iterator().next();
 137         }
 138 
 139         // Revisit this tricky case when it will start bothering.
 140         throw notFoundException.get();
 141     }
 142 
 143     @Override
 144     public void accept(Object thiz) throws Throwable {
 145         method.invoke(thiz, methodArgs);
 146     }
 147 
 148     private final Object[] methodArgs;
 149     private final Method method;
 150     private final Object[] ctorArgs;
 151 
 152     final static Object[] DEFAULT_CTOR_ARGS = new Object[0];
 153 }