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.command.dump;
  
  import com.taobao.android.dx.cf.code.ConcreteMethod;
  import com.taobao.android.dx.cf.iface.Member;
  import com.taobao.android.dx.cf.iface.ParseObserver;
  import com.taobao.android.dx.util.ByteArray;
  import com.taobao.android.dx.util.Hex;
  import com.taobao.android.dx.util.IndentingWriter;
  import com.taobao.android.dx.util.TwoColumnOutput;
  import java.io.IOException;
  import java.io.PrintStream;
  import java.io.StringWriter;
  
  /**
   * Base class for the various human-friendly dumpers.
   */
  public abstract class BaseDumper
          implements ParseObserver {
      /** {@code non-null;} array of data being dumped */
      private final byte[] bytes;
  
      /** whether or not to include the raw bytes (in a column on the left) */
      private final boolean rawBytes;
  
      /** {@code non-null;} where to dump to */
      private final PrintStream out;
  
      /** width of the output in columns */
      private final int width;
  
      /**
       * {@code non-null;} the file path for the class, excluding any base
       * directory specification
       */
      private final String filePath;
  
      /** whether to be strict about parsing */
      private final boolean strictParse;
  
       /** number of bytes per line in hex dumps */
      private final int hexCols;
  
      /** the current level of indentation */
      private int indent;
  
      /** {@code non-null;} the current column separator string */
      private String separator;
  
      /** the offset of the next byte to dump */
      private int at;
  
      /** commandline parsedArgs */
      protected Args args;
  
      /**
       * Constructs an instance.
       *
       * @param bytes {@code non-null;} bytes of the (alleged) class file
       * on the left)
       * @param out {@code non-null;} where to dump to
       * @param filePath the file path for the class, excluding any base
       * directory specification
       */
      public BaseDumper(byte[] bytes, PrintStream out,
                        String filePath, Args args) {
          this.bytes = bytes;
          this.rawBytes = args.rawBytes;
          this.out = out;
          this.width = (args.width <= 0) ? 79 : args.width;
          this.filePath = filePath;
          this.strictParse = args.strictParse;
          this.indent = 0;
          this.separator = rawBytes ? "|" : "";
          this.at = 0;
          this.args = args;
  
          int hexCols = (((width - 5) / 15) + 1) & ~1;
          if (hexCols < 6) {
              hexCols = 6;
          } else if (hexCols > 10) {
              hexCols = 10;
          }
          this.hexCols = hexCols;
      }
  
      /**
       * Computes the total width, in register-units, of the parameters for
       * this method.
       * @param meth method to process
       * @return width in register-units
       */
      static int computeParamWidth(ConcreteMethod meth, boolean isStatic) {
          return meth.getEffectiveDescriptor().getParameterTypes().
              getWordCount();
      }
  
      /** {@inheritDoc} */
      public void changeIndent(int indentDelta) {
          indent += indentDelta;
  
          separator = rawBytes ? "|" : "";
          for (int i = 0; i < indent; i++) {
              separator += "  ";
          }
      }
  
      /** {@inheritDoc} */
      public void parsed(ByteArray bytes, int offset, int len, String human) {
          offset = bytes.underlyingOffset(offset, getBytes());
  
          boolean rawBytes = getRawBytes();
  
          if (offset < at) {
              println("<dump skipped backwards to " + Hex.u4(offset) + ">");
              at = offset;
          } else if (offset > at) {
              String hex = rawBytes ? hexDump(at, offset - at) : "";
              print(twoColumns(hex, "<skipped to " + Hex.u4(offset) + ">"));
              at = offset;
          }
  
          String hex = rawBytes ? hexDump(offset, len) : "";
          print(twoColumns(hex, human));
          at += len;
      }
  
      /** {@inheritDoc} */
      public void startParsingMember(ByteArray bytes, int offset, String name,
                                     String descriptor) {
          // This space intentionally left blank.
      }
  
      /** {@inheritDoc} */
      public void endParsingMember(ByteArray bytes, int offset, String name,
                                   String descriptor, Member member) {
          // This space intentionally left blank.
      }
  
      /**
       * Gets the current dump cursor (that is, the offset of the expected
       * next byte to dump).
       *
       * @return {@code >= 0;} the dump cursor
       */
      protected final int getAt() {
          return at;
      }
  
      /**
       * Sets the dump cursor to the indicated offset in the given array.
       *
       * @param arr {@code non-null;} array in question
       * @param offset {@code >= 0;} offset into the array
       */
      protected final void setAt(ByteArray arr, int offset) {
          at = arr.underlyingOffset(offset, bytes);
      }
  
      /**
       * Gets the array of {@code byte}s to process.
       *
       * @return {@code non-null;} the bytes
       */
      protected final byte[] getBytes() {
          return bytes;
      }
  
      /**
       * Gets the filesystem/jar path of the file being dumped.
       *
       * @return {@code non-null;} the path
       */
      protected final String getFilePath() {
          return filePath;
      }
  
      /**
       * Gets whether to be strict about parsing.
       *
       * @return whether to be strict about parsing
       */
      protected final boolean getStrictParse() {
          return strictParse;
      }
  
      /**
       * Prints the given string to this instance's output stream.
       *
       * @param s {@code null-ok;} string to print
       */
      protected final void print(String s) {
          out.print(s);
      }
  
      /**
       * Prints the given string to this instance's output stream, followed
       * by a newline.
       *
       * @param s {@code null-ok;} string to print
       */
      protected final void println(String s) {
          out.println(s);
      }
  
      /**
       * Gets whether this dump is to include raw bytes.
       *
       * @return the raw bytes flag
       */
      protected final boolean getRawBytes() {
          return rawBytes;
      }
  
      /**
       * Gets the width of the first column of output. This is {@code 0}
       * unless raw bytes are being included in the output.
       *
       * @return {@code >= 0;} the width of the first column
       */
      protected final int getWidth1() {
          if (rawBytes) {
              return 5 + (hexCols * 2) + (hexCols / 2);
          }
  
          return 0;
      }
  
      /**
       * Gets the width of the second column of output.
       *
       * @return {@code >= 0;} the width of the second column
       */
      protected final int getWidth2() {
          int w1 = rawBytes ? (getWidth1() + 1) : 0;
          return width - w1 - (indent * 2);
      }
  
      /**
       * Constructs a hex data dump of the given portion of {@link #bytes}.
       *
       * @param offset offset to start dumping at
       * @param len length to dump
       * @return {@code non-null;} the dump
       */
      protected final String hexDump(int offset, int len) {
          return Hex.dump(bytes, offset, len, offset, hexCols, 4);
      }
  
      /**
       * Combines a pair of strings as two columns, or if this is one-column
       * output, format the otherwise-second column.
       *
       * @param s1 {@code non-null;} the first column's string
       * @param s2 {@code non-null;} the second column's string
       * @return {@code non-null;} the combined output
       */
      protected final String twoColumns(String s1, String s2) {
          int w1 = getWidth1();
          int w2 = getWidth2();
  
          try {
              if (w1 == 0) {
                  int len2 = s2.length();
                  StringWriter sw = new StringWriter(len2 * 2);
                  IndentingWriter iw = new IndentingWriter(sw, w2, separator);
  
                  iw.write(s2);
                  if ((len2 == 0) || (s2.charAt(len2 - 1) != '\n')) {
                      iw.write('\n');
                  }
                  iw.flush();
  
                  return sw.toString();
              } else {
                  return TwoColumnOutput.toString(s1, w1, separator, s2, w2);
              }
          } catch (IOException ex) {
              throw new RuntimeException(ex);
          }
      }
  }
  