/*
 * Copyright (c) 2002,2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.shrike.shrikeBT;

public final class NewInstruction extends Instruction {
  private final String type;

  private final short arrayBoundsCount;

  private NewInstruction(short opcode, String type, short arrayBoundsCount) {
    super(opcode);
    this.type = type;
    this.arrayBoundsCount = arrayBoundsCount;
  }

  /**
   * @param type the type of the object that will be returned (in JVM format, e.g.,
   *     [Ljava/lang/String;)
   * @param arrayBoundsCount the number of array dimensions to preconstruct (equal to the number of
   *     integer parameters this instruction expects)
   * @throws IllegalArgumentException if type is null
   */
  public static NewInstruction make(String type, int arrayBoundsCount)
      throws IllegalArgumentException {
    if (type == null) {
      throw new IllegalArgumentException("type is null");
    }
    if (arrayBoundsCount < 0 || arrayBoundsCount > 255) {
      throw new IllegalArgumentException("Too many array bounds: " + arrayBoundsCount);
    } else {
      if (type.length() < arrayBoundsCount + 1) {
        throw new IllegalArgumentException(
            "Not enough array nesting in " + type + " for bounds count " + arrayBoundsCount);
      }
      for (int i = 0; i < arrayBoundsCount; i++) {
        if (type.charAt(i) != '[') {
          throw new IllegalArgumentException(
              "Not enough array nesting in " + type + " for bounds count " + arrayBoundsCount);
        }
      }

      short opcode;

      switch (arrayBoundsCount) {
        case 0:
          opcode = OP_new;
          break;
        case 1:
          char ch = type.charAt(1);
          if (ch != 'L' && ch != '[') {
            // array of primitive type
            opcode = OP_newarray;
          } else {
            opcode = OP_anewarray;
          }
          break;
        default:
          opcode = OP_multianewarray;
          break;
      }
      return new NewInstruction(opcode, type, (short) arrayBoundsCount);
    }
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof NewInstruction) {
      NewInstruction i = (NewInstruction) o;
      return i.type.equals(type) && i.arrayBoundsCount == arrayBoundsCount;
    } else {
      return false;
    }
  }

  public int getArrayBoundsCount() {
    return arrayBoundsCount;
  }

  @Override
  public int hashCode() {
    return 13111143 * type.hashCode() + arrayBoundsCount;
  }

  @Override
  public int getPoppedCount() {
    return arrayBoundsCount;
  }

  @Override
  public String getPushedType(String[] types) {
    return type;
  }

  @Override
  public byte getPushedWordSize() {
    return 1;
  }

  public String getType() {
    return type;
  }

  @Override
  public String toString() {
    return "New(" + type + ',' + arrayBoundsCount + ')';
  }

  @Override
  public void visit(IInstruction.Visitor v) throws IllegalArgumentException {
    if (v == null) {
      throw new IllegalArgumentException();
    }
    v.visitNew(this);
  }

  @Override
  public boolean isPEI() {
    return true;
  }
}
