Merge pull request #6637 from lahodaj/string-templates-write-model
Improving String templates support in the code gen.
diff --git a/java/java.source.base/apichanges.xml b/java/java.source.base/apichanges.xml
index 27f23bf..8b55514 100644
--- a/java/java.source.base/apichanges.xml
+++ b/java/java.source.base/apichanges.xml
@@ -25,6 +25,18 @@
<apidef name="javasource_base">Java Source API</apidef>
</apidefs>
<changes>
+ <change id="TreeMaker.StringTemplate">
+ <api name="javasource_base" />
+ <summary>Adding TreeMaker.StringTemplate</summary>
+ <version major="1" minor="2.65.0"/>
+ <date day="3" month="11" year="2023"/>
+ <author login="jlahoda"/>
+ <compatibility addition="yes" binary="compatible" source="compatible"/>
+ <description>
+ Adding TreeMaker.StringTemplate
+ </description>
+ <class name="TreeMaker" package="org.netbeans.api.java.source"/>
+ </change>
<change id="SourceUtils.getFile">
<api name="javasource_base" />
<summary>Source file name for Element</summary>
diff --git a/java/java.source.base/nbproject/project.properties b/java/java.source.base/nbproject/project.properties
index 5a122a5..59475c5 100644
--- a/java/java.source.base/nbproject/project.properties
+++ b/java/java.source.base/nbproject/project.properties
@@ -23,7 +23,7 @@
javadoc.title=Java Source Base
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml
-spec.version.base=2.64.0
+spec.version.base=2.65.0
test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/nb-javac-api.jar
test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\
${o.n.core.dir}/lib/boot.jar:\
diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
index 74a4e8b..8eeca54 100644
--- a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
+++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java
@@ -3916,4 +3916,17 @@
public LambdaExpressionTree setLambdaBody(LambdaExpressionTree method, Tree newBody) {
return delegate.setLambdaBody(method, newBody);
}
+
+ /**Creates a new string template expression from the given parameters.
+ *
+ * @param processor the processor of the string template
+ * @param fragments the template fragments
+ * @param expressions the template expressions
+ * @return the string template instance
+ * @since 2.65
+ */
+ public StringTemplateTree StringTemplate(ExpressionTree processor, List<String> fragments, List<? extends ExpressionTree> expressions) {
+ return delegate.StringTemplate(processor, fragments, expressions);
+ }
+
}
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java
index 9c94b6f..23851bb 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java
@@ -116,6 +116,7 @@
import org.netbeans.modules.java.source.parsing.FileObjects;
import org.netbeans.modules.java.source.parsing.JavacParser;
import org.netbeans.modules.java.source.save.CasualDiff;
+import org.netbeans.modules.java.source.save.CasualDiff.StringTemplateFragmentTree;
import org.netbeans.modules.java.source.save.DiffContext;
import org.netbeans.modules.java.source.save.PositionEstimator;
import org.netbeans.modules.java.source.save.Reformatter;
@@ -1865,7 +1866,7 @@
if ( diffContext != null
&& diffContext.origUnit != null
&& (start = diffContext.trees.getSourcePositions().getStartPosition(diffContext.origUnit, tree)) >= 0 //#137564
- && (end = diffContext.trees.getSourcePositions().getEndPosition(diffContext.origUnit, tree)) >= 0
+ && (end = diffContext.getEndPosition(diffContext.origUnit, tree)) >= 0
&& origText != null) {
print(origText.substring((int) start, (int) end));
return ;
@@ -1873,7 +1874,7 @@
if ( diffContext != null
&& diffContext.mainUnit != null
&& (start = diffContext.trees.getSourcePositions().getStartPosition(diffContext.mainUnit, tree)) >= 0 //#137564
- && (end = diffContext.trees.getSourcePositions().getEndPosition(diffContext.mainUnit, tree)) >= 0
+ && (end = diffContext.getEndPosition(diffContext.mainUnit, tree)) >= 0
&& diffContext.mainCode != null) {
print(diffContext.mainCode.substring((int) start, (int) end));
return ;
@@ -1899,7 +1900,22 @@
break;
case CLASS:
if (tree.value instanceof String) {
- print("\"" + quote((String) tree.value, '\'') + "\"");
+ String leading;
+ String trailing;
+ if (tree instanceof StringTemplateFragmentTree) {
+ StringTemplateFragmentTree stf = (StringTemplateFragmentTree) tree;
+ switch (stf.fragmentKind) {
+ case START: leading = "\""; trailing = "\\{"; break;
+ case MIDDLE: leading = "}"; trailing = "\\{"; break;
+ case END: leading = "}"; trailing = "\""; break;
+ default: throw new IllegalStateException(stf.fragmentKind.name());
+ }
+ } else {
+ leading = trailing = "\"";
+ }
+ print(leading);
+ print(quote((String) tree.value, '\''));
+ print(trailing);
} else if (tree.value instanceof String[]) {
int indent = out.col;
print("\"\"\"");
@@ -2077,6 +2093,31 @@
}
@Override
+ public void visitStringTemplate(JCStringTemplate tree) {
+ printExpr(tree.processor, TreeInfo.postfixPrec);
+ print('.');
+
+ Iterator<? extends String> fragmentIt = tree.fragments.iterator();
+ Iterator<? extends JCExpression> expressionIt = tree.expressions.iterator();
+ boolean start = true;
+
+ while (expressionIt.hasNext()) {
+ if (start) {
+ print("\"");
+ } else {
+ print("}");
+ }
+ print(quote(fragmentIt.next(), '\''));
+ print("\\{");
+ print(expressionIt.next());
+ start = false;
+ }
+ print("}");
+ print(quote(fragmentIt.next(), '\''));
+ print("\"");
+ }
+
+ @Override
public void visitLetExpr(LetExpr tree) {
print("(let " + tree.defs + " in " + tree.expr + ")");
}
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java
index a4df3d1..78846b4 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java
@@ -109,6 +109,7 @@
import com.sun.tools.javac.tree.JCTree.JCRequires;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCStringTemplate;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
@@ -128,6 +129,7 @@
import com.sun.tools.javac.tree.JCTree.TypeBoundKind;
import com.sun.tools.javac.tree.Pretty;
import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
@@ -173,6 +175,7 @@
import org.openide.util.NbBundle;
import org.openide.util.NbCollections;
import javax.lang.model.type.TypeKind;
+import org.netbeans.modules.java.source.save.CasualDiff.StringTemplateFragmentTree.FragmentKind;
import org.netbeans.modules.java.source.transform.TreeHelpers;
public class CasualDiff {
@@ -190,6 +193,7 @@
private VeryPretty printer;
private final Context context;
private final Names names;
+ private final TreeMaker make;
private static final Logger LOG = Logger.getLogger(CasualDiff.class.getName());
public static final int GENERATED_MEMBER = 1<<24;
@@ -223,6 +227,7 @@
this.origText = diffContext.origText;
this.context = context;
this.names = Names.instance(context);
+ this.make = TreeMaker.instance(context);
this.tree2Tag = tree2Tag;
this.tree2Doc = tree2Doc;
this.tag2Span = (Map<Object, int[]>) tag2Span;//XXX
@@ -539,7 +544,7 @@
VariableTree vt = fgt.getVariables().get(fgt.getVariables().size() - 1);
return TreeInfo.getEndPos((JCTree)vt, oldTopLevel.endPositions);
}
- int endPos = TreeInfo.getEndPos(t, oldTopLevel.endPositions);
+ int endPos = diffContext.getEndPosition(oldTopLevel, t);
if (endPos == Position.NOPOS) {
if (t instanceof JCAssign) {
@@ -1987,6 +1992,61 @@
return bounds[1];
}
+ protected int diffStringTemplate(JCStringTemplate oldT, JCStringTemplate newT, int[] bounds) {
+ int localPointer = bounds[0];
+
+ // processor
+ int[] processorBounds = getBounds(oldT.processor);
+ copyTo(localPointer, processorBounds[0]);
+ localPointer = diffTree(oldT.processor, newT.processor, processorBounds);
+
+ tokenSequence.move(processorBounds[1]);
+ do { } while (tokenSequence.moveNext() && JavaTokenId.DOT != tokenSequence.token().id());
+ tokenSequence.moveNext();
+ copyTo(localPointer, localPointer = tokenSequence.offset());
+
+ // expressions
+ List<? extends JCExpression> oldFragmentsAndExpressions = zipFragmentsAndExpressions(oldT, localPointer);
+ List<? extends JCExpression> newFragmentsAndExpressions = zipFragmentsAndExpressions(newT, NOPOS);
+ PositionEstimator est = EstimatorFactory.stringTemplate(oldFragmentsAndExpressions, newFragmentsAndExpressions, diffContext);
+ localPointer = diffList(oldFragmentsAndExpressions, newFragmentsAndExpressions, localPointer, est, Measure.REAL_MEMBER, printer);
+ copyTo(localPointer, bounds[1]);
+ return bounds[1];
+ }
+
+ private List<JCExpression> zipFragmentsAndExpressions(JCStringTemplate template, int pos) {
+ ListBuffer<JCExpression> result = new ListBuffer<>();
+ Iterator<? extends String> fragmentIt = template.fragments.iterator();
+ Iterator<? extends JCExpression> expressionIt = template.expressions.iterator();
+
+ while (fragmentIt.hasNext()) {
+ JCExpression expression = expressionIt.hasNext() ? expressionIt.next() : null;
+ FragmentKind fragmentKind = result.isEmpty() ? FragmentKind.START
+ : expression != null ? FragmentKind.MIDDLE
+ : FragmentKind.END;
+ JCLiteral literal = new StringTemplateFragmentTree(TypeTag.CLASS, fragmentIt.next(), fragmentKind);
+
+ if (pos != NOPOS) {
+ tokenSequence.move(pos);
+
+ if (tokenSequence.moveNext() && tokenSequence.token().id() == JavaTokenId.STRING_LITERAL) {
+ literal.pos = tokenSequence.offset();
+ diffContext.syntheticEndPositions.put(literal, literal.pos + tokenSequence.token().length());
+ }
+
+ pos = expression != null ? endPos(expression) : NOPOS;
+ }
+
+ result.append(literal);
+
+ if (expression != null) {
+ result.append(expression);
+ }
+ }
+
+ return result.toList();
+ }
+
protected int diffConstantCaseLabel(JCConstantCaseLabel oldT, JCConstantCaseLabel newT, int[] bounds) {
return diffTree((JCTree) oldT.expr, (JCTree) newT.expr, bounds);
}
@@ -3372,7 +3432,7 @@
assert oldList != null && newList != null;
int lastOldPos = initialPos;
- ListMatcher<JCTree> matcher = ListMatcher.<JCTree>instance(oldList, newList);
+ ListMatcher<JCTree> matcher = ListMatcher.<JCTree>instance(oldList, newList, null);
if (!matcher.match()) {
return initialPos;
}
@@ -5753,6 +5813,9 @@
case RECORDPATTERN:
retVal = diffRecordPattern((JCRecordPattern) oldT, (JCRecordPattern) newT, elementBounds);
break;
+ case STRING_TEMPLATE:
+ retVal = diffStringTemplate((JCStringTemplate) oldT, (JCStringTemplate) newT, elementBounds);
+ break;
default:
// handle special cases like field groups and enum constants
if (oldT.getKind() == Kind.OTHER) {
@@ -6380,4 +6443,20 @@
}
return -1;
}
+
+ public static final class StringTemplateFragmentTree extends JCLiteral {
+
+ public final FragmentKind fragmentKind;
+
+ public StringTemplateFragmentTree(TypeTag typetag, Object value, FragmentKind kind) {
+ super(typetag, value);
+ this.fragmentKind = kind;
+ }
+
+ public enum FragmentKind {
+ START,
+ MIDDLE,
+ END;
+ }
+ }
}
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/DiffContext.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/DiffContext.java
index b6cc3bd..36b2ad6 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/save/DiffContext.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/DiffContext.java
@@ -22,8 +22,11 @@
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.Trees;
+import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Position;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
@@ -63,6 +66,7 @@
public final int textLength;
public final BlockSequences blockSequences;
+ public final Map<JCTree, Integer> syntheticEndPositions = new HashMap<>();
/**
* Special flag; when creating new CUs from template, always include their initial comments
@@ -136,4 +140,13 @@
return CodeStyle.getDefault((Document)null);
}
+ public int getEndPosition(JCCompilationUnit unit, JCTree t) {
+ int endPos = TreeInfo.getEndPos(t, unit.endPositions);
+
+ if (endPos == Position.NOPOS && unit == origUnit) {
+ endPos = syntheticEndPositions.getOrDefault(t, Position.NOPOS);
+ }
+
+ return endPos;
+ }
}
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/EstimatorFactory.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/EstimatorFactory.java
index c7733a8..5a2de80 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/save/EstimatorFactory.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/EstimatorFactory.java
@@ -47,6 +47,13 @@
return new PositionEstimator.CasePatternEstimator(oldL, newL, diffContext);
}
+ static PositionEstimator stringTemplate(List<? extends Tree> oldL,
+ List<? extends Tree> newL,
+ DiffContext diffContext)
+ {
+ return new PositionEstimator.StringTemaplateEstimator(oldL, newL, diffContext);
+ }
+
static PositionEstimator exportsOpensTo(List<? extends ExpressionTree> oldL,
List<? extends ExpressionTree> newL,
DiffContext diffContext)
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/PositionEstimator.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/PositionEstimator.java
index 40a91a3..c6d73c3 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/save/PositionEstimator.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/PositionEstimator.java
@@ -27,7 +27,9 @@
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.TreeInfo;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
@@ -190,6 +192,37 @@
}
+ static class StringTemaplateEstimator extends BaseEstimator {
+ StringTemaplateEstimator(List<? extends Tree> oldL,
+ List<? extends Tree> newL,
+ DiffContext diffContext)
+ {
+ super(DOT, oldL, newL, diffContext);
+ }
+
+ @Override
+ public String head() {
+ return precToken.fixedText();
+ }
+
+ @Override
+ public int getInsertPos(int index) {
+ if (index == oldL.size()) {
+ return diffContext.getEndPosition(diffContext.origUnit, (JCTree) oldL.get(index - 1));
+ }
+ return (int) diffContext.trees.getSourcePositions().getStartPosition(diffContext.origUnit, oldL.get(index));
+ }
+
+ @Override
+ public int[] getPositions(int index) {
+ int start = (int) diffContext.trees.getSourcePositions().getStartPosition(diffContext.origUnit, oldL.get(index));
+ int end = diffContext.getEndPosition(diffContext.origUnit, (JCTree) oldL.get(index));
+
+ return new int[] {start, end};
+ }
+
+ }
+
static class ExportsOpensToEstimator extends BaseEstimator {
ExportsOpensToEstimator(List<? extends ExpressionTree> oldL,
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java
index f123913..86cb917 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java
@@ -3557,6 +3557,18 @@
}
@Override
+ public Boolean visitStringTemplate(StringTemplateTree node, Void p) {
+ scan(node.getProcessor(), p);
+ accept(DOT);
+ for (ExpressionTree expression : node.getExpressions()) {
+ accept(STRING_LITERAL);
+ scan(expression, p);
+ }
+ accept(STRING_LITERAL);
+ return true;
+ }
+
+ @Override
public Boolean visitOther(Tree node, Void p) {
do {
col += tokens.token().length();
diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/StringTemplateTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/StringTemplateTest.java
new file mode 100644
index 0000000..df7b4cf
--- /dev/null
+++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/StringTemplateTest.java
@@ -0,0 +1,349 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.api.java.source.gen;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.StringTemplateTree;
+import com.sun.source.tree.SwitchExpressionTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.tree.JCTree;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.swing.event.ChangeListener;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.Task;
+import org.netbeans.api.java.source.TestUtilities;
+import org.netbeans.api.java.source.TreeMaker;
+import org.netbeans.api.java.source.WorkingCopy;
+import org.netbeans.junit.NbTestSuite;
+import org.netbeans.modules.java.source.parsing.JavacParser;
+import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation;
+import org.openide.filesystems.FileObject;
+import org.openide.util.lookup.ServiceProvider;
+
+public class StringTemplateTest extends TreeRewriteTestBase {
+
+ private static final List<String> EXTRA_OPTIONS = new ArrayList<>();
+
+ public StringTemplateTest(String testName) {
+ super(testName);
+ }
+
+ public static NbTestSuite suite() {
+ NbTestSuite suite = new NbTestSuite();
+ suite.addTestSuite(StringTemplateTest.class);
+ return suite;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ sourceLevel = "1.21";
+ JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true;
+ EXTRA_OPTIONS.add("--enable-preview");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = false;
+
+ }
+
+ @ServiceProvider(service = CompilerOptionsQueryImplementation.class, position = 100)
+ public static class TestCompilerOptionsQueryImplementation implements CompilerOptionsQueryImplementation {
+
+ @Override
+ public CompilerOptionsQueryImplementation.Result getOptions(FileObject file) {
+ return new CompilerOptionsQueryImplementation.Result() {
+ @Override
+ public List<? extends String> getArguments() {
+ return EXTRA_OPTIONS;
+ }
+
+ @Override
+ public void addChangeListener(ChangeListener listener) {
+ }
+
+ @Override
+ public void removeChangeListener(ChangeListener listener) {
+ }
+ };
+ }
+
+ }
+
+ public void testRenameInStringTemplate() throws Exception {
+ String code = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b) {\n"
+ + " return STR.\"\\{a}\";\n"
+ + " }\n"
+ + "}\n";
+ String golden = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b) {\n"
+ + " return FMT.\"\\{b}\";\n"
+ + " }\n"
+ + "}\n";
+
+ prepareTest("Test", code);
+
+ JavaSource js = getJavaSource();
+ assertNotNull(js);
+
+ Task<WorkingCopy> task = new Task<WorkingCopy>() {
+ public void run(WorkingCopy workingCopy) throws IOException {
+ workingCopy.toPhase(JavaSource.Phase.RESOLVED);
+ TreeMaker make = workingCopy.getTreeMaker();
+ CompilationUnitTree cut = workingCopy.getCompilationUnit();
+ new TreeScanner<Void, Void>() {
+ @Override
+ public Void visitIdentifier(IdentifierTree node, Void p) {
+ if (node.getName().contentEquals("a")) {
+ workingCopy.rewrite(node, make.Identifier("b"));
+ return null;
+ }
+ if (node.getName().contentEquals("STR")) {
+ workingCopy.rewrite(node, make.Identifier("FMT"));
+ return null;
+ }
+ return super.visitIdentifier(node, p);
+ }
+ }.scan(cut, null);
+ }
+
+ };
+
+ js.runModificationTask(task).commit();
+
+ String res = TestUtilities.copyFileToString(getTestFile());
+ //System.err.println(res);
+ assertEquals(golden, res);
+
+ }
+
+ public void testRemoveStringTemplateFragment() throws Exception {
+ String code = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b, int c) {\n"
+ + " String s1 = STR.\"a\\{a}b\\{b}c\\{c}e\";\n"
+ + " String s2 = STR.\"a\\{a}b\\{b}c\\{c}e\";\n"
+ + " String s3 = STR.\"a\\{a}b\\{b}c\\{c}e\";\n"
+ + " }\n"
+ + "}\n";
+ String golden = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b, int c) {\n"
+ + " String s1 = STR.\"b\\{b}c\\{c}e\";\n"
+ + " String s2 = STR.\"a\\{a}c\\{c}e\";\n"
+ + " String s3 = STR.\"a\\{a}b\\{b}e\";\n"
+ + " }\n"
+ + "}\n";
+
+ prepareTest("Test", code);
+
+ JavaSource js = getJavaSource();
+ assertNotNull(js);
+
+ Task<WorkingCopy> task = new Task<WorkingCopy>() {
+ public void run(WorkingCopy workingCopy) throws IOException {
+ workingCopy.toPhase(JavaSource.Phase.RESOLVED);
+ TreeMaker make = workingCopy.getTreeMaker();
+ CompilationUnitTree cut = workingCopy.getCompilationUnit();
+ new TreeScanner<Void, Void>() {
+ @Override
+ public Void visitVariable(VariableTree node, Void p) {
+ CONT: if (node.getInitializer() != null) {
+ int toDelete;
+ switch (node.getName().toString()) {
+ case "s1": toDelete = 0; break;
+ case "s2": toDelete = 1; break;
+ case "s3": toDelete = 2; break;
+ default: break CONT;
+ }
+
+ StringTemplateTree template = (StringTemplateTree) node.getInitializer();
+ List<String> fragments = new ArrayList<>(template.getFragments());
+ List<? extends ExpressionTree> expressions = new ArrayList<>(template.getExpressions());
+
+ fragments.remove(toDelete);
+ expressions.remove(toDelete);
+
+ workingCopy.rewrite(template, make.StringTemplate(template.getProcessor(), fragments, expressions));
+ }
+ return super.visitVariable(node, p);
+ }
+ }.scan(cut, null);
+ }
+
+ };
+
+ js.runModificationTask(task).commit();
+
+ String res = TestUtilities.copyFileToString(getTestFile());
+ //System.err.println(res);
+ assertEquals(golden, res);
+
+ }
+
+ public void testAddStringTemplateFragment() throws Exception {
+ String code = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b, int c) {\n"
+ + " String s1 = STR.\"0\\{0}1\\{1}e\";\n"
+ + " String s2 = STR.\"0\\{0}1\\{1}e\";\n"
+ + " String s3 = STR.\"0\\{0}1\\{1}e\";\n"
+ + " }\n"
+ + "}\n";
+ String golden = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b, int c) {\n"
+ + " String s1 = STR.\"a\\{a}0\\{0}1\\{1}e\";\n"
+ + " String s2 = STR.\"0\\{0}a\\{a}1\\{1}e\";\n"
+ + " String s3 = STR.\"0\\{0}1\\{1}a\\{a}e\";\n"
+ + " }\n"
+ + "}\n";
+
+ prepareTest("Test", code);
+
+ JavaSource js = getJavaSource();
+ assertNotNull(js);
+
+ Task<WorkingCopy> task = new Task<WorkingCopy>() {
+ public void run(WorkingCopy workingCopy) throws IOException {
+ workingCopy.toPhase(JavaSource.Phase.RESOLVED);
+ TreeMaker make = workingCopy.getTreeMaker();
+ CompilationUnitTree cut = workingCopy.getCompilationUnit();
+ new TreeScanner<Void, Void>() {
+ @Override
+ public Void visitVariable(VariableTree node, Void p) {
+ CONT: if (node.getInitializer() != null) {
+ int insertPos;
+ switch (node.getName().toString()) {
+ case "s1": insertPos = 0; break;
+ case "s2": insertPos = 1; break;
+ case "s3": insertPos = 2; break;
+ default: break CONT;
+ }
+
+ StringTemplateTree template = (StringTemplateTree) node.getInitializer();
+ List<String> fragments = new ArrayList<>(template.getFragments());
+ List<ExpressionTree> expressions = new ArrayList<>(template.getExpressions());
+
+ fragments.add(insertPos, "a");
+ expressions.add(insertPos, make.Identifier("a"));
+
+ workingCopy.rewrite(template, make.StringTemplate(template.getProcessor(), fragments, expressions));
+ }
+ return super.visitVariable(node, p);
+ }
+ }.scan(cut, null);
+ }
+
+ };
+
+ js.runModificationTask(task).commit();
+
+ String res = TestUtilities.copyFileToString(getTestFile());
+ //System.err.println(res);
+ assertEquals(golden, res);
+
+ }
+
+ public void testNewStringTemplate() throws Exception {
+ String code = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b, int c) {\n"
+ + " String s;\n"
+ + " }\n"
+ + "}\n";
+ String golden = "package test; \n"
+ + "public class Test {\n"
+ + " private String test(int a, int b, int c) {\n"
+ + " String s = STR.\"a\\{a}b\\{b}c\\{c}e\";\n"
+ + " }\n"
+ + "}\n";
+
+ prepareTest("Test", code);
+
+ JavaSource js = getJavaSource();
+ assertNotNull(js);
+
+ Task<WorkingCopy> task = new Task<WorkingCopy>() {
+ public void run(WorkingCopy workingCopy) throws IOException {
+ workingCopy.toPhase(JavaSource.Phase.RESOLVED);
+ TreeMaker make = workingCopy.getTreeMaker();
+ CompilationUnitTree cut = workingCopy.getCompilationUnit();
+ new TreeScanner<Void, Void>() {
+ @Override
+ public Void visitVariable(VariableTree node, Void p) {
+ if (node.getName().contentEquals("s")) {
+ List<String> fragments = new ArrayList<>();
+ List<ExpressionTree> expressions = new ArrayList<>();
+
+ fragments.add("a");
+ expressions.add(make.Identifier("a"));
+ fragments.add("b");
+ expressions.add(make.Identifier("b"));
+ fragments.add("c");
+ expressions.add(make.Identifier("c"));
+ fragments.add("e");
+
+ workingCopy.rewrite(node, make.setInitialValue(node, make.StringTemplate(make.Identifier("STR"), fragments, expressions)));
+ }
+ return super.visitVariable(node, p);
+ }
+ }.scan(cut, null);
+ }
+
+ };
+
+ js.runModificationTask(task).commit();
+
+ String res = TestUtilities.copyFileToString(getTestFile());
+ //System.err.println(res);
+ assertEquals(golden, res);
+
+ }
+
+}
diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java
index c11be84..fc0911d 100644
--- a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java
+++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java
@@ -6753,6 +6753,32 @@
reformat(doc, content, golden);
}
+ public void testStringTemplate() throws Exception {
+ testFile = new File(getWorkDir(), "Test.java");
+ TestUtilities.copyStringToFile(testFile, "");
+ FileObject testSourceFO = FileUtil.toFileObject(testFile);
+ DataObject testSourceDO = DataObject.find(testSourceFO);
+ EditorCookie ec = (EditorCookie) testSourceDO.getCookie(EditorCookie.class);
+ final Document doc = ec.openDocument();
+ doc.putProperty(Language.class, JavaTokenId.language());
+ String content
+ = "\n"
+ + "class Test {\n\n"
+ + " private String t() {\n"
+ + " return STR.\"a\\{1 + 2}b\";\n"
+ + " }\n"
+ + "}\n";
+
+ String golden
+ = "\n"
+ + "class Test {\n\n"
+ + " private String t() {\n"
+ + " return STR.\"a\\{1 + 2}b\";\n"
+ + " }\n"
+ + "}\n";
+ reformat(doc, content, golden);
+ }
+
private void reformat(Document doc, String content, String golden) throws Exception {
reformat(doc, content, golden, 0, content.length());
}