/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.  The Android Open Source
 * Project designates this particular file as subject to the "Classpath"
 * exception as provided by The Android Open Source Project 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.
 */

package java.lang.invoke;

import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

// Android-changed: Android specific implementation.
// The whole class was implemented from scratch for the Android runtime based
// on the specification of the MethodHandle class.
// The code does not originate from upstream OpenJDK.
/**
 * A method handle that's directly associated with an ArtField or an ArtMethod and
 * specifies no additional transformations.
 *
 * @hide
 */
public class MethodHandleImpl extends MethodHandle implements Cloneable {
    private HandleInfo info;

    MethodHandleImpl(long artFieldOrMethod, int handleKind, MethodType type) {
        super(artFieldOrMethod, handleKind, type);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    MethodHandleInfo reveal() {
        if (info == null) {
            final Member member = getMemberInternal();
            info = new HandleInfo(member, this);
        }

        return info;
    }

    /**
     * Materialize a member from this method handle's ArtField or ArtMethod pointer.
     */
    public native Member getMemberInternal();

    /**
     * Implementation of {@code MethodHandleInfo} in terms of the handle being cracked
     * and its corresponding {@code java.lang.reflect.Member}.
     */
    static class HandleInfo implements MethodHandleInfo {
        private final Member member;
        private final MethodHandle handle;

        HandleInfo(Member member, MethodHandle handle) {
            this.member = member;
            this.handle = handle;
        }

        @Override
        public int getReferenceKind() {
            switch (handle.getHandleKind()) {
                case INVOKE_VIRTUAL: {
                    if (member.getDeclaringClass().isInterface()) {
                        return REF_invokeInterface;
                    } else {
                        return REF_invokeVirtual;
                    }
                }

                case INVOKE_DIRECT: {
                    if (member instanceof Constructor) {
                        return REF_newInvokeSpecial;
                    } else {
                        return REF_invokeSpecial;
                    }
                }

                case INVOKE_SUPER:
                    return MethodHandleInfo.REF_invokeSpecial;
                case INVOKE_STATIC:
                    return MethodHandleInfo.REF_invokeStatic;
                case IGET:
                    return MethodHandleInfo.REF_getField;
                case IPUT:
                    return MethodHandleInfo.REF_putField;
                case SGET:
                    return MethodHandleInfo.REF_getStatic;
                case SPUT:
                    return MethodHandleInfo.REF_putStatic;
                default:
                    throw new AssertionError("Unexpected handle kind: " + handle.getHandleKind());
            }
        }

        @Override
        public Class<?> getDeclaringClass() {
            return member.getDeclaringClass();
        }

        @Override
        public String getName() {
            if (member instanceof Constructor) {
                return "<init>";
            }

            return member.getName();
        }

        @Override
        public MethodType getMethodType() {
            // The "nominal" type of a cracked method handle is the same as the type
            // of the handle itself, except in the cases enumerated below.
            MethodType handleType = handle.type();

            boolean omitLeadingParam = false;

            // For constructs, the return type is always void.class, and not the type of
            // the object returned. We also need to omit the leading reference, which is
            // nominally the type of the object being constructed.
            if (member instanceof Constructor) {
                handleType = handleType.changeReturnType(void.class);
                omitLeadingParam = true;
            }

            // For instance field gets/puts and instance method gets/puts, we omit the
            // leading reference parameter to |this|.
            switch (handle.getHandleKind()) {
                case IGET:
                case IPUT:
                case INVOKE_INTERFACE:
                case INVOKE_DIRECT:
                case INVOKE_VIRTUAL:
                case INVOKE_SUPER:
                    omitLeadingParam = true;
            }

            return omitLeadingParam ? handleType.dropParameterTypes(0, 1) : handleType;
        }

        @Override
        public <T extends Member> T reflectAs(Class<T> expected, MethodHandles.Lookup lookup) {
            try {
                final Class declaringClass = member.getDeclaringClass();
                if (Modifier.isNative(getModifiers()) &&
                        (MethodHandle.class.isAssignableFrom(declaringClass)
                                || VarHandle.class.isAssignableFrom(declaringClass))) {
                    if (member instanceof Method) {
                        Method m = (Method) member;
                        if (m.isVarArgs()) {
                            // Signature-polymorphic methods should not be reflected as there
                            // is no support for invoking them via reflection.
                            //
                            // We've identified this method as signature-polymorphic due to
                            // its flags (var-args and native) and its class.
                            throw new IllegalArgumentException(
                                    "Reflecting signature polymorphic method");
                        }
                    }
                }
                lookup.checkAccess(
                        declaringClass, declaringClass, member.getModifiers(), member.getName());
            } catch (IllegalAccessException exception) {
                throw new IllegalArgumentException("Unable to access member.", exception);
            }

            return (T) member;
        }

        @Override
        public int getModifiers() {
            return member.getModifiers();
        }

        @Override
        public String toString() {
            return MethodHandleInfo.toString(
                    getReferenceKind(), getDeclaringClass(), getName(), getMethodType());
        }
    }
}
