content: /**
   * @(#)BranchTable.java
   * 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.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Vector;
  import java.util.logging.Logger;
  
  import org.jreversepro.CustomLoggerFactory;
  import org.jreversepro.jvm.Opcodes;
  import org.jreversepro.reflect.Method;
  import org.jreversepro.reflect.ExceptionThrown;
  import org.jreversepro.reflect.instruction.Instruction;
  
  
  /**
   * BranchTable manages the objects of GotoEntry and BranchEntry.
   * 
   * @author Karthik Kumar
   */
  public class BranchTable implements BranchConstants, Opcodes {
  
    /**
     * @param method
     *          Method reference.
     */
    public BranchTable(Method method) {
      mJSRTarget = new Vector<Integer>();
      mMonitor = new HashMap<Integer, String>();
      branches = new Vector<BranchEntry>();
      switches = new Vector<Instruction>();
      gotos = new HashMap<Integer, Integer>();
      this.method = method;
    }
  
    /**
     * Setter method for the branch tables.
     * 
     * @param aBranches
     *          Branches to be added.
     */
    public void setTables(List<BranchEntry> aBranches) {
      branches.addAll(aBranches);
    }
  
    /**
     * Getter method for goto tables.
     * 
     * @return Map of goto table entries. key - goto pc. value - target of that
     *         goto table.
     */
    public Map<Integer, Integer> getGotoTable() {
      return gotos;
    }
  
    /**
     * Adds a Goto entry to the internal data structure.
     * 
     * @param startPc
     *          StartPc of the goto statement.
     * @param targetPc
     *          TargetPc of the goto statement.
     */
    public void addGotoEntry(int startPc, int targetPc) {
      gotos.put(Integer.valueOf(startPc), Integer.valueOf(targetPc));
    }
  
  
  
    /**
     * Adds a new branch entry to the list of branches.
     * 
     * @param ent
     *          branch entry to be added.
     */
    public void add(BranchEntry ent) {
      branches.add(ent);
    }
  
    /**
     * Checks if the Pc passed as argument is the target for any JSR instructions.
     * 
     * @param currPc
     *          Pc for which it is to be checked if it is the target for any JSR
     *          instruction.
     * @return true, if there exists a JSR instruction with its target currPc.
     *         false, otherwise.
     */
    public boolean isJSRTarget(int currPc) {
      return mJSRTarget.contains(Integer.valueOf(currPc));
    }
  
    /**
     * This adds the pc given as input as a JSR target.
     * 
     * @param targetPc
     *          TargetPc for a JSR instruction that is to be added to the internal
     *          data structure ( list ).
     */
    public void addJSRPc(int targetPc) {
      Integer intPc = Integer.valueOf(targetPc);
      if (!mJSRTarget.contains(intPc)) {
        mJSRTarget.add(intPc);
      }
    }
  
    /**
     * When a RET instruction is encountered we add a branch with the last element
     * of the JSR target lists. JSR instructions signify 'synchronized' and
     * catch..all blocks.
     * 
     * @param retPc
     *          PC of the instruction which is a RET.
     */
    public void addRetPc(int retPc) {
      int startPc = mJSRTarget.lastElement().intValue();
      branches.add(new BranchEntry(method, startPc, startPc, retPc, TYPE_JSR, "",
          "", ""));
    }
  
    /**
     * This sorts the list containing branches such that no branch overlaps with
     * the one previously existing. See JBranchComparator for more details.
     */
    public void sort() {
      Collections.sort(branches, new BranchComparator());
    }
  
    /**
     * Adds a monitor Pc.
     * 
     * @param aMonitorPc
     *          Pc that is monitorenter.
     * @param aMonObject
     *          Object that is 'monitored'. In the sense object for which lock is
     *          obtained before entering a 'synchronized' object.
     */
    public void addMonitorPc(int aMonitorPc, String aMonObject) {
      mMonitor.put(Integer.valueOf(aMonitorPc), aMonObject);
    }
  
    /**
     * Returns the monitor type for the monitor that begins with Pc.
     * 
     * @param monitorBeginPc
     *          Pc that begins with the monitor.
     * @return monitor object associated with this branch.
     */
    public String doesMonitorBegin(int monitorBeginPc) {
      return mMonitor.get(Integer.valueOf(monitorBeginPc));
    }
  
    /**
     * Identifies the else..if and else branches. Identifies catch.. branches.
     * 
     * @throws RevEngineException
     *           Thrown in case of any error.
     */
    public void identifyMoreBranches() {
  
      for (BranchEntry jbe : branches) {
        int gotoStartPc = jbe.getEndBlockPc() - 3;
        int gotoNextPc = gotoStartPc + 3;
        Integer obj = gotos.get( Integer.valueOf(gotoStartPc));
        switch (jbe.getType()) {
        case TYPE_IF:
        case TYPE_ELSE_IF:
          if (obj != null) {
            // Before adding else, check for else if.
            int gotoTargetPc = obj.intValue();
            if (gotoTargetPc - gotoStartPc == 3) {
              break;
            }
            BranchEntry elsif = contains(startsWith(gotoNextPc), TYPE_IF);
  
            if (elsif == null) {
              BranchEntry caseEntry = contains(startsWith(gotoNextPc), TYPE_CASE);
              if (caseEntry == null) {
                BranchEntry elseEntry = new BranchEntry(method, gotoNextPc,
                    gotoNextPc, gotoTargetPc, TYPE_ELSE, jbe.opr1, jbe.opr2,
                    jbe.operator);
                branches.add(elseEntry);
              }
            } else {
              elsif.setType(TYPE_ELSE_IF);
            }
          }
          break;
        case TYPE_DO_WHILE:
          if (gotos.containsValue( Integer.valueOf(jbe.getStartPc()))) {
            jbe.setType(TYPE_WHILE);
          }
          break;
        }
      }
    }
  
    /**
     * Adds the switch entries and the case entries under the same to the branch
     * table.
     * 
     * @param switchEntry
     *          switch table containing entries about switch statements.
     */
    public void addSwitch(SwitchTable switchEntry) {
      int defaultByte = switchEntry.getDefaultByte();
      int maxTarget = defaultByte;
      List<CaseEntry> enumCases = switchEntry.getCases();
  
      logger.fine("No: Case Entries " + enumCases.size());
      for (CaseEntry singleCase : enumCases) {
        int caseTarget = singleCase.getTarget();
        int endCase = singleCase.getEndTarget();
  
        List<String> caseValues = singleCase.getValues();
        StringBuilder sb = new StringBuilder();
        for (String singleCaseValue : caseValues) {
          sb.append(singleCaseValue + ",");
        }
        BranchEntry ent = new BranchEntry(method, caseTarget, caseTarget,
            endCase, TYPE_CASE, sb.toString(), "", "");
        branches.add(ent);
      }
      branches.add(switchEntry.getBranchEntry());
    }
  
    /**
     * List of JException entries.
     * 
     * @param excTryTable
     *          Individual entries being JException.
     */
    public void addTryBlocks(List<ExceptionThrown> excTryTable) {
      for (ExceptionThrown exc : excTryTable) {
        int insIndex = exc.getStartPc();
        if (insIndex == -1) {
          continue;
        }
  
        int endPc = exc.getEndPc();
        String syncLock = doesMonitorBegin(insIndex - 1);
        if (syncLock != null) {
          branches.add(new BranchEntry(method, insIndex, insIndex, endPc,
              TYPE_SYNC, syncLock, "", ""));
        } else {
          branches.add(new BranchEntry(method, insIndex, insIndex, endPc, (exc
              .isAny()) ? TYPE_TRY_ANY : TYPE_TRY, "", "", ""));
        }
      }
    }
  
    /**
     * For the given pc return the target of the instruction. The instruction is a
     * goto statement.
     * 
     * @param startPc
     *          Start Pc.
     * @return the TargetPc for the goto instruction at the startPc
     */
    public int findGotoTarget(int startPc) {
      Integer gotoStartPc = Integer.valueOf(startPc);
      Integer obj = gotos.get(gotoStartPc);
      if (obj == null) {
        return -1;
      } else {
        return obj.intValue();
      }
    }
  
    /**
     * Returns the list of branches that starts with the mentioned aInsIndex.
     * 
     * @param aInsIndex
     *          Instruction index.
     * @return List of JBranchEntry - list of branches that starts with the
     *         mentioned instruction index.
     * @throws RevEngineException
     *           thrown in case of an error.
     */
    public List<BranchEntry> startsWith(int aInsIndex) {
  
      List<BranchEntry> branchEntries = new ArrayList<BranchEntry>();
      for (BranchEntry jbe : branches) {
        if (jbe.doesStartWith(aInsIndex)) {
          branchEntries.add(jbe);
        }
      }
      return branchEntries;
    }
  
    /**
     * Delete the branch that corresponds to a else .. branch starting with the
     * given Pc
     * 
     * @param startElse
     *          PC for which the else statement is to be deleted.
     */
    public void deleteElse(int startElse) {
      for (int i = 0; i < branches.size(); i++) {
        BranchEntry jbe = branches.get(i);
        if (jbe.getType() == TYPE_ELSE && jbe.getStartPc() == startElse) {
          branches.remove(i);
        }
      }
    }
  
    /**
     * Returns the first branch in the mentioned branchlist that matches the
     * particular type.
     * 
     * @param listBranchEntries
     *          list of branch entries.
     * @param type
     *          Type that is to be searched for.
     * @return first branch entry that matches the type mentioned in the list
     *         given.
     */
    public static BranchEntry contains(List<BranchEntry> listBranchEntries,
        int type) {
      if (listBranchEntries.size() == 0) {
        return null;
      }
      for (final BranchEntry ent : listBranchEntries) {
        if (ent.getType() == type) {
          return ent;
        }
      }
      return null;
    }
  
    /**
     * @param byteIns
     *          List of Instructions.
     * @return null.
     */
    private Instruction getNextGoto(List<Instruction> byteIns) {
      return null;
    }
  
    /**
     * @param byteIns
     *          BytecodeInstruction List.
     * @param start
     *          StartPc.
     * @param end
     *          EndPc.
     * @return Returns a JInstruction reference.
     */
    public Instruction findGotoIns(List<Instruction> byteIns, int start, int end) {
      int i;
      for (i = 0; i < byteIns.size(); i++) {
        if (byteIns.get(i).currentPc == start) {
          break;
        }
      }
      Instruction ins = byteIns.get(i);
      while (ins != null && ins.opcode != OPCODE_GOTO
          && ins.opcode != OPCODE_GOTOW) {
        if (ins.currentPc == end) {
          ins = null;
          break;
        } else if (ins.opcode == OPCODE_RETURN) {
          // TODO: Revisit this code
          // ins = byteIns.get(ins.position + 1);
          break;
        }
        // TODO: Revisit this piece of code.
        // ins = byteIns.get(ins.position + 1);
      }
      return ins;
    }
  
    /**
     * Stringifies the braches alone.
     * 
     * @return Returns a Stringifed version of the branches alone.
     */
    public String branchesToString() {
      StringBuilder sb = new StringBuilder("");
      int size = branches.size();
      if (size > 0) {
        sb.append("Branches:\n");
        for (int i = 0; i < size; i++) {
          sb.append(branches.get(i) + "\n");
        }
      }
      return sb.toString();
    }
  
    /**
     * @return Stringified form of the class
     */
    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder("");
      sb.append(branchesToString());
      int size = gotos.size();
      if (size > 0) {
        sb.append("Gotos:\n");
        for (Object obj : gotos.entrySet()) {
          sb.append(obj.toString() + "\n");
        }
      }
      size = mJSRTarget.size();
      if (size > 0) {
        sb.append("JSRTargets:\n");
        for (int i = 0; i < size; i++) {
          sb.append(mJSRTarget.get(i) + "\n");
        }
      }
      return sb.toString();
    }
  
    /**
     * List of branch representations.
     */
    List<BranchEntry> branches;
  
    /**
     * gotos Key - StartPc ( java.lang.Integer ) Value - TargetPc ( Absolute
     * target -java.lang.Integer).
     */
    Map<Integer, Integer> gotos;
  
    /**
     * List of switch instructions. Individual members are JInstruction.
     */
    List<Instruction> switches;
  
    /**
     * It is a Vector of 'Integer's. The integers are the target of the jump sub
     * routine instruction.
     */
    Vector<Integer> mJSRTarget;
  
    /**
     * Map of monitor instructions.
     */
    Map<Integer, String> mMonitor;
  
    /** Method reference * */
    Method method;
  
    private final Logger logger = CustomLoggerFactory.createLogger();
  }
  