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

Reply via email to