1/*
2 * Copyright (C) 2009 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 signature.converter.dex;
18
19import java.io.IOException;
20import java.util.Collections;
21import java.util.EnumSet;
22import java.util.HashSet;
23import java.util.List;
24import java.util.Set;
25
26import signature.converter.Visibility;
27import signature.model.IClassDefinition;
28import signature.model.Kind;
29import signature.model.Modifier;
30import signature.model.impl.SigPackage;
31import signature.model.util.ModelUtil;
32import dex.reader.DexBuffer;
33import dex.reader.DexFileReader;
34import dex.structure.DexAnnotatedElement;
35import dex.structure.DexAnnotation;
36import dex.structure.DexAnnotationAttribute;
37import dex.structure.DexClass;
38import dex.structure.DexEncodedValue;
39import dex.structure.DexField;
40import dex.structure.DexFile;
41import dex.structure.DexMethod;
42
43
44public class DexUtil {
45
46    private static final String PACKAGE_INFO = "package-info";
47    private static final String THROWS_ANNOTATION =
48            "Ldalvik/annotation/Throws;";
49    private static final String SIGNATURE_ANNOTATION =
50            "Ldalvik/annotation/Signature;";
51    private static final String ANNOTATION_DEFAULT_ANNOTATION =
52            "Ldalvik/annotation/AnnotationDefault;";
53    private static final String ENCLOSING_CLASS_ANNOTATION =
54            "Ldalvik/annotation/EnclosingClass;";
55    private static final String ENCLOSING_METHOD_ANNOTATION =
56            "Ldalvik/annotation/EnclosingMethod;";
57    private static final String INNER_CLASS_ANNOTATION =
58            "Ldalvik/annotation/InnerClass;";
59    private static final String MEMBER_CLASS_ANNOTATION =
60            "Ldalvik/annotation/MemberClasses;";
61    private static final String JAVA_LANG_OBJECT = "Ljava/lang/Object;";
62
63    private static final Set<String> INTERNAL_ANNOTATION_NAMES;
64
65    static {
66        Set<String> tmp = new HashSet<String>();
67        tmp.add(THROWS_ANNOTATION);
68        tmp.add(SIGNATURE_ANNOTATION);
69        tmp.add(ANNOTATION_DEFAULT_ANNOTATION);
70        tmp.add(ENCLOSING_CLASS_ANNOTATION);
71        tmp.add(ENCLOSING_METHOD_ANNOTATION);
72        tmp.add(INNER_CLASS_ANNOTATION);
73        tmp.add(MEMBER_CLASS_ANNOTATION);
74        INTERNAL_ANNOTATION_NAMES = Collections.unmodifiableSet(tmp);
75    }
76
77    private DexUtil() {
78        // not constructable from outside
79    }
80
81    /**
82     * "La/b/c/A;" -> "a.b.c" "LA;" -> "" empty string
83     *
84     * @param classIdentifier
85     * @return the package name
86     */
87    public static String getPackageName(String classIdentifier) {
88        String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
89        return ModelUtil.getPackageName(name.replace("/", "."));
90    }
91
92    /**
93     * "La/b/c/A;" -> "A" "LA;" -> "A"
94     *
95     * @param classIdentifier
96     *            the dalvik internal identifier
97     * @return the class name
98     */
99    public static String getClassName(String classIdentifier) {
100        String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
101        return ModelUtil.getClassName(name.replace("/", ".")).replace('$', '.');
102    }
103
104    public static String getQualifiedName(String classIdentifier) {
105        String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
106        return name.replace('/', '.');
107    }
108
109    private static String removeHeadingL(String className) {
110        assert className.startsWith("L");
111        return className.substring(1);
112    }
113
114    private static String removeTrailingSemicolon(String className) {
115        assert className.endsWith(";");
116        return className.substring(0, className.length() - 1);
117    }
118
119    public static String getDexName(String packageName, String className) {
120        return "L" + packageName.replace('.', '/') + "/"
121                + className.replace('.', '$') + ";";
122    }
123
124    public static String getDexName(IClassDefinition sigClass) {
125        return getDexName(sigClass.getPackageName(), sigClass.getName());
126    }
127
128    /**
129     * Returns correct modifiers for inner classes
130     */
131    public static int getClassModifiers(DexClass clazz) {
132        int modifiers = 0;
133        if (isInnerClass(clazz)) {
134            Integer accessFlags = (Integer) getAnnotationAttributeValue(
135                    getAnnotation(clazz, INNER_CLASS_ANNOTATION),
136                            "accessFlags");
137            modifiers = accessFlags.intValue();
138        } else {
139            modifiers = clazz.getModifiers();
140        }
141        return modifiers;
142    }
143
144    /**
145     * Returns a set containing all modifiers for the given int.
146     *
147     * @param mod
148     *            the original bit coded modifiers as specified by
149     *            {@link java.lang.reflect.Modifier}
150     * @return a set containing {@link signature.model.Modifier} elements
151     */
152    public static Set<Modifier> getModifier(int mod) {
153        Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
154        if (java.lang.reflect.Modifier.isAbstract(mod))
155            modifiers.add(Modifier.ABSTRACT);
156        if (java.lang.reflect.Modifier.isFinal(mod))
157            modifiers.add(Modifier.FINAL);
158        // if (java.lang.reflect.Modifier.isNative(mod))
159        // modifiers.add(Modifier.NATIVE);
160        if (java.lang.reflect.Modifier.isPrivate(mod))
161            modifiers.add(Modifier.PRIVATE);
162        if (java.lang.reflect.Modifier.isProtected(mod))
163            modifiers.add(Modifier.PROTECTED);
164        if (java.lang.reflect.Modifier.isPublic(mod))
165            modifiers.add(Modifier.PUBLIC);
166        if (java.lang.reflect.Modifier.isStatic(mod))
167            modifiers.add(Modifier.STATIC);
168        // if (java.lang.reflect.Modifier.isStrict(mod))
169        // modifiers.add(Modifier.STRICT);
170        // if (java.lang.reflect.Modifier.isSynchronized(mod))
171        // modifiers.add(Modifier.SYNCHRONIZED);
172        // if (java.lang.reflect.Modifier.isTransient(mod))
173        // modifiers.add(Modifier.TRANSIENT);
174        if (java.lang.reflect.Modifier.isVolatile(mod))
175            modifiers.add(Modifier.VOLATILE);
176
177        return modifiers;
178    }
179
180    /**
181     * Returns true if the given class is an enumeration, false otherwise.
182     *
183     * @param dexClass
184     *            the DexClass under test
185     * @return true if the given class is an enumeration, false otherwise
186     */
187    public static boolean isEnum(DexClass dexClass) {
188        return (getClassModifiers(dexClass) & 0x4000) > 0;
189    }
190
191    /**
192     * Returns true if the given class is an interface, false otherwise.
193     *
194     * @param dexClass
195     *            the DexClass under test
196     * @return true if the given class is an interface, false otherwise
197     */
198    public static boolean isInterface(DexClass dexClass) {
199        int modifiers = getClassModifiers(dexClass);
200        return java.lang.reflect.Modifier.isInterface(modifiers);
201    }
202
203    /**
204     * Returns true if the given class is an annotation, false otherwise.
205     *
206     * @param dexClass
207     *            the DexClass under test
208     * @return true if the given class is an annotation, false otherwise
209     */
210    public static boolean isAnnotation(DexClass dexClass) {
211        return (getClassModifiers(dexClass) & 0x2000) > 0;
212    }
213
214    public static boolean isSynthetic(int modifier) {
215        return (modifier & 0x1000) > 0;
216    }
217
218    /**
219     * Returns the Kind of the given DexClass.
220     *
221     * @param dexClass
222     *            the DexClass under test
223     * @return the Kind of the given class
224     */
225    public static Kind getKind(DexClass dexClass) {
226        // order of branches is crucial since a annotation is also an interface
227        if (isEnum(dexClass)) {
228            return Kind.ENUM;
229        } else if (isAnnotation(dexClass)) {
230            return Kind.ANNOTATION;
231        } else if (isInterface(dexClass)) {
232            return Kind.INTERFACE;
233        } else {
234            return Kind.CLASS;
235        }
236    }
237
238    /**
239     * Returns whether the specified annotated element has an annotation with
240     * type "Ldalvik/annotation/Throws;".
241     *
242     * @param annotatedElement
243     *            the annotated element to check
244     * @return <code>true</code> if the given annotated element has the
245     *         mentioned annotation, false otherwise
246     */
247    public static boolean declaresExceptions(
248            DexAnnotatedElement annotatedElement) {
249        return getAnnotation(annotatedElement, THROWS_ANNOTATION) != null;
250    }
251
252    /**
253     * Returns the throws signature if the given element has such an annotation,
254     * null otherwise.
255     *
256     * @param annotatedElement
257     *            the annotated element
258     * @return he generic signature if the given element has such an annotation,
259     *         null otherwise
260     */
261    @SuppressWarnings("unchecked")
262    public static String getExceptionSignature(
263            DexAnnotatedElement annotatedElement) {
264        DexAnnotation annotation = getAnnotation(annotatedElement,
265                THROWS_ANNOTATION);
266        if (annotation != null) {
267            List<DexEncodedValue> value =
268                    (List<DexEncodedValue>) getAnnotationAttributeValue(
269                            annotation, "value");
270            return concatEncodedValues(value);
271        }
272        return null;
273    }
274
275    /**
276     * Splits a list of types:
277     * "Ljava/io/IOException;Ljava/lang/IllegalStateException;" <br>
278     * into separate type designators: <br>
279     * "Ljava/io/IOException;" , "Ljava/lang/IllegalStateException;"
280     *
281     * @param typeList
282     *            the type list
283     * @return a set of type designators
284     */
285    public static Set<String> splitTypeList(String typeList) {
286        String[] split = typeList.split(";");
287        Set<String> separateTypes = new HashSet<String>();
288        for (String string : split) {
289            separateTypes.add(string + ";");// add semicolon again
290        }
291        return separateTypes;
292    }
293
294    /**
295     * Returns whether the specified annotated element has an annotation with
296     * type "Ldalvik/annotation/Signature;".
297     *
298     * @param annotatedElement
299     *            the annotated element to check
300     * @return <code>true</code> if the given annotated element has the
301     *         mentioned annotation, false otherwise
302     */
303    public static boolean hasGenericSignature(
304            DexAnnotatedElement annotatedElement) {
305        return getAnnotation(annotatedElement, SIGNATURE_ANNOTATION) != null;
306    }
307
308    /**
309     * Returns the generic signature if the given element has such an
310     * annotation, null otherwise.
311     *
312     * @param annotatedElement
313     *            the annotated element
314     * @return he generic signature if the given element has such an annotation,
315     *         null otherwise
316     */
317    @SuppressWarnings("unchecked")
318    public static String getGenericSignature(
319            DexAnnotatedElement annotatedElement) {
320        DexAnnotation annotation = getAnnotation(annotatedElement,
321                SIGNATURE_ANNOTATION);
322        if (annotation != null) {
323            List<DexEncodedValue> value =
324                    (List<DexEncodedValue>) getAnnotationAttributeValue(
325                            annotation, "value");
326            return concatEncodedValues(value);
327        }
328        return null;
329    }
330
331    /**
332     * Returns whether the specified annotated element has an annotation with
333     * type "Ldalvik/annotation/AnnotationDefault;".
334     *
335     * @param annotatedElement
336     *            the annotated element to check
337     * @return <code>true</code> if the given annotated element has the
338     *         mentioned annotation, false otherwise
339     */
340    public static boolean hasAnnotationDefaultSignature(
341            DexAnnotatedElement annotatedElement) {
342        return getAnnotation(
343                annotatedElement, ANNOTATION_DEFAULT_ANNOTATION)!= null;
344    }
345
346    /**
347     * Returns a mapping form annotation attribute name to its default value.
348     *
349     * @param dexClass
350     *            the class defining a annotation
351     * @return a mapping form annotation attribute name to its default value
352     */
353    public static DexAnnotation getDefaultMappingsAnnotation(
354            DexClass dexClass) {
355        return getAnnotation(dexClass, ANNOTATION_DEFAULT_ANNOTATION);
356    }
357
358    /**
359     * Returns the annotation with the specified type from the given element or
360     * null if no such annotation is available.
361     *
362     * @param element
363     *            the annotated element
364     * @param annotationType
365     *            the dex internal name of the annotation type
366     * @return the annotation with the specified type or null if not present
367     */
368    public static DexAnnotation getAnnotation(DexAnnotatedElement element,
369            String annotationType) {
370        assert element != null;
371        assert annotationType != null;
372
373        for (DexAnnotation anno : element.getAnnotations()) {
374            if (annotationType.equals(anno.getTypeName())) {
375                return anno;
376            }
377        }
378        return null;
379    }
380
381    /**
382     * Returns the value for the specified attribute name of the given
383     * annotation or null if not present.
384     *
385     * @param annotation
386     *            the annotation
387     * @param attributeName
388     *            the name of the attribute
389     * @return the value for the specified attribute
390     */
391    public static Object getAnnotationAttributeValue(DexAnnotation annotation,
392            String attributeName) {
393        for (DexAnnotationAttribute dexAnnotationAttribute : annotation
394                .getAttributes()) {
395            if (attributeName.equals(dexAnnotationAttribute.getName())) {
396                return dexAnnotationAttribute.getEncodedValue().getValue();
397            }
398        }
399        return null;
400    }
401
402    private static String concatEncodedValues(List<DexEncodedValue> values) {
403        StringBuilder builder = new StringBuilder();
404        for (DexEncodedValue string : values) {
405            builder.append(string.getValue());
406        }
407        return builder.toString();
408    }
409
410    /**
411     * Returns true if the given method is a constructor, false otherwise.
412     *
413     * @param method
414     *            the method to test
415     * @return true if the given method is a constructor, false otherwise
416     */
417    public static boolean isConstructor(DexMethod method) {
418        return "<init>".equals(method.getName());
419    }
420
421    /**
422     * Returns true if the given method is a static constructor, false
423     * otherwise.
424     *
425     * @param method
426     *            the method to test
427     * @return true if the given method is a static constructor, false otherwise
428     */
429    public static boolean isStaticConstructor(DexMethod method) {
430        return "<clinit>".equals(method.getName());
431    }
432
433    public static boolean isMethod(DexMethod method) {
434        return !isConstructor(method) && !isStaticConstructor(method);
435    }
436
437    /**
438     * Returns the package-info class for the given package.
439     *
440     * @param aPackage
441     *            the package
442     * @return the class called "package-info" or null, if not available
443     */
444    public static IClassDefinition findPackageInfo(SigPackage aPackage) {
445        for (IClassDefinition clazz : aPackage.getClasses()) {
446            if (PACKAGE_INFO.equals(clazz.getName())) {
447                return clazz;
448            }
449        }
450        return null;
451    }
452
453    public static boolean isPackageInfo(DexClass clazz) {
454        return PACKAGE_INFO.equals(getClassName(clazz.getName()));
455    }
456
457    public static boolean isInternalAnnotation(DexAnnotation dexAnnotation) {
458        return INTERNAL_ANNOTATION_NAMES.contains(dexAnnotation.getTypeName());
459    }
460
461    /**
462     * An InnerClass annotation is attached to each class which is defined in
463     * the lexical scope of another class's definition. Any class which has this
464     * annotation must also have either an EnclosingClass annotation or an
465     * EnclosingMethod annotation.
466     */
467    public static boolean isInnerClass(DexClass clazz) {
468        return getAnnotation(clazz, INNER_CLASS_ANNOTATION) != null;
469    }
470
471    /**
472     * An EnclosingClass annotation is attached to each class which is either
473     * defined as a member of another class, per se, or is anonymous but not
474     * defined within a method body (e.g., a synthetic inner class). Every class
475     * that has this annotation must also have an InnerClass annotation.
476     * Additionally, a class may not have both an EnclosingClass and an
477     * EnclosingMethod annotation.
478     */
479    public static boolean isEnclosingClass(DexClass clazz) {
480        return getAnnotation(clazz, ENCLOSING_CLASS_ANNOTATION) != null;
481    }
482
483    public static boolean declaresMemberClasses(DexClass dexClass) {
484        return getAnnotation(dexClass, MEMBER_CLASS_ANNOTATION) != null;
485    }
486
487    @SuppressWarnings("unchecked")
488    public static Set<String> getMemberClassNames(DexClass dexClass) {
489        DexAnnotation annotation = getAnnotation(dexClass,
490                MEMBER_CLASS_ANNOTATION);
491        List<DexEncodedValue> enclosedClasses =
492                (List<DexEncodedValue>) getAnnotationAttributeValue(
493                        annotation, "value");
494        Set<String> enclosedClassesNames = new HashSet<String>();
495        for (DexEncodedValue string : enclosedClasses) {
496            enclosedClassesNames.add((String) string.getValue());
497        }
498        return enclosedClassesNames;
499    }
500
501
502    public static String getEnclosingClassName(DexClass dexClass) {
503        DexAnnotation annotation = getAnnotation(dexClass,
504                ENCLOSING_CLASS_ANNOTATION);
505        String value = (String) getAnnotationAttributeValue(annotation,
506                "value");
507        return value;
508    }
509
510    public static boolean convertAnyWay(DexClass dexClass) {
511        return !isSynthetic(getClassModifiers(dexClass))
512                && !isAnonymousClassName(dexClass.getName())
513                || isPackageInfo(dexClass);
514    }
515
516    public static boolean isVisible(DexClass dexClass, Visibility visibility) {
517        // package info is always visible
518        if (isPackageInfo(dexClass)) {
519            return true;
520        }
521
522        if (isDeclaredInMethod(dexClass)) {
523            return false;
524        }
525
526        if (isAnonymousClassName(dexClass.getName())) {
527            return false;
528        }
529
530        int modifiers = getClassModifiers(dexClass);
531
532        return isVisible(modifiers, visibility);
533    }
534
535    private static boolean isDeclaredInMethod(DexClass dexClass) {
536        return getAnnotation(dexClass, ENCLOSING_METHOD_ANNOTATION) != null;
537    }
538
539    /**
540     * Returns whether the given dex identifier is an anonymous class name.
541     * Format: La/b/C$1;
542     *
543     * @param dexName
544     *            the name to analyze
545     * @return whether the given dex identifier is an anonymous class name
546     */
547    public static boolean isAnonymousClassName(String dexName) {
548        int index = dexName.lastIndexOf('$');
549        return (index != 0) ? Character.isDigit(dexName.charAt(index + 1))
550                : false;
551    }
552
553    public static boolean isVisible(DexField dexField, Visibility visibility) {
554        return isVisible(dexField.getModifiers(), visibility);
555    }
556
557    public static boolean isVisible(DexMethod dexMethod,
558            Visibility visibility) {
559        return isVisible(dexMethod.getModifiers(), visibility);
560    }
561
562    private static boolean isVisible(int modifiers, Visibility visibility) {
563
564        if (isSynthetic(modifiers)) {
565            return false;
566        }
567
568        Set<Modifier> elementModifiers = getModifier(modifiers);
569        if (elementModifiers.contains(Modifier.PUBLIC)) {
570            return true;
571        } else if (elementModifiers.contains(Modifier.PROTECTED)) {
572            return visibility == Visibility.PROTECTED
573                    || visibility == Visibility.PACKAGE
574                    || visibility == Visibility.PRIVATE;
575        } else if (elementModifiers.contains(Modifier.PRIVATE)) {
576            return visibility == Visibility.PRIVATE;
577        } else {
578            return visibility == Visibility.PACKAGE
579                    || visibility == Visibility.PRIVATE;
580        }
581    }
582
583    public static Set<DexFile> getDexFiles(Set<String> fileNames)
584            throws IOException {
585        Set<DexFile> parsedFiles = new HashSet<DexFile>();
586
587        for (String dexFile : fileNames) {
588            DexFileReader reader = new DexFileReader();
589            DexBuffer dexBuffer = new DexBuffer(dexFile);
590            parsedFiles.add(reader.read(dexBuffer));
591        }
592        return parsedFiles;
593    }
594
595
596    public static boolean isJavaLangObject(DexClass dexClass) {
597        assert dexClass != null;
598        return JAVA_LANG_OBJECT.equals(dexClass.getName());
599    }
600}
601