/* * 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.android.dx.command.dump; import com.android.dx.cf.code.ConcreteMethod; import com.android.dx.cf.iface.Member; import com.android.dx.cf.iface.ParseObserver; import com.android.dx.rop.code.AccessFlags; import com.android.dx.util.ByteArray; import com.android.dx.util.Hex; import com.android.dx.util.IndentingWriter; import com.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); } } }