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

live.document.mavenplugin.rootmethod.RootMethodFinder Maven / Gradle / Ivy

package live.document.mavenplugin.rootmethod;

import live.document.mavenplugin.common.Constants;
import live.document.plsqlscanner.DbAction;
import live.document.plsqlscanner.PlSqlExplained;
import live.document.plsqlscanner.PlSqlObject;
import live.document.scanner.CallGraph;
import live.document.scanner.CallProcedureObject;
import live.document.scanner.ClassObject;
import live.document.scanner.MethodObject;
import live.document.scanner.VisitTableObject;
import org.apache.maven.plugin.logging.Log;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class RootMethodFinder {
    private CallGraph callGraph;
    private PlSqlExplained plSqlExplained;
    private Log logger;
    private List allMethods;
    private Map> calleeCallerMap = new HashMap<>();
    private Map> rootAllChildrenMap = new HashMap<>();
    private Set allRootMethods = new HashSet<>();
    private Function isRootMethod;
    private Boolean minimizeRootMethod;
    private Map> rootMethodsOfEntities;
    private Map> rootMethodsOfTables;

    public RootMethodFinder(CallGraph callGraph, PlSqlExplained plSqlExplained) {
        this.callGraph = callGraph;
        this.plSqlExplained = plSqlExplained;
        initCalleeCallerMap();
    }

    protected void findResults(Set entities, Set tables) {
        Set finalResults = new HashSet<>();
        //rootMethod -> JavaMethod which has entity/table operation
        Map> rootMethodAndChildMethodRelation = new HashMap<>();
        //entity results
        rootMethodsOfEntities = new HashMap<>();
        //table results
        rootMethodsOfTables = new HashMap<>();

        for (String entity : entities) {
            List methodObjects = findAllMethodObjectsByEntity(entity);
            List methodObjectOperators = getMethodObjectOperators(methodObjects);
            List rootMethodOperators = getRootMethodsOperator(methodObjectOperators);
            rootMethodsOfEntities.put(entity, rootMethodOperators);

            for (RootMethodOperator rootMethodOperator : rootMethodOperators) {
                Set childMethods = getOrInitChildMethods(rootMethodOperator, rootMethodAndChildMethodRelation);
                childMethods.addAll(findMethodWithinRoot(rootMethodOperator, methodObjects));
            }
        }

        for (String table : tables) {
            List methodOperators = findAllMethodOperationsByTable(table);
            List rootMethodOperators = getRootMethodsOperator(methodOperators);
            rootMethodsOfTables.put(table, rootMethodOperators);

            List methodObjects = methodOperators.stream().map(o -> o.methodObject).collect(Collectors.toList());
            for (RootMethodOperator rootMethodOperator : rootMethodOperators) {
                Set childMethods = getOrInitChildMethods(rootMethodOperator, rootMethodAndChildMethodRelation);
                childMethods.addAll(findMethodWithinRoot(rootMethodOperator, methodObjects));
            }
        }

        if (minimizeRootMethod) {
            minimizeRootMethod(rootMethodAndChildMethodRelation);
            removeDuplicatedRootMethods(rootMethodAndChildMethodRelation.keySet(), rootMethodsOfEntities);
            removeDuplicatedRootMethods(rootMethodAndChildMethodRelation.keySet(), rootMethodsOfTables);
        }
    }

    private Set findMethodWithinRoot(RootMethodOperator rootMethodOperator, List methodObjects) {
        String rootMethodName = rootMethodOperator.getName();
        if (! rootAllChildrenMap.containsKey(rootMethodName)) {
            Set childrenMethods = new HashSet<>();
            for (MethodObject rootMethod : allRootMethods) {
                if (rootMethodName.equals(rootMethod.getFullName())) {
                    childrenMethods.addAll(findAllCalleeMethod(rootMethod, new HashSet<>()));
                }
            }

            rootAllChildrenMap.put(rootMethodName, childrenMethods);
        }

        Set allChildMethodObjects = rootAllChildrenMap.get(rootMethodName);
        Set results = new HashSet<>(methodObjects);
        results.retainAll(allChildMethodObjects);

        return results;
    }

    private void removeDuplicatedRootMethods(Set finalRootMethodNames, Map> allRootMethods) {
        for (Map.Entry> entry : allRootMethods.entrySet()) {
            Iterator iterator = entry.getValue().iterator();
            while (iterator.hasNext()) {
                RootMethodOperator next = iterator.next();
                if (! finalRootMethodNames.contains(next.getName())) {
                    iterator.remove();
                }
            }
        }
    }

    private void minimizeRootMethod(Map> rootAndChildMethodMap) {
        info("Before minimize: " + rootAndChildMethodMap.size());

        List>> orderedEntries = rootAndChildMethodMap.entrySet()
                .stream()
                .sorted((m1, m2) -> m2.getValue().size() - m1.getValue().size()) //倒序
                .collect(Collectors.toList());

        Set keptMethods = new HashSet<>();

        for (int i = 0; i < orderedEntries.size(); i++) {
            Set temp = new HashSet<>(orderedEntries.get(i).getValue());
            if (temp.size() > 0) {
                temp.removeAll(keptMethods);
                if (temp.size() == 0) {
                    rootAndChildMethodMap.remove(orderedEntries.get(i).getKey());
                } else {
                    keptMethods.addAll(orderedEntries.get(i).getValue());
                }
            }
        }

        info("After minimize: " + rootAndChildMethodMap.size());
    }

    private Set getOrInitChildMethods(RootMethodOperator rootMethodOperator, Map> rootMethodMap) {
        Set results = rootMethodMap.get(rootMethodOperator.getName());
        if (results == null) {
            results = new HashSet<>();
            rootMethodMap.put(rootMethodOperator.getName(), results);
        }
        return results;
    }

    public List findByEntity(String entity) {
        findResults(new HashSet<>(Arrays.asList(entity)), new HashSet<>());

        if (this.rootMethodsOfEntities.containsKey(entity)) {
            return this.rootMethodsOfEntities.get(entity);
        }

        return new ArrayList<>();
    }

    public List findByTable(String tableName) {
        findResults(new HashSet<>(), new HashSet<>(Arrays.asList(tableName)));
        if (this.rootMethodsOfTables.containsKey(tableName)) {
            return this.rootMethodsOfTables.get(tableName);
        }

        return new ArrayList<>();
    }

    private List getRootMethodsOperator(List methodObjectOperators) {
        List results = getRootMethodOperators(methodObjectOperators);

        DuplicatedRootMethodRemover remover = new DuplicatedRootMethodRemover(callGraph);
        return remover.removeDuplicatedRootMethodOperator(results);
    }

    public void setIsRootMethod(Function isRootMethod) {
        this.isRootMethod = isRootMethod;
    }

    private List getRootMethodOperators(List methodObjectOperators) {
        Set rootMethods = findRootMethods(methodObjectOperators);
        List results = combineRootMethodOperators(rootMethods);
        return results;
    }

    private List findAllMethodOperationsByTable(String tableName) {
        List results = new ArrayList<>();

        for (ClassObject aClass : callGraph.getClasses()) {
            for (MethodObject method : aClass.getMethods()) {
                Optional tableOperation = getTableOperation(method, tableName);
                if (tableOperation.isPresent()) {
                    results.add(new MethodObjectOperator(method, tableOperation.get()));
                }
            }
        }

        return results;
    }

    private Optional getTableOperation(MethodObject method, String tableName) {
        Optional operation = method.calleeStream(VisitTableObject.class)
                .filter(v -> v.getTableName().equalsIgnoreCase(tableName))
                .map(v -> DbAction.WriteTableActions.contains(v.getOperation().getValue()) ? "W" : "R")
                .findFirst();

        if (operation.isPresent()) {
            return operation;
        }

        return method.calleeStream(CallProcedureObject.class)
                .map(procedureCall -> getTableOperationOfProcedure(procedureCall.getProcedure(), tableName, new HashSet<>()))
                .filter(Objects::nonNull)
                .findFirst();
    }

    private String getTableOperationOfProcedure(String procedureName, String tableName, HashSet scannedProcedure) {
        scannedProcedure.add(procedureName);

        PlSqlObject plSqlObject = plSqlExplained.findByName(procedureName);
        if (plSqlObject == null) {
            return null;
        }

        if (plSqlObject.getDeleteTables().contains(tableName)) {
            return "W";
        }
        if (plSqlObject.getUpdateTables().contains(tableName)) {
            return "W";
        }
        if (plSqlObject.getInsertTables().contains(tableName)) {
            return "W";
        }

        boolean found = false;
        for (String functionCall : plSqlObject.getFunctionCalls()) {
            if (! scannedProcedure.contains(functionCall)) {
                String tableOperationOfProcedure = getTableOperationOfProcedure(functionCall, tableName, scannedProcedure);
                if (tableOperationOfProcedure != null) {
                    if (tableOperationOfProcedure.equals("W")) {
                        return "W";
                    } else {
                        found = true;
                    }
                }
            }
        }

        if (plSqlObject.getQueryTables().contains(tableName)) {
            return "R";
        }

        if (found) {
            return "R";
        }

        return null;
    }

    private Set getAllTables(PlSqlObject plSqlObject) {
        Set tableNames = new HashSet<>();
        tableNames.addAll(plSqlObject.getQueryTables());
        tableNames.addAll(plSqlObject.getDeleteTables());
        tableNames.addAll(plSqlObject.getUpdateTables());
        tableNames.addAll(plSqlObject.getInsertTables());

        return tableNames;
    }

    /**
     * 查找方法的所有Callee,包含Callee的Callee等等。
     * @param methodObject
     * @param scannedMethods
     * @return 所有Callee
     */
    private List findAllCalleeMethod(MethodObject methodObject, Set scannedMethods) {
        List results = new ArrayList<>();
        methodObject.calleeStream(MethodObject.class)
                .filter(callee -> !scannedMethods.contains(callee))
                .forEach(callee -> {
                    scannedMethods.add(callee);

                    results.add(callee);
                    results.addAll(findAllCalleeMethod(callee, scannedMethods));
                });

        return results;
    }

    /**
     * 获取方法的RW操作
     * @param methodObjects
     * @return
     */
    private List getMethodObjectOperators(List methodObjects) {
        List methodObjectOperators = methodObjects.stream()
                .map(this::getMethodOperator)
                .collect(Collectors.toList());
        return methodObjectOperators;
    }

    /**
     * 合并方法的RW操作
     * @param rootMethods
     * @return
     */
    private List combineRootMethodOperators(Set rootMethods) {
        Map results = new HashMap<>();
        for (MethodObjectOperator mOperator : rootMethods) {
            String fullName = mOperator.methodObject.getFullName();
            RootMethodOperator rootMethodOperator = new RootMethodOperator(fullName, mOperator.operator);
            results.put(fullName, rootMethodOperator.combine(results.get(fullName)));
        }

        List rootMethodOperators = new ArrayList<>(results.values());
        rootMethodOperators.sort(RootMethodOperator::compareTo);

        return rootMethodOperators;
    }

    private void info(String message) {
        if (this.logger != null) {
            this.logger.info(message);
        }
    }

    /**
     * 查找方法的入口方法、RW操作
     * @param methodObjectOperators
     * @return
     */
    private Set findRootMethods(List methodObjectOperators) {
        Set rootMethods = methodObjectOperators
                .stream()
                .flatMap(m -> findRootMethodOperator(m).stream())
                .filter(m -> ! m.methodObject.getMethodName().contains("lambda$")) //root method去除lambda表达式
                .collect(Collectors.toSet());
        return rootMethods;
    }

    private List findRootMethodOperator(MethodObjectOperator m) {
        List results = new ArrayList<>();

        List rootMethods = findRootMethods(m.methodObject, new HashSet<>());
        allRootMethods.addAll((rootMethods));

        if (rootMethods.isEmpty()) {
            if (isRootMethod != null && isRootMethod.apply(m.methodObject)) {
                results.add(m);
            }
        }

        results.addAll(rootMethods.stream()
                .map(r -> new MethodObjectOperator(r, m.operator))
                .collect(Collectors.toList()));

        return results;
    }

    private List findRootMethods(MethodObject methodObject, HashSet scannedMethodObjects) {
        scannedMethodObjects.add(methodObject);
        List results = new ArrayList<>();

        List parentMethods = calleeCallerMap.get(methodObject);
        if (parentMethods == null) {
            return results;
        }

        for (MethodObject parentMethod : parentMethods) {
            if (isRootMethod != null && isRootMethod.apply(parentMethod)) {
                results.add(parentMethod);
            } else {
                if (! scannedMethodObjects.contains(parentMethod)) {
                    results.addAll(findRootMethods(parentMethod, scannedMethodObjects));
                }
            }
        }

        return results;
    }

    private void initCalleeCallerMap() {
        calleeCallerMap = new HashMap<>();
        for (MethodObject method : getAllMethods()) {
            method.calleeStream(MethodObject.class)
                    .forEach(callee -> {
                        List methodObjects = calleeCallerMap.get(callee);
                        if (methodObjects == null) {
                            methodObjects = new ArrayList<>();
                        }
                        methodObjects.add(method);
                        calleeCallerMap.put(callee, methodObjects);
                    });
        }
    }

    private List getAllMethods() {
        if (allMethods == null) {
            allMethods = callGraph.getClasses()
                    .stream().flatMap(c -> c.getMethods().stream())
                    .collect(Collectors.toList());
        }

        return allMethods;
    }

    /**
     * 根据Entity查找所有Jvm方法
     * @param entity
     * @return
     */
    private List findAllMethodObjectsByEntity(String entity) {
        List results = new ArrayList<>();

        for (ClassObject aClass : callGraph.getClasses()) {
            for (MethodObject method : aClass.getMethods()) {
                if (hasEntityOperation(method, entity)) {
                    results.add(method);
                }
            }
        }

        return results;
    }

    private MethodObjectOperator getMethodOperator(MethodObject methodObject) {
        String operator = "R";
        boolean hasWriteOperation = methodObject.calleeStream(MethodObject.class)
                .filter(c -> isEntityWriteOperation(c.getMethodName()))
                .findAny()
                .isPresent();
        if (hasWriteOperation) {
            operator = "W";
        }

        return new MethodObjectOperator(methodObject, operator);
    }

    private boolean hasEntityOperation(MethodObject methodObject, String entity) {
        return methodObject.calleeStream(MethodObject.class)
                .filter(callee -> callee.getClassName().equalsIgnoreCase(entity))
                .findFirst()
                .isPresent();
    }

    private boolean isEntityWriteOperation(String methodName) {
        for (String readonlyName : Constants.READONLY_METHOD_NAME) {
            if (methodName.startsWith(readonlyName)) {
                return false;
            }
        }

        return true;
    }

    public void setLogger(Log logger) {
        this.logger = logger;
    }

    public void setMinimizeRootMethod(Boolean minimizeRootMethod) {
        this.minimizeRootMethod = minimizeRootMethod;
    }

    class MethodObjectOperator {
        MethodObject methodObject;
        String operator;

        public MethodObjectOperator(MethodObject methodObject, String operator) {
            this.methodObject = methodObject;
            this.operator = operator;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            MethodObjectOperator that = (MethodObjectOperator) o;

            if (!methodObject.equals(that.methodObject)) return false;
            return operator.equals(that.operator);
        }

        @Override
        public int hashCode() {
            int result = methodObject.hashCode();
            result = 31 * result + operator.hashCode();
            return result;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy