/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.runtime.linker;

import java.lang.invoke.MethodHandle;

/**
 * A tuple of method handles, one for dynamically getting function as a property of an object, and another for invoking
 * a function with a given signature. A typical use for this class is to create method handles that can be used to
 * efficiently recreate dynamic invocation of a method on an object from Java code. E.g. if you would have a call site
 * in JavaScript that says
 * <pre>
 *     value = obj.toJSON(key)
 * </pre>
 * then the efficient way to code an exact equivalent of this in Java would be:
 * <pre>
 *     private static final InvokeByName TO_JSON = new InvokeByName("toJSON", Object.class, Object.class, Object.class);
 *     ...
 *     final Object toJSONFn = TO_JSON.getGetter().invokeExact(obj);
 *     value = TO_JSON.getInvoker().invokeExact(toJSONFn, obj, key);
 * </pre>
 * In practice, you can have stronger type assumptions if it makes sense for your code, just remember that you must use
 * the same parameter types as the formal types of the arguments for {@code invokeExact} to work:
 * <pre>
 *     private static final InvokeByName TO_JSON = new InvokeByName("toJSON", ScriptObject.class, Object.class, Object.class);
 *     ...
 *     final ScriptObject sobj = (ScriptObject)obj;
 *     final Object toJSONFn = TO_JSON.getGetter().invokeExact(sobj);
 *     if(toJSONFn instanceof ScriptFunction) {
 *         value = TO_JSON.getInvoker().invokeExact(toJSONFn, sobj, key);
 *     }
 * </pre>
 * Note that in general you will not want to reuse a single instance of this class for implementing more than one call
 * site - that would increase the risk of them becoming megamorphic or otherwise hard to optimize by the JVM. Even if
 * you dynamically invoke a function with the same name from multiple places in your code, it is advisable to create a
 * separate instance of this class for every place.
 */
public final class InvokeByName {
    private final String name;
    private final MethodHandle getter;
    private final MethodHandle invoker;

    /**
     * Creates a getter and invoker for a function of the given name that takes no arguments and has a return type of
     * {@code Object}.
     * @param name the name of the function
     * @param targetClass the target class it is invoked on; e.g. {@code Object} or {@code ScriptObject}.
     */
    public InvokeByName(final String name, final Class<?> targetClass) {
        this(name, targetClass, Object.class);
    }

    /**
     * Creates a getter and invoker for a function of the given name with given parameter types and a given return type
     * of {@code Object}.
     * @param name the name of the function
     * @param targetClass the target class it is invoked on; e.g. {@code Object} or {@code ScriptObject}.
     * @param rtype the return type of the function
     * @param ptypes the parameter types of the function.
     */
    public InvokeByName(final String name, final Class<?> targetClass, final Class<?> rtype, final Class<?>... ptypes) {
        this.name = name;
        getter  = Bootstrap.createDynamicInvoker("dyn:getMethod|getProp|getElem:" + name, Object.class, targetClass);

        final Class<?>[] finalPtypes;
        final int plength = ptypes.length;
        if(plength == 0) {
            finalPtypes = new Class<?>[] { Object.class, targetClass };
        } else {
            finalPtypes = new Class<?>[plength + 2];
            finalPtypes[0] = Object.class;
            finalPtypes[1] = targetClass;
            System.arraycopy(ptypes, 0, finalPtypes, 2, plength);
        }
        invoker = Bootstrap.createDynamicInvoker("dyn:call", rtype, finalPtypes);
    }

    /**
     * Returns the name of the function retrieved through this invoker.
     * @return the name of the function retrieved through this invoker.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the property getter that can be invoked on an object to retrieve the function object that will be
     * subsequently invoked by the invoker returned by {@link #getInvoker()}.
     * @return the property getter method handle for the function.
     */
    public MethodHandle getGetter() {
        return getter;
    }

    /**
     * Returns the function invoker that can be used to invoke a function object previously retrieved by invoking
     * the getter retrieved with {@link #getGetter()} on the target object.
     * @return the invoker method handle for the function.
     */
    public MethodHandle getInvoker() {
        return invoker;
    }
}
