/*******************************************************************************
 * Copyright (c) 2010, 2014 Jesper Steen Moller and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Jesper Steen Moller - initial API and implementation
 *     IBM Corporation - Bug fixing
 *******************************************************************************/

package org.eclipse.jdt.internal.debug.core.model;

import com.sun.jdi.Method;

/**
 * Class for analysing Java methods while debugging.
 *
 * @author jmoeller2
 *
 */
public class JDIMethod {

	// Known Java byte codes, from the JVM spec
	private static final int ALOAD_0 = 0x2a;

	private static final int ILOAD_1 = 0x1b;
	private static final int LLOAD_1 = 0x1f;
	private static final int FLOAD_1 = 0x23;
	private static final int DLOAD_1 = 0x27;
	private static final int ALOAD_1 = 0x2b;

	private static final int IRETURN = 0xac;
	private static final int LRETURN = 0xad;
	private static final int FRETURN = 0xae;
	private static final int DRETURN = 0xaf;
	private static final int ARETURN = 0xb0;

	private static final int GETFIELD = 0xb4;
	private static final int PUTFIELD = 0xb5;

	private static final int RETURN = 0xb1;

	/**
	 * Determines if the opcode passes in is one of the value return
	 * instructions.
	 *
	 * @param opCode
	 *            opCode to check
	 * @return If <code>opCode</code> is one of 'areturn', 'ireturn', etc.
	 */
	public static final boolean isXReturn(byte opCode) {
		return (opCode & 0xFF) == IRETURN || (opCode & 0xFF) == LRETURN
				|| (opCode & 0xFF) == FRETURN || (opCode & 0xFF) == DRETURN
				|| (opCode & 0xFF) == ARETURN;
	}

	/**
	 * Determines if the opcode passes in is one of the 'loado_1' instruxtions.
	 *
	 * @param opCode
	 *            opCode to check
	 * @return If <code>opCode</code> is one of 'aload_1', 'iload_1', etc.
	 */
	public static final boolean isXLoad1(byte opCode) {
		return (opCode & 0xFF) == ILOAD_1 || (opCode & 0xFF) == LLOAD_1
				|| (opCode & 0xFF) == FLOAD_1 || (opCode & 0xFF) == DLOAD_1
				|| (opCode & 0xFF) == ALOAD_1;
	}

	/**
	 * Determines if the method in question is a simple getter, JavaBean style.
	 *
	 * Simple getters have byte code which look like this, and they start with
	 * "get" or "is":
	 *
	 * 0 aload_0 1 getfield 4 Xreturn
	 *
	 * @param method
	 *            Method to check
	 * @return true if the method is a simple getter
	 */
	public static boolean isGetterMethod(Method method) {
		if (!(method.name().startsWith("get") || method.name().startsWith("is"))) //$NON-NLS-1$ //$NON-NLS-2$
		 {
			return false;
		}

		byte[] bytecodes = method.bytecodes();
		return bytecodes.length == 5 && (bytecodes[0] & 0xFF) == ALOAD_0
				&& (bytecodes[1] & 0xFF) == GETFIELD && isXReturn(bytecodes[4]);
	}

	/**
	 * Determines if the method in question is a simple getter, JavaBean style.
	 *
	 * Simple setters have byte code which look like this, and they start with
	 * "set":
	 *
	 * 0 aload_0 1 Xload_1 2 putfield 5 return
	 *
	 * @param method
	 *            Method to check
	 * @return true if the method is a simple setter
	 */
	public static boolean isSetterMethod(Method method) {
		if (!method.name().startsWith("set")) { //$NON-NLS-1$
			return false;
		}

		byte[] bytecodes = method.bytecodes();
		return bytecodes.length == 6 && (bytecodes[0] & 0xFF) == ALOAD_0
				&& isXLoad1(bytecodes[1]) && (bytecodes[2] & 0xFF) == PUTFIELD
				&& (bytecodes[5] & 0xFF) == RETURN;
	}
}
