Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion harness/nbjunit/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# under the License.

javac.compilerargs=-Xlint:unchecked
javac.source=1.8
javac.release=17
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml

Expand Down
158 changes: 158 additions & 0 deletions harness/nbjunit/src/org/netbeans/junit/AssertLinesEqualHelpers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* 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.junit;

import java.io.PrintStream;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.fail;
import static org.netbeans.junit.AssertLinesEqualHelpers.StringsCompareMode.EXACT;

/**
*
* @author homberghp
*/
public class AssertLinesEqualHelpers {

public enum StringsCompareMode {
EXACT, IGNORE_DUP_SPACES, IGNORE_INDENTATION, IGNORE_WHITESPACE_DIFF
}
private static StringsCompareMode stringsCompareMode = EXACT;
private static Function<String, String> linePreprocessor = s -> s;
public static boolean showOutputOnPass = false;

/**
* Sets the string compare mode.
* <ul>
* <li>EXACT: exact line by line, including empty lines.
* <li>IGNORE_DUP_SPACES: ignore duplicate (inner) spacing
* </li>INGNORE_INDENTATION ignore difference in leading and trailing white
* space
* </li>INGNORE_WHITE_SPACE_DIFF ignore difference in any white space
* </ul>
*
* @param mode
*/
public static void setStringCompareMode(StringsCompareMode mode) {
stringsCompareMode = mode;
linePreprocessor = switch (mode) {
case EXACT ->
s -> s;
case IGNORE_DUP_SPACES ->
s -> s.replaceAll("\\s{2,}", " ");
case IGNORE_INDENTATION ->
s -> s.trim();
case IGNORE_WHITESPACE_DIFF ->
s -> s.trim().replaceAll("\\s{2,}", " ");
};

}

/**
* Prints a source by splitting on the line breaks and prefixing with name
* and line number.
*
* @param out the stream to print to
* @param name the name as prefix to each line
* @param source the source code to print to the out stream.
*/
public static void printNumbered(final PrintStream out, final String name, String source) {
AtomicInteger c = new AtomicInteger(1);
source.trim().lines().forEach(l -> out.println("%s [%4d] %s".formatted(name, c.getAndIncrement(), l)));
}

/**
* Compare strings by replacing all multiples of white space([ \t\n\r]) with
* a space.
*
* The test programmer chooses this to make it easier to write the input and
* the expected strings.
*
* @param expected to compare
* @param actual to compare
*/
public static void assertLinesEqual1(String name, String expected, String actual) {
try {
assertEquals(name, expected.replaceAll("[ \t\r\n\n]+", " "), actual.replaceAll("[ \t\r\n\n]+", " "));
} catch (Throwable t) {
System.err.println("expected:");
System.err.println(expected);
System.err.println("actual:");
System.err.println(actual);
throw t;
}
}

/**
* Compare strings by splitting them into lines, remove empty lines, and
* trim white space.Only when any of the lines differ, all lines are printed
* with the unequal lines flagged.
*
* Before the lines are compared, they are trimmed and the white space is
* normalized by collapsing multiple white space characters into one. This
* should make the tests less brittle.
*
* If any of the compared lines are unequal, this test fails and the
* comparison result is shown on stderr in a simplified diff format.
*
* @param testName to print before comparison result.
* @param fileName to print before each compared line.
* @param expected to compare
* @param actual to compare
*/
public static void assertLinesEqual2(String testName,String fileName, String expected, String actual) {
if (stringsCompareMode != EXACT) {
expected = expected.trim().replaceAll("([\t\r\n])\\1+", "$1");
actual = actual.trim().replaceAll("([\t\r\n])\\1+", "$1");
}
String[] linesExpected;
String[] linesActual;
linesExpected = expected.lines().toArray(String[]::new);
linesActual = actual.lines().toArray(String[]::new);
int limit = Math.max(linesExpected.length, linesActual.length);
StringBuilder sb = new StringBuilder();
boolean equals = true;
for (int i = 0; i < limit; i++) {
String oe = (i < linesExpected.length ? linesExpected[i] : "");
String oa = (i < linesActual.length ? linesActual[i] : "");
String e = linePreprocessor.apply(oe);
String a = linePreprocessor.apply(oa);
// somehow my user is inserted, so avoid to test those lines.
if (e.contains("@author") && a.contains("@author")) {
e = a = "* @author goes here";
oa = oe;
}
boolean same = e.equals(a);
String sep = same ? " " : " | ";
equals &= same;
sb.append(String.format(fileName + " [%3d] %-80s%s%-80s%n", i, oe, sep, oa));
}
if (!equals || showOutputOnPass) {
System.err.println("test " + testName + (equals ? " PASSED" : " FAILED") + " with compare mode = " + stringsCompareMode);
System.err.print(String.format(fileName + " %-80s%s%-80s%n", "expected", " + ", "actual"));
System.err.println(sb.toString());
System.err.flush();
if (!equals) {
fail("lines differ, see stderr for more details.");
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,26 @@ public ClassTree Enum(ModifiersTree modifiers,
return delegate.Enum(modifiers, simpleName, implementsClauses, memberDecls);
}

/**
* Creates a new ClassTree representing record.
*
* @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 implementsClauses the list of the interfaces this class
* implements, or an empty list.
* @param memberDecls the list of fields defined by this class, or an
* empty list.
* @see com.sun.source.tree.ClassTree
*/
public ClassTree Record(ModifiersTree modifiers,
CharSequence simpleName,
List<? extends TypeParameterTree> typeParameters,
List<? extends Tree> implementsClauses,
List<? extends Tree> memberDecls) {
return delegate.Record(modifiers, simpleName, typeParameters, implementsClauses, memberDecls);
}

/**
* Creates a new CompilationUnitTree.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,7 @@ String template(ElementKind kind) {
case INTERFACE: return "Templates/Classes/Interface.java"; // NOI18N
case ANNOTATION_TYPE: return "Templates/Classes/AnnotationType.java"; // NOI18N
case ENUM: return "Templates/Classes/Enum.java"; // NOI18N
case RECORD: return "Templates/Classes/Record.java"; // NOI18N
case PACKAGE: return "Templates/Classes/package-info.java"; // NOI18N
default:
Logger.getLogger(WorkingCopy.class.getName()).log(Level.SEVERE, "Cannot resolve template for {0}", kind);
Expand Down Expand Up @@ -1246,6 +1247,9 @@ FileObject doCreateFromTemplate(CompilationUnitTree cut) throws IOException {
case ENUM:
kind = ElementKind.ENUM;
break;
case RECORD:
kind = ElementKind.RECORD;
break;
default:
Logger.getLogger(WorkingCopy.class.getName()).log(Level.SEVERE, "Cannot resolve template for {0}", cut.getTypeDecls().get(0).getKind());
kind = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,15 @@ public ClassTree Enum(ModifiersTree modifiers,
long flags = getBitFlags(modifiers.getFlags()) | Flags.ENUM;
return Class(flags, (com.sun.tools.javac.util.List<JCAnnotation>) modifiers.getAnnotations(), simpleName, Collections.<TypeParameterTree>emptyList(), null, implementsClauses, Collections.emptyList(), memberDecls);
}

public ClassTree Record(ModifiersTree modifiers,
CharSequence simpleName,
List<? extends TypeParameterTree> typeParameters,
List<? extends Tree> implementsClauses,
List<? extends Tree> memberDecls) {
long flags = getBitFlags(modifiers.getFlags()) | Flags.RECORD;
return Class(flags, (com.sun.tools.javac.util.List<JCAnnotation>) modifiers.getAnnotations(), simpleName, typeParameters, null, implementsClauses, Collections.emptyList(), memberDecls);
}

public CompilationUnitTree CompilationUnit(PackageTree packageDecl,
List<? extends ImportTree> importDecls,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.stream.Collectors.toCollection;

import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.lexer.JavaTokenId;
Expand Down Expand Up @@ -869,6 +870,7 @@ public void visitClassDef(JCClassDecl tree) {
toLeftMargin();
printAnnotations(tree.mods.annotations);
long flags = tree.mods.flags;
boolean isRecord= (flags & RECORD) != 0;
if ((flags & ENUM) != 0)
printFlags(flags & ~(INTERFACE | FINAL));
else
Expand All @@ -884,15 +886,24 @@ public void visitClassDef(JCClassDecl tree) {
? out.col : out.leftMargin + cs.getContinuationIndentSize());
}
} else {
if ((flags & ENUM) != 0)
if ((flags & ENUM) != 0) {
print("enum ");
else {
if ((flags & ABSTRACT) != 0)
} else if (isRecord){
print("record ");
} else {
if ((flags & ABSTRACT) != 0) {
print("abstract ");
}
print("class ");
}
print(tree.name);
printTypeParameters(tree.typarams);
if (isRecord) {
print("(");
List<JCVariableDecl> components = getRecordComponents(tree);
wrapTrees(components, cs.wrapMethodParams(), out.col); //TODO: read from settings(!)
print(") ");
}
if (tree.extending != null) {
wrap("extends ", cs.wrapExtendsImplementsKeyword());
print(tree.extending);
Expand Down Expand Up @@ -930,6 +941,9 @@ public void visitClassDef(JCClassDecl tree) {
blankLines(enclClass.name.isEmpty() ? cs.getBlankLinesAfterAnonymousClassHeader() : (flags & ENUM) != 0 ? cs.getBlankLinesAfterEnumHeader() : cs.getBlankLinesAfterClassHeader());
boolean firstMember = true;
for (JCTree t : members) {
if (t.getKind()==Kind.VARIABLE && t instanceof JCVariableDecl vardecl && 0!=(vardecl.mods.flags & Flags.RECORD)) {
continue;
}
printStat(t, true, firstMember, true, true, false);
firstMember = false;
}
Expand All @@ -943,6 +957,36 @@ public void visitClassDef(JCClassDecl tree) {
enclClass = enclClassPrev;
}

/**
* Get the record components, either by fetching the field-members or
* the parameters from a fitting constructor
* @param tree of a record
* @return the canonical parameters for the record
*/
private List<JCVariableDecl> getRecordComponents(JCClassDecl tree) {
List<JCVariableDecl> components
= List.from(tree.defs
.stream()
.filter(member -> member.getKind() == Kind.VARIABLE)
.map(member -> (JCVariableDecl) member)
.filter(comp -> (comp.mods.flags & RECORD) != 0)
.toList());

final long syntOrCompact = Flags.SYNTHETIC | Flags.COMPACT_RECORD_CONSTRUCTOR;
var recordParams = tree.getMembers()
.stream()
.filter(m -> m.getKind() == Kind.METHOD && m instanceof JCMethodDecl mdecl && mdecl.getReturnType() == null)
.map(JCMethodDecl.class::cast)
.filter(met -> (met.mods.flags & syntOrCompact) != 0)
.findFirst()
.map(m -> m.params);
if (recordParams.isPresent()) {
components = recordParams.get();
}

return components;
}

private void printEnumConstants(java.util.List<? extends JCTree> defs, boolean forceSemicolon, boolean printComments) {
boolean first = true;
boolean hasNonEnumerator = false;
Expand Down Expand Up @@ -1003,26 +1047,39 @@ public void visitMethodDef(JCMethodDecl tree) {
needSpace();
print(tree.name);
}
print(cs.spaceBeforeMethodDeclParen() ? " (" : "(");
if (cs.spaceWithinMethodDeclParens() && tree.params.nonEmpty())
print(' ');
boolean oldPrintingMethodParams = printingMethodParams;
printingMethodParams = true;
wrapTrees(tree.params, cs.wrapMethodParams(), cs.alignMultilineMethodParams()
? out.col : out.leftMargin + cs.getContinuationIndentSize(),
true);
printingMethodParams = oldPrintingMethodParams;
if (cs.spaceWithinMethodDeclParens() && tree.params.nonEmpty())
needSpace();
print(')');
if ((tree.mods.flags & Flags.COMPACT_RECORD_CONSTRUCTOR)==0L) {
print(cs.spaceBeforeMethodDeclParen() ? " (" : "(");
if (cs.spaceWithinMethodDeclParens() && tree.params.nonEmpty())
print(' ');
boolean oldPrintingMethodParams = printingMethodParams;
printingMethodParams = true;
wrapTrees(tree.params, cs.wrapMethodParams(), cs.alignMultilineMethodParams()
? out.col : out.leftMargin + cs.getContinuationIndentSize(),
true);
printingMethodParams = oldPrintingMethodParams;
if (cs.spaceWithinMethodDeclParens() && tree.params.nonEmpty())
needSpace();
print(')');
}
if (tree.thrown.nonEmpty()) {
wrap("throws ", cs.wrapThrowsKeyword());
wrapTrees(tree.thrown, cs.wrapThrowsList(), cs.alignMultilineThrows()
? out.col : out.leftMargin + cs.getContinuationIndentSize(),
true);
}
if (tree.body != null) {
printBlock(tree.body, tree.body.stats, cs.getMethodDeclBracePlacement(), cs.spaceBeforeMethodDeclLeftBrace(), true);

List<JCStatement> stats = tree.body.stats;
if ((tree.mods.flags & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0L) {
ListBuffer<JCStatement> nstats= new ListBuffer<>();
for (JCStatement stat : stats) {
if (!stat.toString().contains("super();")){
nstats.append(stat);
}
}
stats=nstats.toList();
}
printBlock(tree.body, stats, cs.getMethodDeclBracePlacement(), cs.spaceBeforeMethodDeclLeftBrace(), true);
} else {
if (tree.defaultValue != null) {
print(" default ");
Expand All @@ -1037,9 +1094,10 @@ public void visitMethodDef(JCMethodDecl tree) {
@Override
public void visitVarDef(JCVariableDecl tree) {
boolean notEnumConst = (tree.mods.flags & Flags.ENUM) == 0;
boolean isRecordComponent = (tree.mods.flags & Flags.RECORD) != 0;
printAnnotations(tree.mods.annotations);
if (notEnumConst) {
printFlags(tree.mods.flags);
if(!isRecordComponent) printFlags(tree.mods.flags);
if (!suppressVariableType) {
if ((tree.mods.flags & VARARGS) != 0) {
// Variable arity method. Expecting ArrayType, print ... instead of [].
Expand Down Expand Up @@ -2159,6 +2217,7 @@ private void blankLines(JCTree tree, boolean before) {
case ANNOTATION_TYPE:
case CLASS:
case ENUM:
case RECORD:
case INTERFACE:
n = before ? cs.getBlankLinesBeforeClass() : cs.getBlankLinesAfterClass();
if (((JCClassDecl) tree).defs.nonEmpty() && !before) {
Expand Down
Loading