blob: ad6540b591d14d4522e02e3674d16e8cf3eaad2b [file] [log] [blame]
/*
* 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);
}
}
}