Code Monkey home page Code Monkey logo

gumtree-spoon-ast-diff's People

Contributors

algomaster99 avatar andre15silva avatar benmss avatar delvalletquentin avatar dependabot[bot] avatar gerardpaligot avatar itwoi avatar khaes-kth avatar martinezmatias avatar monperrus avatar renovate[bot] avatar ruil1n avatar sedflix avatar slarse avatar surli avatar tdurieux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gumtree-spoon-ast-diff's Issues

Modifiers change no diff detected

The tool does not detect change on method modifiers or parameter change.

Example:

String c1 = "class X {" + "public void foo() {" + " int x = 0; if(x < 0)x = 1;}};";
String c2 = "class X {" + "private void foo() {" + " int x = 0; if(x < 0)x = 1;}};";
DiffSpoon diff = new DiffSpoon(true);
CtDiff editScript = diff.compare(c1, c2);
assertEquals(1, editScript.getRootActions().size()); // assert false

[Feature] add nodes corresponding to imports

Proposition of feature (easy to implement).

As we don't have nodes for imports we can have an option to create them.
I imagine that It's quite easy to do it: navigate all the TypeReference and create for each of them a new node of type import and as value the Qualified of each TypeReference. (All these new node will we wild of a new Node called imports).

The goal of this feature is to remove the Qualified name from the nodes corresponding to TypeReference and replace it by the SimpleName.

Thus, combining the mentioned changes we will able to correctly detect renames of packages (changes in imports) and (most importantly) avoiding Updates of TypeReference nodes which are children of, for instance, Variables.

WDYT? It makes sense to implement all the mentioned changes?

Unexpected operation when insert or delete modifiers

Input

Just delete public before class

AstComparator diff = new AstComparator();
        Diff results = diff.compare("public class Calculator {\n" +
                "    public static int add(int a, int b){\n" +
                "        return a + b;\n" +
                "    }\n" +
                "}", "class Calculator {\n" +
                "    public static int add(int a, int b){\n" +
                "        return a + b;\n" +
                "    }\n" +
                "}");

Output

only a Del class operation !

Similar problems happen when insert or delete modifiers private and public. It may relate to the number of modifiers since it can detect insertion of static when changing public xxx to public static xxx, but fail when changing "xxx" to static xxx.

Screen Shot 2019-04-09 at 4 00 38 PM

Expected output

However, when I change public class to private class, the operation will be Update class. I wonder what is the definition of update class ? It is important for me since I need to handle operations of various AST nodes using different strategies.

Case Coderep Dataset 1806

The case Dataset 1 of coderep 1806 .
The diff 1806 only updates an import.
However, GTSpoon shows different changes.
The problem is that it detects all the Updates on the field reads which types were involved in the modified import.

What is the difference between gumtree-spoon-ast-diff and gumtreediff?

Hi there,

I found the 2 most-starred AST diff tools on github 2 months ago, and had been developing applications on top of them.

I noticed that though the two tools belong to different accounts, the author seems to be the same person---Prof. Jean-Rémy Falleri

Firstly, I managed to integrate gumtree-spoon-ast-diff into my application, because I only need to handle Java files and it is smaller than gumtreediff. However, I spotted a number of bugs and exceptions in using it, some of them lies in the dependencies(spoon, eclipse jdt, etc.) while others lies in the tool itself. I fixed few of them, though trying my best(as a student).

Afterwards I turn to gumtreediff for help, which supports multiple languages and shows to be more robust. For now it works well, and I have figured out its workflow for Java files. But since I need to make more modifications on it, I want to learn more about it, like:

  • What is the main difference between gumtree-spoon-ast-diff and gumtreediff?(Except for language support and backend parser)
  • Is the gumtree-spoon-ast-diff still under development?
  • What's the advantage of spoon comparing with JavaParser and JDT?

Thanks to all~

No diff when reading Java from .txt file

Hi,

If we have add_pre.txt & add_pre.java with the same content. And add_post.txt & add_post.java with the same content.

Executing:

Diff diff = new AstComparator().compare(new File("add_pre.txt"), new File("add_post.txt"));`

cause diff.getAllOperations().size() to be zero

While executing:

Diff diff = new AstComparator().compare(new File("add_pre.java"), new File("add_post.java"));`

will have non-zero diff.getAllOperations().size()

It is nothing serious but worth pointing out. Any explanation for this kind of behavior?

How to obtain pre- and post-version of commonAncestor()?

Hi,

Let us assume that we have the following two java files:

add_pre.java:

public class foo{
    public int add(int x, int y){
        return(x-y);
    }
}

add_post.java:

public class foo{
    public int add(int x, int y){
        return(x+y);
    }
}

How I obtained the diff:

Diff diff = new AstComparator().compare(new File("add_pre.java"), new File("add_post.java"));
CtElement method = diff.commonAncestor();
while(!(method instanceof CtMethod)){
    method = method.getParent();
}

How can I get the pre- and post-version of CtMethod method? One way that I found is to swap the arguments to compare():

Diff diff = new AstComparator().compare(new File("add_post.java"), new File("add_pre.java"));

Are there any other easier way to do it?

Thanks!

Type Label

Tree has type id but not type label.
Improve the way to retrieve the type label from a type id

Fail to recognize Generics in Superclass

Input:
1.java

package com.test;

public class TestClass extends SuperClass<Old> {

}

2.java

package com.test;

public class TestClass extends SuperClass<New> {

}

Output:

java -jar gumtree-spoon-ast-diff-1.17-jar-with-dependencies.jar 1.java 2.java
no AST change

I would have expected the change "Old > New" to be represented in the AST.
This is happening because this is a CtTypeReference and all CtReferences are ignored.

missing file information

When we compare two files, the model (in particular the Position entity) does not have the file name corresponding to such files. Instead it has as file name "/test".

Strange AST diff with too many changes

The diff of this file produces a strange diff. I don't understand how gumtree produces the diff.

Delete If at org.apache.camel.processor.MulticastProcessor:445
	if ((future == null) && timedOut) {
	    break;
	}else
	    if (future == null) {
	        org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(null);
	        if (strategy instanceof org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) {
	            Exchange oldExchange = result.get();
	            if (oldExchange == null) {
	                oldExchange = original;
	            }
	            ((org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) (strategy)).timeout(oldExchange, aggregated, total.intValue(), timeout);
	        }else {
	            org.apache.camel.processor.MulticastProcessor.LOG.warn("Parallel processing timed out after {} millis for number {}. This task will be cancelled and will not be aggregated.", timeout, aggregated);
	        }
	        org.apache.camel.processor.MulticastProcessor.LOG.debug("Timeout occurred after {} millis for number {} task.", timeout, aggregated);
	        timedOut = true;
	        if ((completion) instanceof org.apache.camel.util.concurrent.SubmitOrderedCompletionService) {
	            ((org.apache.camel.util.concurrent.SubmitOrderedCompletionService<?>) (completion)).timeoutTask();
	        }
	    }else {
	        Exchange subExchange = future.get();
	        java.lang.Integer number = getExchangeIndex(subExchange);
	        boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, ("Parallel processing failed for number " + number), org.apache.camel.processor.MulticastProcessor.LOG);
	        if ((stopOnException) && (!continueProcessing)) {
	            result.set(subExchange);
	            stoppedOnException = true;
	            break;
	        }
	        org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(subExchange);
	        doAggregate(strategy, result, subExchange);
	    }
Delete Block at org.apache.camel.processor.MulticastProcessor:408
	{
	    boolean timedOut = false;
	    boolean stoppedOnException = false;
	    final StopWatch watch = new StopWatch();
	    int aggregated = 0;
	    boolean done = false;
	    while (!done) {
	        if ((allTasksSubmitted.get()) && (aggregated >= (total.get()))) {
	            MulticastProcessor.LOG.debug("Done aggregating {} exchanges on the fly.", aggregated);
	            break;
	        }
	        Future<Exchange> future;
	        if (timedOut) {
	            future = completion.poll();
	            MulticastProcessor.LOG.trace("Polled completion task #{} after timeout to grab already completed tasks: {}", aggregated, future);
	        }else
	            if ((timeout) > 0) {
	                long left = (timeout) - (watch.taken());
	                if (left < 0) {
	                    left = 0;
	                }
	                MulticastProcessor.LOG.trace("Polling completion task #{} using timeout {} millis.", aggregated, left);
	                future = completion.poll(left, java.util.concurrent.TimeUnit.MILLISECONDS);
	            }else {
	                MulticastProcessor.LOG.trace("Polling completion task #{}", aggregated);
	                future = completion.poll(1, java.util.concurrent.TimeUnit.SECONDS);
	                if (future == null) {
	                    continue;
	                }
	            }
	        
	        if ((future == null) && timedOut) {
	            break;
	        }else
	            if (future == null) {
	                org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(null);
	                if (strategy instanceof org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) {
	                    Exchange oldExchange = result.get();
	                    if (oldExchange == null) {
	                        oldExchange = original;
	                    }
	                    ((org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) (strategy)).timeout(oldExchange, aggregated, total.intValue(), timeout);
	                }else {
	                    MulticastProcessor.LOG.warn("Parallel processing timed out after {} millis for number {}. This task will be cancelled and will not be aggregated.", timeout, aggregated);
	                }
	                MulticastProcessor.LOG.debug("Timeout occurred after {} millis for number {} task.", timeout, aggregated);
	                timedOut = true;
	                if ((completion) instanceof org.apache.camel.util.concurrent.SubmitOrderedCompletionService) {
	                    ((org.apache.camel.util.concurrent.SubmitOrderedCompletionService<?>) (completion)).timeoutTask();
	                }
	            }else {
	                Exchange subExchange = future.get();
	                java.lang.Integer number = getExchangeIndex(subExchange);
	                boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, ("Parallel processing failed for number " + number), MulticastProcessor.LOG);
	                if ((stopOnException) && (!continueProcessing)) {
	                    result.set(subExchange);
	                    stoppedOnException = true;
	                    break;
	                }
	                org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(subExchange);
	                doAggregate(strategy, result, subExchange);
	            }
	        
	        aggregated++;
	    } 
	    if (timedOut || stoppedOnException) {
	        if (timedOut) {
	            MulticastProcessor.LOG.debug("Cancelling tasks due timeout after {} millis.", timeout);
	        }
	        if (stoppedOnException) {
	            MulticastProcessor.LOG.debug("Cancelling tasks due stopOnException.");
	        }
	        running.set(false);
	    }
	}
Insert While at org.apache.camel.processor.MulticastProcessor:415
	while (!done) {
	    if ((allTasksSubmitted.get()) && (aggregated >= (total.get()))) {
	        MulticastProcessor.LOG.debug("Done aggregating {} exchanges on the fly.", aggregated);
	        break;
	    }
	    Future<Exchange> future;
	    if (timedOut) {
	        future = completion.poll();
	        MulticastProcessor.LOG.trace("Polled completion task #{} after timeout to grab already completed tasks: {}", aggregated, future);
	    }else
	        if ((timeout) > 0) {
	            long left = (timeout) - (watch.taken());
	            if (left < 0) {
	                left = 0;
	            }
	            MulticastProcessor.LOG.trace("Polling completion task #{} using timeout {} millis.", aggregated, left);
	            future = completion.poll(left, java.util.concurrent.TimeUnit.MILLISECONDS);
	        }else {
	            MulticastProcessor.LOG.trace("Polling completion task #{}", aggregated);
	            future = completion.poll(1, java.util.concurrent.TimeUnit.SECONDS);
	            if (future == null) {
	                continue;
	            }
	        }
	    
	    if (future == null) {
	        AggregationStrategy strategy = getAggregationStrategy(null);
	        if (strategy instanceof TimeoutAwareAggregationStrategy) {
	            Exchange oldExchange = result.get();
	            if (oldExchange == null) {
	                oldExchange = original;
	            }
	            ((TimeoutAwareAggregationStrategy) (strategy)).timeout(oldExchange, aggregated, total.intValue(), timeout);
	        }else {
	            MulticastProcessor.LOG.warn("Parallel processing timed out after {} millis for number {}. This task will be cancelled and will not be aggregated.", timeout, aggregated);
	        }
	        MulticastProcessor.LOG.debug("Timeout occurred after {} millis for number {} task.", timeout, aggregated);
	        timedOut = true;
	        if ((completion) instanceof SubmitOrderedCompletionService) {
	            ((SubmitOrderedCompletionService<?>) (completion)).timeoutTask();
	        }
	    }else {
	        Exchange subExchange = future.get();
	        Integer number = getExchangeIndex(subExchange);
	        boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, ("Parallel processing failed for number " + number), MulticastProcessor.LOG);
	        if ((stopOnException) && (!continueProcessing)) {
	            result.set(subExchange);
	            stoppedOnException = true;
	            break;
	        }
	        AggregationStrategy strategy = getAggregationStrategy(subExchange);
	        doAggregate(strategy, result, subExchange);
	    }
	    aggregated++;
	} 
Move Block from org.apache.camel.processor.MulticastProcessor$AggregateOnTheFlyTask:415 to org.apache.camel.processor.MulticastProcessor$AggregateOnTheFlyTask:408
	{
	    if ((allTasksSubmitted.get()) && (aggregated >= (total.get()))) {
	        MulticastProcessor.LOG.debug("Done aggregating {} exchanges on the fly.", aggregated);
	        break;
	    }
	    Future<Exchange> future;
	    if (timedOut) {
	        future = completion.poll();
	        MulticastProcessor.LOG.trace("Polled completion task #{} after timeout to grab already completed tasks: {}", aggregated, future);
	    }else
	        if ((timeout) > 0) {
	            long left = (timeout) - (watch.taken());
	            if (left < 0) {
	                left = 0;
	            }
	            MulticastProcessor.LOG.trace("Polling completion task #{} using timeout {} millis.", aggregated, left);
	            future = completion.poll(left, java.util.concurrent.TimeUnit.MILLISECONDS);
	        }else {
	            MulticastProcessor.LOG.trace("Polling completion task #{}", aggregated);
	            future = completion.poll(1, java.util.concurrent.TimeUnit.SECONDS);
	            if (future == null) {
	                continue;
	            }
	        }
	    
	    if ((future == null) && timedOut) {
	        break;
	    }else
	        if (future == null) {
	            org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(null);
	            if (strategy instanceof org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) {
	                Exchange oldExchange = result.get();
	                if (oldExchange == null) {
	                    oldExchange = original;
	                }
	                ((org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) (strategy)).timeout(oldExchange, aggregated, total.intValue(), timeout);
	            }else {
	                MulticastProcessor.LOG.warn("Parallel processing timed out after {} millis for number {}. This task will be cancelled and will not be aggregated.", timeout, aggregated);
	            }
	            MulticastProcessor.LOG.debug("Timeout occurred after {} millis for number {} task.", timeout, aggregated);
	            timedOut = true;
	            if ((completion) instanceof org.apache.camel.util.concurrent.SubmitOrderedCompletionService) {
	                ((org.apache.camel.util.concurrent.SubmitOrderedCompletionService<?>) (completion)).timeoutTask();
	            }
	        }else {
	            Exchange subExchange = future.get();
	            java.lang.Integer number = getExchangeIndex(subExchange);
	            boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, ("Parallel processing failed for number " + number), MulticastProcessor.LOG);
	            if ((stopOnException) && (!continueProcessing)) {
	                result.set(subExchange);
	                stoppedOnException = true;
	                break;
	            }
	            org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(subExchange);
	            doAggregate(strategy, result, subExchange);
	        }
	    
	    aggregated++;
	}
Move Block from org.apache.camel.processor.MulticastProcessor$AggregateOnTheFlyTask:-1 to org.apache.camel.processor.MulticastProcessor$AggregateOnTheFlyTask:415
	
	    if (future == null) {
	        org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(null);
	        if (strategy instanceof org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) {
	            org.apache.camel.Exchange oldExchange = result.get();
	            if (oldExchange == null) {
	                oldExchange = original;
	            }
	            ((org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy) (strategy)).timeout(oldExchange, aggregated, total.intValue(), timeout);
	        }else {
	            org.apache.camel.processor.MulticastProcessor.LOG.warn("Parallel processing timed out after {} millis for number {}. This task will be cancelled and will not be aggregated.", timeout, aggregated);
	        }
	        org.apache.camel.processor.MulticastProcessor.LOG.debug("Timeout occurred after {} millis for number {} task.", timeout, aggregated);
	        timedOut = true;
	        if ((completion) instanceof org.apache.camel.util.concurrent.SubmitOrderedCompletionService) {
	            ((org.apache.camel.util.concurrent.SubmitOrderedCompletionService<?>) (completion)).timeoutTask();
	        }
	    }else {
	        org.apache.camel.Exchange subExchange = future.get();
	        java.lang.Integer number = getExchangeIndex(subExchange);
	        boolean continueProcessing = PipelineHelper.continueProcessing(subExchange, ("Parallel processing failed for number " + number), org.apache.camel.processor.MulticastProcessor.LOG);
	        if ((stopOnException) && (!continueProcessing)) {
	            result.set(subExchange);
	            stoppedOnException = true;
	            break;
	        }
	        org.apache.camel.processor.aggregate.AggregationStrategy strategy = getAggregationStrategy(subExchange);
	        doAggregate(strategy, result, subExchange);
	    }
Move UnaryOperator from org.apache.camel.processor.MulticastProcessor$AggregateOnTheFlyTask:415 to org.apache.camel.processor.MulticastProcessor$AggregateOnTheFlyTask:415
	!done

diff.txt
original.txt
patched.txt

feat: Compare AST modulo identifier

It would be nice to be able to compare AST, without having names impacting the results.
It could be useful to detect clones, or to compare decopiled code.

Modifiable without children

In our model, we add a GumTree node named Modifiers_X, which groups the modifiers of a Modifiable element.
However, if that element does not have modifiers, the GT node Modifiers is empty.
So, in branch https://github.com/SpoonLabs/gumtree-spoon-ast-diff/tree/emptymodifiable I propose to avoid creating such elements, with the goal of creating smaller trees, and consequently faster matchings.
By contrary, we loose explicit information in our model (i.e., the Modifiable element does not have any modifier).
WDYT?
If you agree, we create a PR from that branch.

Adds a license

That could be a good idea to apply a license on this project.

Why not something like an Apache or MIT license?

NullPointerException because the typeAccess.getAccessedType() is null

Hi, when I am processing a java file with gumtreespoon, I find that for some files the visitCtTypeAccess() in LabelFinder.java throws NullPointerException, and the reason is that the typeAccess.getAccessedType() returns null but it calls getQualifiedName().

Sorry I am not allowed to upload the java file that triggers the bug, but you can find it in AOSP(Google Android Open Source Project), the file is \base\core\java\android\widget\ImageView.java)

Use the role in the parent instead of the type

I was thinking that maybe we will obtain better results if we use the role in the parent to label each node instead of the class.
Because what matter is the role stay consistent the the type of element can change.

Ex:

-            yearOfEra,
+            (era == GregorianCalendar.AD ? yearOfEra : 1 - yearOfEra),

Currently, this operation will be tagged as delete + insert because yearOfEra is a CtVariableAccess and (era == GregorianCalendar.AD ? yearOfEra : 1 - yearOfEra) is a CtConditional.
The two type does not match and is thus not considered as an update.

WDYT?

Unable to detect change in child of BinaryOperator

Concerned patch:

--- /src/com/google/javascript/rhino/testing/Asserts.java
+++ /src/com/google/javascript/rhino/testing/Asserts.java
@@ -102,3 +102,3 @@
         (a == null) == (b == null));
-    if (a == null) {
+    if (message!=null) {
       return;

Output:

Update BinaryOperator at com.google.javascript.rhino.testing.Asserts:103
        a == null to message != null

The concerned files can be found here (private repo)

Can not detect the change of Exception type

The tool does not detect changes in Exception Type.
for example:

public class TestMain {
    static public void main() throws IOException {
        try{
            TestClass.testMethod(1);
        }catch (IllegalArgumentException e){
            e.printStackTrace();
           
        }
    }
}
public class TestMain {
    static public void main() throws IOException {
        try{
            TestClass.testMethod(1);
        }catch (NullPointerException e){
            e.printStackTrace();
          
        }
    }
}

and I got "no AST change" as Result

Spoon Upgrade

Spoon 5.2.0-Snapshot breaks Test cases. test_t_223454() and test_t_225073()
We are currently using 5.0.1

JVM used: 1.8.0.65

Detects a method as Updated and on the same time as Inserted

Hello, I am processing two revisions of a Java file (rev. 1 and rev. 2) with Gumtree Spoon AST Diff.

File fl = new File("src/test/resources/FileCleaner(1)f732c9a50cbaf0675e8b1846c8d3480c06f297a2.java");
File fr = new File("src/test/resources/FileCleaner(2)9c18390762527fe8d1bf7af8ded586566cdbb59c.java");

Diff result = new AstComparator().compare(fl, fr);
List<Operation> operations = result.getAllOperations();
for (Operation operation : operations) {

   CtElement element = (CtElement) operation.getAction().getNode().getMetadata(SpoonGumTreeBuilder.SPOON_OBJECT);
   String nodeType = element.getClass().getSimpleName();
   nodeType = nodeType.substring(2, nodeType.length() - 4);

   if (Objects.equals(nodeType, "Method")) {
      if (operation.getAction() instanceof Insert) {
         InsertOperation insertOperation = new InsertOperation((Insert) operation.getAction());
         System.out.println(insertOperation);
      }
      else if (operation.getAction() instanceof Update) {
         UpdateOperation updateOperation = new UpdateOperation((Update) operation.getAction());
         System.out.println(updateOperation);
      }
   }
}

When I execute the above snippet, it returns the method: exitWhenFinished Updated and on the same time Inserted. But this is not true, since the method does not exist on rev. 1.
Am I doing something wrong or is there a bug?
Thank you in advance!

GumTree dependency

GumTree project now uses Gradle instead of Maven.
This change complicates the installation of our tool.

Inconsistency on Move

Inconsistency with Parent attributes from MoveOperation and MoveAction.
That one from MoveOperator references to the element in the Left tree. That one from MoveAction references to the Right tree.

Left and Right versions of the same CtElement

Hello,

this is just a question.

Let A and B be two versions of the same file. An insert action happened between the two versions. From the action I can extract the CtElement inserted. From the element, I can extract its parent method. This is the method as it appears in version B.

Question: how can I get the same method as it appears in version A?

What I tried
From Metadata
The following returns me the node in the version B (if is Insert action):
CtElement element = (CtElement) action.getNode().getMetadata(SpoonGumTreeBuilder.SPOON_OBJECT);

The following metadata:
SpoonGumTreeBuilder.SPOON_OBJECT_DEST
is null for Insert operation (looks like only set for Move operations).

Inverse Diff
I tried to perform two diffs: the A-to-B diff and the B-to-A diff. However, the insert operation (in A-to-B) become a delete operation (in B-to-A) and therefore I always get only the B version of the method.

Manually map Method signatures
The only way I see is to go in the file A, extract the types of CtElement I want (let's say CtMethod) and check if the signature is equal to the CtElement I got in B. This should work, except when an operation changed the signature of the method!

Do you guys have any suggestion for this? Maybe is less complicated than this and I'm just missing an API call.

p.s. Thanks for yet another great SpoonLab project! :)

question: what is the definition of root operations?

when I diff the following code, I found there are 2 operations on the same node from different granularities (the second operation seems a sub-operation of the first one). What I want is something like delete method sub; insert method mul or update method sub to mul.

root operations I got

1

origin

public class Calculator {
    public static int add(int a, int b){
        return a + b;
    }

    public static int sub(int a, int b){
        return a-b;
    }
}

modified

public class Calculator {
    public static int add(int a, int b){
        return a + b;
    }

    public static int mul(int a, int b){
        return a*b;
    }

    public static double div(int a, int b){
        return 1.0*a/b;
    }
}

[Important] Bug inducing Fix

One of the changes introduces a bug (or at least a strange situation) such as "empty" Insert operations with Null nodes. i.e., we dont know the node that is inserted.

The problem is here:
4899ca0#diff-009fc305480e133b71e1bbe2ddadac09L29

There, it was removed one line that associates a Tree node with the corresponding Spoon node. Thus, GumTree detects changes on its model (the Tree) that has not any Spoon element associated.

Crash on runtime because Spoon is not up to date with Gumtree

When I try to execute Gumtree-Spoon, Java returns me this error :

java.lang.IllegalArgumentException: Please support the new type class com.github.gumtreediff.actions.model.TreeDelete
	at gumtree.spoon.diff.DiffImpl.lambda$convertToSpoon$0(DiffImpl.java:100)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at gumtree.spoon.diff.DiffImpl.convertToSpoon(DiffImpl.java:102)
	at gumtree.spoon.diff.DiffImpl.<init>(DiffImpl.java:71)
	at gumtree.spoon.AstComparator.compare(AstComparator.java:92)
	at gumtree.spoon.AstComparator.compare(AstComparator.java:84)
	at launcher.Launcher.main(Launcher.java:86)

It's explicit enough, I think Gumtree-Spoon need to implement a new structure inside gumtree (TreeDelete) in order to be used.

Signature of CtInvocation changed with Spoon 6.0.0

Gumtree-spoon-ast-diff unit tests are not passing anymore with Spoon 6.0.0 because the printed signature of a CtInvocation node changed.
For example in test7, the following signature is expected:

QuickNotepadTextArea#addKeyListener(QuickNotepad$KeyHandler)

and the current result is now:

addKeyListener(QuickNotepad$KeyHandler)

In other word, the declaring type is not printed anymore before the signature of the executable.
I do not know if it's a problem that should be reported to Spoon, or if we need to change the tests here.
WDYT?

Tree representation.

test_t_223454() fails at representing one Tree.
The label represent a Constructor Call with one argument (String), where the original changed code is a constructor call with two arguments String+ File.
The changed version removes String args

Throw exception when insert `static` at a method head

Input

AstComparator comp = new AstComparator();
        Diff diff = comp.compare("public class Calculator {\n" +
                "    public int add(int a, int b){\n" +
                "        return a + b;\n" +
                "    }\n" +
                "}", "public class Calculator {\n" +
                "    public static int add(int a, int b){\n" +
                "        return a + b;\n" +
                "    }\n" +
                "}");

Result

The operation Delete class is wrong since the class name is not changed!

The move operation is wired and throws exception!

Screen Shot 2019-04-08 at 4 12 48 PM

Update 'the latest stable JAR' in README

Using the JAR from 'the latest stable JAR' in REAMDE cause the following exception:

Exception in thread "main" java.lang.NoClassDefFoundError: com/github/gumtreediff/tree/TreeContext
	at gumtree.spoon.builder.SpoonGumTreeBuilder.<init>(SpoonGumTreeBuilder.java:34)
	at gumtree.spoon.AstComparator.compare(AstComparator.java:91)
	at gumtree.spoon.AstComparator.compare(AstComparator.java:77)
	at se.kth.statementDiff.App.main(App.java:32)
Caused by: java.lang.ClassNotFoundException: com.github.gumtreediff.tree.TreeContext
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 4 more

However, building and using JAR (gumtree-spoon-ast-diff-1.6-SNAPSHOT-jar-with-dependencies.jar) from source is fine.

Support for generics

Currently, we don't support identifying changes which are related generics in Java.
This happens due to two reasons:

  • Ignoring all CtReference in isToIgnore()
  • No labels are assigned to CtReference in gumtree.spoon.builder.LabelFinder.

In the scenario of Class<T>, T has the role of TYPE_ARGUMENT and Class<T> has the role of TYPE. Checking for this pattern in isToIgnore() and making corresponding changes in LabelFinder should help us consider the scenario of generics, but it may also result in consideration of unwanted cases.

For example,
The above conditions lead to a weird failure of one test case, ie: DiffTest.testToString().
image

Feature Request: modify the ast trees with changes

Feature Desciption

I get a change list after calling AstComparator.compare method. Assume I want to modify the left ast tree, which means I may replace some change nodes with the corresponding nodes in the the right ast tree.

Then, I want to generate a new .java file from the modified ast tree.

How can I do this based on current version gumtree-spoon-ast-diff?

Detects no changes when adding 'throws Exception'

Hi,

Let us assume that we have the following two java files:

foo_pre.java:

class foo{
    public static void main(String[] args) {}
}

foo_post.java:

class foo{
    public static void main(String[] args) throws Exception {}
}

gumtree-spoon-ast-diff does not detect any changes, is it a bug?

not able to handle one case of ExplicitConversion

For example, run this

@Test
public void testExplicitConversion() throws Exception {
    AstComparator comparator = new AstComparator();
    String a = "class Foo{public void bar(){\ndouble b = 0;\n}}";
    String b = "class Foo{public void bar(){\ndouble b = (double) 0;\n}}";
    Diff diff = comparator.compare(a, b);
    List<Operation> operations = diff.getRootOperations();
    for (Operation operation : operations) {
        System.out.println(operation);
        System.out.println("================");
    }
    a = "class Foo{public void bar(){\nint a = 1;\ndouble b = a;\n}}";
    b = "class Foo{public void bar(){\nint a = 1;\ndouble b = (double) a;\n}}";
    diff = comparator.compare(a, b);
    operations = diff.getRootOperations();
    for (Operation operation : operations) {
        System.out.println(operation);
        System.out.println("================");
    }
}

and we only get

Update Literal at Foo:2
	0 to ((double) (0))

================

I also tried the webdiff tool from gumtree and did not meet this issue.

Output messages in case of Modifiable elements

Case 1

Input:
1.java

package com.test;

class TestClass {
    private int a;
    TestClass() {
    }
}

2.java

package com.test;

class TestClass {
    public int a;
    TestClass() {
    }
}

Output:

Update Wra at com.test.TestClass
	 to public

Case 2

Input:
same as above with the following diff
2.java

-    public int a;
+    int a;

Output:

Delete VirtualEle at com.test.TestClass

add higher-level operation "Replace"

In Gumtree, an "Update" operation means that:

  • either the it's a string based element and the string has changed
  • or that only a small fraction of children has changed (to be verified).

Assume that we have one literal replaced by a method call. This is represented by one deletion and one addition. We can have a higher-level operation "Replace" instead.

configuration parameter

A design decision:
How we can implement in an elegant manner the configuration of properties that this tool uses?
In Astor/Coming, we use a static map, initialized at the beginning from a file, then updates according to the command line and finally the different component access to that map to get the values.

Any other alternative? Should we implement a similar strategy?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.