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.dex.cf;
18
19import com.android.dx.cf.attrib.AttAnnotationDefault;
20import com.android.dx.cf.attrib.AttEnclosingMethod;
21import com.android.dx.cf.attrib.AttExceptions;
22import com.android.dx.cf.attrib.AttInnerClasses;
23import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations;
24import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations;
25import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
26import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations;
27import com.android.dx.cf.attrib.AttSignature;
28import com.android.dx.cf.attrib.AttSourceDebugExtension;
29import com.android.dx.cf.attrib.InnerClassList;
30import com.android.dx.cf.direct.DirectClassFile;
31import com.android.dx.cf.iface.AttributeList;
32import com.android.dx.cf.iface.Method;
33import com.android.dx.cf.iface.MethodList;
34import com.android.dx.dex.file.AnnotationUtils;
35import com.android.dx.rop.annotation.Annotation;
36import com.android.dx.rop.annotation.AnnotationVisibility;
37import com.android.dx.rop.annotation.Annotations;
38import com.android.dx.rop.annotation.AnnotationsList;
39import com.android.dx.rop.annotation.NameValuePair;
40import com.android.dx.rop.code.AccessFlags;
41import com.android.dx.rop.cst.CstMethodRef;
42import com.android.dx.rop.cst.CstNat;
43import com.android.dx.rop.cst.CstType;
44import com.android.dx.rop.type.StdTypeList;
45import com.android.dx.rop.type.Type;
46import com.android.dx.rop.type.TypeList;
47import com.android.dx.util.Warning;
48import java.util.ArrayList;
49
50/**
51 * Utility methods that translate various classfile attributes
52 * into forms suitable for use in creating {@code dex} files.
53 */
54/*package*/ class AttributeTranslator {
55    /**
56     * This class is uninstantiable.
57     */
58    private AttributeTranslator() {
59        // This space intentionally left blank.
60    }
61
62    /**
63     * Gets the list of thrown exceptions for a given method.
64     *
65     * @param method {@code non-null;} the method in question
66     * @return {@code non-null;} the list of thrown exceptions
67     */
68    public static TypeList getExceptions(Method method) {
69        AttributeList attribs = method.getAttributes();
70        AttExceptions exceptions = (AttExceptions)
71            attribs.findFirst(AttExceptions.ATTRIBUTE_NAME);
72
73        if (exceptions == null) {
74            return StdTypeList.EMPTY;
75        }
76
77        return exceptions.getExceptions();
78    }
79
80    /**
81     * Gets the annotations out of a given {@link AttributeList}. This
82     * combines both visible and invisible annotations into a single
83     * result set and also adds in a system annotation for the
84     * {@code Signature} attribute if present.
85     *
86     * @param attribs {@code non-null;} the attributes list to search in
87     * @return {@code non-null;} the set of annotations, which may be empty
88     */
89    public static Annotations getAnnotations(AttributeList attribs) {
90        Annotations result = getAnnotations0(attribs);
91        Annotation signature = getSignature(attribs);
92        Annotation sourceDebugExtension = getSourceDebugExtension(attribs);
93
94        if (signature != null) {
95            result = Annotations.combine(result, signature);
96        }
97
98        if (sourceDebugExtension != null) {
99            result = Annotations.combine(result, sourceDebugExtension);
100        }
101
102        return result;
103    }
104
105    /**
106     * Gets the annotations out of a given class, similar to {@link
107     * #getAnnotations}, also including annotations for translations
108     * of class-level attributes {@code EnclosingMethod} and
109     * {@code InnerClasses}, if present. Additionally, if the
110     * class is an annotation class, then this also includes a
111     * representation of all the {@code AnnotationDefault}
112     * values.
113     *
114     * @param cf {@code non-null;} the class in question
115     * @param args {@code non-null;} the high-level options
116     * @return {@code non-null;} the set of annotations, which may be empty
117     */
118    public static Annotations getClassAnnotations(DirectClassFile cf,
119            CfOptions args) {
120        CstType thisClass = cf.getThisClass();
121        AttributeList attribs = cf.getAttributes();
122        Annotations result = getAnnotations(attribs);
123        Annotation enclosingMethod = translateEnclosingMethod(attribs);
124
125        try {
126            Annotations innerClassAnnotations =
127                translateInnerClasses(thisClass, attribs,
128                        enclosingMethod == null);
129            if (innerClassAnnotations != null) {
130                result = Annotations.combine(result, innerClassAnnotations);
131            }
132        } catch (Warning warn) {
133            args.warn.println("warning: " + warn.getMessage());
134        }
135
136        if (enclosingMethod != null) {
137            result = Annotations.combine(result, enclosingMethod);
138        }
139
140        if (AccessFlags.isAnnotation(cf.getAccessFlags())) {
141            Annotation annotationDefault =
142                translateAnnotationDefaults(cf);
143            if (annotationDefault != null) {
144                result = Annotations.combine(result, annotationDefault);
145            }
146        }
147
148        return result;
149    }
150
151    /**
152     * Gets the annotations out of a given method, similar to {@link
153     * #getAnnotations}, also including an annotation for the translation
154     * of the method-specific attribute {@code Exceptions}.
155     *
156     * @param method {@code non-null;} the method in question
157     * @return {@code non-null;} the set of annotations, which may be empty
158     */
159    public static Annotations getMethodAnnotations(Method method) {
160        Annotations result = getAnnotations(method.getAttributes());
161        TypeList exceptions = getExceptions(method);
162
163        if (exceptions.size() != 0) {
164            Annotation throwsAnnotation =
165                AnnotationUtils.makeThrows(exceptions);
166            result = Annotations.combine(result, throwsAnnotation);
167        }
168
169        return result;
170    }
171
172    /**
173     * Helper method for {@link #getAnnotations} which just gets the
174     * existing annotations, per se.
175     *
176     * @param attribs {@code non-null;} the attributes list to search in
177     * @return {@code non-null;} the set of annotations, which may be empty
178     */
179    private static Annotations getAnnotations0(AttributeList attribs) {
180        AttRuntimeVisibleAnnotations visible =
181            (AttRuntimeVisibleAnnotations)
182            attribs.findFirst(AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
183        AttRuntimeInvisibleAnnotations invisible =
184            (AttRuntimeInvisibleAnnotations)
185            attribs.findFirst(AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
186
187        if (visible == null) {
188            if (invisible == null) {
189                return Annotations.EMPTY;
190            }
191            return invisible.getAnnotations();
192        }
193
194        if (invisible == null) {
195            return visible.getAnnotations();
196        }
197
198        // Both are non-null, so combine them.
199
200        return Annotations.combine(visible.getAnnotations(),
201                invisible.getAnnotations());
202    }
203
204    /**
205     * Gets the {@code Signature} attribute out of a given
206     * {@link AttributeList}, if any, translating it to an annotation.
207     *
208     * @param attribs {@code non-null;} the attributes list to search in
209     * @return {@code null-ok;} the converted {@code Signature} annotation,
210     * if there was an attribute to translate
211     */
212    private static Annotation getSignature(AttributeList attribs) {
213        AttSignature signature = (AttSignature)
214            attribs.findFirst(AttSignature.ATTRIBUTE_NAME);
215
216        if (signature == null) {
217            return null;
218        }
219
220        return AnnotationUtils.makeSignature(signature.getSignature());
221    }
222
223
224    private static Annotation getSourceDebugExtension(AttributeList attribs) {
225        AttSourceDebugExtension extension = (AttSourceDebugExtension)
226            attribs.findFirst(AttSourceDebugExtension.ATTRIBUTE_NAME);
227
228        if (extension == null) {
229            return null;
230        }
231
232        return AnnotationUtils.makeSourceDebugExtension(extension.getSmapString());
233    }
234
235    /**
236     * Gets the {@code EnclosingMethod} attribute out of a given
237     * {@link AttributeList}, if any, translating it to an annotation.
238     * If the class really has an enclosing method, this returns an
239     * {@code EnclosingMethod} annotation; if not, this returns
240     * an {@code EnclosingClass} annotation.
241     *
242     * @param attribs {@code non-null;} the attributes list to search in
243     * @return {@code null-ok;} the converted {@code EnclosingMethod} or
244     * {@code EnclosingClass} annotation, if there was an
245     * attribute to translate
246     */
247    private static Annotation translateEnclosingMethod(AttributeList attribs) {
248        AttEnclosingMethod enclosingMethod = (AttEnclosingMethod)
249            attribs.findFirst(AttEnclosingMethod.ATTRIBUTE_NAME);
250
251        if (enclosingMethod == null) {
252            return null;
253        }
254
255        CstType enclosingClass = enclosingMethod.getEnclosingClass();
256        CstNat nat = enclosingMethod.getMethod();
257
258        if (nat == null) {
259            /*
260             * Dalvik doesn't use EnclosingMethod annotations unless
261             * there really is an enclosing method. Anonymous classes
262             * are unambiguously identified by having an InnerClass
263             * annotation with an empty name along with an appropriate
264             * EnclosingClass.
265             */
266            return AnnotationUtils.makeEnclosingClass(enclosingClass);
267        }
268
269        return AnnotationUtils.makeEnclosingMethod(
270                new CstMethodRef(enclosingClass, nat));
271    }
272
273    /**
274     * Gets the {@code InnerClasses} attribute out of a given
275     * {@link AttributeList}, if any, translating it to one or more of an
276     * {@code InnerClass}, {@code EnclosingClass}, or
277     * {@code MemberClasses} annotation.
278     *
279     * @param thisClass {@code non-null;} type representing the class being
280     * processed
281     * @param attribs {@code non-null;} the attributes list to search in
282     * @param needEnclosingClass whether to include an
283     * {@code EnclosingClass} annotation
284     * @return {@code null-ok;} the converted list of annotations, if there
285     * was an attribute to translate
286     */
287    private static Annotations translateInnerClasses(CstType thisClass,
288            AttributeList attribs, boolean needEnclosingClass) {
289        AttInnerClasses innerClasses = (AttInnerClasses)
290            attribs.findFirst(AttInnerClasses.ATTRIBUTE_NAME);
291
292        if (innerClasses == null) {
293            return null;
294        }
295
296        /*
297         * Search the list for the element representing the current class
298         * as well as for any named member classes.
299         */
300
301        InnerClassList list = innerClasses.getInnerClasses();
302        int size = list.size();
303        InnerClassList.Item foundThisClass = null;
304        ArrayList<Type> membersList = new ArrayList<Type>();
305
306        for (int i = 0; i < size; i++) {
307            InnerClassList.Item item = list.get(i);
308            CstType innerClass = item.getInnerClass();
309            if (innerClass.equals(thisClass)) {
310                foundThisClass = item;
311            } else if (thisClass.equals(item.getOuterClass())) {
312                membersList.add(innerClass.getClassType());
313            }
314        }
315
316        int membersSize = membersList.size();
317
318        if ((foundThisClass == null) && (membersSize == 0)) {
319            return null;
320        }
321
322        Annotations result = new Annotations();
323
324        if (foundThisClass != null) {
325            result.add(AnnotationUtils.makeInnerClass(
326                               foundThisClass.getInnerName(),
327                               foundThisClass.getAccessFlags()));
328            if (needEnclosingClass) {
329                CstType outer = foundThisClass.getOuterClass();
330                if (outer == null) {
331                    throw new Warning(
332                            "Ignoring InnerClasses attribute for an " +
333                            "anonymous inner class\n" +
334                            "(" + thisClass.toHuman() +
335                            ") that doesn't come with an\n" +
336                            "associated EnclosingMethod attribute. " +
337                            "This class was probably produced by a\n" +
338                            "compiler that did not target the modern " +
339                            ".class file format. The recommended\n" +
340                            "solution is to recompile the class from " +
341                            "source, using an up-to-date compiler\n" +
342                            "and without specifying any \"-target\" type " +
343                            "options. The consequence of ignoring\n" +
344                            "this warning is that reflective operations " +
345                            "on this class will incorrectly\n" +
346                            "indicate that it is *not* an inner class.");
347                }
348                result.add(AnnotationUtils.makeEnclosingClass(
349                                   foundThisClass.getOuterClass()));
350            }
351        }
352
353        if (membersSize != 0) {
354            StdTypeList typeList = new StdTypeList(membersSize);
355            for (int i = 0; i < membersSize; i++) {
356                typeList.set(i, membersList.get(i));
357            }
358            typeList.setImmutable();
359            result.add(AnnotationUtils.makeMemberClasses(typeList));
360        }
361
362        result.setImmutable();
363        return result;
364    }
365
366    /**
367     * Gets the parameter annotations out of a given method. This
368     * combines both visible and invisible annotations into a single
369     * result set.
370     *
371     * @param method {@code non-null;} the method in question
372     * @return {@code non-null;} the list of annotation sets, which may be
373     * empty
374     */
375    public static AnnotationsList getParameterAnnotations(Method method) {
376        AttributeList attribs = method.getAttributes();
377        AttRuntimeVisibleParameterAnnotations visible =
378            (AttRuntimeVisibleParameterAnnotations)
379            attribs.findFirst(
380                    AttRuntimeVisibleParameterAnnotations.ATTRIBUTE_NAME);
381        AttRuntimeInvisibleParameterAnnotations invisible =
382            (AttRuntimeInvisibleParameterAnnotations)
383            attribs.findFirst(
384                    AttRuntimeInvisibleParameterAnnotations.ATTRIBUTE_NAME);
385
386        if (visible == null) {
387            if (invisible == null) {
388                return AnnotationsList.EMPTY;
389            }
390            return invisible.getParameterAnnotations();
391        }
392
393        if (invisible == null) {
394            return visible.getParameterAnnotations();
395        }
396
397        // Both are non-null, so combine them.
398
399        return AnnotationsList.combine(visible.getParameterAnnotations(),
400                invisible.getParameterAnnotations());
401    }
402
403    /**
404     * Gets the {@code AnnotationDefault} attributes out of a
405     * given class, if any, reforming them as an
406     * {@code AnnotationDefault} annotation.
407     *
408     * @param cf {@code non-null;} the class in question
409     * @return {@code null-ok;} an appropriately-constructed
410     * {@code AnnotationDefault} annotation, if there were any
411     * annotation defaults in the class, or {@code null} if not
412     */
413    private static Annotation translateAnnotationDefaults(DirectClassFile cf) {
414        CstType thisClass = cf.getThisClass();
415        MethodList methods = cf.getMethods();
416        int sz = methods.size();
417        Annotation result =
418            new Annotation(thisClass, AnnotationVisibility.EMBEDDED);
419        boolean any = false;
420
421        for (int i = 0; i < sz; i++) {
422            Method one = methods.get(i);
423            AttributeList attribs = one.getAttributes();
424            AttAnnotationDefault oneDefault = (AttAnnotationDefault)
425                attribs.findFirst(AttAnnotationDefault.ATTRIBUTE_NAME);
426
427            if (oneDefault != null) {
428                NameValuePair pair = new NameValuePair(
429                        one.getNat().getName(),
430                        oneDefault.getValue());
431                result.add(pair);
432                any = true;
433            }
434        }
435
436        if (! any) {
437            return null;
438        }
439
440        result.setImmutable();
441        return AnnotationUtils.makeAnnotationDefault(result);
442    }
443}
444