This is an automated email from the ASF dual-hosted git repository.
jlahoda 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 731e7c9e05 Adding an option to disable unused hint for package private
elements, ignoring elements that possibly are looked up using
MethodHandles.Lookup.
new c649c82879 Merge pull request #5173 from jlahoda/unused-improvements
731e7c9e05 is described below
commit 731e7c9e05f38ee7950bf32150391d5ca2cc474a
Author: Jan Lahoda <[email protected]>
AuthorDate: Sun Jan 1 09:21:22 2023 +0100
Adding an option to disable unused hint for package private elements,
ignoring elements that possibly are looked up using MethodHandles.Lookup.
---
.../java/editor/base/semantic/UnusedDetector.java | 88 +++++++-
.../editor/base/semantic/UnusedDetectorTest.java | 225 +++++++++++++++++++++
.../netbeans/modules/java/hints/bugs/Unused.java | 14 +-
.../modules/java/hints/bugs/UnusedTest.java | 20 ++
4 files changed, 335 insertions(+), 12 deletions(-)
diff --git
a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
index d5a7cadce4..6e2e28e46f 100644
---
a/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
+++
b/java/java.editor.base/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetector.java
@@ -22,15 +22,19 @@ import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
+import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
@@ -76,11 +80,13 @@ public class UnusedDetector {
public static class UnusedDescription {
public final Element unusedElement;
public final TreePath unusedElementPath;
+ public final boolean packagePrivate;
public final UnusedReason reason;
- public UnusedDescription(Element unusedElement, TreePath
unusedElementPath, UnusedReason reason) {
+ public UnusedDescription(Element unusedElement, TreePath
unusedElementPath, boolean packagePrivate, UnusedReason reason) {
this.unusedElement = unusedElement;
this.unusedElementPath = unusedElementPath;
+ this.packagePrivate = packagePrivate;
this.reason = reason;
}
@@ -118,39 +124,39 @@ public class UnusedDetector {
boolean isWritten = uses.contains(UseTypes.WRITTEN);
boolean isRead = uses.contains(UseTypes.READ);
if (!isWritten && !isRead) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_WRITTEN_READ));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_WRITTEN_READ));
} else if (!isWritten) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_WRITTEN));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_WRITTEN));
} else if (!isRead) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_READ));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_READ));
}
} else if (el.getKind().isField() && (isPrivate || isPkgPrivate)) {
- if (!isSerialSpecField(info, el)) {
+ if (!isSerialSpecField(info, el) && !lookedUpElement(el,
uv.type2LookedUpFields, uv.allStringLiterals)) {
boolean isWritten = uses.contains(UseTypes.WRITTEN);
boolean isRead = uses.contains(UseTypes.READ);
if (!isWritten && !isRead) {
if (isPrivate || isUnusedInPkg(info, el, cancel)) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_WRITTEN_READ));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_WRITTEN_READ));
}
} else if (!isWritten) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_WRITTEN));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_WRITTEN));
} else if (!isRead) {
if (isPrivate || isUnusedInPkg(info, el, cancel)) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_READ));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_READ));
}
}
}
} else if ((el.getKind() == ElementKind.CONSTRUCTOR ||
el.getKind() == ElementKind.METHOD) && (isPrivate || isPkgPrivate)) {
if (!isSerializationMethod(info, (ExecutableElement)el) &&
!uses.contains(UseTypes.USED)
- &&
!info.getElementUtilities().overridesMethod((ExecutableElement)el)) {
+ &&
!info.getElementUtilities().overridesMethod((ExecutableElement)el) &&
!lookedUpElement(el, uv.type2LookedUpMethods, uv.allStringLiterals)) {
if (isPrivate || isUnusedInPkg(info, el, cancel)) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_USED));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_USED));
}
}
} else if ((el.getKind().isClass() || el.getKind().isInterface())
&& (isPrivate || isPkgPrivate)) {
if (!uses.contains(UseTypes.USED)) {
if (isPrivate || isUnusedInPkg(info, el, cancel)) {
- result.add(new UnusedDescription(el, declaration,
UnusedReason.NOT_USED));
+ result.add(new UnusedDescription(el, declaration,
isPkgPrivate, UnusedReason.NOT_USED));
}
}
}
@@ -280,6 +286,17 @@ public class UnusedDetector {
LOCAL_VARIABLES.contains(el.getKind());
}
+ private static boolean lookedUpElement(Element element, Map<Element,
Set<String>> type2LookedUp, Set<String> allStringLiterals) {
+ String name = element.getKind() == ElementKind.CONSTRUCTOR ? "<init>"
: element.getSimpleName().toString();
+ return isLookedUp(element.getEnclosingElement(), name, type2LookedUp,
allStringLiterals) ||
+ isLookedUp(null, name, type2LookedUp, allStringLiterals);
+ }
+
+ private static boolean isLookedUp(Element owner, String name, Map<Element,
Set<String>> type2LookedUp, Set<String> allStringLiterals) {
+ Set<String> lookedUp = type2LookedUp.getOrDefault(owner,
Collections.emptySet());
+ return lookedUp.contains(name) || (allStringLiterals.contains(name) &&
lookedUp.contains(null));
+ }
+
private static boolean isUnusedInPkg(CompilationInfo info, Element el,
Callable<Boolean> cancel) {
TypeElement typeElement;
Set<? extends String> packageSet =
Collections.singleton(info.getElements().getPackageOf(el).getQualifiedName().toString());
@@ -389,11 +406,16 @@ public class UnusedDetector {
private final Map<Element, Set<UseTypes>> useTypes = new HashMap<>();
private final Map<Element, TreePath> element2Declaration = new
HashMap<>();
+ private final Map<Element, Set<String>> type2LookedUpMethods = new
HashMap<>();
+ private final Map<Element, Set<String>> type2LookedUpFields = new
HashMap<>();
+ private final Set<String> allStringLiterals = new HashSet<>();
+ private final TypeElement methodHandlesLookup;
private final CompilationInfo info;
private ExecutableElement recursionDetector;
public UnusedVisitor(CompilationInfo info) {
this.info = info;
+ this.methodHandlesLookup =
info.getElements().getTypeElement(MethodHandles.Lookup.class.getCanonicalName());
}
@Override
@@ -597,5 +619,49 @@ public class UnusedDetector {
}
}
}
+
+ @Override
+ public Void visitLiteral(LiteralTree node, Void p) {
+ if (node.getKind() == Kind.STRING_LITERAL) {
+ allStringLiterals.add((String) ((LiteralTree)
node).getValue());
+ }
+ return super.visitLiteral(node, p);
+ }
+
+ @Override
+ public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
+ Element invoked = info.getTrees().getElement(new
TreePath(getCurrentPath(), node.getMethodSelect()));
+ if (invoked != null && invoked.getEnclosingElement() ==
methodHandlesLookup && node.getArguments().size() > 0) {
+ ExpressionTree clazz = node.getArguments().get(0);
+ Element lookupType = null;
+ if (clazz.getKind() == Kind.MEMBER_SELECT) {
+ MemberSelectTree mst = (MemberSelectTree) clazz;
+ if (mst.getIdentifier().contentEquals("class")) {
+ lookupType = info.getTrees().getElement(new
TreePath(new TreePath(getCurrentPath(), clazz), mst.getExpression()));
+ }
+ }
+ String lookupName = null;
+ if (node.getArguments().size() > 1) {
+ ExpressionTree name = node.getArguments().get(1);
+ if (name.getKind() == Kind.STRING_LITERAL) {
+ lookupName = (String) ((LiteralTree) name).getValue();
+ }
+ }
+ switch (invoked.getSimpleName().toString()) {
+ case "findStatic": case "findVirtual": case "findSpecial":
+ type2LookedUpMethods.computeIfAbsent(lookupType, t ->
new HashSet<>()).add(lookupName);
+ break;
+ case "findConstructor":
+ type2LookedUpMethods.computeIfAbsent(lookupType, t ->
new HashSet<>()).add("<init>");
+ break;
+ case "findGetter": case "findSetter": case
"findStaticGetter":
+ case "findStaticSetter": case "findStaticVarHandle": case
"findVarHandle":
+ type2LookedUpFields.computeIfAbsent(lookupType, t ->
new HashSet<>()).add(lookupName);
+ break;
+ }
+ }
+ return super.visitMethodInvocation(node, p);
+ }
+
}
}
diff --git
a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetectorTest.java
b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetectorTest.java
index a94df908b7..d62fbd9b97 100644
---
a/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetectorTest.java
+++
b/java/java.editor.base/test/unit/src/org/netbeans/modules/java/editor/base/semantic/UnusedDetectorTest.java
@@ -302,6 +302,231 @@ public class UnusedDetectorTest extends NbTestCase {
"3:<init>:NOT_USED");
}
+ @Test
+ public void testNoUnusedWhenLookup() throws Exception {
+ performTest("test/Test.java",
+ "package test;\n" +
+ "import java.lang.invoke.MethodHandles.Lookup;\n" +
+ "public class Test implements I {\n" +
+ " public void lookup(Lookup l) {\n" +
+ " l.findConstructor(T1.class, null);\n" +
+ " l.findSpecial(Test.class, \"test1\", null,
null);\n" +
+ " l.findStatic(Test.class, \"test3\", null);\n" +
+ " l.findVirtual(Test.class, \"test5\", null);\n" +
+ " l.findStaticGetter(Test.class, \"f1\", null);\n" +
+ " l.findStaticSetter(Test.class, \"f2\", null);\n" +
+ " l.findStaticVarHandle(Test.class, \"f3\",
null);\n" +
+ " l.findGetter(Test.class, \"f5\", null);\n" +
+ " l.findSetter(Test.class, \"f6\", null);\n" +
+ " l.findVarHandle(Test.class, \"f7\", null);\n" +
+ " }\n" +
+ " private static void test1() {\n" +
+ " }\n" +
+ " private static void test2() {\n" +
+ " }\n" +
+ " private static void test3() {\n" +
+ " }\n" +
+ " private static void test4() {\n" +
+ " }\n" +
+ " private void test5() {\n" +
+ " }\n" +
+ " private void test6() {\n" +
+ " }\n" +
+ " public class T1 {\n" +
+ " private T1(int i) { System.err.println(i); }\n" +
+ " }\n" +
+ " public class T2 {\n" +
+ " private T2(int i) { System.err.println(i); }\n" +
+ " }\n" +
+ " private static int f1;\n" +
+ " private static int f2;\n" +
+ " private static int f3;\n" +
+ " private static int f4;\n" +
+ " private int f5;\n" +
+ " private int f6;\n" +
+ " private int f7;\n" +
+ " private int f8;\n" +
+ "}\n" +
+ "interface I {}\n" +
+ "}\n",
+ "18:test2:NOT_USED",
+ "22:test4:NOT_USED",
+ "26:test6:NOT_USED",
+ "32:<init>:NOT_USED",
+ "37:f4:NOT_READ",
+ "41:f8:NOT_READ");
+ }
+
+ @Test
+ public void testNoUnusedWhenLookupNoLiterals() throws Exception {
+ performTest("test/Test.java",
+ "package test;\n" +
+ "import java.lang.invoke.MethodHandles.Lookup;\n" +
+ "public class Test implements I {\n" +
+ " public void lookup(Lookup l, String name) {\n" +
+ " doLookup(l, \"test1\");\n" +
+ " doLookup(l, \"test3\");\n" +
+ " doLookup(l, \"test5\");\n" +
+ " doLookup(l, \"f1\");\n" +
+ " doLookup(l, \"f2\");\n" +
+ " doLookup(l, \"f3\");\n" +
+ " doLookup(l, \"f5\");\n" +
+ " doLookup(l, \"f6\");\n" +
+ " doLookup(l, \"f7\");\n" +
+ " }\n" +
+ " public void doLookup(Lookup l, String name) {\n" +
+ " l.findSpecial(Test.class, name, null, null);\n" +
+ " l.findStatic(Test.class, name, null);\n" +
+ " l.findVirtual(Test.class, name, null);\n" +
+ " l.findStaticGetter(Test.class, name, null);\n" +
+ " l.findStaticSetter(Test.class, name, null);\n" +
+ " l.findStaticVarHandle(Test.class, name, null);\n"
+
+ " l.findGetter(Test.class, name, null);\n" +
+ " l.findSetter(Test.class, name, null);\n" +
+ " l.findVarHandle(Test.class, name, null);\n" +
+ " }\n" +
+ " private static void test1() {\n" +
+ " }\n" +
+ " private static void test2() {\n" +
+ " }\n" +
+ " private static void test3() {\n" +
+ " }\n" +
+ " private static void test4() {\n" +
+ " }\n" +
+ " private void test5() {\n" +
+ " }\n" +
+ " private void test6() {\n" +
+ " }\n" +
+ " private static int f1;\n" +
+ " private static int f2;\n" +
+ " private static int f3;\n" +
+ " private static int f4;\n" +
+ " private int f5;\n" +
+ " private int f6;\n" +
+ " private int f7;\n" +
+ " private int f8;\n" +
+ "}\n" +
+ "interface I {}\n" +
+ "}\n",
+ "28:test2:NOT_USED",
+ "32:test4:NOT_USED",
+ "36:test6:NOT_USED",
+ "41:f4:NOT_READ",
+ "45:f8:NOT_READ");
+ }
+
+ @Test
+ public void testNoUnusedWhenLookupNoClass() throws Exception {
+ performTest("test/Test.java",
+ "package test;\n" +
+ "import java.lang.invoke.MethodHandles.Lookup;\n" +
+ "public class Test implements I {\n" +
+ " public void lookup(Lookup l, Class<?> c) {\n" +
+ " l.findConstructor(c, null);\n" +
+ " l.findSpecial(c, \"test1\", null, null);\n" +
+ " l.findStatic(c, \"test3\", null);\n" +
+ " l.findVirtual(c, \"test5\", null);\n" +
+ " l.findStaticGetter(c, \"f1\", null);\n" +
+ " l.findStaticSetter(c, \"f2\", null);\n" +
+ " l.findStaticVarHandle(c, \"f3\", null);\n" +
+ " l.findGetter(c, \"f5\", null);\n" +
+ " l.findSetter(c, \"f6\", null);\n" +
+ " l.findVarHandle(c, \"f7\", null);\n" +
+ " }\n" +
+ " private static void test1() {\n" +
+ " }\n" +
+ " private static void test2() {\n" +
+ " }\n" +
+ " private static void test3() {\n" +
+ " }\n" +
+ " private static void test4() {\n" +
+ " }\n" +
+ " private void test5() {\n" +
+ " }\n" +
+ " private void test6() {\n" +
+ " }\n" +
+ " public class T1 {\n" +
+ " private T1(int i) { System.err.println(i); }\n" +
+ " }\n" +
+ " public class T2 {\n" +
+ " private T2(int i) { System.err.println(i); }\n" +
+ " }\n" +
+ " private static int f1;\n" +
+ " private static int f2;\n" +
+ " private static int f3;\n" +
+ " private static int f4;\n" +
+ " private int f5;\n" +
+ " private int f6;\n" +
+ " private int f7;\n" +
+ " private int f8;\n" +
+ "}\n" +
+ "interface I {}\n" +
+ "}\n",
+ "18:test2:NOT_USED",
+ "22:test4:NOT_USED",
+ "26:test6:NOT_USED",
+ "37:f4:NOT_READ",
+ "41:f8:NOT_READ");
+ }
+
+ @Test
+ public void testUnusedWhenNoLookup() throws Exception {
+ performTest("test/Test.java",
+ "package test;\n" +
+ "public class Test implements I {\n" +
+ " public void lookup() {\n" +
+ " String[] s = new String[] {\n" +
+ " \"test1\",\n" +
+ " \"test3\",\n" +
+ " \"test5\",\n" +
+ " \"f1\",\n" +
+ " \"f5\",\n" +
+ " };\n" +
+ " }\n" +
+ " private static void test1() {\n" +
+ " }\n" +
+ " private static void test3() {\n" +
+ " }\n" +
+ " private void test5() {\n" +
+ " }\n" +
+ " public class T1 {\n" +
+ " private T1(int i) { System.err.println(i); }\n" +
+ " }\n" +
+ " private static int f1;\n" +
+ " private int f5;\n" +
+ "}\n" +
+ "interface I {}\n" +
+ "}\n",
+ "4:s:NOT_READ",
+ "12:test1:NOT_USED",
+ "14:test3:NOT_USED",
+ "16:test5:NOT_USED",
+ "19:<init>:NOT_USED",
+ "21:f1:NOT_READ",
+ "22:f5:NOT_READ");
+ }
+
+ @Test
+ public void testUnusedWhenDifferentClass() throws Exception {
+ performTest("test/Test.java",
+ "package test;\n" +
+ "import java.lang.invoke.MethodHandles.Lookup;\n" +
+ "public class Test implements I {\n" +
+ " public void lookup(Lookup l) {\n" +
+ " l.findStatic(T1.class, \"test1\", null);\n" +
+ " }\n" +
+ " private static void test1() {\n" +
+ " }\n" +
+ " public class T1 {\n" +
+ " private static void test1() {\n" +
+ " }\n" +
+ " }\n" +
+ "}\n" +
+ "interface I {}\n" +
+ "}\n",
+ "7:test1:NOT_USED");
+ }
+
protected String sourceLevel = "1.8";
protected void performTest(String fileName, String code, String...
expected) throws Exception {
diff --git
a/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
b/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
index 207bbf4e54..34744e9678 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/bugs/Unused.java
@@ -27,6 +27,7 @@ import
org.netbeans.modules.java.editor.base.semantic.UnusedDetector;
import
org.netbeans.modules.java.editor.base.semantic.UnusedDetector.UnusedDescription;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.Fix;
+import org.netbeans.spi.java.hints.BooleanOption;
import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
import org.netbeans.spi.java.hints.Hint;
import org.netbeans.spi.java.hints.HintContext;
@@ -41,18 +42,29 @@ import org.openide.util.NbBundle.Messages;
@Hint(displayName = "#DN_org.netbeans.modules.java.hints.bugs.Unused",
description = "#DESC_org.netbeans.modules.java.hints.bugs.Unused",
category="bugs", options=Hint.Options.QUERY, suppressWarnings="unused")
@Messages({
"DN_org.netbeans.modules.java.hints.bugs.Unused=Unused Element",
- "DESC_org.netbeans.modules.java.hints.bugs.Unused=Detects and reports
unused variables, methods and classes"
+ "DESC_org.netbeans.modules.java.hints.bugs.Unused=Detects and reports
unused variables, methods and classes",
+ "LBL_UnusedPackagePrivate=Also detect unused package private elements",
+ "TP_UnusedPackagePrivate=Will also detect package private elements that
are unused"
})
public class Unused {
+ private static final boolean DETECT_UNUSED_PACKAGE_PRIVATE_DEFAULT = true;
+
+ @BooleanOption(displayName="#LBL_UnusedPackagePrivate",
tooltip="#TP_UnusedPackagePrivate",
defaultValue=DETECT_UNUSED_PACKAGE_PRIVATE_DEFAULT)
+ public static final String DETECT_UNUSED_PACKAGE_PRIVATE =
"detect.unused.package.private";
+
@TriggerTreeKind(Kind.COMPILATION_UNIT)
public static List<ErrorDescription> unused(HintContext ctx) {
List<UnusedDescription> unused =
UnusedDetector.findUnused(ctx.getInfo(), () -> ctx.isCanceled());
List<ErrorDescription> result = new ArrayList<>(unused.size());
+ boolean detectUnusedPackagePrivate =
ctx.getPreferences().getBoolean(DETECT_UNUSED_PACKAGE_PRIVATE,
DETECT_UNUSED_PACKAGE_PRIVATE_DEFAULT);
for (UnusedDescription ud : unused) {
if (ctx.isCanceled()) {
break;
}
+ if (!detectUnusedPackagePrivate && ud.packagePrivate) {
+ continue;
+ }
ErrorDescription err = convertUnused(ctx, ud);
if (err != null) {
result.add(err);
diff --git
a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/UnusedTest.java
b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/UnusedTest.java
index b00212fe31..e924a4183b 100644
---
a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/UnusedTest.java
+++
b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/UnusedTest.java
@@ -65,4 +65,24 @@ public class UnusedTest extends NbTestCase {
.findWarning("3:35-3:36:verifier:Variable s is never read")
.assertFixes();
}
+
+ public void testUnusedNoPackagePrivate() throws Exception {
+ HintTest
+ .create()
+ .input("package test;\n" +
+ "public class Test {\n" +
+ " void packagePrivate() {}\n" +
+ "}\n")
+ .run(Unused.class)
+ .assertWarnings("2:9-2:23:verifier:" +
Bundle.ERR_NotUsed("packagePrivate"));
+ HintTest
+ .create()
+ .preference(Unused.DETECT_UNUSED_PACKAGE_PRIVATE, false)
+ .input("package test;\n" +
+ "public class Test {\n" +
+ " void packagePrivate() {}\n" +
+ "}\n")
+ .run(Unused.class)
+ .assertWarnings();
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists