blob: f661af2ae42929235945f6fb108e83f0ab270caa [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package signature.converter.dex;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import signature.converter.Visibility;
import signature.model.IClassDefinition;
import signature.model.Kind;
import signature.model.Modifier;
import signature.model.impl.SigPackage;
import signature.model.util.ModelUtil;
import dex.reader.DexBuffer;
import dex.reader.DexFileReader;
import dex.structure.DexAnnotatedElement;
import dex.structure.DexAnnotation;
import dex.structure.DexAnnotationAttribute;
import dex.structure.DexClass;
import dex.structure.DexEncodedValue;
import dex.structure.DexField;
import dex.structure.DexFile;
import dex.structure.DexMethod;
public class DexUtil {
private static final String PACKAGE_INFO = "package-info";
private static final String THROWS_ANNOTATION =
"Ldalvik/annotation/Throws;";
private static final String SIGNATURE_ANNOTATION =
"Ldalvik/annotation/Signature;";
private static final String ANNOTATION_DEFAULT_ANNOTATION =
"Ldalvik/annotation/AnnotationDefault;";
private static final String ENCLOSING_CLASS_ANNOTATION =
"Ldalvik/annotation/EnclosingClass;";
private static final String ENCLOSING_METHOD_ANNOTATION =
"Ldalvik/annotation/EnclosingMethod;";
private static final String INNER_CLASS_ANNOTATION =
"Ldalvik/annotation/InnerClass;";
private static final String MEMBER_CLASS_ANNOTATION =
"Ldalvik/annotation/MemberClasses;";
private static final String JAVA_LANG_OBJECT = "Ljava/lang/Object;";
private static final Set<String> INTERNAL_ANNOTATION_NAMES;
static {
Set<String> tmp = new HashSet<String>();
tmp.add(THROWS_ANNOTATION);
tmp.add(SIGNATURE_ANNOTATION);
tmp.add(ANNOTATION_DEFAULT_ANNOTATION);
tmp.add(ENCLOSING_CLASS_ANNOTATION);
tmp.add(ENCLOSING_METHOD_ANNOTATION);
tmp.add(INNER_CLASS_ANNOTATION);
tmp.add(MEMBER_CLASS_ANNOTATION);
INTERNAL_ANNOTATION_NAMES = Collections.unmodifiableSet(tmp);
}
private DexUtil() {
// not constructable from outside
}
/**
* "La/b/c/A;" -> "a.b.c" "LA;" -> "" empty string
*
* @param classIdentifier
* @return the package name
*/
public static String getPackageName(String classIdentifier) {
String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
return ModelUtil.getPackageName(name.replace("/", "."));
}
/**
* "La/b/c/A;" -> "A" "LA;" -> "A"
*
* @param classIdentifier
* the dalvik internal identifier
* @return the class name
*/
public static String getClassName(String classIdentifier) {
String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
return ModelUtil.getClassName(name.replace("/", ".")).replace('$', '.');
}
public static String getQualifiedName(String classIdentifier) {
String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
return name.replace('/', '.');
}
private static String removeHeadingL(String className) {
assert className.startsWith("L");
return className.substring(1);
}
private static String removeTrailingSemicolon(String className) {
assert className.endsWith(";");
return className.substring(0, className.length() - 1);
}
public static String getDexName(String packageName, String className) {
return "L" + packageName.replace('.', '/') + "/"
+ className.replace('.', '$') + ";";
}
public static String getDexName(IClassDefinition sigClass) {
return getDexName(sigClass.getPackageName(), sigClass.getName());
}
/**
* Returns correct modifiers for inner classes
*/
public static int getClassModifiers(DexClass clazz) {
int modifiers = 0;
if (isInnerClass(clazz)) {
Integer accessFlags = (Integer) getAnnotationAttributeValue(
getAnnotation(clazz, INNER_CLASS_ANNOTATION),
"accessFlags");
modifiers = accessFlags.intValue();
} else {
modifiers = clazz.getModifiers();
}
return modifiers;
}
/**
* Returns a set containing all modifiers for the given int.
*
* @param mod
* the original bit coded modifiers as specified by
* {@link java.lang.reflect.Modifier}
* @return a set containing {@link signature.model.Modifier} elements
*/
public static Set<Modifier> getModifier(int mod) {
Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
if (java.lang.reflect.Modifier.isAbstract(mod))
modifiers.add(Modifier.ABSTRACT);
if (java.lang.reflect.Modifier.isFinal(mod))
modifiers.add(Modifier.FINAL);
// if (java.lang.reflect.Modifier.isNative(mod))
// modifiers.add(Modifier.NATIVE);
if (java.lang.reflect.Modifier.isPrivate(mod))
modifiers.add(Modifier.PRIVATE);
if (java.lang.reflect.Modifier.isProtected(mod))
modifiers.add(Modifier.PROTECTED);
if (java.lang.reflect.Modifier.isPublic(mod))
modifiers.add(Modifier.PUBLIC);
if (java.lang.reflect.Modifier.isStatic(mod))
modifiers.add(Modifier.STATIC);
// if (java.lang.reflect.Modifier.isStrict(mod))
// modifiers.add(Modifier.STRICT);
// if (java.lang.reflect.Modifier.isSynchronized(mod))
// modifiers.add(Modifier.SYNCHRONIZED);
// if (java.lang.reflect.Modifier.isTransient(mod))
// modifiers.add(Modifier.TRANSIENT);
if (java.lang.reflect.Modifier.isVolatile(mod))
modifiers.add(Modifier.VOLATILE);
return modifiers;
}
/**
* Returns true if the given class is an enumeration, false otherwise.
*
* @param dexClass
* the DexClass under test
* @return true if the given class is an enumeration, false otherwise
*/
public static boolean isEnum(DexClass dexClass) {
return (getClassModifiers(dexClass) & 0x4000) > 0;
}
/**
* Returns true if the given class is an interface, false otherwise.
*
* @param dexClass
* the DexClass under test
* @return true if the given class is an interface, false otherwise
*/
public static boolean isInterface(DexClass dexClass) {
int modifiers = getClassModifiers(dexClass);
return java.lang.reflect.Modifier.isInterface(modifiers);
}
/**
* Returns true if the given class is an annotation, false otherwise.
*
* @param dexClass
* the DexClass under test
* @return true if the given class is an annotation, false otherwise
*/
public static boolean isAnnotation(DexClass dexClass) {
return (getClassModifiers(dexClass) & 0x2000) > 0;
}
public static boolean isSynthetic(int modifier) {
return (modifier & 0x1000) > 0;
}
/**
* Returns the Kind of the given DexClass.
*
* @param dexClass
* the DexClass under test
* @return the Kind of the given class
*/
public static Kind getKind(DexClass dexClass) {
// order of branches is crucial since a annotation is also an interface
if (isEnum(dexClass)) {
return Kind.ENUM;
} else if (isAnnotation(dexClass)) {
return Kind.ANNOTATION;
} else if (isInterface(dexClass)) {
return Kind.INTERFACE;
} else {
return Kind.CLASS;
}
}
/**
* Returns whether the specified annotated element has an annotation with
* type "Ldalvik/annotation/Throws;".
*
* @param annotatedElement
* the annotated element to check
* @return <code>true</code> if the given annotated element has the
* mentioned annotation, false otherwise
*/
public static boolean declaresExceptions(
DexAnnotatedElement annotatedElement) {
return getAnnotation(annotatedElement, THROWS_ANNOTATION) != null;
}
/**
* Returns the throws signature if the given element has such an annotation,
* null otherwise.
*
* @param annotatedElement
* the annotated element
* @return he generic signature if the given element has such an annotation,
* null otherwise
*/
@SuppressWarnings("unchecked")
public static String getExceptionSignature(
DexAnnotatedElement annotatedElement) {
DexAnnotation annotation = getAnnotation(annotatedElement,
THROWS_ANNOTATION);
if (annotation != null) {
List<DexEncodedValue> value =
(List<DexEncodedValue>) getAnnotationAttributeValue(
annotation, "value");
return concatEncodedValues(value);
}
return null;
}
/**
* Splits a list of types:
* "Ljava/io/IOException;Ljava/lang/IllegalStateException;" <br>
* into separate type designators: <br>
* "Ljava/io/IOException;" , "Ljava/lang/IllegalStateException;"
*
* @param typeList
* the type list
* @return a set of type designators
*/
public static Set<String> splitTypeList(String typeList) {
String[] split = typeList.split(";");
Set<String> separateTypes = new HashSet<String>();
for (String string : split) {
separateTypes.add(string + ";");// add semicolon again
}
return separateTypes;
}
/**
* Returns whether the specified annotated element has an annotation with
* type "Ldalvik/annotation/Signature;".
*
* @param annotatedElement
* the annotated element to check
* @return <code>true</code> if the given annotated element has the
* mentioned annotation, false otherwise
*/
public static boolean hasGenericSignature(
DexAnnotatedElement annotatedElement) {
return getAnnotation(annotatedElement, SIGNATURE_ANNOTATION) != null;
}
/**
* Returns the generic signature if the given element has such an
* annotation, null otherwise.
*
* @param annotatedElement
* the annotated element
* @return he generic signature if the given element has such an annotation,
* null otherwise
*/
@SuppressWarnings("unchecked")
public static String getGenericSignature(
DexAnnotatedElement annotatedElement) {
DexAnnotation annotation = getAnnotation(annotatedElement,
SIGNATURE_ANNOTATION);
if (annotation != null) {
List<DexEncodedValue> value =
(List<DexEncodedValue>) getAnnotationAttributeValue(
annotation, "value");
return concatEncodedValues(value);
}
return null;
}
/**
* Returns whether the specified annotated element has an annotation with
* type "Ldalvik/annotation/AnnotationDefault;".
*
* @param annotatedElement
* the annotated element to check
* @return <code>true</code> if the given annotated element has the
* mentioned annotation, false otherwise
*/
public static boolean hasAnnotationDefaultSignature(
DexAnnotatedElement annotatedElement) {
return getAnnotation(
annotatedElement, ANNOTATION_DEFAULT_ANNOTATION)!= null;
}
/**
* Returns a mapping form annotation attribute name to its default value.
*
* @param dexClass
* the class defining a annotation
* @return a mapping form annotation attribute name to its default value
*/
public static DexAnnotation getDefaultMappingsAnnotation(
DexClass dexClass) {
return getAnnotation(dexClass, ANNOTATION_DEFAULT_ANNOTATION);
}
/**
* Returns the annotation with the specified type from the given element or
* null if no such annotation is available.
*
* @param element
* the annotated element
* @param annotationType
* the dex internal name of the annotation type
* @return the annotation with the specified type or null if not present
*/
public static DexAnnotation getAnnotation(DexAnnotatedElement element,
String annotationType) {
assert element != null;
assert annotationType != null;
for (DexAnnotation anno : element.getAnnotations()) {
if (annotationType.equals(anno.getTypeName())) {
return anno;
}
}
return null;
}
/**
* Returns the value for the specified attribute name of the given
* annotation or null if not present.
*
* @param annotation
* the annotation
* @param attributeName
* the name of the attribute
* @return the value for the specified attribute
*/
public static Object getAnnotationAttributeValue(DexAnnotation annotation,
String attributeName) {
for (DexAnnotationAttribute dexAnnotationAttribute : annotation
.getAttributes()) {
if (attributeName.equals(dexAnnotationAttribute.getName())) {
return dexAnnotationAttribute.getEncodedValue().getValue();
}
}
return null;
}
private static String concatEncodedValues(List<DexEncodedValue> values) {
StringBuilder builder = new StringBuilder();
for (DexEncodedValue string : values) {
builder.append(string.getValue());
}
return builder.toString();
}
/**
* Returns true if the given method is a constructor, false otherwise.
*
* @param method
* the method to test
* @return true if the given method is a constructor, false otherwise
*/
public static boolean isConstructor(DexMethod method) {
return "<init>".equals(method.getName());
}
/**
* Returns true if the given method is a static constructor, false
* otherwise.
*
* @param method
* the method to test
* @return true if the given method is a static constructor, false otherwise
*/
public static boolean isStaticConstructor(DexMethod method) {
return "<clinit>".equals(method.getName());
}
public static boolean isMethod(DexMethod method) {
return !isConstructor(method) && !isStaticConstructor(method);
}
/**
* Returns the package-info class for the given package.
*
* @param aPackage
* the package
* @return the class called "package-info" or null, if not available
*/
public static IClassDefinition findPackageInfo(SigPackage aPackage) {
for (IClassDefinition clazz : aPackage.getClasses()) {
if (PACKAGE_INFO.equals(clazz.getName())) {
return clazz;
}
}
return null;
}
public static boolean isPackageInfo(DexClass clazz) {
return PACKAGE_INFO.equals(getClassName(clazz.getName()));
}
public static boolean isInternalAnnotation(DexAnnotation dexAnnotation) {
return INTERNAL_ANNOTATION_NAMES.contains(dexAnnotation.getTypeName());
}
/**
* An InnerClass annotation is attached to each class which is defined in
* the lexical scope of another class's definition. Any class which has this
* annotation must also have either an EnclosingClass annotation or an
* EnclosingMethod annotation.
*/
public static boolean isInnerClass(DexClass clazz) {
return getAnnotation(clazz, INNER_CLASS_ANNOTATION) != null;
}
/**
* An EnclosingClass annotation is attached to each class which is either
* defined as a member of another class, per se, or is anonymous but not
* defined within a method body (e.g., a synthetic inner class). Every class
* that has this annotation must also have an InnerClass annotation.
* Additionally, a class may not have both an EnclosingClass and an
* EnclosingMethod annotation.
*/
public static boolean isEnclosingClass(DexClass clazz) {
return getAnnotation(clazz, ENCLOSING_CLASS_ANNOTATION) != null;
}
public static boolean declaresMemberClasses(DexClass dexClass) {
return getAnnotation(dexClass, MEMBER_CLASS_ANNOTATION) != null;
}
@SuppressWarnings("unchecked")
public static Set<String> getMemberClassNames(DexClass dexClass) {
DexAnnotation annotation = getAnnotation(dexClass,
MEMBER_CLASS_ANNOTATION);
List<DexEncodedValue> enclosedClasses =
(List<DexEncodedValue>) getAnnotationAttributeValue(
annotation, "value");
Set<String> enclosedClassesNames = new HashSet<String>();
for (DexEncodedValue string : enclosedClasses) {
enclosedClassesNames.add((String) string.getValue());
}
return enclosedClassesNames;
}
public static String getEnclosingClassName(DexClass dexClass) {
DexAnnotation annotation = getAnnotation(dexClass,
ENCLOSING_CLASS_ANNOTATION);
String value = (String) getAnnotationAttributeValue(annotation,
"value");
return value;
}
public static boolean convertAnyWay(DexClass dexClass) {
return !isSynthetic(getClassModifiers(dexClass))
&& !isAnonymousClassName(dexClass.getName())
|| isPackageInfo(dexClass);
}
public static boolean isVisible(DexClass dexClass, Visibility visibility) {
// package info is always visible
if (isPackageInfo(dexClass)) {
return true;
}
if (isDeclaredInMethod(dexClass)) {
return false;
}
if (isAnonymousClassName(dexClass.getName())) {
return false;
}
int modifiers = getClassModifiers(dexClass);
return isVisible(modifiers, visibility);
}
private static boolean isDeclaredInMethod(DexClass dexClass) {
return getAnnotation(dexClass, ENCLOSING_METHOD_ANNOTATION) != null;
}
/**
* Returns whether the given dex identifier is an anonymous class name.
* Format: La/b/C$1;
*
* @param dexName
* the name to analyze
* @return whether the given dex identifier is an anonymous class name
*/
public static boolean isAnonymousClassName(String dexName) {
int index = dexName.lastIndexOf('$');
return (index != 0) ? Character.isDigit(dexName.charAt(index + 1))
: false;
}
public static boolean isVisible(DexField dexField, Visibility visibility) {
return isVisible(dexField.getModifiers(), visibility);
}
public static boolean isVisible(DexMethod dexMethod,
Visibility visibility) {
return isVisible(dexMethod.getModifiers(), visibility);
}
private static boolean isVisible(int modifiers, Visibility visibility) {
if (isSynthetic(modifiers)) {
return false;
}
Set<Modifier> elementModifiers = getModifier(modifiers);
if (elementModifiers.contains(Modifier.PUBLIC)) {
return true;
} else if (elementModifiers.contains(Modifier.PROTECTED)) {
return visibility == Visibility.PROTECTED
|| visibility == Visibility.PACKAGE
|| visibility == Visibility.PRIVATE;
} else if (elementModifiers.contains(Modifier.PRIVATE)) {
return visibility == Visibility.PRIVATE;
} else {
return visibility == Visibility.PACKAGE
|| visibility == Visibility.PRIVATE;
}
}
public static Set<DexFile> getDexFiles(Set<String> fileNames)
throws IOException {
Set<DexFile> parsedFiles = new HashSet<DexFile>();
for (String dexFile : fileNames) {
DexFileReader reader = new DexFileReader();
DexBuffer dexBuffer = new DexBuffer(dexFile);
parsedFiles.add(reader.read(dexBuffer));
}
return parsedFiles;
}
public static boolean isJavaLangObject(DexClass dexClass) {
assert dexClass != null;
return JAVA_LANG_OBJECT.equals(dexClass.getName());
}
}