content: /**
   * JReversePro - Java Decompiler / Disassembler.
   * Copyright (C) 2008 Karthik Kumar.
   * 
   * Licensed under the Apache License, Version 2.0 (the "License"); 
   * you may not use this file except in compliance with the License. 
   * You may obtain a copy of the License at 
   *  
   *  	http://www.apache.org/licenses/LICENSE-2.0 
   *  
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, 
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
   * See the License for the specific language governing permissions and
   * limitations under the License. 
   * 
   */
  package org.jreversepro.decompile.simulate;
  
  import java.io.ByteArrayInputStream;
  import java.io.DataInputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.logging.Logger;
  
  
  import org.apache.commons.io.IOUtils;
  import org.jreversepro.CustomLoggerFactory;
  import org.jreversepro.ast.expression.Expression;
  import org.jreversepro.jls.JLSConstants;
  import org.jreversepro.jvm.Opcodes;
  import org.jreversepro.jvm.TypeInferrer;
  import org.jreversepro.reflect.Method;
  import org.jreversepro.reflect.instruction.Instruction;
  
  public class SwitchTable {
  
    /**
     * @param method
     *          Reference to current method.
     * @param ins
     *          Instruction that corresponds to a tableswitch or a lookupswitch
     *          instruction.
     * @param gotos
     *          Map of goto statements.
     * 
     * @throws IOException
     *           thrown in case of any error.
     * @throws RevEngineException
     *           if the instruction passed is not a switch opcode.
     */
    public SwitchTable(Method method, Instruction ins, Map<Integer, Integer> gotos)
        throws IOException {
  
      this.method = method;
      insIndex = ins.currentPc;
      cases = new ArrayList<CaseEntry>();
  
      if (ins.opcode == Opcodes.OPCODE_TABLESWITCH) {
        createTableSwitch(ins.args, ins.currentPc, gotos);
      } else if (ins.opcode == Opcodes.OPCODE_LOOKUPSWITCH) {
        createLookupSwitch(ins.args, ins.currentPc, gotos);
      } else {
        throw new IllegalArgumentException("Opcode " + ins.opcode
            + " Not a switch statement");
      }
      datatype = null;
    }
  
    /**
     * @param method
     *          Reference to current method.
     * @param ins
     *          Instruction that corresponds to a tableswitch or a lookupswitch
     *          instruction.
     * @param op1
     *          Operand that is to be used inside the switch statement.
     * @param gotos
     *          Map of goto statements.
     * @throws IOException
     *           thrown in case of any error.
     * @throws RevEngineException
     *           if the instruction passed is not a switch opcode.
     */
    public SwitchTable(Method method, Instruction ins, Expression op1,
        Map<Integer, Integer> gotos) throws IOException {
      this.datatype = op1.getType();
      this.varName = op1.getJLSCode();
  
      // Copy - paste from prev. constructor.
      this.method = method;
      insIndex = ins.currentPc;
      cases = new ArrayList<CaseEntry>();
  
      if (ins.opcode == Opcodes.OPCODE_TABLESWITCH) {
        createTableSwitch(ins.args, ins.currentPc, gotos);
      } else if (ins.opcode == Opcodes.OPCODE_LOOKUPSWITCH) {
        createLookupSwitch(ins.args, ins.currentPc, gotos);
      } else {
        throw new IllegalArgumentException("Opcode " + ins.opcode
            + " Not a switch statement");
      }
      logger.fine("switch datatype " + datatype);
    }
  
    /**
     * @return Returns the default byte of this switch block.
     */
    public int getDefaultByte() {
      return defaultByte;
    }
  
    /**
     * For 'tableswitch' opcode this fills the data structure - JSwitchTable.
     * 
     * @param entries
     *          Bytecode entries that contain the case values and their target
     *          opcodes.
     * @param offset
     *          offset is the index of the current tableswitch instruction into
     *          the method bytecode array.
     * @param gotos
     *          Map of goto statements.
     * @throws IOException
     *           Thrown in case of an i/o error when reading from the bytes.
     */
    private void createTableSwitch(byte[] entries, int offset,
        Map<Integer, Integer> gotos) throws IOException {
      DataInputStream dis = null;
      try {
        dis = new DataInputStream(new ByteArrayInputStream(entries));
        defaultByte = dis.readInt() + offset;
        int lowVal = dis.readInt();
        int highVal = dis.readInt();
  
        Map<Integer, CaseEntry> mapCases = new HashMap<Integer, CaseEntry>();
        for (int i = lowVal; i <= highVal; i++) {
          int curTarget = dis.readInt() + offset;
          String value = TypeInferrer.getValue(String.valueOf(i), this.datatype);
          CaseEntry ent = mapCases.get(Integer.valueOf(curTarget));
          if (ent == null) {
            mapCases.put(Integer.valueOf(curTarget), new CaseEntry(value,
                curTarget));
          } else {
            ent.addValue(value);
          }
        }
        cases = new ArrayList<CaseEntry>(mapCases.values());
      } finally {
        IOUtils.closeQuietly(dis);
      }
      processData(gotos);
    }
  
    /**
     * For 'lookupswitch' opcode this fills the data structure - JSwitchTable.
     * 
     * @param entries
     *          Bytecode entries that contain the case values and their target
     *          opcodes.
     * @param offset
     *          offset is the index of the current lookupswitch instruction into
     *          the method bytecode array.
     * @param gotos
     *          Map of goto statements.
     * 
     * @throws IOException
     *           Thrown in case of an i/o error when reading from the bytes.
     */
    private void createLookupSwitch(byte[] entries, int offset,
        Map<Integer, Integer> gotos) throws IOException {
      DataInputStream dis = null;
      try {
        dis = new DataInputStream(new ByteArrayInputStream(entries));
  
        defaultByte = dis.readInt() + offset;
        int numVal = dis.readInt();
  
        Map<Integer, CaseEntry> mapCases = new HashMap<Integer, CaseEntry>();
  
        for (int i = 0; i < numVal; i++) {
          String value = TypeInferrer.getValue(String.valueOf(dis.readInt()),
              datatype);
          int curTarget = dis.readInt() + offset;
  
          CaseEntry ent = mapCases.get(Integer.valueOf(curTarget));
          if (ent == null) {
            mapCases.put(Integer.valueOf(curTarget), new CaseEntry(value,
                curTarget));
          } else {
            ent.addValue(value);
          }
        }
        cases = new ArrayList<CaseEntry>(mapCases.values());
      } finally {
        IOUtils.closeQuietly(dis);
      }
      processData(gotos);
    }
  
    /**
     * @return Returns the list of cases. Individual elements are JCaseEntry.
     */
    public List<CaseEntry> getCases() {
      return cases;
    }
  
    /**
     * @return Returns the disassembled string for this switch statement block.
     */
    public String disassemble() {
      StringBuilder sb = new StringBuilder("");
      for (int i = 0; i < cases.size(); i++) {
        sb.append("\n\t\t\t" + cases.get(i));
      }
      sb.append("\n\t\t\tDefault Byte " + defaultByte);
      return sb.toString();
    }
  
    /**
     * @param rhsType
     *          Type of the switch variable of this block.
     * @param rhsValue
     *          Value ( name ) of the switch variable for this block.
     */
    public void setTypeValue(String rhsType, String rhsValue) {
      varName = rhsValue;
      datatype = rhsType;
      // dataType could be either int or char.
    }
  
    /**
     * Process the bytecode stream of case values and targets to individual case
     * blocks.
     * 
     * @param gotos
     *          Map of goto statements.
     */
    public void processData(Map<Integer, Integer> gotos) {
      maxTarget = defaultByte;
      if (gotos != null) {
        for (CaseEntry ent : cases) {
          Integer obj = gotos.get(Integer.valueOf(ent.getTarget() - 3));
          if (obj != null) {
            int tempVal = obj.intValue();
            maxTarget = (maxTarget > tempVal) ? maxTarget : tempVal;
          }
        }
        if (maxTarget > defaultByte) {
          boolean targetPresent = false;
          for (int i = 0; i < cases.size() - 1; i++) {
            CaseEntry ent = cases.get(i);
            if (ent.getTarget() == defaultByte) {
              ent.addValue(JLSConstants.DEFAULT);
              // ent.setTarget(defa);
              targetPresent = true;
            }
          }
          if (!targetPresent) {
            cases.add(new CaseEntry(JLSConstants.DEFAULT, defaultByte));
          }
        }
      }
  
      // Sort the entries
      Collections.sort(cases, new CaseComparator());
  
      // Assign endTargets for all of them.
      int i = 0;
      for (; i < cases.size() - 1; i++) {
        CaseEntry ent = cases.get(i);
        CaseEntry entNext = cases.get(i + 1);
        ent.setEndTarget(entNext.getTarget());
      }
      CaseEntry entLast = cases.get(i);
      entLast.setEndTarget(maxTarget);
    }
  
    /**
     * @return Returns a branch entry for this switch statement.
     */
    public BranchEntry getBranchEntry() {
      return new BranchEntry(method, insIndex, maxTarget, maxTarget,
          BranchConstants.TYPE_SWITCH, varName, "", "");
    }
  
    /**
     * Inserts a CaseEntry in the list.
     * 
     * @param caseEntry
     *          Case Entry to be appended.
     */
    public void addCaseEntry(CaseEntry caseEntry) {
      cases.add(caseEntry);
    }
  
    /**
     * @return Returns the Stringified version of this class.
     */
    @Override
    public String toString() {
      return cases.toString();
    }
  
    /**
     * Instruction index of the switch statement.
     */
    int insIndex;
  
    /**
     * List of cases that are available. Individual elements are JCaseEntry.
     */
    List<CaseEntry> cases;
  
    /**
     * DefaultByte for this switch statement.
     */
    int defaultByte;
  
    /** Max Target from this switch statement group * */
    int maxTarget;
  
    /**
     * Name of the variable that is put under switch statement.
     */
    String varName;
  
    /**
     * Datatype of the variable for which switch is used.
     */
    String datatype;
  
    /**
     * Reference to method to which this switch table belongs.
     */
    Method method;
  
    private final Logger logger = CustomLoggerFactory.createLogger();
  
  }
  