How to capture changes inside methods in an Eclips

2019-05-24 00:51发布

As part of my Eclipse plugin project I need to track additions, deletions and changes to methods in order to implement the functionality I'm after.

By registering a listener through JavaCore.addElementChangedListener and recursively traversing the IJavaElementDelta until the IJavaElementDelta.getElement() gives a reference to an affected method, I am able to capture method additions and deletions.

E.g. I add method a into class B I get:

[Working copy] B.java[*]: {CHILDREN | FINE GRAINED | AST AFFECTED}
    B[*]: {CHILDREN | FINE GRAINED}
        a()[+]: {}]

The problem is that this does not take place when already existing methods are edited: when I modify the implementation of a method and a change event is triggered for it, the delta's resolution stops at the class containing this method instead of extending to it.

E.g. I modify the method a in class B I get:

[Working copy] B.java[*]: {CONTENT | FINE GRAINED | AST AFFECTED}

This information contains no information about method a, even though its implementation was just changed. This problem might be partially connected to this old Eclipse bug report https://bugs.eclipse.org/bugs/show_bug.cgi?id=327753

Thus, the question is: how can I track and get notified about methods that have their implementation changed (without building and storing the AST multiple times)?

1条回答
Anthone
2楼-- · 2019-05-24 01:49

After a somewhat rigorous investigation, I've concluded that it is impossible to capture changes inside methods without AST-related information. Thus, I've looked for the most efficient way to store the bare minimum of information needed as well as for the most suitable approach to comparing this information.

Here is the solution that I've come up and according to a couple of days of testing it seems to be efficient enough to be feasible to execute during every single ElementChangedEvent.

// during each user-invoked-compile, these are processed and cleared
private static HashMap<String, IMethod> added = new HashMap<String, IMethod>();
private static HashMap<String, IMethod> changed = new HashMap<String, IMethod>();
private static HashMap<String, IMethod> removed = new HashMap<String, IMethod>();

// this persists through out the entire session
private static HashMap<String, ASTNode> subtrees = new HashMap<String, ASTNode>();

private static void attachModelPart() {
    JavaCore.addElementChangedListener(new IElementChangedListener() {

        @Override
        public void elementChanged(ElementChangedEvent event) {
            ... // added and removed IMethod handling
            IJavaElementDelta delta = event.getDelta();
            if (delta.getElement() instanceof CompilationUnit) {
                delta.getCompilationUnitAST().accept(new ASTVisitor() {

                    @Override
                    public boolean visit(MethodDeclaration node) {
                        String mName = ((TypeDeclaration) node.getParent()).getName()
                                .getFullyQualifiedName() + "." + node.getName().getFullyQualifiedName();
                        // Finding match for this methods name(mName) in saved method subtrees...
                        boolean methodHasChanged = false;
                        if (subtrees.containsKey(mName)) {
                            // Found match
                            // Comparing new subtree to one saved during an earlier event (using ASTNode.subtreeMatch())
                            methodHasChanged = !node.subtreeMatch(new ASTMatcher(), subtrees.get(mName));
                        } else {
                            // No earlier entry found, definitely changed
                            methodHasChanged = true;
                        }
                        if (methodHasChanged) {
                            // "changed" is a HashMap of IMethods that have been earlierly identified as changed
                            // "added" works similarly but for added methods (using IJavaElementDelta.getAddedChildren())
                            if (!changed.containsKey(mName) && !added.containsKey(mName)) {
                                // Method has indeed changed and is not yet queued for further actions
                                changed.put(mName, (IMethod) node.resolveBinding().getJavaElement());
                            }
                        }
                        // "subtrees" must be updated with every method's AST subtree in order for this to work
                        subtrees.put(mName, node);
                        // continue visiting after first MethodDeclaration
                        return true;
                    }
                });
            }
        }
    }
}

Comments are most welcome!

查看更多
登录 后发表回答