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

feat(SpoonifierVisitor): a visitor that generate the spoon code to recreate a spoon model #3105

Merged
merged 14 commits into from
Sep 22, 2019
128 changes: 128 additions & 0 deletions src/main/java/spoon/experimental/SpoonifyVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package spoon.experimental;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.path.CtRole;
import spoon.reflect.reference.CtReference;
import spoon.reflect.visitor.CtScanner;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

public class SpoonifyVisitor extends CtScanner {
nharrand marked this conversation as resolved.
Show resolved Hide resolved
StringBuilder result = new StringBuilder();
Map<String,Integer> variableCount = new HashMap<>();
Stack<String> parentName = new Stack<>();
Stack<Map<CtRole,String>> roleContainer = new Stack<>();

private String getVariableName(String className) {
if(!variableCount.containsKey(className)) {
variableCount.put(className,0);
}
int count = variableCount.get(className);
String variableName = className.substring(0, 1).toLowerCase() + className.substring(1) + count;
variableCount.put(className, count + 1);
return variableName;
}

@Override
public void enter(CtElement element) {
String elementClass = element.getClass().getSimpleName();
if(elementClass.endsWith("Impl")) {
elementClass = elementClass.replace("Impl","");
}
String variableName = getVariableName(elementClass);

result.append(elementClass + " " + variableName + " = factory.create" + elementClass.replaceFirst("Ct","") + "();\n");

if(element instanceof CtNamedElement) {
result.append(variableName + ".setSimpleName(\"" + ((CtNamedElement) element).getSimpleName() + "\");\n");
}

if(element instanceof CtReference) {
result.append(variableName + ".setSimpleName(\"" + ((CtReference) element).getSimpleName() + "\");\n");
}
if(element instanceof CtModifiable && !((CtModifiable) element).getModifiers().isEmpty()) {
result.append("Set<ModifierKind> " + variableName + "Modifiers = new HashSet<>();\n");
for(ModifierKind mod : ((CtModifiable) element).getModifiers()) {
result.append(variableName + "Modifiers.add(ModifierKind." + mod.name() + ");\n");
}
result.append(variableName + ".setModifiers(" + variableName + "Modifiers);\n");
}

if(element.isImplicit()) {
result.append(variableName + ".setImplicit(true);\n");
}

if(element.isParentInitialized() && !parentName.isEmpty()) {
CtRole elementRoleInParent = element.getRoleInParent();

CtElement parent = element.getParent();
Object o = parent.getValueByRole(elementRoleInParent);
if(o instanceof Map) {
handleContainer(element,elementRoleInParent,variableName,"Map");
} else if(o instanceof List) {
handleContainer(element,elementRoleInParent,variableName,"List");
} else if(o instanceof Set) {
handleContainer(element,elementRoleInParent,variableName,"Set");
} else {
result.append(parentName.peek() + ".setValueByRole(CtRole." + elementRoleInParent.name() + ", " + variableName + ");\n");
}
}
parentName.push(variableName);
roleContainer.push(new HashMap<>());
}

private void handleContainer(CtElement element, CtRole elementRoleInParent, String variableName, String container) {
String concreteClass = null;

switch (container) {
case "Map":
concreteClass = "HashMap";
break;
case "List":
concreteClass = "ArrayList";
break;
case "Set":
concreteClass = "HashSet";
break;
}

String containerName;
if (!roleContainer.peek().containsKey(elementRoleInParent)) {
containerName = parentName.peek() + elementRoleInParent.toString().substring(0,1).toUpperCase() + elementRoleInParent.toString().substring(1) + "s";
roleContainer.peek().put(elementRoleInParent, containerName);
result.append(container + " " + containerName + " = new " + concreteClass + "();\n");
} else {
containerName = roleContainer.peek().get(elementRoleInParent);
}

if (container.equals("Map")) {
result.append(containerName + ".put(" + ((CtNamedElement) element).getSimpleName() + "," + variableName + ");\n");
} else {
result.append(containerName + ".add(" + variableName + ");\n");
}
}

@Override
public void exit(CtElement element) {
if(!roleContainer.peek().isEmpty()) {
for(CtRole role: roleContainer.peek().keySet()) {
String variableName = roleContainer.peek().get(role);
result.append(parentName.peek() + ".setValueByRole(CtRole." + role.name() + ", " + variableName + ");\n");
//result.append(variableName + ".clear();\n");
}
}
parentName.pop();
roleContainer.pop();
}

public String getResult() {
return result.toString();
}
}
110 changes: 110 additions & 0 deletions src/test/java/spoon/test/spoonifier/SpoonifierTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package spoon.test.spoonifier;

import org.junit.Test;

import spoon.Launcher;
import spoon.SpoonModelBuilder;
import spoon.experimental.SpoonifyVisitor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.filter.TypeFilter;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;


import static org.junit.Assert.assertEquals;

public class SpoonifierTest {

@Test
public void testSpoonifier() throws ClassNotFoundException, MalformedURLException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
int i = 0;
testSpoonifierWith("src/test/java/spoon/test/prettyprinter/testclasses/A.java", i++);
testSpoonifierWith("src/test/java/spoon/test/prettyprinter/testclasses/AClass.java", i++);
testSpoonifierWith("src/test/java/spoon/test/prettyprinter/testclasses/ArrayRealVector.java", i++);
testSpoonifierWith("src/test/java/spoon/test/prettyprinter/testclasses/FooCasper.java", i++);
testSpoonifierWith("src/test/java/spoon/test/prettyprinter/testclasses/Rule.java", i++);

}

/*
* @pathToClass path to the class that will be Spoonified
* @i an integer to avoid duplicated classes
*
* This method verify that SpoonifyVisitor can generate code that replicate a class
nharrand marked this conversation as resolved.
Show resolved Hide resolved
*/
public void testSpoonifierWith(String pathToClass, int i)
throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

//Build the model of the given class
Launcher launcher = new Launcher();
launcher.addInputResource(pathToClass);
CtModel model = launcher.buildModel();
CtType targetType = model.getElements(new TypeFilter<CtType>(CtType.class)).get(0);

//Spoonify
String wrapper = generateSpoonifiyWrapper(targetType, i);

//Output launcher containing a class wrapping the generated code
Launcher oLauncher = new Launcher();
File outputBinDir = new File("./spooned-classes/");
oLauncher.setBinaryOutputDirectory(outputBinDir);

CtClass wrapperClass = oLauncher.parseClass(wrapper);
CtModel oModel = oLauncher.buildModel();
oLauncher.getEnvironment().disableConsistencyChecks();
oModel.getRootPackage().addType(wrapperClass);
oLauncher.getModelBuilder().compile(SpoonModelBuilder.InputType.CTTYPES);

//Invoke the code generated
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{outputBinDir.toURI().toURL()});
Class rtWrapper = urlClassLoader.loadClass("SpoonifierWrapper" + i);
Method get = rtWrapper.getMethod("get", Factory.class);
CtElement generatedElement = (CtElement) get.invoke(null, targetType.getFactory());

//The element created by the code generated by SpoonifyVisitor is equivalent to the visited element
assertEquals(targetType, generatedElement);
}

/*
* Generates a class with a single static method:
* public static CtElement get(Factory factory);
* This method calls the code generated by SpoonifyVisitor to
* recreate a CtElement equaled to the one visited.
*/
public String generateSpoonifiyWrapper(CtElement element, int i) {
String elementClass = element.getClass().getSimpleName();
if(elementClass.endsWith("Impl")) {
elementClass = elementClass.replace("Impl","");
}
String variableName = elementClass.substring(0, 1).toLowerCase() + elementClass.substring(1) + "0";

SpoonifyVisitor spoonifier = new SpoonifyVisitor();
element.accept(spoonifier);
StringBuffer buf = new StringBuffer();
buf.append("import java.util.*;\n");
buf.append("import spoon.reflect.code.*;\n");
buf.append("import spoon.reflect.declaration.*;\n");
buf.append("import spoon.reflect.factory.Factory;\n");
buf.append("import spoon.reflect.path.CtRole;\n");
buf.append("import spoon.reflect.reference.*;\n");
buf.append("public class SpoonifierWrapper" + i + "{\n");
buf.append("\tpublic static CtElement get(Factory factory) {\n");
buf.append(spoonifier.getResult());
buf.append("\treturn " + variableName + ";\n");
buf.append("\t}\n");
buf.append("}\n");

return buf.toString();
}


}