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