/* Software Name : AsmDex
* Version : 1.0
*
* Copyright © 2012 France Télécom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.ow2.asmdex.util;
import java.util.List;
/**
* An abstract visitor for the AsmDexifier.
*
* @author Julien Névo, based on the ASM framework.
*/
public class AsmDexPrinter {
/**
* Strings for access flags. ACC_BRIDGE and ACC_VARARGS are omitted because they have the
* same value as ACC_VOLATILE and ACC_TRANSIENT respectively. Using one instead of another
* shouldn't be a problem...
*/
private static String[] accessStrings = new String[] { "ACC_PUBLIC", "ACC_PRIVATE", "ACC_PROTECTED",
"ACC_STATIC", "ACC_FINAL", "ACC_SYNCHRONIZED", "ACC_VOLATILE", /*"ACC_BRIDGE", */"ACC_TRANSIENT",
/*"ACC_VARARGS", */"ACC_NATIVE", "ACC_INTERFACE", "ACC_ABSTRACT", "ACC_STRICT", "ACC_SYNTHETIC",
"ACC_ANNOTATION", "ACC_ENUM", "", "ACC_CONSTRUCTOR", "ACC_DECLARED_SYNCHRONIZED"
};
/**
* The tabulation of the current element.
*/
protected int currentTabulation = 0;
/**
* A buffer that can be used to create strings.
*/
protected StringBuffer text = new StringBuffer();
/**
* The text to be printed. Since the code of methods is not necessarily
* visited in sequential order, one method after the other, but can be
* interlaced (some instructions from method one, then some instructions
* from method two, then some instructions from method one again...), it is
* not possible to print the visited instructions directly to a sequential
* stream. A class is therefore printed in a two steps process: a string
* tree is constructed during the visit, and printed to a sequential stream
* at the end of the visit. This tree is stored in this field, as a
* Composite that can contain other Composite, which can themselves
* contain other Composite, and so on.
*/
private TextComposite textComposite = new TextComposite();
/**
* Returns the Text Component this element holds.
* @return the Text Component this element holds.
*/
protected TextComponent getTextComponent() {
return textComposite;
}
/**
* Adds the current text to the list of texts, and resets the text.
* Must be done for each text (or part of it) that is over.
*/
public void closeText() {
TextLeaf component = new TextLeaf(text);
textComposite.addComponent(component);
text = new StringBuffer();
}
/**
* Adds the given Text Component to the list of this element.
* @param tc the Text Component to add.
*/
public void addTextToList(TextComponent tc) {
textComposite.addComponent(tc);
}
/**
* Appends a comma and a space to the current text.
*/
public void addComma() {
text.append(", ");
}
/**
* Adds an End Of Line tag at the end of the current text.
*/
public void addEOL() {
text.append("\n");
}
/**
* Adds a boolean string to the current text. This last one needs to be closed (with the
* closeText() method) in order to be actually used.
* @param bool the boolean value to encode.
* @param addComma true to add a comma at the end of the text.
*/
public void addBoolean(boolean bool, boolean addComma) {
addText(bool ? "true" : "false", addComma);
}
/**
* Adds a given text to the current text. This last one needs to be closed (with the
* closeText() method) in order to be actually used.
* @param textToAdd the text to add.
*/
public void addText(String textToAdd) {
text.append(textToAdd);
}
/**
* Adds a given text to the current text. This last one needs to be closed (with the
* closeText() method) in order to be actually used.
* @param textToAdd the text to add.
* @param addComma true to add a comma at the end of the text.
*/
public void addText(String textToAdd, boolean addComma) {
addText(textToAdd);
if (addComma) {
addComma();
}
}
/**
* Appends a string representation of the given constant to the given
* buffer, with quotes if necessary.
*
* @param cst a single or array of ints, {@link Integer}, {@link Float}, {@link Long},
* {@link Double} or {@link String} object. May be <tt>null</tt>.
* @param addComma true to add a comma at the end.
*/
public void addConstant(final Object cst, boolean addComma) {
// Some primitive types must be boxed because it is often the only way for AsmDex to know their
// type (ex : annotationVisitor.visit requires an Object. Floats, doubles and longs have prefixes
// and int is used by default. But shorts, bytes and characters must be boxed.
// Primitives inside Arrays are written directly without boxing, with the prefixes if needed.
if (cst == null) {
text.append("null");
} else if (cst instanceof String) {
appendString((String)cst);
} else if (cst instanceof Integer) {
text.append(cst);
} else if (cst instanceof Boolean) {
text.append(cst);
} else if (cst instanceof Byte) {
text.append("Byte.valueOf((byte)").append(cst).append(')');
} else if (cst instanceof Character) {
int c = ((Character)cst).charValue();
text.append("Character.valueOf((char)").append(c).append(')');
} else if (cst instanceof Short) {
text.append("Short.valueOf((short)").append(cst).append(')');
} else if (cst instanceof Double) {
addNumber((Double)cst, false);
} else if (cst instanceof Float) {
addNumber((Float)cst, false);
} else if (cst instanceof Long) {
addNumber((Long)cst, false);
// Tests arrays.
} else if (cst instanceof int[]) {
text.append("new int[] { ");
int[] ints = (int[])cst;
for (int i = 0, size = ints.length; i < size; i++) {
addNumber(ints[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof boolean[]) {
text.append("new boolean[] { ");
boolean[] array = (boolean[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addBoolean(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof byte[]) {
text.append("new byte[] { ");
byte[] array = (byte[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof short[]) {
text.append("new short[] { ");
short[] array = (short[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof char[]) {
text.append("new char[] { ");
char[] array = (char[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof float[]) {
text.append("new float[] { ");
float[] array = (float[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof double[]) {
text.append("new double[] { ");
double[] array = (double[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof long[]) {
text.append("new long[] { ");
long[] array = (long[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof String[]) {
text.append("new String[] { ");
String[] strings = (String[])cst;
for (int i = 0, size = strings.length; i < size; i++) {
addConstant(strings[i], (i != (size - 1)));
}
text.append(" }");
}
// Manages Object[] arrays. This is only useful for FillArrayData instructions.
else if (cst instanceof Byte[]) {
text.append("new Byte[] { ");
Byte[] array = (Byte[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Integer[]) {
text.append("new Integer[] { ");
Integer[] ints = (Integer[])cst;
for (int i = 0, size = ints.length; i < size; i++) {
addNumber(ints[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Boolean[]) {
text.append("new Boolean[] { ");
Boolean[] array = (Boolean[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addBoolean(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Short[]) {
text.append("new Short[] { ");
Short[] array = (Short[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Character[]) {
text.append("new Character[] { ");
Character[] array = (Character[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Float[]) {
text.append("new Float[] { ");
Float[] array = (Float[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Double[]) {
text.append("new Double[] { ");
Double[] array = (Double[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
} else if (cst instanceof Long[]) {
text.append("new Long[] { ");
Long[] array = (Long[])cst;
for (int i = 0, size = array.length; i < size; i++) {
addNumber(array[i], (i != (size - 1)));
}
text.append(" }");
}
else {
try { throw new Exception ("Unhandled constant type.");
} catch (Exception e) { e.printStackTrace(); }
}
if (addComma) {
addComma();
}
}
/**
* Adds the declaration of an ArrayList of Strings to the current text.
* @param list the List of Strings to add.
* @param addComma true to add a comma at the end.
*/
public void addStringArrayList(final List<String> list, boolean addComma) {
if (list == null) {
text.append("null");
} else {
text.append("new ArrayList<Label>(Arrays.asList(");
int i = 0;
for (String string : list) {
if (i != 0) {
text.append(", ");
}
text.append(string);
i++;
}
text.append("))");
}
if (addComma) {
addComma();
}
}
/**
* Adds the declaration of an Array of Labels to the current text, from their names.
* @param labelNames the names of the Labels.
* @param addComma true to add a comma at the end.
*/
public void addLabelArray(final String[] labelNames, boolean addComma) {
if (labelNames == null) {
addText("null", addComma);
} else {
addText("new Label[] { ");
boolean isFirst = true;
for (String string : labelNames) {
if (!isFirst) {
addComma();
}
addText(string);
isFirst = false;
}
addText(" }", addComma);
}
}
/**
* Appends the Access flags to the given buffer.
* @param accessFlags the access flags to encode.
* @param addComma true to add a comma at the end.
*/
public void addAccessFlags(int accessFlags, boolean addComma) {
if (accessFlags == 0) {
text.append('0');
} else {
boolean isFirst = true;
int accessIndex = 0;
while (accessFlags != 0) {
if ((accessFlags & 1) != 0) {
if (!isFirst) {
text.append(" + ");
}
text.append(accessStrings[accessIndex]);
isFirst = false;
}
accessFlags >>>= 1;
accessIndex++;
}
}
if (addComma) {
addComma();
}
}
/**
* Appends a quoted string to a given buffer.
*
*
* @param s the string to be added.
*/
public void appendString(final String s) {
text.append('\"');
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (c == '\n') {
text.append("\\n");
} else if (c == '\r') {
text.append("\\r");
} else if (c == '\\') {
text.append("\\\\");
} else if (c == '"') {
text.append("\\\"");
} else if (c < 0x20 || c > 0x7f) {
text.append("\\u");
if (c < 0x10) {
text.append("000");
} else if (c < 0x100) {
text.append("00");
} else if (c < 0x1000) {
text.append('0');
}
text.append(Integer.toString(c, 16));
} else {
text.append(c);
}
}
text.append('\"');
}
/**
* Adds a number at the end of the current text.
* @param nb the number to add.
* @param addComma true to add a comma at the end.
*/
public void addNumber(int nb, boolean addComma) {
text.append(nb);
if (addComma) {
addComma();
}
}
/**
* Adds a Float number at the end of the current text.
* @param nb the number to add.
* @param addComma true to add a comma at the end.
*/
public void addNumber(float nb, boolean addComma) {
text.append(nb);
text.append('f');
if (addComma) {
addComma();
}
}
/**
* Adds a long number at the end of the current text.
* @param nb the number to add.
* @param addComma true to add a comma at the end.
*/
public void addNumber(long nb, boolean addComma) {
text.append(nb);
text.append('L');
if (addComma) {
addComma();
}
}
/**
* Adds a long number at the end of the current text.
* @param nb the number to add.
* @param addComma true to add a comma at the end.
*/
public void addNumber(double nb, boolean addComma) {
text.append(nb);
text.append('d');
if (addComma) {
addComma();
}
}
/**
* Adds to the current text the name of the given opcode, or the number in hexadecimal
* if the opcode isn't known.
* @param opcode the opcode.
* @param addComma true to add a comma after the opcode name.
*/
public void addOpcode(int opcode, boolean addComma) {
String string = AsmDexifierApplicationVisitor.getOpcodeName(opcode);
if (string == null) {
string = "0x" + Integer.toHexString(opcode);
}
text.append(string);
if (addComma) {
addComma();
}
}
/**
* Adds tabulation according to the tabulation count stored in the current element.
*/
public void addTabulation() {
for (int i = 0; i < currentTabulation; i++) {
text.append("\t");
}
}
}