/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.lang.reflect;

import com.android.dex.Dex;
import java.lang.annotation.Annotation;
import libcore.reflect.AnnotationAccess;
import libcore.util.EmptyArray;

/**
 * This class represents methods and constructors.
 * @hide
 */
public final class ArtMethod {

    /** Method's declaring class */
    private Class<?> declaringClass;
    /** Method access flags (modifiers) */
    private int accessFlags;
    /** DexFile index */
    private int methodDexIndex;
    /** Dispatch table entry */
    private int methodIndex;
    /** DexFile offset of CodeItem for this Method */
    private int codeItemOffset;
    /* ART compiler meta-data */
    private int frameSizeInBytes;
    private int coreSpillMask;
    private int fpSpillMask;
    private int mappingTable;
    private int gcMap;
    private int vmapTable;
    /** ART: compiled managed code associated with this Method */
    private int entryPointFromCompiledCode;
    /** ART: entry point from interpreter associated with this Method */
    private int entryPointFromInterpreter;
    /** ART: if this is a native method, the native code that will be invoked */
    private int nativeMethod;
    /* ART: dex cache fast access */
    private String[] dexCacheStrings;
    Class<?>[] dexCacheResolvedTypes;
    private ArtMethod[] dexCacheResolvedMethods;

    /**
     * Only created by art directly.
     */
    private ArtMethod() {}

    Class getDeclaringClass() {
        return declaringClass;
    }

    public int getAccessFlags() {
        return accessFlags;
    }

    int getDexMethodIndex() {
        return methodDexIndex;
    }

    public static String getMethodName(ArtMethod artMethod) {
        artMethod = artMethod.findOverriddenMethodIfProxy();
        Dex dex = artMethod.getDeclaringClass().getDex();
        int nameIndex = dex.nameIndexFromMethodIndex(artMethod.getDexMethodIndex());
        // Note, in the case of a Proxy the dex cache strings are equal.
        return artMethod.getDexCacheString(dex, nameIndex);
    }

    /**
     * Returns true if the given parameters match those of the method in the given order.
     *
     * @hide
     */
    public static boolean equalConstructorParameters(ArtMethod artMethod, Class<?>[] params) {
        Dex dex = artMethod.getDeclaringClass().getDex();
        short[] types = dex.parameterTypeIndicesFromMethodIndex(artMethod.getDexMethodIndex());
        if (types.length != params.length) {
            return false;
        }
        for (int i = 0; i < types.length; i++) {
            if (artMethod.getDexCacheType(dex, types[i]) != params[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if the given parameters match those of this method in the given order.
     *
     * @hide
     */
    public static boolean equalMethodParameters(ArtMethod artMethod, Class<?>[] params) {
        return equalConstructorParameters(artMethod.findOverriddenMethodIfProxy(), params);
    }

    Class<?>[] getParameterTypes() {
        Dex dex = getDeclaringClass().getDex();
        short[] types = dex.parameterTypeIndicesFromMethodIndex(methodDexIndex);
        if (types.length == 0) {
            return EmptyArray.CLASS;
        }
        Class<?>[] parametersArray = new Class[types.length];
        for (int i = 0; i < types.length; i++) {
            // Note, in the case of a Proxy the dex cache types are equal.
            parametersArray[i] = getDexCacheType(dex, types[i]);
        }
        return parametersArray;
    }

    Class<?> getReturnType() {
        Dex dex = declaringClass.getDex();
        int returnTypeIndex = dex.returnTypeIndexFromMethodIndex(methodDexIndex);
        // Note, in the case of a Proxy the dex cache types are equal.
        return getDexCacheType(dex, returnTypeIndex);
    }

    /**
     * Performs a comparison of the parameters to this method with the given parameters.
     *
     * @hide
     */
    int compareParameters(Class<?>[] params) {
        Dex dex = getDeclaringClass().getDex();
        short[] types = dex.parameterTypeIndicesFromMethodIndex(methodDexIndex);
        int length = Math.min(types.length, params.length);
        for (int i = 0; i < length; i++) {
            Class<?> aType = getDexCacheType(dex, types[i]);
            Class<?> bType = params[i];
            if (aType != bType) {
                int comparison = aType.getName().compareTo(bType.getName());
                if (comparison != 0) {
                    return comparison;
                }
            }
        }
        return types.length - params.length;
    }

    Annotation[][] getParameterAnnotations() {
        return AnnotationAccess.getParameterAnnotations(declaringClass, methodDexIndex);
    }

    /**
     * Returns a string from the dex cache, computing the string from the dex file if necessary.
     * Note this method replicates {@link java.lang.Class#getDexCacheString(Dex, int)}, but in
     * Method we can avoid one indirection.
     */
    private String getDexCacheString(Dex dex, int dexStringIndex) {
        String s = (String) dexCacheStrings[dexStringIndex];
        if (s == null) {
            s = dex.strings().get(dexStringIndex).intern();
            dexCacheStrings[dexStringIndex] = s;
        }
        return s;
    }

    /**
     * Returns a resolved type from the dex cache, computing the string from the dex file if
     * necessary. Note this method delegates to {@link java.lang.Class#getDexCacheType(Dex, int)},
     * but in Method we can avoid one indirection.
     */
    private Class<?> getDexCacheType(Dex dex, int dexTypeIndex) {
        Class<?> resolvedType = dexCacheResolvedTypes[dexTypeIndex];
        if (resolvedType == null) {
            resolvedType = declaringClass.getDexCacheType(dex, dexTypeIndex);
        }
        return resolvedType;
    }

    /**
     * Returns the {@code ArtMethod} that this method overrides for
     * proxy methods, otherwise returns this method. Used to determine
     * the interface method overridden by a proxy method (as the proxy
     * method doesn't directly support operations such as {@link
     * Method#getName}).
     */
    ArtMethod findOverriddenMethodIfProxy() {
        if (declaringClass.isProxy()) {
            // Proxy method's declaring class' dex cache refers to that of Proxy. The local cache in
            // Method refers to the original interface's dex cache and is ensured to be resolved by
            // proxy generation.
            return dexCacheResolvedMethods[methodDexIndex];
        }
        return this;
    }
}
