diff --git a/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java b/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java index 076e41c446ae..5c1fad00a391 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/ElementHandle.java @@ -107,7 +107,7 @@ private ElementHandle(final ElementKind kind, String... signatures) { * Resolves an {@link Element} from the {@link ElementHandle}. * @param compilationInfo representing the {@link javax.tools.JavaCompiler.CompilationTask} * in which the {@link Element} should be resolved. - * @return resolved subclass of {@link Element} or null if the elment does not exist on + * @return resolved subclass of {@link Element} or null if the element does not exist on * the classpath/sourcepath of {@link javax.tools.JavaCompiler.CompilationTask}. */ @SuppressWarnings ("unchecked") // NOI18N @@ -143,12 +143,6 @@ private T resolveImpl (final ModuleElement module, final JavacTaskImpl jt) { if (log.isLoggable(Level.FINE)) log.log(Level.FINE, "Resolving element kind: {0}", this.kind); // NOI18N ElementKind simplifiedKind = this.kind; - if (simplifiedKind.name().equals("RECORD")) { - simplifiedKind = ElementKind.CLASS; //TODO: test - } - if (simplifiedKind.name().equals("RECORD_COMPONENT")) { - simplifiedKind = ElementKind.FIELD; //TODO: test - } switch (simplifiedKind) { case PACKAGE: assert signatures.length == 1; @@ -156,6 +150,7 @@ private T resolveImpl (final ModuleElement module, final JavacTaskImpl jt) { case CLASS: case INTERFACE: case ENUM: + case RECORD: case ANNOTATION_TYPE: { assert signatures.length == 1; final Element type = getTypeElementByBinaryName (module, signatures[0], jt); @@ -213,6 +208,7 @@ private T resolveImpl (final ModuleElement module, final JavacTaskImpl jt) { } case FIELD: case ENUM_CONSTANT: + case RECORD_COMPONENT: { assert signatures.length == 3; final Element type = getTypeElementByBinaryName (module, signatures[0], jt); diff --git a/java/java.source.base/src/org/netbeans/api/java/source/RecordUtils.java b/java/java.source.base/src/org/netbeans/api/java/source/RecordUtils.java new file mode 100644 index 000000000000..e808df26d570 --- /dev/null +++ b/java/java.source.base/src/org/netbeans/api/java/source/RecordUtils.java @@ -0,0 +1,150 @@ +/* + * 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; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.tree.JCTree; +//import java.util.List; +//import com.sun.tools.javac.util.List; +import java.util.List; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.lang.model.element.Modifier; +import org.netbeans.api.annotations.common.NonNull; + +/** + * Utilities specific for record types. + * + * @author homberghp + */ +public class RecordUtils { + + public static boolean isConStructor(Tree m) { + + if (m.getKind() != Kind.METHOD) { + return false; + } + MethodTree met = (MethodTree) m; + if (met.getModifiers().getFlags().contains(Modifier.STATIC)) { + return false; + } + return met.getReturnType() == null; + } + + /** + * Count the record components of a record. + * + * @param record to be counted for its components. + * @return the count + */ + public static int countComponents(ClassTree record) { + return (int) record.getMembers() + .stream() + .filter(RecordUtils::isNormalField) + .count(); + } + + public static List componentNames(ClassTree record) { + List result = components(record).stream() + .map(m -> ((VariableTree) m).getName().toString()) + .toList(); + return result; + } + + public static List components(ClassTree record) { + + List result = record.getMembers() + .stream() + .filter(m -> m.getKind() == Kind.VARIABLE) + .map(VariableTree.class::cast) + .filter(RecordUtils::isNormalField) + .map(JCTree.class::cast) + .toList(); + return result; + } + + public static Set parameterNames(MethodTree method) { + Set result = method.getParameters() + .stream() + .map(m -> ((VariableTree) m).getName().toString()) + .collect(Collectors.toSet()); + return result; + } + + public static boolean isNormalField(Tree t) { + return t.getKind() == Kind.VARIABLE && !((VariableTree) t).getModifiers().getFlags().contains(Modifier.STATIC); + } + + /** + * A member is a normal field when not a class, (or enum ...) not a method + * and not static. For record that should be a record component then. + * + * @param t + * @return true if fields of the class/record are of same amount, order and + * types + */ + public static boolean isRecordComponent(JCTree t) { + if (t.getKind() == Kind.CLASS) { + return false; + } + if (t.getKind() == Kind.METHOD) { + return false; + } + if (t.getKind() == Kind.VARIABLE && t instanceof VariableTree vt) { + return !vt.getModifiers().getFlags().contains(Modifier.STATIC); + } + return false; + } + + public static boolean hasAllParameterNames(MethodTree method, Set names) { + Set actualNames = method.getParameters().stream().map(m -> m.getName().toString()).collect(Collectors.toSet()); + return names.size() == actualNames.size() && names.containsAll(actualNames); + } + + /** + * Get the canonical parameters of a record. + * + * Implementation detail: The method scans the tree and looks for a + * constructor with a matching signature. returns 0 if not a record or no + * constructor found. + * + * @param record tree presenting the record + * @return the list of fields as declared in the record header. + */ + public static List canonicalParameters(JCTree.JCClassDecl node) { + Set expectedNames = node.getMembers().stream() + .filter(m -> RecordUtils.isNormalField(m)) + .map(m -> ((JCTree.JCVariableDecl) m).getName().toString()) + .collect(Collectors.toSet()); + List result = node.getMembers().stream() + .filter(m -> RecordUtils.isConStructor(m)) + .map(JCTree.JCMethodDecl.class::cast) + .filter(t -> RecordUtils.hasAllParameterNames(t, expectedNames)) + .flatMap(m -> ((JCTree.JCMethodDecl) m).getParameters().stream()).toList(); + return result; + } + +} diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java b/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java index 14b097f0afb8..af7ab34baa7b 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/TreePathHandle.java @@ -322,6 +322,7 @@ private static boolean isSupported(Element el) { case ENUM_CONSTANT: case RECORD: //TODO: record component + case RECORD_COMPONENT: return true; case PARAMETER: //only method and constructor parameters supported (not lambda): @@ -869,4 +870,4 @@ public Void scan(Tree node, Void p) { return result; } -} \ No newline at end of file +} diff --git a/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java b/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java index ff93ca70b8b5..87a76549adcb 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java @@ -1221,6 +1221,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); @@ -1248,6 +1249,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; 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 559d428ebb64..d16ea5869125 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 @@ -104,6 +104,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; +import javax.lang.model.element.Modifier; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.lexer.JavaTokenId; @@ -231,7 +232,7 @@ public void reset(int margin, int col) { public int getIndent() { return out.leftMargin; } - + public void setIndent(int indent) { out.leftMargin = indent; } @@ -270,14 +271,14 @@ public final void print(String s) { if (s == null) return; out.append(s); - } + } public final void print(Name n) { if (n == null) return; out.append(n.toString()); } - + private void print(javax.lang.model.element.Name n) { if (n == null) return; @@ -291,11 +292,11 @@ public void print(JCTree t) { doAccept(t, true); blankLines(t, false); } - + public void print(DCTree t) { print(t, false); } - + public void print(DCTree t, boolean noMarginAfter) { if (t == null) return; blankLines(t, true, false); @@ -303,7 +304,7 @@ public void print(DCTree t, boolean noMarginAfter) { doAccept(t); blankLines(t, false, noMarginAfter); } - + private Map overrideStartPositions; private int getOldPos(JCTree oldT) { @@ -318,7 +319,7 @@ private int getOldPos(JCTree oldT) { public int endPos(JCTree t) { return TreeInfo.getEndPos(t, diffContext.origUnit.endPositions); } - + private java.util.List getStatements(Tree tree) { switch (tree.getKind()) { case BLOCK: return ((BlockTree) tree).getStatements(); @@ -326,31 +327,31 @@ private java.util.List getStatements(Tree tree) { default: return null; } } - + private java.util.List printOriginalPartOfFieldGroup(FieldGroupTree fgt) { java.util.List variables = fgt.getVariables(); TreePath tp = TreePath.getPath(diffContext.origUnit, variables.get(0)); TreePath parent = tp != null ? tp.getParentPath() : null; - + if (parent == null) return variables; - + java.util.List statements = getStatements(parent.getLeaf()); - + if (statements == null) return variables; JCVariableDecl firstDecl = fgt.getVariables().get(0); int startIndex = statements.indexOf(firstDecl); - + if (startIndex < 0) return variables; //XXX: should not happen - + int origCount = 0; int s = statements.size(); for (JCTree t : variables) { if (startIndex >= s || statements.get(startIndex++) != t) break; origCount++; } - + if (origCount < 2) return variables; - + int firstPos = getOldPos(firstDecl); int groupStart = startIndex; // find the start of the field group among the original statement. The fieldgroup is detected using @@ -381,7 +382,7 @@ private java.util.List printOriginalPartOfFieldGroup(FieldGroupT overrideStartPositions = null; return variables.subList(origCount, variables.size()); } - + public Set oldTrees = Collections.emptySet(); public SortedSet reindentRegions = new TreeSet<>(new Comparator() { @Override public int compare(int[] o1, int[] o2) { @@ -394,7 +395,7 @@ private java.util.List printOriginalPartOfFieldGroup(FieldGroupT private void doAccept(JCTree t, boolean printComments/*XXX: should ideally always print comments?*/) { if (!handlePossibleOldTrees(Collections.singletonList(t), printComments)) { if (printComments) printPrecedingComments(t, true); - + int start = out.length(); if (t instanceof FieldGroupTree) { @@ -404,7 +405,7 @@ private void doAccept(JCTree t, boolean printComments/*XXX: should ideally alway printEnumConstants(List.from(fgt.getVariables().toArray(new JCTree[0])), !fgt.isEnum() || fgt.moreElementsFollowEnum(), printComments); } else { java.util.List remainder = printOriginalPartOfFieldGroup(fgt); - + //XXX: this will unroll the field group (see FieldGroupTest.testMove187766) //XXX: copied from visitClassDef boolean firstMember = remainder.size() == fgt.getVariables().size(); @@ -430,14 +431,14 @@ private void doAccept(JCTree t, boolean printComments/*XXX: should ideally alway if (tag != null) { tag2Span.put(tag, new int[]{start + initialOffset, end + initialOffset}); } - + if (printComments) { printInnerCommentsAsTrailing(t, true); printTrailingComments(t, true); } } } - + private void doAccept(DCTree t) { // int start = toString().length(); @@ -465,36 +466,36 @@ public boolean handlePossibleOldTrees(java.util.List toPrint, CommentSet cs = commentHandler.getComments(t); if (cs.hasChanges()) return false; } - + if (toPrint.size() > 1) { //verify that all the toPrint trees belong to the same parent, and appear //in the same uninterrupted order under that parent: TreePath tp = TreePath.getPath(diffContext.mainUnit, toPrint.get(0)); TreePath parent = tp.getParentPath(); - + if (parent == null) return false; //XXX: should not happen, right? - + java.util.List statements = getStatements(parent.getLeaf()); - + if (statements == null) return false; - + int startIndex = statements.indexOf(toPrint.get(0)); - + if (startIndex < 0) return false; //XXX: should not happen - + for (JCTree t : toPrint) { if (statements.get(startIndex++) != t) return false; } } - + doPrintOriginalTree(toPrint, includeComments); - + return true; } - + private void doPrintOriginalTree(java.util.List toPrint, final boolean includeComments) { if (out.isWhitespaceLine()) toLeftMargin(); - + JCTree firstTree = toPrint.get(0); JCTree lastTree = toPrint.get(toPrint.size() - 1); @@ -507,7 +508,7 @@ private void doPrintOriginalTree(java.util.List toPrint, final } else { realStart = getOldPos(firstTree); } - + final int newStart = out.length() + initialOffset; final int[] realEnd = {endPos(lastTree)}; @@ -520,7 +521,7 @@ public Void scan(Tree node, Void p) { realEnd[0] = Math.max(realEnd[0], Math.max(CasualDiff.commentEnd(old, CommentSet.RelativePosition.INLINE), CasualDiff.commentEnd(old, CommentSet.RelativePosition.TRAILING))); trailingCommentsHandled.add(node); } - + Object tag = tree2Tag != null ? tree2Tag.get(node) : null; if (tag != null) { @@ -528,7 +529,7 @@ public Void scan(Tree node, Void p) { int e = endPos((JCTree) node); tag2Span.put(tag, new int[]{s - realStart + newStart, e - realStart + newStart}); } - + } return super.scan(node, p); } @@ -557,10 +558,10 @@ private void copyToIndented(int from, int to) { } String text = origText.substring(from, to); - + int newLine = text.indexOf("\n") + 1; boolean wasWhitespaceLine = out.isWhitespaceLine(); - + if (newLine == 0 && !wasWhitespaceLine) { print(text); } else { @@ -575,8 +576,8 @@ private void copyToIndented(int from, int to) { /** * Adjusts {@link #reindentRegions} if the char buffer conntents is * trimmed. - * - * @param limit + * + * @param limit */ @Override public void trimmed(int limit) { @@ -595,7 +596,7 @@ public void trimmed(int limit) { } } } - + /** Print a package declaration. */ @@ -640,9 +641,14 @@ public String getMethodHeader(MethodTree t, String s) { print(tree.name); s = replace(s, NAME); } - print('('); - wrapTrees(tree.params, WrapStyle.WRAP_NEVER, out.col); - print(')'); + boolean isRecord = enclClass.getKind()== Kind.RECORD; + // return type null makes this method a constructor + boolean isCandidateCompactCtor = isRecord && null == t.getReturnType(); + if (!isCandidateCompactCtor) { + print('('); + wrapTrees(tree.params, WrapStyle.WRAP_NEVER, out.col); + print(')'); + } s = replace(s, PARAMETERS); if (tree.thrown.nonEmpty()) { print(" throws "); @@ -653,6 +659,7 @@ public String getMethodHeader(MethodTree t, String s) { } public String getClassHeader(ClassTree t, String s) { +// System.err.println("VP getClassHeader "+ t); JCClassDecl tree = (JCClassDecl) t; printAnnotations(tree.mods.annotations); s = replace(s, ANNOTATIONS); @@ -844,7 +851,7 @@ public void visitPackageDef(JCPackageDecl tree) { printPackage(tree.pid); } } - + @Override public void visitImport(JCImport tree) { print("import "); @@ -856,6 +863,7 @@ public void visitImport(JCImport tree) { @Override public void visitClassDef(JCClassDecl tree) { +// System.err.println("VP visitClassDef "+tree); JCClassDecl enclClassPrev = enclClass; enclClass = tree; toLeftMargin(); @@ -977,6 +985,10 @@ private void printEnumConstants(java.util.List defs, boolean f @Override public void visitMethodDef(JCMethodDecl tree) { +// System.err.println("VP visitMethodDef +" + tree); + boolean methodIsConstructor = null==tree.getReturnType(); + boolean enclosingIsRecord= enclClass.getKind() == Kind.RECORD; + boolean paramsIsComponents= paramsIsComponents(tree.getParameters()); if ((tree.mods.flags & Flags.SYNTHETIC)==0 && tree.name != names.init || enclClass != null) { @@ -995,18 +1007,9 @@ 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 (!(methodIsConstructor && enclosingIsRecord && paramsIsComponents)){ + printMethodParams(tree); + } if (tree.thrown.nonEmpty()) { wrap("throws ", cs.wrapThrowsKeyword()); wrapTrees(tree.thrown, cs.wrapThrowsList(), cs.alignMultilineThrows() @@ -1026,6 +1029,21 @@ public void visitMethodDef(JCMethodDecl tree) { } } + void printMethodParams(JCMethodDecl tree) { + 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(')'); + } + @Override public void visitVarDef(JCVariableDecl tree) { boolean notEnumConst = (tree.mods.flags & Flags.ENUM) == 0; @@ -1656,7 +1674,7 @@ else if (tree.clazz.type != null) printNewClassBody(tree); } } - + public void printNewClassBody(JCNewClass tree) { JCClassDecl enclClassPrev = enclClass; enclClass = tree.def; @@ -1828,8 +1846,8 @@ public void visitTypeCast(JCTypeCast tree) { @Override public void visitTypeUnion(JCTypeUnion that) { boolean sep = cs.spaceAroundBinaryOps(); - wrapTrees(that.getTypeAlternatives(), - cs.wrapDisjunctiveCatchTypes(), + wrapTrees(that.getTypeAlternatives(), + cs.wrapDisjunctiveCatchTypes(), cs.alignMultilineDisjunctiveCatchTypes() ? out.col : out.leftMargin + cs.getContinuationIndentSize(), false, sep, sep, "|"); // NOI18N } @@ -1959,15 +1977,15 @@ private static String quote(String val, char keep, boolean charContext) { } return sb.toString(); } - + private static final String[] typeTagNames = new String[TypeTag.values().length]; - + static { for (TypeTag tt : TypeTag.values()) { typeTagNames[tt.ordinal()] = tt.name().toLowerCase(Locale.ENGLISH); } } - + /** * Workaround for defect #239258. Converts typetag names into lowercase using ENGLISH locale. */ @@ -2000,7 +2018,7 @@ public void visitAnnotatedType(JCAnnotatedType tree) { print(' '); printExpr(tree.underlyingType); } - + @Override public void visitTypeParameter(JCTypeParameter tree) { print(tree.name); @@ -2195,7 +2213,7 @@ private void blankLines(JCTree tree, boolean before) { return; } } - + private boolean isFirst(JCTree tree, List list) { for (JCTree t : list) { if (!isSynthetic(t)) { @@ -2204,7 +2222,7 @@ private boolean isFirst(JCTree tree, List list) { } return false; } - + private boolean isLast(JCTree tree, List list) { boolean b = false; for (JCTree t : list) { @@ -2214,7 +2232,7 @@ private boolean isLast(JCTree tree, List list) { } return b; } - + /** * The following tags are block-tags *
    @@ -2729,6 +2747,42 @@ public void setDocCommentKind(JavaTokenId docCommentKind) { this.docCommentKind = docCommentKind; } + /** + * Check that the given parameters are equal to the classes (record) component. + * + * return true iff parameters and components are of same length, in same name and type order + * @param parameters of the method + * @return if the parameters equal the record components. + */ + private boolean paramsIsComponents(List parameters) { + record TypeInfo(String name, String type){} + TypeInfo[] params = parameters.stream() + .map(p -> new TypeInfo(p.getName().toString(), p.getType().toString())) + .toArray(TypeInfo[]::new); + TypeInfo[] components = enclClass.getMembers().stream() + .filter(VeryPretty::isRecordComponent) + .map(x ->(VariableTree) x) + .map(p -> new TypeInfo(p.getName().toString(), p.getType().toString())) + .toArray(TypeInfo[]::new); + // short circuit on different length. + if (params.length != components.length) return false; + return Arrays.deepEquals(params, components); + } + + /** + * A member is a normal field when not a class, (or enum ...) not a method + * and not static. For record that should be a record component then. + * @param t + * @return true if fields of the class/record are of same amount, order and types + */ + static boolean isRecordComponent(JCTree t){ + if (t.getKind()==Kind.CLASS) return false; + if (t.getKind()==Kind.METHOD) return false; + if (t.getKind()==Kind.VARIABLE && t instanceof VariableTree vt){ + return !vt.getModifiers().getFlags().contains(Modifier.STATIC); + } + return false; + } private final class Linearize extends ErrorAwareTreeScanner> { @Override public Boolean scan(Tree node, java.util.List p) { @@ -2766,12 +2820,12 @@ private void adjustSpans(Iterable original, String code) { if (tree2Tag == null) { return; //nothing to copy } - + java.util.List linearized = new LinkedList(); if (!new Linearize().scan(original, linearized) != Boolean.TRUE) { return; //nothing to copy } - + ClassPath empty = ClassPathSupport.createClassPath(new URL[0]); ClasspathInfo cpInfo = ClasspathInfo.create(JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries(), empty, empty); JavacTaskImpl javacTask = JavacParser.createJavacTask(cpInfo, null, null, null, null, null, null, null, Arrays.asList(FileObjects.memoryFileObject("", "Scratch.java", code))); @@ -2798,7 +2852,7 @@ private static String whitespace(int num) { private boolean printAnnotationsFormatted(List annotations) { if (reallyPrintAnnotations) return false; - + VeryPretty del = new VeryPretty(diffContext, cs, new HashMap(), tree2Doc, new HashMap(), origText, 0); del.reallyPrintAnnotations = true; del.printingMethodParams = printingMethodParams; @@ -2807,7 +2861,7 @@ private boolean printAnnotationsFormatted(List annotations) { String str = del.out.toString(); int col = printingMethodParams ? out.leftMargin + cs.getContinuationIndentSize() : out.col; - + str = Reformatter.reformat(str + " class A{}", cs, cs.getRightMargin() - col); str = str.trim().replace("\n", "\n" + whitespace(col)); @@ -2823,7 +2877,7 @@ private boolean printAnnotationsFormatted(List annotations) { return true; } - + private void printAnnotations(List annotations) { if (annotations.isEmpty()) return ; @@ -2831,7 +2885,7 @@ private void printAnnotations(List annotations) { toColExactly(out.leftMargin); return ; } - + while (annotations.nonEmpty()) { printNoParenExpr(annotations.head); if (annotations.tail != null && annotations.tail.nonEmpty()) { @@ -2889,19 +2943,19 @@ else if (addSpace) needSpace(); } } - + private static final String[] flagLowerCaseNames = new String[Flag.values().length]; - + static { for (Flag flag : Flag.values()) { flagLowerCaseNames[flag.ordinal()] = flag.name().toLowerCase(Locale.ENGLISH); } } - + /** - * Workaround for defect #239258. Prints flag names converted to lowercase in ENGLISH locale to + * Workaround for defect #239258. Prints flag names converted to lowercase in ENGLISH locale to * avoid weird Turkish I > i-without-dot-above conversion. - * + * * @param flags flags * @return flag names, space-separated. */ @@ -3009,7 +3063,7 @@ private void printExprs(List < T > trees, String sep) { private void printStat(JCTree tree) { printStat(tree, false, false); } - + private void printStat(JCTree tree, boolean member, boolean first) { printStat(tree, member, first, false, false, false); } @@ -3017,12 +3071,12 @@ private void printStat(JCTree tree, boolean member, boolean first) { /** * Prints blank lines before, positions to the exact column (optional), prints tree and * blank lines after. And optional additional newline. - * + * * @param tree * @param member * @param first * @param col - * @param nl + * @param nl */ private void printStat(JCTree tree, boolean member, boolean first, boolean col, boolean nl, boolean printComments) { if(tree==null) { @@ -3040,13 +3094,13 @@ private void printStat(JCTree tree, boolean member, boolean first, boolean col, if (col) { toColExactly(out.leftMargin); } - // because of comment duplication + // because of comment duplication if(printComments) printPrecedingComments(tree, !member); printInnerCommentsAsTrailing(tree, !member); printExpr(tree, TreeInfo.notExpression); int tag = tree.getTag().ordinal();//XXX: comparing ordinals!!! if(JCTree.Tag.APPLY.ordinal()<=tag && tag<=JCTree.Tag.MOD_ASG.ordinal()) print(';'); - + printTrailingComments(tree, !member); blankLines(tree, false); if (nl) { @@ -3122,7 +3176,7 @@ private void printBlock(JCTree tree, List stats, BracePlacemen } public int conditionStartHack = (-1); - + private void printBlock(JCTree tree, List stats, BracePlacement bracePlacement, boolean spaceBeforeLeftBrace, boolean members, boolean printComments) { if (printComments) printPrecedingComments(tree, true); int old = indent(); @@ -3446,14 +3500,14 @@ private void wrap(String s, WrapStyle wrapStyle) { private void wrapTrees(List trees, WrapStyle wrapStyle, int wrapIndent) { wrapTrees(trees, wrapStyle, wrapIndent, false); //TODO: false for "compatibility", with the previous release, but maybe should be true for everyone? } - + private void wrapTrees(List trees, WrapStyle wrapStyle, int wrapIndent, boolean wrapFirst) { wrapTrees(trees, wrapStyle, wrapIndent, wrapFirst, cs.spaceBeforeComma(), cs.spaceAfterComma(), ","); // NOI18N } private void wrapTrees(List trees, WrapStyle wrapStyle, int wrapIndent, boolean wrapFirst, boolean spaceBeforeSeparator, boolean spaceAfterSeparator, String separator) { - + boolean first = true; for (List < T > l = trees; l.nonEmpty(); l = l.tail) { if (!first) { @@ -3462,7 +3516,7 @@ private void wrapTrees(List trees, WrapStyle wrapStyle, in } print(separator); } - + if (!first || wrapFirst) { switch(first && wrapStyle != WrapStyle.WRAP_NEVER ? WrapStyle.WRAP_IF_LONG : wrapStyle) { case WRAP_IF_LONG: @@ -3487,7 +3541,7 @@ private void wrapTrees(List trees, WrapStyle wrapStyle, in first = false; } } - + private void wrapAssignOpTree(final String operator, int col, final Runnable print) { final boolean spaceAroundAssignOps = cs.spaceAroundAssignOps(); if (cs.wrapAfterAssignOps()) { @@ -3506,7 +3560,7 @@ private void wrapAssignOpTree(final String operator, int col, final Runnable pri } }); } - + private void wrapTree(WrapStyle wrapStyle, boolean needsSpaceBefore, int colAfterWrap, Runnable print) { switch(wrapStyle) { case WRAP_NEVER: 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 8b77849102a8..79aaf997e2ee 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 @@ -175,6 +175,7 @@ import org.openide.util.NbBundle; import org.openide.util.NbCollections; import javax.lang.model.type.TypeKind; +import org.netbeans.api.java.source.RecordUtils; import org.netbeans.modules.java.source.transform.TreeHelpers; public class CasualDiff { @@ -205,7 +206,7 @@ public class CasualDiff { // such variable should not provide new line at the end. private boolean parameterPrint = false; private boolean enumConstantPrint = false; - + /** * Aliases places in the new text with block sequence boundaries in the old text. The diff can then get information * on how guarded blocks (or other divisor of the source) is mapped into the changed text and not generate diffs across @@ -251,9 +252,9 @@ public static Collection diff(Context context, for (Tree t : oldTreePath) { if (t == oldTree) continue; - + List embeddedElements; - + if (TreeUtilities.CLASS_TREE_KINDS.contains(t.getKind())) { embeddedElements = ((ClassTree) t).getMembers(); } else if (t.getKind() == Kind.BLOCK) { @@ -261,9 +262,9 @@ public static Collection diff(Context context, } else { continue; } - + embeddedElements = td.filterHidden(NbCollections.checkedListByCopy(embeddedElements, JCTree.class, false)); - + if (embeddedElements.isEmpty()) { int indent = getOldIndent(diffContext, t); @@ -305,7 +306,7 @@ public static Collection diff(Context context, //was: // int ln = td.oldTopLevel.lineMap.getLineNumber(start); // int lineStart = td.oldTopLevel.lineMap.getStartPosition(ln); - + td.printer.setInitialOffset(lineStart); Tree current = oldTree; @@ -325,7 +326,7 @@ public static Collection diff(Context context, for (Tree p : ((LambdaExpressionTree)t).getParameters()) { if (p == current) { td.parameterPrint = true; - + } } break; @@ -364,7 +365,7 @@ public static Collection diff(Context context, } try { String toParse = origText.substring(0, start) + resultSrc + origText.substring(end); - + final Document doc = LineDocumentUtils.createDocument("text/x-java"); doc.insertString(0, toParse, null); doc.putProperty(Language.class, JavaTokenId.language()); @@ -380,7 +381,7 @@ public static Collection diff(Context context, } final Indent i = Indent.get(doc); i.lock(); - + AtomicLockDocument adoc = LineDocumentUtils.asRequired(doc, AtomicLockDocument.class); try { Runnable r = new Runnable() { @@ -425,7 +426,7 @@ public static Collection diff(Context context, String originalText = isCUT ? origText : origText.substring(start, end); userInfo.putAll(td.diffInfo); - return td.checkDiffs(DiffUtilities.diff(originalText, resultSrc, start, + return td.checkDiffs(DiffUtilities.diff(originalText, resultSrc, start, td.readSections(originalText.length(), resultSrc.length(), lineStart, start), lineStart)); } @@ -433,23 +434,23 @@ private static class SectKey { private int off; SectKey(int off) { this.off = off; } } - + /** * Reads the section map. While the printer produced the text matching the region * starting at 'start', the diff will only cover text starting and textStart, which may * be in the middle of the line. the blockSequenceMap was filled by the printer, * so offsets may need to be moved backwards. - * + * * @param l1 length of the original text * @param l2 length of the new text * @param printerStart printer start * @param diffStart - * @return + * @return */ private int[] readSections(int l1, int l2, int printerStart, int diffStart) { Map seqMap = blockSequenceMap; if (seqMap.isEmpty()) { - // must offset the lengths, they come from the origtext/resultsrc, which may be already + // must offset the lengths, they come from the origtext/resultsrc, which may be already // only substrings of the printed area. int delta = diffStart - printerStart; return new int[] { l1 + delta, l2 + delta }; @@ -466,11 +467,11 @@ private int[] readSections(int l1, int l2, int printerStart, int diffStart) { } return res; } - + private List checkDiffs(List theDiffs) { if (theDiffs != null) { for (Diff d : theDiffs) { - if (diffContext.positionConverter.getOriginalPosition(d.getPos()) > diffContext.textLength || + if (diffContext.positionConverter.getOriginalPosition(d.getPos()) > diffContext.textLength || diffContext.positionConverter.getOriginalPosition(d.getEnd()) > diffContext.textLength) { LOG.warning("Invalid diff: " + d); } @@ -495,10 +496,10 @@ public static Collection diff(Context context, // TODO: the package name actually ends at the end of the name, so the semicolon could be treated as part // of the diffed list int start = td.oldTopLevel.getPackage() != null ? td.endPos(td.oldTopLevel.getPackage()) : 0; - + //XXX: no-javac-patch: td.tokenSequence.move(start); - + while (td.tokenSequence.movePrevious()) { if (td.isNoop(td.tokenSequence.token().id())) { start = td.tokenSequence.offset(); @@ -571,7 +572,7 @@ private int endPos(List trees) { return -1; return endPos(trees.get(trees.size()-1)); } - + private int checkLocalPointer(JCTree oldT, JCTree newT, int localPointer) { // diagnostics for defect #226498: log the tres iff the localPointer is bad if (localPointer < 0 || localPointer > origText.length()) { @@ -599,20 +600,20 @@ protected void diffTopLevel(JCCompilationUnit oldT, JCCompilationUnit newT, int[ checkLocalPointer(oldT, newT, localPointer); printer.print(origText.substring(localPointer)); } - + private static int getOldIndent(DiffContext diffContext, Tree t) { int offset = (int) diffContext.trees.getSourcePositions().getStartPosition(diffContext.origUnit, t); - + if (offset < 0) return -1; - + while (offset > 0 && diffContext.origText.charAt(offset - 1) != '\n') offset--; - + int indent = 0; - + while (offset < diffContext.origText.length()) { char c = diffContext.origText.charAt(offset++); - + if (c == '\t') { indent += diffContext.style.getTabSize(); } else if (c == '\n' || !Character.isWhitespace(c)) { @@ -621,10 +622,10 @@ private static int getOldIndent(DiffContext diffContext, Tree t) { indent++; } } - + return indent; } - + private boolean needStar(int localPointer) { if (localPointer <= 0) { return false; @@ -729,7 +730,7 @@ protected int diffModuleDef(JCModuleDecl oldT, JCModuleDecl newT, int[] bounds) } return bounds[1]; } - + protected int diffRequires(JCRequires oldT, JCRequires newT, int[] bounds) { int localPointer = bounds[0]; // module name @@ -790,7 +791,7 @@ protected int diffRequires(JCRequires oldT, JCRequires newT, int[] bounds) { localPointer = diffTree(oldT.moduleName, newT.moduleName, nameBounds); copyTo(localPointer, bounds[1]); - return bounds[1]; + return bounds[1]; } protected int diffExports(JCExports oldT, JCExports newT, int[] bounds) { @@ -811,7 +812,7 @@ protected int diffExports(JCExports oldT, JCExports newT, int[] bounds) { PositionEstimator est = EstimatorFactory.exportsOpensTo(oldT.moduleNames, newT.moduleNames, diffContext); localPointer = diffList2(oldT.moduleNames, newT.moduleNames, posHint, est); copyTo(localPointer, bounds[1]); - return bounds[1]; + return bounds[1]; } protected int diffOpens(JCOpens oldT, JCOpens newT, int[] bounds) { @@ -832,7 +833,7 @@ protected int diffOpens(JCOpens oldT, JCOpens newT, int[] bounds) { PositionEstimator est = EstimatorFactory.exportsOpensTo(oldT.moduleNames, newT.moduleNames, diffContext); localPointer = diffList2(oldT.moduleNames, newT.moduleNames, posHint, est); copyTo(localPointer, bounds[1]); - return bounds[1]; + return bounds[1]; } protected int diffProvides(JCProvides oldT, JCProvides newT, int[] bounds) { @@ -1012,10 +1013,11 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { } //TODO: class to record and vice versa! if (oldT.getKind() == Kind.RECORD && newT.getKind() == Kind.RECORD) { - ComponentsAndOtherMembers oldParts = splitOutRecordComponents(filteredOldTDefs); - ComponentsAndOtherMembers newParts = splitOutRecordComponents(filteredNewTDefs); +// System.err.println("CD record diffClassDef oldT'"+oldT+ "'\n"+" newT"+newT); + ComponentsAndOtherMembers oldParts = splitOutRecordComponents(oldT,filteredOldTDefs); + ComponentsAndOtherMembers newParts = splitOutRecordComponents(newT, filteredNewTDefs); int posHint; - if (oldParts.components().isEmpty()) { + if (oldParts.canonicalParameters().isEmpty()) { // compute the position. Find the parameters closing ')', its // start position is important for us. This is used when // there was not any parameter in original tree. @@ -1025,21 +1027,22 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { posHint = tokenSequence.offset(); } else { // take the position of the first old parameter - posHint = oldParts.components.iterator().next().getStartPosition(); + posHint = oldParts.canonicalParameters.iterator().next().getStartPosition(); } - if (!listsMatch(oldParts.components, newParts.components)) { + if (!listsMatch(oldParts.canonicalParameters, newParts.canonicalParameters)) { copyTo(localPointer, posHint); int old = printer.setPrec(TreeInfo.noPrec); parameterPrint = true; JCClassDecl oldEnclClass = printer.enclClass; printer.enclClass = null; - localPointer = diffParameterList(oldParts.components, newParts.components, null, posHint, Measure.MEMBER); + localPointer = diffParameterList(oldParts.canonicalParameters, newParts.canonicalParameters, null, posHint, Measure.MEMBER); printer.enclClass = oldEnclClass; parameterPrint = false; printer.setPrec(old); } //make sure the ')' is printed: - moveFwdToToken(tokenSequence, oldParts.components.isEmpty() ? posHint : endPos(oldParts.components.get(oldParts.components.size() - 1)), JavaTokenId.RPAREN); + moveFwdToToken(tokenSequence, oldParts.canonicalParameters.isEmpty() ? posHint : + endPos(oldParts.canonicalParameters.get(oldParts.canonicalParameters.size() - 1)), JavaTokenId.RPAREN); tokenSequence.moveNext(); posHint = tokenSequence.offset(); if (localPointer < posHint) @@ -1051,6 +1054,7 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { // it can be > (GT) or >> (SHIFT) insertHint = tokenSequence.offset() + tokenSequence.token().length(); } + switch (getChangeKind(oldT.extending, newT.extending)) { case NOCHANGE: insertHint = oldT.extending != null ? endPos(oldT.extending) : insertHint; @@ -1161,8 +1165,9 @@ protected int diffClassDef(JCClassDecl oldT, JCClassDecl newT, int[] bounds) { } return bounds[1]; } - - private ComponentsAndOtherMembers splitOutRecordComponents(List defs) { + + private ComponentsAndOtherMembers splitOutRecordComponents(JCClassDecl cdecl, List defs) { +// System.err.println("CD splitOutRecordComponents "+cdecl); ListBuffer components = new ListBuffer<>(); ListBuffer filteredDefs = new ListBuffer<>(); @@ -1174,18 +1179,20 @@ private ComponentsAndOtherMembers splitOutRecordComponents(List defs) { filteredDefs.add(t); } } - + List canonicalParameters = RecordUtils.canonicalParameters(cdecl); +// System.err.println("CD canonicalParameters = " + canonicalParameters); return new ComponentsAndOtherMembers(components.toList(), - filteredDefs.toList()); + filteredDefs.toList(), + canonicalParameters); } - record ComponentsAndOtherMembers(List components, List defs) {} + record ComponentsAndOtherMembers(List components, List defs, List canonicalParameters) {} /** - * When the enumeration contains just methods, it is necessary to preced them with single ;. If a constant is - * inserted, it must be inserted first; and the semicolon should be removed. This method will attempt to remove entire + * When the enumeration contains just methods, it is necessary to precede them with single ;. If a constant is + * inserted, it must be inserted first; and the semicolon should be removed. This method will attempt to remove entire * lines of whitespace around the semicolon. Preceding or following comments are preserved. - * + * * @param insertHint the local Pointer value * @return new localPointer value */ @@ -1246,7 +1253,7 @@ private int removeExtraEnumSemicolon(int insertHint) { tokenSequence.moveNext(); return insertHint; } - + private boolean isEnum(Tree tree) { if (tree instanceof FieldGroupTree) return ((FieldGroupTree) tree).isEnum(); if (tree instanceof VariableTree) return (((JCVariableDecl) tree).getModifiers().flags & Flags.ENUM) != 0; @@ -1333,7 +1340,7 @@ protected int diffMethodDef(JCMethodDecl oldT, JCMethodDecl newT, int[] bounds) printer.print(newRestype); printer.print(" "); // print the space after return type } - + int posHint; if (oldT.typarams.isEmpty()) { posHint = oldRestype != null ? oldRestype.getStartPosition() : oldT.getStartPosition(); @@ -1360,7 +1367,7 @@ protected int diffMethodDef(JCMethodDecl oldT, JCMethodDecl newT, int[] bounds) } if (oldT.params.isEmpty()) { // compute the position. Find the parameters closing ')', its - // start position is important for us. This is used when + // start position is important for us. This is used when // there was not any parameter in original tree. int startOffset = oldT.pos; @@ -1763,8 +1770,8 @@ protected int diffBlock(JCBlock oldT, JCBlock newT, int[] blockBounds) { JCTree tree = oldstats.get(oldstats.size() - 1); localPointer = adjustLocalPointer(localPointer, comments.getComments(oldT), CommentSet.RelativePosition.INNER); CommentSet cs = comments.getComments(tree); - localPointer = adjustLocalPointer(localPointer, cs, CommentSet.RelativePosition.INLINE); - localPointer = adjustLocalPointer(localPointer, cs, CommentSet.RelativePosition.TRAILING); + localPointer = adjustLocalPointer(localPointer, cs, CommentSet.RelativePosition.INLINE); + localPointer = adjustLocalPointer(localPointer, cs, CommentSet.RelativePosition.TRAILING); */ copyTo(localPointer, localPointer = endPos); } @@ -1783,7 +1790,7 @@ private int adjustLocalPointer(int localPointer, CommentSet cs, CommentSet.Relat } return localPointer; } - + private boolean isComment(JavaTokenId tid) { switch (tid) { case LINE_COMMENT: @@ -1990,7 +1997,7 @@ protected int diffSwitch(JCSwitch oldT, JCSwitch newT, int[] bounds) { copyTo(localPointer, localPointer = tokenSequence.offset()); PositionEstimator est = EstimatorFactory.cases(oldT.getCases(), newT.getCases(), diffContext); localPointer = diffList(oldT.cases, newT.cases, localPointer, est, Measure.MEMBER, printer); - + List cases = newT.cases; if (cases.size() != 0) { String caseKind = String.valueOf(cases.get(0).getCaseKind()); @@ -2188,7 +2195,7 @@ protected int diffTry(JCTry oldT, JCTry newT, int[] bounds) { for (Tree t = l.head; t!= null; l = l.tail, t = l.head) { printer.oldTrees.remove(t); } - } + } localPointer = diffParameterList(oldT.resources, newT.resources, null, @@ -2206,7 +2213,7 @@ protected int diffTry(JCTry oldT, JCTry newT, int[] bounds) { } } } - + copyTo(localPointer, bodyPos[0]); localPointer = diffTree(oldT.body, newT.body, bodyPos); copyTo(localPointer, localPointer = bodyPos[1]); @@ -2604,7 +2611,7 @@ protected int diffAssign(JCAssign oldT, JCAssign newT, JCTree parent, int[] boun } } //#174552 end - + // rhs copyTo(localPointer, rhsBounds[0]); localPointer = diffTree(oldT.rhs, newT.rhs, rhsBounds); @@ -2811,7 +2818,7 @@ protected int diffSelect(JCFieldAccess oldT, JCFieldAccess newT, copyTo(localPointer, bounds[1]); return bounds[1]; } - + protected int diffMemberReference(JCMemberReference oldT, JCMemberReference newT, int[] bounds) { int localPointer = bounds[0]; int[] exprBounds = getBounds(oldT.expr); @@ -2923,7 +2930,7 @@ protected int diffTypeApply(JCTypeApply oldT, JCTypeApply newT, int[] bounds) { copyTo(localPointer, bounds[1]); return bounds[1]; } - + protected int diffAnnotatedType(JCAnnotatedType oldT, JCAnnotatedType newT, int[] bounds) { int localPointer = bounds[0]; if (!listsMatch(oldT.annotations, newT.annotations)) { @@ -3048,7 +3055,7 @@ protected int diffModifiers(JCModifiers oldT, JCModifiers newT, JCTree parent, i localPointer = tokenSequence.offset(); } } - + localPointer = diffAnnotationsLists(oldT.getAnnotations(), newT.getAnnotations(), startPos, localPointer); if ((oldT.flags & Flags.ANNOTATION) != 0) { @@ -3094,7 +3101,7 @@ protected int diffModifiers(JCModifiers oldT, JCModifiers newT, JCTree parent, i private int diffAnnotationsLists(com.sun.tools.javac.util.List oldAnnotations, com.sun.tools.javac.util.List newAnnotations, int startPos, int localPointer) { int annotationsEnd = oldAnnotations.nonEmpty() ? endPos(oldAnnotations) : localPointer; - + if (listsMatch(oldAnnotations, newAnnotations)) { copyTo(localPointer, localPointer = (annotationsEnd != localPointer ? annotationsEnd : startPos)); } else { @@ -3131,13 +3138,13 @@ protected int diffErroneous(JCErroneous oldT, JCErroneous newT, int[] bounds) { copyTo(localPointer, bounds[1]); return bounds[1]; } - + protected int diffLambda(JCLambda oldT, JCLambda newT, int[] bounds) { int localPointer = bounds[0]; int posHint; if (oldT.params.isEmpty()) { // compute the position. Find the parameters closing ')', its - // start position is important for us. This is used when + // start position is important for us. This is used when // there was not any parameter in original tree. int startOffset = oldT.pos; @@ -3185,7 +3192,7 @@ protected int diffLambda(JCLambda oldT, JCLambda newT, int[] bounds) { localPointer = copyUpTo(localPointer, bounds[1]); return localPointer; } - + private static final EnumSet LAMBDA_PARAM_END_TOKENS = EnumSet.of(JavaTokenId.RPAREN, JavaTokenId.ARROW); protected int diffFieldGroup(FieldGroupTree oldT, FieldGroupTree newT, int[] bounds) { @@ -3382,14 +3389,14 @@ public boolean treesMatch(JCTree t1, JCTree t2, boolean deepMatch) { SourcePositions sps = this.diffContext.trees.getSourcePositions(); int a1 = (int)sps.getStartPosition(diffContext.origUnit, t1); int a2 = (int)sps.getEndPosition(diffContext.origUnit, t1); - + int b1 = (int)sps.getStartPosition(diffContext.origUnit, t2); int b2 = (int)sps.getEndPosition(diffContext.origUnit, t2); - + if (a1 == b1 && a2 == b2) { return true; } - + if (a1 == NOPOS || a2 == NOPOS || b1 == NOPOS || b2 == NOPOS) { return false; } @@ -3398,7 +3405,7 @@ public boolean treesMatch(JCTree t1, JCTree t2, boolean deepMatch) { } String sa = diffContext.origText.substring(a1, a2); String sb = diffContext.origText.substring(b1, b2); - + return sa.equals(sb); } default: @@ -3570,7 +3577,7 @@ private int printBreakContinueTree(int[] bounds, final Name oldTLabel, final Nam copyTo(localPointer, localPointer = getOldPos(oldT)); printer.print(stmt); localPointer += stmt.length(); - + int commentStart = -1; int commentEnd = -1; if (oldTLabel != null && oldTLabel.length() > 0) { @@ -3656,12 +3663,12 @@ private int diffParameterList( { return diffParameterList(oldList, newList, null, makeAround, pos, measure, spaceBefore, spaceAfter, listType, separator); } - + /** * Suppresses print out of parameter types; used for diff of parameters of an IMPLICIT param kind lambda expression. */ private boolean suppressParameterTypes; - + private int diffParameterList( List oldList, List newList, @@ -3722,7 +3729,7 @@ private int diffParameterList( tokenSequence.moveNext(); start = Math.max(tokenSequence.offset(), pos); } - + // in case when invoked through diffFieldGroup for enums, comments are already handled. if (start < bounds[0]) { copyTo(start, bounds[0], printer); @@ -4075,7 +4082,7 @@ private boolean commaNeeded(ResultItem[] arr, ResultItem item) { private List filterHidden(List list) { return filterHidden(diffContext, list); } - + public static List filterHidden(DiffContext diffContext, List list) { LinkedList result = new LinkedList<>(); // todo (#pf): capacity? List fieldGroup = new ArrayList<>(); @@ -4209,13 +4216,13 @@ private int diffList( ? diffContext.style.getImportGroups() : null; int lastGroup = -1; int i = 0; - + // if an item will be _inserted_ at the start (= first insert after possibly some deletes, but no modifies), // the text in between localPointer and insertPos should be copied. Insert pos may differ from estimator.getPositions()[0] // the text should be only included for INSERT operation, so save the range. See also delete op for compensation int insertPos = Math.min(getCommentCorrectedOldPos(oldList.get(i)), estimator.getInsertPos(0)); int insertSaveLocalPointer = localPointer; - + if (insertPos < localPointer) { insertPos = -1; } @@ -4246,7 +4253,7 @@ private int diffList( boolean match = false; if (lastdel != null) { boolean wasInFieldGroup = false; - // PENDING - should it be tested also in the loop of *all* deleted items ? Originally both the + // PENDING - should it be tested also in the loop of *all* deleted items ? Originally both the // FieldGroup and others only cared about the lastdel Tree. if(lastdel instanceof FieldGroupTree) { FieldGroupTree fieldGroupTree = (FieldGroupTree) lastdel; @@ -4268,7 +4275,7 @@ private int diffList( } } } - + // if inserting at the start (after possible deletes), copy the saved content detected before the result cycle start. if (insertPos > -1 && i > 0) { // do not copy past the element start, diffTree will print from that pos. @@ -4314,7 +4321,7 @@ private int diffList( break; } if (LineInsertionType.BEFORE == estimator.lineInsertType()) printer.newline(); - // specific case: the item is shuffled within the same parent. It's not expected that the printer will print it with the usual + // specific case: the item is shuffled within the same parent. It's not expected that the printer will print it with the usual // codestyle-defined blanklines before/after. However the blank lines are more expected when the item shifts to another scope. if (!oldList.contains(item.element) || !printer.handlePossibleOldTrees(Collections.singletonList(item.element), true)) { printer.print(item.element); @@ -4408,10 +4415,10 @@ private static boolean isVarTypeVariable(JCVariableDecl tree){ *

    * The return may be NEGATIVE to indicate, that the comment set is the same and should be retained * in the output. If the value is POSITIVE, the method has handled the copying. - * + * * @param t * @param preceding - * @return + * @return */ private CommentSet getCommentsForTree(Tree t, boolean preceding) { if (t instanceof FieldGroupTree) { @@ -4421,7 +4428,7 @@ private CommentSet getCommentsForTree(Tree t, boolean preceding) { } return comments.getComments(t); } - + protected int diffInnerComments(JCTree oldT, JCTree newT, int localPointer) { if (innerCommentsProcessed) { return localPointer; @@ -4448,7 +4455,7 @@ protected int diffInnerComments(JCTree oldT, JCTree newT, int localPointer) { false, localPointer); } - + private DocCommentTree getDocComment(JCTree t, boolean old) { if (t instanceof FieldGroupTree) { FieldGroupTree fgt = (FieldGroupTree)t; @@ -4457,7 +4464,7 @@ private DocCommentTree getDocComment(JCTree t, boolean old) { } return old ? oldTopLevel.docComments.getCommentTree(t) : tree2Doc.get(t); } - + // note: the oldTreeStartPos must be the real start, without preceding comments. protected int diffPrecedingComments(JCTree oldT, JCTree newT, int oldTreeStartPos, int localPointer, boolean doNotDelete) { if (parent instanceof FieldGroupTree) { @@ -4482,20 +4489,20 @@ protected int diffPrecedingComments(JCTree oldT, JCTree newT, int oldTreeStartPo } else { return localPointer; } - + } DocCommentTree oldD = getDocComment(oldT, true); return diffCommentLists(oldTreeStartPos, oldPrecedingComments, newPrecedingComments, oldD, newD, false, true, false, doNotDelete, localPointer); } - + protected int diffTrailingComments(JCTree oldT, JCTree newT, int localPointer, int elementEndWithComments) { CommentSet cs = getCommentsForTree(newT, false); CommentSet old = getCommentsForTree(oldT, false); List oldInlineComments = cs == old ? ((CommentSetImpl)cs).getOrigComments(CommentSet.RelativePosition.INLINE) : old.getComments(CommentSet.RelativePosition.INLINE); List newInlineComments = cs.getComments(CommentSet.RelativePosition.INLINE); - + List oldTrailingComments = cs == old ? ((CommentSetImpl)cs).getOrigComments(CommentSet.RelativePosition.TRAILING) : old.getComments(CommentSet.RelativePosition.TRAILING); List newTrailingComments = cs.getComments(CommentSet.RelativePosition.TRAILING); boolean sameInline = sameComments(oldInlineComments, newInlineComments); @@ -4558,7 +4565,7 @@ private boolean sameComments(List oldList, List newList) { Iterator newIter = newList.iterator(); Comment oldC = safeNext(oldIter); Comment newC = safeNext(newIter); - + while (oldC != null && newC != null) { if (!commentsMatch(oldC, newC)) return false; oldC = safeNext(oldIter); @@ -4567,7 +4574,7 @@ private boolean sameComments(List oldList, List newList) { return !((oldC == null) ^ (newC == null)); } - + // refactor it! make it better private int diffCommentLists(int oldTreeStartPos, List oldList, List newList, DocCommentTree oldDoc, DocCommentTree newDoc, boolean trailing, boolean preceding, boolean inner, @@ -4642,7 +4649,7 @@ private int diffCommentLists(int oldTreeStartPos, List oldList, } while (newC != null) { if (Style.WHITESPACE != newC.style()) { -// printer.print(newC.getText()); +// printer.print(newC.getText()); if (!firstNewCommentPrinted && preceding) { copyTo(localPointer, localPointer = oldTreeStartPos); } @@ -4655,13 +4662,13 @@ private int diffCommentLists(int oldTreeStartPos, List oldList, if (!firstNewCommentPrinted && preceding) { copyTo(localPointer, localPointer = oldTreeStartPos); } - // suppress potential margin after doc comment: there's a whitespace ready between the comment and the - // JCTree. + // suppress potential margin after doc comment: there's a whitespace ready between the comment and the + // JCTree. printer.print((DCTree) newDoc, firstNewCommentPrinted); } return localPointer; } - + private int diffDocTree(DCDocComment doc, DCTree oldT, DCTree newT, int[] elementBounds) { if (oldT == null && newT != null) { throw new IllegalArgumentException("Null is not allowed in parameters."); @@ -4681,9 +4688,9 @@ private int diffDocTree(DCDocComment doc, DCTree oldT, DCTree newT, int[] elemen } return tokenSequence.offset(); } - + int localpointer = elementBounds[0]; - + if (oldT.getKind() != newT.getKind()) { // different kind of trees found, print the whole new one. int[] oldBounds = getBounds(oldT, doc); @@ -4693,7 +4700,7 @@ private int diffDocTree(DCDocComment doc, DCTree oldT, DCTree newT, int[] elemen printer.print(newT); return oldBounds[1]; } - + switch(oldT.getKind()) { case ATTRIBUTE: localpointer = diffAttribute(doc, (DCAttribute) oldT, (DCAttribute) newT, elementBounds); @@ -4816,11 +4823,11 @@ private int diffDocTree(DCDocComment doc, DCTree oldT, DCTree newT, int[] elemen return localpointer; } - + private int diffAttribute(DCDocComment doc, DCAttribute oldT, DCAttribute newT, int[] elementBounds) { return elementBounds[1]; } - + private int diffDocComment(DCDocComment doc, DCDocComment oldT, DCDocComment newT, int[] elementBounds) { //set the existing token kind, to produce correct line-beginnings: int commentPos = getOldPos(oldT, oldT); @@ -4859,7 +4866,7 @@ private int diffDocComment(DCDocComment doc, DCDocComment oldT, DCDocComment new } return elementBounds[1]; } - + private int diffParam(DCDocComment doc, DCParam oldT, DCParam newT, int[] elementBounds) { int localpointer; if(oldT.isTypeParameter != newT.isTypeParameter) { @@ -4895,7 +4902,7 @@ private int diffParam(DCDocComment doc, DCParam oldT, DCParam newT, int[] elemen } return elementBounds[1]; } - + private int diffReturn(DCDocComment doc, DCReturn oldT, DCReturn newT, int[] elementBounds) { int localpointer = oldT.description.isEmpty()? elementBounds[1] : getOldPos(oldT.description.get(0), doc); copyTo(elementBounds[0], localpointer); @@ -4905,7 +4912,7 @@ private int diffReturn(DCDocComment doc, DCReturn oldT, DCReturn newT, int[] ele } return elementBounds[1]; } - + private int diffIdentifier(DCDocComment doc, DCIdentifier oldT, DCIdentifier newT, int[] elementBounds) { if(oldT.name.equals(newT.name)) { copyTo(elementBounds[0], elementBounds[1]); @@ -4914,20 +4921,20 @@ private int diffIdentifier(DCDocComment doc, DCIdentifier oldT, DCIdentifier new } return elementBounds[1]; } - + private int diffLink(DCDocComment doc, DCLink oldT, DCLink newT, int[] elementBounds) { int localpointer = getOldPos(oldT.ref, doc); copyTo(elementBounds[0], localpointer); - + localpointer = diffDocTree(doc, oldT.ref, newT.ref, new int[] {localpointer, endPos(oldT.ref, doc)}); localpointer = diffList(doc, oldT.label, newT.label, localpointer, Measure.TAGS); - + if(localpointer < elementBounds[1]) { copyTo(localpointer, elementBounds[1]); } return elementBounds[1]; } - + private int diffSee(DCDocComment doc, DCSee oldT, DCSee newT, int[] elementBounds) { int localpointer; localpointer = getOldPos(oldT.reference.get(0), doc); @@ -4938,7 +4945,7 @@ private int diffSee(DCDocComment doc, DCSee oldT, DCSee newT, int[] elementBound } return elementBounds[1]; } - + private int diffText(DCDocComment doc, DCText oldT, DCText newT, int[] elementBounds) { if(oldT.text.equals(newT.text)) { copyTo(elementBounds[0], elementBounds[1]); @@ -4947,6 +4954,8 @@ private int diffText(DCDocComment doc, DCText oldT, DCText newT, int[] elementBo } return elementBounds[1]; } +<<<<<<< HEAD +======= private int diffRawText(DCDocComment doc, DCTree.DCRawText oldT, DCTree.DCRawText newT, int[] elementBounds) { if(oldT.code.equals(newT.code)) { @@ -4956,6 +4965,7 @@ private int diffRawText(DCDocComment doc, DCTree.DCRawText oldT, DCTree.DCRawTex } return elementBounds[1]; } +>>>>>>> origin/master private int diffAuthor(DCDocComment doc, DCAuthor oldT, DCAuthor newT, int[] elementBounds) { int localpointer = oldT.name.isEmpty()? elementBounds[1] : getOldPos(oldT.name.get(0), doc); @@ -5166,7 +5176,7 @@ private int diffVersion(DCDocComment doc, DCVersion oldT, DCVersion newT, int[] } return elementBounds[1]; } - + private int diffList( DCDocComment doc, List oldList, @@ -5175,7 +5185,7 @@ private int diffList( Comparator measure) { assert oldList != null && newList != null; - + if (oldList == newList || oldList.equals(newList)) { return localPointer; } @@ -5333,7 +5343,7 @@ private boolean listContains(Listlist, Comment comment) { return true; return false; } - + private int commentStartCorrect(Comment c) { tokenSequence.move(c.pos()); @@ -5375,7 +5385,7 @@ public static int commentStart(DiffContext diffContext, CommentSet comments, Com return start; } } - + public static int commentEnd(CommentSet comments, CommentSet.RelativePosition pos) { List list = comments.getComments(pos); @@ -5385,7 +5395,7 @@ public static int commentEnd(CommentSet comments, CommentSet.RelativePosition po return list.get(list.size() - 1).endPos(); } } - + private static int commentEnd(DCDocComment doc) { int length = doc.comment.getText().length(); return doc.comment.getSourcePos(length-1); @@ -5394,16 +5404,16 @@ private static int commentEnd(DCDocComment doc) { private static int getOldPos(JCTree oldT) { return TreeInfo.getStartPos(oldT); } - + private int getOldPos(DCTree oldT, DCDocComment doc) { return oldT.pos(doc).getStartPosition(); } - + public int endPos(DCTree oldT, DCDocComment doc) { DocSourcePositions sp = JavacTrees.instance(context).getSourcePositions(); return (int) sp.getEndPosition(null, doc, oldT); } - + private int endPos(List trees, DCDocComment doc) { if (trees.isEmpty()) return -1; @@ -5423,12 +5433,12 @@ private int endPos(List trees, DCDocComment doc) { protected int diffTree(JCTree oldT, JCTree newT, int[] elementBounds) { return checkLocalPointer(oldT, newT, diffTree(oldT, newT, null, elementBounds)); } - + /** * Tracks current OLD node's path, so printout can be modified according to context */ private @NullAllowed TreePath currentPath; - + int diffTree(TreePath oldPath, JCTree newT, int[] elementBounds) { JCTree oldT = (JCTree)oldPath.getLeaf(); this.currentPath = oldPath; @@ -5455,7 +5465,7 @@ protected int diffTree(JCTree oldT, JCTree newT, JCTree parent /*used only for m currentPath = savePath; return result; } - + private int getPosAfterCommentEnd(Tree t, int minPos) { CommentSet cs = getCommentsForTree(t, false); List cmm = cs.getComments(CommentSet.RelativePosition.TRAILING); @@ -5476,7 +5486,7 @@ private int getPosAfterCommentEnd(Tree t, int minPos) { } return Math.max(minPos, pos); } - + private int getPosAfterCommentStart(Tree t, int minPos) { CommentSet cs = getCommentsForTree(t, true); List cmm = cs.getComments(CommentSet.RelativePosition.PRECEDING); @@ -5491,11 +5501,11 @@ private int getPosAfterCommentStart(Tree t, int minPos) { assert pos >= 0; return Math.max(minPos, pos); } - + private int getPosAfterTreeComments(JCTree t, int end) { class Scn extends ErrorAwareTreeScanner { int max = -1; - + @Override public Void scan(Tree node, Void p) { max = Math.max(getPosAfterCommentEnd((JCTree)node, -1), max); @@ -5507,7 +5517,7 @@ public Void scan(Tree node, Void p) { scn.scan(t, null); return Math.max(scn.max, end); } - + /** * True, if inner comments have been processed by a specialized code, or they will be printed after the tree. * Inner comments are used rarely, but e.g. blocks and classes use inner comments if they have empty body. @@ -5516,7 +5526,7 @@ public Void scan(Tree node, Void p) { * diffTreeImpl will print the comments. At the end, the flag is reset to the original value */ private boolean innerCommentsProcessed; - + private JCTree parent; protected int diffTreeImpl(JCTree oldT, JCTree newT, JCTree parent /*used only for modifiers*/, int[] elementBounds) { @@ -5528,7 +5538,7 @@ protected int diffTreeImpl(JCTree oldT, JCTree newT, JCTree parent /*used only f this.parent = saveParent; return ret; } - + private int diffTreeImpl0(JCTree oldT, JCTree newT, JCTree parent /*used only for modifiers*/, int[] elementBounds) { innerCommentsProcessed = false; if (oldT == null && newT != null) @@ -5552,7 +5562,7 @@ private int diffTreeImpl0(JCTree oldT, JCTree newT, JCTree parent /*used only fo if (printer.handlePossibleOldTrees(Collections.singletonList(newT), true)) { return getCommentCorrectedEndPos(oldT); } - + boolean handleImplicitLambda = parent != null && parent.hasTag(Tag.LAMBDA) && ((JCLambda)parent).params.size() == 1 && ((JCLambda)parent).params.get(0) == oldT &&((JCLambda)parent).paramKind == JCLambda.ParameterKind.IMPLICIT && newT.hasTag(Tag.VARDEF) && ((JCVariableDecl)newT).getType() != null; @@ -5576,15 +5586,15 @@ private int diffTreeImpl0(JCTree oldT, JCTree newT, JCTree parent /*used only fo copyTo(elementBounds[0], oldBounds[0]); } printer.print(newT); - // the printer will print attached traling comments, skip in the obsolete comments in the stream, so they don't duplicate. + // the printer will print attached traling comments, skip in the obsolete comments in the stream, so they don't duplicate. return getPosAfterTreeComments(oldT, oldBounds[1]); } } - + // if comments are the same, diffPredComments will skip them so that printer.print(newT) will // not emit them from the new element. But if printer.print() won't be used (the newT will be merged in rather // than printed anew), then surviving comments have to be printed. - int predComments = diffPrecedingComments(oldT, newT, getOldPos(oldT), elementBounds[0], + int predComments = diffPrecedingComments(oldT, newT, getOldPos(oldT), elementBounds[0], oldT.getTag() == Tag.TOPLEVEL && diffContext.forceInitialComment); int retVal = -1; // if (predComments < 0 && elementBounds[0] < -predComments) { @@ -6054,7 +6064,7 @@ private boolean matchIndexed(JCArrayAccess t1, JCArrayAccess t2) { private boolean matchSelect(JCFieldAccess t1, JCFieldAccess t2) { return treesMatch(t1.selected, t2.selected) && t1.name == t2.name; } - + private boolean matchReference(JCMemberReference t1, JCMemberReference t2) { return treesMatch(t1.expr, t2.expr) && t1.name == t2.name; } @@ -6066,12 +6076,12 @@ private boolean matchLiteral(JCLiteral t1, JCLiteral t2) { private boolean possibleTextBlock(JCLiteral t1, JCLiteral t2) { return t1.getKind() == Tree.Kind.STRING_LITERAL && t2.getKind() == Tree.Kind.STRING_LITERAL; } - + private boolean matchTypeApply(JCTypeApply t1, JCTypeApply t2) { return treesMatch(t1.clazz, t2.clazz) && listsMatch(t1.arguments, t2.arguments); } - + private boolean matchAnnotatedType(JCAnnotatedType t1, JCAnnotatedType t2) { return treesMatch(t1.underlyingType, t2.underlyingType) && listsMatch(t1.annotations, t2.annotations); @@ -6101,7 +6111,7 @@ private boolean matchLetExpr(LetExpr t1, LetExpr t2) { private boolean matchLambda(JCLambda t1, JCLambda t2) { return listsMatch(t1.params, t2.params) && treesMatch(t1.body, t2.body); } - + private boolean isCommaSeparated(JCVariableDecl oldT) { if (getOldPos(oldT) <= 0 || oldT.pos <= 0) { return false; @@ -6159,11 +6169,11 @@ private int[] getCommentCorrectedBounds(JCTree tree) { private int[] getBounds(JCTree tree) { return new int[] { getOldPos(tree), endPos(tree) }; } - + private int[] getBounds(DCTree tree, DCDocComment doc) { return new int[] { getOldPos(tree, doc), endPos(tree, doc) }; } - + private int copyUpTo(int from, int to, VeryPretty printer) { if (from < to) { copyTo(from, to, printer); @@ -6172,7 +6182,7 @@ private int copyUpTo(int from, int to, VeryPretty printer) { return from; } } - + private int copyUpTo(int from, int to) { return copyUpTo(from, to, printer); } @@ -6182,7 +6192,7 @@ private void copyTo(int from, int to) { } public static boolean noInvalidCopyTos = false; - + public void copyTo(int from, int to, VeryPretty loc) { if (from == to) { return; @@ -6207,7 +6217,7 @@ public void copyTo(int from, int to, VeryPretty loc) { } if (nextBlockBoundary == -1 && boundaries.hasNext()) { nextBlockBoundary = boundaries.next(); - } + } while (nextBlockBoundary != -1 && nextBlockBoundary < from) { if (boundaries.hasNext()) { nextBlockBoundary = boundaries.next(); @@ -6217,14 +6227,14 @@ public void copyTo(int from, int to, VeryPretty loc) { } } // map the boundary if the copied text starts at OR ends at the boundary. E.g. the after-boundary text might be - // generated, but the boundary itself is still preserved. + // generated, but the boundary itself is still preserved. while (from <= nextBlockBoundary && to >= nextBlockBoundary) { int off = nextBlockBoundary - from; int mapped = loc.out.length() + (from < nextBlockBoundary ? off : 0); - + Integer prev = blockSequenceMap.put(nextBlockBoundary, mapped); if (prev != null) { - // the first recorded value holds. + // the first recorded value holds. blockSequenceMap.put(nextBlockBoundary, prev); } nextBlockBoundary = boundaries.hasNext() ? boundaries.next() : -1; @@ -6236,13 +6246,13 @@ public void copyTo(int from, int to, VeryPretty loc) { private int diffTree(JCTree oldT, JCTree newT, int[] elementBounds, Kind parentKind) { return diffTree(oldT, newT, elementBounds, parentKind, true); } - + /** * This form contains a special hack for if, so that `else' can be placed on the same * line as the statement block end. * If a block statement is generated instead of a single stat, a newline is appended unless `retainNewline' * is false. If statement print passes false when else part of the if is present. - * + * * @param oldT old tree * @param newT new tree * @param elementBounds the old element bounds @@ -6426,7 +6436,7 @@ private void addDiffString(StringBuffer sb, Object o1, Object o2) { } } } - + private static String printCodeStyle(CodeStyle style) { if (style == null) { return ""; // NOI18N @@ -6448,12 +6458,12 @@ private static String printCodeStyle(CodeStyle style) { val = Arrays.asList((Object[])val); } sb.append(s).append(":").append(val).append("\n"); // NOI18N - } + } } } catch (Exception ex) {} return sb.toString(); } - + private int findVar(int start, int end) { tokenSequence.move(end); while (tokenSequence.movePrevious() && tokenSequence.offset() >= start) { 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 0e44783168e2..dceb2de63967 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 @@ -32,10 +32,10 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers; import java.net.URL; import java.util.*; +import java.util.stream.Collectors; import javax.lang.model.element.Name; import javax.swing.text.BadLocationException; import javax.swing.text.Document; -import javax.lang.model.element.Modifier; import org.netbeans.api.editor.document.LineDocumentUtils; import org.netbeans.api.editor.guards.DocumentGuards; import org.netbeans.api.java.classpath.ClassPath; @@ -1276,6 +1276,7 @@ public Boolean visitVariable(VariableTree node, Void p) { } private Boolean scanRecord(ClassTree node, Void p) { + System.err.println("Reformatted scanRecord"); boolean old = continuationIndent; int oldIndent = indent; try { @@ -1336,18 +1337,7 @@ private Boolean scanRecord(ClassTree node, Void p) { spaces(cs.spaceBeforeMethodDeclParen() ? 1 : 0); accept(LPAREN); List members = node.getMembers(); - List recParams = new ArrayList(); - - for (Tree member : members) { - if (member.getKind() == Tree.Kind.VARIABLE) { - ModifiersTree modifiers = ((VariableTree) member).getModifiers(); - Set modifierSet = modifiers.getFlags(); - - if (!modifierSet.contains(Modifier.STATIC)) { - recParams.add(member); - } - } - } + List recParams = getRecordComponents(node); if (!recParams.isEmpty()) { spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true); @@ -1414,6 +1404,21 @@ private Boolean scanRecord(ClassTree node, Void p) { return true; } + // this should be merged with RecordUtils.canonicalComponents. + protected static List getRecordComponents(ClassTree node) { + System.err.println("Reformatter get record components node = " + node); + Set expectedNames = node.getMembers().stream() + .filter(m-> RecordUtils.isNormalField(m) ) + .map(m-> ((VariableTree)m).getName().toString()) + .collect(Collectors.toSet()); + + List components= (List)node.getMembers().stream() + .filter(m-> RecordUtils.isConStructor(m)) + .filter(t -> RecordUtils.hasAllParameterNames((MethodTree)t,expectedNames)) + .map(c -> ((MethodTree)c).getParameters()); + return components; + } + private void classLeftBracePlacement() { CodeStyle.BracePlacement bracePlacement = cs.getClassDeclBracePlacement(); boolean spaceBeforeLeftBrace = cs.spaceBeforeClassDeclLeftBrace(); diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/RecordUtilsTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/RecordUtilsTest.java new file mode 100644 index 000000000000..d48d7efc1635 --- /dev/null +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/RecordUtilsTest.java @@ -0,0 +1,136 @@ +/* + * 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; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.junit.Test; +import static org.netbeans.api.java.source.CommentCollectorTest.getJavaSource; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.source.usages.IndexUtil; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * + * @author homberghp + */ +public class RecordUtilsTest extends NbTestCase { + + public RecordUtilsTest(String s) { + super(s); + } + private File work; + + @Override + protected void setUp() throws Exception { + File wd = getWorkDir(); + FileObject cache = FileUtil.createFolder(new File(wd, "cache")); + IndexUtil.setCacheFolder(FileUtil.toFile(cache)); + work = new File(wd, "work"); + work.mkdirs(); + + super.setUp(); + } + + @Test + public void testComponentCount() throws Exception { + + File testFile = new File(work, "Test.java"); + JCClassDecl[] records = prepareClassTree(testFile, recordCode); + assertEquals(3, RecordUtils.countComponents(records[0])); + } + + String recordCode + = """ + package test; + public record Test(int age, String name, G... grade) implements Serializable { + public Test(String name, G... grade){ + this(1, name, grade); + } + public Test{ + assert age > 0; + assert !name.isBlank(); + } + + public static String me = "puk"; + public int m(){ + return age+1; + } + } + """; + + static JCClassDecl[] prepareClassTree(File testFile, final String recordCode) throws Exception, IOException { + TestUtilities.copyStringToFile(testFile, recordCode); + JavaSource src = getJavaSource(testFile); + final JCClassDecl[] records = new JCClassDecl[1]; + Task task = new Task() { + public void run(final WorkingCopy workingCopy) throws Exception { + workingCopy.toPhase(JavaSource.Phase.PARSED); + records[0] = (JCClassDecl) workingCopy.getCompilationUnit().getTypeDecls().get(0); + } + }; + src.runModificationTask(task); + return records; + } + + @Test + public void testCanonicalParameters() throws Exception { + File testFile = new File(work, "Test.java"); + JCClassDecl[] records = prepareClassTree(testFile, recordCode); + List canonicalParameters = RecordUtils.canonicalParameters(records[0]); + assertEquals(3, canonicalParameters.size()); + + } + + @Test + public void testComponentNames() throws Exception { + File testFile = new File(work, "Test.java"); + JCClassDecl[] records = prepareClassTree(testFile, recordCode); + List componentNames = RecordUtils.componentNames(records[0]); + List expected = List.of("age", "name", "grade"); + assertEquals(expected, componentNames); + } + + @Test + public void testGetComponents() throws Exception { + File testFile = new File(work, "Test.java"); + JCClassDecl[] records = prepareClassTree(testFile, recordCode); + + List recordComponents = RecordUtils.components(records[0]); + assertEquals(3, recordComponents.size()); + } + + @Test + public void testHassAllParams() throws Exception { + File testFile = new File(work, "Test.java"); + JCClassDecl[] records = prepareClassTree(testFile, recordCode); + List members = records[0].getMembers(); + JCTree.JCMethodDecl cand = (JCTree.JCMethodDecl) members.get(4); + Set componentNames = new LinkedHashSet(RecordUtils.componentNames(records[0])); + assertTrue(RecordUtils.hasAllParameterNames(cand, componentNames)); + } +} diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java index bc33aa888e5d..91279a55f6ab 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/RecordTest.java @@ -194,6 +194,59 @@ public void run(WorkingCopy workingCopy) throws IOException { assertEquals(golden, res); } + /** + * Is the compact constructor preserved? + * Added check for #7044 + * + */ + public void testPreserveCompact() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + """ + package hierbas.del.litoral; + public record R(String first, String component) { + public R { + assert null != first; + } + } + """); + String golden = + """ + package hierbas.del.litoral; + public record R(String first) { + public R { + assert null != first; + } + } + """; + + JavaSource src = getJavaSource(testFile); + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + + Tree recordDecl = cut.getTypeDecls().get(0); + assertEquals(Kind.RECORD, recordDecl.getKind()); + ClassTree classTree = (ClassTree) recordDecl; + for (Tree m : classTree.getMembers()) { + if (m.getKind() == Kind.VARIABLE && + ((VariableTree) m).getName().contentEquals("component")) { + workingCopy.rewrite(classTree, make.removeClassMember(classTree, m)); + break; + } + } + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + //System.err.println(res); + assertEquals(golden, res); + } + public void testRemoveComponent() throws Exception { testFile = new File(getWorkDir(), "Test.java"); TestUtilities.copyStringToFile(testFile, diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/api/InnerToOuterRefactoring.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/api/InnerToOuterRefactoring.java index 52d457c59b8e..ab9c6cfae744 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/api/InnerToOuterRefactoring.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/api/InnerToOuterRefactoring.java @@ -22,10 +22,10 @@ import org.netbeans.modules.refactoring.api.AbstractRefactoring; import org.openide.util.lookup.Lookups; -/** +/** * Convert Inner to Top-Level refactoring implementation class. This refactoring * is capable of converting an inner class into a top-level class. - * + * * @see org.netbeans.modules.refactoring.spi.RefactoringPlugin * @see org.netbeans.modules.refactoring.spi.RefactoringPluginFactory * @see org.netbeans.modules.refactoring.api.AbstractRefactoring @@ -39,18 +39,23 @@ public final class InnerToOuterRefactoring extends AbstractRefactoring { // parameters of the refactoring private String className; private String referenceName; - + + private boolean innerIsRecord; + /** * Creates a new instance of InnerToOuterRefactoring. - * - * @param sourceType An inner class that should be converted to a top-level class. + * + * @param sourceType An inner class that should be converted to a top-level + * class. */ public InnerToOuterRefactoring(TreePathHandle sourceType) { super(Lookups.singleton(sourceType)); } - - /** Returns the type the members of which should be pulled up - * by this refactoring. + + /** + * Returns the type the members of which should be pulled up by this + * refactoring. + * * @return Source of the members to be pulled up. */ public TreePathHandle getSourceType() { @@ -58,34 +63,79 @@ public TreePathHandle getSourceType() { } // --- PARAMETERS ---------------------------------------------------------- - - /** Returns the name for the top-level class to be created. + /** + * Returns the name for the top-level class to be created. + * * @return Class name. */ public String getClassName() { return className; } - /** Sets name for the top-level class to be created. + /** + * Sets name for the top-level class to be created. + * * @param className Class name. */ public void setClassName(String className) { this.className = className; } - /** Returns name of the field that should be generated as a reference to the original - * outer class. If null, no field will be generated. - * @return Name of the field to be generated or null if no field will be generated. + /** + * Returns name of the field that should be generated as a reference to the + * original outer class. If null, no field will be generated. + * + * @return Name of the field to be generated or null if no field will be + * generated. */ public String getReferenceName() { return referenceName; } - - /** Sets name of the field that should be generated as a reference to the original - * outer class. Can be set to null which indicates that no field should be generated. - * @param referenceName Name of the field or null if no field should be generated. - */ + + /** + * Sets name of the field that should be generated as a reference to the + * original outer class. Can be set to null which indicates that no field + * should be generated. + * + * @param referenceName Name of the field or null if no field should be + * generated. + */ public void setReferenceName(String referenceName) { this.referenceName = referenceName; } + + /** + * Inner records need special handling because of the RecordComponents which + * are declared before the first curly brace, which differs from class, + * interface and enum. + * + * Also, the compact constructor should be considered. + * + * A compact constructor consists of the name of the Record, no parameters + * (not even the parens) and a block that does NOT assign the fields. + * + * @return the current value for this refactoring + */ + public boolean isInnerIsRecord() { + + return innerIsRecord; + } + + /** + * Inner records need special handling because of the RecordComponents which + * are declared before the first curly brace, which differs from class, + * interface and enum. + * + * Also, the compact constructor should be considered. + * + * A compact constructor consists of the name of the Record, no parameters + * (not even the parens) and a block that does NOT assign the fields. + * + * @param innerIsRecord use when inner class needs the special handling of + * an inner record. + */ + public void setInnerIsRecord(boolean innerIsRecord) { + this.innerIsRecord = innerIsRecord; + } + } diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/InnerToOuterTransformer.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/InnerToOuterTransformer.java index 226e6ae0b404..e606852549f9 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/InnerToOuterTransformer.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/InnerToOuterTransformer.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.netbeans.modules.refactoring.java.plugins; import com.sun.source.tree.*; @@ -52,7 +51,7 @@ public class InnerToOuterTransformer extends RefactoringVisitor { private InnerToOuterRefactoring refactoring; private boolean isInInnerClass = false; private Set referencedPrivateElement; - + private Element getCurrentElement() { return workingCopy.getTrees().getElement(getCurrentPath()); } @@ -78,7 +77,7 @@ public Tree visitIdentifier(IdentifierTree node, Element p) { return null; } if (inner.equals(current)) { - Tree newTree = make.setLabel(node, refactoring.getClassName()); + Tree newTree = make.setLabel(node, refactoring.getClassName()); rewrite(node, newTree); } else if (isThisReferenceToOuter() && isThisInInner()) { if (current.getModifiers().contains(Modifier.PRIVATE)) { @@ -105,7 +104,7 @@ public Tree visitIdentifier(IdentifierTree node, Element p) { TreePath elementPath = workingCopy.getTrees().getPath(current); JavaFileObject sourceFile = elementPath != null ? elementPath.getCompilationUnit().getSourceFile() : null; if ( (parent != null && parent.getKind() == Tree.Kind.CASE && ((CaseTree) parent).getExpression() == node && current.getKind() == ElementKind.ENUM_CONSTANT) - || path.getCompilationUnit().getSourceFile() == sourceFile) { + || path.getCompilationUnit().getSourceFile() == sourceFile) { rewrite(node, make.Identifier(current.getSimpleName())); } else { rewrite(node, make.QualIdent(current)); @@ -141,7 +140,7 @@ public Tree visitNewClass(NewClassTree arg0, Element arg1) { else { thisString = "this"; // NOI18N } - + } if (thisString != null && currentElement instanceof ExecutableElement) { ExecutableElement constr = (ExecutableElement) currentElement; @@ -154,7 +153,7 @@ public Tree visitNewClass(NewClassTree arg0, Element arg1) { removeEnclosingExpression = true; } } - + int index = constr.getParameters().size(); if (constr.isVarArgs()) { index--; @@ -209,7 +208,7 @@ public Tree visitNewClass(NewClassTree arg0, Element arg1) { } return super.visitNewClass(arg0, arg1); } - + private TypeElement getOuter(TypeElement element) { while (element != null && !workingCopy.getTypes().isSubtype(element.asType(),outer.asType())) { element = workingCopy.getElementUtilities().enclosingTypeElement(element); @@ -231,7 +230,7 @@ public Tree visitMethod(MethodTree constructor, Element element) { rewrite(superCall, newSuperCall); } } - + } return super.visitMethod(constructor, element); } @@ -242,26 +241,26 @@ public Tree visitClass(ClassTree classTree, Element element) { if (currentElement == null) { return super.visitClass(classTree, element); } - GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy); // helper + GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy); // helper if (currentElement!=null && currentElement == outer) { Element outerouter = outer.getEnclosingElement(); Tree superVisit = super.visitClass(classTree, element); - TreePath tp = workingCopy.getTrees().getPath(inner); - if (tp==null) { + TreePath innerTp = workingCopy.getTrees().getPath(inner); + if (innerTp==null) { //#194346 return superVisit; } - ClassTree innerClass = (ClassTree) tp.getLeaf(); + ClassTree innerClass = (ClassTree) innerTp.getLeaf(); ClassTree newInnerClass = innerClass; newInnerClass = genUtils.importComments(newInnerClass, workingCopy.getCompilationUnit()); newInnerClass = make.setLabel(newInnerClass, refactoring.getClassName()); - + newInnerClass = refactorInnerClass(newInnerClass); - + TreePath outerPath = workingCopy.getTrees().getPath(outer); - + if (outerouter.getKind() == ElementKind.PACKAGE) { FileObject sourceRoot=ClassPath.getClassPath(workingCopy.getFileObject(), ClassPath.SOURCE).findOwnerRoot(workingCopy.getFileObject()); ClassTree outerTree = (ClassTree) workingCopy.getTrees().getTree(outer); @@ -270,7 +269,7 @@ public Tree visitClass(ClassTree classTree, Element element) { if(outerPath != null) { JavaRefactoringUtils.cacheTreePathInfo(outerPath, workingCopy); } - CompilationUnitTree compilationUnit = tp.getCompilationUnit(); + CompilationUnitTree compilationUnit = innerTp.getCompilationUnit(); String relativePath = RefactoringUtils.getPackageName(compilationUnit).replace('.', '/') + '/' + refactoring.getClassName() + ".java"; // NOI18N CompilationUnitTree newCompilation = JavaPluginUtils.createCompilationUnit(sourceRoot, relativePath, newInnerClass, workingCopy, make); rewrite(null, newCompilation); @@ -290,53 +289,53 @@ public Tree visitClass(ClassTree classTree, Element element) { rewrite(outerouterTree, newOuterOuter); return newOuterOuter; } - + } else if (refactoring.getReferenceName() != null && currentElement!=null && workingCopy.getTypes().isSubtype(currentElement.asType(), inner.asType()) && currentElement!=inner) { - VariableTree variable = make.Variable(make.Modifiers(Collections.emptySet()), refactoring.getReferenceName(), make.Type(outer.asType()), null); + VariableTree variable = make.Variable(make.Modifiers(Collections.emptySet()), refactoring.getReferenceName(), make.Type(outer.asType()), null); for (Tree member:classTree.getMembers()) { - if (member.getKind() == Tree.Kind.METHOD) { - MethodTree m = (MethodTree) member; + if (member.getKind() == Tree.Kind.METHOD) { + MethodTree m = (MethodTree) member; if (m.getReturnType()==null) { - + for( VariableTree var: m.getParameters() ) { if( var.getName().contentEquals(refactoring.getReferenceName()) ) { - problem = MoveTransformer.createProblem(problem, true, NbBundle.getMessage(InnerToOuterTransformer.class, "ERR_InnerToOuter_OuterNameClashSubtype", refactoring.getReferenceName(), refactoring.getClassName(), currentElement.getSimpleName())); - } + problem = MoveTransformer.createProblem(problem, true, NbBundle.getMessage(InnerToOuterTransformer.class, "ERR_InnerToOuter_OuterNameClashSubtype", refactoring.getReferenceName(), refactoring.getClassName(), currentElement.getSimpleName())); } + } - MethodInvocationTree superCall = (MethodInvocationTree) ((ExpressionStatementTree) m.getBody().getStatements().get(0)).getExpression(); - List newArgs = new ArrayList(superCall.getArguments()); - - MethodTree newConstructor = null; - ExpressionTree exprTree = (ExpressionTree)make.Identifier(variable.getName().toString()); - if (hasVarArgs(m)) { - int index = m.getParameters().size() - 1; - newArgs.add(index, exprTree); - newConstructor = make.insertMethodParameter(m, index, variable); - } else { - newArgs.add(exprTree); - newConstructor = make.addMethodParameter(m, variable); - } - MethodInvocationTree method = make.MethodInvocation( - Collections.emptyList(), - make.Identifier("super"), // NOI18N - newArgs); - - BlockTree block = make.insertBlockStatement(m.getBody(), 0, make.ExpressionStatement(method)); - block = make.removeBlockStatement(block, 1); - - newConstructor = make.Constructor( - make.Modifiers(newConstructor.getModifiers().getFlags(), newConstructor.getModifiers().getAnnotations()), - newConstructor.getTypeParameters(), - newConstructor.getParameters(), - newConstructor.getThrows(), - block); - - rewrite(m, newConstructor); + MethodInvocationTree superCall = (MethodInvocationTree) ((ExpressionStatementTree) m.getBody().getStatements().get(0)).getExpression(); + List newArgs = new ArrayList(superCall.getArguments()); + + MethodTree newConstructor = null; + ExpressionTree exprTree = (ExpressionTree) make.Identifier(variable.getName().toString()); + if (hasVarArgs(m)) { + int index = m.getParameters().size() - 1; + newArgs.add(index, exprTree); + newConstructor = make.insertMethodParameter(m, index, variable); + } else { + newArgs.add(exprTree); + newConstructor = make.addMethodParameter(m, variable); } + MethodInvocationTree method = make.MethodInvocation( + Collections.emptyList(), + make.Identifier("super"), // NOI18N + newArgs); + + BlockTree block = make.insertBlockStatement(m.getBody(), 0, make.ExpressionStatement(method)); + block = make.removeBlockStatement(block, 1); + + newConstructor = make.Constructor( + make.Modifiers(newConstructor.getModifiers().getFlags(), newConstructor.getModifiers().getAnnotations()), + newConstructor.getTypeParameters(), + newConstructor.getParameters(), + newConstructor.getThrows(), + block); + + rewrite(m, newConstructor); } - } + } } + } if (currentElement == inner) { try { @@ -346,7 +345,7 @@ public Tree visitClass(ClassTree classTree, Element element) { isInInnerClass = false; } } - + return super.visitClass(classTree, element); } @@ -359,7 +358,7 @@ public Tree visitCompilationUnit(CompilationUnitTree node, Element p) { for (Element privEl : this.referencedPrivateElement) { problem = MoveTransformer.createProblem(problem, false, NbBundle.getMessage(InnerToOuterRefactoringPlugin.class, "WRN_InnerToOuterRefToPrivate", privEl)); } - + Trees trees = workingCopy.getTrees(); CompilationUnitTree newNode = node; for (ImportTree imp : node.getImports()) { @@ -376,7 +375,7 @@ public Tree visitCompilationUnit(CompilationUnitTree node, Element p) { } return result; } - + private Problem problem; public Problem getProblem() { @@ -391,7 +390,7 @@ private boolean containsImport(String imp) { } return false; } - + @Override public Tree visitMemberSelect(MemberSelectTree memberSelect, Element element) { @@ -456,9 +455,9 @@ public Tree visitMemberSelect(MemberSelectTree memberSelect, Element element) { rewrite(variable.getModifiers(), make.removeModifiersModifier(variable.getModifiers(), Modifier.PRIVATE)); } } - + } - + return super.visitMemberSelect(memberSelect, element); } @@ -495,7 +494,7 @@ private boolean isThisReferenceToInner() { TypeElement encl = workingCopy.getElementUtilities().enclosingTypeElement(cur); return encl!=null && workingCopy.getTypes().isSubtype(encl.asType(), inner.asType()) ; } - + private boolean isThisReferenceToOuter() { Element cur = getCurrentElement(); if (cur==null || cur.getKind() == ElementKind.PACKAGE) { @@ -542,7 +541,7 @@ private boolean isIn(Element el) { } return false; } - + private boolean hasVarArgs(MethodTree mt) { List list = mt.getParameters(); if (list.isEmpty()) { @@ -551,7 +550,7 @@ private boolean hasVarArgs(MethodTree mt) { VariableTree vt = (VariableTree)list.get(list.size() - 1); return vt.toString().indexOf("...") != -1; // [NOI18N] [TODO] temporal hack, will be rewritten } - + private ClassTree refactorInnerClass(ClassTree innerClass) { ClassTree newInnerClass = innerClass; String referenceName = refactoring.getReferenceName(); @@ -568,11 +567,13 @@ private ClassTree refactorInnerClass(ClassTree innerClass) { } else { outerType = outer.asType(); } + // for none static inner classes, add a member to point to the 'old' outer. + // because there has to be an explicit member after refactoring. if (referenceName != null) { VariableTree variable = make.Variable(make.Modifiers(EnumSet.of(Modifier.PRIVATE, Modifier.FINAL)), refactoring.getReferenceName(), make.Type(outerType), null); newInnerClass = genUtils.insertClassMember(newInnerClass, variable); } - + ModifiersTree modifiersTree = newInnerClass.getModifiers(); ModifiersTree newModifiersTree = make.removeModifiersModifier(modifiersTree, Modifier.PRIVATE); newModifiersTree = make.removeModifiersModifier(newModifiersTree, Modifier.STATIC); @@ -582,16 +583,17 @@ private ClassTree refactorInnerClass(ClassTree innerClass) { } rewrite(modifiersTree, newModifiersTree); + // create constructor which refers with member to old outer. if (referenceName != null) { for (Tree member:newInnerClass.getMembers()) { if (member.getKind() == Tree.Kind.METHOD) { - MethodTree m = (MethodTree) member; - if (m.getName().contentEquals("") || m.getReturnType() == null) { - VariableTree parameter = make.Variable(make.Modifiers(EnumSet.of(Modifier.FINAL)), refactoring.getReferenceName(), make.Type(outerType), null); - MethodTree newConstructor = hasVarArgs(m) ? - make.insertMethodParameter(m, m.getParameters().size() - 1, parameter) : - make.addMethodParameter(m, parameter); - + MethodTree ctor = (MethodTree) member; + if (ctor.getName().contentEquals("") || ctor.getReturnType() == null) { + VariableTree parameter = make.Variable(make.Modifiers(EnumSet.of(Modifier.FINAL)), referenceName, make.Type(outerType), null); + MethodTree newConstructor = hasVarArgs(ctor) ? + make.insertMethodParameter(ctor, ctor.getParameters().size() - 1, parameter) : + make.addMethodParameter(ctor, parameter); + AssignmentTree assign = make.Assignment(make.Identifier("this."+referenceName), make.Identifier(referenceName)); // NOI18N BlockTree block = make.insertBlockStatement(newConstructor.getBody(), 1, make.ExpressionStatement(assign)); Set modifiers = EnumSet.noneOf(Modifier.class); @@ -599,20 +601,20 @@ private ClassTree refactorInnerClass(ClassTree innerClass) { modifiers.remove(Modifier.PRIVATE); newConstructor = make.Constructor( make.Modifiers(modifiers,newConstructor.getModifiers().getAnnotations()), - newConstructor.getTypeParameters(), + newConstructor.getTypeParameters(), newConstructor.getParameters(), newConstructor.getThrows(), block); - newInnerClass = make.removeClassMember(newInnerClass, m); - genUtils.copyComments(m, newConstructor, true); - genUtils.copyComments(m, newConstructor, false); + newInnerClass = make.removeClassMember(newInnerClass, ctor); + genUtils.copyComments(ctor, newConstructor, true); + genUtils.copyComments(ctor, newConstructor, false); newInnerClass = genUtils.insertClassMember(newInnerClass, newConstructor); } } } } - + if(innerClass != newInnerClass) { genUtils.copyComments(innerClass, newInnerClass, true); genUtils.copyComments(innerClass, newInnerClass, false); diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java index 5377f15eb677..a0eea7f238b1 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/JavaPluginUtils.java @@ -523,8 +523,11 @@ private static String guessLiteralName(String str) { public static CompilationUnitTree createCompilationUnit(FileObject sourceRoot, String relativePath, Tree typeDecl, WorkingCopy workingCopy, TreeMaker make) { GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy); CompilationUnitTree newCompilation; + ElementKind clzOrRecord = ElementKind.CLASS; + if (typeDecl.getKind()== Kind.RECORD) + clzOrRecord=ElementKind.RECORD; try { - newCompilation = genUtils.createFromTemplate(sourceRoot, relativePath, ElementKind.CLASS); + newCompilation = genUtils.createFromTemplate(sourceRoot, relativePath, clzOrRecord);//ElementKind.CLASS); List typeDecls = newCompilation.getTypeDecls(); if (typeDecls.isEmpty()) { newCompilation = make.addCompUnitTypeDecl(newCompilation, typeDecl); diff --git a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/RefactoringVisitor.java b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/RefactoringVisitor.java index 357fb4b64047..844f77025c9e 100644 --- a/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/RefactoringVisitor.java +++ b/java/refactoring.java/src/org/netbeans/modules/refactoring/java/spi/RefactoringVisitor.java @@ -183,6 +183,7 @@ public Tree scan(Tree tree, Element p) { case CLASS: case ENUM: case INTERFACE: + case RECORD: case VARIABLE: TreePath path = new TreePath(currentPath, tree); scanJavadoc(path, p); @@ -456,6 +457,7 @@ public DocTree visitVersion(VersionTree node, Element p) { return docScanner.visitVersion(node, p, null); } + /** * @since 1.47 */ diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/InnerOuterRecordTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/InnerOuterRecordTest.java new file mode 100644 index 000000000000..c539c5d2044f --- /dev/null +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/InnerOuterRecordTest.java @@ -0,0 +1,686 @@ +/* + * 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.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Name; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; +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.TreePathHandle; +import org.netbeans.modules.refactoring.api.Problem; +import org.netbeans.modules.refactoring.api.RefactoringSession; +import org.netbeans.modules.refactoring.java.api.InnerToOuterRefactoring; +import static org.netbeans.modules.refactoring.java.test.RefactoringTestBase.addAllProblems; +import org.openide.util.Exceptions; + +/** + * Test inner to outer refactoring for test. + * + * In the input files, and the expected outcomes, the indentation does not + * really matter as far as the tests are concerned because the indentation is + * stripped away before the remaining source lines are compared to the expected + * lines. + * + * @author homberghp {@code } + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class InnerOuterRecordTest extends RefactoringTestBase { + + public InnerOuterRecordTest(String name) { + super(name, "16"); + //ensure we are running on at least 16. + try { + SourceVersion.valueOf("RELEASE_16"); //NOI18N + } catch (IllegalArgumentException ex) { + //OK, no RELEASE_16, skip test + throw new RuntimeException("need at least Java 16 for record"); + } + sideBySideCompare = true; + } + + public void test9ApacheNetbeans7044() throws Exception { + // initial outer has record with meaningful canonical constructor. + // note that Inner class should be in last member for assumptions in the test. + String source + = """ + package t; + import java.time.LocalDate; + import java.util.Objects; + public class A { + void useStudent() { + F s = new F(42,"Jan Klaassen", LocalDate.now().minusDays(1)); + System.out.println("student = " + s); + } + record F(int id, String name, LocalDate dob) { + /** + * Validate stuff. + */ + public F { + Objects.requireNonNull(id); + Objects.requireNonNull(name); + Objects.requireNonNull(dob); + assert !name.isEmpty() && !name.isBlank(); + assert dob.isAfter(LocalDate.EPOCH); + } + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + import java.util.Objects; + public class A { + void useStudent() { + F s = new F(42,"Jan Klaassen", LocalDate.now().minusDays(1)); + System.out.println("student = " + s); + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + import java.time.LocalDate; + import java.util.Objects; + /** + * + * @author junit + */ + record F(int id, String name, LocalDate dob) { + /** + * Validate stuff. + */ + public F { + Objects.requireNonNull(id); + Objects.requireNonNull(name); + Objects.requireNonNull(dob); + assert !name.isEmpty() && !name.isBlank(); + assert dob.isAfter(LocalDate.EPOCH); + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void test1BasicClassInClass() throws Exception { + // initial outer has record with meaningful canonical constructor. + String source + = """ + package t; + import java.time.LocalDate; + import java.util.Objects; + public class A { + void useStudent() { + F s = new F(42, "Jan Klaassen", LocalDate.now().minusDays(1)); + System.out.println("student = " + s); + } + public static class F { + int id; + String name; + LocalDate dob + public Student(int id, String name, LocalDate dob) { + Objects.requireNonNull(id); + Objects.requireNonNull(name); + Objects.requireNonNull(dob); + assert !name.isEmpty() && !name.isBlank(); + assert dob.isAfter(LocalDate.EPOCH); + this.id=id; + this.name=name; + this.dob=dob; + } + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + import java.util.Objects; + public class A { + void useStudent() { + F s = new F(42, "Jan Klaassen", LocalDate.now().minusDays(1)); + System.out.println("student = " + s); + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + import java.time.LocalDate; + import java.util.Objects; + /** + * + * @author junit + */ + public class F { + int id; + String name; + LocalDate dob; + public F(int id, String name, LocalDate dob) { + Objects.requireNonNull(id); + Objects.requireNonNull(name); + Objects.requireNonNull(dob); + assert !name.isEmpty() && !name.isBlank(); + assert dob.isAfter(LocalDate.EPOCH); + this.id = id; + this.name = name; + this.dob = dob; + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void test2BasicRecordInRecord() throws Exception { + String source + = """ + package t; + import java.time.LocalDate; + record A(int id, String name, LocalDate dob) { + static F f; + record F(int x, int y){ + /** I should be back. */ + static String code = "nix"; + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + record A(int id, String name, LocalDate dob) { + static F f; + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + /** + * + * @author hom + */ + record F(int x, int y) { + /** I should be back. */ + static String code = "nix"; + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + /** + * Test to verify what happens to the compact constructor in the outer + * record. It appears to survive the refactoring. + * + * @throws Exception + */ + public void test3OuterWithCompact() throws Exception { + String source + = """ + package t; + import java.time.LocalDate; + /** Record with compact ctor. */ + record A(F f){ + public A{ + assert f!=null; + } + record F(int id, String name, LocalDate dob){} + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + /** Record with compact ctor. */ + record A(F f){ + public A{ + assert f!=null; + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + import java.time.LocalDate; + /** + * + * @author junit + */ + record F(int id, String name, LocalDate dob) { + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void test4InnerWithCompact() throws Exception { + String source + = """ + package t; + import java.time.LocalDate; + record A(F f) { + public A { + assert f != null; + } + record F(int id, String name, LocalDate dob) { + public F { + if (dob.isBefore(LocalDate.EPOCH)) { + throw new IllegalArgumentException("to old " + dob); + } + } + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + record A(F f) { + public A { + assert f != null; + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + import java.time.LocalDate; + /** + * + * @author junit + */ + record F(int id, String name, LocalDate dob) { + public F { + if (dob.isBefore(LocalDate.EPOCH)) { + throw new IllegalArgumentException("to old " + dob); + } + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + // outer may have effect + public void test5ClassWithInnerRecord() throws Exception { + String source + = """ + package t; + import java.time.LocalDate; + class A { + final F f; + public A(F f) { + assert f != null; + this.f=f; + } + record F(int id, String name, LocalDate dob) { + public F { + if (dob.isBefore(LocalDate.EPOCH)) { + throw new IllegalArgumentException("to old " + dob); + } + } + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + class A { + final F f; + public A(F f) { + assert f != null; + this.f=f; + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + import java.time.LocalDate; + /** + * + * @author junit + */ + record F(int id, String name, LocalDate dob) { + public F { + if (dob.isBefore(LocalDate.EPOCH)) { + throw new IllegalArgumentException("to old " + dob); + } + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void test6InnerWithCompactAndMethodAndExtraCtor() throws Exception { + String source + = """ + package t; + import java.time.LocalDate; + record A(F f) { + enum Suite { + SPADE, CLUB, DIAMOND, HEART; + } + public A { + assert f != null; + } + record F(int id, String name, LocalDate dob) { + public F { + if (dob.isBefore(LocalDate.EPOCH)) { + throw new IllegalArgumentException("to old " + dob); + } + } + public F(int id, String name){ + this(id,name,LocalDate.now()); + } + boolean bornBefore(LocalDate someDate) { + return dob.isBefore(someDate); + } + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + record A(F f) { + enum Suite { + SPADE, CLUB, DIAMOND, HEART; + } + public A { + assert f != null; + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + import java.time.LocalDate; + /** + * + * @author junit + */ + record F(int id, String name, LocalDate dob) { + public F { + if (dob.isBefore(LocalDate.EPOCH)) { + throw new IllegalArgumentException("to old " + dob); + } + } + public F(int id, String name) { + this(id, name, LocalDate.now()); + } + boolean bornBefore(LocalDate someDate) { + return dob.isBefore(someDate); + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void test7Generic() throws Exception { + String source + = """ + package t; + record A(F f) { + public A { + assert f != null; + } + record F(P first, Q second) { + public F { + assert null != first; + assert null != second; + } + } + } + """; + String newOuter + = """ + package t; + record A(F f) { + public A { + assert f != null; + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + /** + * + * @author junit + */ + record F(P first, Q second) { + public F { + assert null != first; + assert null != second; + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void test8Varargs() throws Exception { + RETRIES=0; + debug = true; + String source + = """ + package t; + record A(F f) { + public A { + assert f != null; + } + record F

    (P first, String... second) { + public F { + assert null != first; + assert null != second; + } + } + } + """; + String newOuter + = """ + package t; + record A(F f) { + public A { + assert f != null; + } + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + /** + * + * @author junit + */ + record F

    (P first, String... second) { + public F { + assert null != first; + assert null != second; + } + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + public void _test8RecordImplements() throws Exception { + debug = true; + sideBySideCompare=true; + String source + = """ + package t; + import java.time.LocalDate; + record A(int id, String name, LocalDate dob) implements Cloneable { + static F f; + record F(int x, int y) implements Cloneable { + /** I should be back. */ + static String code = "nix"; + } + } + """; + String newOuter + = """ + package t; + import java.time.LocalDate; + record A(int id, String name, LocalDate dob) implements Cloneable { + static F f; + } + """; + String newInner + = """ + /* + * Refactoring License + */ + package t; + /** + * + * @author hom + */ + record F(int x, int y) implements Cloneable { + /** I should be back. */ + static String code = "nix"; + } + """; + innerOuterSetupAndTest(source, newOuter, newInner); + } + + void innerOuterSetupAndTest(String source, String newOuterName, String newInnerName) throws Exception { + writeFilesNoIndexing(src, new File("t/A.java", source)); + performInnerToOuterTest2(null); + verifyContent(src, new File("t/A.java", newOuterName), new File("t/F.java", newInnerName)); + } + boolean debug = false; + + // variant for record inner to outer test + private void performInnerToOuterTest2(String newOuterName, Problem... expectedProblems) throws Exception { + final InnerToOuterRefactoring[] r = new InnerToOuterRefactoring[1]; + JavaSource.forFileObject(src.getFileObject("t/A.java")).runUserActionTask(new Task() { + @Override + public void run(CompilationController parameter) { + try { + parameter.toPhase(JavaSource.Phase.RESOLVED); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + CompilationUnitTree cut = parameter.getCompilationUnit(); + if (debug) { + System.err.println("cut is of type " + cut.getClass().getCanonicalName()); + } + ClassTree outer = (ClassTree) cut.getTypeDecls().get(0); + if (debug) { + printNumbered(System.err, "start source " + outer.getKind().toString(), outer.toString()); + } + List members = outer.getMembers(); + int m = 0; + if (debug) { + printMembers(members, m); + } + // selecting the last element assumes that the inner class is the last member in the outer class. + Tree lastInnerClass + = outer.getMembers().get(outer.getMembers().size() - 1); + if (debug && lastInnerClass instanceof ClassTree lct) { +// String n = "lastInnerClass " + lastInnerClass.getKind().toString(); +// printNumbered(System.err, n, lastInnerClass.toString()); + printClassTree(lct); + } + TreePath tp = TreePath.getPath(cut, lastInnerClass); + try { + r[0] + = new InnerToOuterRefactoring(TreePathHandle.create(tp, parameter)); + } catch (Throwable t) { + System.err.println("InnerOuter refatoring failed with exception " + t); + t.printStackTrace(System.out); + throw t; + } + } + }, true); + r[0].setClassName("F"); + if (debug) { + printNumbered(System.err, "result ", r[0].toString()); + } + r[0].setReferenceName(newOuterName); + RefactoringSession rs = RefactoringSession.create("Session"); + List problems = new LinkedList(); + addAllProblems(problems, r[0].preCheck()); + addAllProblems(problems, r[0].prepare(rs)); + addAllProblems(problems, rs.doRefactoring(true)); + assertProblems(Arrays.asList(expectedProblems), problems); + } + + // test helper + static void printMembers(List members, int m) { + printMembers(members, m, ""); + } + + // test helper + static void printMembers(List members, int m, String indent) { + for (Tree member : members) { + printNumbered(System.err, indent + "member %d %15s".formatted(m, member.getKind()), member.toString()); + String toString = member.toString(); + if (member instanceof ClassTree ct) { + int n = 0; + Name simpleName = ct.getSimpleName(); + List members1 = ct.getMembers(); + printMembers(members1, n, indent + " " + m + " "); + } + m++; + } + } + + // test helper + static void printClassTree(ClassTree ct) { + printMembers(ct.getMembers(), 0, "class " + ct.getSimpleName() + " type " + ct.getKind() + " "); + } +} diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/PullUpTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/PullUpTest.java index fb7cde7eda01..e9b32f1b366a 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/PullUpTest.java +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/PullUpTest.java @@ -51,12 +51,13 @@ public class PullUpTest extends RefactoringTestBase { public PullUpTest(String name) { super(name, "1.8"); + RETRIES=1; } - + static { JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true; } - + public void test241514a() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public interface A { void x(); }"), @@ -66,7 +67,7 @@ public void test241514a() throws Exception { new File("pullup/A.java", "package pullup; public interface A { void x(); void y(); }"), new File("pullup/B.java", "package pullup; public interface B extends A { default void y() { } }")); } - + public void test241514b() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public interface A { void x(); }"), @@ -76,7 +77,7 @@ public void test241514b() throws Exception { new File("pullup/A.java", "package pullup; public interface A { void x(); default void y() { } }"), new File("pullup/B.java", "package pullup; public interface B extends A {}")); } - + public void testPullUpOverridingMethod() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public class A extends B implements C { @Override public void i() { } }"), @@ -87,7 +88,7 @@ public void testPullUpOverridingMethod() throws Exception { new File("pullup/A.java", "package pullup; public class A extends B implements C {}"), new File("pullup/B.java", "package pullup; public class B { public void i() { } }"), new File("pullup/C.java", "package pullup; public interface C { void i(); }")); - + writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public class A extends B { @Override public void i() { } }"), new File("pullup/B.java", "package pullup; public class B extends C { }"), @@ -98,7 +99,7 @@ public void testPullUpOverridingMethod() throws Exception { new File("pullup/B.java", "package pullup; public class B extends C { @Override public void i() { } }"), new File("pullup/C.java", "package pullup; public class C { public void i() { } }")); } - + public void test230719() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public interface A {\n" @@ -127,14 +128,14 @@ public void test230719() throws Exception { + " }\n" + "}")); } - + public void test230930() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public interface A { }"), new File("pullup/B.java", "package pullup; public class B implements A { static void y(); }")); performPullUpIface(src.getFileObject("pullup/B.java"), 0, 0, true); } - + public void test229061() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public interface A { void x(); }"), @@ -154,7 +155,7 @@ public void test134034() throws Exception { new File("pullup/A.java", "package pullup; public class A { void x() { } void y() { x(); } }"), new File("pullup/B.java", "package pullup; public class B extends A {}")); } - + public void test212934() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public class A { }"), @@ -181,7 +182,7 @@ public void testPullUpField() throws Exception { new File("pullup/A.java", "package pullup; public class A extends B {}"), new File("pullup/B.java", "package pullup; public class B { public int i; }")); } - + public void testPullUpGenMethoda() throws Exception { // #147508 - [Pull Up][Push down] Remap generic names writeFilesAndWaitForScan(src, new File("pullup/PullUpBaseClass.java", "package pullup;\n" @@ -303,7 +304,7 @@ public void testPullUpGenMethodb() throws Exception { new File("pullup/B.java", "package pullup; public class B extends C { }"), new File("pullup/C.java", "package pullup; public class C { void method(String x) { } }")); } - + public void testPullUpGenMethodc() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public class A extends B { X method() { } }"), @@ -325,7 +326,7 @@ public void testPullUpGenMethodc() throws Exception { new File("pullup/B.java", "package pullup; public class B extends C { }"), new File("pullup/C.java", "package pullup; public class C { void method(String x) { } }")); } - + public void testPullUpGenField() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public class A extends B { X x; }"), @@ -447,7 +448,7 @@ public void testPullUpMethod() throws Exception { + "\n" + "}")); } - + public void testPullUpMethodUndo() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/PullUpBaseClass.java", "package pullup;\n" @@ -1166,7 +1167,7 @@ public void testPullUpInterface() throws Exception { new File("pullup/A.java", "package pullup; public class A extends B { public void run() { } }"), new File("pullup/B.java", "package pullup; public class B implements Runnable { }")); } - + public void testPullUpInterface2() throws Exception { writeFilesAndWaitForScan(src, new File("pullup/A.java", "package pullup; public class A implements B { }"), @@ -1206,6 +1207,254 @@ public void testPullUpLocalyReferenced() throws Exception { new File("pullup/B.java", "package pullup; public class B { protected void foo() { } }")); } + public void testPullUpInnerTypeEnum() throws Exception { + + writeFilesAndWaitForScan(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + private void foo() { + } + enum Suite implements I{ Heart, Diamond, Club, Spade; } + private void method(Suite s) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { } + """ + ), + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + ); + performPullUp(src.getFileObject("pullup/A.java"), 2, Boolean.FALSE); + verifyContent(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + private void foo() { + } + private void method(Suite s) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { + + enum Suite implements I { + Heart, Diamond, Club, Spade + } + } + """ + ) , + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + + ); + } + + public void testPullUpInnerSimpleRecord() throws Exception { + sideBySideCompare=true; + writeFilesAndWaitForScan(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + record R( int i, String name ) {} + private void foo() { + } + private void method(R r) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { } + """ + ), + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + ); + performPullUp(src.getFileObject("pullup/A.java"), 1, Boolean.FALSE); + verifyContent(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + private void foo() { + } + private void method(R r) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { + + record R(int i, String name) { + } + } + """ + ) , + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + + ); + } + + public void testPullUpVarargRecord() throws Exception { + sideBySideCompare=true; + writeFilesAndWaitForScan(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + record R( int i, String... name ) {} + private void foo() { + } + private void method(R r) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { } + """ + ), + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + ); + performPullUp(src.getFileObject("pullup/A.java"), 1, Boolean.FALSE); + verifyContent(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + private void foo() { + } + private void method(R r) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { + + record R(int i, String... name) { + } + } + """ + ) , + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + + ); + } + + // disable because implements part is broken + public void _testPullUpInnerRecord() throws Exception { + sideBySideCompare=true; + writeFilesAndWaitForScan(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + record R( int i, String name ) implements I {} + private void foo() { + } + private void method(R r) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { } + """ + ), + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + ); + String asText = src.getFileObject("pullup/A.java").asText(); + System.out.println("asText = " + asText); + performPullUp(src.getFileObject("pullup/A.java"), 1, Boolean.FALSE); + verifyContent(src, + new File("pullup/A.java", + """ + package pullup; + public class A extends B { + private void foo() { + } + private void method(R r) { + foo(); + } + }""" + ), + new File("pullup/B.java", + """ + package pullup; + public class B { + + record R(int i, String name) implements I { + } + } + """ + ) , + new File("pullup/I.java", + """ + package pullup; + public interface I{ } + """ + ) + + ); + } + private void performPullUpImplements(FileObject source, final int position, final int supertype, Problem... expectedProblems) throws IOException, IllegalArgumentException, InterruptedException { final PullUpRefactoring[] r = new PullUpRefactoring[1]; JavaSource.forFileObject(source).runUserActionTask(new Task() { @@ -1300,8 +1549,9 @@ private void performPullUp(FileObject source, final int position, final Boolean public void run(CompilationController info) throws Exception { info.toPhase(JavaSource.Phase.RESOLVED); CompilationUnitTree cut = info.getCompilationUnit(); - + final ClassTree classTree = (ClassTree) cut.getTypeDecls().get(0); + System.err.println("compiled to classTree = " + classTree); final TreePath classPath = info.getTrees().getPath(cut, classTree); TypeElement classEl = (TypeElement) info.getTrees().getElement(classPath); diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java index ebc261b08126..d916e4628e3d 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RefactoringTestBase.java @@ -21,6 +21,7 @@ import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; @@ -31,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.ChangeListener; @@ -78,13 +80,44 @@ public RefactoringTestBase(String name) { super(name); sourcelevel = "1.6"; } - + public RefactoringTestBase(String name, String sourcelevel) { super(name); this.sourcelevel = sourcelevel; } + static boolean debug = false; +// static boolean skipIndexing = false; + + /** + * Write given files to sourceRoot and fully re index. + * + * First the (file) children of sourceRoot are deleted. This method can take + * a substantial time of your patience, so use wisely. See the doc in + * {@link IndexManager#refreshIndexAndWait} + * + * @param sourceRoot sic + * @param files to save + * @throws Exception whenever + */ protected static void writeFilesAndWaitForScan(FileObject sourceRoot, File... files) throws Exception { + writeFilesAndWaitForScan(true, sourceRoot, files); + } + + /** + * Write given files to sourceRoot and possibly reindex. + * + * First the (file) children of sourceRoot are deleted. This method can take + * a substantial time of your patience, so use wisely. See the doc in + * {@link IndexManager#refreshIndexAndWait} + * + * @param fulleIndex fully reindex the type repo + * @param sourceRoot sic + * @param files to save + * @throws Exception whenever + */ + protected static void writeFilesAndWaitForScan(boolean fullIndex, FileObject sourceRoot, File... files) throws Exception { + long currentTimeMillis = System.currentTimeMillis(); for (FileObject c : sourceRoot.getChildren()) { c.delete(); } @@ -93,10 +126,39 @@ protected static void writeFilesAndWaitForScan(FileObject sourceRoot, File... fi FileObject fo = FileUtil.createData(sourceRoot, f.filename); TestUtilities.copyStringToFile(fo, f.content); } + + if (fullIndex) { + IndexingManager.getDefault().refreshIndexAndWait(sourceRoot.toURL(), null, true); + long currentTimeMillis1 = System.currentTimeMillis(); + if (debug) { + System.err.println("writeFilesAndWaitForScan took " + (currentTimeMillis1 - currentTimeMillis) + " millis"); + } + } + } - IndexingManager.getDefault().refreshIndexAndWait(sourceRoot.toURL(), null, true); + /** + * Save file but do not reindex. + * + * Deletes the existing files under sourceRoot, then saves the given files. + * + * Makes tests run faster. In particular single tests. + * + * @param sourceRoot sic + * @param files to save + * @throws Exception whenever + */ + protected static void writeFilesNoIndexing(FileObject sourceRoot, File... files) throws Exception { + writeFilesAndWaitForScan(false, sourceRoot, files); } + /** + * Verify that the given file(names) are present in the sourceRoot and that + * the files in said sourceRoot are equal to the given files. + * + * @param sourceRoot to contain generated (refactored) files + * @param files expected files + * @throws Exception well why not? + */ protected void verifyContent(FileObject sourceRoot, File... files) throws Exception { List todo = new LinkedList(); @@ -109,33 +171,30 @@ protected void verifyContent(FileObject sourceRoot, File... files) throws Except while (!todo.isEmpty()) { FileObject file = todo.remove(0); - if (file.isData()) { + if (file.isData()) { // normal file content.put(FileUtil.getRelativePath(sourceRoot, file), copyFileToString(FileUtil.toFile(file))); - } else { + } else { // it is a folder todo.addAll(Arrays.asList(file.getChildren())); } } - + Throwable exception = null; for (File f : files) { + // take the element from the map filled by sourceRootTraversal. String fileContent = content.remove(f.filename); - assertNotNull(f); assertNotNull(f.content); - assertNotNull("Cannot find " + f.filename + " in map " + content, fileContent); - try { - assertEquals(getName() ,f.content.replaceAll("[ \t\r\n\n]+", " "), fileContent.replaceAll("[ \t\r\n\n]+", " ")); - } catch (Throwable t) { - System.err.println("expected:"); - System.err.println(f.content); - System.err.println("actual:"); - System.err.println(fileContent); - throw t; + assertNotNull("Cannot find expected " + f.filename + " in map filled by sourceRoot " + content, fileContent); + if (sideBySideCompare) { + assertLinesEqual2(f.filename, f.content, fileContent); + } else { // original tests. + assertLinesEqual1(f.content, fileContent); } } - - assertTrue(content.toString(), content.isEmpty()); + assertTrue("not all files processeed", content.isEmpty()); } + protected boolean sideBySideCompare=false; + /** * Returns a string which contains the contents of a file. * @@ -366,10 +425,10 @@ private void prepareTest() throws Exception { src = FileUtil.createFolder(projectFolder, "src"); test = FileUtil.createFolder(projectFolder, "test"); - FileObject cache = FileUtil.createFolder(workdir, "cache"); + FileObject cache = FileUtil.createFolder(workdir, "cache"); - CacheFolder.setCacheFolder(cache); - } + CacheFolder.setCacheFolder(cache); + } @ServiceProvider(service=MimeDataProvider.class) public static final class MimeDataProviderImpl implements MimeDataProvider { @@ -384,7 +443,7 @@ public Lookup getLookup(MimePath mimePath) { return null; } - + } protected static boolean problemIsFatal(List problems) { @@ -400,7 +459,7 @@ protected static boolean problemIsFatal(List problems) { return false; } - private static final int RETRIES = 3; + protected int RETRIES = 3; @Override protected void runTest() throws Throwable { @@ -411,10 +470,96 @@ protected void runTest() throws Throwable { super.runTest(); return; } catch (Throwable t) { - if (exc == null) exc = t; + if (exc == null) { + exc = t; + } } } - throw exc; + if (exc != null) { + throw exc; + } } -} \ No newline at end of file + /** + * 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 whitrespace([ \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 void assertLinesEqual1(String expected, String actual) { + try { + assertEquals(getName(), 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 expected to compare + * @param actual to compare + */ + public void assertLinesEqual2(String name, String expected, String actual) { + expected = expected.trim().replaceAll("([\t\r\n])\\1+", "$1"); + actual = actual.trim().replaceAll("([\t\r\n])\\1+", "$1"); + String[] linesExpected = expected.lines().filter(l-> !l.isEmpty()).toArray(String[]::new); + String[] linesActual = actual.lines().filter(l-> !l.isEmpty()).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= oe.trim(); + String a= oa.trim(); + // 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(name + " [%3d] %-80s%s%-80s%n", i, oe, sep, oa)); + } + if (!equals) { + System.err.println("test " + getName() + " failed"); + System.err.print(String.format(name + " %-80s%s%-80s%n", "expected", " + ", "actual")); + System.err.println(sb.toString()); + fail("lines differ, see stderr for more details."); + } + } +} diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java index 43fbba7faa72..da6e5ae7f020 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameRecordTest.java @@ -344,7 +344,7 @@ public void testRenameRecord() throws Exception { package test; public record Te|st(int component) { public Test { - component = ""; + component = 42; } } """; @@ -367,7 +367,7 @@ private Test test() { package test; public record NewName(int component) { public NewName { - component = ""; + component = 42; } } """), @@ -381,6 +381,38 @@ private NewName test() { } """)); + } + + public void testRenameInnerRecord() throws Exception { + String testCode = """ + package test; + public record Test(Component component) { + public Test { + assert component !=null; + } + record Comp|onent(int c){ + assert c >= 0; + } + } + """; + TestInput splitCode = TestUtilities.splitCodeAndPos(testCode); + writeFilesAndWaitForScan(src, + new File("Test.java", splitCode.code())); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("Test.java"), splitCode.pos(), "Part", props, true); + verifyContent(src, new File("Test.java", + """ + package test; + public record Test(Part component) { + public Test { + assert component !=null; + } + record Part(int c){ + assert c >= 0; + } + } + """)); + } 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];