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 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 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" ];