/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.bytecode;

import com.caucho.util.IntArray;
import com.caucho.util.L10N;

import java.util.ArrayList;

/**
 * Visitor for travelling the code.
 */
public class CodeVisitor {
  static private final L10N L = new L10N(CodeVisitor.class);

  private JavaClass _javaClass;
  protected CodeAttribute _codeAttr;
  private byte []_code;
  protected int _offset;

  public CodeVisitor()
  {
  }

  public CodeVisitor(JavaClass javaClass, CodeAttribute codeAttr)
  {
    init(javaClass, codeAttr);
  }

  public void init(JavaClass javaClass, CodeAttribute codeAttr)
  {
    _javaClass = javaClass;
    _codeAttr = codeAttr;
    _code = codeAttr.getCode();
    _offset = 0;
  }

  /**
   * Returns the java class.
   */
  public JavaClass getJavaClass()
  {
    return _javaClass;
  }
  
  /**
   * Returns the code attribute.
   */
  public CodeAttribute getCodeAttribute()
  {
    return _codeAttr;
  }

  /**
   * Returns the offset.
   */
  public int getOffset()
  {
    return _offset;
  }

  /**
   * Sets the offset.
   */
  public void setOffset(int offset)
  {
    _offset = offset;
  }

  /**
   * Returns the code buffer.
   */
  public byte []getCode()
  {
    return _code;
  }

  /**
   * Returns the exceptions.
   */
  public ArrayList<CodeAttribute.ExceptionItem> getExceptions()
  {
    return _codeAttr.getExceptions();
  }

  /**
   * Returns the opcode at the cursor.
   */
  public int getOpcode()
  {
    int op = getCode()[_offset] & 0xff;

    if (op == WIDE)
      return getCode()[_offset + 1] & 0xff;
    else
      return op; 
  }

  /**
   * Goes to the next opcode.
   */
  public boolean next()
  {
    _offset = nextOffset();

    return _offset >= 0;
  }

  /**
   * Goes to the next opcode.
   */
  protected int nextOffset()
  {
    int opcode = getCode()[_offset] & 0xff;
    
    int length = OP_LEN[opcode];

    switch (opcode) {
    case GOTO:
    case GOTO_W:
    case RET:
    case IRETURN:
    case LRETURN:
    case FRETURN:
    case DRETURN:
    case ARETURN:
    case RETURN:
    case ATHROW:
      return -1;

    case TABLESWITCH:
      {
        int arg = _offset + 1;
        arg += (4 - arg % 4) % 4;

        int low = getInt(arg + 4);
        int high = getInt(arg + 8);

        return arg + 12 + (high - low + 1) * 4;
      }

    case LOOKUPSWITCH:
      {
        return -1;

        /*
        int arg = _offset + 1;
        arg += (4 - arg % 4) % 4;

        int n = getInt(arg + 4);

        int next = arg + 12 + n * 8;

        return next;
        */
      }

    case WIDE:
      {
        int op2 = getCode()[_offset + 1] & 0xff;

        if (op2 == IINC)
          length = 5;
        else
          length = 3;
        break;
      }
    }
    
    if (length < 0 || length > 0x10)
      throw new UnsupportedOperationException(L.l("{0}: can't handle opcode {1}",
                                                  "" + _offset,
                                                  "" + getOpcode()));

    return _offset + length + 1;
  }

  /**
   * Returns true for a simple branch, i.e. a branch with a simple target.
   */
  public boolean isBranch()
  {
    switch (getOpcode()) {
    case IFNULL:
    case IFNONNULL:
    case IFNE:
    case IFEQ:
    case IFLT:
    case IFGE:
    case IFGT:
    case IFLE:
    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 JSR:
    case JSR_W:
    case GOTO:
    case GOTO_W:
      return true;
    }

    return false;
  }

  /**
   * Returns the branch target.
   */
  public int getBranchTarget()
  {
    switch (getOpcode()) {
    case IFNULL:
    case IFNONNULL:
    case IFNE:
    case IFEQ:
    case IFLT:
    case IFGE:
    case IFGT:
    case IFLE:
    case IF_ICMPEQ:
    case IF_ICMPNE:
    case IF_ICMPLT:
    case IF_ICMPLE:
    case IF_ICMPGE:
    case IF_ICMPGT:
    case IF_ACMPEQ:
    case IF_ACMPNE:
    case GOTO:
    case JSR:
      return _offset + getShortArg();
      
    case GOTO_W:
    case JSR_W:
      return _offset + getIntArg();

    default:
      throw new UnsupportedOperationException();
    }
  }

  /**
   * Returns true for a switch.
   */
  public boolean isSwitch()
  {
    switch (getOpcode()) {
    case TABLESWITCH:
    case LOOKUPSWITCH:
      return true;
    default:
      return false;
    }
  }

  /**
   * Returns the branch target.
   */
  public int []getSwitchTargets()
  {
    switch (getOpcode()) {
    case TABLESWITCH:
      {
        int arg = _offset + 1;
        arg += (4 - arg % 4) % 4;

        int low = getInt(arg + 4);
        int high = getInt(arg + 8);

        int []targets = new int[high - low + 2];
        targets[0] = getInt(arg) + _offset;

        for (int i = 0; i <= high - low; i++) {
          targets[i + 1] = getInt(arg + 12 + i * 4) + _offset;
        }

        return targets;
      }
      
    case LOOKUPSWITCH:
      {
        int arg = _offset + 1;
        arg += (4 - arg % 4) % 4;

        int n = getInt(arg + 4);

        int []targets = new int[n + 1];
        targets[0] = getInt(arg) + _offset;

        for (int i = 0; i < n; i++) {
          int off = arg + 8 + i * 8 + 4;

          targets[i + 1] = getInt(off) + _offset;
        }

        return targets;
      }

    default:
      throw new UnsupportedOperationException("getSwitchTargets");
    }
  }

  /**
   * Returns a constant pool item.
   */
  public ConstantPoolEntry getConstantArg()
  {
    switch (getOpcode()) {
    case LDC:
      return _javaClass.getConstantPool().getEntry(getByteArg());
      
    case LDC_W:
      return _javaClass.getConstantPool().getEntry(getShortArg());

    default:
      throw new UnsupportedOperationException();
    }
  }

  /**
   * Reads a byte argument.
   */
  public int getByteArg()
  {
    return getCode()[_offset + 1];
  }

  /**
   * Gets a byte arg
   */
  public int getByteArg(int offset)
  {
    return getByte(_offset + offset);
  }

  /**
   * Sets a byte value.
   */
  public void setByteArg(int offset, int value)
  {
    getCode()[_offset + offset + 0] = (byte) value;
  }

  /**
   * Reads a short argument.
   */
  public int getShortArg()
  {
    return getShort(_offset + 1);
  }

  /**
   * Sets a short value.
   */
  public int getShortArg(int offset)
  {
    return getShort(_offset + offset);
  }

  /**
   * Sets a short value.
   */
  public void setShortArg(int offset, int value)
  {
    setShort(_offset + offset, value);
  }

  /**
   * Sets a short value.
   */
  public void setShort(int offset, int value)
  {
    byte []code = getCode();
    
    code[offset + 0]  = (byte) (value >> 8);
    code[offset + 1]  = (byte) (value);
  }

  /**
   * Reads an integer.
   */
  public int getIntArg()
  {
    return getInt(_offset + 1);
  }

  /**
   * Sets a short value.
   */
  public void setInt(int offset, int value)
  {
    byte []code = getCode();
    
    code[offset + 0]  = (byte) (value >> 24);
    code[offset + 1]  = (byte) (value >> 16);
    code[offset + 2]  = (byte) (value >> 8);
    code[offset + 3]  = (byte) (value);
  }

  /**
   * Reads a byte value.
   */
  public int getByte(int offset)
  {
    return getCode()[offset + 0] & 0xff;
  }

  /**
   * Reads a short value.
   */
  public int getShort(int offset)
  {
    int b0 = getCode()[offset + 0] & 0xff;
    int b1 = getCode()[offset + 1] & 0xff;
    
    return (short) ((b0 << 8) + b1);
  }

  /**
   * Reads an int argument.
   */
  public int getInt(int offset)
  {
    byte []code = getCode();
    
    int b0 = code[offset + 0] & 0xff;
    int b1 = code[offset + 1] & 0xff;
    int b2 = code[offset + 2] & 0xff;
    int b3 = code[offset + 3] & 0xff;
    
    return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
  }

  /**
   * Analyzes the code for a method
   */
  public void analyze(Analyzer analyzer)
    throws Exception
  {
    analyze(analyzer, true);
  }

  /**
   * Analyzes the code for a method
   */
  public void analyze(Analyzer analyzer, boolean allowFlow)
    throws Exception
  {
    analyzeImpl(analyzer, allowFlow, new IntArray(), new IntArray());
  }

  /**
   * Analyzes the code for a method
   */
  protected void analyzeImpl(Analyzer analyzer, boolean allowFlow,
                             IntArray pendingTargets,
                             IntArray completedTargets)
    throws Exception
  {
    setOffset(0);
    
    pendingTargets.add(0);

    ArrayList<CodeAttribute.ExceptionItem> exns;
    exns = getExceptions();

    for (int i = 0; i < exns.size(); i++) {
      CodeAttribute.ExceptionItem exn = exns.get(i);

      pendingTargets.add(exn.getHandler());
    }
    
    analyze(analyzer, allowFlow, pendingTargets, completedTargets);
  }
  
  /**
   * Analyzes the code for a basic block.
   */
  private void analyze(Analyzer analyzer,
                       boolean allowFlow,
                       IntArray pendingTargets,
                       IntArray completedTargets)
    throws Exception
  {
    pending:
    while (pendingTargets.size() > 0) {
      int pc = pendingTargets.pop();

      if (allowFlow) {
        if (completedTargets.contains(pc))
          continue pending;

        completedTargets.add(pc);
      }

      setOffset(pc);

      flow:
      do {
        pc = getOffset();

        if (pc < 0)
          throw new IllegalStateException();
    
        if (! allowFlow) {
          if (completedTargets.contains(pc))
            break flow;

          completedTargets.add(pc);
        }

        if (isBranch()) {
          int targetPC = getBranchTarget();

          if (! pendingTargets.contains(targetPC))
            pendingTargets.add(targetPC);
        }
        else if (isSwitch()) {
          int []switchTargets = getSwitchTargets();

          for (int i = 0; i < switchTargets.length; i++) {
            if (! pendingTargets.contains(switchTargets[i]))
              pendingTargets.add(switchTargets[i]);
          }
        }

        analyzer.analyze(this);
      } while (next());
    }
  }

  public static final int NOP             = 0x00;
  public static final int ACONST_NULL     = 0x01;
  public static final int ICONST_M1       = 0x02;
  public static final int ICONST_0        = 0x03;
  public static final int ICONST_1        = 0x04;
  public static final int ICONST_2        = 0x05;
  public static final int ICONST_3        = 0x06;
  public static final int ICONST_4        = 0x07;
  public static final int ICONST_5        = 0x08;
  public static final int LCONST_0        = 0x09;
  public static final int LCONST_1        = 0x0a;
  public static final int FCONST_0        = 0x0b;
  public static final int FCONST_1        = 0x0c;
  public static final int FCONST_2        = 0x0d;
  public static final int DCONST_0        = 0x0e;
  public static final int DCONST_1        = 0x0f;
  
  public static final int BIPUSH          = 0x10;
  public static final int SIPUSH          = 0x11;
  public static final int LDC             = 0x12;
  public static final int LDC_W           = 0x13;
  public static final int LDC2_W          = 0x14;
  public static final int ILOAD           = 0x15;
  public static final int LLOAD           = 0x16;
  public static final int FLOAD           = 0x17;
  public static final int DLOAD           = 0x18;
  public static final int ALOAD           = 0x19;
  public static final int ILOAD_0         = 0x1a;
  public static final int ILOAD_1         = 0x1b;
  public static final int ILOAD_2         = 0x1c;
  public static final int ILOAD_3         = 0x1d;
  public static final int LLOAD_0         = 0x1e;
  public static final int LLOAD_1         = 0x1f;
  
  public static final int LLOAD_2         = 0x20;
  public static final int LLOAD_3         = 0x21;
  public static final int FLOAD_0         = 0x22;
  public static final int FLOAD_1         = 0x23;
  public static final int FLOAD_2         = 0x24;
  public static final int FLOAD_3         = 0x25;
  public static final int DLOAD_0         = 0x26;
  public static final int DLOAD_1         = 0x27;
  public static final int DLOAD_2         = 0x28;
  public static final int DLOAD_3         = 0x29;
  public static final int ALOAD_0         = 0x2a;
  public static final int ALOAD_1         = 0x2b;
  public static final int ALOAD_2         = 0x2c;
  public static final int ALOAD_3         = 0x2d;
  public static final int IALOAD          = 0x2e;
  public static final int LALOAD          = 0x2f;
  
  public static final int FALOAD          = 0x30;
  public static final int DALOAD          = 0x31;
  public static final int AALOAD          = 0x32;
  public static final int BALOAD          = 0x33;
  public static final int CALOAD          = 0x34;
  public static final int SALOAD          = 0x35;
  public static final int ISTORE          = 0x36;
  public static final int LSTORE          = 0x37;
  public static final int FSTORE          = 0x38;
  public static final int DSTORE          = 0x39;
  public static final int ASTORE          = 0x3a;
  public static final int ISTORE_0        = 0x3b;
  public static final int ISTORE_1        = 0x3c;
  public static final int ISTORE_2        = 0x3d;
  public static final int ISTORE_3        = 0x3e;
  public static final int LSTORE_0        = 0x3f;
  
  public static final int LSTORE_1        = 0x40;
  public static final int LSTORE_2        = 0x41;
  public static final int LSTORE_3        = 0x42;
  public static final int FSTORE_0        = 0x43;
  public static final int FSTORE_1        = 0x44;
  public static final int FSTORE_2        = 0x45;
  public static final int FSTORE_3        = 0x46;
  public static final int DSTORE_0        = 0x47;
  public static final int DSTORE_1        = 0x48;
  public static final int DSTORE_2        = 0x49;
  public static final int DSTORE_3        = 0x4a;
  public static final int ASTORE_0        = 0x4b;
  public static final int ASTORE_1        = 0x4c;
  public static final int ASTORE_2        = 0x4d;
  public static final int ASTORE_3        = 0x4e;
  public static final int IASTORE         = 0x4f;
  
  public static final int LASTORE         = 0x50;
  public static final int FASTORE         = 0x51;
  public static final int DASTORE         = 0x52;
  public static final int AASTORE         = 0x53;
  public static final int BASTORE         = 0x54;
  public static final int CASTORE         = 0x55;
  public static final int SASTORE         = 0x56;
  public static final int POP             = 0x57;
  public static final int POP2            = 0x58;
  public static final int DUP             = 0x59;
  public static final int DUP_X1          = 0x5a;
  public static final int DUP_X2          = 0x5b;
  public static final int DUP2            = 0x5c;
  public static final int DUP2_X1         = 0x5d;
  public static final int DUP2_X2         = 0x5e;
  public static final int SWAP            = 0x5f;
  
  public static final int IADD            = 0x60;
  public static final int LADD            = 0x61;
  public static final int FADD            = 0x62;
  public static final int DADD            = 0x63;
  public static final int ISUB            = 0x64;
  public static final int LSUB            = 0x65;
  public static final int FSUB            = 0x66;
  public static final int DSUB            = 0x67;
  public static final int IMUL            = 0x68;
  public static final int LMUL            = 0x69;
  public static final int FMUL            = 0x6a;
  public static final int DMUL            = 0x6b;
  public static final int IDIV            = 0x6c;
  public static final int LDIV            = 0x6d;
  public static final int FDIV            = 0x6e;
  public static final int DDIV            = 0x6f;
  
  public static final int IREM            = 0x70;
  public static final int LREM            = 0x71;
  public static final int FREM            = 0x72;
  public static final int DREM            = 0x73;
  public static final int INEG            = 0x74;
  public static final int LNEG            = 0x75;
  public static final int FNEG            = 0x76;
  public static final int DNEG            = 0x77;
  public static final int ISHL            = 0x78;
  public static final int LSHL            = 0x79;
  public static final int ISHR            = 0x7a;
  public static final int LSHR            = 0x7b;
  public static final int IUSHR           = 0x7c;
  public static final int LUSHR           = 0x7d;
  public static final int IAND            = 0x7e;
  public static final int LAND            = 0x7f;
  
  public static final int IOR             = 0x80;
  public static final int LOR             = 0x81;
  public static final int IXOR            = 0x82;
  public static final int LXOR            = 0x83;
  public static final int IINC            = 0x84;
  public static final int I2L             = 0x85;
  public static final int I2F             = 0x86;
  public static final int I2D             = 0x87;
  public static final int L2I             = 0x88;
  public static final int L2F             = 0x89;
  public static final int L2D             = 0x8a;
  public static final int F2I             = 0x8b;
  public static final int F2L             = 0x8c;
  public static final int F2D             = 0x8d;
  public static final int D2I             = 0x8e;
  public static final int D2L             = 0x8f;
  
  public static final int D2F             = 0x90;
  public static final int I2B             = 0x91;
  public static final int I2C             = 0x92;
  public static final int I2S             = 0x93;
  public static final int LCMP            = 0x94;
  public static final int FCMPL           = 0x95;
  public static final int FCMPG           = 0x96;
  public static final int DCMPL           = 0x97;
  public static final int DCMPG           = 0x98;
  public static final int IFEQ            = 0x99;
  public static final int IFNE            = 0x9a;
  public static final int IFLT            = 0x9b;
  public static final int IFGE            = 0x9c;
  public static final int IFGT            = 0x9d;
  public static final int IFLE            = 0x9e;
  public static final int IF_ICMPEQ       = 0x9f;
  
  public static final int IF_ICMPNE       = 0xa0;
  public static final int IF_ICMPLT       = 0xa1;
  public static final int IF_ICMPGE       = 0xa2;
  public static final int IF_ICMPGT       = 0xa3;
  public static final int IF_ICMPLE       = 0xa4;
  public static final int IF_ACMPEQ       = 0xa5;
  public static final int IF_ACMPNE       = 0xa6;
  public static final int GOTO            = 0xa7;
  public static final int JSR             = 0xa8;
  public static final int RET             = 0xa9;
  public static final int TABLESWITCH     = 0xaa;
  public static final int LOOKUPSWITCH    = 0xab;
  public static final int IRETURN         = 0xac;
  public static final int LRETURN         = 0xad;
  public static final int FRETURN         = 0xae;
  public static final int DRETURN         = 0xaf;
  
  public static final int ARETURN         = 0xb0;
  public static final int RETURN          = 0xb1;
  public static final int GETSTATIC       = 0xb2;
  public static final int PUTSTATIC       = 0xb3;
  public static final int GETFIELD        = 0xb4;
  public static final int PUTFIELD        = 0xb5;
  public static final int INVOKEVIRTUAL   = 0xb6;
  public static final int INVOKESPECIAL   = 0xb7;
  public static final int INVOKESTATIC    = 0xb8;
  public static final int INVOKEINTERFACE = 0xb9;
  public static final int RESERVED_0      = 0xba;
  public static final int NEW             = 0xbb;
  public static final int NEWARRAY        = 0xbc;
  public static final int ANEWARRAY       = 0xbd;
  public static final int ARRAYLENGTH     = 0xbe;
  public static final int ATHROW          = 0xbf;
  
  public static final int CHECKCAST       = 0xc0;
  public static final int INSTANCEOF      = 0xc1;
  public static final int MONITORENTER    = 0xc2;
  public static final int MONITOREXIT     = 0xc3;
  public static final int WIDE            = 0xc4;
  public static final int MULTIANEWARRAY  = 0xc5;
  public static final int IFNULL          = 0xc6;
  public static final int IFNONNULL       = 0xc7;
  public static final int GOTO_W          = 0xc8;
  public static final int JSR_W           = 0xc9;

  // the reset are reserved

  private static final int OP_LEN[] = {
     0,  0,  0,  0,  0,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 00 */
     1,  2,  1,  2,  2,  1,  1,  1,    1,  1,  0,  0,  0,  0,  0,  0, /* 10 */
     0,  0,  0,  0,  0,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 20 */
     0,  0,  0,  0,  0,  0,  1,  1,    1,  1,  1,  0,  0,  0,  0,  0, /* 30 */
    
     0,  0,  0,  0,  0,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 40 */
     0,  0,  0,  0,  0,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 50 */
     0,  0,  0,  0,  0,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 60 */
     0,  0,  0,  0,  0,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 70 */
    
     0,  0,  0,  0,  2,  0,  0,  0,    0,  0,  0,  0,  0,  0,  0,  0, /* 80 */
     0,  0,  0,  0,  0,  0,  0,  0,    0,  2,  2,  2,  2,  2,  2,  2, /* 90 */
     2,  2,  2,  2,  2,  2,  2,  2,    2,  1, 99, 99,  0,  0,  0,  0, /* a0 */
     0,  0,  2,  2,  2,  2,  2,  2,    2,  4, -1,  2,  1,  2,  0,  0, /* b0 */
    
     2,  2,  0,  0, 99,  3,  2,  2,    4,  4, -1, -1, -1, -1, -1, -1, /* c0 */
    -1, -1, -1, -1, -1, -1, -1, -1,   -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */
    -1, -1, -1, -1, -1, -1, -1, -1,   -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */
    -1, -1, -1, -1, -1, -1, -1, -1,   -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */
  };
}
