package org.eclipse.recommenders.jdt.templates; import static java.util.Arrays.asList; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor; import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; import org.eclipse.jdt.ui.SharedASTProvider; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.recommenders.testing.jdt.JavaProjectFixture; import org.eclipse.recommenders.utils.Pair; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import com.google.common.base.Joiner; @SuppressWarnings("restriction") @RunWith(Parameterized.class) public class SnippetCodeBuilderTest { private static final Joiner JOINER = Joiner.on('\n'); private static final String NO_SNIPPET = ""; private static final String HASH_MARKER = "#"; private JavaProjectFixture fixture = new JavaProjectFixture(ResourcesPlugin.getWorkspace(), getClass().getName()); private final CharSequence code; private final String customMarker; private final String expectedResult; private final List<String> nodeNames; private CompilationUnit ast; private IDocument document; private IRegion selection; private HashMap<ASTNode, String> nodesToReplace; private static int testCount; public SnippetCodeBuilderTest(String description, CharSequence code, String customMarker, List<String> nodeNames, String expectedResult) { this.code = code; this.customMarker = customMarker; this.expectedResult = expectedResult; this.nodeNames = nodeNames; } @Parameters(name = "{index}: {0}") public static Collection<Object[]> scenarios() { Collection<Object[]> scenarios = new LinkedList<>(); // @formatter:off scenarios.add(scenario("No selection", multiLine("class Example { }"), NO_SNIPPET)); scenarios.add(scenario("Local variable variable declaration", multiLine("package org.example;", "class Example {", " void m() {", " $String s;$", " }", "}"), multiLine("String ${s:newName(java.lang.String)};", "${cursor}"))); scenarios.add(scenario("Local variable declaration and initialization", multiLine("package org.example;", "class Example {", " void m() {", " $int i = 1;$", " }", "}"), multiLine("int ${i:newName(int)} = 1;", "${cursor}"))); scenarios.add(scenario("Local variable declaration and assignment with line break", multiLine("package org.example;", "class Example {", " void m() {", " $int i = 0;", " i = 1;$", " }", "}"), multiLine("int ${i:newName(int)} = 0;", "${i} = 1;", "${cursor}"))); scenarios.add(scenario("Array declaration and initialization", multiLine("package org.example;", "class Example {", " void m() {", " $String[][] s = new String[][] { { \"hello\" } };$", " }", "}"), multiLine("String[][] ${s:newName('java.lang.String[][]')} = new String[][] { { \"hello\" } };", "${cursor}"))); scenarios.add(scenario("Field access of array field", multiLine("package org.example;", "class Example {", " String f[];", " void m() {", " $f = null;$", " }", "}"), multiLine("${f:field('java.lang.String[]')} = null;", "${cursor}"))); scenarios.add(scenario("Field access of array field: declaration after access", multiLine("package org.example;", "class Example {", " void m() {", " $f = null;$", " }", " String f[];", "}"), multiLine("${f:field('java.lang.String[]')} = null;", "${cursor}"))); scenarios.add(scenario("Static field reference", multiLine("package org.example;", "class Example {", " static String F;", " void m() {", " $F = null;$", " }", "}"), multiLine("F = null;", "${:importStatic(org.example.Example.F)}${cursor}"))); scenarios.add(scenario("Method call on local variable of imported type", multiLine("package org.example;", "import java.util.ArrayList;", "import java.util.List;", "class Example {", " void m() {", " $List l = new ArrayList();", " l.hashCode();$", " }", "}"), multiLine("List ${l:newName(java.util.List)} = new ArrayList();", "${l}.hashCode();", "${:import(java.util.ArrayList, java.util.List)}${cursor}"))); scenarios.add(scenario("Variable declaration of imported nested type", multiLine("package org.example;", "import java.util.Map;", "class Example {", " void m() {", " $Map.Entry e = null;$", " }", "}"), multiLine("Map.Entry ${e:newName(java.util.Map.Entry)} = null;", "${:import(java.util.Map)}${cursor}"))); scenarios.add(scenario("Multiple same type local variables in different blocks", multiLine("package org.example;", "class Example {", " $void m1() { String e; }", " void m2() { String e2; }", " void m3() { String e; }$", "}"), multiLine("void m1() { String ${e:newName(java.lang.String)}; }", "void m2() { String ${e2:newName(java.lang.String)}; }", "void m3() { String ${e1:newName(java.lang.String)}; }", "${cursor}"))); scenarios.add(scenario("Method declaration with argument", multiLine("package org.example;", "class Example {", " $void m(String s) { s = \"hello\"; }$", "}"), multiLine("void m(String ${s:newName(java.lang.String)}) { ${s} = \"hello\"; }", "${cursor}"))); scenarios.add(scenario("Interface declaration", multiLine("package org.example;", "$interface Interface { }$"), multiLine("interface Interface { }", "${cursor}"))); scenarios.add(scenario("Local Constructor invocation", multiLine("package org.example;", "class Example {", " static void main(String[] args) {", " $Example example = new Example();$", " }", "}"), multiLine("Example ${example:newName(org.example.Example)} = new Example();", "${:import(org.example.Example)}${cursor}"))); scenarios.add(scenario("Field declaration: unresolvable type", multiLine("package org.example;", "class Example {", " $Unresolvable f;$", "}"), multiLine("Unresolvable f;", "${cursor}"))); scenarios.add(scenario("Generic Field declaration", multiLine("package org.example;", "class Example<T> {", " $Example<String> f;$", "}"), multiLine("Example<String> ${f:newName(org.example.Example)};", "${:import(org.example.Example)}${cursor}"))); scenarios.add(scenario("Generic Field declaration: unresolvable generic type", multiLine("package org.example;", "class Example<T> {", " $Example<Unresolvable> f;$", "}"), multiLine("Example<Unresolvable> ${f:newName(org.example.Example)};", "${:import(org.example.Example)}${cursor}"))); scenarios.add(customMarkerScenario("Dollar string", multiLine("class Example {", " String f = #\"Cost: $20\";#", "}"), HASH_MARKER, multiLine("\"Cost: $$20\";", "${cursor}"))); scenarios.add(customMarkerScenario("Dollar string: two dollars", multiLine("class Example {", " String f = #\"Cost: $$20\";#", "}"), HASH_MARKER, multiLine("\"Cost: $$$$20\";", "${cursor}"))); scenarios.add(customMarkerScenario("Dollar string: variable declaration with dollar in name", multiLine("class Example {", " void m() {", " #String f$ield;#", " }", "}"), HASH_MARKER, multiLine("String ${f_ield:newName(java.lang.String)};", "${cursor}"))); scenarios.add(customMarkerScenario("Dollar string: two names, one with dollar", multiLine("class Example {", " void m() {", " String var_name;", " String var$name;", " #var_name = var$name;#", " }", "}"), HASH_MARKER, multiLine("${var_name:var(java.lang.String)} = ${var_name1:var(java.lang.String)};", "${cursor}"))); scenarios.add(scenario("Badly formatted code", multiLine("class Example {", " void m() {", " $ int var;", " var = 1;", " var = 2;", " var = 3; $", " }", "}"), multiLine(" int ${var:newName(int)};", "${var} = 1;", " ${var} = 2;", " ${var} = 3; ", "${cursor}"))); scenarios.add(scenario("Line break within node", multiLine("class Example {", " void m() {", " $new Example()", " .m();$", " }", "}"), multiLine("new Example()", " .m();", "${cursor}"))); scenarios.add(scenario("Line break within node: non-whitespace before selection", multiLine("class Example {", " int m() {", " int result = $new Example()", " .m();$", " return result;", " }", "}"), multiLine("new Example()", " .m();", "${cursor}"))); scenarios.add(scenario("Single line file selected", "$class Example { }$", multiLine("class Example { }", "${cursor}"))); scenarios.add(scenario("Single line file with leading whitespace selected", "$ class Example { }$", multiLine("class Example { }", "${cursor}"))); scenarios.add(scenario("Local variable referenced multiple times (Bug 455598)", multiLine("class Example {", " void m(String s) {", " $s = s.toString();", " s = s.toString();$", " }", "}"), multiLine("${s:var(java.lang.String)} = ${s}.toString();", "${s} = ${s}.toString();", "${cursor}"))); scenarios.add(scenario("Field referenced multiple times (Bug 455598)", multiLine("class Example {", " String f;", " void m() {", " $f = f.toString();", " f = f.toString();$", " }", "}"), multiLine("${f:field(java.lang.String)} = ${f}.toString();", "${f} = ${f}.toString();", "${cursor}"))); scenarios.add(scenario("Entire selected region replaced", multiLine("package org.example;", "class Example {", " void m() {", " $$String s;$$", " }", "}"), asList("replaced"), multiLine("${replaced}", "${cursor}"))); scenarios.add(scenario("Part of selected region replaced", multiLine("package org.example;", "class Example {", " void m() {", " $int i = $0$;$", " }", "}"), asList("replaced"), multiLine("int ${i:newName(int)} = ${replaced:var(int)};", "${cursor}"))); scenarios.add(scenario("Multiple selected regions replaced", multiLine("package org.example;", "class Example {", " void m() {", " $int i = $0$ + $1$;$", " }", "}"), asList("zero", "one"), multiLine("int ${i:newName(int)} = ${zero:var(int)} + ${one:var(int)};", "${cursor}"))); scenarios.add(scenario("Name clash of preferred template variable names", multiLine("package org.example;", "class Example {", " void m() {", " $int i = $0$;$", " }", "}"), asList("i"), multiLine("int ${i:newName(int)} = ${i1:var(int)};", "${cursor}"))); scenarios.add(scenario("Name clash of preferred template variable names", multiLine("package org.example;", "class Example {", " void m() {", " $int x = $0$;", " int y = 1;$", " }", "}"), asList("y"), multiLine("int ${x:newName(int)} = ${y:var(int)};", "int ${y1:newName(int)} = 1;", "${cursor}"))); scenarios.add(scenario("Line break after replaced node", multiLine("class Example {", " void m() {", " $$new Example()$", " .m();$", " }", "}"), asList("example"), multiLine("${example:var(Example)}", " .m();", "${cursor}"))); // @formatter:on return scenarios; } private static Object[] scenario(String description, CharSequence code, String expectedResult) { return new Object[] { description, code, null, Collections.emptyList(), expectedResult }; } private static Object[] scenario(String description, CharSequence code, List<String> nodeNames, String expectedResult) { return new Object[] { description, code, null, nodeNames, expectedResult }; } private static Object[] customMarkerScenario(String description, CharSequence code, String marker, String expectedResult) { return new Object[] { description, code, marker, Collections.emptyList(), expectedResult }; } private static String multiLine(String... lines) { String result = JOINER.join(lines); result += '\n'; return result; } @Before public void setUp() throws Exception { fixture = new JavaProjectFixture(ResourcesPlugin.getWorkspace(), SnippetCodeBuilderTest.class.getName() + testCount++); Pair<ICompilationUnit, List<Integer>> struct; if (customMarker != null) { struct = fixture.createFileAndParseWithMarkers(code, customMarker); } else { struct = fixture.createFileAndPackageAndParseWithMarkers(code); } ICompilationUnit cu = struct.getFirst(); CompilationUnitEditor editor = (CompilationUnitEditor) EditorUtility.openInEditor(cu); ITypeRoot root = (ITypeRoot) editor.getViewPartInput(); ast = SharedASTProvider.getAST(root, SharedASTProvider.WAIT_YES, null); document = editor.getViewer().getDocument(); int start, end; List<Integer> markers = struct.getSecond(); if (!markers.isEmpty()) { start = markers.get(0); end = markers.get(markers.size() - 1); } else { start = -1; end = -1; } selection = new Region(start, end - start); nodesToReplace = new HashMap<>(); for (int i = 0; i < nodeNames.size(); i++) { int nodeStart = markers.get(1 + 2 * i); int nodeEnd = markers.get(2 + 2 * i); ASTNode nodeToReplace = NodeFinder.perform(ast, nodeStart, nodeEnd - nodeStart); String nodeName = nodeNames.get(i); nodesToReplace.put(nodeToReplace, nodeName); } } @Test public void test() throws Exception { SnippetCodeBuilder sut = new SnippetCodeBuilder(ast, document, selection, nodesToReplace); String result = sut.build(); assertThat(result, is(equalTo(expectedResult))); } }