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