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