001/*
002 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.graphbuilderconf;
024
025import java.lang.reflect.*;
026import java.util.*;
027import java.util.stream.*;
028
029import jdk.internal.jvmci.common.*;
030import jdk.internal.jvmci.meta.*;
031import sun.misc.*;
032
033import com.oracle.graal.nodes.*;
034
035/**
036 * An {@link InvocationPlugin} for a method where the implementation of the method is provided by a
037 * {@linkplain #getSubstitute(MetaAccessProvider) substitute} method. A substitute method must be
038 * static even if the substituted method is not.
039 */
040public final class MethodSubstitutionPlugin implements InvocationPlugin {
041
042    private ResolvedJavaMethod cachedSubstitute;
043
044    /**
045     * The class in which the substitute method is declared.
046     */
047    private final Class<?> declaringClass;
048
049    /**
050     * The name of the original and substitute method.
051     */
052    private final String name;
053
054    /**
055     * The parameter types of the substitute method.
056     */
057    private final Class<?>[] parameters;
058
059    private final boolean originalIsStatic;
060
061    /**
062     * Creates a method substitution plugin.
063     *
064     * @param declaringClass the class in which the substitute method is declared
065     * @param name the name of the substitute method
066     * @param parameters the parameter types of the substitute method. If the original method is not
067     *            static, then {@code parameters[0]} must be the {@link Class} value denoting
068     *            {@link InvocationPlugin.Receiver}
069     */
070    public MethodSubstitutionPlugin(Class<?> declaringClass, String name, Class<?>... parameters) {
071        this.declaringClass = declaringClass;
072        this.name = name;
073        this.parameters = parameters;
074        this.originalIsStatic = parameters.length == 0 || parameters[0] != InvocationPlugin.Receiver.class;
075    }
076
077    /**
078     * Creates a method substitution plugin.
079     *
080     * @param declaringClass the class in which the substitute method is declared
081     * @param name the name of the substitute method
082     * @param parameters the parameter types of the substitute method
083     */
084    public MethodSubstitutionPlugin(boolean originalIsStatic, Class<?> declaringClass, String name, Class<?>... parameters) {
085        this.declaringClass = declaringClass;
086        this.name = name;
087        this.parameters = parameters;
088        this.originalIsStatic = originalIsStatic;
089    }
090
091    public boolean inlineOnly() {
092        // Conservatively assume MacroNodes may be used in a substitution
093        return true;
094    }
095
096    /**
097     * Gets the substitute method, resolving it first if necessary.
098     */
099    public ResolvedJavaMethod getSubstitute(MetaAccessProvider metaAccess) {
100        if (cachedSubstitute == null) {
101            cachedSubstitute = metaAccess.lookupJavaMethod(getJavaSubstitute());
102        }
103        return cachedSubstitute;
104    }
105
106    /**
107     * Gets the reflection API version of the substitution method.
108     */
109    Method getJavaSubstitute() throws JVMCIError {
110        Method substituteMethod = lookupSubstitute();
111        int modifiers = substituteMethod.getModifiers();
112        if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) {
113            throw new JVMCIError("Substitution method must not be abstract or native: " + substituteMethod);
114        }
115        if (!Modifier.isStatic(modifiers)) {
116            throw new JVMCIError("Substitution method must be static: " + substituteMethod);
117        }
118        return substituteMethod;
119    }
120
121    /**
122     * Determines if a given method is the substitute method of this plugin.
123     */
124    private boolean isSubstitute(Method m) {
125        if (Modifier.isStatic(m.getModifiers()) && m.getName().equals(name)) {
126            if (parameters.length == m.getParameterCount()) {
127                Class<?>[] mparams = m.getParameterTypes();
128                int start = 0;
129                if (!originalIsStatic) {
130                    start = 1;
131                    if (!mparams[0].isAssignableFrom(parameters[0])) {
132                        return false;
133                    }
134                }
135                for (int i = start; i < mparams.length; i++) {
136                    if (mparams[i] != parameters[i]) {
137                        return false;
138                    }
139                }
140            }
141            return true;
142        }
143        return false;
144    }
145
146    /**
147     * Gets the substitute method of this plugin.
148     */
149    private Method lookupSubstitute() {
150        for (Method m : declaringClass.getDeclaredMethods()) {
151            if (isSubstitute(m)) {
152                return m;
153            }
154        }
155        throw new JVMCIError("No method found specified by %s", this);
156    }
157
158    /**
159     * Resolves a name to a class.
160     *
161     * @param className the name of the class to resolve
162     * @param optional if true, resolution failure returns null
163     * @return the resolved class or null if resolution fails and {@code optional} is true
164     */
165    public static Class<?> resolveClass(String className, boolean optional) {
166        try {
167            // Need to use launcher class path to handle classes
168            // that are not on the boot class path
169            ClassLoader cl = Launcher.getLauncher().getClassLoader();
170            return Class.forName(className, false, cl);
171        } catch (ClassNotFoundException e) {
172            if (optional) {
173                return null;
174            }
175            throw new JVMCIError("Could not resolve type " + className);
176        }
177    }
178
179    @Override
180    public boolean execute(GraphBuilderContext b, ResolvedJavaMethod targetMethod, InvocationPlugin.Receiver receiver, ValueNode[] argsIncludingReceiver) {
181        ResolvedJavaMethod subst = getSubstitute(b.getMetaAccess());
182        if (receiver != null) {
183            receiver.get();
184        }
185        b.intrinsify(targetMethod, subst, argsIncludingReceiver);
186        return true;
187    }
188
189    public StackTraceElement getApplySourceLocation(MetaAccessProvider metaAccess) {
190        Class<?> c = getClass();
191        for (Method m : c.getDeclaredMethods()) {
192            if (m.getName().equals("execute")) {
193                return metaAccess.lookupJavaMethod(m).asStackTraceElement(0);
194            }
195        }
196        throw new JVMCIError("could not find method named \"execute\" in " + c.getName());
197    }
198
199    @Override
200    public String toString() {
201        return String.format("%s[%s.%s(%s)]", getClass().getSimpleName(), declaringClass.getName(), name,
202                        Arrays.asList(parameters).stream().map(c -> c.getSimpleName()).collect(Collectors.joining(", ")));
203    }
204}