package com.telerik.metadata;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.telerik.metadata.TreeNode.FieldInfo;
import com.telerik.metadata.TreeNode.MethodInfo;
import com.telerik.metadata.bcl.JarFile;
import com.telerik.metadata.desc.MetadataInfoAnnotationDescriptor;
import com.telerik.metadata.desc.ClassDescriptor;
import com.telerik.metadata.desc.FieldDescriptor;
import com.telerik.metadata.desc.MethodDescriptor;
import com.telerik.metadata.desc.TypeDescriptor;
import com.telerik.metadata.dx.DexFile;
public class Builder {
private static class MethodNameComparator implements Comparator<MethodDescriptor> {
@Override
public int compare(MethodDescriptor o1, MethodDescriptor o2) {
return o1.getName().compareTo(o2.getName());
}
}
private static MethodNameComparator methodNameComparator = new MethodNameComparator();
public static TreeNode build(String[] paths) throws Exception {
for (String path : paths) {
File file = new File(path);
if (file.exists()) {
if (file.isFile()) {
if (path.endsWith(".jar")) {
JarFile jar = JarFile.readJar(path);
ClassRepo.addToCache(jar);
} else if (path.endsWith(".dex")) {
DexFile dex = DexFile.readDex(path);
ClassRepo.addToCache(dex);
}
} else if (file.isDirectory()) {
ClassDirectory dir = ClassDirectory.readDirectory(path);
ClassRepo.addToCache(dir);
}
}
}
TreeNode root = TreeNode.getRoot();
String[] classNames = ClassRepo.getClassNames();
for (String className : classNames) {
try {
// possible exceptions here are:
// - NoClassDefFoundError
// - ClassNotFoundException
// both are raised due to some API level mismatch -
// e.g. we are processing jars with API 21 while we have in
// our class path API 17
// Class<?> clazz = Class.forName(className, false, loader);
ClassDescriptor clazz = ClassRepo.findClass(className);
generate(clazz, root);
} catch (Throwable e) {
System.out.println("Skip " + className);
System.out.println("\tError: " + e.toString());
//e.printStackTrace();
}
}
return root;
}
private static Boolean isClassPublic(ClassDescriptor clazz) {
Boolean isPublic = true;
try {
ClassDescriptor currClass = clazz;
while (currClass != null) {
if (!currClass.isPublic() && !currClass.isProtected()) {
isPublic = false;
break;
}
currClass = ClassUtil.getEnclosingClass(currClass);
}
} catch (NoClassDefFoundError e) {
isPublic = false;
}
return isPublic;
}
private static void generate(ClassDescriptor clazz, TreeNode root) throws Exception {
if (!isClassPublic(clazz)) {
return;
}
MetadataInfoAnnotationDescriptor metadataInfo = clazz.getMetadataInfoAnnotation();
boolean hasClassMetadataInfo = metadataInfo != null;
String predefinedSuperClassname = null;
if (hasClassMetadataInfo) {
if (metadataInfo.skip()) {
return;
} else {
predefinedSuperClassname = metadataInfo.getSuperClassname();
}
}
TreeNode node = getOrCreateNode(root, clazz, predefinedSuperClassname);
setNodeMembers(clazz, node, root, hasClassMetadataInfo);
}
private static void setNodeMembers(ClassDescriptor clazz, TreeNode node, TreeNode root, boolean hasClassMetadataInfo) throws Exception {
Map<String, MethodInfo> existingMethods = new HashMap<String, MethodInfo>();
for (MethodInfo mi : node.instanceMethods) {
existingMethods.put(mi.name + mi.sig, mi);
}
MethodDescriptor[] allMethods = ClassUtil.getAllMethods(clazz);
MethodDescriptor[] methods = clazz.getMethods();
Arrays.sort(methods, methodNameComparator);
for (MethodDescriptor m : methods) {
if (m.isSynthetic()) {
continue;
}
if (hasClassMetadataInfo && !m.getName().equals("<init>")) {
MetadataInfoAnnotationDescriptor metadataInfo = m.getMetadataInfoAnnotation();
if ((metadataInfo != null) && metadataInfo.skip()) {
continue;
}
}
if (m.isPublic() || m.isProtected()) {
boolean isStatic = m.isStatic();
MethodInfo mi = new MethodInfo(m);
int countUnique = 0;
for (MethodDescriptor m1 : allMethods) {
boolean m1IsStatic = m1.isStatic();
if (!m1.isSynthetic()
&& (m1.isPublic() || m1.isProtected())
&& (isStatic == m1IsStatic)
&& (m1.getName().equals(mi.name) && (m1
.getArgumentTypes().length == m
.getArgumentTypes().length))) {
if (++countUnique > 1) {
break;
}
}
}
mi.isResolved = countUnique == 1;
TypeDescriptor[] params = m.getArgumentTypes();
mi.signature = getMethodSignature(root, m.getReturnType(),
params);
if (mi.signature != null) {
if (isStatic) {
mi.declaringType = getOrCreateNode(root, clazz, null);
node.staticMethods.add(mi);
} else {
String sig = m.getName() + m.getSignature();
if (existingMethods.containsKey(sig)) {
continue;
}
node.instanceMethods.add(mi);
}
}
}
}
FieldDescriptor[] fields = clazz.getFields();
setFieldInfo(clazz, node, root, fields, null);
// adds static fields from interface to implementing class because java can call them from implementing class... no problem.
getFieldsFromImplementedInterfaces(clazz, node, root, fields);
}
private static void setFieldInfo(ClassDescriptor clazz, TreeNode node, TreeNode root, FieldDescriptor[] fields, ClassDescriptor interfaceClass) throws Exception {
for (FieldDescriptor f : fields) {
if (f.isPublic() || f.isProtected()) {
FieldInfo fi = new FieldInfo(f.getName());
TypeDescriptor t = f.getType();
boolean isPrimitive = ClassUtil.isPrimitive(t);
fi.valueType = isPrimitive ? TreeNode.getPrimitive(t): getOrCreateNode(root, t);
fi.isFinalType = f.isFinal();
if (f.isStatic()) {
if (interfaceClass != null) {
// changes declaring type of static fields from implementing class to interface
fi.declaringType = getOrCreateNode(root, interfaceClass, null);
} else {
fi.declaringType = getOrCreateNode(root, clazz, null);
}
node.staticFields.add(fi);
} else {
node.instanceFields.add(fi);
}
}
}
}
private static void getFieldsFromImplementedInterfaces(ClassDescriptor clazz, TreeNode node, TreeNode root, FieldDescriptor[] classFields) throws Exception {
FieldDescriptor[] fields = null;
List<FieldDescriptor> originalClassFields = Arrays.asList(classFields);
ClassDescriptor interfaceClass = null;
String[] implementedInterfacesNames = clazz.getInterfaceNames();
if (implementedInterfacesNames.length > 0) {
for (String currInterface : implementedInterfacesNames) {
interfaceClass = ClassRepo.findClass(currInterface);
if (interfaceClass != null) {
fields = interfaceClass.getFields();
// If the interface iteself extends other interfaces - add their fields too
getFieldsFromImplementedInterfaces(interfaceClass, node, root, fields);
//if interface and implementing class declare the same static field name the class take precedence
if (originalClassFields.size() > 0) {
for (FieldDescriptor f : fields) {
if (originalClassFields.contains(f)) {
return;
}
}
}
setFieldInfo(clazz, node, root, fields, interfaceClass);
}
}
}
}
private static TreeNode getOrCreateNode(TreeNode root, TypeDescriptor type)
throws Exception {
TreeNode node;
String typeName = type.getSignature();
if (typeName.startsWith("[")) {
node = createArrayNode(root, typeName);
} else {
String name = ClassUtil.getCanonicalName(type.getSignature());
ClassDescriptor clazz = ClassRepo.findClass(name);
node = getOrCreateNode(root, clazz, null);
}
return node;
}
private static TreeNode getOrCreateNode(TreeNode root, ClassDescriptor clazz, String predefinedSuperClassname) throws Exception {
if (ClassUtil.isPrimitive(clazz)) {
return TreeNode.getPrimitive(clazz);
}
if (ClassUtil.isArray(clazz)) {
throw new UnsupportedOperationException("unexpected class="
+ clazz.getClassName());
}
TreeNode node = root;
String name = ClassUtil.getSimpleName(clazz);
String[] packages = clazz.getPackageName().split("\\.");
for (String p : packages) {
TreeNode child = node.getChild(p);
if (child == null) {
child = node.createChild(p);
node.nodeType = TreeNode.Package;
}
node = child;
}
ClassDescriptor outer = ClassUtil.getEnclosingClass(clazz);
ArrayList<ClassDescriptor> outerClasses = new ArrayList<ClassDescriptor>();
while (outer != null) {
if (!outer.isPublic()) {
return null;
}
outerClasses.add(outer);
outer = ClassUtil.getEnclosingClass(outer);
}
if (outerClasses.size() > 0) {
for (int i = outerClasses.size() - 1; i >= 0; i--) {
outer = outerClasses.get(i);
String outerClassname = ClassUtil.getSimpleName(outer);
TreeNode child = node.getChild(outerClassname);
if (child == null) {
child = node.createChild(outerClassname);
child.nodeType = outer.isInterface() ? TreeNode.Interface
: TreeNode.Class;
if (outer.isStatic()) {
child.nodeType |= TreeNode.Static;
}
}
node = child;
}
}
TreeNode child = node.getChild(name);
if (child == null) {
child = node.createChild(name);
if (ClassUtil.isPrimitive(clazz)) {
TreeNode tmp = TreeNode.getPrimitive(clazz);
child.nodeType = tmp.nodeType;
} else {
child.nodeType = clazz.isInterface() ? TreeNode.Interface
: TreeNode.Class;
if (clazz.isStatic()) {
child.nodeType |= TreeNode.Static;
}
}
}
node = child;
if (node.baseClassNode == null) {
ClassDescriptor baseClass = null;
if (predefinedSuperClassname != null) {
baseClass = ClassUtil.getClassByName(predefinedSuperClassname);
} else {
baseClass = clazz.isInterface()
? ClassUtil.getClassByName("java.lang.Object")
: ClassUtil.getSuperclass(clazz);
}
if (baseClass != null) {
node.baseClassNode = getOrCreateNode(root, baseClass, null);
copyBasePublicApi(baseClass, node, root);
}
}
return node;
}
private static void copyBasePublicApi(ClassDescriptor baseClass, TreeNode node,
TreeNode root) throws Exception {
while ((baseClass != null) && !baseClass.isPublic()) {
setNodeMembers(baseClass, node, root, false);
baseClass = ClassUtil.getSuperclass(baseClass);
}
}
private static TreeNode createArrayNode(TreeNode root, String className)
throws Exception {
TreeNode currentNode = root;
String currentClassname = className;
while (ClassUtil.isArray(currentClassname)) {
TreeNode child = currentNode.getChild("[");
if (child == null) {
child = currentNode.createChild("[");
child.nodeType = TreeNode.Array;
child.offsetValue = 1;
}
currentClassname = currentClassname.substring(1);
currentNode = child;
}
String name = ClassUtil.getCanonicalName(currentClassname);
TreeNode child = currentNode.getChild(name);
if (child == null) {
child = currentNode.createChild(name);
if (ClassUtil.isPrimitive(name)) {
TreeNode node = TreeNode.getPrimitive(name);
child.nodeType = node.nodeType;
child.arrayElement = node;
} else {
ClassDescriptor clazz = ClassRepo.findClass(name);
child.nodeType = clazz.isInterface() ? TreeNode.Interface
: TreeNode.Class;
if (clazz.isStatic()) {
child.nodeType |= TreeNode.Static;
}
child.arrayElement = getOrCreateNode(root, clazz, null);
}
}
return child;
}
private static ArrayList<TreeNode> getMethodSignature(TreeNode root,
TypeDescriptor retType, TypeDescriptor[] params) throws Exception {
ArrayList<TreeNode> sig = new ArrayList<TreeNode>();
boolean isVoid = retType.equals(TypeDescriptor.VOID);
TreeNode node = null;
if (!isVoid) {
boolean isPrimitive = ClassUtil.isPrimitive(retType);
node = isPrimitive ? TreeNode.getPrimitive(retType)
: getOrCreateNode(root, retType);
}
sig.add(node);
for (TypeDescriptor param : params) {
boolean isPrimitive = ClassUtil.isPrimitive(param);
node = isPrimitive ? TreeNode.getPrimitive(param)
: getOrCreateNode(root, param);
if (node == null) {
return null;
}
sig.add(node);
}
return sig;
}
}