
com.yiwowang.intellij.finding.base.util.ProjectFinding Maven / Gradle / Ivy
The newest version!
package com.yiwowang.intellij.finding.base.util;
import com.yiwowang.intellij.finding.base.bean.*;
import org.apache.bcel.classfile.JavaClass;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
// TODO 前两次慢,后来就快了。多线程是否有必要?
public class ProjectFinding {
private GradleData gradleData;
private List mComponents;
private Map mComponentMap;
private List mClasses;
private Map mClassMap;
private List androidClassList = new ArrayList<>();
private boolean androidClassEnable;
private boolean isInit;
private File mProjectDir;
private InitProgressCallback callback;
public void initAsync(File projectDir, InitProgressCallback callback) {
mProjectDir = projectDir;
this.callback = callback;
Utils.executeThread(new Runnable() {
@Override
public void run() {
initSync(projectDir);
}
});
}
public void initSync(File projectDir) {
mProjectDir = projectDir;
try {
if (!isInit) {
isInit = true;
long start = System.currentTimeMillis();
initGradleData();
long end = System.currentTimeMillis();
System.out.println("initComponents cost time :" + (end - start) + "; size:" + mComponents.size());
initComponentRef();
long end2 = System.currentTimeMillis();
System.out.println("initClasses cost time:" + (end2 - end) + "; size:" + mClasses.size());
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println("init error");
}
if (callback != null) {
if (gradleData == null) {
callback.onFinish("Init Error:Please add gradle plugin [Finding] and gradle sync");
} else {
callback.onFinish(null);
}
}
}
private void setProgress(int value, int max) {
if (callback != null) {
callback.onProgress(value, max);
}
}
private void initGradleData() {
File depFile = CacheUtils.getComponentsFile(mProjectDir);
gradleData = CacheUtils.parseGradleData(depFile);
if (gradleData != null) {
mComponents = gradleData.dependencies;
Collections.sort(mComponents, new Comparator() {
@Override
public int compare(Component o1, Component o2) {
return o1.name.compareTo(o2.name);
}
});
}
// for quick query
if (mComponents != null) {
mComponentMap = new HashMap<>();
for (Component component : mComponents) {
mComponentMap.put(component.name, component);
}
for (Component component : mComponents) {
component.depRefFull = new HashSet<>();
fillDepRefFull(component.name, new HashSet<>(), component.depRefFull);
}
}
}
private void fillDepRefFull(String dep, Collection visitedList, Collection depRefFull) {
if (visitedList.contains(dep)) {
return;
}
visitedList.add(dep);
depRefFull.add(dep);
Component component = getComponent(dep);
if (component == null) {
System.out.println("====找不到组件在map " + dep);
return;
}
if (component.depRef == null) {
return;
}
for (Component ref : component.depRef) {
fillDepRefFull(ref.name, visitedList, depRefFull);
}
}
public List getClasses() {
return mClasses;
}
public List getComponents() {
return mComponents;
}
public List searchComponent(String searchStr, boolean ignoreCase, int limitNum) {
if (CollectionUtils.isEmpty(mComponents)) {
return null;
}
int searchNum = 0;
List results = new ArrayList<>();
for (Component component : mComponents) {
if (Utils.contains(component.name, searchStr, ignoreCase)) {
searchNum++;
if (limitNum >= 0 && searchNum >= limitNum) {
break;
}
results.add(component);
}
}
return results;
}
public Map> searchClass(String searchStr, boolean ignoreCase, int limitNum) {
if (CollectionUtils.isEmpty(mClasses) || searchStr == null) {
return null;
}
Map> map = new HashMap<>();
int searchNum = 0;
if (androidClassEnable && gradleData != null && !CollectionUtils.isEmpty(gradleData.android_path)) {
String name = "android-" + gradleData.compile_sdk_version;
String filePath = gradleData.android_path.get(0);
Component component = new Component(name, filePath);
component.fromSystem = true;
List androidClass = new ArrayList<>();
for (String clazz : androidClassList) {
if (Utils.contains(clazz, searchStr, ignoreCase)) {
searchNum++;
if (limitNum >= 0 && searchNum >= limitNum) {
break;
}
ClassBean classBean = new ClassBean(component, getClassNameByPath(clazz));
androidClass.add(classBean);
}
}
if (androidClass.size() > 0) {
map.put(component.name, androidClass);
}
return map;
}
for (ClassBean clazz : mClasses) {
if (clazz == null) {
continue;
}
if (Utils.contains(clazz.name, searchStr, ignoreCase)) {
searchNum++;
if (limitNum >= 0 && searchNum >= limitNum) {
break;
}
List classes = map.get(clazz.component.name);
if (classes == null) {
classes = new ArrayList<>();
map.put(clazz.component.name, classes);
}
classes.add(clazz);
}
}
return map;
}
public ClassBean getClassBean(String className) {
return mClassMap.get(className);
}
public Component getComponent(String componentName) {
return mComponentMap.get(componentName);
}
public Component getComponentByClass(String className) {
ClassBean classBean = getClassBean(className);
if (classBean == null) {
return null;
}
return classBean.component;
}
private boolean componentCacheChange = false;
private void initComponentRef() {
mClasses = new ArrayList<>();
mClassMap = new HashMap<>();
File componentCacheFile = CacheUtils.getComponentCacheFile(mProjectDir);
ComponentCache componentCache = CacheUtils.parseComponentCache(componentCacheFile);
if (componentCache == null) {
componentCache = new ComponentCache();
}
if (componentCache.component_class == null) {
componentCache.component_class = new HashMap<>();
}
if (componentCache.component_ref == null) {
componentCache.component_ref = new HashMap<>();
}
long start = System.currentTimeMillis();
int progress = 0;
int max = mComponents.size() * 2;
for (Component component : mComponents) {
progress++;
setProgress(progress, max);
Collection cacheClasses = componentCache.component_class.get(component.name);
if (!CollectionUtils.isEmpty(cacheClasses)) {
for (String className : cacheClasses) {
ClassBean classBean = new ClassBean(component, className);
mClasses.add(classBean);
mClassMap.put(className, classBean);
}
continue;
}
cacheClasses = new ArrayList<>();
componentCache.component_class.put(component.name, cacheClasses);
File file = new File(component.filePath);
// component and class
Collection finalClasses = cacheClasses;
eachClass(file, new ZipEachCallback() {
@Override
public boolean visit(String name, InputStream inputStream) {
String className = getClassNameByPath(name);
ClassBean classBean = new ClassBean(component, className);
mClasses.add(classBean);
mClassMap.put(className, classBean);
finalClasses.add(className);
componentCacheChange = true;
return false;
}
});
}
// 组件和组件的关系
for (Component component : mComponents) {
progress++;
setProgress(progress, max);
component.codeRef = new HashSet<>();
Collection cacheRefComponents = componentCache.component_ref.get(component.name);
if (!CollectionUtils.isEmpty(cacheRefComponents)) {
for (String componentName : cacheRefComponents) {
component.codeRef.add(getComponent(componentName));
}
continue;
}
cacheRefComponents = new HashSet<>();
componentCache.component_ref.put(component.name, cacheRefComponents);
File file = new File(component.filePath);
Collection finalCacheRefComponents = cacheRefComponents;
eachClass(file, new ZipEachCallback() {
@Override
public boolean visit(String name, InputStream inputStream) {
String className = getClassNameByPath(name);
JavaClass javaClass = FindApi.getJavaClass(inputStream, name);
if (javaClass != null) {
// javaClass.getInterfaceNames();
// String superClassName = javaClass.getSuperclassName();
// if (!StringUtils.startsWith(superClassName, "java")) {
// ClassBean classBean = getClassBean(className);
// if (classBean != null) {
// classBean.superClassList=new ArrayList<>();
// classBean.superClassList.add(superClassName);
// }
// }
}
Set refClasses = FindApi.getRefClasses(javaClass);
if (refClasses == null || refClasses.size() == 0) {
return false;
}
for (String clazz : refClasses) {
String name1 = clazz.replace("/", ".");
Component refComponent = getComponentByClass(name1);
if (refComponent != null) {
component.codeRef.add(refComponent);
finalCacheRefComponents.add(refComponent.name);
componentCacheChange = true;
}
}
return false;
}
});
}
// remove invalid cache
Collection componentNames = componentCache.component_class.keySet();
for (String componentName : componentNames) {
if (!mComponentMap.containsKey(componentName)) {
componentCache.component_class.remove(componentName);
componentCache.component_ref.remove(componentName);
}
}
if (componentCacheChange) {
CacheUtils.saveComponentCache(componentCacheFile, componentCache);
}
System.out.println("initComponentRef cost time:" + (System.currentTimeMillis() - start));
}
public static InputStream findClass(File file, String classPath) {
String targetEntryName = classPath + ".class";
final InputStream[] result = {null};
if (file.getAbsolutePath().endsWith(".aar")) {
eachInAar(file, new ZipEachCallback() {
@Override
public boolean visit(String entryName, InputStream inputStream) {
if (StringUtils.equals(entryName, targetEntryName)) {
result[0] = inputStream;
return true;
}
return false;
}
});
} else {
try {
ZipFile zipFile = new ZipFile(file);
ZipEntry zipEntry = zipFile.getEntry(targetEntryName);
if (zipEntry != null) {
result[0] = zipFile.getInputStream(zipEntry);
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result[0];
}
public static void eachClass(File file, ZipEachCallback callback) {
if (file.getAbsolutePath().endsWith(".aar")) {
eachInAar(file, callback);
} else {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
eachInJar(fileInputStream, callback);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void eachInAar(File file, ZipEachCallback callback) {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(file);
ZipEntry zipEntry = zipFile.getEntry("classes.jar");
if (zipEntry != null) {
eachInJar(zipFile.getInputStream(zipEntry), callback);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void eachInJar(InputStream jarInputStream, ZipEachCallback callback) throws IOException {
ZipInputStream inputStream = new ZipInputStream(jarInputStream) {
@Override
public void close() throws IOException {
// super.close();
}
};
ZipEntry entry = inputStream.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(".class")) {
boolean stop = callback.visit(entry.getName(), inputStream);
if (stop) {
break;
}
}
entry = inputStream.getNextEntry();
}
}
public List findUsages(FindUsagesParams params) {
List results = new ArrayList<>();
ClassBean classBean = getClassBean(params.className);
Collection needBeFind = getNeedBeFindComponent(classBean);
if (needBeFind == null) {
return null;
}
String targetClassPath = classBean == null ? null : classBean.path;
String targetSign = params.methodParamType != null ? MemberBean.getSignFromInput(params.methodParamType) : null;
for (Component component : needBeFind) {
File file = new File(component.filePath);
try {
eachClass(file, new ZipEachCallback() {
@Override
public boolean visit(String entryName, InputStream inputStream) {
boolean ok = false;
switch (params.searchType) {
case FindUsagesParams.TYPE_CLASS:
ok = FindApi.findUsagesClass(inputStream, entryName, targetClassPath);
break;
case FindUsagesParams.TYPE_METHOD:
ok = FindApi.findUsagesMethod(inputStream, entryName, targetClassPath, params.methodName, targetSign);
break;
case FindUsagesParams.TYPE_FIELD:
ok = FindApi.findUsagesField(inputStream, entryName, targetClassPath, params.fieldName);
break;
case FindUsagesParams.TYPE_CONSTANTS:
ok = FindApi.findUsagesConstant(inputStream, entryName, targetClassPath, params.constant, FindApi.MATCH_EQUAL, params.ignoreCase);
break;
case FindUsagesParams.TYPE_PACKAGE:
String packagePath = params.packageName.replace(".", "/");
ok = FindApi.findUsagesPackage(inputStream, entryName, packagePath);
break;
}
if (ok) {
RefResult result = new RefResult();
String name = ProjectFinding.getClassNameByPath(entryName);
result.clazz = getClassBean(name);
results.add(result);
LogUtils.log("in class " + component.name + ",found " + entryName + " using " + params.toString());
}
return false;
}
});
} catch (Throwable e) {
e.printStackTrace();
System.out.println("err=" + e);
}
}
return results;
}
private Collection getNeedBeFindComponent(ClassBean classBean) {
List components = getComponents();
if (components == null) {
return null;
}
if (classBean == null || classBean.component == null || classBean.component.fromSystem) {
return components;
}
Set needBeFind = new HashSet<>();
for (Component component : components) {
if (component.codeRef.contains(classBean.component)) {
needBeFind.add(component);
}
}
return needBeFind;
}
public void decodeClass(ClassBean classBean) {
FindApi.fill(classBean);
}
public static String getClassNameByPath(String classPath) {
if (StringUtils.isEmpty(classPath)) {
return null;
}
int indexEnd = classPath.indexOf(".class");
if (indexEnd <= 0) {
return null;
}
return classPath.substring(0, indexEnd).replace("/", ".");
}
public void setAndroidClassEnable(boolean androidClassEnable) {
this.androidClassEnable = androidClassEnable;
initAndroidJar();
}
private void initAndroidJar() {
if (androidClassList.size() > 0 || gradleData == null ||
CollectionUtils.isEmpty(gradleData.android_path)) {
return;
}
long startTime = System.currentTimeMillis();
File file = new File(gradleData.android_path.get(0));
ProjectFinding.eachClass(file, new ZipEachCallback() {
@Override
public boolean visit(String entryName, InputStream inputStream) {
if (!entryName.endsWith(".class")) {
return false;
}
androidClassList.add(entryName);
return false;
}
});
System.out.println("initAndroidJar cost time:" + (System.currentTimeMillis() - startTime) + "; class size:" + androidClassList.size());
}
public interface ZipEachCallback {
boolean visit(String entryName, InputStream inputStream);
}
public interface InitProgressCallback {
void onProgress(int value, int max);
void onFinish(String error);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy