diff --git a/java.source/nbproject/project.properties b/java.source/nbproject/project.properties --- a/java.source/nbproject/project.properties +++ b/java.source/nbproject/project.properties @@ -46,7 +46,7 @@ javadoc.title=Java Source javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=0.65.0 +spec.version.base=0.66.0 test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/javac-api-nb-7.0-b07.jar test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\ ${o.n.core.dir}/lib/boot.jar:\ diff --git a/java.source/src/org/netbeans/api/java/source/TreeMaker.java b/java.source/src/org/netbeans/api/java/source/TreeMaker.java --- a/java.source/src/org/netbeans/api/java/source/TreeMaker.java +++ b/java.source/src/org/netbeans/api/java/source/TreeMaker.java @@ -52,11 +52,11 @@ import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; -import com.sun.source.util.TreePathScanner; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCModifiers; +import java.util.Collections; import java.util.HashMap; import javax.lang.model.element.*; import javax.lang.model.type.*; @@ -71,16 +71,13 @@ import org.netbeans.api.java.lexer.JavaTokenId; -import org.netbeans.modules.java.source.query.CommentHandler; import org.netbeans.modules.java.source.query.CommentSet; -import org.netbeans.api.java.source.Comment.Style; import org.netbeans.modules.java.source.query.CommentHandler; import org.netbeans.modules.java.source.builder.CommentHandlerService; import org.netbeans.modules.java.source.builder.TreeFactory; import org.netbeans.modules.java.source.save.PositionEstimator; import org.openide.util.Parameters; -import static org.netbeans.modules.java.source.save.PositionEstimator.*; /** * Factory interface for creating new com.sun.source.tree instances. The @@ -321,10 +318,29 @@ * @see com.sun.source.tree.CompilationUnitTree */ public CompilationUnitTree CompilationUnit(ExpressionTree packageName, + List imports, + List typeDeclarations, + JavaFileObject sourceFile) { + return delegate.CompilationUnit(Collections.emptyList(), packageName, imports, typeDeclarations, sourceFile); + } + + /** + * Creates a new CompilationUnitTree. + * + * @param packageAnnotations package annotations + * @param packageName a tree representing the package name. + * @param imports a list of import statements. + * @param typeDeclarations a list of type (class, interface or enum) declarations. + * @param sourceFile the source file associated with this compilation unit. + * @see com.sun.source.tree.CompilationUnitTree + * @since 0.66 + */ + public CompilationUnitTree CompilationUnit(List packageAnnotations, + ExpressionTree packageName, List imports, List typeDeclarations, JavaFileObject sourceFile) { - return delegate.CompilationUnit(packageName, imports, typeDeclarations, sourceFile); + return delegate.CompilationUnit(packageAnnotations, packageName, imports, typeDeclarations, sourceFile); } @@ -343,8 +359,29 @@ String[] nameComponent = FileObjects.getFolderAndBaseName(path,'/'); //NOI18N JavaFileObject sourceFile = FileObjects.templateFileObject(sourceRoot, nameComponent[0], nameComponent[1]); IdentifierTree pkg = nameComponent[0].length() == 0 ? null : Identifier(nameComponent[0].replace('/', '.')); - return delegate.CompilationUnit(pkg, imports, typeDeclarations, sourceFile); - } + return delegate.CompilationUnit(Collections.emptyList(), pkg, imports, typeDeclarations, sourceFile); + } + + /** + * Creates a new CompilationUnitTree. + * @param packageAnnotations package annotations + * @param sourceRoot a source root under which the new file is created + * @param path a relative path to file separated by '/' + * @param imports a list of import statements. + * @param typeDeclarations a list of type (class, interface or enum) declarations. + * @see com.sun.source.tree.CompilationUnitTree + * @since 0.66 + */ + public CompilationUnitTree CompilationUnit(List packageAnnotations, + FileObject sourceRoot, + String path, + List imports, + List typeDeclarations) { + String[] nameComponent = FileObjects.getFolderAndBaseName(path,'/'); //NOI18N + JavaFileObject sourceFile = FileObjects.templateFileObject(sourceRoot, nameComponent[0], nameComponent[1]); + IdentifierTree pkg = nameComponent[0].length() == 0 ? null : Identifier(nameComponent[0].replace('/', '.')); + return delegate.CompilationUnit(packageAnnotations, pkg, imports, typeDeclarations, sourceFile); + } /** * Creates a new CompoundAssignmentTree. @@ -1488,6 +1525,66 @@ return delegate.removeCompUnitImport(compilationUnit, index); } + /** + * Appends specified element annotation to the end of package annotations + * list. + * + * @param cut compilation unit tree containing package annotations list. + * @param annotation element to be appended to annotations list. + * @return compilation unit tree with modified package annotations. + * @since 0.66 + */ + public CompilationUnitTree addPackageAnnotation(CompilationUnitTree cut, AnnotationTree annotation) { + return delegate.addPackageAnnotation(cut, annotation); + } + + /** + * Inserts the specified element annotation at the specified + * position in package annotations list. + * + * @param cut compilation unit tree containing package annotations list. + * @param index index at which the specified element is to be inserted. + * @param annotation element to be inserted to the package annotations list. + * @return compilation unit tree with modified package annotations. + * + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > size()). + * @since 0.66 + */ + public CompilationUnitTree insertPackageAnnotation(CompilationUnitTree cut, int index, AnnotationTree annotation) { + return delegate.insertPackageAnnotation(cut, index, annotation); + } + + /** + * Removes the first occurrence in package annotations list of the specified + * element. If this list does not contain the element, it is + * unchanged. + * + * @param cut compilation unit tree containing package annotations list. + * @param annotation element to be removed from this list, if present. + * @return compilation unit tree with modified package annotations. + * @since 0.66 + */ + public CompilationUnitTree removePackageAnnotation(CompilationUnitTree cut, AnnotationTree annotation) { + return delegate.removePackageAnnotation(cut, annotation); + } + + /** + * Removes the element at the specified position in package annotations list. + * Returns the modified compilation unit tree. + * + * @param cut compilation unit tree containing package annotations list. + * @param index the index of the element to be removed. + * @return compilation unit tree with modified package annotations. + * + * @throws IndexOutOfBoundsException if the index is out of range (index + * < 0 || index >= size()). + * @since 0.66 + */ + public CompilationUnitTree removePackageAnnotation(CompilationUnitTree cut, int index) { + return delegate.removePackageAnnotation(cut, index); + } + /** ErroneousTree */ // ForLoopInitializer diff --git a/java.source/src/org/netbeans/modules/java/source/builder/TreeFactory.java b/java.source/src/org/netbeans/modules/java/source/builder/TreeFactory.java --- a/java.source/src/org/netbeans/modules/java/source/builder/TreeFactory.java +++ b/java.source/src/org/netbeans/modules/java/source/builder/TreeFactory.java @@ -73,6 +73,7 @@ import java.util.Set; import javax.lang.model.type.ArrayType; import javax.lang.model.util.Types; +import org.netbeans.api.annotations.common.NonNull; import static org.netbeans.modules.java.source.save.PositionEstimator.*; import static com.sun.tools.javac.code.Flags.*; @@ -235,11 +236,16 @@ return Class(flags, (com.sun.tools.javac.util.List) modifiers.getAnnotations(), simpleName, Collections.emptyList(), null, implementsClauses, memberDecls); } - public CompilationUnitTree CompilationUnit(ExpressionTree packageDecl, + public CompilationUnitTree CompilationUnit(@NonNull List packageAnnotations, + ExpressionTree packageDecl, List importDecls, List typeDecls, JavaFileObject sourceFile) { + ListBuffer annotations = new ListBuffer(); + for (AnnotationTree at : packageAnnotations) { + annotations.add((JCAnnotation) at); + } ListBuffer defs = new ListBuffer(); if (importDecls != null) for (Tree t : importDecls) @@ -247,8 +253,8 @@ if (typeDecls != null) for (Tree t : typeDecls) defs.append((JCTree)t); - JCCompilationUnit unit = make.at(NOPOS).TopLevel(com.sun.tools.javac.util.List.nil(), - (JCExpression)packageDecl, defs.toList()); + JCCompilationUnit unit = make.at(NOPOS).TopLevel(annotations.toList(), + (JCExpression)packageDecl, defs.toList()); unit.sourcefile = sourceFile; return unit; } @@ -916,6 +922,7 @@ private CompilationUnitTree modifyCompUnitTypeDecl(CompilationUnitTree compilationUnit, int index, Tree typeDeclaration, Operation op) { CompilationUnitTree copy = CompilationUnit( + compilationUnit.getPackageAnnotations(), compilationUnit.getPackageName(), compilationUnit.getImports(), c(compilationUnit.getTypeDecls(), index, typeDeclaration, op), @@ -943,6 +950,7 @@ private CompilationUnitTree modifyCompUnitImport(CompilationUnitTree compilationUnit, int index, ImportTree importt, Operation op) { CompilationUnitTree copy = CompilationUnit( + compilationUnit.getPackageAnnotations(), compilationUnit.getPackageName(), c(compilationUnit.getImports(), index, importt, op), compilationUnit.getTypeDecls(), @@ -951,6 +959,33 @@ return copy; } + public CompilationUnitTree addPackageAnnotation(CompilationUnitTree cut, AnnotationTree annotation) { + return modifyPackageAnnotation(cut, -1, annotation, Operation.ADD); + } + + public CompilationUnitTree insertPackageAnnotation(CompilationUnitTree cut, int index, AnnotationTree annotation) { + return modifyPackageAnnotation(cut, index, annotation, Operation.ADD); + } + + public CompilationUnitTree removePackageAnnotation(CompilationUnitTree cut, AnnotationTree annotation) { + return modifyPackageAnnotation(cut, -1, annotation, Operation.REMOVE); + } + + public CompilationUnitTree removePackageAnnotation(CompilationUnitTree cut, int index) { + return modifyPackageAnnotation(cut, index, null, Operation.REMOVE); + } + + private CompilationUnitTree modifyPackageAnnotation(CompilationUnitTree cut, int index, AnnotationTree annotation, Operation op) { + CompilationUnitTree copy = CompilationUnit( + c(cut.getPackageAnnotations(), index, annotation, op), + cut.getPackageName(), + cut.getImports(), + cut.getTypeDecls(), + cut.getSourceFile() + ); + return copy; + } + /** ErroneousTree */ // ForLoop diff --git a/java.source/src/org/netbeans/modules/java/source/pretty/VeryPretty.java b/java.source/src/org/netbeans/modules/java/source/pretty/VeryPretty.java --- a/java.source/src/org/netbeans/modules/java/source/pretty/VeryPretty.java +++ b/java.source/src/org/netbeans/modules/java/source/pretty/VeryPretty.java @@ -570,6 +570,7 @@ @Override public void visitTopLevel(JCCompilationUnit tree) { + printAnnotations(tree.getPackageAnnotations()); printPackage(tree.pid); List l = tree.defs; ArrayList imports = new ArrayList(); @@ -577,7 +578,7 @@ imports.add((JCImport) l.head); l = l.tail; } - printImportsBlock(imports); + printImportsBlock(imports, !l.isEmpty()); while (l.nonEmpty()) { printStat(l.head, true, false); newline(); @@ -1879,7 +1880,7 @@ } } - public void printImportsBlock(java.util.List imports) { + public void printImportsBlock(java.util.List imports, boolean maybeAppendNewLine) { boolean hasImports = !imports.isEmpty(); if (hasImports) { blankLines(cs.getBlankLinesBeforeImports()); @@ -1888,7 +1889,7 @@ printStat(importStat); newline(); } - if (hasImports) { + if (hasImports && maybeAppendNewLine) { blankLines(cs.getBlankLinesAfterImports()); } } diff --git a/java.source/src/org/netbeans/modules/java/source/save/CasualDiff.java b/java.source/src/org/netbeans/modules/java/source/save/CasualDiff.java --- a/java.source/src/org/netbeans/modules/java/source/save/CasualDiff.java +++ b/java.source/src/org/netbeans/modules/java/source/save/CasualDiff.java @@ -271,8 +271,7 @@ protected void diffTopLevel(JCCompilationUnit oldT, JCCompilationUnit newT) { int localPointer = 0; oldTopLevel = oldT; - // todo (#pf): make package annotation diffing correctly - // diffList(oldT.packageAnnotations, newT.packageAnnotations, LineInsertionType.NONE, 0); + localPointer = diffAnnotationsLists(oldT.packageAnnotations, newT.packageAnnotations, 0, 0); localPointer = diffPackageStatement(oldT, newT, localPointer); PositionEstimator est = EstimatorFactory.imports(oldT.getImports(), newT.getImports(), workingCopy); localPointer = diffList(oldT.getImports(), newT.getImports(), localPointer, est, Measure.DEFAULT, printer); @@ -1741,26 +1740,10 @@ // modifiers wasn't changed, return the position lastPrinted. return localPointer; } - int annotationsEnd = oldT.annotations.nonEmpty() ? endPos(oldT.annotations) : localPointer; + int startPos = oldT.pos != Position.NOPOS ? getOldPos(oldT) : getOldPos(parent); - if (listsMatch(oldT.annotations, newT.annotations)) { - copyTo(localPointer, localPointer = (annotationsEnd != localPointer ? annotationsEnd : startPos)); - } else { - tokenSequence.move(startPos); - tokenSequence.movePrevious(); - if (JavaTokenId.WHITESPACE == tokenSequence.token().id()) { - String text = tokenSequence.token().text().toString(); - int index = text.lastIndexOf('\n'); - startPos = tokenSequence.offset(); - if (index > -1) { - startPos += index + 1; - } - if (startPos < localPointer) startPos = localPointer; - } - copyTo(localPointer, startPos); - PositionEstimator est = EstimatorFactory.annotations(oldT.getAnnotations(),newT.getAnnotations(), workingCopy, parameterPrint); - localPointer = diffList(oldT.annotations, newT.annotations, startPos, est, Measure.DEFAULT, printer); - } + + localPointer = diffAnnotationsLists(oldT.getAnnotations(), newT.getAnnotations(), startPos, localPointer); int endOffset = endPos(oldT); @@ -1802,6 +1785,30 @@ return localPointer; } + 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 { + tokenSequence.move(startPos); + if (tokenSequence.movePrevious() && JavaTokenId.WHITESPACE == tokenSequence.token().id()) { + String text = tokenSequence.token().text().toString(); + int index = text.lastIndexOf('\n'); + startPos = tokenSequence.offset(); + if (index > -1) { + startPos += index + 1; + } + if (startPos < localPointer) startPos = localPointer; + } + copyTo(localPointer, startPos); + PositionEstimator est = EstimatorFactory.annotations(oldAnnotations,newAnnotations, workingCopy, parameterPrint); + localPointer = diffList(oldAnnotations, newAnnotations, startPos, est, Measure.DEFAULT, printer); + } + + return localPointer; + } + protected void diffLetExpr(LetExpr oldT, LetExpr newT) { // TODO: perhaps better to throw exception here. Should be never // called. @@ -2584,7 +2591,7 @@ copyTo(localPointer, pos, printer); if (newList.get(0).getKind() == Kind.IMPORT) { - printer.printImportsBlock(newList); + printer.printImportsBlock(newList, true); } else { printer.print(aHead.toString()); for (JCTree item : newList) { diff --git a/java.source/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java b/java.source/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java --- a/java.source/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java +++ b/java.source/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java @@ -509,12 +509,13 @@ importAnalysis.setPackage(tree.getPackageName()); importAnalysis.setImports(translate(tree.getImports())); + List annotations = translate(tree.getPackageAnnotations()); List types = translate(tree.getTypeDecls()); List imps = importAnalysis.getImports(); - if (pid!=tree.getPackageName() || !imps.equals(tree.getImports()) || + if (!annotations.equals(tree.getPackageAnnotations()) || pid!=tree.getPackageName() || !imps.equals(tree.getImports()) || !types.equals(tree.getTypeDecls())) { - CompilationUnitTree n = make.CompilationUnit(pid, imps, types, tree.getSourceFile()); + CompilationUnitTree n = make.CompilationUnit(annotations, pid, imps, types, tree.getSourceFile()); model.setElement(n, model.getElement(tree)); copyCommentTo(tree,n); model.setPos(n, model.getPos(tree)); diff --git a/java.source/test/unit/src/org/netbeans/api/java/source/gen/CompilationUnitTest.java b/java.source/test/unit/src/org/netbeans/api/java/source/gen/CompilationUnitTest.java --- a/java.source/test/unit/src/org/netbeans/api/java/source/gen/CompilationUnitTest.java +++ b/java.source/test/unit/src/org/netbeans/api/java/source/gen/CompilationUnitTest.java @@ -35,6 +35,7 @@ import java.io.File; import java.util.Collections; import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.source.ClasspathInfo; @@ -67,7 +68,7 @@ // suite.addTest(new CompilationUnitTest("testNewCompilationUnit")); // suite.addTest(new CompilationUnitTest("test77010")); // suite.addTest(new CompilationUnitTest("test117607_1")); -// suite.addTest(new CompilationUnitTest("test117607_2")); +// suite.addTest(new CompilationUnitTest("test157760")); return suite; } @@ -542,6 +543,96 @@ assertEquals(res, golden); } + public void test157760a() throws Exception { + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, "package foo; public @interface YY {}"); + + FileObject emptyJava = FileUtil.createData(FileUtil.getConfigRoot(), "Templates/Classes/Empty.java"); + emptyJava.setAttribute("template", Boolean.TRUE); + FileObject testSourceFO = FileUtil.toFileObject(testFile); + assertNotNull(testSourceFO); + ClassPath sourcePath = ClassPath.getClassPath(testSourceFO, ClassPath.SOURCE); + assertNotNull(sourcePath); + FileObject[] roots = sourcePath.getRoots(); + assertEquals(1, roots.length); + final FileObject sourceRoot = roots[0]; + assertNotNull(sourceRoot); + ClassPath compilePath = ClassPath.getClassPath(testSourceFO, ClassPath.COMPILE); + assertNotNull(compilePath); + ClassPath bootPath = ClassPath.getClassPath(testSourceFO, ClassPath.BOOT); + assertNotNull(bootPath); + ClasspathInfo cpInfo = ClasspathInfo.create(bootPath, compilePath, sourcePath); + + String golden = + "@YY\n" + + "package zoo;\n\n" + + "import foo.YY;\n"; + + JavaSource javaSource = JavaSource.create(cpInfo, FileUtil.toFileObject(testFile)); + + Task task = new Task() { + public void run(WorkingCopy workingCopy) throws Exception { + workingCopy.toPhase(JavaSource.Phase.RESOLVED); + TypeElement yyAnnotation = workingCopy.getElements().getTypeElement("foo.YY"); + assertNotNull(yyAnnotation); + TreeMaker make = workingCopy.getTreeMaker(); + CompilationUnitTree newTree = make.CompilationUnit( + Collections.singletonList(make.Annotation(make.QualIdent(yyAnnotation), Collections.emptyList())), + sourceRoot, + "zoo/package-info.java", + Collections.emptyList(), + Collections.emptyList() + ); + workingCopy.rewrite(null, newTree); + } + }; + ModificationResult result = javaSource.runModificationTask(task); + result.commit(); + String res = TestUtilities.copyFileToString(new File(getDataDir().getAbsolutePath() + "/zoo/package-info.java")); + System.err.println(res); + assertEquals(golden, res); + } + + public void test157760b() throws Exception { + testFile = new File(getWorkDir(), "package-info.java"); + TestUtilities.copyStringToFile(testFile, "@AA\n@BB\n@CC\npackage foo;"); + + FileObject testSourceFO = FileUtil.toFileObject(testFile); + assertNotNull(testSourceFO); + ClassPath sourcePath = ClassPath.getClassPath(testSourceFO, ClassPath.SOURCE); + assertNotNull(sourcePath); + FileObject[] roots = sourcePath.getRoots(); + assertEquals(1, roots.length); + final FileObject sourceRoot = roots[0]; + assertNotNull(sourceRoot); + ClassPath compilePath = ClassPath.getClassPath(testSourceFO, ClassPath.COMPILE); + assertNotNull(compilePath); + ClassPath bootPath = ClassPath.getClassPath(testSourceFO, ClassPath.BOOT); + assertNotNull(bootPath); + ClasspathInfo cpInfo = ClasspathInfo.create(bootPath, compilePath, sourcePath); + + String golden = "@EE\n@CC\n@DD\npackage foo;"; + + JavaSource javaSource = JavaSource.create(cpInfo, FileUtil.toFileObject(testFile)); + + Task task = new Task() { + public void run(WorkingCopy workingCopy) throws Exception { + workingCopy.toPhase(JavaSource.Phase.RESOLVED); + TreeMaker make = workingCopy.getTreeMaker(); + CompilationUnitTree nue = make.addPackageAnnotation(workingCopy.getCompilationUnit(), make.Annotation(make.Identifier("DD"), Collections.emptyList())); + nue = make.insertPackageAnnotation(nue, 0, make.Annotation(make.Identifier("EE"), Collections.emptyList())); + nue = make.removePackageAnnotation(nue, 1); + nue = make.removePackageAnnotation(nue, workingCopy.getCompilationUnit().getPackageAnnotations().get(1)); + workingCopy.rewrite(workingCopy.getCompilationUnit(), nue); + } + }; + ModificationResult result = javaSource.runModificationTask(task); + result.commit(); + String res = TestUtilities.copyFileToString(testFile); + System.err.println(res); + assertEquals(golden, res); + } + String getGoldenPckg() { return ""; }