// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// 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. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
package org.apache.tapestry5.internal.plastic.asm.tree.analysis;

import java.util.List;

import org.apache.tapestry5.internal.plastic.asm.ConstantDynamic;
import org.apache.tapestry5.internal.plastic.asm.Handle;
import org.apache.tapestry5.internal.plastic.asm.Opcodes;
import org.apache.tapestry5.internal.plastic.asm.Type;
import org.apache.tapestry5.internal.plastic.asm.tree.AbstractInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.FieldInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.IntInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.InvokeDynamicInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.LdcInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.MethodInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.MultiANewArrayInsnNode;
import org.apache.tapestry5.internal.plastic.asm.tree.TypeInsnNode;

/**
 * An {@link Interpreter} for {@link BasicValue} values.
 *
 * @author Eric Bruneton
 * @author Bing Ran
 */
public class BasicInterpreter extends Interpreter<BasicValue> implements Opcodes {

  /**
   * Special type used for the {@literal null} literal. This is an object reference type with
   * descriptor 'Lnull;'.
   */
  public static final Type NULL_TYPE = Type.getObjectType("null");

  /**
   * Constructs a new {@link BasicInterpreter} for the latest ASM API version. <i>Subclasses must
   * not use this constructor</i>. Instead, they must use the {@link #BasicInterpreter(int)}
   * version.
   */
  public BasicInterpreter() {
    super(/* latest api = */ ASM9);
    if (getClass() != BasicInterpreter.class) {
      throw new IllegalStateException();
    }
  }

  /**
   * Constructs a new {@link BasicInterpreter}.
   *
   * @param api the ASM API version supported by this interpreter. Must be one of {@link
   *     org.apache.tapestry5.internal.plastic.asm.Opcodes#ASM4}, {@link org.apache.tapestry5.internal.plastic.asm.Opcodes#ASM5}, {@link
   *     org.apache.tapestry5.internal.plastic.asm.Opcodes#ASM6}, {@link org.apache.tapestry5.internal.plastic.asm.Opcodes#ASM7}, {@link
   *     org.apache.tapestry5.internal.plastic.asm.Opcodes#ASM8} or {@link org.apache.tapestry5.internal.plastic.asm.Opcodes#ASM9}.
   */
  protected BasicInterpreter(final int api) {
    super(api);
  }

  @Override
  public BasicValue newValue(final Type type) {
    if (type == null) {
      return BasicValue.UNINITIALIZED_VALUE;
    }
    switch (type.getSort()) {
      case Type.VOID:
        return null;
      case Type.BOOLEAN:
      case Type.CHAR:
      case Type.BYTE:
      case Type.SHORT:
      case Type.INT:
        return BasicValue.INT_VALUE;
      case Type.FLOAT:
        return BasicValue.FLOAT_VALUE;
      case Type.LONG:
        return BasicValue.LONG_VALUE;
      case Type.DOUBLE:
        return BasicValue.DOUBLE_VALUE;
      case Type.ARRAY:
      case Type.OBJECT:
        return BasicValue.REFERENCE_VALUE;
      default:
        throw new AssertionError();
    }
  }

  @Override
  public BasicValue newOperation(final AbstractInsnNode insn) throws AnalyzerException {
    switch (insn.getOpcode()) {
      case ACONST_NULL:
        return newValue(NULL_TYPE);
      case ICONST_M1:
      case ICONST_0:
      case ICONST_1:
      case ICONST_2:
      case ICONST_3:
      case ICONST_4:
      case ICONST_5:
        return BasicValue.INT_VALUE;
      case LCONST_0:
      case LCONST_1:
        return BasicValue.LONG_VALUE;
      case FCONST_0:
      case FCONST_1:
      case FCONST_2:
        return BasicValue.FLOAT_VALUE;
      case DCONST_0:
      case DCONST_1:
        return BasicValue.DOUBLE_VALUE;
      case BIPUSH:
      case SIPUSH:
        return BasicValue.INT_VALUE;
      case LDC:
        Object value = ((LdcInsnNode) insn).cst;
        if (value instanceof Integer) {
          return BasicValue.INT_VALUE;
        } else if (value instanceof Float) {
          return BasicValue.FLOAT_VALUE;
        } else if (value instanceof Long) {
          return BasicValue.LONG_VALUE;
        } else if (value instanceof Double) {
          return BasicValue.DOUBLE_VALUE;
        } else if (value instanceof String) {
          return newValue(Type.getObjectType("java/lang/String"));
        } else if (value instanceof Type) {
          int sort = ((Type) value).getSort();
          if (sort == Type.OBJECT || sort == Type.ARRAY) {
            return newValue(Type.getObjectType("java/lang/Class"));
          } else if (sort == Type.METHOD) {
            return newValue(Type.getObjectType("java/lang/invoke/MethodType"));
          } else {
            throw new AnalyzerException(insn, "Illegal LDC value " + value);
          }
        } else if (value instanceof Handle) {
          return newValue(Type.getObjectType("java/lang/invoke/MethodHandle"));
        } else if (value instanceof ConstantDynamic) {
          return newValue(Type.getType(((ConstantDynamic) value).getDescriptor()));
        } else {
          throw new AnalyzerException(insn, "Illegal LDC value " + value);
        }
      case JSR:
        return BasicValue.RETURNADDRESS_VALUE;
      case GETSTATIC:
        return newValue(Type.getType(((FieldInsnNode) insn).desc));
      case NEW:
        return newValue(Type.getObjectType(((TypeInsnNode) insn).desc));
      default:
        throw new AssertionError();
    }
  }

  @Override
  public BasicValue copyOperation(final AbstractInsnNode insn, final BasicValue value)
      throws AnalyzerException {
    return value;
  }

  @Override
  public BasicValue unaryOperation(final AbstractInsnNode insn, final BasicValue value)
      throws AnalyzerException {
    switch (insn.getOpcode()) {
      case INEG:
      case IINC:
      case L2I:
      case F2I:
      case D2I:
      case I2B:
      case I2C:
      case I2S:
        return BasicValue.INT_VALUE;
      case FNEG:
      case I2F:
      case L2F:
      case D2F:
        return BasicValue.FLOAT_VALUE;
      case LNEG:
      case I2L:
      case F2L:
      case D2L:
        return BasicValue.LONG_VALUE;
      case DNEG:
      case I2D:
      case L2D:
      case F2D:
        return BasicValue.DOUBLE_VALUE;
      case IFEQ:
      case IFNE:
      case IFLT:
      case IFGE:
      case IFGT:
      case IFLE:
      case TABLESWITCH:
      case LOOKUPSWITCH:
      case IRETURN:
      case LRETURN:
      case FRETURN:
      case DRETURN:
      case ARETURN:
      case PUTSTATIC:
        return null;
      case GETFIELD:
        return newValue(Type.getType(((FieldInsnNode) insn).desc));
      case NEWARRAY:
        switch (((IntInsnNode) insn).operand) {
          case T_BOOLEAN:
            return newValue(Type.getType("[Z"));
          case T_CHAR:
            return newValue(Type.getType("[C"));
          case T_BYTE:
            return newValue(Type.getType("[B"));
          case T_SHORT:
            return newValue(Type.getType("[S"));
          case T_INT:
            return newValue(Type.getType("[I"));
          case T_FLOAT:
            return newValue(Type.getType("[F"));
          case T_DOUBLE:
            return newValue(Type.getType("[D"));
          case T_LONG:
            return newValue(Type.getType("[J"));
          default:
            break;
        }
        throw new AnalyzerException(insn, "Invalid array type");
      case ANEWARRAY:
        return newValue(Type.getType("[" + Type.getObjectType(((TypeInsnNode) insn).desc)));
      case ARRAYLENGTH:
        return BasicValue.INT_VALUE;
      case ATHROW:
        return null;
      case CHECKCAST:
        return newValue(Type.getObjectType(((TypeInsnNode) insn).desc));
      case INSTANCEOF:
        return BasicValue.INT_VALUE;
      case MONITORENTER:
      case MONITOREXIT:
      case IFNULL:
      case IFNONNULL:
        return null;
      default:
        throw new AssertionError();
    }
  }

  @Override
  public BasicValue binaryOperation(
      final AbstractInsnNode insn, final BasicValue value1, final BasicValue value2)
      throws AnalyzerException {
    switch (insn.getOpcode()) {
      case IALOAD:
      case BALOAD:
      case CALOAD:
      case SALOAD:
      case IADD:
      case ISUB:
      case IMUL:
      case IDIV:
      case IREM:
      case ISHL:
      case ISHR:
      case IUSHR:
      case IAND:
      case IOR:
      case IXOR:
        return BasicValue.INT_VALUE;
      case FALOAD:
      case FADD:
      case FSUB:
      case FMUL:
      case FDIV:
      case FREM:
        return BasicValue.FLOAT_VALUE;
      case LALOAD:
      case LADD:
      case LSUB:
      case LMUL:
      case LDIV:
      case LREM:
      case LSHL:
      case LSHR:
      case LUSHR:
      case LAND:
      case LOR:
      case LXOR:
        return BasicValue.LONG_VALUE;
      case DALOAD:
      case DADD:
      case DSUB:
      case DMUL:
      case DDIV:
      case DREM:
        return BasicValue.DOUBLE_VALUE;
      case AALOAD:
        return BasicValue.REFERENCE_VALUE;
      case LCMP:
      case FCMPL:
      case FCMPG:
      case DCMPL:
      case DCMPG:
        return BasicValue.INT_VALUE;
      case IF_ICMPEQ:
      case IF_ICMPNE:
      case IF_ICMPLT:
      case IF_ICMPGE:
      case IF_ICMPGT:
      case IF_ICMPLE:
      case IF_ACMPEQ:
      case IF_ACMPNE:
      case PUTFIELD:
        return null;
      default:
        throw new AssertionError();
    }
  }

  @Override
  public BasicValue ternaryOperation(
      final AbstractInsnNode insn,
      final BasicValue value1,
      final BasicValue value2,
      final BasicValue value3)
      throws AnalyzerException {
    return null;
  }

  @Override
  public BasicValue naryOperation(
      final AbstractInsnNode insn, final List<? extends BasicValue> values)
      throws AnalyzerException {
    int opcode = insn.getOpcode();
    if (opcode == MULTIANEWARRAY) {
      return newValue(Type.getType(((MultiANewArrayInsnNode) insn).desc));
    } else if (opcode == INVOKEDYNAMIC) {
      return newValue(Type.getReturnType(((InvokeDynamicInsnNode) insn).desc));
    } else {
      return newValue(Type.getReturnType(((MethodInsnNode) insn).desc));
    }
  }

  @Override
  public void returnOperation(
      final AbstractInsnNode insn, final BasicValue value, final BasicValue expected)
      throws AnalyzerException {
    // Nothing to do.
  }

  @Override
  public BasicValue merge(final BasicValue value1, final BasicValue value2) {
    if (!value1.equals(value2)) {
      return BasicValue.UNINITIALIZED_VALUE;
    }
    return value1;
  }
}
