All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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