content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * 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 com.taobao.android.dx.cf.code;
  
  import com.taobao.android.dx.rop.cst.CstType;
  import com.taobao.android.dx.rop.type.StdTypeList;
  import com.taobao.android.dx.rop.type.TypeList;
  import com.taobao.android.dx.util.FixedSizeList;
  import com.taobao.android.dx.util.IntList;
  
  /**
   * List of catch entries, that is, the elements of an "exception table,"
   * which is part of a standard {@code Code} attribute.
   */
  public final class ByteCatchList extends FixedSizeList {
      /** {@code non-null;} convenient zero-entry instance */
      public static final ByteCatchList EMPTY = new ByteCatchList(0);
  
      /**
       * Constructs an instance.
       *
       * @param count the number of elements to be in the table
       */
      public ByteCatchList(int count) {
          super(count);
      }
  
      /**
       * Gets the total length of this structure in bytes, when included in
       * a {@code Code} attribute. The returned value includes the
       * two bytes for {@code exception_table_length}.
       *
       * @return {@code >= 2;} the total length, in bytes
       */
      public int byteLength() {
          return 2 + size() * 8;
      }
  
      /**
       * Gets the indicated item.
       *
       * @param n {@code >= 0;} which item
       * @return {@code null-ok;} the indicated item
       */
      public Item get(int n) {
          return (Item) get0(n);
      }
  
      /**
       * Sets the item at the given index.
       *
       * @param n {@code >= 0, < size();} which entry to set
       * @param item {@code non-null;} the item
       */
      public void set(int n, Item item) {
          if (item == null) {
              throw new NullPointerException("item == null");
          }
  
          set0(n, item);
      }
  
      /**
       * Sets the item at the given index.
       *
       * @param n {@code >= 0, < size();} which entry to set
       * @param startPc {@code >= 0;} the start pc (inclusive) of the handler's range
       * @param endPc {@code >= startPc;} the end pc (exclusive) of the
       * handler's range
       * @param handlerPc {@code >= 0;} the pc of the exception handler
       * @param exceptionClass {@code null-ok;} the exception class or
       * {@code null} to catch all exceptions with this handler
       */
      public void set(int n, int startPc, int endPc, int handlerPc,
              CstType exceptionClass) {
          set0(n, new Item(startPc, endPc, handlerPc, exceptionClass));
      }
  
      /**
       * Gets the list of items active at the given address. The result is
       * automatically made immutable.
       *
       * @param pc which address
       * @return {@code non-null;} list of exception handlers active at
       * {@code pc}
       */
      public ByteCatchList listFor(int pc) {
          int sz = size();
          Item[] resultArr = new Item[sz];
          int resultSz = 0;
  
          for (int i = 0; i < sz; i++) {
              Item one = get(i);
              if (one.covers(pc) && typeNotFound(one, resultArr, resultSz)) {
                  resultArr[resultSz] = one;
                  resultSz++;
              }
          }
  
          if (resultSz == 0) {
              return EMPTY;
          }
  
          ByteCatchList result = new ByteCatchList(resultSz);
          for (int i = 0; i < resultSz; i++) {
              result.set(i, resultArr[i]);
          }
  
          result.setImmutable();
          return result;
      }
  
      /**
       * Helper method for {@link #listFor}, which tells whether a match
       * is <i>not</i> found for the exception type of the given item in
       * the given array. A match is considered to be either an exact type
       * match or the class {@code Object} which represents a catch-all.
       *
       * @param item {@code non-null;} item with the exception type to look for
       * @param arr {@code non-null;} array to search in
       * @param count {@code non-null;} maximum number of elements in the array to check
       * @return {@code true} iff the exception type is <i>not</i> found
       */
      private static boolean typeNotFound(Item item, Item[] arr, int count) {
          CstType type = item.getExceptionClass();
  
          for (int i = 0; i < count; i++) {
              CstType one = arr[i].getExceptionClass();
              if ((one == type) || (one == CstType.OBJECT)) {
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * Returns a target list corresponding to this instance. The result
       * is a list of all the exception handler addresses, with the given
       * {@code noException} address appended if appropriate. The
       * result is automatically made immutable.
       *
       * @param noException {@code >= -1;} the no-exception address to append, or
       * {@code -1} not to append anything
       * @return {@code non-null;} list of exception targets, with
       * {@code noException} appended if necessary
       */
      public IntList toTargetList(int noException) {
          if (noException < -1) {
              throw new IllegalArgumentException("noException < -1");
          }
  
          boolean hasDefault = (noException >= 0);
          int sz = size();
  
          if (sz == 0) {
              if (hasDefault) {
                  /*
                   * The list is empty, but there is a no-exception
                   * address; so, the result is just that address.
                   */
                  return IntList.makeImmutable(noException);
              }
              /*
               * The list is empty and there isn't even a no-exception
               * address.
               */
              return IntList.EMPTY;
          }
  
          IntList result = new IntList(sz + (hasDefault ? 1 : 0));
  
          for (int i = 0; i < sz; i++) {
              result.add(get(i).getHandlerPc());
          }
  
          if (hasDefault) {
              result.add(noException);
          }
  
          result.setImmutable();
          return result;
      }
  
      /**
       * Returns a rop-style catches list equivalent to this one.
       *
       * @return {@code non-null;} the converted instance
       */
      public TypeList toRopCatchList() {
          int sz = size();
          if (sz == 0) {
              return StdTypeList.EMPTY;
          }
  
          StdTypeList result = new StdTypeList(sz);
  
          for (int i = 0; i < sz; i++) {
              result.set(i, get(i).getExceptionClass().getClassType());
          }
  
          result.setImmutable();
          return result;
      }
  
      /**
       * Item in an exception handler list.
       */
      public static class Item {
          /** {@code >= 0;} the start pc (inclusive) of the handler's range */
          private final int startPc;
  
          /** {@code >= startPc;} the end pc (exclusive) of the handler's range */
          private final int endPc;
  
          /** {@code >= 0;} the pc of the exception handler */
          private final int handlerPc;
  
          /** {@code null-ok;} the exception class or {@code null} to catch all
           * exceptions with this handler */
          private final CstType exceptionClass;
  
          /**
           * Constructs an instance.
           *
           * @param startPc {@code >= 0;} the start pc (inclusive) of the
           * handler's range
           * @param endPc {@code >= startPc;} the end pc (exclusive) of the
           * handler's range
           * @param handlerPc {@code >= 0;} the pc of the exception handler
           * @param exceptionClass {@code null-ok;} the exception class or
           * {@code null} to catch all exceptions with this handler
           */
          public Item(int startPc, int endPc, int handlerPc,
                  CstType exceptionClass) {
              if (startPc < 0) {
                  throw new IllegalArgumentException("startPc < 0");
              }
  
              if (endPc < startPc) {
                  throw new IllegalArgumentException("endPc < startPc");
              }
  
              if (handlerPc < 0) {
                  throw new IllegalArgumentException("handlerPc < 0");
              }
  
              this.startPc = startPc;
              this.endPc = endPc;
              this.handlerPc = handlerPc;
              this.exceptionClass = exceptionClass;
          }
  
          /**
           * Gets the start pc (inclusive) of the handler's range.
           *
           * @return {@code >= 0;} the start pc (inclusive) of the handler's range.
           */
          public int getStartPc() {
              return startPc;
          }
  
          /**
           * Gets the end pc (exclusive) of the handler's range.
           *
           * @return {@code >= startPc;} the end pc (exclusive) of the
           * handler's range.
           */
          public int getEndPc() {
              return endPc;
          }
  
          /**
           * Gets the pc of the exception handler.
           *
           * @return {@code >= 0;} the pc of the exception handler
           */
          public int getHandlerPc() {
              return handlerPc;
          }
  
          /**
           * Gets the class of exception handled.
           *
           * @return {@code non-null;} the exception class; {@link CstType#OBJECT}
           * if this entry handles all possible exceptions
           */
          public CstType getExceptionClass() {
              return (exceptionClass != null) ?
                  exceptionClass : CstType.OBJECT;
          }
  
          /**
           * Returns whether the given address is in the range of this item.
           *
           * @param pc the address
           * @return {@code true} iff this item covers {@code pc}
           */
          public boolean covers(int pc) {
              return (pc >= startPc) && (pc < endPc);
          }
      }
  }
  