diff --git a/java/java.source.base/apichanges.xml b/java/java.source.base/apichanges.xml index 8d6347cead60..acbb67aa27c2 100644 --- a/java/java.source.base/apichanges.xml +++ b/java/java.source.base/apichanges.xml @@ -25,6 +25,18 @@ Java Source API + + + Adding TreeMaker.Class overload that takes permitted subclasses + + + + + + Adding TreeMaker.Class overload that allows to specify the permitted subclasses. + + + Adding TreeMaker.RawText diff --git a/java/java.source.base/nbproject/project.properties b/java/java.source.base/nbproject/project.properties index 183dfae24847..31ef685fd842 100644 --- a/java/java.source.base/nbproject/project.properties +++ b/java/java.source.base/nbproject/project.properties @@ -23,7 +23,7 @@ javadoc.name=Java Source Base javadoc.title=Java Source Base javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=2.72.0 +spec.version.base=2.74.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 2b443de9ba75..184aae7c66ed 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 @@ -364,8 +364,36 @@ public ClassTree Class(ModifiersTree modifiers, Tree extendsClause, List implementsClauses, List memberDecls) { - return delegate.Class(modifiers, simpleName, typeParameters, extendsClause, implementsClauses, memberDecls); + return Class(modifiers, simpleName, typeParameters, extendsClause, implementsClauses, List.of(), memberDecls); } + + /** + * Creates a new ClassTree. + * + * @param modifiers the modifiers declaration + * @param simpleName the name of the class without its package, such + * as "String" for the class "java.lang.String". + * @param typeParameters the list of type parameters, or an empty list. + * @param extendsClause the name of the class this class extends, or null. + * @param implementsClauses the list of the interfaces this class + * implements, or an empty list. + * @param permitsClauses the list of the subtype this class + * permits, or an empty list. + * @param memberDecls the list of fields defined by this class, or an + * empty list. + * @see com.sun.source.tree.ClassTree + * @since 2.74 + */ + public ClassTree Class(ModifiersTree modifiers, + CharSequence simpleName, + List typeParameters, + Tree extendsClause, + List implementsClauses, + List permitsClauses, + List memberDecls) { + return delegate.Class(modifiers, simpleName, typeParameters, extendsClause, implementsClauses, permitsClauses, memberDecls); + } + /** * Creates a new ClassTree representing interface. * diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java index abb39db21f52..82e4d661b075 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java @@ -279,6 +279,7 @@ public ClassTree Class(ModifiersTree modifiers, List typeParameters, Tree extendsClause, List implementsClauses, + List permitsClauses, List memberDecls) { ListBuffer typarams = new ListBuffer(); @@ -287,6 +288,9 @@ public ClassTree Class(ModifiersTree modifiers, ListBuffer impls = new ListBuffer(); for (Tree t : implementsClauses) impls.append((JCExpression)t); + ListBuffer permits = new ListBuffer(); + for (Tree t : permitsClauses) + permits.append((JCExpression)t); ListBuffer defs = new ListBuffer(); for (Tree t : memberDecls) defs.append((JCTree)t); @@ -295,6 +299,7 @@ public ClassTree Class(ModifiersTree modifiers, typarams.toList(), (JCExpression)extendsClause, impls.toList(), + permits.toList(), defs.toList()); } @@ -1105,6 +1110,7 @@ private ClassTree modifyClassMember(ClassTree clazz, int index, Tree member, Ope clazz.getTypeParameters(), clazz.getExtendsClause(), (List) clazz.getImplementsClause(), + clazz.getPermitsClause(), c(clazz.getMembers(), index, member, op) ); return copy; @@ -1133,6 +1139,7 @@ private ClassTree modifyClassTypeParameter(ClassTree clazz, int index, TypeParam c(clazz.getTypeParameters(), index, typeParameter, op), clazz.getExtendsClause(), (List) clazz.getImplementsClause(), + clazz.getPermitsClause(), clazz.getMembers() ); return copy; @@ -1161,6 +1168,7 @@ private ClassTree modifyClassImplementsClause(ClassTree clazz, int index, Tree i clazz.getTypeParameters(), clazz.getExtendsClause(), c((List) clazz.getImplementsClause(), index, implementsClause, op), // todo: cast! + clazz.getPermitsClause(), clazz.getMembers() ); return copy; 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 037eb8251775..d05aad7c33ca 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 @@ -1071,6 +1071,7 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { localPointer = endPos(oldT.extending); break; } + { // TODO (#pf): there is some space for optimization. If the new list // is also empty, we can skip this computation. if (oldT.implementing.isEmpty()) { @@ -1098,6 +1099,39 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { if (!newT.implementing.isEmpty()) copyTo(localPointer, insertHint); localPointer = diffList2(oldT.implementing, newT.implementing, insertHint, estimator); + } + + { + // TODO (#pf): there is some space for optimization. If the new list + // is also empty, we can skip this computation. + if (oldT.permitting.isEmpty()) { + // if there is not any permits part, we need to adjust position + // from different place. Look at the examples in all if branches. + // | represents current adjustment and ! where we want to point to + if (oldT.implementing.nonEmpty()) { + // public class Yerba| implements Runnable! { ... + insertHint = endPos(oldT.implementing); + } else if (oldT.extending != null) + // public class Yerba| extends Object! { ... + insertHint = endPos(oldT.extending); + else { + // currently no need to adjust anything here: + // public class Yerba|! { ... + } + } else { + // we already have any permits, adjust position to first + // public class Yerba| permits !Mate { ... + // Note: in case of all permits classes are removed, + // diffing mechanism will solve the permits keyword. + insertHint = oldT.permitting.iterator().next().getStartPosition(); + } + PositionEstimator estimator = + EstimatorFactory.permits(oldT.getImplementsClause(), newT.getImplementsClause(), diffContext); + if (!newT.permitting.isEmpty()) + copyTo(localPointer, insertHint); + localPointer = diffList2(oldT.permitting, newT.permitting, insertHint, estimator); + } + insertHint = endPos(oldT) - 1; if (filteredOldTDefs.isEmpty()) { 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 5a2de8080233..c5908a7972eb 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 @@ -82,6 +82,13 @@ static PositionEstimator extendz(List oldL, return new PositionEstimator.ExtendsEstimator(oldL, newL, diffContext); } + static PositionEstimator permits(List oldL, + List newL, + DiffContext diffContext) + { + return new PositionEstimator.ExtendsEstimator(oldL, newL, diffContext); + } + static PositionEstimator statements(List oldL, List 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 5ee0dcd97900..9d3d76faf0c2 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 @@ -36,6 +36,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Predicate; import javax.swing.text.StyledDocument; import org.netbeans.api.editor.guards.GuardedSection; import org.netbeans.api.editor.guards.GuardedSectionManager; @@ -168,6 +169,18 @@ static class ExtendsEstimator extends BaseEstimator { } } + static class PermitsEstimator extends BaseEstimator { + private static final java.lang.String CONSTANT = "permits"; + PermitsEstimator(List oldL, + List newL, + DiffContext diffContext) + { + super(token -> token.id() == JavaTokenId.IDENTIFIER && + CONSTANT.contentEquals(token.text()), + CONSTANT, oldL, newL, diffContext); + } + } + static class ThrowsEstimator extends BaseEstimator { ThrowsEstimator(List oldL, List newL, @@ -187,7 +200,7 @@ static class CasePatternEstimator extends BaseEstimator { @Override public String head() { - return precToken.fixedText() + " "; + return prefixTokenText + " "; } } @@ -202,7 +215,7 @@ static class StringTemaplateEstimator extends BaseEstimator { @Override public String head() { - return precToken.fixedText(); + return prefixTokenText; } @Override @@ -814,19 +827,31 @@ public int[] sectionRemovalBounds(StringBuilder replacement) { private abstract static class BaseEstimator extends PositionEstimator { - JavaTokenId precToken; + Predicate> prefixTokenAcceptor; + String prefixTokenText; private ArrayList separatorList; private BaseEstimator(JavaTokenId precToken, List oldL, List newL, DiffContext diffContext) + { + this(token -> token.id() != precToken, precToken.fixedText(), + oldL, newL, diffContext); + } + + private BaseEstimator(Predicate> prefixTokenAcceptor, + String prefixTokenText, + List oldL, + List newL, + DiffContext diffContext) { super(oldL, newL, diffContext); - this.precToken = precToken; + this.prefixTokenAcceptor = prefixTokenAcceptor; + this.prefixTokenText = prefixTokenText; } - public String head() { return " " + precToken.fixedText() + " "; } + public String head() { return " " + prefixTokenText + " "; } public String sep() { return ", "; } @SuppressWarnings("empty-statement") @@ -848,7 +873,7 @@ public void initialize() { int beforer = -1; if (first) { // go back to throws keywrd. - while (seq.movePrevious() && seq.token().id() != precToken) ; + while (seq.movePrevious() && prefixTokenAcceptor.test(seq.token())) ; int throwsIndex = seq.index(); beforer = throwsIndex+1; // go back to closing ) diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java b/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java index 4efeb9b2af6c..f8e1ba0a5a17 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java @@ -686,15 +686,18 @@ protected final ClassTree rewriteChildren(ClassTree tree) { Tree extending = translateClassRef(tree.getExtendsClause()); List implementing = translateClassRef((List)tree.getImplementsClause()); + List permits = + translateClassRef((List)tree.getPermitsClause()); importAnalysis.enterVisibleThroughClasses(tree); List defs = translate(tree.getMembers()); boolean typeChanged = !typarams.equals(tree.getTypeParameters()) || extending != tree.getExtendsClause() || - !implementing.equals(tree.getImplementsClause()); + !implementing.equals(tree.getImplementsClause()) || + !permits.equals(tree.getPermitsClause()); if (typeChanged || mods != tree.getModifiers() || !defs.equals(tree.getMembers())) { ClassTree n = make.Class(mods, tree.getSimpleName(), typarams, - extending, implementing, defs); + extending, implementing, permits, defs); if (!typeChanged) { model.setElement(n, model.getElement(tree)); model.setType(n, model.getType(tree)); diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/transform/TreeDuplicator.java b/java/java.source.base/src/org/netbeans/modules/java/source/transform/TreeDuplicator.java index c09091ec46d1..d516505f6d70 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/transform/TreeDuplicator.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/transform/TreeDuplicator.java @@ -215,7 +215,8 @@ public Tree visitCatch(CatchTree tree, Void p) { @Override public Tree visitClass(ClassTree tree, Void p) { ClassTree n = make.Class(tree.getModifiers(), tree.getSimpleName(), tree.getTypeParameters(), - tree.getExtendsClause(), tree.getImplementsClause(), tree.getMembers()); + tree.getExtendsClause(), tree.getImplementsClause(), + tree.getPermitsClause(), tree.getMembers()); model.setElement(n, model.getElement(tree)); model.setType(n, model.getType(tree)); comments.copyComments(tree, n); diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/usages/SourceAnalyzerFactory.java b/java/java.source.base/src/org/netbeans/modules/java/source/usages/SourceAnalyzerFactory.java index ba099217f152..8d0bf5a4fde6 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/usages/SourceAnalyzerFactory.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/usages/SourceAnalyzerFactory.java @@ -718,6 +718,7 @@ public Void visitClass (@NonNull final ClassTree node, @NonNull final Map task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + make.setLabel(clazz.getPermitsClause().get(0), "Subtype2"); + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + + public void testModifyExistingPermits2() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + String code = """ + package hierbas.del.litoral; + + public sealed class Test permits Subtype2 { + } + final class Subtype1 extends Test {} + final class Subtype2 extends Test {} + final class Subtype3 extends Test {} + """; + TestUtilities.copyStringToFile(testFile, code); + + JavaSource src = getJavaSource(testFile); + + Task task; + String res; + + //add first: + task = (WorkingCopy workingCopy) -> { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + List augmentedPermits = new ArrayList<>(clazz.getPermitsClause()); + augmentedPermits.add(0, make.QualIdent("hierbas.del.litoral.Subtype1")); + ClassTree newClass = make.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), clazz.getMembers()); + workingCopy.rewrite(clazz, newClass); + }; + src.runModificationTask(task).commit(); + res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(""" + package hierbas.del.litoral; + + public sealed class Test permits Subtype1, Subtype2 { + } + final class Subtype1 extends Test {} + final class Subtype2 extends Test {} + final class Subtype3 extends Test {} + """, res); + + //remove first: + task = (WorkingCopy workingCopy) -> { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + List augmentedPermits = new ArrayList<>(clazz.getPermitsClause()); + augmentedPermits.remove(0); + ClassTree newClass = make.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), clazz.getMembers()); + workingCopy.rewrite(clazz, newClass); + }; + src.runModificationTask(task).commit(); + res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(code, res); + + //add last: + task = (WorkingCopy workingCopy) -> { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + List augmentedPermits = new ArrayList<>(clazz.getPermitsClause()); + augmentedPermits.add(make.QualIdent("hierbas.del.litoral.Subtype3")); + ClassTree newClass = make.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), clazz.getMembers()); + workingCopy.rewrite(clazz, newClass); + }; + src.runModificationTask(task).commit(); + res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(""" + package hierbas.del.litoral; + + public sealed class Test permits Subtype2, Subtype3 { + } + final class Subtype1 extends Test {} + final class Subtype2 extends Test {} + final class Subtype3 extends Test {} + """, res); + + //remove last: + task = (WorkingCopy workingCopy) -> { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + List augmentedPermits = new ArrayList<>(clazz.getPermitsClause()); + augmentedPermits.remove(1); + ClassTree newClass = make.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), clazz.getMembers()); + workingCopy.rewrite(clazz, newClass); + }; + src.runModificationTask(task).commit(); + res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(code, res); + } + + public void testIntroduceRemovePermits() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + String code = """ + package hierbas.del.litoral; + + public sealed class Test { + } + final class Subtype1 extends Test {} + final class Subtype2 extends Test {} + final class Subtype3 extends Test {} + """; + TestUtilities.copyStringToFile(testFile, code); + + JavaSource src = getJavaSource(testFile); + + Task task; + String res; + + //add first: + task = (WorkingCopy workingCopy) -> { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + List augmentedPermits = new ArrayList<>(); + augmentedPermits.add(make.QualIdent("hierbas.del.litoral.Subtype1")); + ClassTree newClass = make.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), clazz.getMembers()); + workingCopy.rewrite(clazz, newClass); + }; + src.runModificationTask(task).commit(); + res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(""" + package hierbas.del.litoral; + + public sealed class Test permits Subtype1 { + } + final class Subtype1 extends Test {} + final class Subtype2 extends Test {} + final class Subtype3 extends Test {} + """, res); + + //remove first: + task = (WorkingCopy workingCopy) -> { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + List augmentedPermits = new ArrayList<>(clazz.getPermitsClause()); + augmentedPermits.remove(0); + ClassTree newClass = make.Class(clazz.getModifiers(), clazz.getSimpleName(), clazz.getTypeParameters(), clazz.getExtendsClause(), clazz.getImplementsClause(), clazz.getPermitsClause(), clazz.getMembers()); + workingCopy.rewrite(clazz, newClass); + }; + src.runModificationTask(task).commit(); + res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(code, res); + } + + String getGoldenPckg() { + return ""; + } + + String getSourcePckg() { + return ""; + } + + @Override + String getSourceLevel() { + return "17"; + } +} diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/usages/SourceAnalyzerTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/usages/SourceAnalyzerTest.java index 42d56fc861af..097f31ba8491 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/usages/SourceAnalyzerTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/usages/SourceAnalyzerTest.java @@ -231,6 +231,63 @@ public void report(Diagnostic diagnostic) { } } + public void testPermittedSubclasses() throws Exception { + final FileObject javaFile = FileUtil.toFileObject(TestFileUtils.writeFile( + new File(FileUtil.toFile(src),"Test.java"), //NOI18N + "public class Test permits Foo { \n" + //NOI18N + " public static void main(String[] args) {\n" + //NOI18N + " }\n" + //NOI18N + "}\n" + //NOI18N + "class Foo extends Test {\n" + //NOI18N + "}")); //NOI18N + + final DiagnosticListener diag = new DiagnosticListener() { + + private final Queue> problems = new ArrayDeque>(); + + @Override + public void report(Diagnostic diagnostic) { + problems.offer(diagnostic); + } + }; + SLQ slq = Lookup.getDefault().lookup(SLQ.class); + String prevSourceLevel = slq.setSourceLevel("17"); + TransactionContext.beginStandardTransaction(src.toURL(), true, ()->false, true); + try { + final ClasspathInfo cpInfo = ClasspathInfoAccessor.getINSTANCE().create( + src, + null, + true, + true, + false, + true); + final JavaFileObject jfo = FileObjects.sourceFileObject(javaFile, src); + final JavacTaskImpl jt = JavacParser.createJavacTask( + cpInfo, + diag, + SourceLevelQuery.getSourceLevel(src), //NOI18N + SourceLevelQuery.Profile.DEFAULT, + null, null, null, null, Arrays.asList(jfo)); + final Iterable trees = jt.parse(); + jt.enter(); + jt.analyze(); + final SourceAnalyzerFactory.SimpleAnalyzer sa = SourceAnalyzerFactory.createSimpleAnalyzer(); + List, Object[]>> data = sa.analyseUnit(trees.iterator().next(), jt); + assertEquals(2, data.size()); + Pair, java.lang.Object[]> testRecord = + data.stream() + .filter(p -> p.first().first().getBinaryName().equals("Test")) + .findAny() + .orElseThrow(); + assertTrue(((Collection)testRecord.second()[0]).contains( + DocumentUtil.encodeUsage("Foo", EnumSet.of( //NOI18N + ClassIndexImpl.UsageType.TYPE_REFERENCE)))); + } finally { + TransactionContext.get().rollBack(); + slq.setSourceLevel(prevSourceLevel); + } + } + public static final class CPP implements ClassPathProvider { @@ -255,7 +312,7 @@ public ClassPath findClassPath(FileObject file, String type) { public static final class SLQ implements SourceLevelQueryImplementation2 { - private static final Result2 R = new Result2() { + private final Result2 R = new Result2() { @Override public SourceLevelQuery.Profile getProfile() { @@ -263,7 +320,7 @@ public SourceLevelQuery.Profile getProfile() { } @Override public String getSourceLevel() { - return "1.8"; //NOI18N + return sourceLevel; //NOI18N } @Override public void addChangeListener(ChangeListener listener) { @@ -274,6 +331,7 @@ public void removeChangeListener(ChangeListener listener) { }; private volatile FileObject root; + private volatile String sourceLevel = "1.8"; @Override public Result getSourceLevel(FileObject javaFile) { @@ -284,7 +342,12 @@ public Result getSourceLevel(FileObject javaFile) { return null; } + public String setSourceLevel(String newSourceLevel) { + String prev = sourceLevel; + sourceLevel = newSourceLevel; + return prev; + } } } diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameSealedTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameSealedTest.java new file mode 100644 index 000000000000..20b794b219c5 --- /dev/null +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameSealedTest.java @@ -0,0 +1,178 @@ +/* + * 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.modules.refactoring.java.test; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import org.netbeans.api.java.source.CompilationController; +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.TestUtilities.TestInput; +import org.netbeans.api.java.source.TreePathHandle; +import org.netbeans.modules.refactoring.api.Problem; +import org.netbeans.modules.refactoring.api.RefactoringSession; +import org.netbeans.modules.refactoring.api.RenameRefactoring; +import org.netbeans.modules.refactoring.java.ui.JavaRenameProperties; +import org.netbeans.modules.refactoring.spi.impl.UndoManager; +import org.openide.filesystems.FileObject; +import org.openide.util.lookup.Lookups; + +/** + * + * @author Jan Becicka + */ +public class RenameSealedTest extends RefactoringTestBase { + + public RenameSealedTest(String name) { + super(name, "17"); + } + + public void testRenamePermittedClass0() throws Exception { + String testCode = """ + package test; + public class Outter { + public final class Sub|type implements Test { + } + public sealed interface Test permits Subtype { + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Outter.java", splitCode.code())); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Outter.java"), splitCode.pos(), "NewSubtype", props, true); + verifyContent(src, new File("Outter.java", + """ + package test; + public class Outter { + public final class NewSubtype implements Test { + } + public sealed interface Test permits NewSubtype { + } + } + """)); + + } + + public void testRenamePermittedClass1() throws Exception { + String testCode = """ + package test; + public final class Sub|type implements Test { + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", + """ + package test; + public sealed interface Test permits Subtype { + } + """), + new File("Subtype.java", splitCode.code())); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Subtype.java"), splitCode.pos(), "NewSubtype", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public sealed interface Test permits NewSubtype { + } + """), + new File("Subtype.java", + """ + package test; + public final class NewSubtype implements Test { + } + """)); + + } + + public void testRenamePermittedClass2() throws Exception { + String testCode = """ + package test; + public sealed interface Test permits Sub|type { + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code()), + new File("Subtype.java", + """ + package test; + public class Subtype implements Test { + } + """)); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "NewSubtype", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public sealed interface Test permits NewSubtype { + } + """), + new File("Subtype.java", + """ + package test; + public class NewSubtype implements Test { + } + """)); + + } + + private void performRename(FileObject source, final int absPos, final String newname, final JavaRenameProperties props, final boolean searchInComments, Problem... expectedProblems) throws Exception { + final RenameRefactoring[] r = new RenameRefactoring[1]; + JavaSource.forFileObject(source).runUserActionTask(new Task() { + + @Override + public void run(CompilationController javac) throws Exception { + javac.toPhase(JavaSource.Phase.RESOLVED); + CompilationUnitTree cut = javac.getCompilationUnit(); + + TreePath tp = javac.getTreeUtilities().pathFor(absPos); + + r[0] = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(tp, javac))); + r[0].setNewName(newname); + r[0].setSearchInComments(searchInComments); + if(props != null) { + r[0].getContext().add(props); + } + } + }, true); + + RefactoringSession rs = RefactoringSession.create("Rename"); + List problems = new LinkedList<>(); + + addAllProblems(problems, r[0].preCheck()); + if (!problemIsFatal(problems)) { + addAllProblems(problems, r[0].prepare(rs)); + } + if (!problemIsFatal(problems)) { + addAllProblems(problems, rs.doRefactoring(true)); + } + + assertProblems(Arrays.asList(expectedProblems), problems); + } + +} diff --git a/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java b/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java index 7081b179a542..ca3a8a904c2b 100644 --- a/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java +++ b/java/spi.java.hints/src/org/netbeans/modules/java/hints/spiimpl/Utilities.java @@ -501,7 +501,7 @@ private static Tree parseAndAttribute(CompilationInfo info, JavacTaskImpl jti, S newMembers.add(make.ExpressionStatement(make.Identifier("$$1$"))); newMembers.addAll(members.subList(syntheticOffset, members.size())); - patternTree = make.Class(mt, "$", List.of(), null, List.of(), newMembers); + patternTree = make.Class(mt, "$", List.of(), null, List.of(), List.of(), newMembers); } else { patternTree = members.get(0 + syntheticOffset); }