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 0727cbe743be..0e44783168e2 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
@@ -947,7 +947,7 @@ public Boolean visitClass(ClassTree node, Void p) {
}
List extends Tree> perms = node.getPermitsClause();
if (perms != null && !perms.isEmpty()) {
- wrapToken(cs.wrapExtendsImplementsKeyword(), 1, EXTENDS);
+ wrapToken(cs.wrapExtendsImplementsKeyword(), 1, EXTENDS);
wrapList(cs.wrapExtendsImplementsList(), cs.alignMultilineImplements(), true, COMMA, perms);
}
} finally {
@@ -1352,6 +1352,7 @@ private Boolean scanRecord(ClassTree node, Void p) {
if (!recParams.isEmpty()) {
spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true);
wrapList(cs.wrapMethodParams(), cs.alignMultilineMethodParams(), false, COMMA, recParams);
+ spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true); // solves #7403
}
accept(RPAREN);
List extends Tree> impls = node.getImplementsClause();
@@ -1398,8 +1399,6 @@ private Boolean scanRecord(ClassTree node, Void p) {
isFirstMember = false;
}
}
-
- spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true);
}
} finally {
indent = oldIndent;
@@ -5425,7 +5424,7 @@ private void reformatComment() {
}
/**
- *
+ *
* @see for more info on inline tags check documentation here.
* @return returns true if has inline tag prefix like "{+@tagname"
*/
diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/RecordFormattingTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/RecordFormattingTest.java
new file mode 100644
index 000000000000..385328784609
--- /dev/null
+++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/RecordFormattingTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.java.source.save;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.prefs.Preferences;
+import javax.lang.model.SourceVersion;
+import javax.swing.JEditorPane;
+import javax.swing.text.Document;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.fail;
+import org.netbeans.api.editor.mimelookup.MimeLookup;
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.lexer.JavaTokenId;
+import org.netbeans.api.java.source.SourceUtilsTestUtil;
+import org.netbeans.api.java.source.TestUtilities;
+import org.netbeans.api.lexer.Language;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.junit.NbTestSuite;
+import org.netbeans.modules.editor.indent.api.Reformat;
+import org.netbeans.modules.java.source.BootClassPathUtil;
+import org.netbeans.modules.java.source.usages.IndexUtil;
+import org.netbeans.spi.java.classpath.ClassPathProvider;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.netbeans.spi.java.queries.SourceLevelQueryImplementation;
+import org.openide.cookies.EditorCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataObject;
+import org.openide.util.SharedClassObject;
+
+/**
+ * Test to show the problem mentioned in #7043.
+ *
+ * For some strange reason, the setting spaceWithinMethodDeclParens affects the
+ * formatting (indentation) of the trailing brace (}) of a record definition,
+ * both as inner record and as top level record.
+ *
+ * @author homberghp
+ */
+public class RecordFormattingTest extends NbTestCase {
+
+ File testFile = null;
+ private String sourceLevel = "1.8";
+ private static final List EXTRA_OPTIONS = new ArrayList<>();
+
+ /**
+ * Creates a new instance of FormatingTest
+ */
+ public RecordFormattingTest(String name) {
+ super(name);
+ }
+
+ public static NbTestSuite suite() {
+ NbTestSuite suite = new NbTestSuite();
+ suite.addTestSuite(RecordFormattingTest.class);
+ return suite;
+ }
+
+ String content
+ = """
+ record Student(int id,String lastname,String firstname) implements Serializable {
+ int m(int x){
+ return id+x;
+ }
+ } // should stay flush to left margin
+ """;
+
+ public void test7043NoSpaces() throws Exception {
+ run7043("""
+ record Student(int id, String lastname, String firstname) implements Serializable {
+ int m(int x) {
+ return id + x;
+ }
+ } // should stay flush to left margin
+ """, false);
+ }
+
+ public void test7043WithSpaces() throws Exception {
+ run7043("""
+ record Student( int id, String lastname, String firstname ) implements Serializable {
+ int m( int x ) {
+ return id + x;
+ }
+ } // should stay flush to left margin
+ """, true);
+ }
+
+ /**
+ * The java formatter indents the final brace '}' of a record to far to the
+ * right, when the setting spaceWithinMethodDeclParens is True AND the
+ * record header defines parameters. This
+ *
+ * @throws Exception for some reason
+ */
+ // copied from testSealed
+ public void run7043(String golden, boolean spacesInMethodDecl) throws Exception {
+ try {
+ SourceVersion.valueOf("RELEASE_17"); //NOI18N
+ } catch (IllegalArgumentException ex) {
+ //OK, no RELEASE_15, skip test
+ return;
+ }
+ 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);
+ Preferences preferences = MimeLookup.getLookup(JavaTokenId.language().mimeType()).lookup(Preferences.class);
+ preferences.putBoolean("spaceWithinMethodDeclParens", spacesInMethodDecl);
+ preferences.putInt("blankLinesAfterClassHeader", 0);
+
+ final Document doc = ec.openDocument();
+ doc.putProperty(Language.class, JavaTokenId.language());
+ reformat(doc, content, golden);
+ }
+
+ private void reformat(Document doc, String content, String golden) throws Exception {
+ reformat(doc, content, golden, 0, content.length());
+ }
+
+ private void reformat(Document doc, String content, String golden, int startOffset, int endOffset) throws Exception {
+ doc.remove(0, doc.getLength());
+ doc.insertString(0, content, null);
+
+ Reformat reformat = Reformat.get(doc);
+ reformat.lock();
+ try {
+ reformat.reformat(startOffset, endOffset);
+ } finally {
+ reformat.unlock();
+ }
+ String res = doc.getText(0, doc.getLength());
+ assertNoDiff(golden, res);
+ }
+
+ static void assertNoDiff(String expected, String actual) {
+ var expectedLines = expected.trim().split("\n");
+ var actualLines = actual.trim().split("\n");
+ // fail if not of equal length
+ String assertResult = "";
+ if (expectedLines.length != actualLines.length) {
+ assertResult
+ = "number of lines differ :\n expected nr of lines = " + expectedLines.length
+ + " actual nr of lines= " + actualLines.length;
+ }
+
+ for (int i = 0; i < expectedLines.length && i < actualLines.length; i++) {
+ String actualLine = actualLines[i];
+ String expLine = expectedLines[i];
+ if (expLine.equals(actualLine)) {
+ continue;
+ }
+ assertResult += "\n expected at line " + (i + 1) + ": \n'" + expLine + "'\n not equal to actual \n'" + actualLine + "'";
+ }
+ if (assertResult.equals("")) {
+ return;
+ }
+ // output generated only when test fails, to help finding the difference.
+ for (int i = 1; i <= expectedLines.length; i++) {
+ String line = expectedLines[i-1];
+ System.err.format("%3d: %s\n", i,line);
+
+ }
+ System.err.println("actual");
+ for (int i = 1; i <= actualLines.length; i++) {
+ String line = actualLines[i-1];
+ System.err.format("%3d: %s\n", i,line);
+ }
+ fail("not golden, see stderr for more info");
+
+ }
+
+}
diff --git a/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties b/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties
index 7589b9380dfe..eb2edc7a97b6 100644
--- a/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties
+++ b/java/java.source/src/org/netbeans/modules/java/ui/Bundle.properties
@@ -497,6 +497,10 @@ default\:\
public int add(int a, int b) {\
return a + b;\
}\
+public record ARecord(String name, int age){\
+}\
+}\
+record TopLevelRecord(String name, double value){\
}
#do not translate