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 extends Tree> implementsClauses,
List extends Tree> 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 extends TypeParameterTree> typeParameters,
+ Tree extendsClause,
+ List extends Tree> implementsClauses,
+ List extends Tree> permitsClauses,
+ List extends Tree> 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 extends TypeParameterTree> typeParameters,
Tree extendsClause,
List extends Tree> implementsClauses,
+ List extends Tree> permitsClauses,
List extends Tree> 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 extends Tree> oldL,
return new PositionEstimator.ExtendsEstimator(oldL, newL, diffContext);
}
+ static PositionEstimator permits(List extends Tree> oldL,
+ List extends Tree> newL,
+ DiffContext diffContext)
+ {
+ return new PositionEstimator.ExtendsEstimator(oldL, newL, diffContext);
+ }
+
static PositionEstimator statements(List extends Tree> oldL,
List extends Tree> 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 extends Tree> oldL,
+ List extends Tree> newL,
+ DiffContext diffContext)
+ {
+ super(token -> token.id() == JavaTokenId.IDENTIFIER &&
+ CONSTANT.contentEquals(token.text()),
+ CONSTANT, oldL, newL, diffContext);
+ }
+ }
+
static class ThrowsEstimator extends BaseEstimator {
ThrowsEstimator(List extends ExpressionTree> oldL,
List extends ExpressionTree> 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 extends Tree> oldL,
List extends Tree> newL,
DiffContext diffContext)
+ {
+ this(token -> token.id() != precToken, precToken.fixedText(),
+ oldL, newL, diffContext);
+ }
+
+ private BaseEstimator(Predicate> prefixTokenAcceptor,
+ String prefixTokenText,
+ List extends Tree> oldL,
+ List extends Tree> 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 extends ExpressionTree> implementing =
translateClassRef((List extends ExpressionTree>)tree.getImplementsClause());
+ List extends ExpressionTree> permits =
+ translateClassRef((List extends ExpressionTree>)tree.getPermitsClause());
importAnalysis.enterVisibleThroughClasses(tree);
List extends Tree> 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 extends JavaFileObject> 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 extends JavaFileObject> 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 extends CompilationUnitTree> 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);
}