package org.aspectj.apache.bcel.generic;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (https://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache BCEL" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache BCEL", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <https://www.apache.org/>.
 */

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;

import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.util.ByteSequence;

/**
 * Abstract super class for all Java byte codes.
 *
 * @version $Id: Instruction.java,v 1.10 2011/04/05 15:15:33 aclement Exp $
 * @author <A HREF="mailto:markus.dahm@berlin.de">M. Dahm</A>
 */
public class Instruction implements Cloneable, Serializable, Constants {
	public short opcode = -1;

	public Instruction(short opcode) {
		this.opcode = opcode;
	}

	public void dump(DataOutputStream out) throws IOException {
		out.writeByte(opcode);
	}

	public String getName() {
		return Constants.OPCODE_NAMES[opcode];
	}

	/**
	 * Use with caution, since 'BranchInstruction's have a 'target' reference which is not copied correctly (only basic types are).
	 * This also applies for 'Select' instructions with their multiple branch targets.
	 *
	 * @return (shallow) copy of an instruction
	 */
	// GET RID OF THIS - make it throw an exception and track the callers
	final public Instruction copy() {
		// if overridden correctly can just return 'this' here
		if (InstructionConstants.INSTRUCTIONS[opcode] != null) { // immutable instructions do not need copying
			return this;
		} else {
			Instruction i = null;
			try {// OPTIMIZE is clone the right thing to do here? it is horrible
				i = (Instruction) clone();
			} catch (CloneNotSupportedException e) {
				System.err.println(e);
			}
			return i;
		}
	}

	/**
	 * Read an instruction bytecode from an input stream and return the appropriate object.
	 *
	 * @param file file to read from
	 * @return instruction object being read
	 */
	public static final Instruction readInstruction(ByteSequence bytes) throws IOException {
		boolean wide = false;
		short opcode = (short) bytes.readUnsignedByte();

		if (opcode == Constants.WIDE) {
			wide = true;
			opcode = (short) bytes.readUnsignedByte();
		}

		Instruction constantInstruction = InstructionConstants.INSTRUCTIONS[opcode];

		if (constantInstruction != null) {
			return constantInstruction;
		}

		Instruction obj = null;
		try {
			switch (opcode) {
			case Constants.BIPUSH:
				obj = new InstructionByte(Constants.BIPUSH, bytes.readByte());
				break;
			case Constants.SIPUSH:
				obj = new InstructionShort(Constants.SIPUSH, bytes.readShort());
				break;
			case Constants.LDC:
				obj = new InstructionCP(Constants.LDC, bytes.readUnsignedByte());
				break;
			case Constants.LDC_W:
			case Constants.LDC2_W:
				obj = new InstructionCP(opcode, bytes.readUnsignedShort());
				break;
			case Constants.ILOAD:
			case Constants.LLOAD:
			case Constants.FLOAD:
			case Constants.DLOAD:
			case Constants.ALOAD:
			case Constants.ISTORE:
			case Constants.LSTORE:
			case Constants.FSTORE:
			case Constants.DSTORE:
			case Constants.ASTORE:
				obj = new InstructionLV(opcode, wide ? bytes.readUnsignedShort() : bytes.readUnsignedByte());
				break;
			case Constants.IINC:
				obj = new IINC(wide ? bytes.readUnsignedShort() : bytes.readUnsignedByte(), wide ? bytes.readShort()
						: bytes.readByte(), wide);
				break;
			case Constants.IFNULL:
			case Constants.IFNONNULL:
			case Constants.IFEQ:
			case Constants.IFNE:
			case Constants.IFLT:
			case Constants.IFGE:
			case Constants.IFGT:
			case Constants.IFLE:
			case Constants.IF_ICMPEQ:
			case Constants.IF_ICMPNE:
			case Constants.IF_ICMPLT:
			case Constants.IF_ICMPGE:
			case Constants.IF_ICMPGT:
			case Constants.IF_ICMPLE:
			case Constants.IF_ACMPEQ:
			case Constants.IF_ACMPNE:
			case Constants.GOTO:
			case Constants.JSR:
				obj = new InstructionBranch(opcode, bytes.readShort());
				break;
			case Constants.GOTO_W:
			case Constants.JSR_W:
				obj = new InstructionBranch(opcode, bytes.readInt());
				break;
			case Constants.TABLESWITCH:
				obj = new TABLESWITCH(bytes);
				break;
			case Constants.LOOKUPSWITCH:
				obj = new LOOKUPSWITCH(bytes);
				break;
			case Constants.RET:
				obj = new RET(wide ? bytes.readUnsignedShort() : bytes.readUnsignedByte(), wide);
				break;
			case Constants.NEW:
				obj = new InstructionCP(Constants.NEW, bytes.readUnsignedShort());
				break;
			case Constants.GETSTATIC:
			case Constants.PUTSTATIC:
			case Constants.GETFIELD:
			case Constants.PUTFIELD:
				obj = new FieldInstruction(opcode, bytes.readUnsignedShort());
				break;
			case Constants.INVOKEVIRTUAL:
			case Constants.INVOKESPECIAL:
			case Constants.INVOKESTATIC:
				obj = new InvokeInstruction(opcode, bytes.readUnsignedShort());
				break;
			case Constants.INVOKEINTERFACE:
				obj = new INVOKEINTERFACE(bytes.readUnsignedShort(), bytes.readUnsignedByte(), bytes.readByte());
				break;
			case Constants.INVOKEDYNAMIC:
				obj = new InvokeDynamic(bytes.readUnsignedShort(),bytes.readUnsignedShort());
				break;
			case Constants.NEWARRAY:
				obj = new InstructionByte(Constants.NEWARRAY, bytes.readByte());
				break;
			case Constants.ANEWARRAY:
			case Constants.CHECKCAST:
				obj = new InstructionCP(opcode, bytes.readUnsignedShort());
				break;
			case Constants.INSTANCEOF:
				obj = new InstructionCP(Constants.INSTANCEOF, bytes.readUnsignedShort());
				break;
			case Constants.MULTIANEWARRAY:
				obj = new MULTIANEWARRAY(bytes.readUnsignedShort(), bytes.readByte());
				break;
			default:
				throw new ClassGenException("Illegal opcode detected");
			}
		} catch (ClassGenException e) {
			throw e;
		} catch (Exception e) {
			throw new ClassGenException(e.toString());
		}

		return obj;
	}

	/**
	 * @return Number of words consumed from stack by this instruction, or Constants.UNPREDICTABLE, if this can not be computed
	 *         statically
	 */
	public int consumeStack(ConstantPool cpg) {
		return Constants.CONSUME_STACK[opcode];
	}

	/**
	 * @return Number of words produced onto stack by this instruction, or Constants.UNPREDICTABLE, if this can not be computed
	 *         statically
	 */
	public int produceStack(ConstantPool cpg) {
		return Constants.stackEntriesProduced[opcode];
	}

	public short getOpcode() {
		return opcode;
	}

	public int getLength() {
		// if it is zero, it should have been provided by an overriding implementation of getLength()
		int len = Constants.iLen[opcode];
		assert len != 0;
		// if (len == 0) {
		// throw new IllegalStateException("Length not right for " + getName().toUpperCase());
		// }
		return len;
	}

	/** Some instructions may be reused, so don't do anything by default */
	void dispose() {
	}

	@Override
	public boolean equals(Object other) {
		if (this.getClass() != Instruction.class) {
			throw new RuntimeException("NO WAY " + this.getClass());
		}
		if (!(other instanceof Instruction)) {
			return false;
		}
		return ((Instruction) other).opcode == opcode;

		// IMPLEMENT EQUALS AND HASHCODE IN THE SUBTYPES!

		// Instruction i1 = this;
		// Instruction i2 = (Instruction) that;
		// if (i1.opcode == i2.opcode) {
		// if (i1.isConstantInstruction()) {
		// return i1.getValue().equals(i2.getValue());
		// } else if (i1.isIndexedInstruction()) {
		// return i1.getIndex() == i2.getIndex();
		// } else if (i1.opcode == Constants.NEWARRAY) {
		// return ((InstructionByte) i1).getTypecode() == ((InstructionByte) i2).getTypecode();
		// } else {
		// return true;
		// }
		// }
		//
		// return false;
	}

	@Override
	public int hashCode() {
		if (this.getClass() != Instruction.class) {
			throw new RuntimeException("NO WAY " + this.getClass());
		}
		return opcode * 37;
		// int result = 17 + opcode * 37;
		// if (isConstantInstruction()) {
		// result = 37 * getValue().hashCode() + result;
		// } else if (isIndexedInstruction()) {
		// result = 37 * getIndex() + result;
		// } else if (opcode == Constants.NEWARRAY) {
		// result = 37 * ((InstructionByte) this).getTypecode() + result;
		// }
		// return result;
	}

	public Type getType() {
		return getType(null);
	}

	public Type getType(ConstantPool cp) {
		// if (types[opcode]==null) throw new RuntimeException(getName()+" is not a typed instruction");
		Type t = Constants.types[opcode];
		if (t != null) {
			return t;
		}
		throw new RuntimeException("Do not know type for instruction " + getName() + "(" + opcode + ")");
	}

	public Number getValue() {
		assert (instFlags[opcode] & CONSTANT_INST) == 0;
		// if ((instFlags[opcode] & CONSTANT_INST) == 0) {
		// throw new RuntimeException(getName() + " is not a constant instruction");
		// }
		switch (opcode) {
		case ICONST_M1:
		case ICONST_0:
		case ICONST_1:
		case ICONST_2:
		case ICONST_3:
		case ICONST_4:
		case ICONST_5:
			return opcode - ICONST_0;
		default:
			throw new IllegalStateException("Not implemented yet for " + getName());
		}
	}

	public int getIndex() {
		return -1;
	}

	public void setIndex(int i) {
		throw new IllegalStateException("Shouldnt be asking " + getName().toUpperCase());
	}

	public Object getValue(ConstantPool cpg) {
		throw new IllegalStateException("Shouldnt be asking " + getName().toUpperCase());
	}

	public boolean isLoadInstruction() {
		return (Constants.instFlags[opcode] & LOAD_INST) != 0;
	}

	// remove these from here, leave them in the InstructionLV
	public boolean isASTORE() {
		return false;
	}

	public boolean isALOAD() {
		return false;
	}

	public boolean isStoreInstruction() {
		return (Constants.instFlags[opcode] & STORE_INST) != 0;
	}

	// public boolean containsTarget(InstructionHandle ih) {
	// throw new IllegalStateException("Dont ask!!");
	// }

	public boolean isJsrInstruction() {
		return (Constants.instFlags[opcode] & JSR_INSTRUCTION) != 0;
	}

	public boolean isConstantInstruction() {
		return (Constants.instFlags[opcode] & CONSTANT_INST) != 0;
	}

	public boolean isConstantPoolInstruction() {
		return (Constants.instFlags[opcode] & CP_INST) != 0;
	}

	public boolean isStackProducer() {
		return Constants.stackEntriesProduced[opcode] != 0;
	}

	public boolean isStackConsumer() {
		return Constants.CONSUME_STACK[opcode] != 0;
	}

	public boolean isIndexedInstruction() {
		return (Constants.instFlags[opcode] & INDEXED) != 0;
	}

	public boolean isArrayCreationInstruction() {
		return opcode == NEWARRAY || opcode == ANEWARRAY || opcode == MULTIANEWARRAY;
	}

	public ObjectType getLoadClassType(ConstantPool cpg) {
		assert (Constants.instFlags[opcode] & Constants.LOADCLASS_INST) == 0;
		// if ((Constants.instFlags[opcode] & Constants.LOADCLASS_INST) == 0) {
		// throw new IllegalStateException("This opcode " + opcode + " does not have the property "
		// + Long.toHexString(Constants.LOADCLASS_INST));
		// }
		Type t = getType(cpg);
		if (t instanceof ArrayType) {
			t = ((ArrayType) t).getBasicType();
		}
		return t instanceof ObjectType ? (ObjectType) t : null;
	}

	public boolean isReturnInstruction() {
		return (Constants.instFlags[opcode] & RET_INST) != 0;
	}

	// public boolean isGoto() {
	// return opcode == GOTO || opcode == GOTO_W;
	// }

	public boolean isLocalVariableInstruction() {
		return (Constants.instFlags[opcode] & LV_INST) != 0;
	}

	/**
	 * Long output format: 'name of opcode' "[" 'opcode number' "]" "(" 'length of instruction' ")"
	 */
	public String toString(boolean verbose) {
		if (verbose) {
			StringBuilder sb = new StringBuilder();
			sb.append(getName()).append("[").append(opcode).append("](size").append(Constants.iLen[opcode]).append(")");
			return sb.toString();
		} else {
			return getName();
		}
	}

	@Override
	public String toString() {
		return toString(true);
	}
}
