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