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 2020/12/15 18:01:00 UTC

[netbeans] branch master updated: Existing CodeGenerators exposed as CodeActions via LSP. (#2587)

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 b6104f5  Existing CodeGenerators exposed as CodeActions via LSP. (#2587)
b6104f5 is described below

commit b6104f59cea7350169f9d0737350bc71225b1e6c
Author: Dusan Balek <du...@oracle.com>
AuthorDate: Tue Dec 15 19:00:41 2020 +0100

    Existing CodeGenerators exposed as CodeActions via LSP. (#2587)
    
    * Exisitng CodeGenerators exposed as CodeActions via LSP.
---
 .../editor/codegen/DelegateMethodGenerator.java    |   2 +-
 .../editor/codegen/EqualsHashCodeGenerator.java    |   6 +-
 .../java/editor/codegen/LoggerGenerator.java       |   2 +-
 .../java/editor/codegen/ToStringGenerator.java     |   2 +-
 java/java.lsp.server/nbproject/project.xml         |  43 +-
 .../java/lsp/server/protocol/CodeGenerator.java    | 215 ++++++
 .../lsp/server/protocol/ConstructorGenerator.java  | 217 ++++++
 .../server/protocol/DelegateMethodGenerator.java   | 223 ++++++
 .../server/protocol/EqualsHashCodeGenerator.java   | 172 +++++
 .../lsp/server/protocol/GetterSetterGenerator.java | 184 +++--
 .../protocol/ImplementOverrideMethodGenerator.java | 183 +++++
 .../java/lsp/server/protocol/LoggerGenerator.java  | 144 ++++
 .../lsp/server/protocol/NbCodeClientWrapper.java   |  12 +-
 .../lsp/server/protocol/NbCodeLanguageClient.java  |  23 +-
 .../java/lsp/server/protocol/QuickPickItem.java    | 206 ++++++
 .../modules/java/lsp/server/protocol/Server.java   |  27 +-
 .../lsp/server/protocol/ShowInputBoxParams.java    | 124 ++++
 .../lsp/server/protocol/ShowQuickPickParams.java   | 159 +++++
 .../server/protocol/TextDocumentServiceImpl.java   | 131 +---
 .../lsp/server/protocol/ToStringGenerator.java     | 165 +++++
 .../lsp/server/protocol/WorkspaceServiceImpl.java  |  31 +-
 .../java/lsp/server/protocol/ServerTest.java       | 757 ++++++++++++++++++++-
 java/java.lsp.server/vscode/package-lock.json      |  30 +-
 java/java.lsp.server/vscode/package.json           |  50 +-
 java/java.lsp.server/vscode/src/extension.ts       |  33 +-
 java/java.lsp.server/vscode/src/protocol.ts        |  36 +
 26 files changed, 2845 insertions(+), 332 deletions(-)

diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
index c4b3277..6b95763 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/DelegateMethodGenerator.java
@@ -299,7 +299,7 @@ public class DelegateMethodGenerator implements CodeGenerator {
         return ElementNode.Description.create(descriptions);
     }
     
-    static void generateDelegatingMethods(WorkingCopy wc, TreePath path, VariableElement delegate, Iterable<? extends ExecutableElement> methods, int offset) {
+    public static void generateDelegatingMethods(WorkingCopy wc, TreePath path, VariableElement delegate, Iterable<? extends ExecutableElement> methods, int offset) {
         assert TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind());
         TypeElement te = (TypeElement)wc.getTrees().getElement(path);
         if (te != null) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
index 08ebbac..8795d63 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java
@@ -393,8 +393,8 @@ public class EqualsHashCodeGenerator implements CodeGenerator {
 
         generateEqualsAndHashCode(wc, path, e, h, -1);
     }
-    
-    static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iterable<? extends VariableElement> equalsFields, Iterable<? extends VariableElement> hashCodeFields, int offset) {
+
+    public static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iterable<? extends VariableElement> equalsFields, Iterable<? extends VariableElement> hashCodeFields, int offset) {
         assert TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind());
         TypeElement te = (TypeElement)wc.getTrees().getElement(path);
         if (te != null) {
@@ -419,7 +419,7 @@ public class EqualsHashCodeGenerator implements CodeGenerator {
                 members.add(createEqualsMethod(wc, equalsFields, dt, scope));
             }
             wc.rewrite(nue, GeneratorUtils.insertClassMembers(wc, nue, members, offset));
-        }        
+        }
     }
 
     private static MethodTree createEqualsMethod(WorkingCopy wc, Iterable<? extends VariableElement> equalsFields, DeclaredType type, Scope scope) {
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
index bf91dcd..7b11f99 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/LoggerGenerator.java
@@ -152,7 +152,7 @@ public class LoggerGenerator implements CodeGenerator {
         }
     }
 
-    private static VariableTree createLoggerField(TreeMaker make, ClassTree cls, CharSequence name, Set<Modifier> mods) {
+    public static VariableTree createLoggerField(TreeMaker make, ClassTree cls, CharSequence name, Set<Modifier> mods) {
         ModifiersTree modifiers = make.Modifiers(mods, Collections.<AnnotationTree>emptyList());
         final List<ExpressionTree> none = Collections.<ExpressionTree>emptyList();
         IdentifierTree className = make.Identifier(cls.getSimpleName());
diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
index e0ccca1..c855d3c 100644
--- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
+++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java
@@ -209,7 +209,7 @@ public class ToStringGenerator implements CodeGenerator {
         }
     }
 
-    private static MethodTree createToStringMethod(WorkingCopy wc, Iterable<? extends VariableElement> fields, String typeName, boolean useStringBuilder) {
+    public static MethodTree createToStringMethod(WorkingCopy wc, Iterable<? extends VariableElement> fields, String typeName, boolean useStringBuilder) {
         TreeMaker make = wc.getTreeMaker();
         Set<Modifier> mods = EnumSet.of(Modifier.PUBLIC);
         List<AnnotationTree> annotations = new LinkedList<>();
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index b940103..287f215 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -26,6 +26,23 @@
             <code-name-base>org.netbeans.modules.java.lsp.server</code-name-base>
             <module-dependencies>
                 <dependency>
+                    <code-name-base>com.google.gson</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>2.7</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.api.annotations.common</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.36</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.api.debugger</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -61,15 +78,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.api.annotations.common</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <release-version>1</release-version>
-                        <specification-version>1.36</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.netbeans.api.java.classpath</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -130,6 +138,15 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>2.31</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -410,14 +427,6 @@
                         <specification-version>9.14</specification-version>
                     </run-dependency>
                 </dependency>
-                <dependency>
-                    <code-name-base>com.google.gson</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>2.7</specification-version>
-                    </run-dependency>
-                </dependency>
             </module-dependencies>
             <test-dependencies>
                 <test-type>
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java
new file mode 100644
index 0000000..9c741f8
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeGenerator.java
@@ -0,0 +1,215 @@
+/*
+ * 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.sun.source.tree.LineMap;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+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.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.Command;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.modules.editor.java.Utilities;
+import org.netbeans.modules.java.source.ElementHandleAccessor;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public abstract class CodeGenerator {
+
+    public static final String CODE_GENERATOR_KIND = "source.generate";
+    protected static final String ERROR = "<error>"; //NOI18N
+
+    public abstract List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params);
+
+    public abstract Set<String> getCommands();
+
+    public abstract CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments);
+
+    protected static int getOffset(CompilationInfo info, Position pos) {
+        LineMap lm = info.getCompilationUnit().getLineMap();
+        return (int) lm.getPosition(pos.getLine() + 1, pos.getCharacter() + 1);
+    }
+
+    protected static CodeAction createCodeAction(String name, String kind, String command, Object... args) {
+        CodeAction action = new CodeAction(name);
+        action.setKind(kind);
+        action.setCommand(new Command(name, command, Arrays.asList(args)));
+        return action;
+    }
+
+    protected static String createLabel(CompilationInfo info, TypeElement e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(e.getSimpleName());
+        List<? extends TypeParameterElement> typeParams = e.getTypeParameters();
+        if (typeParams != null && !typeParams.isEmpty()) {
+            sb.append("<"); // NOI18N
+            for(Iterator<? extends TypeParameterElement> it = typeParams.iterator(); it.hasNext();) {
+                TypeParameterElement tp = it.next();
+                sb.append(tp.getSimpleName());
+                List<? extends TypeMirror> bounds = tp.getBounds();
+                if (!bounds.isEmpty()) {
+                    if (bounds.size() > 1 || !"java.lang.Object".equals(bounds.get(0).toString())) { // NOI18N
+                        sb.append(" extends "); // NOI18N
+                        for (Iterator<? extends TypeMirror> bIt = bounds.iterator(); bIt.hasNext();) {
+                            sb.append(Utilities.getTypeName(info, bIt.next(), false));
+                            if (bIt.hasNext()) {
+                                sb.append(" & "); // NOI18N
+                            }
+                        }
+                    }
+                }
+                if (it.hasNext()) {
+                    sb.append(", "); // NOI18N
+                }
+            }
+            sb.append(">"); // NOI18N
+        }
+        return sb.toString();
+    }
+
+    protected static String createLabel(CompilationInfo info, VariableElement e) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(e.getSimpleName());
+        if (e.getKind() != ElementKind.ENUM_CONSTANT) {
+            sb.append(" : "); // NOI18N
+            sb.append(Utilities.getTypeName(info, e.asType(), false));
+        }
+        return sb.toString();
+    }
+
+    protected static String createLabel(CompilationInfo info, ExecutableElement e) {
+        StringBuilder sb = new StringBuilder();
+        if (e.getKind() == ElementKind.CONSTRUCTOR) {
+            sb.append(e.getEnclosingElement().getSimpleName());
+        } else {
+            sb.append(e.getSimpleName());
+        }
+        sb.append("("); // NOI18N
+        for (Iterator<? extends VariableElement> it = e.getParameters().iterator(); it.hasNext();) {
+            VariableElement param = it.next();
+            if (!it.hasNext() && e.isVarArgs() && param.asType().getKind() == TypeKind.ARRAY) {
+                sb.append(Utilities.getTypeName(info, ((ArrayType) param.asType()).getComponentType(), false));
+                sb.append("...");
+            } else {
+                sb.append(Utilities.getTypeName(info, param.asType(), false));
+            }
+            sb.append(" "); // NOI18N
+            sb.append(param.getSimpleName());
+            if (it.hasNext()) {
+                sb.append(", "); // NOI18N
+            }
+        }
+        sb.append(")"); // NOI18N
+        if (e.getKind() != ElementKind.CONSTRUCTOR) {
+            TypeMirror rt = e.getReturnType();
+            if (rt.getKind() != TypeKind.VOID) {
+                sb.append(" : "); // NOI18N
+                sb.append(Utilities.getTypeName(info, e.getReturnType(), false));
+            }
+        }
+        return sb.toString();
+    }
+
+    public static class ElementData {
+
+        private String kind;
+        private String[] signature;
+
+        public ElementData() {
+        }
+
+        public ElementData(Element element) {
+            ElementHandle<Element> handle = ElementHandle.create(element);
+            this.kind = handle.getKind().name();
+            this.signature = ElementHandleAccessor.getInstance().getJVMSignature(handle);
+        }
+
+        Element resolve(CompilationInfo info) {
+            ElementHandle handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(kind), signature);
+            return handle.resolve(info);
+        }
+
+        @Pure
+        public String getKind() {
+            return kind;
+        }
+
+        public void setKind(final String kind) {
+            this.kind = kind;
+        }
+
+        @Pure
+        public String[] getSignature() {
+            return signature;
+        }
+
+        public void setSignature(final String[] signature) {
+            this.signature = signature;
+        }
+
+        @Override
+        @Pure
+        public int hashCode() {
+            int hash = 7;
+            hash = 97 * hash + Objects.hashCode(this.kind);
+            hash = 97 * hash + Arrays.deepHashCode(this.signature);
+            return hash;
+        }
+
+        @Override
+        @Pure
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final ElementData other = (ElementData) obj;
+            if (this.kind != other.kind) {
+                return false;
+            }
+            if (!Arrays.deepEquals(this.signature, other.signature)) {
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
new file mode 100644
index 0000000..97a39f7
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
@@ -0,0 +1,217 @@
+/*
+ * 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.util.TreePath;
+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 java.util.stream.Collectors;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+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.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 10)
+public final class ConstructorGenerator extends CodeGenerator {
+
+    public static final String GENERATE_CONSTRUCTOR =  "java.generate.constructor";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_CONSTRUCTOR);
+    private final Gson gson = new Gson();
+
+    public ConstructorGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateConstructor=Generate Constructor...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || !typeElement.getKind().isClass() || NestingKind.ANONYMOUS.equals(typeElement.getNestingKind())) {
+            return Collections.emptyList();
+        }
+        final Set<? extends VariableElement> uninitializedFields = info.getTreeUtilities().getUninitializedFields(tp);
+        final List<ExecutableElement> inheritedConstructors = new ArrayList<>();
+        TypeMirror superClassType = typeElement.getSuperclass();
+        if (superClassType.getKind() == TypeKind.DECLARED) {
+            TypeElement superClass = (TypeElement) ((DeclaredType) superClassType).asElement();
+            Elements elements = info.getElements();
+            for (ExecutableElement executableElement : ElementFilter.constructorsIn(superClass.getEnclosedElements())) {
+                PackageElement currentPackage = elements.getPackageOf(typeElement);
+                PackageElement ctorPackage = elements.getPackageOf(executableElement);
+                Set<Modifier> ctorMods = executableElement.getModifiers();
+                if ((currentPackage != ctorPackage && !(ctorMods.contains(Modifier.PUBLIC) || ctorMods.contains(Modifier.PROTECTED)))
+                        || ctorMods.contains(Modifier.PRIVATE)) {
+                    continue;
+                }
+                inheritedConstructors.add(executableElement);
+            }
+        }
+        List<QuickPickItem> constructors = null;
+        if (typeElement.getKind() != ElementKind.ENUM && inheritedConstructors.size() == 1) {
+            QuickPickItem item = new QuickPickItem(createLabel(info, inheritedConstructors.get(0)));
+            item.setUserData(new ElementData(inheritedConstructors.get(0)));
+            constructors = Collections.singletonList(item);
+        } else if (inheritedConstructors.size() > 1) {
+            constructors = new ArrayList<>(inheritedConstructors.size());
+            for (ExecutableElement constructorElement : inheritedConstructors) {
+                QuickPickItem item = new QuickPickItem(createLabel(info, constructorElement));
+                item.setUserData(new ElementData(constructorElement));
+                constructors.add(item);
+            }
+        }
+        List<QuickPickItem> fields = null;
+        if (!uninitializedFields.isEmpty()) {
+            fields = new ArrayList<>();
+            for (VariableElement variableElement : uninitializedFields) {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                fields.add(item);
+            }
+        }
+        if (constructors == null && fields == null) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateConstructor(), CODE_GENERATOR_KIND, GENERATE_CONSTRUCTOR, uri, offset, constructors, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectSuperConstructor=Select super constructor",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        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);
+            List<QuickPickItem> constructors = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+            if (constructors.size() < 2 && fields.isEmpty()) {
+                generate(client, uri, offset, constructors, fields);
+            } else {
+                if (constructors.size() > 1) {
+                    client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectSuperConstructor(), true, constructors)).thenAccept(selected -> {
+                        if (selected != null) {
+                            selectFields(client, uri, offset, selected, fields);
+                        }
+                    });
+                } else {
+                    selectFields(client, uri, offset, constructors, fields);
+                }
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    @NbBundle.Messages({
+        "DN_SelectConstructorFields=Select fields to be initialized by constructor",
+    })
+    private void selectFields(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+        if (!fields.isEmpty()) {
+            client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectConstructorFields(), true, fields)).thenAccept(selected -> {
+                if (selected != null) {
+                    generate(client, uri, offset, constructors, selected);
+                }
+            });
+        } else {
+            generate(client, uri, offset, constructors, fields);
+        }
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    List<ExecutableElement> selectedConstructors = constructors.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (ExecutableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    List<VariableElement> selectedFields = fields.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (VariableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    GeneratorUtils.generateConstructors(wc, tp, selectedFields, selectedConstructors, -1);
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
new file mode 100644
index 0000000..d59cec4
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
@@ -0,0 +1,223 @@
+/*
+ * 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.Scope;
+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 java.util.stream.Collectors;
+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 javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+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.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 60)
+public final class DelegateMethodGenerator extends CodeGenerator {
+
+    public static final String GENERATE_DELEGATE_METHOD =  "java.generate.delegateMethod";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_DELEGATE_METHOD);
+    private final Gson gson = new Gson();
+
+    public DelegateMethodGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateDelegateMethod=Generate Delegate Method...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || !typeElement.getKind().isClass()) {
+            return Collections.emptyList();
+        }
+        Elements elements = info.getElements();
+        Trees trees = info.getTrees();
+        Scope scope = trees.getScope(tp);
+        List<QuickPickItem> fields = new ArrayList<>();
+        TypeElement cls;
+        while (scope != null && (cls = scope.getEnclosingClass()) != null) {
+            DeclaredType type = (DeclaredType) cls.asType();
+            for (VariableElement field : ElementFilter.fieldsIn(elements.getAllMembers(cls))) {
+                TypeMirror fieldType = field.asType();
+                if (!ERROR.contentEquals(field.getSimpleName()) && !fieldType.getKind().isPrimitive() && fieldType.getKind() != TypeKind.ARRAY
+                        && (fieldType.getKind() != TypeKind.DECLARED || ((DeclaredType)fieldType).asElement() != cls) && trees.isAccessible(scope, field, type)) {
+                    QuickPickItem item = new QuickPickItem(createLabel(info, field));
+                    item.setUserData(new ElementData(field));
+                    fields.add(item);
+                }
+            }
+            scope = scope.getEnclosingScope();
+        }
+        if (fields.isEmpty()) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        QuickPickItem typeItem = new QuickPickItem(createLabel(info, typeElement));
+        typeItem.setUserData(new ElementData(typeElement));
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateDelegateMethod(), CODE_GENERATOR_KIND, GENERATE_DELEGATE_METHOD, uri, offset, typeItem, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectDelegateMethodField=Select target field to generate delegates for",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        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 type = gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+            if (fields.size() == 1) {
+                selectMethods(client, uri, offset, type, fields.get(0));
+            } else {
+                client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethodField(), false, fields)).thenAccept(selected -> {
+                    if (selected != null && !selected.isEmpty()) {
+                        selectMethods(client, uri, offset, type, selected.get(0));
+                    }
+                });
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    @NbBundle.Messages({
+        "DN_SelectDelegateMethods=Select methods to generate delegates for",
+    })
+    private void selectMethods(NbCodeLanguageClient client, String uri, int offset, QuickPickItem type, QuickPickItem selectedField) {
+        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 -> {
+                info.toPhase(JavaSource.Phase.RESOLVED);
+                TypeElement origin = (TypeElement) gson.fromJson(gson.toJson(type.getUserData()), ElementData.class).resolve(info);
+                VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(info);
+                if (origin != null && field != null) {
+                    final ElementUtilities eu = info.getElementUtilities();
+                    final Trees trees = info.getTrees();
+                    final Scope scope = info.getTreeUtilities().scopeFor(offset);
+                    ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
+                        @Override
+                        public boolean accept(Element e, TypeMirror type) {
+                            if (e.getKind() == ElementKind.METHOD && trees.isAccessible(scope, e, (DeclaredType)type)) {
+                                Element impl = eu.getImplementationOf((ExecutableElement)e, origin);
+                                return impl == null || (!impl.getModifiers().contains(Modifier.FINAL) && impl.getEnclosingElement() != origin);
+                            }
+                            return false;
+                        }
+                    };
+                    List<QuickPickItem> methods = new ArrayList<>();
+                    for (ExecutableElement method : ElementFilter.methodsIn(eu.getMembers(field.asType(), acceptor))) {
+                        QuickPickItem item = new QuickPickItem(String.format("%s.%s", field.getSimpleName().toString(), createLabel(info, method)));
+                        item.setUserData(new ElementData(method));
+                        methods.add(item);
+                    }
+                    client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethods(), true, methods)).thenAccept(selected -> {
+                        if (selected != null && !selected.isEmpty()) {
+                            generate(client, uri, offset, selectedField, selected);
+                        }
+                    });
+                }
+            }, true);
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, QuickPickItem selectedField, List<QuickPickItem> selectedMethods) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(wc);
+                    List<ExecutableElement> methods = selectedMethods.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (ExecutableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    org.netbeans.modules.java.editor.codegen.DelegateMethodGenerator.generateDelegatingMethods(wc, tp, field, methods, -1);
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
new file mode 100644
index 0000000..45fb6d3
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
@@ -0,0 +1,172 @@
+/*
+ * 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.Tree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+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 java.util.stream.Collectors;
+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 javax.lang.model.util.ElementFilter;
+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.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 40)
+public final class EqualsHashCodeGenerator extends CodeGenerator {
+
+    public static final String GENERATE_EQUALS =  "java.generate.equals";
+    public static final String GENERATE_HASH_CODE =  "java.generate.hashCode";
+    public static final String GENERATE_EQUALS_HASH_CODE =  "java.generate.equals.hashCode";
+
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_EQUALS_HASH_CODE, GENERATE_EQUALS, GENERATE_HASH_CODE)));
+    private final Gson gson = new Gson();
+
+    public EqualsHashCodeGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateEquals=Generate equals()...",
+        "DN_GenerateHashCode=Generate hashCode()...",
+        "DN_GenerateEqualsHashCode=Generate equals() and hashCode()...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+        if (type == null || type.getKind() != ElementKind.CLASS) {
+            return Collections.emptyList();
+        }
+        ExecutableElement[] equalsHashCode = org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.overridesHashCodeAndEquals(info, type, null);
+        if (equalsHashCode[0] != null && equalsHashCode[1] != null) {
+            return Collections.emptyList();
+        }
+        List<QuickPickItem> fields = new ArrayList<>();
+        for (VariableElement variableElement : ElementFilter.fieldsIn(type.getEnclosedElements())) {
+            if (!ERROR.contentEquals(variableElement.getSimpleName()) && !variableElement.getModifiers().contains(Modifier.STATIC)) {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                fields.add(item);
+            }
+        }
+        if (fields.isEmpty()) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        if (equalsHashCode[0] == null) {
+            if (equalsHashCode[1] == null) {
+                return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEqualsHashCode(), CODE_GENERATOR_KIND, GENERATE_EQUALS_HASH_CODE, uri, offset, fields));
+            }
+            return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEquals(), CODE_GENERATOR_KIND, GENERATE_EQUALS, uri, offset, fields));
+        }
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateHashCode(), CODE_GENERATOR_KIND, GENERATE_HASH_CODE, uri, offset, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectEquals=Select fields to be included in equals()",
+        "DN_SelectHashCode=Select fields to be included in hashCode()",
+        "DN_SelectEqualsHashCode=Select fields to be included in equals() and hashCode()",
+    })
+    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);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            String text;
+            boolean generateEquals = !GENERATE_HASH_CODE.equals(command);
+            boolean generateHashCode = !GENERATE_EQUALS.equals(command);
+            switch (command) {
+                case GENERATE_EQUALS: text = Bundle.DN_SelectEquals(); break;
+                case GENERATE_HASH_CODE: text = Bundle.DN_SelectHashCode(); break;
+                default: text = Bundle.DN_SelectEqualsHashCode(); break;
+            }
+            client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
+                if (selected != null) {
+                    try {
+                        FileObject file = Utils.fromUri(uri);
+                        JavaSource js = JavaSource.forFileObject(file);
+                        if (js == null) {
+                            throw new IOException("Cannot get JavaSource for: " + uri);
+                        }
+                        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                            wc.toPhase(JavaSource.Phase.RESOLVED);
+                            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                            tp = wc.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
+                            if (tp != null) {
+                                List<VariableElement> selectedFields = selected.stream().map(item -> {
+                                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                                    return (VariableElement)data.resolve(wc);
+                                }).collect(Collectors.toList());
+                                org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.generateEqualsAndHashCode(wc, tp, generateEquals ? selectedFields : null, generateHashCode ? selectedFields : null, -1);
+                            }
+                        });
+                        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+                    } catch (IOException | IllegalArgumentException ex) {
+                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+                    }
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
index e51e341..4b7df97 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
@@ -18,58 +18,180 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
+import com.google.gson.Gson;
 import com.sun.source.tree.ClassTree;
-import com.sun.source.tree.LineMap;
 import com.sun.source.tree.Tree;
 import com.sun.source.util.TreePath;
 import java.io.IOException;
-import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.util.ElementFilter;
 import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
-import org.eclipse.lsp4j.Position;
+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.eclipse.lsp4j.Range;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
-import org.eclipse.lsp4j.services.LanguageClient;
 import org.netbeans.api.java.source.CodeStyle;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.api.java.source.ElementUtilities;
 import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.java.source.TreeUtilities;
 import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
-import static org.netbeans.modules.java.lsp.server.protocol.TextDocumentServiceImpl.fromUri;
+import org.netbeans.modules.java.lsp.server.Utils;
 import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
 import org.openide.util.Pair;
+import org.openide.util.lookup.ServiceProvider;
 
 /**
  *
  * @author lahvac
  */
-public class GetterSetterGenerator {
+@ServiceProvider(service = CodeGenerator.class, position = 30)
+public final class GetterSetterGenerator extends CodeGenerator {
 
-    private static final String ERROR = "<error>"; //NOI18N
+    public static final String GENERATE_GETTERS =  "java.generate.getters";
+    public static final String GENERATE_SETTERS =  "java.generate.setters";
+    public static final String GENERATE_GETTERS_SETTERS =  "java.generate.getters.setters";
 
-    public static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, Range range, boolean all) {
-        TreePath tp = info.getTreeUtilities().pathFor(getOffset(info, range.getStart()));
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet(Arrays.asList(GENERATE_GETTERS, GENERATE_SETTERS, GENERATE_GETTERS_SETTERS)));
+    private final Gson gson = new Gson();
+
+    public GetterSetterGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateGetters=Generate Getters...",
+        "DN_GenerateSetters=Generate Setters...",
+        "DN_GenerateGettersSetters=Generate Getters and Setters...",
+        "DN_GenerateGetterFor=Generate Getter for \"{0}\"",
+        "DN_GenerateSetterFor=Generate Setter for \"{0}\"",
+        "DN_GenerateGetterSetterFor=Generate Getter and Setter for \"{0}\"",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        boolean all = only != null && only.contains(CodeActionKind.Source);
+        Pair<Set<VariableElement>, Set<VariableElement>> pair = findMissingGettersSetters(info, params.getRange(), all);
+        boolean missingGetters = !pair.first().isEmpty();
+        boolean missingSetters = !pair.second().isEmpty();
+        String uri = Utils.toUri(info.getFileObject());
+        int offset = getOffset(info, params.getRange().getStart());
+        List<CodeAction> result = new ArrayList<>();
+        if (missingGetters) {
+            String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGetters();
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                return item;
+            }).collect(Collectors.toList())));
+        }
+        if (missingSetters) {
+            String name = pair.second().size() == 1 ? Bundle.DN_GenerateSetterFor(pair.second().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateSetters();
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_SETTERS, uri, offset, all, pair.second().stream().map(variableElement -> {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                return item;
+            }).collect(Collectors.toList())));
+        }
+        if (missingGetters && missingSetters) {
+            pair.first().retainAll(pair.second());
+            String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterSetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGettersSetters();
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS_SETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+                QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
+                item.setUserData(new ElementData(variableElement));
+                return item;
+            }).collect(Collectors.toList())));
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectGetters=Select fields to generate getters for",
+        "DN_SelectSetters=Select fields to generate setters for",
+        "DN_SelectGettersSetters=Select fields to generate getters and setters for",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        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);
+            boolean all = gson.fromJson(gson.toJson(arguments.get(2)), boolean.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+            int kind;
+            String text;
+            switch (command) {
+                case GENERATE_GETTERS: kind = GeneratorUtils.GETTERS_ONLY; text = Bundle.DN_SelectGetters(); break;
+                case GENERATE_SETTERS: kind = GeneratorUtils.SETTERS_ONLY; text = Bundle.DN_SelectSetters(); break;
+                default: kind = 0; text = Bundle.DN_SelectGettersSetters(); break;
+            }
+            if (all && fields.size() > 1) {
+                client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
+                    if (selected != null && !selected.isEmpty()) {
+                        generate(client, kind, uri, offset, selected);
+                    }
+                });
+            } else if (fields.size() == 1) {
+                generate(client, kind, uri, offset, fields);
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
 
-        while (tp != null && !TreeUtilities.CLASS_TREE_KINDS.contains(tp.getLeaf().getKind())) {
-            tp = tp.getParentPath();
+    private void generate(NbCodeLanguageClient client, int kind, String uri, int offset, List<QuickPickItem> fields) throws IllegalArgumentException {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    List<VariableElement> variableElements = fields.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (VariableElement) data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    GeneratorUtils.generateGettersAndSetters(wc, tp, variableElements, kind, -1);
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
+    }
 
+    private static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, Range range, boolean all) {
+        TreePath tp = info.getTreeUtilities().pathFor(getOffset(info, range.getStart()));
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
         if (tp == null) {
             return Pair.of(Collections.emptySet(), Collections.emptySet());
         }
 
         TypeElement type = (TypeElement) info.getTrees().getElement(tp);
-
         if (type == null) {
             return Pair.of(Collections.emptySet(), Collections.emptySet());
         }
@@ -90,7 +212,7 @@ public class GetterSetterGenerator {
             }
         }
 
-        Pair<Set<VariableElement>, Set<VariableElement>> pair = GetterSetterGenerator.findMissingGettersSetters(info, type);
+        Pair<Set<VariableElement>, Set<VariableElement>> pair = findMissingGettersSetters(info, type);
 
         pair.first().retainAll(selectedFields);
         pair.second().retainAll(selectedFields);
@@ -125,40 +247,4 @@ public class GetterSetterGenerator {
 
         return Pair.of(missingGetters, missingSetters);
     }
-
-    public static void generateGettersSetters(LanguageClient client, String uri, GenKind kind, Range range, boolean all) throws MalformedURLException, IOException {
-        FileObject file = fromUri(uri);
-        JavaSource js = JavaSource.forFileObject(file);
-
-        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
-            wc.toPhase(JavaSource.Phase.RESOLVED);
-            Pair<Set<VariableElement>, Set<VariableElement>> missingGettersSetters = findMissingGettersSetters(wc, range, all);
-            Set<VariableElement> fields = new LinkedHashSet<>();
-            fields.addAll(missingGettersSetters.first());
-            fields.addAll(missingGettersSetters.second());
-            if (!fields.isEmpty()) {
-                TreePath tp = wc.getTrees().getPath(fields.iterator().next().getEnclosingElement());
-                GeneratorUtils.generateGettersAndSetters(wc, tp, fields, kind.type, getOffset(wc, range.getStart()));
-            }
-        });
-
-        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-    }
-
-    private static int getOffset(CompilationInfo info, Position pos) {
-        LineMap lm = info.getCompilationUnit().getLineMap();
-        return (int) lm.getPosition(pos.getLine() + 1, pos.getCharacter() + 1);
-    }
-
-    public enum GenKind {
-        GETTERS(GeneratorUtils.GETTERS_ONLY),
-        SETTERS(GeneratorUtils.SETTERS_ONLY),
-        GETTERS_SETTERS(0);
-
-        private final int type;
-
-        private GenKind(int type) {
-            this.type = type;
-        }
-    }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
new file mode 100644
index 0000000..231287d
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
@@ -0,0 +1,183 @@
+/*
+ * 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.util.TreePath;
+import java.io.IOException;
+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 java.util.stream.Collectors;
+import javax.lang.model.SourceVersion;
+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 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.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 70)
+public final class ImplementOverrideMethodGenerator extends CodeGenerator {
+
+    public static final String GENERATE_IMPLEMENT_METHOD =  "java.generate.implementMethod";
+    public static final String GENERATE_OVERRIDE_METHOD =  "java.generate.overrideMethod";
+
+    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_IMPLEMENT_METHOD, GENERATE_OVERRIDE_METHOD)));
+    private final Gson gson = new Gson();
+
+    public ImplementOverrideMethodGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateImplementMethod=Generate Implement Method...",
+        "DN_GenerateOverrideMethod=Generate Override Method...",
+        "DN_From=(from {0})",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
+            return Collections.emptyList();
+        }
+        List<CodeAction> result = new ArrayList<>();
+        String uri = Utils.toUri(info.getFileObject());
+        ElementUtilities eu = info.getElementUtilities();
+        if (typeElement.getKind().isClass() || typeElement.getKind().isInterface() && SourceVersion.RELEASE_8.compareTo(info.getSourceVersion()) <= 0) {
+            List<QuickPickItem> implementMethods = new ArrayList<>();
+            for (ExecutableElement method : eu.findUnimplementedMethods(typeElement, true)) {
+                boolean mustImplement = !method.getModifiers().contains(Modifier.DEFAULT);
+                Element enclosingElement = method.getEnclosingElement();
+                String enclosingTypeName = enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface() ? Bundle.DN_From(((TypeElement)enclosingElement).getQualifiedName().toString()) : null;
+                implementMethods.add(new QuickPickItem(createLabel(info, method), enclosingTypeName, null, mustImplement, new ElementData(method)));
+            }
+            if (!implementMethods.isEmpty()) {
+                result.add(createCodeAction(Bundle.DN_GenerateImplementMethod(), CODE_GENERATOR_KIND, GENERATE_IMPLEMENT_METHOD, uri, offset, implementMethods));
+            }
+        }
+        if (typeElement.getKind().isClass() || typeElement.getKind().isInterface()) {
+            List<QuickPickItem> overrideMethods = new ArrayList<>();
+            for (ExecutableElement method : eu.findOverridableMethods(typeElement)) {
+                Element enclosingElement = method.getEnclosingElement();
+                String enclosingTypeName = enclosingElement.getKind().isClass() || enclosingElement.getKind().isInterface() ? Bundle.DN_From(((TypeElement) enclosingElement).getQualifiedName().toString()) : null;
+                QuickPickItem item = new QuickPickItem(createLabel(info, method));
+                if (enclosingTypeName != null) {
+                    item.setDescription(enclosingTypeName);
+                }
+                item.setUserData(new ElementData(method));
+                overrideMethods.add(item);
+            }
+            if (!overrideMethods.isEmpty()) {
+                result.add(createCodeAction(Bundle.DN_GenerateOverrideMethod(), CODE_GENERATOR_KIND, GENERATE_OVERRIDE_METHOD, uri, offset, overrideMethods));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectImplementMethod=Select methods to implement",
+        "DN_SelectOverrideMethod=Select methods to override",
+    })
+    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);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> methods = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            String text = command == GENERATE_IMPLEMENT_METHOD ? Bundle.DN_SelectImplementMethod() : Bundle.DN_SelectOverrideMethod();
+            boolean isImplement = command == GENERATE_IMPLEMENT_METHOD;
+            client.showQuickPick(new ShowQuickPickParams(text, true, methods)).thenAccept(selected -> {
+                if (selected != null && !selected.isEmpty()) {
+                    generate(client, uri, offset, isImplement, selected);
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, boolean isImplement, List<QuickPickItem> methods) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    List<ExecutableElement> selectedMethods = methods.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (ExecutableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    if (isImplement) {
+                        GeneratorUtils.generateAbstractMethodImplementations(wc, tp, selectedMethods, -1);
+                    } else {
+                        GeneratorUtils.generateMethodOverrides(wc, tp, selectedMethods, -1);
+                    }
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
new file mode 100644
index 0000000..e0fcc30
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
@@ -0,0 +1,144 @@
+/*
+ * 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.VariableTree;
+import com.sun.source.util.TreePath;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+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.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.GeneratorUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.BaseUtilities;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 20)
+public final class LoggerGenerator extends CodeGenerator {
+
+    public static final String GENERATE_LOGGER =  "java.generate.logger";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_LOGGER);
+    private final Gson gson = new Gson();
+
+    public LoggerGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateLogger=Generate Logger...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement typeElement = (TypeElement) info.getTrees().getElement(tp);
+        if (typeElement == null || !typeElement.getKind().isClass()) {
+            return Collections.emptyList();
+        }
+        for (VariableElement ve : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
+            TypeMirror type = ve.asType();
+            if (type.getKind() == TypeKind.DECLARED && ((TypeElement)((DeclaredType)type).asElement()).getQualifiedName().contentEquals(Logger.class.getName())
+                    || type.getKind() == TypeKind.ERROR && ((TypeElement)((DeclaredType)type).asElement()).getSimpleName().contentEquals(Logger.class.getSimpleName())) {
+                return Collections.emptyList();
+            }
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateLogger(), CODE_GENERATOR_KIND, GENERATE_LOGGER, uri, offset));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectLoggerName=Logger field name",
+    })
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        if (arguments.size() > 1) {
+            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            client.showInputBox(new ShowInputBoxParams(Bundle.DN_SelectLoggerName(), "LOG")).thenAccept(value -> {
+                if (value != null && BaseUtilities.isJavaIdentifier(value)) {
+                    try {
+                        FileObject file = Utils.fromUri(uri);
+                        JavaSource js = JavaSource.forFileObject(file);
+                        if (js == null) {
+                            throw new IOException("Cannot get JavaSource for: " + uri);
+                        }
+                        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                            wc.toPhase(JavaSource.Phase.RESOLVED);
+                            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                            tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                            if (tp != null) {
+                                ClassTree cls = (ClassTree) tp.getLeaf();
+                                VariableTree field = org.netbeans.modules.java.editor.codegen.LoggerGenerator.createLoggerField(wc.getTreeMaker(), cls, value, EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL));
+                                wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, field));
+                            }
+                        });
+                        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+                    } catch (IOException | IllegalArgumentException ex) {
+                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+                    }
+                }
+            });
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
index c4ba4b5..afdeb2d 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
@@ -52,7 +52,7 @@ class NbCodeClientWrapper implements NbCodeLanguageClient {
             this.clientCaps = clientCaps;
         }
     }
-    
+
     @Override
     public NbCodeClientCapabilities getNbCodeCapabilities() {
         return clientCaps;
@@ -64,6 +64,16 @@ class NbCodeClientWrapper implements NbCodeLanguageClient {
     }
 
     @Override
+    public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+        return remote.showQuickPick(params);
+    }
+
+    @Override
+    public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+        return remote.showInputBox(params);
+    }
+
+    @Override
     public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
         return remote.applyEdit(params);
     }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
index 06177ae..50fe926 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
@@ -18,8 +18,11 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
 import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
 import org.eclipse.lsp4j.services.LanguageClient;
 
@@ -40,7 +43,25 @@ public interface NbCodeLanguageClient extends LanguageClient {
      */
     @JsonNotification("window/showStatusBarMessage")
     public void showStatusBarMessage(@NonNull ShowStatusMessageParams params);
-    
+
+    /**
+     * Shows a selection list allowing multiple selections.
+     *
+     * @param params input parameters
+     * @return selected items
+     */
+    @JsonRequest("window/showQuickPick")
+    public CompletableFuture<List<QuickPickItem>> showQuickPick(@NonNull ShowQuickPickParams params);
+
+    /**
+     * Shows an input box to ask the user for input.
+     *
+     * @param params input parameters
+     * @return input value
+     */
+    @JsonRequest("window/showInputBox")
+    public CompletableFuture<String> showInputBox(@NonNull ShowInputBoxParams params);
+
     /**
      * Returns extended code capabilities.
      * @return code capabilities.
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
new file mode 100644
index 0000000..3b17385
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
@@ -0,0 +1,206 @@
+/*
+ * 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.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * Represents an item that can be selected from a list of items.
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class QuickPickItem {
+
+    /**
+     * A human-readable string which is rendered prominent.
+     */
+    @NonNull
+    private String label;
+
+    /**
+     * A human-readable string which is rendered less prominent in the same line.
+     */
+    private String description;
+
+    /**
+     * A human-readable string which is rendered less prominent in a separate line.
+     */
+    private String detail;
+
+    /**
+     * Optional flag indicating if this item is picked initially.
+     */
+    private boolean picked;
+
+    /**
+     * Optional user data.
+     */
+    private Object userData;
+
+    public QuickPickItem() {
+    }
+
+    public QuickPickItem(@NonNull final String label) {
+        this.label = Preconditions.checkNotNull(label, "label");
+    }
+
+    public QuickPickItem(@NonNull final String label, final String description, final String detail, final boolean picked, final Object userData) {
+        this(label);
+        this.description = description;
+        this.detail = detail;
+        this.picked = picked;
+        this.userData = userData;
+    }
+
+    /**
+     * A human-readable string which is rendered prominent.
+     */
+    @Pure
+    @NonNull
+    public String getLabel() {
+        return label;
+    }
+
+    /**
+     * A human-readable string which is rendered prominent.
+     */
+    public void setLabel(@NonNull final String label) {
+        this.label = label;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in the same line.
+     */
+    @Pure
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in the same line.
+     */
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in a separate line.
+     */
+    @Pure
+    public String getDetail() {
+        return detail;
+    }
+
+    /**
+     * A human-readable string which is rendered less prominent in a separate line.
+     */
+    public void setDetail(final String detail) {
+        this.detail = detail;
+    }
+
+    /**
+     * Optional flag indicating if this item is picked initially.
+     */
+    @Pure
+    public boolean isPicked() {
+        return picked;
+    }
+
+    /**
+     * Optional flag indicating if this item is picked initially.
+     */
+    public void setPicked(boolean picked) {
+        this.picked = picked;
+    }
+
+    /**
+     * Optional user data.
+     */
+    @Pure
+    public Object getUserData() {
+        return userData;
+    }
+
+    /**
+     * Optional user data.
+     */
+    public void setUserData(final Object userData) {
+        this.userData = userData;
+    }
+
+    @Override
+    @Pure
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("label", label);
+        b.add("description", description);
+        b.add("detail", detail);
+        b.add("picked", picked);
+        b.add("userData", userData);
+        return b.toString();
+    }
+
+    @Override
+    @Pure
+    public int hashCode() {
+        int hash = 7;
+        hash = 83 * hash + Objects.hashCode(this.label);
+        hash = 83 * hash + Objects.hashCode(this.description);
+        hash = 83 * hash + Objects.hashCode(this.detail);
+        hash = 83 * hash + (this.picked ? 1 : 0);
+        hash = 83 * hash + Objects.hashCode(this.userData);
+        return hash;
+    }
+
+    @Override
+    @Pure
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final QuickPickItem other = (QuickPickItem) obj;
+        if (this.picked != other.picked) {
+            return false;
+        }
+        if (!Objects.equals(this.label, other.label)) {
+            return false;
+        }
+        if (!Objects.equals(this.description, other.description)) {
+            return false;
+        }
+        if (!Objects.equals(this.detail, other.detail)) {
+            return false;
+        }
+        if (!Objects.equals(this.userData, other.userData)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index 08ac47f..c73e500 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -32,6 +32,8 @@ import java.util.concurrent.Future;
 import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionOptions;
 import org.eclipse.lsp4j.CompletionOptions;
 import org.eclipse.lsp4j.ExecuteCommandOptions;
 import org.eclipse.lsp4j.InitializeParams;
@@ -248,12 +250,16 @@ public final class Server {
                 completionOptions.setResolveProvider(true);
                 completionOptions.setTriggerCharacters(Collections.singletonList("."));
                 capabilities.setCompletionProvider(completionOptions);
-                capabilities.setCodeActionProvider(true);
+                capabilities.setCodeActionProvider(new CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix, CodeActionKind.Source)));
                 capabilities.setDocumentSymbolProvider(true);
                 capabilities.setDefinitionProvider(true);
                 capabilities.setDocumentHighlightProvider(true);
                 capabilities.setReferencesProvider(true);
-                capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT, GENERATE_GETTERS, GENERATE_SETTERS, GENERATE_GETTERS_SETTERS)));
+                List<String> commands = new ArrayList<>(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT));
+                for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+                    commands.addAll(codeGenerator.getCommands());
+                }
+                capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(commands));
                 capabilities.setWorkspaceSymbolProvider(true);
             }
             return new InitializeResult(capabilities);
@@ -332,12 +338,9 @@ public final class Server {
     
     public static final String JAVA_BUILD_WORKSPACE =  "java.build.workspace";
     public static final String GRAALVM_PAUSE_SCRIPT =  "graalvm.pause.script";
-    public static final String GENERATE_GETTERS =  "java.generate.getters";
-    public static final String GENERATE_SETTERS =  "java.generate.setters";
-    public static final String GENERATE_GETTERS_SETTERS =  "java.generate.getters.setters";
     static final String INDEXING_COMPLETED = "Indexing completed.";
     static final String NO_JAVA_SUPPORT = "Cannot initialize Java support on JDK ";
-    
+
     static final NbCodeLanguageClient STUB_CLIENT = new NbCodeLanguageClient() {
         private final NbCodeClientCapabilities caps = new NbCodeClientCapabilities();
         
@@ -352,6 +355,18 @@ public final class Server {
         }
 
         @Override
+        public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+            logWarning(params);
+            return CompletableFuture.completedFuture(params.getCanPickMany() || params.getItems().isEmpty() ? params.getItems() : Collections.singletonList(params.getItems().get(0)));
+        }
+
+        @Override
+        public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+            logWarning(params);
+            return CompletableFuture.completedFuture(params.getValue());
+        }
+
+        @Override
         public NbCodeClientCapabilities getNbCodeCapabilities() {
             logWarning();
             return caps;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
new file mode 100644
index 0000000..a262919
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
@@ -0,0 +1,124 @@
+/*
+ * 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.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class ShowInputBoxParams {
+
+    /**
+     * The text to display underneath the input box.
+     */
+    @NonNull
+    private String prompt;
+
+    /**
+     * The value to prefill in the input box.
+     */
+    @NonNull
+    private String value;
+
+    public ShowInputBoxParams() {
+        this("", "");
+    }
+
+    public ShowInputBoxParams(@NonNull final String prompt, @NonNull final String value) {
+        this.prompt = Preconditions.checkNotNull(prompt, "prompt");
+        this.value = Preconditions.checkNotNull(value, "value");
+    }
+
+    /**
+     * The text to display underneath the input box.
+     */
+    @Pure
+    @NonNull
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * The text to display underneath the input box.
+     */
+    public void setPrompt(@NonNull final String prompt) {
+        this.prompt = prompt;
+    }
+
+    /**
+     * The value to prefill in the input box.
+     */
+    @Pure
+    @NonNull
+    public String getValue() {
+        return value;
+    }
+
+    /**
+     * The value to prefill in the input box.
+     */
+    public void setValue(@NonNull final String value) {
+        this.value = value;
+    }
+
+    @Override
+    @Pure
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("prompt", prompt);
+        b.add("value", value);
+        return b.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 59 * hash + Objects.hashCode(this.prompt);
+        hash = 59 * hash + Objects.hashCode(this.value);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ShowInputBoxParams other = (ShowInputBoxParams) obj;
+        if (!Objects.equals(this.prompt, other.prompt)) {
+            return false;
+        }
+        if (!Objects.equals(this.value, other.value)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
new file mode 100644
index 0000000..107084f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
@@ -0,0 +1,159 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * A selection list parameters.
+ *
+ * @author Dusan Balek
+ */
+@SuppressWarnings("all")
+public class ShowQuickPickParams {
+
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    @NonNull
+    private String placeHolder;
+
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    private boolean canPickMany;
+
+    /**
+     * A list of items.
+     */
+    @NonNull
+    private List<QuickPickItem> items;
+
+    public ShowQuickPickParams() {
+        this("", new ArrayList<>());
+    }
+
+    public ShowQuickPickParams(@NonNull final String placeHolder, @NonNull final List<QuickPickItem> items) {
+        this.placeHolder = Preconditions.checkNotNull(placeHolder, "placeHolder");
+        this.items = Preconditions.checkNotNull(items, "items");
+    }
+
+    public ShowQuickPickParams(final String placeHolder, final boolean canPickMany, @NonNull final List<QuickPickItem> items) {
+        this(placeHolder, items);
+        this.canPickMany = canPickMany;
+    }
+
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    @Pure
+    @NonNull
+    public String getPlaceHolder() {
+        return placeHolder;
+    }
+
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    public void setPlaceHolder(@NonNull final String placeHolder) {
+        this.placeHolder = placeHolder;
+    }
+
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    @Pure
+    public boolean getCanPickMany() {
+        return canPickMany;
+    }
+
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    public void setCanPickMany(final boolean canPickMany) {
+        this.canPickMany = canPickMany;
+    }
+
+    /**
+     * A list of items.
+     */
+    @Pure
+    @NonNull
+    public List<QuickPickItem> getItems() {
+        return items;
+    }
+
+    /**
+     * A list of items.
+     */
+    public void setItems(@NonNull final List<QuickPickItem> items) {
+        this.items = items;
+    }
+
+    @Override
+    @Pure
+    public String toString() {
+        ToStringBuilder b = new ToStringBuilder(this);
+        b.add("placeHolder", placeHolder);
+        b.add("canPickMany", canPickMany);
+        b.add("items", items);
+        return b.toString();
+    }
+
+    @Override
+    @Pure
+    public int hashCode() {
+        int hash = 7;
+        hash = 29 * hash + Objects.hashCode(this.placeHolder);
+        hash = 29 * hash + (this.canPickMany ? 1 : 0);
+        hash = 29 * hash + Objects.hashCode(this.items);
+        return hash;
+    }
+
+    @Override
+    @Pure
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ShowQuickPickParams other = (ShowQuickPickParams) obj;
+        if (this.canPickMany != other.canPickMany) {
+            return false;
+        }
+        if (!Objects.equals(this.placeHolder, other.placeHolder)) {
+            return false;
+        }
+        if (!Objects.equals(this.items, other.items)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 287585b..9054896 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -30,16 +30,8 @@ import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UncheckedIOException;
 import java.net.MalformedURLException;
-import java.net.URI;
-import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -51,7 +43,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
@@ -179,14 +170,10 @@ import org.netbeans.spi.editor.hints.LazyFixList;
 import org.netbeans.spi.java.hints.JavaFix;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
-import org.openide.filesystems.FileUtil;
-import org.openide.filesystems.URLMapper;
-import org.openide.modules.Places;
 import org.openide.text.NbDocument;
 import org.openide.text.PositionBounds;
 import org.openide.util.Exceptions;
-import org.openide.util.NbBundle.Messages;
-import org.openide.util.Pair;
+import org.openide.util.Lookup;
 import org.openide.util.RequestProcessor;
 import org.openide.util.lookup.Lookups;
 
@@ -960,11 +947,6 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     }
 
     @Override
-    @Messages({
-        "DN_GenerateGetters=Generate getter(s).",
-        "DN_GenerateSetters=Generate setter(s).",
-        "DN_GenerateGettersSetters=Generate getter(s) and setter(s).",
-    })
     public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
         Document doc = openedDocuments.get(params.getTextDocument().getUri());
         if (doc == null) {
@@ -1063,20 +1045,10 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         try {
             js.runUserActionTask(cc -> {
                 cc.toPhase(JavaSource.Phase.RESOLVED);
-
-                Pair<Set<VariableElement>, Set<VariableElement>> pair = GetterSetterGenerator.findMissingGettersSetters(cc, params.getRange(), false);
-                boolean missingGetters = !pair.first().isEmpty();
-                boolean missingSetters = !pair.second().isEmpty();
-                String uri = toUri(cc.getFileObject());
-
-                if (missingGetters) {
-                    result.add(Either.forRight(createCodeGeneratorAction(Bundle.DN_GenerateGetters(), Server.GENERATE_GETTERS, uri, params.getRange())));
-                }
-                if (missingSetters) {
-                    result.add(Either.forRight(createCodeGeneratorAction(Bundle.DN_GenerateSetters(), Server.GENERATE_SETTERS, uri, params.getRange())));
-                }
-                if (missingGetters && missingSetters) {
-                    result.add(Either.forRight(createCodeGeneratorAction(Bundle.DN_GenerateGettersSetters(), Server.GENERATE_GETTERS_SETTERS, uri, params.getRange())));
+                for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+                    for (CodeAction codeAction : codeGenerator.getCodeActions(cc, params)) {
+                        result.add(Either.forRight(codeAction));
+                    }
                 }
             }, true);
         } catch (IOException ex) {
@@ -1087,17 +1059,6 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         return CompletableFuture.completedFuture(result);
     }
 
-    private CodeAction createCodeGeneratorAction(String name, String command, String uri, Range range) {
-        CodeAction action = new CodeAction(name);
-        List<Object> arguments = new ArrayList<>();
-
-        arguments.add(uri);
-        arguments.add(range);
-        action.setCommand(new Command(name, command, arguments));
-        return action;
-    }
-
-
     //TODO: copied from spi.editor.hints/.../FixData:
     private List<Fix> sortFixes(Collection<Fix> fixes) {
         List<Fix> result = new ArrayList<Fix>(fixes);
@@ -1348,88 +1309,6 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter();
     }
 
-    private static String toUri(FileObject file) {
-        if (FileUtil.isArchiveArtifact(file)) {
-            //VS code cannot open jar:file: URLs, workaround:
-            //another workaround, should be:
-            //File cacheDir = Places.getCacheSubfile("java-server");
-            //but that locks up VS Code, using a temp directory:
-            File cacheDir;
-            try {
-                cacheDir = Files.createTempDirectory("nbcode").toFile();
-            } catch (IOException ex) {
-                throw new UncheckedIOException(ex);
-            }
-            File segments = new File(cacheDir, "segments");
-            Properties props = new Properties();
-
-            try (InputStream in = new FileInputStream(segments)) {
-                props.load(in);
-            } catch (IOException ex) {
-                //OK, may not exist yet
-            }
-            FileObject archive = FileUtil.getArchiveFile(file);
-            String archiveString = archive.toURL().toString();
-            File foundSegment = null;
-            for (String segment : props.stringPropertyNames()) {
-                if (archiveString.equals(props.getProperty(segment))) {
-                    foundSegment = new File(cacheDir, segment);
-                    break;
-                }
-            }
-            if (foundSegment == null) {
-                int i = 0;
-                while (props.getProperty("s" + i) != null)
-                    i++;
-                foundSegment = new File(cacheDir, "s" + i);
-                props.put("s" + i, archiveString);
-                try (OutputStream in = new FileOutputStream(segments)) {
-                    props.store(in, "");
-                } catch (IOException ex) {
-                    Exceptions.printStackTrace(ex);
-                }
-            }
-            File cache = new File(foundSegment, FileUtil.getRelativePath(FileUtil.getArchiveRoot(archive), file));
-            cache.getParentFile().mkdirs();
-            try (OutputStream out = new FileOutputStream(cache)) {
-                out.write(file.asBytes());
-                return cache.toURI().toString();
-            } catch (IOException ex) {
-                Exceptions.printStackTrace(ex);
-            }
-        }
-        return file.toURI().toString();
-    }
-
-    //TODO: move to a separate Utils class:
-    public static FileObject fromUri(String uri) throws MalformedURLException {
-        File cacheDir = Places.getCacheSubfile("java-server");
-        URI uriUri = URI.create(uri);
-        URI relative = cacheDir.toURI().relativize(uriUri);
-        if (relative != null && new File(cacheDir, relative.toString()).canRead()) {
-            String segmentAndPath = relative.toString();
-            int slash = segmentAndPath.indexOf('/');
-            String segment = segmentAndPath.substring(0, slash);
-            String path = segmentAndPath.substring(slash + 1);
-            File segments = new File(cacheDir, "segments");
-            Properties props = new Properties();
-
-            try (InputStream in = new FileInputStream(segments)) {
-                props.load(in);
-                String archiveUri = props.getProperty(segment);
-                FileObject archive = URLMapper.findFileObject(URI.create(archiveUri).toURL());
-                archive = archive != null ? FileUtil.getArchiveRoot(archive) : null;
-                FileObject file = archive != null ? archive.getFileObject(path) : null;
-                if (file != null) {
-                    return file;
-                }
-            } catch (IOException ex) {
-                Exceptions.printStackTrace(ex);
-            }
-        }
-        return URLMapper.findFileObject(URI.create(uri).toURL());
-    }
-
     public static List<TextEdit> modify2TextEdits(JavaSource js, Task<WorkingCopy> task) throws IOException {
         FileObject[] file = new FileObject[1];
         LineMap[] lm = new LineMap[1];
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
new file mode 100644
index 0000000..846bd3f
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.MethodTree;
+import com.sun.source.util.TreePath;
+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 java.util.stream.Collectors;
+import javax.lang.model.element.Element;
+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.MessageParams;
+import org.eclipse.lsp4j.MessageType;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.GeneratorUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeGenerator.class, position = 50)
+public final class ToStringGenerator extends CodeGenerator {
+
+    public static final String GENERATE_TO_STRING =  "java.generate.toString";
+
+    private final Set<String> commands = Collections.singleton(GENERATE_TO_STRING);
+    private final Gson gson = new Gson();
+
+    public ToStringGenerator() {
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_GenerateToString=Generate toString()...",
+    })
+    public List<CodeAction> getCodeActions(CompilationInfo info, CodeActionParams params) {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source)) {
+            return Collections.emptyList();
+        }
+        int offset = getOffset(info, params.getRange().getStart());
+        TreePath tp = info.getTreeUtilities().pathFor(offset);
+        tp = info.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+        if (tp == null) {
+            return Collections.emptyList();
+        }
+        TypeElement type = (TypeElement) info.getTrees().getElement(tp);
+        if (type == null || !type.getKind().isClass()) {
+            return Collections.emptyList();
+        }
+        List<QuickPickItem> fields = new ArrayList<>();
+        for (Element element : type.getEnclosedElements()) {
+            switch (element.getKind()) {
+                case METHOD:
+                    if (element.getSimpleName().contentEquals("toString") && ((ExecutableElement) element).getParameters().isEmpty()) { //NOI18N
+                        return Collections.emptyList();
+                    }
+                    break;
+                case FIELD:
+                    if (!ERROR.contentEquals(element.getSimpleName()) && !element.getModifiers().contains(Modifier.STATIC)) {
+                        QuickPickItem item = new QuickPickItem(createLabel(info, (VariableElement)element));
+                        item.setUserData(new ElementData(element));
+                        fields.add(item);
+                    }
+                    break;
+            }
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateToString(), CODE_GENERATOR_KIND, GENERATE_TO_STRING, uri, offset, fields));
+    }
+
+    @Override
+    public Set<String> getCommands() {
+        return commands;
+    }
+
+    @Override
+    @NbBundle.Messages({
+        "DN_SelectToString=Select fields to be included in toString()",
+    })
+    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);
+            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+            if (fields.isEmpty()) {
+                generate(client, uri, offset, fields);
+            } else {
+                client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectToString(), true, fields)).thenAccept(selected -> {
+                    if (selected != null) {
+                        generate(client, uri, offset, selected);
+                    }
+                });
+            }
+        } else {
+            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> fields) {
+        try {
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                TreePath tp = wc.getTreeUtilities().pathFor(offset);
+                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+                if (tp != null) {
+                    ClassTree cls = (ClassTree) tp.getLeaf();
+                    List<VariableElement> selectedFields = fields.stream().map(item -> {
+                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                        return (VariableElement)data.resolve(wc);
+                    }).collect(Collectors.toList());
+                    MethodTree method = org.netbeans.modules.java.editor.codegen.ToStringGenerator.createToStringMethod(wc, selectedFields, cls.getSimpleName().toString(), true);
+                    wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, method));
+                }
+            });
+            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+        } catch (IOException | IllegalArgumentException ex) {
+            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index 41e481c..e542b39 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -18,7 +18,6 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
-import com.google.gson.Gson;
 import com.sun.source.util.TreePath;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -36,9 +35,6 @@ import org.eclipse.lsp4j.DidChangeConfigurationParams;
 import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
 import org.eclipse.lsp4j.ExecuteCommandParams;
 import org.eclipse.lsp4j.Location;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
-import org.eclipse.lsp4j.Range;
 import org.eclipse.lsp4j.SymbolInformation;
 import org.eclipse.lsp4j.WorkspaceSymbolParams;
 import org.eclipse.lsp4j.services.LanguageClient;
@@ -56,7 +52,6 @@ import org.netbeans.api.java.source.JavaSource.Phase;
 import org.netbeans.api.java.source.SourceUtils;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ui.OpenProjects;
-import org.netbeans.modules.java.lsp.server.protocol.GetterSetterGenerator.GenKind;
 import org.netbeans.modules.java.lsp.server.Utils;
 import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
 import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler;
@@ -81,7 +76,6 @@ public final class WorkspaceServiceImpl implements WorkspaceService, LanguageCli
     private static final RequestProcessor WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false);
 
     private NbCodeLanguageClient client;
-    private final Gson gson = new Gson();
 
     public WorkspaceServiceImpl() {
     }
@@ -133,29 +127,14 @@ public final class WorkspaceServiceImpl implements WorkspaceService, LanguageCli
                 progressOfCompilation.checkStatus();
                 return compileFinished;
             }
-            case Server.GENERATE_GETTERS:
-            case Server.GENERATE_SETTERS:
-            case Server.GENERATE_GETTERS_SETTERS:
-                if (params.getArguments().size() >= 2) {
-                    String uri = gson.fromJson(gson.toJson(params.getArguments().get(0)), String.class);
-                    Range sel = gson.fromJson(gson.toJson(params.getArguments().get(1)), Range.class);
-                    boolean all = params.getArguments().size() == 3;
-                    try {
-                        GenKind kind;
-                        switch (command) {
-                            case Server.GENERATE_GETTERS: kind = GenKind.GETTERS; break;
-                            case Server.GENERATE_SETTERS: kind = GenKind.SETTERS; break;
-                            default: kind = GenKind.GETTERS_SETTERS; break;
-                        }
-                        GetterSetterGenerator.generateGettersSetters(client, uri, kind, sel, all);
-                    } catch (IOException ex) {
-                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+            default:
+                for (CodeGenerator codeGenerator : Lookup.getDefault().lookupAll(CodeGenerator.class)) {
+                    if (codeGenerator.getCommands().contains(command)) {
+                        return codeGenerator.processCommand(client, command, params.getArguments());
                     }
                 }
-                return CompletableFuture.completedFuture(true);
-            default:
-                throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
         }
+        throw new UnsupportedOperationException("Command not supported: " + params.getCommand());
     }
 
     @Override
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 29e4d05..8e463ba 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
@@ -18,7 +18,6 @@
  */
 package org.netbeans.modules.java.lsp.server.protocol;
 
-import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import java.io.File;
 import java.io.FileWriter;
@@ -45,13 +44,13 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import javax.swing.text.Document;
 import javax.swing.text.StyledDocument;
 import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionContext;
+import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
 import org.eclipse.lsp4j.Command;
 import org.eclipse.lsp4j.CompletionItem;
@@ -1136,10 +1135,20 @@ public class ServerTest extends NbTestCase {
             }
 
             @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
             public NbCodeClientCapabilities getNbCodeCapabilities() {
                 throw new UnsupportedOperationException("Not supported yet.");
             }
-            
+
             @Override
             public void telemetryEvent(Object arg0) {
                 throw new UnsupportedOperationException("Not supported yet.");
@@ -1360,7 +1369,7 @@ public class ServerTest extends NbTestCase {
                      actual);
     }
 
-    public void testCodeActionGetterSetting() throws Exception {
+    public void testCodeActionGetterSetter() throws Exception {
         File src = new File(getWorkDir(), "Test.java");
         src.getParentFile().mkdirs();
         String code = "public class Test {\n" +
@@ -1407,26 +1416,26 @@ public class ServerTest extends NbTestCase {
         }, client.getInputStream(), client.getOutputStream());
         serverLauncher.startListening();
         LanguageServer server = serverLauncher.getRemoteProxy();
-        InitializeResult result = server.initialize(new InitializeParams()).get();
+        server.initialize(new InitializeParams()).get();
         String uri = src.toURI().toString();
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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(2, 6), new Position(3, 7)), new CodeActionContext(Arrays.asList()))).get();
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 6), new Position(2, 6)), new CodeActionContext(Arrays.asList()))).get();
         assertEquals(3, codeActions.size());
-        Optional<CodeAction> generateGettersSetters =
+        Optional<CodeAction> generateGetterSetter =
                 codeActions.stream()
                            .filter(Either::isRight)
                            .map(Either::getRight)
-                           .filter(a -> Bundle.DN_GenerateGettersSetters().equals(a.getTitle()))
+                           .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
                            .findAny();
-        assertTrue(generateGettersSetters.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGettersSetters.get().getCommand().getCommand(), generateGettersSetters.get().getCommand().getArguments())).get();
+        assertTrue(generateGetterSetter.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
         assertEquals(1, edit[0].getChanges().size());
         List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
-        assertEquals(new Range(new Position(3, 0),
-                               new Position(3, 0)),
+        assertEquals(new Range(new Position(6, 0),
+                               new Position(6, 0)),
                      fileChanges.get(0).getRange());
         assertEquals("\n" +
                      "    public String getF2() {\n" +
@@ -1435,18 +1444,734 @@ public class ServerTest extends NbTestCase {
                      "\n" +
                      "    public void setF2(String f2) {\n" +
                      "        this.f2 = f2;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionGetterSetter() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "    private String f2;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        WorkspaceEdit[] edit = new WorkspaceEdit[1];
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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));
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(9, codeActions.size());
+        Optional<CodeAction> generateGetterSetter =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateGetterSetter.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(3, 0),
+                               new Position(3, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public String getF2() {\n" +
+                     "        return f2;\n" +
                      "    }\n" +
                      "\n" +
-                     "    public String getF3() {\n" +
-                     "        return f3;\n" +
+                     "    public void setF2(String f2) {\n" +
+                     "        this.f2 = f2;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+        server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(fileChanges.get(0).getRange(), 0, fileChanges.get(0).getNewText()))));
+        codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateGetter =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateGetterFor("f1").equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateGetter.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetter.get().getCommand().getCommand(), generateGetter.get().getCommand().getArguments())).get();
+        assertEquals(1, edit[0].getChanges().size());
+        fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(11, 0),
+                               new Position(11, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public String getF1() {\n" +
+                     "        return f1;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionConstructor() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test extends Exception {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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 void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateConstructor =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateConstructor().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateConstructor.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateConstructor.get().getCommand().getCommand(), generateConstructor.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    public Test(String f1) {\n" +
+                     "        this.f1 = f1;\n" +
                      "    }\n" +
                      "\n" +
-                     "    public void setF3(String f3) {\n" +
-                     "        this.f3 = f3;\n" +
+                     "    public Test(String f1, String string) {\n" +
+                     "        super(string);\n" +
+                     "        this.f1 = f1;\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionEqualsHashCode() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "import java.util.Objects;\n" +
+                      "\n" +
+                      "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "    private java.util.List<String> f2;\n" +
+                      "\n" +
+                      "    @Override\n" +
+                      "    public int hashCode() {\n" +
+                      "        int hash = 3;\n" +
+                      "        hash = 71 * hash + Objects.hashCode(this.f1);\n" +
+                      "        hash = 71 * hash + Objects.hashCode(this.f2);\n" +
+                      "        return hash;\n" +
+                      "    }\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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 void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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, 0), new Position(5, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(9, codeActions.size());
+        Optional<CodeAction> generateEquals =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateEquals().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateEquals.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateEquals.get().getCommand().getCommand(), generateEquals.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(13, 0),
+                               new Position(13, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    @Override\n" +
+                     "    public boolean equals(Object obj) {\n" +
+                     "        if (this == obj) {\n" +
+                     "            return true;\n" +
+                     "        }\n" +
+                     "        if (obj == null) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        if (getClass() != obj.getClass()) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        final Test other = (Test) obj;\n" +
+                     "        if (!Objects.equals(this.f1, other.f1)) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        if (!Objects.equals(this.f2, other.f2)) {\n" +
+                     "            return false;\n" +
+                     "        }\n" +
+                     "        return true;\n" +
                      "    }\n",
                      fileChanges.get(0).getNewText());
     }
 
+    public void testSourceActionToString() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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 void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateToString =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateToString().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateToString.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateToString.get().getCommand().getCommand(), generateToString.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    @Override\n" +
+                     "    public String toString() {\n" +
+                     "        StringBuilder sb = new StringBuilder();\n" +
+                     "        sb.append(\"Test{f1=\").append(f1);\n" +
+                     "        sb.append('}');\n" +
+                     "        return sb.toString();\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionDelegateMethod() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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 void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateDelegateMethod =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateDelegateMethod().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateDelegateMethod.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateDelegateMethod.get().getCommand().getCommand(), generateDelegateMethod.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(2, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\nimport java.util.stream.IntStream;\n\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(1).getRange());
+        assertEquals("\n" +
+                     "    public IntStream chars() {\n" +
+                     "        return f1.chars();\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    public IntStream codePoints() {\n" +
+                     "        return f1.codePoints();\n" +
+                     "    }\n",
+                     fileChanges.get(1).getNewText());
+    }
+
+    public void testSourceActionOverrideMethod() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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 void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateOverrideMethod =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateOverrideMethod().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateOverrideMethod.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateOverrideMethod.get().getCommand().getCommand(), generateOverrideMethod.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(1, fileChanges.size());
+        assertEquals(new Range(new Position(2, 0),
+                               new Position(2, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\n" +
+                     "    @Override\n" +
+                     "    protected void finalize() throws Throwable {\n" +
+                     "        super.finalize(); //To change body of generated methods, choose Tools | Templates.\n" +
+                     "    }\n" +
+                     "\n" +
+                     "    @Override\n" +
+                     "    public String toString() {\n" +
+                     "        return super.toString(); //To change body of generated methods, choose Tools | Templates.\n" +
+                     "    }\n",
+                     fileChanges.get(0).getNewText());
+    }
+
+    public void testSourceActionLogger() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "public class Test {\n" +
+                      "    private final String f1;\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        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) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @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 void showStatusBarMessage(ShowStatusMessageParams params) {
+                throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+            }
+
+            @Override
+            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
+                return CompletableFuture.completedFuture(params.getItems().size() > 2 ? params.getItems().subList(0, 2) : params.getItems());
+            }
+
+            @Override
+            public CompletableFuture<String> showInputBox(ShowInputBoxParams params) {
+                return CompletableFuture.completedFuture("LOGGER");
+            }
+
+            @Override
+            public NbCodeClientCapabilities getNbCodeCapabilities() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "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(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(7, codeActions.size());
+        Optional<CodeAction> generateLogger =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_GenerateLogger().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(generateLogger.isPresent());
+        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateLogger.get().getCommand().getCommand(), generateLogger.get().getCommand().getArguments())).get();
+        int cnt = 0;
+        while(edit[0] == null && cnt++ < 10) {
+            Thread.sleep(1000);
+        }
+        assertEquals(1, edit[0].getChanges().size());
+        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(2, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(0, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("\nimport java.util.logging.Logger;\n\n",
+                     fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(1, 0),
+                               new Position(1, 0)),
+                     fileChanges.get(1).getRange());
+        assertEquals("\n" +
+                     "    private static final Logger LOGGER = Logger.getLogger(Test.class.getName());\n",
+                     fileChanges.get(1).getNewText());
+    }
+
     private String toString(Location location) {
         String path = location.getUri();
         String simpleName = path.substring(path.lastIndexOf('/') + 1);
diff --git a/java/java.lsp.server/vscode/package-lock.json b/java/java.lsp.server/vscode/package-lock.json
index 0e3253f..3ed2b2e 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -810,6 +810,11 @@
 				"glob": "^7.1.3"
 			}
 		},
+		"semver": {
+			"version": "6.3.0",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+			"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+		},
 		"set-blocking": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -884,18 +889,18 @@
 			"dev": true
 		},
 		"vscode-debugadapter": {
-			"version": "1.33.0",
-			"resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.33.0.tgz",
-			"integrity": "sha512-GsSD6PyVokjyW+7LU3FRXbu8SINv9muQtMQEDvwB6SIGQfrQzld8kECpQRr71j3mgXW+sTY+44/XMzueeaU6/Q==",
+			"version": "1.42.1",
+			"resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.42.1.tgz",
+			"integrity": "sha512-bICDB8mxReU2kGL13ftjNqeInYtVN3zpRdZKjRZHCpXC/mofrdaj9KRlJsALwcUNivMA9S/LwTZ2n+ros3X0jg==",
 			"requires": {
-				"mkdirp": "^0.5.1",
-				"vscode-debugprotocol": "1.33.0"
+				"mkdirp": "^0.5.5",
+				"vscode-debugprotocol": "1.42.0"
 			}
 		},
 		"vscode-debugprotocol": {
-			"version": "1.33.0",
-			"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.33.0.tgz",
-			"integrity": "sha512-d+l4lrEz6OP2kmGpweqe37x9H7icAMV8S4m8azTWGAIlNJxBP4rlSTnZa7NMLcbgqWkWG9lTGY7fJ+rSPaW7yg=="
+			"version": "1.42.0",
+			"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.42.0.tgz",
+			"integrity": "sha512-nVsfVCat9FZlOso5SYB1LQQiFGifTyOALpkpJdudDlRXGTpI3mSFiDYXWaoFm7UcfqTOzn1SC7Hqw4d89btT0w=="
 		},
 		"vscode-jsonrpc": {
 			"version": "5.0.1",
@@ -903,11 +908,12 @@
 			"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
 		},
 		"vscode-languageclient": {
-			"version": "4.4.2",
-			"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.4.2.tgz",
-			"integrity": "sha512-9TUzsg1UM6n1UEyPlWbDf7tK1wJAK7UGFRmGDN8sz4KmbbDiVRh6YicaB/5oRSVTpuV47PdJpYlOl3SJ0RiK1Q==",
+			"version": "6.1.3",
+			"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz",
+			"integrity": "sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==",
 			"requires": {
-				"vscode-languageserver-protocol": "^3.10.3"
+				"semver": "^6.3.0",
+				"vscode-languageserver-protocol": "^3.15.3"
 			}
 		},
 		"vscode-languageserver-protocol": {
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index 8753f2b..be8ee42 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -141,21 +141,6 @@
 				"category": "Java"
 			},
 			{
-				"command": "java.generate.getters.menu",
-				"title": "Generate all getters",
-				"category": "Java"
-			},
-			{
-				"command": "java.generate.setters.menu",
-				"title": "Generate all setters",
-				"category": "Java"
-			},
-			{
-				"command": "java.generate.getters.setters.menu",
-				"title": "Generate all getters and setters",
-				"category": "Java"
-			},
-			{
 				"command": "graalvm.pause.script",
 				"title": "Pause in Script",
 				"category": "GraalVM"
@@ -178,37 +163,8 @@
 					"command": "graalvm.pause.script",
 					"when": "nbJavaLSReady"
 				}
-			],
-			"editor/context": [
-				{
-                                        "submenu": "java.generate",
-                                        "group": "1_modification"
-				}
-			],
-			"java.generate": [
-				{
-                                        "command": "java.generate.getters.menu",
-					"when": "nbJavaLSReady",
-                                        "group": "1_getters_setters"
-				},
-				{
-                                        "command": "java.generate.setters.menu",
-					"when": "nbJavaLSReady",
-                                        "group": "1_getters_setters"
-				},
-				{
-                                        "command": "java.generate.getters.setters.menu",
-					"when": "nbJavaLSReady",
-                                        "group": "1_getters_setters"
-				}
 			]
-		},
-                "submenus": [
-                    {
-                        "id": "java.generate",
-                        "label": "Generate"
-                    }
-                ]
+		}
 	},
 	"scripts": {
 		"vscode:prepublish": "npm run compile",
@@ -229,7 +185,7 @@
 		"vscode-test": "^1.3.0"
 	},
 	"dependencies": {
-		"vscode-languageclient": "4.4.2",
-		"vscode-debugadapter": "1.33.0"
+		"vscode-languageclient": "6.1.3",
+		"vscode-debugadapter": "1.42.1"
 	}
 }
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index e2fb699..d0394e3 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -38,7 +38,7 @@ import * as path from 'path';
 import { ChildProcess } from 'child_process';
 import * as vscode from 'vscode';
 import * as launcher from './nbcode';
-import { StatusMessageRequest, ShowStatusMessageParams  } from './protocol';
+import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest } from './protocol';
 
 const API_VERSION : string = "1.0";
 let client: Promise<LanguageClient>;
@@ -194,30 +194,6 @@ export function activate(context: ExtensionContext): VSNetBeansAPI {
             });
         });
     }));
-    for (let generator of ['java.generate.getters', 'java.generate.setters', 'java.generate.getters.setters']) {
-        context.subscriptions.push(commands.registerCommand(generator + '.menu', () => {
-            return window.withProgress({ location: ProgressLocation.Window }, p => {
-                return new Promise(async (resolve, reject) => {
-                    const commands = await vscode.commands.getCommands();
-                    const editor = vscode.window.activeTextEditor;
-                    if (commands.includes(generator + '.menu') && editor != null) {
-                        const uri = editor.document.uri;
-                        const res = await vscode.commands.executeCommand(generator,
-                                                                         uri.scheme + "://" + uri.path,
-                                                                         editor.selection,
-                                                                         "all");
-                        if (res) {
-                            resolve();
-                        } else {
-                            reject();
-                        }
-                    } else {
-                        reject();
-                    }
-                });
-            });
-        }));
-    }
     return Object.freeze({
         version : API_VERSION
     });
@@ -442,6 +418,13 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
             commands.executeCommand('setContext', 'nbJavaLSReady', true);
             c.onNotification(StatusMessageRequest.type, showStatusBarMessage);
             c.onNotification(LogMessageNotification.type, (param) => handleLog(log, param.message));
+            c.onRequest(QuickPickRequest.type, async param => {
+                const selected = await window.showQuickPick(param.items, { placeHolder: param.placeHolder, canPickMany: param.canPickMany });
+                return selected ? Array.isArray(selected) ? selected : [selected] : undefined;
+            });
+            c.onRequest(InputBoxRequest.type, async param => {
+                return await window.showInputBox({ prompt: param.prompt, value: param.value });
+            });
             handleLog(log, 'Language Client: Ready');
             setClient[0](c);
         }).catch(setClient[1]);
diff --git a/java/java.lsp.server/vscode/src/protocol.ts b/java/java.lsp.server/vscode/src/protocol.ts
index 4b485ba..6f56c6d 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -18,8 +18,10 @@
  */
 'use strict';
 
+import {QuickPickItem} from 'vscode';
 import {
     NotificationType,
+    RequestType,
     ShowMessageParams
 } from 'vscode-languageclient';
 
@@ -33,3 +35,37 @@ export interface ShowStatusMessageParams extends ShowMessageParams {
 export namespace StatusMessageRequest {
     export const type = new NotificationType<ShowStatusMessageParams, void>('window/showStatusBarMessage');
 };
+
+export interface ShowQuickPickParams {
+    /**
+     * A string to show as placeholder in the input box to guide the user what to pick on.
+     */
+    placeHolder: string;
+    /**
+     * An optional flag to make the picker accept multiple selections.
+     */
+    canPickMany?: boolean;
+    /**
+     * A list of items.
+     */
+    items: QuickPickItem[];
+}
+
+export namespace QuickPickRequest {
+    export const type = new RequestType<ShowQuickPickParams, QuickPickItem[], void, void>('window/showQuickPick');
+}
+
+export interface ShowInputBoxParams {
+    /**
+     * The text to display underneath the input box.
+     */
+    prompt: string;
+    /**
+     * The value to prefill in the input box.
+     */
+    value: string;
+}
+
+export namespace InputBoxRequest {
+    export const type = new RequestType<ShowInputBoxParams, string | undefined, void, void>('window/showInputBox');
+}


---------------------------------------------------------------------
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