1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.dx.command.dump;
18 
19 import com.android.dx.cf.code.ConcreteMethod;
20 import com.android.dx.cf.iface.Member;
21 import com.android.dx.cf.iface.ParseObserver;
22 import com.android.dx.util.ByteArray;
23 import com.android.dx.util.Hex;
24 import com.android.dx.util.IndentingWriter;
25 import com.android.dx.util.TwoColumnOutput;
26 import java.io.IOException;
27 import java.io.PrintStream;
28 import java.io.StringWriter;
29 
30 /**
31  * Base class for the various human-friendly dumpers.
32  */
33 public abstract class BaseDumper
34         implements ParseObserver {
35     /** {@code non-null;} array of data being dumped */
36     private final byte[] bytes;
37 
38     /** whether or not to include the raw bytes (in a column on the left) */
39     private final boolean rawBytes;
40 
41     /** {@code non-null;} where to dump to */
42     private final PrintStream out;
43 
44     /** width of the output in columns */
45     private final int width;
46 
47     /**
48      * {@code non-null;} the file path for the class, excluding any base
49      * directory specification
50      */
51     private final String filePath;
52 
53     /** whether to be strict about parsing */
54     private final boolean strictParse;
55 
56      /** number of bytes per line in hex dumps */
57     private final int hexCols;
58 
59     /** the current level of indentation */
60     private int indent;
61 
62     /** {@code non-null;} the current column separator string */
63     private String separator;
64 
65     /** the offset of the next byte to dump */
66     private int at;
67 
68     /** commandline parsedArgs */
69     protected Args args;
70 
71     /**
72      * Constructs an instance.
73      *
74      * @param bytes {@code non-null;} bytes of the (alleged) class file
75      * on the left)
76      * @param out {@code non-null;} where to dump to
77      * @param filePath the file path for the class, excluding any base
78      * directory specification
79      */
BaseDumper(byte[] bytes, PrintStream out, String filePath, Args args)80     public BaseDumper(byte[] bytes, PrintStream out,
81                       String filePath, Args args) {
82         this.bytes = bytes;
83         this.rawBytes = args.rawBytes;
84         this.out = out;
85         this.width = (args.width <= 0) ? 79 : args.width;
86         this.filePath = filePath;
87         this.strictParse = args.strictParse;
88         this.indent = 0;
89         this.separator = rawBytes ? "|" : "";
90         this.at = 0;
91         this.args = args;
92 
93         int hexCols = (((width - 5) / 15) + 1) & ~1;
94         if (hexCols < 6) {
95             hexCols = 6;
96         } else if (hexCols > 10) {
97             hexCols = 10;
98         }
99         this.hexCols = hexCols;
100     }
101 
102     /**
103      * Computes the total width, in register-units, of the parameters for
104      * this method.
105      * @param meth method to process
106      * @return width in register-units
107      */
computeParamWidth(ConcreteMethod meth, boolean isStatic)108     static int computeParamWidth(ConcreteMethod meth, boolean isStatic) {
109         return meth.getEffectiveDescriptor().getParameterTypes().
110             getWordCount();
111     }
112 
113     /** {@inheritDoc} */
changeIndent(int indentDelta)114     public void changeIndent(int indentDelta) {
115         indent += indentDelta;
116 
117         separator = rawBytes ? "|" : "";
118         for (int i = 0; i < indent; i++) {
119             separator += "  ";
120         }
121     }
122 
123     /** {@inheritDoc} */
parsed(ByteArray bytes, int offset, int len, String human)124     public void parsed(ByteArray bytes, int offset, int len, String human) {
125         offset = bytes.underlyingOffset(offset, getBytes());
126 
127         boolean rawBytes = getRawBytes();
128 
129         if (offset < at) {
130             println("<dump skipped backwards to " + Hex.u4(offset) + ">");
131             at = offset;
132         } else if (offset > at) {
133             String hex = rawBytes ? hexDump(at, offset - at) : "";
134             print(twoColumns(hex, "<skipped to " + Hex.u4(offset) + ">"));
135             at = offset;
136         }
137 
138         String hex = rawBytes ? hexDump(offset, len) : "";
139         print(twoColumns(hex, human));
140         at += len;
141     }
142 
143     /** {@inheritDoc} */
startParsingMember(ByteArray bytes, int offset, String name, String descriptor)144     public void startParsingMember(ByteArray bytes, int offset, String name,
145                                    String descriptor) {
146         // This space intentionally left blank.
147     }
148 
149     /** {@inheritDoc} */
endParsingMember(ByteArray bytes, int offset, String name, String descriptor, Member member)150     public void endParsingMember(ByteArray bytes, int offset, String name,
151                                  String descriptor, Member member) {
152         // This space intentionally left blank.
153     }
154 
155     /**
156      * Gets the current dump cursor (that is, the offset of the expected
157      * next byte to dump).
158      *
159      * @return {@code >= 0;} the dump cursor
160      */
getAt()161     protected final int getAt() {
162         return at;
163     }
164 
165     /**
166      * Sets the dump cursor to the indicated offset in the given array.
167      *
168      * @param arr {@code non-null;} array in question
169      * @param offset {@code >= 0;} offset into the array
170      */
setAt(ByteArray arr, int offset)171     protected final void setAt(ByteArray arr, int offset) {
172         at = arr.underlyingOffset(offset, bytes);
173     }
174 
175     /**
176      * Gets the array of {@code byte}s to process.
177      *
178      * @return {@code non-null;} the bytes
179      */
getBytes()180     protected final byte[] getBytes() {
181         return bytes;
182     }
183 
184     /**
185      * Gets the filesystem/jar path of the file being dumped.
186      *
187      * @return {@code non-null;} the path
188      */
getFilePath()189     protected final String getFilePath() {
190         return filePath;
191     }
192 
193     /**
194      * Gets whether to be strict about parsing.
195      *
196      * @return whether to be strict about parsing
197      */
getStrictParse()198     protected final boolean getStrictParse() {
199         return strictParse;
200     }
201 
202     /**
203      * Prints the given string to this instance's output stream.
204      *
205      * @param s {@code null-ok;} string to print
206      */
print(String s)207     protected final void print(String s) {
208         out.print(s);
209     }
210 
211     /**
212      * Prints the given string to this instance's output stream, followed
213      * by a newline.
214      *
215      * @param s {@code null-ok;} string to print
216      */
println(String s)217     protected final void println(String s) {
218         out.println(s);
219     }
220 
221     /**
222      * Gets whether this dump is to include raw bytes.
223      *
224      * @return the raw bytes flag
225      */
getRawBytes()226     protected final boolean getRawBytes() {
227         return rawBytes;
228     }
229 
230     /**
231      * Gets the width of the first column of output. This is {@code 0}
232      * unless raw bytes are being included in the output.
233      *
234      * @return {@code >= 0;} the width of the first column
235      */
getWidth1()236     protected final int getWidth1() {
237         if (rawBytes) {
238             return 5 + (hexCols * 2) + (hexCols / 2);
239         }
240 
241         return 0;
242     }
243 
244     /**
245      * Gets the width of the second column of output.
246      *
247      * @return {@code >= 0;} the width of the second column
248      */
getWidth2()249     protected final int getWidth2() {
250         int w1 = rawBytes ? (getWidth1() + 1) : 0;
251         return width - w1 - (indent * 2);
252     }
253 
254     /**
255      * Constructs a hex data dump of the given portion of {@link #bytes}.
256      *
257      * @param offset offset to start dumping at
258      * @param len length to dump
259      * @return {@code non-null;} the dump
260      */
hexDump(int offset, int len)261     protected final String hexDump(int offset, int len) {
262         return Hex.dump(bytes, offset, len, offset, hexCols, 4);
263     }
264 
265     /**
266      * Combines a pair of strings as two columns, or if this is one-column
267      * output, format the otherwise-second column.
268      *
269      * @param s1 {@code non-null;} the first column's string
270      * @param s2 {@code non-null;} the second column's string
271      * @return {@code non-null;} the combined output
272      */
twoColumns(String s1, String s2)273     protected final String twoColumns(String s1, String s2) {
274         int w1 = getWidth1();
275         int w2 = getWidth2();
276 
277         try {
278             if (w1 == 0) {
279                 int len2 = s2.length();
280                 StringWriter sw = new StringWriter(len2 * 2);
281                 IndentingWriter iw = new IndentingWriter(sw, w2, separator);
282 
283                 iw.write(s2);
284                 if ((len2 == 0) || (s2.charAt(len2 - 1) != '\n')) {
285                     iw.write('\n');
286                 }
287                 iw.flush();
288 
289                 return sw.toString();
290             } else {
291                 return TwoColumnOutput.toString(s1, w1, separator, s2, w2);
292             }
293         } catch (IOException ex) {
294             throw new RuntimeException(ex);
295         }
296     }
297 }
298