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
17package com.android.dx.cf.direct;
18
19import com.android.dx.cf.iface.ParseException;
20import com.android.dx.cf.iface.ParseObserver;
21import com.android.dx.rop.annotation.Annotation;
22import com.android.dx.rop.annotation.AnnotationVisibility;
23import com.android.dx.rop.annotation.Annotations;
24import com.android.dx.rop.annotation.AnnotationsList;
25import com.android.dx.rop.annotation.NameValuePair;
26import com.android.dx.rop.cst.Constant;
27import com.android.dx.rop.cst.ConstantPool;
28import com.android.dx.rop.cst.CstAnnotation;
29import com.android.dx.rop.cst.CstArray;
30import com.android.dx.rop.cst.CstBoolean;
31import com.android.dx.rop.cst.CstByte;
32import com.android.dx.rop.cst.CstChar;
33import com.android.dx.rop.cst.CstDouble;
34import com.android.dx.rop.cst.CstEnumRef;
35import com.android.dx.rop.cst.CstFloat;
36import com.android.dx.rop.cst.CstInteger;
37import com.android.dx.rop.cst.CstLong;
38import com.android.dx.rop.cst.CstNat;
39import com.android.dx.rop.cst.CstShort;
40import com.android.dx.rop.cst.CstString;
41import com.android.dx.rop.cst.CstType;
42import com.android.dx.rop.type.Type;
43import com.android.dx.util.ByteArray;
44import com.android.dx.util.Hex;
45
46import java.io.IOException;
47
48/**
49 * Parser for annotations.
50 */
51public final class AnnotationParser {
52    /** {@code non-null;} class file being parsed */
53    private final DirectClassFile cf;
54
55    /** {@code non-null;} constant pool to use */
56    private final ConstantPool pool;
57
58    /** {@code non-null;} bytes of the attribute data */
59    private final ByteArray bytes;
60
61    /** {@code null-ok;} parse observer, if any */
62    private final ParseObserver observer;
63
64    /** {@code non-null;} input stream to parse from */
65    private final ByteArray.MyDataInputStream input;
66
67    /**
68     * {@code non-null;} cursor for use when informing the observer of what
69     * was parsed
70     */
71    private int parseCursor;
72
73    /**
74     * Constructs an instance.
75     *
76     * @param cf {@code non-null;} class file to parse from
77     * @param offset {@code >= 0;} offset into the class file data to parse at
78     * @param length {@code >= 0;} number of bytes left in the attribute data
79     * @param observer {@code null-ok;} parse observer to notify, if any
80     */
81    public AnnotationParser(DirectClassFile cf, int offset, int length,
82            ParseObserver observer) {
83        if (cf == null) {
84            throw new NullPointerException("cf == null");
85        }
86
87        this.cf = cf;
88        this.pool = cf.getConstantPool();
89        this.observer = observer;
90        this.bytes = cf.getBytes().slice(offset, offset + length);
91        this.input = bytes.makeDataInputStream();
92        this.parseCursor = 0;
93    }
94
95    /**
96     * Parses an annotation value ({@code element_value}) attribute.
97     *
98     * @return {@code non-null;} the parsed constant value
99     */
100    public Constant parseValueAttribute() {
101        Constant result;
102
103        try {
104            result = parseValue();
105
106            if (input.available() != 0) {
107                throw new ParseException("extra data in attribute");
108            }
109        } catch (IOException ex) {
110            // ByteArray.MyDataInputStream should never throw.
111            throw new RuntimeException("shouldn't happen", ex);
112        }
113
114        return result;
115    }
116
117    /**
118     * Parses a parameter annotation attribute.
119     *
120     * @param visibility {@code non-null;} visibility of the parsed annotations
121     * @return {@code non-null;} the parsed list of lists of annotations
122     */
123    public AnnotationsList parseParameterAttribute(
124            AnnotationVisibility visibility) {
125        AnnotationsList result;
126
127        try {
128            result = parseAnnotationsList(visibility);
129
130            if (input.available() != 0) {
131                throw new ParseException("extra data in attribute");
132            }
133        } catch (IOException ex) {
134            // ByteArray.MyDataInputStream should never throw.
135            throw new RuntimeException("shouldn't happen", ex);
136        }
137
138        return result;
139    }
140
141    /**
142     * Parses an annotation attribute, per se.
143     *
144     * @param visibility {@code non-null;} visibility of the parsed annotations
145     * @return {@code non-null;} the list of annotations read from the attribute
146     * data
147     */
148    public Annotations parseAnnotationAttribute(
149            AnnotationVisibility visibility) {
150        Annotations result;
151
152        try {
153            result = parseAnnotations(visibility);
154
155            if (input.available() != 0) {
156                throw new ParseException("extra data in attribute");
157            }
158        } catch (IOException ex) {
159            // ByteArray.MyDataInputStream should never throw.
160            throw new RuntimeException("shouldn't happen", ex);
161        }
162
163        return result;
164    }
165
166    /**
167     * Parses a list of annotation lists.
168     *
169     * @param visibility {@code non-null;} visibility of the parsed annotations
170     * @return {@code non-null;} the list of annotation lists read from the attribute
171     * data
172     */
173    private AnnotationsList parseAnnotationsList(
174            AnnotationVisibility visibility) throws IOException {
175        int count = input.readUnsignedByte();
176
177        if (observer != null) {
178            parsed(1, "num_parameters: " + Hex.u1(count));
179        }
180
181        AnnotationsList outerList = new AnnotationsList(count);
182
183        for (int i = 0; i < count; i++) {
184            if (observer != null) {
185                parsed(0, "parameter_annotations[" + i + "]:");
186                changeIndent(1);
187            }
188
189            Annotations annotations = parseAnnotations(visibility);
190            outerList.set(i, annotations);
191
192            if (observer != null) {
193                observer.changeIndent(-1);
194            }
195        }
196
197        outerList.setImmutable();
198        return outerList;
199    }
200
201    /**
202     * Parses an annotation list.
203     *
204     * @param visibility {@code non-null;} visibility of the parsed annotations
205     * @return {@code non-null;} the list of annotations read from the attribute
206     * data
207     */
208    private Annotations parseAnnotations(AnnotationVisibility visibility)
209            throws IOException {
210        int count = input.readUnsignedShort();
211
212        if (observer != null) {
213            parsed(2, "num_annotations: " + Hex.u2(count));
214        }
215
216        Annotations annotations = new Annotations();
217
218        for (int i = 0; i < count; i++) {
219            if (observer != null) {
220                parsed(0, "annotations[" + i + "]:");
221                changeIndent(1);
222            }
223
224            Annotation annotation = parseAnnotation(visibility);
225            annotations.add(annotation);
226
227            if (observer != null) {
228                observer.changeIndent(-1);
229            }
230        }
231
232        annotations.setImmutable();
233        return annotations;
234    }
235
236    /**
237     * Parses a single annotation.
238     *
239     * @param visibility {@code non-null;} visibility of the parsed annotation
240     * @return {@code non-null;} the parsed annotation
241     */
242    private Annotation parseAnnotation(AnnotationVisibility visibility)
243            throws IOException {
244        requireLength(4);
245
246        int typeIndex = input.readUnsignedShort();
247        int numElements = input.readUnsignedShort();
248        CstString typeString = (CstString) pool.get(typeIndex);
249        CstType type = new CstType(Type.intern(typeString.getString()));
250
251        if (observer != null) {
252            parsed(2, "type: " + type.toHuman());
253            parsed(2, "num_elements: " + numElements);
254        }
255
256        Annotation annotation = new Annotation(type, visibility);
257
258        for (int i = 0; i < numElements; i++) {
259            if (observer != null) {
260                parsed(0, "elements[" + i + "]:");
261                changeIndent(1);
262            }
263
264            NameValuePair element = parseElement();
265            annotation.add(element);
266
267            if (observer != null) {
268                changeIndent(-1);
269            }
270        }
271
272        annotation.setImmutable();
273        return annotation;
274    }
275
276    /**
277     * Parses a {@link NameValuePair}.
278     *
279     * @return {@code non-null;} the parsed element
280     */
281    private NameValuePair parseElement() throws IOException {
282        requireLength(5);
283
284        int elementNameIndex = input.readUnsignedShort();
285        CstString elementName = (CstString) pool.get(elementNameIndex);
286
287        if (observer != null) {
288            parsed(2, "element_name: " + elementName.toHuman());
289            parsed(0, "value: ");
290            changeIndent(1);
291        }
292
293        Constant value = parseValue();
294
295        if (observer != null) {
296            changeIndent(-1);
297        }
298
299        return new NameValuePair(elementName, value);
300    }
301
302    /**
303     * Parses an annotation value.
304     *
305     * @return {@code non-null;} the parsed value
306     */
307    private Constant parseValue() throws IOException {
308        int tag = input.readUnsignedByte();
309
310        if (observer != null) {
311            CstString humanTag = new CstString(Character.toString((char) tag));
312            parsed(1, "tag: " + humanTag.toQuoted());
313        }
314
315        switch (tag) {
316            case 'B': {
317                CstInteger value = (CstInteger) parseConstant();
318                return CstByte.make(value.getValue());
319            }
320            case 'C': {
321                CstInteger value = (CstInteger) parseConstant();
322                int intValue = value.getValue();
323                return CstChar.make(value.getValue());
324            }
325            case 'D': {
326                CstDouble value = (CstDouble) parseConstant();
327                return value;
328            }
329            case 'F': {
330                CstFloat value = (CstFloat) parseConstant();
331                return value;
332            }
333            case 'I': {
334                CstInteger value = (CstInteger) parseConstant();
335                return value;
336            }
337            case 'J': {
338                CstLong value = (CstLong) parseConstant();
339                return value;
340            }
341            case 'S': {
342                CstInteger value = (CstInteger) parseConstant();
343                return CstShort.make(value.getValue());
344            }
345            case 'Z': {
346                CstInteger value = (CstInteger) parseConstant();
347                return CstBoolean.make(value.getValue());
348            }
349            case 'c': {
350                int classInfoIndex = input.readUnsignedShort();
351                CstString value = (CstString) pool.get(classInfoIndex);
352                Type type = Type.internReturnType(value.getString());
353
354                if (observer != null) {
355                    parsed(2, "class_info: " + type.toHuman());
356                }
357
358                return new CstType(type);
359            }
360            case 's': {
361                return parseConstant();
362            }
363            case 'e': {
364                requireLength(4);
365
366                int typeNameIndex = input.readUnsignedShort();
367                int constNameIndex = input.readUnsignedShort();
368                CstString typeName = (CstString) pool.get(typeNameIndex);
369                CstString constName = (CstString) pool.get(constNameIndex);
370
371                if (observer != null) {
372                    parsed(2, "type_name: " + typeName.toHuman());
373                    parsed(2, "const_name: " + constName.toHuman());
374                }
375
376                return new CstEnumRef(new CstNat(constName, typeName));
377            }
378            case '@': {
379                Annotation annotation =
380                    parseAnnotation(AnnotationVisibility.EMBEDDED);
381                return new CstAnnotation(annotation);
382            }
383            case '[': {
384                requireLength(2);
385
386                int numValues = input.readUnsignedShort();
387                CstArray.List list = new CstArray.List(numValues);
388
389                if (observer != null) {
390                    parsed(2, "num_values: " + numValues);
391                    changeIndent(1);
392                }
393
394                for (int i = 0; i < numValues; i++) {
395                    if (observer != null) {
396                        changeIndent(-1);
397                        parsed(0, "element_value[" + i + "]:");
398                        changeIndent(1);
399                    }
400                    list.set(i, parseValue());
401                }
402
403                if (observer != null) {
404                    changeIndent(-1);
405                }
406
407                list.setImmutable();
408                return new CstArray(list);
409            }
410            default: {
411                throw new ParseException("unknown annotation tag: " +
412                        Hex.u1(tag));
413            }
414        }
415    }
416
417    /**
418     * Helper for {@link #parseValue}, which parses a constant reference
419     * and returns the referred-to constant value.
420     *
421     * @return {@code non-null;} the parsed value
422     */
423    private Constant parseConstant() throws IOException {
424        int constValueIndex = input.readUnsignedShort();
425        Constant value = (Constant) pool.get(constValueIndex);
426
427        if (observer != null) {
428            String human = (value instanceof CstString)
429                ? ((CstString) value).toQuoted()
430                : value.toHuman();
431            parsed(2, "constant_value: " + human);
432        }
433
434        return value;
435    }
436
437    /**
438     * Helper which will throw an exception if the given number of bytes
439     * is not available to be read.
440     *
441     * @param requiredLength the number of required bytes
442     */
443    private void requireLength(int requiredLength) throws IOException {
444        if (input.available() < requiredLength) {
445            throw new ParseException("truncated annotation attribute");
446        }
447    }
448
449    /**
450     * Helper which indicates that some bytes were just parsed. This should
451     * only be used (for efficiency sake) if the parse is known to be
452     * observed.
453     *
454     * @param length {@code >= 0;} number of bytes parsed
455     * @param message {@code non-null;} associated message
456     */
457    private void parsed(int length, String message) {
458        observer.parsed(bytes, parseCursor, length, message);
459        parseCursor += length;
460    }
461
462    /**
463     * Convenience wrapper that simply calls through to
464     * {@code observer.changeIndent()}.
465     *
466     * @param indent the amount to change the indent by
467     */
468    private void changeIndent(int indent) {
469        observer.changeIndent(indent);
470    }
471}
472