This is an automated email from the ASF dual-hosted git repository.
dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new f31308e LSP: Pull Up and Push Down refactorings added. (#3149)
f31308e is described below
commit f31308e223ca3718f5145b99a6e03bf8d6c2eaa4
Author: Dusan Balek <[email protected]>
AuthorDate: Fri Sep 3 10:53:08 2021 +0200
LSP: Pull Up and Push Down refactorings added. (#3149)
---
.../java/lsp/server/protocol/CodeRefactoring.java | 138 +++++++++
.../ExtractSuperclassOrInterfaceRefactoring.java | 93 +-----
.../java/lsp/server/protocol/MoveRefactoring.java | 94 +-----
.../lsp/server/protocol/PullUpRefactoring.java | 280 +++++++++++++++++
.../lsp/server/protocol/PushDownRefactoring.java | 216 +++++++++++++
.../java/lsp/server/protocol/ServerTest.java | 336 +++++++++++++++++++--
6 files changed, 965 insertions(+), 192 deletions(-)
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeRefactoring.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeRefactoring.java
new file mode 100644
index 0000000..01494f5
--- /dev/null
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeRefactoring.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.eclipse.lsp4j.CreateFile;
+import org.eclipse.lsp4j.DeleteFile;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.RenameFile;
+import org.eclipse.lsp4j.ResourceOperation;
+import org.eclipse.lsp4j.TextDocumentEdit;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+import org.netbeans.api.java.source.ModificationResult;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.refactoring.api.AbstractRefactoring;
+import org.netbeans.modules.refactoring.api.Problem;
+import org.netbeans.modules.refactoring.api.RefactoringSession;
+import org.netbeans.modules.refactoring.api.impl.APIAccessor;
+import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
+import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
+import org.netbeans.modules.refactoring.plugins.FileMovePlugin;
+import org.netbeans.modules.refactoring.spi.RefactoringCommit;
+import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
+import org.netbeans.modules.refactoring.spi.Transaction;
+import org.openide.filesystems.FileObject;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class CodeRefactoring extends CodeActionsProvider {
+
+ protected final WorkspaceEdit perform(AbstractRefactoring refactoring,
String name) throws Exception {
+ RefactoringSession session = RefactoringSession.create(name);
+ Problem p = refactoring.checkParameters();
+ if (p != null && p.isFatal()) {
+ throw new IllegalStateException(p.getMessage());
+ }
+ p = refactoring.preCheck();
+ if (p != null && p.isFatal()) {
+ throw new IllegalStateException(p.getMessage());
+ }
+ p = refactoring.prepare(session);
+ if (p != null && p.isFatal()) {
+ throw new IllegalStateException(p.getMessage());
+ }
+ List<Either<TextDocumentEdit, ResourceOperation>> resultChanges = new
ArrayList<>();
+ Map<String, String> renames = new HashMap<>();
+ List<RefactoringElementImplementation> fileChanges =
APIAccessor.DEFAULT.getFileChanges(session);
+ for (RefactoringElementImplementation rei : fileChanges) {
+ if (rei instanceof FileMovePlugin.MoveFile) {
+ String oldURI = Utils.toUri(rei.getParentFile());
+ int slash = oldURI.lastIndexOf('/');
+ URL url =
((org.netbeans.modules.refactoring.api.MoveRefactoring)refactoring).getTarget().lookup(URL.class);
+ String newURI = url.toString() + oldURI.substring(slash + 1);
+ renames.put(oldURI, newURI);
+ ResourceOperation op = new RenameFile(oldURI, newURI);
+ resultChanges.add(Either.forRight(op));
+ } else if (rei instanceof
org.netbeans.modules.refactoring.java.plugins.DeleteFile) {
+ String oldURI = Utils.toUri(rei.getParentFile());
+ ResourceOperation op = new DeleteFile(oldURI);
+ resultChanges.add(Either.forRight(op));
+ } else {
+ throw new IllegalStateException(rei.getClass().toString());
+ }
+ }
+ List<Transaction> transactions =
APIAccessor.DEFAULT.getCommits(session);
+ List<ModificationResult> results = new ArrayList<>();
+ for (Transaction t : transactions) {
+ if (t instanceof RefactoringCommit) {
+ RefactoringCommit c = (RefactoringCommit) t;
+ for (org.netbeans.modules.refactoring.spi.ModificationResult
refResult : SPIAccessor.DEFAULT.getTransactions(c)) {
+ if (refResult instanceof JavaModificationResult) {
+ results.add(((JavaModificationResult)
refResult).delegate);
+ } else {
+ throw new
IllegalStateException(refResult.getClass().toString());
+ }
+ }
+ } else {
+ throw new IllegalStateException(t.getClass().toString());
+ }
+ }
+ for (ModificationResult mr : results) {
+ Set<File> newFiles = mr.getNewFiles();
+ if (newFiles.size() > 1) {
+ throw new IllegalStateException();
+ }
+ String newFilePath = null;
+ for (File newFile : newFiles) {
+ newFilePath = newFile.toURI().toString();
+ resultChanges.add(Either.forRight(new
CreateFile(newFilePath)));
+ }
+ for (FileObject modified : mr.getModifiedFileObjects()) {
+ String modifiedUri = Utils.toUri(modified);
+ List<TextEdit> edits = new ArrayList<>();
+ for (ModificationResult.Difference diff :
mr.getDifferences(modified)) {
+ String newText = diff.getNewText();
+ if (diff.getKind() ==
ModificationResult.Difference.Kind.CREATE) {
+ Position pos = new Position(0, 0);
+ resultChanges.add(Either.forLeft(new
TextDocumentEdit(new VersionedTextDocumentIdentifier(newFilePath, -1),
Collections.singletonList(new TextEdit(new Range(pos, pos), newText != null ?
newText : "")))));
+ } else {
+ edits.add(new TextEdit(new
Range(Utils.createPosition(modified, diff.getStartPosition().getOffset()),
Utils.createPosition(modified, diff.getEndPosition().getOffset())), newText !=
null ? newText : ""));
+ }
+ }
+ resultChanges.add(Either.forLeft(new TextDocumentEdit(new
VersionedTextDocumentIdentifier(renames.getOrDefault(modifiedUri, modifiedUri),
-1), edits)));
+ }
+ }
+ session.finished();
+ return new WorkspaceEdit(resultChanges);
+ }
+}
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
index e08ce01..970f76a 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
@@ -22,9 +22,9 @@ import com.google.gson.Gson;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
-import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
@@ -44,39 +44,22 @@ import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
-import org.eclipse.lsp4j.Position;
-import org.eclipse.lsp4j.Range;
-import org.eclipse.lsp4j.ResourceOperation;
-import org.eclipse.lsp4j.TextDocumentEdit;
-import org.eclipse.lsp4j.TextEdit;
-import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
-import org.eclipse.lsp4j.WorkspaceEdit;
-import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaSource;
-import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
-import org.netbeans.modules.refactoring.api.Problem;
-import org.netbeans.modules.refactoring.api.RefactoringSession;
-import org.netbeans.modules.refactoring.api.impl.APIAccessor;
-import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
import org.netbeans.modules.refactoring.java.api.ExtractInterfaceRefactoring;
import org.netbeans.modules.refactoring.java.api.ExtractSuperclassRefactoring;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.api.MemberInfo;
-import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
-import org.netbeans.modules.refactoring.spi.RefactoringCommit;
-import org.netbeans.modules.refactoring.spi.Transaction;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.util.NbBundle;
@@ -86,8 +69,8 @@ import org.openide.util.lookup.ServiceProvider;
*
* @author Dusan Balek
*/
-@ServiceProvider(service = CodeActionsProvider.class, position = 160)
-public class ExtractSuperclassOrInterfaceRefactoring extends
CodeActionsProvider {
+@ServiceProvider(service = CodeActionsProvider.class, position = 170)
+public class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactoring {
private static final String EXTRACT_SUPERCLASS_REFACTORING_COMMAND =
"java.refactor.extract.superclass";
private static final String EXTRACT_INTERFACE_REFACTORING_COMMAND =
"java.refactor.extract.interface";
@@ -129,6 +112,7 @@ public class ExtractSuperclassOrInterfaceRefactoring
extends CodeActionsProvider
if (type == null) {
return Collections.emptyList();
}
+ SourcePositions sourcePositions = info.getTrees().getSourcePositions();
List<QuickPickItem> members = new ArrayList();
List<QuickPickItem> allMembers = new ArrayList();
ClassTree sourceTree = (ClassTree) path.getLeaf();
@@ -137,15 +121,18 @@ public class ExtractSuperclassOrInterfaceRefactoring
extends CodeActionsProvider
if (!treeUtilities.isSynthetic(memberTreePath)) {
Element memberElm = trees.getElement(memberTreePath);
if (memberElm != null) {
+ long startMember =
sourcePositions.getStartPosition(info.getCompilationUnit(), member);
+ long endMember =
sourcePositions.getEndPosition(info.getCompilationUnit(), member);
+ boolean selected = offset > startMember && offset <
endMember;
Set<Modifier> mods = memberElm.getModifiers();
if (memberElm.getKind() == ElementKind.FIELD) {
- QuickPickItem memberItem = new
QuickPickItem(createLabel(info, memberElm), null, null, false, new
ElementData(memberElm));
+ QuickPickItem memberItem = new
QuickPickItem(createLabel(info, memberElm), null, null, selected, new
ElementData(memberElm));
allMembers.add(memberItem);
if (mods.contains(Modifier.PUBLIC) &&
mods.contains(Modifier.STATIC) && mods.contains(Modifier.FINAL) &&
((VariableTree) member).getInitializer() != null) {
members.add(memberItem);
}
} else if (memberElm.getKind() == ElementKind.METHOD) {
- QuickPickItem memberItem = new
QuickPickItem(createLabel(info, memberElm), null, null, false, new
ElementData(memberElm));
+ QuickPickItem memberItem = new
QuickPickItem(createLabel(info, memberElm), null, null, selected, new
ElementData(memberElm));
allMembers.add(memberItem);
if (mods.contains(Modifier.PUBLIC) &&
!mods.contains(Modifier.STATIC)) {
members.add(memberItem);
@@ -158,7 +145,9 @@ public class ExtractSuperclassOrInterfaceRefactoring
extends CodeActionsProvider
if (!allMembers.isEmpty()) {
QuickPickItem elementItem = new QuickPickItem(createLabel(info,
type));
elementItem.setUserData(new ElementData(type));
- result.add(createCodeAction(Bundle.DN_ExtractSuperclass(),
CodeActionKind.RefactorExtract, EXTRACT_SUPERCLASS_REFACTORING_COMMAND, uri,
elementItem, allMembers));
+ if (!type.getKind().isInterface()) {
+ result.add(createCodeAction(Bundle.DN_ExtractSuperclass(),
CodeActionKind.RefactorExtract, EXTRACT_SUPERCLASS_REFACTORING_COMMAND, uri,
elementItem, allMembers));
+ }
if (!members.isEmpty()) {
result.add(createCodeAction(Bundle.DN_ExtractInterface(),
CodeActionKind.RefactorExtract, EXTRACT_INTERFACE_REFACTORING_COMMAND, uri,
elementItem, members));
}
@@ -241,63 +230,7 @@ public class ExtractSuperclassOrInterfaceRefactoring
extends CodeActionsProvider
refactoring = r;
}
refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
- RefactoringSession session =
RefactoringSession.create(EXTRACT_SUPERCLASS_REFACTORING_COMMAND.equals(command)
? "Extract Superclass" : "Extract Interface");
- Problem p = refactoring.checkParameters();
- if (p != null && p.isFatal()) {
- throw new IllegalStateException(p.getMessage());
- }
- p = refactoring.preCheck();
- if (p != null && p.isFatal()) {
- throw new IllegalStateException(p.getMessage());
- }
- p = refactoring.prepare(session);
- if (p != null && p.isFatal()) {
- throw new IllegalStateException(p.getMessage());
- }
- List<Either<TextDocumentEdit, ResourceOperation>> resultChanges =
new ArrayList<>();
- List<Transaction> transactions =
APIAccessor.DEFAULT.getCommits(session);
- List<ModificationResult> results = new ArrayList<>();
- for (Transaction t : transactions) {
- if (t instanceof RefactoringCommit) {
- RefactoringCommit c = (RefactoringCommit) t;
- for
(org.netbeans.modules.refactoring.spi.ModificationResult refResult :
SPIAccessor.DEFAULT.getTransactions(c)) {
- if (refResult instanceof JavaModificationResult) {
- results.add(((JavaModificationResult)
refResult).delegate);
- } else {
- throw new
IllegalStateException(refResult.getClass().toString());
- }
- }
- } else {
- throw new IllegalStateException(t.getClass().toString());
- }
- }
- for (ModificationResult mr : results) {
- Set<File> newFiles = mr.getNewFiles();
- if (newFiles.size() > 1) {
- throw new IllegalStateException();
- }
- String newFilePath = null;
- for (File newFile : newFiles) {
- newFilePath = newFile.toURI().toString();
- resultChanges.add(Either.forRight(new
CreateFile(newFilePath)));
- }
- for (FileObject modified : mr.getModifiedFileObjects()) {
- String modifiedUri = Utils.toUri(modified);
- List<TextEdit> edits = new ArrayList<>();
- for (ModificationResult.Difference diff :
mr.getDifferences(modified)) {
- String newText = diff.getNewText();
- if (diff.getKind() ==
ModificationResult.Difference.Kind.CREATE) {
- Position pos = new Position(0, 0);
- resultChanges.add(Either.forLeft(new
TextDocumentEdit(new VersionedTextDocumentIdentifier(newFilePath, -1),
Collections.singletonList(new TextEdit(new Range(pos, pos), newText != null ?
newText : "")))));
- } else {
- edits.add(new TextEdit(new
Range(Utils.createPosition(file, diff.getStartPosition().getOffset()),
Utils.createPosition(file, diff.getEndPosition().getOffset())), newText != null
? newText : ""));
- }
- }
- resultChanges.add(Either.forLeft(new TextDocumentEdit(new
VersionedTextDocumentIdentifier(modifiedUri, -1), edits)));
- }
- }
- session.finished();
- client.applyEdit(new ApplyWorkspaceEditParams(new
WorkspaceEdit(resultChanges)));
+ client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring,
EXTRACT_SUPERCLASS_REFACTORING_COMMAND.equals(command) ? "Extract Superclass" :
"Extract Interface")));
} catch (Exception ex) {
client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
}
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
index 6506569..72160e9 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
@@ -28,10 +28,8 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -41,17 +39,8 @@ import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.DeleteFile;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
-import org.eclipse.lsp4j.Range;
-import org.eclipse.lsp4j.RenameFile;
-import org.eclipse.lsp4j.ResourceOperation;
-import org.eclipse.lsp4j.TextDocumentEdit;
-import org.eclipse.lsp4j.TextEdit;
-import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
-import org.eclipse.lsp4j.WorkspaceEdit;
-import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.source.ClassIndex;
@@ -60,7 +49,6 @@ import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaSource;
-import org.netbeans.api.java.source.ModificationResult;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
@@ -68,17 +56,8 @@ import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.parsing.api.ResultIterator;
-import org.netbeans.modules.refactoring.api.Problem;
-import org.netbeans.modules.refactoring.api.RefactoringSession;
-import org.netbeans.modules.refactoring.api.impl.APIAccessor;
-import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
import org.netbeans.modules.refactoring.java.api.JavaMoveMembersProperties;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
-import org.netbeans.modules.refactoring.java.spi.hooks.JavaModificationResult;
-import org.netbeans.modules.refactoring.plugins.FileMovePlugin;
-import org.netbeans.modules.refactoring.spi.RefactoringCommit;
-import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
-import org.netbeans.modules.refactoring.spi.Transaction;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.util.NbBundle;
@@ -90,7 +69,7 @@ import org.openide.util.lookup.ServiceProvider;
* @author Dusan Balek
*/
@ServiceProvider(service = CodeActionsProvider.class, position = 160)
-public class MoveRefactoring extends CodeActionsProvider {
+public class MoveRefactoring extends CodeRefactoring {
private static final String MOVE_REFACTORING_KIND = "refactor.move";
private static final String MOVE_REFACTORING_COMMAND =
"java.refactor.move";
@@ -225,81 +204,12 @@ public class MoveRefactoring extends CodeActionsProvider {
ElementHandle handle =
gson.fromJson(gson.toJson(target.getUserData()), ElementData.class).toHandle();
refactoring.setTarget(Lookups.singleton(TreePathHandle.from(handle, info)));
}
- RefactoringSession session = RefactoringSession.create("Move");
- Problem p = refactoring.checkParameters();
- if (p != null && p.isFatal()) {
- throw new IllegalStateException(p.getMessage());
- }
- p = refactoring.preCheck();
- if (p != null && p.isFatal()) {
- throw new IllegalStateException(p.getMessage());
- }
- p = refactoring.prepare(session);
- if (p != null && p.isFatal()) {
- throw new IllegalStateException(p.getMessage());
- }
- List<Either<TextDocumentEdit, ResourceOperation>> resultChanges =
new ArrayList<>();
- Map<String, String> renames = new HashMap<>();
- List<RefactoringElementImplementation> fileChanges =
APIAccessor.DEFAULT.getFileChanges(session);
- for (RefactoringElementImplementation rei : fileChanges) {
- if (rei instanceof FileMovePlugin.MoveFile) {
- String oldURI = Utils.toUri(rei.getParentFile());
- int slash = oldURI.lastIndexOf('/');
- URL url = refactoring.getTarget().lookup(URL.class);
- String newURI = url.toString() + oldURI.substring(slash +
1);
- renames.put(oldURI, newURI);
- ResourceOperation op = new RenameFile(oldURI, newURI);
- resultChanges.add(Either.forRight(op));
- } else if (rei instanceof
org.netbeans.modules.refactoring.java.plugins.DeleteFile) {
- String oldURI = Utils.toUri(rei.getParentFile());
- ResourceOperation op = new DeleteFile(oldURI);
- resultChanges.add(Either.forRight(op));
- } else {
- throw new IllegalStateException(rei.getClass().toString());
- }
- }
- List<Transaction> transactions =
APIAccessor.DEFAULT.getCommits(session);
- List<ModificationResult> results = new ArrayList<>();
- for (Transaction t : transactions) {
- if (t instanceof RefactoringCommit) {
- RefactoringCommit c = (RefactoringCommit) t;
- for
(org.netbeans.modules.refactoring.spi.ModificationResult refResult :
SPIAccessor.DEFAULT.getTransactions(c)) {
- if (refResult instanceof JavaModificationResult) {
- results.add(((JavaModificationResult)
refResult).delegate);
- } else {
- throw new
IllegalStateException(refResult.getClass().toString());
- }
- }
- } else {
- throw new IllegalStateException(t.getClass().toString());
- }
- }
- for (ModificationResult mr : results) {
- for (FileObject modified : mr.getModifiedFileObjects()) {
- String modifiedUri = Utils.toUri(modified);
- resultChanges.add(Either.forLeft(new TextDocumentEdit(new
VersionedTextDocumentIdentifier(renames.getOrDefault(modifiedUri, modifiedUri),
-1), fileModifications(mr, modified))));
- }
- }
- session.finished();
- client.applyEdit(new ApplyWorkspaceEditParams(new
WorkspaceEdit(resultChanges)));
+ client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring,
"Move")));
} catch (Exception ex) {
client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
}
}
- private static List<TextEdit> fileModifications(ModificationResult
changes, FileObject file) {
- List<? extends ModificationResult.Difference> diffs =
changes.getDifferences(file);
- if (diffs == null) {
- return Collections.emptyList();
- }
- List<TextEdit> edits = new ArrayList<>();
- for (ModificationResult.Difference diff : diffs) {
- String newText = diff.getNewText();
- edits.add(new TextEdit(new Range(Utils.createPosition(file,
diff.getStartPosition().getOffset()), Utils.createPosition(file,
diff.getEndPosition().getOffset())), newText != null ? newText : ""));
- }
- return edits;
- }
-
private static Element elementForOffset(CompilationInfo info, int offset)
throws RuntimeException {
List<? extends TypeElement> topLevelElements =
info.getTopLevelElements();
Trees trees = info.getTrees();
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
new file mode 100644
index 0000000..2beb73e
--- /dev/null
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreePathHandle;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
+import org.netbeans.modules.refactoring.java.api.MemberInfo;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeActionsProvider.class, position = 180)
+public class PullUpRefactoring extends CodeRefactoring {
+
+ private static final String PULL_UP_REFACTORING_KIND = "refactor.pull.up";
+ private static final String PULL_UP_REFACTORING_COMMAND =
"java.refactor.pull.up";
+
+ private final Set<String> commands =
Collections.singleton(PULL_UP_REFACTORING_COMMAND);
+ private final Gson gson = new Gson();
+
+ @Override
+ @NbBundle.Messages({
+ "DN_PullUp= Pull Up...",
+ })
+ public List<CodeAction> getCodeActions(ResultIterator resultIterator,
CodeActionParams params) throws Exception {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Refactor)) {
+ return Collections.emptyList();
+ }
+ CompilationController info =
CompilationController.get(resultIterator.getParserResult());
+ if (info == null ||
!JavaRefactoringUtils.isRefactorable(info.getFileObject())) {
+ return Collections.emptyList();
+ }
+ info.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+ int offset = getOffset(info, params.getRange().getStart());
+ String uri = Utils.toUri(info.getFileObject());
+ Trees trees = info.getTrees();
+ TreeUtilities treeUtilities = info.getTreeUtilities();
+ TreePath path =
findSelectedClassMemberDeclaration(treeUtilities.pathFor(offset), info);
+ if (path == null) {
+ return Collections.emptyList();
+ }
+ Element element = trees.getElement(path);
+ if (!(element instanceof TypeElement)) {
+ element = info.getElementUtilities().enclosingTypeElement(element);
+ }
+ if (!(element instanceof TypeElement)) {
+ return Collections.emptyList();
+ }
+ Collection<TypeElement> supertypes =
JavaRefactoringUtils.getSuperTypes((TypeElement)element, info, true);
+ if (supertypes.isEmpty()) {
+ return Collections.emptyList();
+ }
+ final List<QuickPickItem> supertypeItems = new
ArrayList<>(supertypes.size());
+ for (TypeElement e: supertypes) {
+ QuickPickItem supertypeItem = new QuickPickItem(createLabel(info,
e), null, null, false, new ElementData(e));
+ supertypeItems.add(supertypeItem);
+ }
+ QuickPickItem elementItem = new QuickPickItem(createLabel(info,
element));
+ elementItem.setUserData(new ElementData(element));
+ return Collections.singletonList(createCodeAction(Bundle.DN_PullUp(),
PULL_UP_REFACTORING_KIND, PULL_UP_REFACTORING_COMMAND, uri, offset,
elementItem, supertypeItems));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectTargetSupertype=Select target supertype",
+ "DN_SelectMembersToPullUp=Select members to pull up",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient
client, String command, List<Object> arguments) {
+ try {
+ if (arguments.size() > 3) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)),
String.class);
+ int offset = gson.fromJson(gson.toJson(arguments.get(1)),
Integer.class);
+ QuickPickItem sourceItem =
gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem.class);
+ List<QuickPickItem> superclasses =
Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)),
QuickPickItem[].class));
+ if (superclasses.size() > 1) {
+ client.showQuickPick(new
ShowQuickPickParams(Bundle.DN_SelectTargetSupertype(), false,
superclasses)).thenAccept(selected -> {
+ if (selected != null && !selected.isEmpty()) {
+ QuickPickItem targetItem = selected.get(0);
+ List<QuickPickItem> members = getMembers(client,
uri, offset, sourceItem, targetItem);
+ if (!members.isEmpty()) {
+ client.showQuickPick(new
ShowQuickPickParams(Bundle.DN_SelectMembersToPullUp(), true,
members)).thenAccept(selectedMembers -> {
+ if (selectedMembers != null &&
!selectedMembers.isEmpty()) {
+ pullUp(client, uri, sourceItem,
targetItem, selectedMembers);
+ }
+ });
+ }
+ }
+ });
+ } else {
+ QuickPickItem targetItem = superclasses.get(0);
+ List<QuickPickItem> members = getMembers(client, uri,
offset, sourceItem, targetItem);
+ if (!members.isEmpty()) {
+ client.showQuickPick(new
ShowQuickPickParams(Bundle.DN_SelectMembersToPullUp(), true,
members)).thenAccept(selectedMembers -> {
+ if (selectedMembers != null &&
!selectedMembers.isEmpty()) {
+ pullUp(client, uri, sourceItem, targetItem,
selectedMembers);
+ }
+ });
+ }
+ }
+ } else {
+ throw new IllegalArgumentException(String.format("Illegal
number of arguments received for command: %s", command));
+ }
+ } catch (Exception ex) {
+ client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ private List<QuickPickItem> getMembers(NbCodeLanguageClient client, String
uri, int offset, QuickPickItem source, QuickPickItem target) {
+ List<QuickPickItem> members = new ArrayList<>();
+ try {
+ FileObject file = Utils.fromUri(uri);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ js.runUserActionTask(info -> {
+ Trees trees = info.getTrees();
+ SourcePositions sourcePositions = trees.getSourcePositions();
+ ElementUtilities eu = info.getElementUtilities();
+ Types types = info.getTypes();
+ TypeElement sourceElement = (TypeElement)
gson.fromJson(gson.toJson(source.getUserData()),
ElementData.class).toHandle().resolve(info);
+ TypeMirror sourceType = sourceElement.asType();
+ TypeElement targetElement = (TypeElement)
gson.fromJson(gson.toJson(target.getUserData()),
ElementData.class).toHandle().resolve(info);
+ for (Element e : sourceElement.getEnclosedElements()) {
+ switch (e.getKind()) {
+ case CONSTRUCTOR:
+ case STATIC_INIT:
+ case INSTANCE_INIT:
+ continue;
+ case METHOD:
+ if (eu.alreadyDefinedIn(e.getSimpleName(),
(ExecutableType) types.asMemberOf((DeclaredType) sourceType, e),
targetElement)) {
+ break;
+ }
+ default: {
+ TreePath path = trees.getPath(e);
+ long startMember = path != null ?
sourcePositions.getStartPosition(path.getCompilationUnit(), path.getLeaf()) :
-1;
+ long endMember = path != null ?
sourcePositions.getEndPosition(path.getCompilationUnit(), path.getLeaf()) : -1;
+ boolean selected = offset > startMember && offset
< endMember;
+ members.add(new QuickPickItem(createLabel(info,
e), null, null, selected, new ElementData(e)));
+ }
+ }
+ }
+ }, true);
+ } catch (Exception ex) {
+ client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
+ }
+ return members;
+ }
+
+ private void pullUp(NbCodeLanguageClient client, String uri, QuickPickItem
source, QuickPickItem target, List<QuickPickItem> members) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ ClasspathInfo info = ClasspathInfo.create(file);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ ElementHandle sourceHandle =
gson.fromJson(gson.toJson(source.getUserData()), ElementData.class).toHandle();
+ ElementHandle targetHandle =
gson.fromJson(gson.toJson(target.getUserData()), ElementData.class).toHandle();
+ List<MemberInfo<ElementHandle<Element>>> memberHandles = new
ArrayList<>();
+ js.runUserActionTask(ci -> {
+ ci.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+ boolean isAbstract =
targetHandle.resolve(ci).getModifiers().contains(Modifier.ABSTRACT);
+ for (QuickPickItem member : members) {
+ Element el =
gson.fromJson(gson.toJson(member.getUserData()), ElementData.class).resolve(ci);
+ MemberInfo<ElementHandle<Element>> memberInfo =
MemberInfo.create(el, ci);
+ memberInfo.setMakeAbstract(isAbstract && el.getKind() ==
ElementKind.METHOD);
+ memberHandles.add(memberInfo);
+ }
+ }, true);
+ org.netbeans.modules.refactoring.java.api.PullUpRefactoring
refactoring = new
org.netbeans.modules.refactoring.java.api.PullUpRefactoring(TreePathHandle.from(sourceHandle,
info));
+ refactoring.setTargetType(targetHandle);
+ refactoring.setMembers(memberHandles.toArray(new
MemberInfo[memberHandles.size()]));
+
refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
+ client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring,
"PullUp")));
+ } catch (Exception ex) {
+ client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
+ }
+ }
+
+ private static TreePath findSelectedClassMemberDeclaration(final TreePath
path, final CompilationInfo javac) {
+ TreePath currentPath = path;
+ TreePath selection = null;
+ while (currentPath != null && selection == null) {
+ switch (currentPath.getLeaf().getKind()) {
+ case ANNOTATION_TYPE:
+ case CLASS:
+ case ENUM:
+ case INTERFACE:
+ case NEW_CLASS:
+ case METHOD:
+ selection = currentPath;
+ break;
+ case VARIABLE:
+ Element elm = javac.getTrees().getElement(currentPath);
+ if (elm != null && elm.getKind().isField()) {
+ selection = currentPath;
+ }
+ break;
+ }
+ if (selection != null &&
javac.getTreeUtilities().isSynthetic(selection)) {
+ selection = null;
+ }
+ if (selection == null) {
+ currentPath = currentPath.getParentPath();
+ }
+ }
+ if (selection == null && path != null) {
+ List<? extends Tree> typeDecls =
path.getCompilationUnit().getTypeDecls();
+ if (!typeDecls.isEmpty() &&
typeDecls.get(0).getKind().asInterface() == ClassTree.class) {
+ selection = TreePath.getPath(path.getCompilationUnit(),
typeDecls.get(0));
+ }
+ }
+ return selection;
+ }
+}
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
new file mode 100644
index 0000000..edb4500
--- /dev/null
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.lsp.server.protocol;
+
+import com.google.gson.Gson;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Types;
+import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreePathHandle;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
+import org.netbeans.modules.refactoring.java.api.MemberInfo;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeActionsProvider.class, position = 190)
+public class PushDownRefactoring extends CodeRefactoring {
+
+ private static final String PUSH_DOWN_REFACTORING_KIND =
"refactor.push.down";
+ private static final String PUSH_DOWN_REFACTORING_COMMAND =
"java.refactor.push.down";
+
+ private final Set<String> commands =
Collections.singleton(PUSH_DOWN_REFACTORING_COMMAND);
+ private final Gson gson = new Gson();
+
+ @Override
+ @NbBundle.Messages({
+ "DN_PushDown= Push Down...",
+ })
+ public List<CodeAction> getCodeActions(ResultIterator resultIterator,
CodeActionParams params) throws Exception {
+ List<String> only = params.getContext().getOnly();
+ if (only == null || !only.contains(CodeActionKind.Refactor)) {
+ return Collections.emptyList();
+ }
+ CompilationController info =
CompilationController.get(resultIterator.getParserResult());
+ if (info == null ||
!JavaRefactoringUtils.isRefactorable(info.getFileObject())) {
+ return Collections.emptyList();
+ }
+ info.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+ int offset = getOffset(info, params.getRange().getStart());
+ String uri = Utils.toUri(info.getFileObject());
+ Trees trees = info.getTrees();
+ SourcePositions sourcePositions = trees.getSourcePositions();
+ TreeUtilities treeUtilities = info.getTreeUtilities();
+ Types types = info.getTypes();
+ TreePath path =
findSelectedClassMemberDeclaration(treeUtilities.pathFor(offset), info);
+ if (path == null) {
+ return Collections.emptyList();
+ }
+ Element element = trees.getElement(path);
+ if (!(element instanceof TypeElement)) {
+ element = info.getElementUtilities().enclosingTypeElement(element);
+ }
+ if (!(element instanceof TypeElement)) {
+ return Collections.emptyList();
+ }
+ List<QuickPickItem> members = new ArrayList<>();
+ for (Element m: element.getEnclosedElements()) {
+ if (m.getKind() == ElementKind.CONSTRUCTOR || m.getKind() ==
ElementKind.STATIC_INIT || m.getKind() == ElementKind.INSTANCE_INIT) {
+ continue;
+ }
+ if (m instanceof TypeElement && types.isSubtype(m.asType(),
element.asType())) {
+ continue;
+ }
+ TreePath mPath = trees.getPath(m);
+ long startMember = mPath != null ?
sourcePositions.getStartPosition(mPath.getCompilationUnit(), mPath.getLeaf()) :
-1;
+ long endMember = mPath != null ?
sourcePositions.getEndPosition(mPath.getCompilationUnit(), mPath.getLeaf()) :
-1;
+ boolean selected = offset > startMember && offset < endMember;
+ members.add(new QuickPickItem(createLabel(info, m), null, null,
selected, new ElementData(m)));
+ }
+ if (members.isEmpty()) {
+ return Collections.emptyList();
+ }
+ QuickPickItem elementItem = new QuickPickItem(createLabel(info,
element));
+ elementItem.setUserData(new ElementData(element));
+ return
Collections.singletonList(createCodeAction(Bundle.DN_PushDown(),
PUSH_DOWN_REFACTORING_KIND, PUSH_DOWN_REFACTORING_COMMAND, uri, elementItem,
members));
+ }
+
+ @Override
+ public Set<String> getCommands() {
+ return commands;
+ }
+
+ @Override
+ @NbBundle.Messages({
+ "DN_SelectMembersToPushDown=Select members to push down",
+ })
+ public CompletableFuture<Object> processCommand(NbCodeLanguageClient
client, String command, List<Object> arguments) {
+ try {
+ if (arguments.size() > 2) {
+ String uri = gson.fromJson(gson.toJson(arguments.get(0)),
String.class);
+ QuickPickItem sourceItem =
gson.fromJson(gson.toJson(arguments.get(1)), QuickPickItem.class);
+ List<QuickPickItem> members =
Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)),
QuickPickItem[].class));
+ client.showQuickPick(new
ShowQuickPickParams(Bundle.DN_SelectMembersToPushDown(), true,
members)).thenAccept(selected -> {
+ if (selected != null && !selected.isEmpty()) {
+ pushDown(client, uri, sourceItem, selected);
+ }
+ });
+ } else {
+ throw new IllegalArgumentException(String.format("Illegal
number of arguments received for command: %s", command));
+ }
+ } catch (Exception ex) {
+ client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
+ }
+ return CompletableFuture.completedFuture(true);
+ }
+
+ private void pushDown(NbCodeLanguageClient client, String uri,
QuickPickItem source, List<QuickPickItem> members) {
+ try {
+ FileObject file = Utils.fromUri(uri);
+ ClasspathInfo info = ClasspathInfo.create(file);
+ JavaSource js = JavaSource.forFileObject(file);
+ if (js == null) {
+ throw new IOException("Cannot get JavaSource for: " + uri);
+ }
+ ElementHandle sourceHandle =
gson.fromJson(gson.toJson(source.getUserData()), ElementData.class).toHandle();
+ List<MemberInfo<ElementHandle<Element>>> memberHandles = new
ArrayList<>();
+ js.runUserActionTask(ci -> {
+ ci.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+ for (QuickPickItem member : members) {
+ Element el =
gson.fromJson(gson.toJson(member.getUserData()), ElementData.class).resolve(ci);
+ MemberInfo<ElementHandle<Element>> memberInfo =
MemberInfo.create(el, ci);
+ memberHandles.add(memberInfo);
+ }
+ }, true);
+ org.netbeans.modules.refactoring.java.api.PushDownRefactoring
refactoring = new
org.netbeans.modules.refactoring.java.api.PushDownRefactoring(TreePathHandle.from(sourceHandle,
info));
+ refactoring.setMembers(memberHandles.toArray(new
MemberInfo[memberHandles.size()]));
+
refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
+ client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring,
"PushDown")));
+ } catch (Exception ex) {
+ client.logMessage(new MessageParams(MessageType.Error,
ex.getLocalizedMessage()));
+ }
+ }
+
+ private static TreePath findSelectedClassMemberDeclaration(final TreePath
path, final CompilationInfo javac) {
+ TreePath currentPath = path;
+ TreePath selection = null;
+ while (currentPath != null && selection == null) {
+ switch (currentPath.getLeaf().getKind()) {
+ case ANNOTATION_TYPE:
+ case CLASS:
+ case ENUM:
+ case INTERFACE:
+ case NEW_CLASS:
+ case METHOD:
+ selection = currentPath;
+ break;
+ case VARIABLE:
+ Element elm = javac.getTrees().getElement(currentPath);
+ if (elm != null && elm.getKind().isField()) {
+ selection = currentPath;
+ }
+ break;
+ }
+ if (selection != null &&
javac.getTreeUtilities().isSynthetic(selection)) {
+ selection = null;
+ }
+ if (selection == null) {
+ currentPath = currentPath.getParentPath();
+ }
+ }
+ if (selection == null && path != null) {
+ List<? extends Tree> typeDecls =
path.getCompilationUnit().getTypeDecls();
+ if (!typeDecls.isEmpty() &&
typeDecls.get(0).getKind().asInterface() == ClassTree.class) {
+ selection = TreePath.getPath(path.getCompilationUnit(),
typeDecls.get(0));
+ }
+ }
+ return selection;
+ }
+}
diff --git
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 5531cff..7cfdb35 100644
---
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -4316,8 +4316,6 @@ public class ServerTest extends NbTestCase {
try (Writer w = new FileWriter(new
File(src.getParentFile().getParentFile(), ".test-project"))) {}
String code = "package a;\n" +
"\n" +
- "import b.Test2;\n" +
- "\n" +
"public class Test {\n" +
" public static final int CNT = 10;\n" +
" public void m(Test t) {}\n" +
@@ -4416,7 +4414,7 @@ public class ServerTest extends NbTestCase {
indexingComplete.await();
server.getTextDocumentService().didOpen(new
DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
VersionedTextDocumentIdentifier id = new
VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
- List<Either<Command, CodeAction>> codeActions =
server.getTextDocumentService().codeAction(new CodeActionParams(id, new
Range(new Position(5, 10), new Position(5, 10)), new
CodeActionContext(Arrays.asList(),
Arrays.asList(CodeActionKind.Refactor)))).get();
+ List<Either<Command, CodeAction>> codeActions =
server.getTextDocumentService().codeAction(new CodeActionParams(id, new
Range(new Position(3, 10), new Position(3, 10)), new
CodeActionContext(Arrays.asList(),
Arrays.asList(CodeActionKind.Refactor)))).get();
Optional<CodeAction> extractInterface =
codeActions.stream()
.filter(Either::isRight)
@@ -4444,16 +4442,16 @@ public class ServerTest extends NbTestCase {
List<TextEdit> fileChanges = tde.getEdits();
assertNotNull(fileChanges);
assertEquals(3, fileChanges.size());
- assertEquals(new Range(new Position(4, 18),
- new Position(5, 10)),
+ assertEquals(new Range(new Position(2, 18),
+ new Position(3, 10)),
fileChanges.get(0).getRange());
assertEquals("implements", fileChanges.get(0).getNewText());
- assertEquals(new Range(new Position(5, 11),
- new Position(5, 17)),
+ assertEquals(new Range(new Position(3, 11),
+ new Position(3, 17)),
fileChanges.get(1).getRange());
assertEquals("NewInterface", fileChanges.get(1).getNewText());
- assertEquals(new Range(new Position(5, 18),
- new Position(5, 37)),
+ assertEquals(new Range(new Position(3, 18),
+ new Position(3, 37)),
fileChanges.get(2).getRange());
assertEquals("{\n @Override",
fileChanges.get(2).getNewText());
} else if
(tde.getTextDocument().getUri().endsWith("a/NewInterface.java")) {
@@ -4483,8 +4481,6 @@ public class ServerTest extends NbTestCase {
try (Writer w = new FileWriter(new
File(src.getParentFile().getParentFile(), ".test-project"))) {}
String code = "package a;\n" +
"\n" +
- "import b.Test2;\n" +
- "\n" +
"public class Test {\n" +
" public int CNT = 10;\n" +
" public void m(Test t) {}\n" +
@@ -4583,7 +4579,7 @@ public class ServerTest extends NbTestCase {
indexingComplete.await();
server.getTextDocumentService().didOpen(new
DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
VersionedTextDocumentIdentifier id = new
VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
- List<Either<Command, CodeAction>> codeActions =
server.getTextDocumentService().codeAction(new CodeActionParams(id, new
Range(new Position(5, 10), new Position(5, 10)), new
CodeActionContext(Arrays.asList(),
Arrays.asList(CodeActionKind.Refactor)))).get();
+ List<Either<Command, CodeAction>> codeActions =
server.getTextDocumentService().codeAction(new CodeActionParams(id, new
Range(new Position(3, 10), new Position(3, 10)), new
CodeActionContext(Arrays.asList(),
Arrays.asList(CodeActionKind.Refactor)))).get();
Optional<CodeAction> extractSuperclass =
codeActions.stream()
.filter(Either::isRight)
@@ -4611,20 +4607,20 @@ public class ServerTest extends NbTestCase {
List<TextEdit> fileChanges = tde.getEdits();
assertNotNull(fileChanges);
assertEquals(4, fileChanges.size());
- assertEquals(new Range(new Position(4, 18),
- new Position(5, 10)),
+ assertEquals(new Range(new Position(2, 18),
+ new Position(3, 10)),
fileChanges.get(0).getRange());
assertEquals("extends", fileChanges.get(0).getNewText());
- assertEquals(new Range(new Position(5, 11),
- new Position(5, 14)),
+ assertEquals(new Range(new Position(3, 11),
+ new Position(3, 14)),
fileChanges.get(1).getRange());
assertEquals("NewClass", fileChanges.get(1).getNewText());
- assertEquals(new Range(new Position(5, 15),
- new Position(6, 26)),
+ assertEquals(new Range(new Position(3, 15),
+ new Position(4, 26)),
fileChanges.get(2).getRange());
assertEquals("", fileChanges.get(2).getNewText());
- assertEquals(new Range(new Position(6, 27),
- new Position(6, 28)),
+ assertEquals(new Range(new Position(4, 27),
+ new Position(4, 28)),
fileChanges.get(3).getRange());
assertEquals("", fileChanges.get(3).getNewText());
} else if
(tde.getTextDocument().getUri().endsWith("a/NewClass.java")) {
@@ -4649,6 +4645,306 @@ public class ServerTest extends NbTestCase {
}
}
+ public void testPullUp() throws Exception {
+ File src = new File(getWorkDir(), "a/Test.java");
+ src.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(new
File(src.getParentFile().getParentFile(), ".test-project"))) {}
+ String code = "package a;\n" +
+ "\n" +
+ "import b.Iface;\n" +
+ "\n" +
+ "public class Test implements Iface {\n" +
+ " public int CNT = 10;\n" +
+ " public void foo() {}\n" +
+ " public void bar(int i) {}\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ File src2 = new File(getWorkDir(), "b/Iface.java");
+ src2.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(src2)) {
+ w.write("package b;\n" +
+ "\n" +
+ "public interface Iface {\n" +
+ " void foo();\n" +
+ "}\n");
+ }
+ List<Diagnostic>[] diags = new List[1];
+ CountDownLatch indexingComplete = new CountDownLatch(1);
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher =
LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams params) {
+ if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+ indexingComplete.countDown();
+ } else {
+ throw new UnsupportedOperationException("Unexpected
message.");
+ }
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem>
showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse>
applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new
ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public CompletableFuture<String>
createTextEditorDecoration(DecorationRenderOptions params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void disposeTextEditorDecoration(String params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void setTextEditorDecoration(SetTextEditorDecorationParams
params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams
params) {
+ return CompletableFuture.completedFuture(params.getValue());
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>>
showQuickPick(ShowQuickPickParams params) {
+ List<QuickPickItem> items = params.getItems();
+ return CompletableFuture.completedFuture(items.size() > 1 ?
items.subList(1, 2) : items);
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeParams initParams = new InitializeParams();
+ initParams.setRootUri(getWorkDir().toURI().toString());
+ InitializeResult result = server.initialize(initParams).get();
+ indexingComplete.await();
+ server.getTextDocumentService().didOpen(new
DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new
VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions =
server.getTextDocumentService().codeAction(new CodeActionParams(id, new
Range(new Position(5, 10), new Position(5, 10)), new
CodeActionContext(Arrays.asList(),
Arrays.asList(CodeActionKind.Refactor)))).get();
+ Optional<CodeAction> pullUp =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a ->
Bundle.DN_PullUp().equals(a.getTitle()))
+ .findAny();
+ assertTrue(pullUp.isPresent());
+ server.getWorkspaceService().executeCommand(new
ExecuteCommandParams(pullUp.get().getCommand().getCommand(),
pullUp.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ List<Either<TextDocumentEdit, ResourceOperation>> documentChanges =
edit[0].getDocumentChanges();
+ assertEquals(1, documentChanges.size());
+ Either<TextDocumentEdit, ResourceOperation> change =
documentChanges.get(0);
+ assertTrue(change.isLeft());
+ TextDocumentEdit tde = change.getLeft();
+ assertTrue(tde.getTextDocument().getUri().endsWith("b/Iface.java"));
+ List<TextEdit> fileChanges = tde.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(4, 0),
+ new Position(4, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n void bar(int i);\n",
fileChanges.get(0).getNewText());
+ }
+
+ public void testPushDown() throws Exception {
+ File src = new File(getWorkDir(), "b/Base.java");
+ src.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(new
File(src.getParentFile().getParentFile(), ".test-project"))) {}
+ String code = "package b;\n" +
+ "\n" +
+ "public class Base {\n" +
+ " public void foo() {}\n" +
+ "}\n";
+ try (Writer w = new FileWriter(src)) {
+ w.write(code);
+ }
+ File src2 = new File(getWorkDir(), "a/Test.java");
+ src2.getParentFile().mkdirs();
+ try (Writer w = new FileWriter(src2)) {
+ w.write("package a;\n" +
+ "\n" +
+ "import b.Base;\n" +
+ "\n" +
+ "public class Test extends Base {\n" +
+ " public int CNT = 10;\n" +
+ " public void bar(int i) {}\n" +
+ "}\n");
+ }
+ List<Diagnostic>[] diags = new List[1];
+ CountDownLatch indexingComplete = new CountDownLatch(1);
+ WorkspaceEdit[] edit = new WorkspaceEdit[1];
+ Launcher<LanguageServer> serverLauncher =
LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
+ @Override
+ public void telemetryEvent(Object arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams params) {
+ synchronized (diags) {
+ diags[0] = params.getDiagnostics();
+ diags.notifyAll();
+ }
+ }
+
+ @Override
+ public void showMessage(MessageParams params) {
+ if (Server.INDEXING_COMPLETED.equals(params.getMessage())) {
+ indexingComplete.countDown();
+ } else {
+ throw new UnsupportedOperationException("Unexpected
message.");
+ }
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem>
showMessageRequest(ShowMessageRequestParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void logMessage(MessageParams arg0) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<ApplyWorkspaceEditResponse>
applyEdit(ApplyWorkspaceEditParams params) {
+ edit[0] = params.getEdit();
+ return CompletableFuture.completedFuture(new
ApplyWorkspaceEditResponse(false));
+ }
+
+ @Override
+ public CompletableFuture<String>
createTextEditorDecoration(DecorationRenderOptions params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void disposeTextEditorDecoration(String params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void setTextEditorDecoration(SetTextEditorDecorationParams
params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams
params) {
+ return CompletableFuture.completedFuture(params.getValue());
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>>
showQuickPick(ShowQuickPickParams params) {
+ List<QuickPickItem> items = params.getItems();
+ return CompletableFuture.completedFuture(items.size() > 1 ?
items.subList(1, 2) : items);
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }, client.getInputStream(), client.getOutputStream());
+ serverLauncher.startListening();
+ LanguageServer server = serverLauncher.getRemoteProxy();
+ InitializeParams initParams = new InitializeParams();
+ initParams.setRootUri(getWorkDir().toURI().toString());
+ InitializeResult result = server.initialize(initParams).get();
+ indexingComplete.await();
+ server.getTextDocumentService().didOpen(new
DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code)));
+ VersionedTextDocumentIdentifier id = new
VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+ List<Either<Command, CodeAction>> codeActions =
server.getTextDocumentService().codeAction(new CodeActionParams(id, new
Range(new Position(3, 10), new Position(3, 10)), new
CodeActionContext(Arrays.asList(),
Arrays.asList(CodeActionKind.Refactor)))).get();
+ Optional<CodeAction> pushDown =
+ codeActions.stream()
+ .filter(Either::isRight)
+ .map(Either::getRight)
+ .filter(a ->
Bundle.DN_PushDown().equals(a.getTitle()))
+ .findAny();
+ assertTrue(pushDown.isPresent());
+ server.getWorkspaceService().executeCommand(new
ExecuteCommandParams(pushDown.get().getCommand().getCommand(),
pushDown.get().getCommand().getArguments())).get();
+ int cnt = 0;
+ while(edit[0] == null && cnt++ < 10) {
+ Thread.sleep(1000);
+ }
+ List<Either<TextDocumentEdit, ResourceOperation>> documentChanges =
edit[0].getDocumentChanges();
+ assertEquals(2, documentChanges.size());
+ for (int i = 0; i <= 1; i++) {
+ Either<TextDocumentEdit, ResourceOperation> change =
documentChanges.get(i);
+ assertTrue(change.isLeft());
+ TextDocumentEdit tde = change.getLeft();
+ if (tde.getTextDocument().getUri().endsWith("a/Test.java")) {
+ List<TextEdit> fileChanges = tde.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(7, 0),
+ new Position(7, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("\n public void foo() {\n }\n",
fileChanges.get(0).getNewText());
+ } else if (tde.getTextDocument().getUri().endsWith("b/Base.java"))
{
+ List<TextEdit> fileChanges = tde.getEdits();
+ assertNotNull(fileChanges);
+ assertEquals(1, fileChanges.size());
+ assertEquals(new Range(new Position(3, 0),
+ new Position(4, 0)),
+ fileChanges.get(0).getRange());
+ assertEquals("", fileChanges.get(0).getNewText());
+ } else {
+ fail("Unknown file modified");
+ }
+ }
+ }
+
public void testNoErrorAndHintsFor() throws Exception {
File src = new File(getWorkDir(), "Test.java");
src.getParentFile().mkdirs();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists