Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add and remove a method to a class and compile it #575

Closed
mehdijk opened this issue Apr 6, 2016 · 15 comments
Closed

add and remove a method to a class and compile it #575

mehdijk opened this issue Apr 6, 2016 · 15 comments

Comments

@mehdijk
Copy link

mehdijk commented Apr 6, 2016

Hi
I have a ctClass object and add a ctMethod to its compilation. It works well but when I remove this method from class again and try to compile, it keeps the ctMethod. Actually it shows that it has been removed successfully but when I compile, it is already there and makes the same compilation error.
I have another question. How can I get the compilation output message when I call compile() from ModelBuilder? It only returns a boolean value but I need detail of the compilation output which is shown in console.
This is a my code:
`ctClazz.addMethod(ctMethod);

launcher.getModelBuilder().compile();
ctClazz.removeMethod(ctMethod);
launcher.getModelBuilder().compile();`

@tdurieux
Copy link
Collaborator

tdurieux commented Apr 7, 2016

Hi,

I can reproduce you problem, it appears when you try to compile more than one time a class (cache).

Unfortunately there is no good workaround (you can use reflection see below).

You can get the compilation error via:

SpoonModelBuilder modelBuilder = launcher.getModelBuilder();
if (modelBuilder instanceof JDTBasedSpoonCompiler) {
    ((JDTBasedSpoonCompiler) modelBuilder).getProblems();
}

But there is no way to get the warning messages (the method that handles the output is JDTBatchCompiler.getBatchRequestor())

Clear compilation cache manually

Field loadedContent = modelBuilder.getClass().getDeclaredField("loadedContent");
loadedContent.setAccessible(true);
Object loadedContentObj = loadedContent.get(modelBuilder);
Method clear = loadedContentObj.getClass().getDeclaredMethod("clear");
clear.invoke(loadedContentObj);

@mehdijk
Copy link
Author

mehdijk commented Apr 7, 2016

Hi
Thank you for response.
I clear compilation cache manually according to that code. The compilation message is expected but at the end of the message, an spoon.compiler.ModelBuildingException occurs again. Actually this situation is happened when I do add and remove like this:
ctClazz.addMethod(ctMethod1); ctClazz.removeMethod(ctMethod2); launcher.getModelBuilder().compile(); //clear the cache ctClazz.addMethod(ctMethod2); ctClazz.removeMethod(ctMethod1); launcher.getModelBuilder().compile();

@tdurieux
Copy link
Collaborator

tdurieux commented Apr 7, 2016

What is the message of ModelBuildingException?

@mehdijk
Copy link
Author

mehdijk commented Apr 7, 2016

After first compile this is the part or output:
....
3 problems (1 error, 2 warnings)
spoon.compiler.ModelBuildingException: s1 cannot be resolved to a variable at C:\Users\mehdi\workspace\myTestProj\src\com\test\ClazzA.java:14
at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.report(JDTBasedSpoonCompiler.java:650)
at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.reportProblems(JDTBasedSpoonCompiler.java:631)
at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.compile(JDTBasedSpoonCompiler.java:185)
at test.SpoonModel.main(SpoonModel.java:240)

and after the second compile it shows the same error:
...
1 problem (1 warning)
spoon.compiler.ModelBuildingException: s1 cannot be resolved to a variable at C:\Users\mehdi\workspace\myTestProj\src\com\test\ClazzA.java:14
at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.report(JDTBasedSpoonCompiler.java:650)
at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.reportProblems(JDTBasedSpoonCompiler.java:631)
at spoon.support.compiler.jdt.JDTBasedSpoonCompiler.compile(JDTBasedSpoonCompiler.java:185)
at test.SpoonModel.main(SpoonModel.java:260)

@tdurieux
Copy link
Collaborator

tdurieux commented Apr 7, 2016

Ok, can you provide the content of ClassA?

@mehdijk
Copy link
Author

mehdijk commented Apr 7, 2016

`package test;

public class ClazzA {
public static final int waitingTime = 1000;
protected String s1;
private int i1;
public double f1;
private boolean b1;

public void a1(int x) throws InterruptedException  {
    s1="test";
    a2();
    a1(5);
}

private int a1(double x) {
    f1=x;
    return i1;
}

public void a2() throws InterruptedException {
    a3();
}

public void a3()  {
    if(b1) a3();
}

}`

I remove a1(int x) and add a new version of method by commenting the first line (//s1="test";) and then try to rollback to the original version by removing the modified method and add the original method.

@tdurieux
Copy link
Collaborator

tdurieux commented Apr 7, 2016

How do you comment the first line (//s1="test";) because comments are not yet supported by spoon?

@mehdijk
Copy link
Author

mehdijk commented Apr 7, 2016

There is no different. even when I remove that line, the result is the same.
Sorry I should remove the S1 definition from the class. but the result is the same.
this is the original version:
`package test;

public class ClazzA {
public static final int waitingTime = 1000;
private int i1;
public double f1;
private boolean b1;

public void a1(int x) throws InterruptedException  {
    a2();
    a1(4.6);
}
private int a1(double x) {
    f1=x;
    return i1;
}

protected void a2() throws InterruptedException {
    a2();
}

}`

I remove the a1(int x) method and add a method with this code:
public void a1(int x) throws InterruptedException { s1="test"; a2(); a1(5); }
and then rollback to the original version.

@tdurieux
Copy link
Collaborator

tdurieux commented Apr 7, 2016

I cannot reproduce your problem.
Can you check that the following code do what you went?

@Test
public void test() throws Exception {
    Launcher launcher = new Launcher();
    launcher.addInputResource("./src/test/java/" + ClazzA.class.getName().replace('.', '/') + ".java");
    launcher.buildModel();
    CoreFactory core = launcher.getFactory().Core();
    CodeFactory code = launcher.getFactory().Code();
    TypeFactory type = launcher.getFactory().Type();

    final CtClass<?> clazzA = (CtClass<?>) launcher.getFactory().Type().get(ClazzA.class);


    SpoonModelBuilder modelBuilder = launcher.getModelBuilder();

    // first compilation
    System.out.println(clazzA);
    modelBuilder.compile();

    // get method a1
    CtMethod<?> a1 = clazzA.getMethodsByName("a1").get(1);
    CtMethod<?> a1Clone = core.clone(a1);

    // create field s1
    CtField f1 = (CtField) core.createField().<CtField>setSimpleName("s1").setType(type.createReference(String.class));
    clazzA.addField(f1);

    // create s1 assignment
    CtStatement test = (CtStatement) core.createAssignment().setAssigned(core.createFieldWrite().setVariable(f1.getReference())).setAssignment(code.createLiteral("test"));
    a1Clone.getBody().insertBegin(test);

    // change invocation parameter
    ((CtInvocation)a1Clone.getBody().getLastStatement()).setArguments(Arrays.asList(code.createLiteral(5)));

    // change a1
    clazzA.removeMethod(a1);
    clazzA.addMethod(a1Clone);

    // second compilation
    System.out.println(clazzA);
    clearCompilationCache(modelBuilder);
    modelBuilder.compile();

    // rollback
    clazzA.removeMethod(a1Clone);
    clazzA.addMethod(a1);
    clazzA.removeField(f1);

    // third compilation
    System.out.println(clazzA);
    clearCompilationCache(modelBuilder);
    modelBuilder.compile();
}

private void clearCompilationCache(SpoonModelBuilder modelBuilder)
        throws NoSuchFieldException, IllegalAccessException,
        NoSuchMethodException, InvocationTargetException {
    Field loadedContent = modelBuilder.getClass().getDeclaredField("loadedContent");
    loadedContent.setAccessible(true);
    Object loadedContentObj = loadedContent.get(modelBuilder);
    Method clear = loadedContentObj.getClass().getDeclaredMethod("clear");
    clear.invoke(loadedContentObj);
}

@mehdijk
Copy link
Author

mehdijk commented Apr 7, 2016

Let me explain my problem clearly:
I have two source code. let suppose it has only one class (ClazzA). First version includes s1 and also on a1 method it assign s1="test";
first version of ClazzA:
`package test;

public class ClazzA {

public static final int waitingTime = 1000;
protected String s1;
private int i1;
public double f1;
private boolean b1;


public void a1(int x) throws InterruptedException  {
    s1="test";
    a2();
    a1(5);

}
private int a1(double x) {
    f1=x;
    return i1;

}

public void a2() throws InterruptedException {
    a3();
}

public void a3()  {
    if(b1) a3();
}

}`

on the new version I remove field s1 and also the line which can use s1 in a1(int x). (I also remove a3() in my test case)
this is the new version of ClazzA:
`package test;
public class ClazzA {

public static final int waitingTime = 1000;
private int i1;
public double f1;
private boolean b1;

public void a1(int x) throws InterruptedException  {
    a2();
    a1(4.6);
}
private int a1(double x) {
    f1=x;
    return i1;
}

protected void a2() throws InterruptedException {
    a2();
}

}
`
Ok. Now I make two spoon models. One for old version and one for the new. Then I try to check if I remove a1(int x) from new version and add a1(int x) from the old version to the new model, could it compile successfully? (it can not compile because s1 is not exist in new version). then try to recover old version and it should compile successfully.
I don't clone ctmethod manually. I use old spoon model ctmethod and add it to the new model.

@mehdijk
Copy link
Author

mehdijk commented Apr 7, 2016

Where can I find this fixed version?
I download spoon-core-5.2.0-20160406.234849-17-jar-with-dependencies.jar . But it is not included.
Thanks

@GerardPaligot
Copy link
Contributor

A snapshot isn't deployed for each PR. If you want to use my fix before its merge, you must:

  1. Fork Spoon.
  2. Fetch the branch of my PR on your local repository.
  3. Execute mvn install to install this version of Spoon on your local maven repository.

@mehdijk
Copy link
Author

mehdijk commented Apr 8, 2016

Thanks @GerardPaligot I couldn't build it. mvn install terminates with an error:
Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project spoon-core: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test failed: The forked VM terminated without saying properly goodbye. VM crash or System.exit called

@mehdijk
Copy link
Author

mehdijk commented Apr 8, 2016

I temporary fix the problem on my code by calling clear method of probs and loadedContent with reflection, according to the @GerardPaligot fixation on Spoon.
Thank you so much @GerardPaligot and @tdurieux

@monperrus
Copy link
Collaborator

I propose to always invalidate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants