diff --git a/nbcode/nbproject/project.properties b/nbcode/nbproject/project.properties
index 864aa455..f3e09060 100644
--- a/nbcode/nbproject/project.properties
+++ b/nbcode/nbproject/project.properties
@@ -27,5 +27,7 @@ auxiliary.org-netbeans-modules-apisupport-installer.os-solaris=false
auxiliary.org-netbeans-modules-apisupport-installer.os-windows=false
auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml
modules=\
- ${project.org.netbeans.modules.nbcode.integration}
+ ${project.org.netbeans.modules.nbcode.integration} :\
+ ${project.org.netbeans.modules.nbcode.java.lsp.server.telemetry}
project.org.netbeans.modules.nbcode.integration=integration
+project.org.netbeans.modules.nbcode.java.lsp.server.telemetry=telemetry
diff --git a/nbcode/telemetry/build.xml b/nbcode/telemetry/build.xml
new file mode 100644
index 00000000..339fecb6
--- /dev/null
+++ b/nbcode/telemetry/build.xml
@@ -0,0 +1,23 @@
+
+
+
+ Builds, tests, and runs the project org.netbeans.modules.nbcode.java.lsp.server.telemetry.
+
+
+
+
+
diff --git a/nbcode/telemetry/manifest.mf b/nbcode/telemetry/manifest.mf
new file mode 100644
index 00000000..61a299de
--- /dev/null
+++ b/nbcode/telemetry/manifest.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: false
+OpenIDE-Module: org.netbeans.modules.nbcode.java.lsp.server.telemetry
+OpenIDE-Module-Implementation-Version: 1
+OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nbcode/java/lsp/server/telemetry/Bundle.properties
+
diff --git a/nbcode/telemetry/nbproject/build-impl.xml b/nbcode/telemetry/nbproject/build-impl.xml
new file mode 100644
index 00000000..208c4139
--- /dev/null
+++ b/nbcode/telemetry/nbproject/build-impl.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ You must set 'suite.dir' to point to your containing module suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nbcode/telemetry/nbproject/genfiles.properties b/nbcode/telemetry/nbproject/genfiles.properties
new file mode 100644
index 00000000..4f8a333c
--- /dev/null
+++ b/nbcode/telemetry/nbproject/genfiles.properties
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2024-2025, Oracle and/or its affiliates.
+#
+# Licensed 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
+#
+# https://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.
+#
+build.xml.data.CRC32=bcbc94fb
+build.xml.script.CRC32=f4d83a2b
+build.xml.stylesheet.CRC32=15ca8a54@2.97
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=decee057
+nbproject/build-impl.xml.script.CRC32=547e2c6a
+nbproject/build-impl.xml.stylesheet.CRC32=49aa68b0@2.97
diff --git a/nbcode/telemetry/nbproject/project.properties b/nbcode/telemetry/nbproject/project.properties
new file mode 100644
index 00000000..787b5258
--- /dev/null
+++ b/nbcode/telemetry/nbproject/project.properties
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024-2025, Oracle and/or its affiliates.
+#
+# Licensed 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
+#
+# https://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.
+#
+javac.source=1.8
+requires.nb.javac=true
+javac.compilerargs=-Xlint -Xlint:-serial
+test.run.args=--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
+ --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \
+ --add-exports=jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED \
+ --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
+
+test.unit.lib.cp=
+test.unit.run.cp.extra=
+license.file=../../LICENSE.txt
+nbm.homepage=https://github.com/oracle/javavscode/
+nbplatform.default.netbeans.dest.dir=${suite.dir}/../netbeans/nbbuild/netbeans
+nbplatform.default.harness.dir=${nbplatform.default.netbeans.dest.dir}/harness
+spec.version.base=1.0
diff --git a/nbcode/telemetry/nbproject/project.xml b/nbcode/telemetry/nbproject/project.xml
new file mode 100644
index 00000000..7a2c0691
--- /dev/null
+++ b/nbcode/telemetry/nbproject/project.xml
@@ -0,0 +1,136 @@
+
+
+
+ org.netbeans.modules.apisupport.project
+
+
+ org.netbeans.modules.nbcode.java.lsp.server.telemetry
+
+
+
+ org.netbeans.api.lsp
+
+
+
+ 1
+ 1.28
+
+
+
+ org.netbeans.libs.javacapi
+
+
+
+
+
+
+
+ org.netbeans.modules.editor.mimelookup
+
+
+
+ 1
+ 1.65
+
+
+
+ org.netbeans.modules.java.lsp.server
+
+
+
+ 2
+
+
+
+
+ org.netbeans.modules.java.platform
+
+
+
+ 1
+ 1.67
+
+
+
+ org.netbeans.modules.java.project
+
+
+
+ 1
+ 1.97
+
+
+
+ org.netbeans.modules.java.source.base
+
+
+
+ 2.68.0.6.4.3.8.1
+
+
+
+ org.netbeans.modules.projectapi
+
+
+
+ 1
+ 1.96
+
+
+
+ org.openide.filesystems
+
+
+
+ 9.38
+
+
+
+ org.openide.util
+
+
+
+ 9.33
+
+
+
+ org.openide.util.lookup
+
+
+
+ 8.59
+
+
+
+
+
+ unit
+
+ org.netbeans.libs.junit4
+
+
+
+ org.netbeans.modules.nbjunit
+
+
+
+
+
+
+
+
+
diff --git a/nbcode/telemetry/nbproject/suite.properties b/nbcode/telemetry/nbproject/suite.properties
new file mode 100644
index 00000000..0b44bb95
--- /dev/null
+++ b/nbcode/telemetry/nbproject/suite.properties
@@ -0,0 +1,16 @@
+#
+# Copyright (c) 2024-2025, Oracle and/or its affiliates.
+#
+# Licensed 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
+#
+# https://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.
+#
+suite.dir=${basedir}/..
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/Bundle.properties b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/Bundle.properties
new file mode 100644
index 00000000..7c1563f7
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/Bundle.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2024-2025, Oracle and/or its affiliates.
+#
+# Licensed 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
+#
+# https://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.
+#
+OpenIDE-Module-Display-Category=Java
+OpenIDE-Module-Name=Java LSP Server - Telemetry
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLangFeatures.java b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLangFeatures.java
new file mode 100644
index 00000000..7595b601
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLangFeatures.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import com.sun.tools.javac.code.Source;
+import com.sun.tools.javac.resources.CompilerProperties;
+import com.sun.tools.javac.util.JCDiagnostic;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+class JavaLangFeatures {
+
+ public static boolean isDiagnosticForUnsupportedFeatures(String diagnosticCode) {
+ return Singleton.javacParentDiagnosticKeys.contains(diagnosticCode);
+ }
+
+ public static String getFeatureName(String featureCode) {
+ Source.Feature feature = Singleton.fragmentCodeToFeature.get(featureCode);
+ if (feature == null && featureCode.startsWith(Singleton.javacFragmentCodePrefix)) {
+ feature = Singleton.fragmentCodeToFeature.get(featureCode.substring(Singleton.javacFragmentCodePrefix.length()));
+ }
+ return feature == null ? featureCode : feature.name();
+ }
+
+ private static class Singleton {
+
+ private static final Map fragmentCodeToFeature;
+ private static final Set javacParentDiagnosticKeys;
+ private static final String javacFragmentCodePrefix;
+
+ static {
+ Map featureFragments = new HashMap<>();
+ Set parentDiagnosticKeys = new HashSet<>();
+ String prefix = "compiler.misc.";
+ try {
+ final JCDiagnostic.Fragment fragment = CompilerProperties.Fragments.FeatureNotSupportedInSource((JCDiagnostic) null, null, null);
+ final String fragmentKey = fragment.key();
+ final String fragmentCode = fragment.getCode();
+ if (fragmentKey.startsWith(fragmentCode)) {
+ prefix = fragmentKey.substring(fragmentCode.length());
+ }
+
+ parentDiagnosticKeys.add(fragmentKey);
+ parentDiagnosticKeys.add(CompilerProperties.Fragments.FeatureNotSupportedInSourcePlural((JCDiagnostic) null, null, null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Errors.FeatureNotSupportedInSource((JCDiagnostic) null, null, null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Errors.FeatureNotSupportedInSourcePlural((JCDiagnostic) null, null, null).key());
+
+ parentDiagnosticKeys.add(CompilerProperties.Errors.PreviewFeatureDisabled((JCDiagnostic) null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Errors.PreviewFeatureDisabledPlural((JCDiagnostic) null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Warnings.PreviewFeatureUse((JCDiagnostic) null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Warnings.PreviewFeatureUsePlural((JCDiagnostic) null).key());
+
+ parentDiagnosticKeys.add(CompilerProperties.Errors.IsPreview(null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Warnings.IsPreview(null).key());
+ parentDiagnosticKeys.add(CompilerProperties.Warnings.IsPreviewReflective(null).key());
+
+ for (Source.Feature f : Source.Feature.values()) {
+ try {
+ featureFragments.put(f.nameFragment().getCode(), f);
+ } catch (AssertionError | NullPointerException e) {
+ // In case no error message code has been registered; for example: LOCAL_VARIABLE_TYPE_INFERENCE
+ featureFragments.put(f.name(), f);
+ }
+ }
+ } catch (VirtualMachineError e) {
+ throw e;
+ } catch (Throwable ignore) {
+ }
+ javacFragmentCodePrefix = prefix;
+ javacParentDiagnosticKeys = parentDiagnosticKeys.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(parentDiagnosticKeys);
+ fragmentCodeToFeature = featureFragments.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(featureFragments);
+ }
+ }
+
+}
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLangFeaturesTelemetryProvider.java b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLangFeaturesTelemetryProvider.java
new file mode 100644
index 00000000..8a0043e0
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLangFeaturesTelemetryProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.lsp.Diagnostic;
+import org.netbeans.modules.java.lsp.server.protocol.LspServerTelemetryManager;
+import org.netbeans.spi.lsp.ErrorProvider;
+import org.openide.util.NbPreferences;
+import org.openide.util.RequestProcessor;
+import org.openide.util.RequestProcessor.Task;
+
+@MimeRegistration(mimeType="text/x-java", service=ErrorProvider.class)
+public class JavaLangFeaturesTelemetryProvider implements ErrorProvider {
+
+ static final String PREFERENCES_NODE = "jdk.telemetry";
+ static final String PREFERENCES_KEY_DEBOUNCE_TIME = "java-lang-features-debounce";
+ static final String PREFERENCES_KEY_CACHE_EXPIRY = "java-lang-features-cache-expiry";
+
+ static RequestProcessor getRequestProcessor() {
+ return RPSingleton.instance;
+ }
+
+ private static int getRequestDebounceTime() {
+ return RPSingleton.DEBOUNCE_TIME;
+ }
+
+ private static final class RPSingleton {
+ private static final RequestProcessor instance = new RequestProcessor(JavaLangFeaturesTelemetryProvider.class.getName(), 10, true, false);
+ private static final int DEBOUNCE_TIME = Math.max(0, NbPreferences.forModule(JavaLangFeaturesTelemetryProvider.class).node(PREFERENCES_NODE).getInt(PREFERENCES_KEY_DEBOUNCE_TIME, 1000)); // 1 sec
+ }
+
+ private static final ConcurrentHashMap> sourceAnalysisTasks = new ConcurrentHashMap<>();
+
+ @Override
+ public List extends Diagnostic> computeErrors(Context context) {
+ if (context.errorKind() == ErrorProvider.Kind.HINTS && LspServerTelemetryManager.getInstance().isTelemetryEnabled()) {
+ final SourceInfo sourceInfo = SourceInfo.getSourceObject(context);
+ if (sourceInfo.source != null) {
+ scheduleTask(sourceInfo, JavaLanguageFeaturesEmitter::new);
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ private WeakReference scheduleTask(SourceInfo sourceInfo, Function runner) {
+ String sourceFileName = sourceInfo.getSourceName();
+ return sourceAnalysisTasks.compute(sourceFileName, (file, existingTaskRef) -> {
+ Task existingTask = existingTaskRef == null ? null : existingTaskRef.get();
+ final Task task;
+ final WeakReference taskRef;
+ if (existingTask == null) {
+ task = getRequestProcessor().create(runner.apply(sourceInfo));
+ taskRef = new WeakReference<>(existingTask);
+ task.addTaskListener(t -> sourceAnalysisTasks.remove(sourceFileName, taskRef));
+ } else {
+ task = existingTask;
+ taskRef = existingTaskRef;
+ }
+ task.schedule(getRequestDebounceTime());
+ return taskRef;
+ });
+ }
+
+ static ConcurrentHashMap> getSourceAnalysisTasks() {
+ return sourceAnalysisTasks;
+ }
+}
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLanguageFeaturesEmitter.java b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLanguageFeaturesEmitter.java
new file mode 100644
index 00000000..dfc70224
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLanguageFeaturesEmitter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.util.JCDiagnostic;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import org.eclipse.lsp4j.services.LanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.LspServerTelemetryManager;
+
+class JavaLanguageFeaturesEmitter implements Runnable {
+ private static final Logger LOG = Logger.getLogger(JavaLanguageFeaturesEmitter.class.getName());
+
+ private final SourceInfo sourceInfo;
+
+ JavaLanguageFeaturesEmitter(SourceInfo sourceInfo) {
+ this.sourceInfo = sourceInfo;
+ }
+
+ @Override
+ public void run() {
+ Set featuresUsed = checkJavaFeatures();
+ if (!featuresUsed.isEmpty() && SourceFeatureCache.add(sourceInfo.getProjectName(), featuresUsed)) {
+ final LanguageClient client = sourceInfo.getLanguageClient();
+ final SourceFeatureCache.SourceFeatureCacheEntry cached = SourceFeatureCache.get(sourceInfo.getProjectName());
+ final boolean previewEnabled = cached == null ? sourceInfo.getPreviewEnabled() : cached.isPreviewEnabled(sourceInfo);
+ final JdkFeatureEvent event = new JdkFeatureEvent.Builder()
+ .setJavaVersion(sourceInfo.getJavaVersion())
+ .setIsPreviewEnabled(previewEnabled)
+ .setNames(featuresUsed)
+ .build();
+ if (client == null) {
+ LspServerTelemetryManager.getInstance().sendTelemetry(event);
+ } else {
+ LspServerTelemetryManager.getInstance().sendTelemetry(client, event);
+ }
+ }
+ }
+
+ Set checkJavaFeatures() {
+ Set featuresUsed = new HashSet<>();
+ DiagnosticListener super JavaFileObject> dl = d -> {
+ //this is not an API, requires access to internals:
+ addNewJavaFeaturesUsed(featuresUsed,
+ d instanceof DiagnosticSourceUnwrapper ? ((DiagnosticSourceUnwrapper) d).d
+ : d instanceof JCDiagnostic ? (JCDiagnostic) d
+ : null);
+ };
+ JavacTask task;
+ try {
+ task = (JavacTask) ToolProvider.getSystemJavaCompiler().getTask(null, null, dl, List.of("--source", "8", "--source-path", sourceInfo.getSourcesPath()), null, List.of(sourceInfo.source));
+ task.analyze();
+ } catch (IOException e) {
+ LOG.log(Level.FINE, "IO error while scanning Java Language features: {0}", e);
+ } catch (IllegalArgumentException e) {
+ LOG.log(Level.CONFIG, "Invalid parsing parameters for scanning Java Language features: {0}", e);
+ } catch (RuntimeException ignored) {
+ }
+ return featuresUsed;
+ }
+
+ void addNewJavaFeaturesUsed(Set featuresUsed, JCDiagnostic jcDiag) {
+ if (jcDiag == null)
+ return;
+ if (JavaLangFeatures.isDiagnosticForUnsupportedFeatures(jcDiag.getCode())) {
+ if (jcDiag.getArgs().length > 0) {
+ if (jcDiag.getArgs()[0] instanceof JCDiagnostic) {
+ featuresUsed.add(JavaLangFeatures.getFeatureName(((JCDiagnostic) jcDiag.getArgs()[0]).getCode()));
+ } else if (jcDiag.getArgs()[0] instanceof JCDiagnostic.DiagnosticInfo) {
+ featuresUsed.add(JavaLangFeatures.getFeatureName(((JCDiagnostic.DiagnosticInfo) jcDiag.getArgs()[0]).getCode()));
+ } else if (jcDiag.getArgs()[0] instanceof Symbol) {
+ featuresUsed.add(JavaLangFeatures.getFeatureName(((Symbol) jcDiag.getArgs()[0]).getSimpleName().toString()));
+ }
+ }
+ return;
+ }
+ for (Object arg : jcDiag.getArgs()) {
+ if (arg instanceof JCDiagnostic)
+ addNewJavaFeaturesUsed(featuresUsed, (JCDiagnostic) arg);
+ }
+ }
+}
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JdkFeatureEvent.java b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JdkFeatureEvent.java
new file mode 100644
index 00000000..a24ac311
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JdkFeatureEvent.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import java.util.Set;
+import org.eclipse.lsp4j.MessageType;
+import org.netbeans.modules.java.lsp.server.protocol.TelemetryEvent;
+
+public class JdkFeatureEvent extends TelemetryEvent {
+
+ public static final String JDK_FEATURES_EVT = "jdkFeature";
+
+ public JdkFeatureEvent(String jsonString) {
+ super(MessageType.Info.toString(), JDK_FEATURES_EVT, jsonString);
+ }
+
+ public JdkFeatureEvent(JdkFeatures features) {
+ super(MessageType.Info.toString(), JDK_FEATURES_EVT, features);
+ }
+
+ public static class JdkFeatures {
+ private String javaVersion;
+ private Boolean isPreviewEnabled;
+ private Set names;
+ private Set jeps;
+
+ public String getJavaVersion() {
+ return javaVersion;
+ }
+
+ public void setJavaVersion(String javaVersion) {
+ this.javaVersion = javaVersion;
+ }
+
+ public Boolean getIsPreviewEnabled() {
+ return isPreviewEnabled;
+ }
+
+ public void setIsPreviewEnabled(Boolean isPreviewEnabled) {
+ this.isPreviewEnabled = isPreviewEnabled;
+ }
+
+ public Set getNames() {
+ return names;
+ }
+
+ public void setNames(Set names) {
+ this.names = names;
+ }
+
+ public Set getJeps() {
+ return jeps;
+ }
+
+ public void setJeps(Set jeps) {
+ this.jeps = jeps;
+ }
+
+ }
+
+ public static class Builder {
+ private final JdkFeatures properties = new JdkFeatures();
+
+ public Builder setJavaVersion(String javaVersion) {
+ properties.setJavaVersion(javaVersion);
+ return this;
+ }
+
+ public Builder setNames(Set names) {
+ properties.setNames(names);
+ return this;
+ }
+
+ public Builder setJeps(Set jeps) {
+ properties.setJeps(jeps);
+ return this;
+ }
+
+ public Builder setIsPreviewEnabled(boolean previewEnabled) {
+ properties.setIsPreviewEnabled(previewEnabled);
+ return this;
+ }
+
+ public JdkFeatureEvent build() {
+ return new JdkFeatureEvent(properties);
+ }
+ }
+
+}
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/SourceFeatureCache.java b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/SourceFeatureCache.java
new file mode 100644
index 00000000..31e392e8
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/SourceFeatureCache.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import org.openide.util.NbPreferences;
+import org.openide.util.RequestProcessor.Task;
+
+class SourceFeatureCache {
+
+ static class SourceFeatureCacheEntry {
+
+ private final long timestamp;
+ private final Set featuresUsed;
+ private final AtomicReference previewEnabled;
+
+ public SourceFeatureCacheEntry(long timestamp, Set featuresUsed) {
+ this(timestamp, featuresUsed, null);
+ }
+
+ protected SourceFeatureCacheEntry(long timestamp, Set featuresUsed, SourceFeatureCacheEntry copy) {
+ this.timestamp = timestamp;
+ this.featuresUsed = featuresUsed == null ? Collections.emptySet() : featuresUsed;
+ this.previewEnabled = new AtomicReference<>(copy == null ? null : copy.previewEnabled.get());
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public Set getFeaturesUsed() {
+ return featuresUsed;
+ }
+
+ public boolean isPreviewEnabled(SourceInfo sourceInfo) {
+ Boolean value = previewEnabled.get();
+ if (value == null) {
+ value = sourceInfo.getPreviewEnabled();
+ if (!previewEnabled.compareAndSet(null, value))
+ value = previewEnabled.get();
+ }
+ return value;
+ }
+ }
+
+ private static class Singleton {
+
+ private static final int CACHE_EXPIRY = Math.max(0, NbPreferences.forModule(JavaLangFeaturesTelemetryProvider.class).node(JavaLangFeaturesTelemetryProvider.PREFERENCES_NODE).getInt(JavaLangFeaturesTelemetryProvider.PREFERENCES_KEY_CACHE_EXPIRY, 3_600_000)); // 1 hour
+ private static final ConcurrentHashMap cachedSourceFeatures = new ConcurrentHashMap<>();
+ }
+
+ static ConcurrentHashMap getCachedSourceFeatures() {
+ return Singleton.cachedSourceFeatures;
+ }
+
+ public static SourceFeatureCacheEntry get(String sourceName) {
+ return Singleton.cachedSourceFeatures.get(sourceName);
+ }
+
+ public static boolean add(String sourceName, Set features) {
+ final Set newFeatures = Collections.unmodifiableSet(features);
+ final SourceFeatureCacheEntry entry = getCachedSourceFeatures().compute(sourceName,
+ (name, cache) -> cache != null && cache.getFeaturesUsed().containsAll(newFeatures) ? cache
+ : new SourceFeatureCacheEntry(System.currentTimeMillis(), newFeatures, cache));
+
+ boolean added = newFeatures == entry.getFeaturesUsed();
+ if (!added) {
+ SourceFeatureCacheCleaner.delay();
+ }
+ return added;
+ }
+
+ private static class SourceFeatureCacheCleaner implements Runnable {
+
+ private static final int CLEANER_DELAY = Math.max(10_000, Singleton.CACHE_EXPIRY / 10); // 10 times/expiry period; min. 10secs.
+ private static final Task cacheCleaner = JavaLangFeaturesTelemetryProvider.getRequestProcessor().create(new SourceFeatureCacheCleaner(), true);
+
+ static {
+ cacheCleaner.schedule(CLEANER_DELAY);
+ }
+
+ static void delay() {
+ cacheCleaner.schedule(CLEANER_DELAY);
+ }
+
+ @Override
+ public void run() {
+ final long cleanBeforeTime = System.currentTimeMillis() - Singleton.CACHE_EXPIRY;
+ final Iterator iterator = getCachedSourceFeatures().values().iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().getTimestamp() < cleanBeforeTime) {
+ iterator.remove();
+ }
+ }
+ cacheCleaner.schedule(CLEANER_DELAY);
+ }
+
+ }
+}
diff --git a/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/SourceInfo.java b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/SourceInfo.java
new file mode 100644
index 00000000..9c6adac3
--- /dev/null
+++ b/nbcode/telemetry/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/SourceInfo.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.util.Map;
+import java.util.function.Function;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import org.eclipse.lsp4j.services.LanguageClient;
+import org.netbeans.api.java.platform.JavaPlatform;
+import org.netbeans.api.java.project.JavaProjectConstants;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.api.project.Sources;
+import org.netbeans.modules.java.lsp.server.protocol.LspServerTelemetryManager;
+import org.netbeans.spi.lsp.ErrorProvider;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.Lookup;
+
+class SourceInfo {
+
+ private final FileObject file;
+ private final Project owner;
+ final JavaFileObject source;
+ /**
+ * A transient reference cache to the LanguageClient associated with this source.
+ * This does not need to be made concurrency-safe since the source is expected
+ * to be associated with a single client during its lifetime here. Further,
+ * it can be queried and rewritten again safely, especially across threads.
+ * So, there is no need to make this an atomic reference.
+ * Finally, it needs to be a weak-reference so that the client resources are
+ * released as soon as possible.
+ */
+ private transient WeakReference client;
+
+ public SourceInfo(FileObject file, Project owner, JavaFileObject source) {
+ this.file = file;
+ this.owner = owner;
+ this.source = source;
+ }
+
+ public String getSourceName() {
+ return source == null ? "" : source.getName();
+ }
+
+ public String getProjectName() {
+ return owner == null ? "" : ProjectUtils.getInformation(ProjectUtils.rootOf(owner)).getName();
+ }
+
+ public String getSourcesPath() {
+ if (owner != null) {
+ final Sources sources = ProjectUtils.getSources(owner);
+ SourceGroup[] sourceGroups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
+ if (sourceGroups.length == 0) {
+ sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (SourceGroup group : sourceGroups) {
+ final File root = FileUtil.toFile(group.getRootFolder());
+ if (root != null) {
+ if (!sb.isEmpty()) {
+ sb.append(File.pathSeparatorChar);
+ }
+ sb.append(root.getAbsolutePath());
+ }
+ }
+ return sb.toString();
+ } else {
+ final File parent = FileUtil.toFile(file.getParent());
+ return parent == null ? "." : parent.getAbsolutePath();
+ }
+ }
+
+ public LanguageClient getLanguageClient() {
+ LanguageClient client = this.client == null ? null : this.client.get();
+ if (client == null) {
+ client = Lookup.getDefault().lookup(LanguageClient.class);
+ if (client != null)
+ this.client = new WeakReference<>(client);
+ }
+ return client;
+ }
+
+ public String getJavaVersion() {
+ final JavaPlatform defaultPlatform = JavaPlatform.getDefault();
+ final Map systemProperties = defaultPlatform.getSystemProperties();
+ Function lookupFunction = systemProperties == null ? System::getProperty : systemProperties::get;
+ return LspServerTelemetryManager.getJavaRuntimeVersion(lookupFunction);
+ }
+
+ public boolean getPreviewEnabled() {
+ return LspServerTelemetryManager.getInstance().isPreviewEnabled(file,
+ owner == null ? LspServerTelemetryManager.ProjectType.standalone : LspServerTelemetryManager.getInstance().getProjectType(owner),
+ getLanguageClient());
+ }
+
+ public static SourceInfo getSourceObject(ErrorProvider.Context context) {
+ final FileObject file = context.file();
+ final Project owner = FileOwnerQuery.getOwner(file);
+ JavaFileObject source = null;
+ try {
+ source = new BasicJavaFileObject(file.toURI(), file.asText());
+ } catch (IOException | IllegalArgumentException ignore) {
+ }
+ return new SourceInfo(file, owner, source);
+ }
+
+ static class BasicJavaFileObject extends SimpleJavaFileObject {
+
+ private final String content;
+
+ public BasicJavaFileObject(URI uri, String content) {
+ super(uri, Kind.SOURCE);
+ this.content = content;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return content;
+ }
+ }
+}
diff --git a/nbcode/telemetry/test/unit/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLanguageFeaturesEmitterTest.java b/nbcode/telemetry/test/unit/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLanguageFeaturesEmitterTest.java
new file mode 100644
index 00000000..52eb2fd4
--- /dev/null
+++ b/nbcode/telemetry/test/unit/src/org/netbeans/modules/nbcode/java/lsp/server/telemetry/JavaLanguageFeaturesEmitterTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2024-2025, Oracle and/or its affiliates.
+ *
+ * Licensed 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
+ *
+ * https://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.nbcode.java.lsp.server.telemetry;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.netbeans.junit.NbTestCase;
+import org.openide.filesystems.FileUtil;
+
+public class JavaLanguageFeaturesEmitterTest extends NbTestCase {
+
+ public JavaLanguageFeaturesEmitterTest(String name) {
+ super(name);
+ }
+
+ @Test
+ public void testDiagToFeatureName() {
+ List diagCodes = Arrays.asList("feature.records", "feature.switch.rules", "feature.diamond.and.anon.class", "feature.pattern.switch", "feature.switch.expressions", "feature.xyz");
+ List featNames = Arrays.asList("RECORDS", "SWITCH_RULE", "DIAMOND_WITH_ANONYMOUS_CLASS_CREATION", "PATTERN_SWITCH", "SWITCH_EXPRESSION", "feature.xyz");
+ for (int i = 0; i < diagCodes.size() ; i++) {
+ String code = diagCodes.get(i);
+ String name = JavaLangFeatures.getFeatureName(code);
+ assertEquals(featNames.get(i), name);
+ }
+ }
+
+ @Test
+ public void testCheckJavaFeatures() {
+ SourceInfo sourceInfo;
+ try {
+ sourceInfo = new SourceInfo(FileUtil.toFileObject(File.createTempFile("test", ".java")), null, new SourceInfo.BasicJavaFileObject(new URI("mem://test.java"), getTestCode1()));
+ } catch (IOException | URISyntaxException ex) {
+ fail(ex.toString());
+ return;
+ }
+ JavaLanguageFeaturesEmitter instance = new JavaLanguageFeaturesEmitter(sourceInfo);
+ Set expResult = new HashSet<>(Arrays.asList("RECORDS", "DIAMOND_WITH_ANONYMOUS_CLASS_CREATION", "SWITCH_EXPRESSION", "SWITCH_RULE", "PATTERN_SWITCH"));
+ Set result = instance.checkJavaFeatures();
+ assertEquals(expResult, result);
+ }
+
+ private String getTestCode1() {
+ return " import java.util.*;\n"
+ + " public record R(int i) {\n"
+ + " private int t(Object o) {\n"
+ + " List l = new ArrayList<>() {};\n"
+ + " return switch (o) { default -> 0; }\n"
+ + " }\n"
+ + " }";
+ }
+}
diff --git a/patches/nb-telemetry.diff b/patches/nb-telemetry.diff
index c7c381cd..93101728 100644
--- a/patches/nb-telemetry.diff
+++ b/patches/nb-telemetry.diff
@@ -1,5 +1,5 @@
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspServerTelemetryManager.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspServerTelemetryManager.java
-index d82646afb1..3d507b5fe3 100644
+index d82646afb1..b008279cc4 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspServerTelemetryManager.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspServerTelemetryManager.java
@@ -21,6 +21,7 @@ package org.netbeans.modules.java.lsp.server.protocol;
@@ -10,7 +10,7 @@ index d82646afb1..3d507b5fe3 100644
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
-@@ -28,25 +29,27 @@ import java.security.NoSuchAlgorithmException;
+@@ -28,25 +29,29 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -19,6 +19,8 @@ index d82646afb1..3d507b5fe3 100644
import java.util.List;
import java.util.Map;
-import java.util.Set;
++import java.util.NavigableMap;
++import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicBoolean;
@@ -42,7 +44,7 @@ index d82646afb1..3d507b5fe3 100644
import org.openide.util.Lookup;
/**
-@@ -55,130 +58,171 @@ import org.openide.util.Lookup;
+@@ -55,130 +60,200 @@ import org.openide.util.Lookup;
*/
public class LspServerTelemetryManager {
@@ -83,11 +85,11 @@ index d82646afb1..3d507b5fe3 100644
+
+ private static final LspServerTelemetryManager instance = new LspServerTelemetryManager();
+ }
-
++
+ private final WeakHashMap>> clients = new WeakHashMap<>();
+ private volatile boolean telemetryEnabled = false;
+ private long lspServerIntializationTime;
-+
+
+ public boolean isTelemetryEnabled() {
+ return telemetryEnabled;
+ }
@@ -175,22 +177,21 @@ index d82646afb1..3d507b5fe3 100644
JsonArray prjProps = new JsonArray();
- Map mp = prjs.stream()
-+ Map mp = projects.stream()
- .collect(Collectors.toMap(project -> project.getProjectDirectory().getPath(), project -> project));
+- .collect(Collectors.toMap(project -> project.getProjectDirectory().getPath(), project -> project));
++ NavigableMap mp = projects.stream()
++ .collect(Collectors.toMap(project -> project.getProjectDirectory().getPath(), project -> project, (p1, p2) -> p1, TreeMap::new));
for (FileObject workspaceFolder : workspaceClientFolders) {
try {
- JsonObject obj = new JsonObject();
+- JsonObject obj = new JsonObject();
++ boolean noProjectFound = true;
String prjPath = workspaceFolder.getPath();
- String prjId = this.getPrjId(prjPath);
-+ String prjId = getPrjId(prjPath);
- obj.addProperty("id", prjId);
+- obj.addProperty("id", prjId);
-
- // In future if different JDK is used for different project then this can be updated
- obj.addProperty("javaVersion", System.getProperty("java.version"));
-+ String javaVersion = getProjectJavaVersion();
-+ obj.addProperty("javaVersion", javaVersion);
-
+-
- if (mp.containsKey(prjPath)) {
- Project prj = mp.get(prjPath);
-
@@ -202,31 +203,40 @@ index d82646afb1..3d507b5fe3 100644
-
- boolean isPreviewFlagEnabled = this.isEnablePreivew(prj.getProjectDirectory(), projectType);
- obj.addProperty("enablePreview", isPreviewFlagEnabled);
-+ Project prj = mp.get(prjPath);
-+ FileObject projectDirectory;
-+ ProjectType projectType;
-+ if (prj == null) {
-+ projectType = ProjectType.standalone;
-+ projectDirectory = workspaceFolder;
- } else {
+- } else {
- obj.addProperty("buildTool", this.STANDALONE_PRJ);
- obj.addProperty("javaVersion", System.getProperty("java.version"));
- obj.addProperty("openedWithProblems", false);
-
- boolean isPreviewFlagEnabled = this.isEnablePreivew(workspaceFolder, this.STANDALONE_PRJ);
- obj.addProperty("enablePreview", isPreviewFlagEnabled);
-+ projectType = getProjectType(prj);
-+ projectDirectory = prj.getProjectDirectory();
-+ obj.addProperty("isOpenedWithProblems", ProjectProblems.isBroken(prj));
++ String prjPathWithSlash = null;
++ for (Map.Entry p : mp.tailMap(prjPath, true).entrySet()) {
++ String projectPath = p.getKey();
++ if (prjPathWithSlash == null) {
++ if (prjPath.equals(projectPath)) {
++ prjProps.add(createProjectInfo(prjPath, p.getValue(), workspaceFolder, client));
++ noProjectFound = false;
++ break;
++ }
++ prjPathWithSlash = prjPath + '/';
++ }
++ if (projectPath.startsWith(prjPathWithSlash)) {
++ prjProps.add(createProjectInfo(p.getKey(), p.getValue(), workspaceFolder, client));
++ noProjectFound = false;
++ continue;
++ }
++ break;
}
-+ obj.addProperty("buildTool", projectType.name());
-+ boolean isPreviewFlagEnabled = isPreviewEnabled(projectDirectory, projectType, client);
-+ obj.addProperty("isPreviewEnabled", isPreviewFlagEnabled);
-
- prjProps.add(obj);
-
+-
+- prjProps.add(obj);
+-
- } catch (NoSuchAlgorithmException ex) {
- Exceptions.printStackTrace(ex);
++ if (noProjectFound) {
++ // No project found
++ prjProps.add(createProjectInfo(prjPath, null, workspaceFolder, client));
++ }
+ } catch (NoSuchAlgorithmException e) {
+ LOG.log(Level.INFO, "NoSuchAlgorithmException while creating workspaceInfo event: {0}", e.getMessage());
+ } catch (Exception e) {
@@ -236,26 +246,55 @@ index d82646afb1..3d507b5fe3 100644
- properties.add("prjsInfo", prjProps);
+ properties.add("projectInfo", prjProps);
-+
-+ properties.addProperty("projInitTimeTaken", timeToOpenProjects);
-+ properties.addProperty("numProjects", workspaceClientFolders.size());
-+ properties.addProperty("lspInitTimeTaken", System.currentTimeMillis() - this.lspServerIntializationTime);
- properties.addProperty("timeToOpenPrjs", timeToOpenPrjs);
- properties.addProperty("numOfPrjsOpened", workspaceClientFolders.size());
- properties.addProperty("lspServerInitializationTime", System.currentTimeMillis() - this.lspServerIntiailizationTime);
-+ this.sendTelemetry(client, new TelemetryEvent(MessageType.Info.toString(), LspServerTelemetryManager.WORKSPACE_INFO_EVT, properties));
-+ }
-
++ properties.addProperty("projInitTimeTaken", timeToOpenProjects);
++ properties.addProperty("numProjects", workspaceClientFolders.size());
++ properties.addProperty("lspInitTimeTaken", System.currentTimeMillis() - this.lspServerIntializationTime);
+
- this.sendTelemetry(client, new TelemetryEvent(MessageType.Info.toString(), this.WORKSPACE_INFO_EVT, properties));
-+ public boolean isPreviewEnabled(FileObject source, ProjectType prjType) {
-+ return isPreviewEnabled(source, prjType, null);
++ this.sendTelemetry(client, new TelemetryEvent(MessageType.Info.toString(), LspServerTelemetryManager.WORKSPACE_INFO_EVT, properties));
}
-
- private boolean isEnablePreivew(FileObject source, String prjType) {
- if (prjType.equals(this.STANDALONE_PRJ)) {
- NbCodeLanguageClient client = Lookup.getDefault().lookup(NbCodeLanguageClient.class);
+
++ private JsonObject createProjectInfo(String prjPath, Project prj, FileObject workspaceFolder, LanguageClient client) throws NoSuchAlgorithmException {
++ JsonObject obj = new JsonObject();
++ String prjId = getPrjId(prjPath);
++ obj.addProperty("id", prjId);
++ FileObject projectDirectory;
++ ProjectType projectType;
++ if (prj == null) {
++ projectType = ProjectType.standalone;
++ projectDirectory = workspaceFolder;
++ } else {
++ projectType = getProjectType(prj);
++ projectDirectory = prj.getProjectDirectory();
++ boolean projectHasProblems;
++ try {
++ projectHasProblems = ProjectProblems.isBroken(prj);
++ } catch (RuntimeException e) {
++ LOG.log(Level.INFO, "Exception while checking project problems for workspaceInfo event: {0}", e.getMessage());
++ projectHasProblems = true;
++ }
++ obj.addProperty("isOpenedWithProblems", projectHasProblems);
++ }
++ String javaVersion = getProjectJavaVersion();
++ obj.addProperty("javaVersion", javaVersion);
++ obj.addProperty("buildTool", projectType.name());
++ boolean isPreviewFlagEnabled = isPreviewEnabled(projectDirectory, projectType, client);
++ obj.addProperty("isPreviewEnabled", isPreviewFlagEnabled);
++ return obj;
++ }
++
++ public boolean isPreviewEnabled(FileObject source, ProjectType prjType) {
++ return isPreviewEnabled(source, prjType, null);
++ }
++
+ public boolean isPreviewEnabled(FileObject source, ProjectType prjType, LanguageClient languageClient) {
+ if (prjType == ProjectType.standalone) {
+ NbCodeLanguageClient client = languageClient instanceof NbCodeLanguageClient ? (NbCodeLanguageClient) languageClient : null ;
@@ -292,7 +331,7 @@ index d82646afb1..3d507b5fe3 100644
}
private String getPrjId(String prjPath) throws NoSuchAlgorithmException {
-@@ -187,15 +231,50 @@ public class LspServerTelemetryManager {
+@@ -187,15 +262,50 @@ public class LspServerTelemetryManager {
BigInteger number = new BigInteger(1, hash);
diff --git a/vscode/src/lsp/launchOptions.ts b/vscode/src/lsp/launchOptions.ts
index 63e93b0e..b9093301 100644
--- a/vscode/src/lsp/launchOptions.ts
+++ b/vscode/src/lsp/launchOptions.ts
@@ -55,6 +55,7 @@ const extraLaunchOptions = [
"--start-java-language-server=listen-hash:0",
"--start-java-debug-adapter-server=listen-hash:0",
"-J-DTopSecurityManager.disable=true",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED",
"-J--enable-native-access=ALL-UNNAMED"
];