You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by db...@apache.org on 2021/08/31 13:59:50 UTC

[netbeans] branch master updated: LSP: Extract Superclass and Extract Interface refactorings added. (#3138)

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 4737743  LSP: Extract Superclass and Extract Interface refactorings added. (#3138)
4737743 is described below

commit 4737743d2b1f854542d5e4c5faef77b582781604
Author: Dusan Balek <du...@oracle.com>
AuthorDate: Tue Aug 31 15:59:32 2021 +0200

    LSP: Extract Superclass and Extract Interface refactorings added. (#3138)
---
 .../ExtractSuperclassOrInterfaceRefactoring.java   | 305 ++++++++++++++++++
 .../java/lsp/server/protocol/MoveRefactoring.java  |   3 +-
 .../java/lsp/server/protocol/ServerTest.java       | 340 +++++++++++++++++++++
 3 files changed, 646 insertions(+), 2 deletions(-)

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
new file mode 100644
index 0000000..e08ce01
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
@@ -0,0 +1,305 @@
+/*
+ * 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.tree.VariableTree;
+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;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+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.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+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;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeActionsProvider.class, position = 160)
+public class ExtractSuperclassOrInterfaceRefactoring extends CodeActionsProvider {
+
+    private static final String EXTRACT_SUPERCLASS_REFACTORING_COMMAND =  "java.refactor.extract.superclass";
+    private static final String EXTRACT_INTERFACE_REFACTORING_COMMAND =  "java.refactor.extract.interface";
+    private static final ClassPath EMPTY_PATH = ClassPathSupport.createClassPath(new URL[0]);
+
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(EXTRACT_INTERFACE_REFACTORING_COMMAND, EXTRACT_SUPERCLASS_REFACTORING_COMMAND)));
+    private final Gson gson = new Gson();
+
+    @Override
+    @NbBundle.Messages({
+        "DN_ExtractSuperclass= Extract Superclass...",
+        "DN_ExtractInterface= Extract Interface...",
+    })
+    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 = treeUtilities.pathFor(offset);
+        path = JavaRefactoringUtils.findEnclosingClass(info, path, true, true, true, true, false);
+        if (path.getLeaf().getKind() == Tree.Kind.COMPILATION_UNIT) {
+            List<? extends Tree> decls = info.getCompilationUnit().getTypeDecls();
+            if (!decls.isEmpty()) {
+                path = TreePath.getPath(info.getCompilationUnit(), decls.get(0));
+            } else {
+                return Collections.emptyList();
+            }
+        }
+        TypeElement type = (TypeElement) trees.getElement(path);
+        if (type == null) {
+            return Collections.emptyList();
+        }
+        List<QuickPickItem> members = new ArrayList();
+        List<QuickPickItem> allMembers = new ArrayList();
+        ClassTree sourceTree = (ClassTree) path.getLeaf();
+        for (Tree member : sourceTree.getMembers()) {
+            TreePath memberTreePath = new TreePath(path, member);
+            if (!treeUtilities.isSynthetic(memberTreePath)) {
+                Element memberElm = trees.getElement(memberTreePath);
+                if (memberElm != null) {
+                    Set<Modifier> mods = memberElm.getModifiers();
+                    if (memberElm.getKind() == ElementKind.FIELD) {
+                        QuickPickItem memberItem = new QuickPickItem(createLabel(info, memberElm), null, null, false, 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));
+                        allMembers.add(memberItem);
+                        if (mods.contains(Modifier.PUBLIC) && !mods.contains(Modifier.STATIC)) {
+                            members.add(memberItem);
+                        }
+                    }
+                }
+            }
+        }
+        List<CodeAction> result = new ArrayList<>();
+        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 (!members.isEmpty()) {
+                result.add(createCodeAction(Bundle.DN_ExtractInterface(), CodeActionKind.RefactorExtract, EXTRACT_INTERFACE_REFACTORING_COMMAND, uri, elementItem, members));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectMembersToExtract=Select members to extract",
+        "DN_SelectClassName=Select class name",
+        "DN_SelectInterfaceName=Select interface name",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 2) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            QuickPickItem type = 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_SelectMembersToExtract(), true, members)).thenAccept(selected -> {
+                if (selected != null && !selected.isEmpty()) {
+                    String label = EXTRACT_SUPERCLASS_REFACTORING_COMMAND.equals(command) ? Bundle.DN_SelectClassName() : Bundle.DN_SelectInterfaceName();
+                    String value = EXTRACT_SUPERCLASS_REFACTORING_COMMAND.equals(command) ? "NewClass" : "NewInterface";
+                    client.showInputBox(new ShowInputBoxParams(label, value)).thenAccept(name -> {
+                        extract(client, uri, command, type, selected, name);
+                    });
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    private void extract(NbCodeLanguageClient client, String uri, String command, QuickPickItem source, List<QuickPickItem> members, String name) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            ClasspathInfo info = ClasspathInfo.create(file);
+            ElementHandle handle = gson.fromJson(gson.toJson(source.getUserData()), ElementData.class).toHandle();
+            AbstractRefactoring refactoring;
+            if (EXTRACT_SUPERCLASS_REFACTORING_COMMAND.equals(command)) {
+                List<MemberInfo<ElementHandle<Element>>> memberHandles = new ArrayList<>();
+                JavaSource js = JavaSource.forFileObject(file);
+                if (js == null) {
+                    throw new IOException("Cannot get JavaSource for: " + uri);
+                }
+                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);
+                        memberHandles.add(MemberInfo.create(el, ci));
+                    }
+                }, true);
+                ExtractSuperclassRefactoring r = new ExtractSuperclassRefactoring(TreePathHandle.from(handle, info));
+                r.setMembers(memberHandles.toArray(new MemberInfo[memberHandles.size()]));
+                r.setSuperClassName(name);
+                refactoring = r;
+            } else {
+                List<ElementHandle<VariableElement>> fields = new ArrayList<>();
+                List<ElementHandle<ExecutableElement>> methods = new ArrayList<>();
+                for (QuickPickItem member : members) {
+                    ElementHandle memberHandle = gson.fromJson(gson.toJson(member.getUserData()), ElementData.class).toHandle();
+                    switch (memberHandle.getKind()) {
+                        case FIELD:
+                            fields.add(memberHandle);
+                            break;
+                        case METHOD:
+                            methods.add(memberHandle);
+                            break;
+                    }
+                }
+                ExtractInterfaceRefactoring r = new ExtractInterfaceRefactoring(TreePathHandle.from(handle, info));
+                r.setFields(fields);
+                r.setMethods(methods);
+                r.setInterfaceName(name);
+                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)));
+        } 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 60e4f68..6506569 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
@@ -24,7 +24,6 @@ import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.Trees;
-import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -283,7 +282,7 @@ public class MoveRefactoring extends CodeActionsProvider {
             }
             session.finished();
             client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(resultChanges)));
-        } catch (IOException | IllegalArgumentException ex) {
+        } catch (Exception ex) {
             client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
     }
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 c19c630..5531cff 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
@@ -72,6 +72,7 @@ import org.eclipse.lsp4j.CompletionItem;
 import org.eclipse.lsp4j.CompletionItemKind;
 import org.eclipse.lsp4j.CompletionList;
 import org.eclipse.lsp4j.CompletionParams;
+import org.eclipse.lsp4j.CreateFile;
 import org.eclipse.lsp4j.DefinitionParams;
 import org.eclipse.lsp4j.Diagnostic;
 import org.eclipse.lsp4j.DidChangeTextDocumentParams;
@@ -4309,6 +4310,345 @@ public class ServerTest extends NbTestCase {
         }
     }
 
+    public void testExtractInterface() 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.Test2;\n" +
+                      "\n" +
+                      "public class Test {\n" +
+                      "    public static final int CNT = 10;\n" +
+                      "    public void m(Test t) {}\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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);
+            }
+
+            @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> extractInterface =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_ExtractInterface().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(extractInterface.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(extractInterface.get().getCommand().getCommand(), extractInterface.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(3, documentChanges.size());
+        Either<TextDocumentEdit, ResourceOperation> change = documentChanges.get(0);
+        assertTrue(change.isRight());
+        ResourceOperation ro = change.getRight();
+        assertEquals(ResourceOperationKind.Create, ro.getKind());
+        assertTrue(((CreateFile) ro).getUri().endsWith("a/NewInterface.java"));
+        for (int i = 1; i <= 2; i++) {
+            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(3, fileChanges.size());
+                assertEquals(new Range(new Position(4, 18),
+                                       new Position(5, 10)),
+                             fileChanges.get(0).getRange());
+                assertEquals("implements", fileChanges.get(0).getNewText());
+                assertEquals(new Range(new Position(5, 11),
+                                       new Position(5, 17)),
+                             fileChanges.get(1).getRange());
+                assertEquals("NewInterface", fileChanges.get(1).getNewText());
+                assertEquals(new Range(new Position(5, 18),
+                                       new Position(5, 37)),
+                             fileChanges.get(2).getRange());
+                assertEquals("{\n    @Override", fileChanges.get(2).getNewText());
+            } else if (tde.getTextDocument().getUri().endsWith("a/NewInterface.java")) {
+                List<TextEdit> fileChanges = tde.getEdits();
+                assertNotNull(fileChanges);
+                assertEquals(1, fileChanges.size());
+                assertEquals(new Range(new Position(0, 0),
+                                       new Position(0, 0)),
+                             fileChanges.get(0).getRange());
+                assertTrue(fileChanges.get(0).getNewText().endsWith(
+                        "public interface NewInterface {\n" +
+                        "\n" +
+                        "    int CNT = 10;\n" +
+                        "\n" +
+                        "    void m(Test t);\n" +
+                        "\n" +
+                        "}\n"));
+            } else {
+                fail("Unknown file modified");
+            }
+        }
+    }
+
+    public void testExtractSuperclass() 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.Test2;\n" +
+                      "\n" +
+                      "public class Test {\n" +
+                      "    public int CNT = 10;\n" +
+                      "    public void m(Test t) {}\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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);
+            }
+
+            @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> extractSuperclass =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_ExtractSuperclass().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(extractSuperclass.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(extractSuperclass.get().getCommand().getCommand(), extractSuperclass.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(3, documentChanges.size());
+        Either<TextDocumentEdit, ResourceOperation> change = documentChanges.get(0);
+        assertTrue(change.isRight());
+        ResourceOperation ro = change.getRight();
+        assertEquals(ResourceOperationKind.Create, ro.getKind());
+        assertTrue(((CreateFile) ro).getUri().endsWith("a/NewClass.java"));
+        for (int i = 1; i <= 2; i++) {
+            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(4, fileChanges.size());
+                assertEquals(new Range(new Position(4, 18),
+                                       new Position(5, 10)),
+                             fileChanges.get(0).getRange());
+                assertEquals("extends", fileChanges.get(0).getNewText());
+                assertEquals(new Range(new Position(5, 11),
+                                       new Position(5, 14)),
+                             fileChanges.get(1).getRange());
+                assertEquals("NewClass", fileChanges.get(1).getNewText());
+                assertEquals(new Range(new Position(5, 15),
+                                       new Position(6, 26)),
+                             fileChanges.get(2).getRange());
+                assertEquals("", fileChanges.get(2).getNewText());
+                assertEquals(new Range(new Position(6, 27),
+                                       new Position(6, 28)),
+                             fileChanges.get(3).getRange());
+                assertEquals("", fileChanges.get(3).getNewText());
+            } else if (tde.getTextDocument().getUri().endsWith("a/NewClass.java")) {
+                List<TextEdit> fileChanges = tde.getEdits();
+                assertNotNull(fileChanges);
+                assertEquals(1, fileChanges.size());
+                assertEquals(new Range(new Position(0, 0),
+                                       new Position(0, 0)),
+                             fileChanges.get(0).getRange());
+                assertTrue(fileChanges.get(0).getNewText().endsWith(
+                        "public class NewClass {\n" +
+                        "\n" +
+                        "    public int CNT = 10;\n" +
+                        "\n" +
+                        "    public void m(Test t) {\n" +
+                        "    }\n" +
+                        "\n" +
+                        "}\n"));
+            } else {
+                fail("Unknown file modified");
+            }
+        }
+    }
+
     public void testNoErrorAndHintsFor() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists