Merge pull request #4032 from apache/delivery

Sync delivery to release140 for 14-rc2
diff --git a/ide/libs.truffleapi/nbproject/project.xml b/ide/libs.truffleapi/nbproject/project.xml
index 75db006..7fa76b7 100644
--- a/ide/libs.truffleapi/nbproject/project.xml
+++ b/ide/libs.truffleapi/nbproject/project.xml
@@ -38,6 +38,7 @@
                 <package>com.oracle.truffle.api</package>
                 <package>com.oracle.truffle.api.debug</package>
                 <package>com.oracle.truffle.api.dsl</package>
+                <package>com.oracle.truffle.api.exception</package>
                 <package>com.oracle.truffle.api.frame</package>
                 <package>com.oracle.truffle.api.instrumentation</package>
                 <package>com.oracle.truffle.api.interop</package>
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java
index 9301f0a..f1de037 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/Utils.java
@@ -195,7 +195,9 @@
             if (cmd.isLeft()) {
                 command = cmd.getLeft();
             } else {
-                Utils.applyWorkspaceEdit(cmd.getRight().getEdit());
+                if(cmd.getRight().getEdit() != null) {
+                    Utils.applyWorkspaceEdit(cmd.getRight().getEdit());
+                }
                 command = cmd.getRight().getCommand();
             }
             if (command != null) {
diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java
index 0fada4a..80c53ce 100644
--- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java
+++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/LanguageClientImpl.java
@@ -20,7 +20,6 @@
 
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
-import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -30,16 +29,14 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import javax.swing.SwingUtilities;
-import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
-import javax.swing.text.StyledDocument;
 import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
 import org.eclipse.lsp4j.CodeAction;
@@ -51,21 +48,20 @@
 import org.eclipse.lsp4j.ConfigurationParams;
 import org.eclipse.lsp4j.Diagnostic;
 import org.eclipse.lsp4j.DiagnosticSeverity;
-import org.eclipse.lsp4j.ExecuteCommandParams;
 import org.eclipse.lsp4j.MessageActionItem;
 import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.ProgressParams;
 import org.eclipse.lsp4j.PublishDiagnosticsParams;
 import org.eclipse.lsp4j.ServerCapabilities;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
 import org.eclipse.lsp4j.TextDocumentIdentifier;
-import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkDoneProgressBegin;
 import org.eclipse.lsp4j.WorkDoneProgressCreateParams;
 import org.eclipse.lsp4j.WorkDoneProgressEnd;
 import org.eclipse.lsp4j.WorkDoneProgressNotification;
 import org.eclipse.lsp4j.WorkDoneProgressReport;
-import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.Endpoint;
 import org.eclipse.lsp4j.jsonrpc.messages.Either;
 import org.eclipse.lsp4j.services.LanguageClient;
 import org.netbeans.api.progress.*;
@@ -78,19 +74,20 @@
 import org.netbeans.spi.editor.hints.HintsController;
 import org.netbeans.spi.editor.hints.LazyFixList;
 import org.netbeans.spi.editor.hints.Severity;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.NotifyDescriptor.QuickPick.Item;
 import org.openide.cookies.EditorCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.URLMapper;
-import org.openide.text.NbDocument;
 import org.openide.util.Exceptions;
-import org.openide.util.NbBundle.Messages;
 import org.openide.util.RequestProcessor;
 
 /**
  *
  * @author lahvac
  */
-public class LanguageClientImpl implements LanguageClient {
+public class LanguageClientImpl implements LanguageClient, Endpoint {
 
     private static final Logger LOG = Logger.getLogger(LanguageClientImpl.class.getName());
     private static final RequestProcessor WORKER = new RequestProcessor(LanguageClientImpl.class.getName(), 1, false, false);
@@ -192,18 +189,86 @@
 
     @Override
     public void showMessage(MessageParams arg0) {
-        System.err.println("showMessage: " + arg0);
+        int messageType;
+
+        switch (Optional.ofNullable(arg0.getType()).orElse(MessageType.Log)) {
+            default:
+            case Log:
+            case Info:
+                messageType = NotifyDescriptor.INFORMATION_MESSAGE;
+                break;
+            case Warning:
+                messageType = NotifyDescriptor.WARNING_MESSAGE;
+                break;
+            case Error:
+                messageType = NotifyDescriptor.ERROR_MESSAGE;
+                break;
+        }
+
+        NotifyDescriptor nd = new NotifyDescriptor.Message(
+                arg0.getMessage(),
+                messageType
+        );
+
+        DialogDisplayer.getDefault().notifyLater(nd);
     }
 
     @Override
     public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
-        System.err.println("showMessageRequest");
-        return null; //???
+        int messageType;
+
+        switch (Optional.ofNullable(arg0.getType()).orElse(MessageType.Log)) {
+            default:
+            case Log:
+            case Info:
+                messageType = NotifyDescriptor.INFORMATION_MESSAGE;
+                break;
+            case Warning:
+                messageType = NotifyDescriptor.WARNING_MESSAGE;
+                break;
+            case Error:
+                messageType = NotifyDescriptor.ERROR_MESSAGE;
+                break;
+        }
+
+        NotifyDescriptor.QuickPick nd = new NotifyDescriptor.QuickPick(
+                arg0.getMessage(),
+                "Please select",
+                arg0.getActions().stream()
+                        .map(mai -> new Item(mai.getTitle(), mai.getTitle()))
+                        .collect(Collectors.toList())
+                ,
+                false
+        );
+
+        nd.setMessageType(messageType);
+
+        return DialogDisplayer.getDefault()
+                .notifyFuture(nd)
+                .thenApply(nd2 -> {
+                    return new MessageActionItem(
+                            nd2.getItems().stream()
+                                    .filter(i -> i.isSelected())
+                                    .findFirst()
+                                    .map(i -> i.getLabel())
+                                    .orElse(""));
+                });
     }
 
     @Override
     public void logMessage(MessageParams arg0) {
-        System.err.println("logMessage: " + arg0);
+        switch (Optional.ofNullable(arg0.getType()).orElse(MessageType.Log)) {
+            case Log:
+            case Info:
+                LOG.info(arg0.getMessage());
+                break;
+            case Warning:
+                LOG.warning(arg0.getMessage());
+                break;
+            case Error:
+                LOG.severe(arg0.getMessage());
+                break;
+        }
     }
 
     @Override
@@ -219,6 +284,15 @@
         return result;
     }
 
+    public CompletableFuture<?> request(String method, Object parameter) {
+        LOG.log(Level.WARNING, "Received unhandled request: {0}: {1}", new Object[] {method, parameter});
+        return CompletableFuture.completedFuture(null);
+    }
+
+    public void notify(String method, Object parameter) {
+        LOG.log(Level.WARNING, "Received unhandled notification: {0}: {1}", new Object[] {method, parameter});
+    }
+
     private final class DiagnosticFixList implements LazyFixList {
 
         private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
diff --git a/java/java.lsp.server/nbcode/nbproject/platform.properties b/java/java.lsp.server/nbcode/nbproject/platform.properties
index 608720b..d48788b 100644
--- a/java/java.lsp.server/nbcode/nbproject/platform.properties
+++ b/java/java.lsp.server/nbcode/nbproject/platform.properties
@@ -30,7 +30,6 @@
 disabled.modules=\
     bcpg,\
     com.googlecode.javaewah.JavaEWAH,\
-    libs.c.kohlschutter.junixsocket,\
     org.apache.commons.lang,\
     org.apache.ws.commons.util,\
     org.apache.xmlrpc,\
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
index a3561e6..0b1afcc 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java
@@ -32,6 +32,7 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
@@ -337,4 +338,45 @@
         return encoded.substring(2, encoded.length() - 2);
     }
 
+    /**
+     * Simple conversion from HTML to plaintext. Removes all html tags incl. attributes,
+     * replaces BR, P and HR tags with newlines.
+     * @param s html text
+     * @return plaintext
+     */
+    public static String html2plain(String s) {
+        boolean inTag = false;
+        int tagStart = -1;
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char ch = s.charAt(i);
+            if (inTag) {
+                boolean alpha = Character.isAlphabetic(ch);
+                if (tagStart > 0 && !alpha) {
+                    String t = s.substring(tagStart, i).toLowerCase(Locale.ENGLISH);
+                    switch (t) {
+                        case "br": case "p": case "hr": // NOI1N
+                            sb.append("\n");
+                            break;
+                    }
+                    // prevent entering tagstart state again
+                    tagStart = -2;
+                }
+                if (ch == '>') { // NOI18N
+                    inTag = false;
+                } else if (tagStart == -1 && alpha) {
+                    tagStart = i;
+                }
+            } else {
+                if (ch == '<') { // NOI18N
+                    tagStart = -1;
+                    inTag = true;
+                    continue;
+                }
+                sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/project/ProjectAlertPresenter.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/project/ProjectAlertPresenter.java
index 0482e7c..035d796 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/project/ProjectAlertPresenter.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/project/ProjectAlertPresenter.java
@@ -39,6 +39,7 @@
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectInformation;
 import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.modules.java.lsp.server.Utils;
 import org.netbeans.spi.project.ui.ProjectProblemsProvider;
 import org.openide.DialogDisplayer;
 import org.openide.NotifyDescriptor;
@@ -489,7 +490,7 @@
             // hack: the LSP protocol does not support title. Until fixed, or implemented through a custom message,
             // embed the title into description:
             String title = Bundle.ProjectProblem_Title(projectName, p.getDisplayName());
-            NotifyDescriptor msg = new NotifyDescriptor(title + ": " + p.getDescription(), title, NotifyDescriptor.DEFAULT_OPTION, type, new Object[]{NotifyDescriptor.OK_OPTION}, null);
+            NotifyDescriptor msg = new NotifyDescriptor(title + ": " + Utils.html2plain(p.getDescription()), title, NotifyDescriptor.DEFAULT_OPTION, type, new Object[]{NotifyDescriptor.OK_OPTION}, null);
 
             // Note: the number of 'fatal' dialogs displayed at the same time is limited by the RP throughput. Dialog API does not support CompletableFuture<> interface
             // so threads may dangle.
@@ -647,7 +648,7 @@
             }
             
             String title = Bundle.ProjectProblems_Fixable_Title(projectName, ref.problem.getDisplayName());
-            String msg = ref.problem.getDescription();
+            String msg = Utils.html2plain(ref.problem.getDescription());
             if (probs.size() > 1) {
                 msg = Bundle.ProjectProblems_Additional(probs.size() - 1, msg);
             }
@@ -739,7 +740,7 @@
         }, RESOLVE_RP).thenApply(r -> {
             if (r.isResolved()) {
                 if (r.getMessage() != null) {
-                    StatusDisplayer.getDefault().setStatusText(r.getMessage());
+                    StatusDisplayer.getDefault().setStatusText(Utils.html2plain(r.getMessage()));
                 }
                 return ctx.autoResolve ? r : null;
             }
@@ -764,18 +765,19 @@
         });
         return f;
     }
-
+    
     private NotifyDescriptor createNotifyDescriptor(ProjectProblemsProvider.ProjectProblem pp, ProjectProblemsProvider.Result r, int probs) {
         String title;
         String msg;
         int type;
+        String plainMessage = Utils.html2plain(r.getMessage());
         if (r.getStatus() == ProjectProblemsProvider.Status.UNRESOLVED) {
             title = Bundle.ProjectProblems_Resolved_Error(projectName);
-            msg = Bundle.ProjectProblems_Resolved_ErrorMessage1(pp.getDisplayName(), r.getMessage());
+            msg = Bundle.ProjectProblems_Resolved_ErrorMessage1(pp.getDisplayName(), plainMessage);
             type = NotifyDescriptor.ERROR_MESSAGE;
         } else {
             title = Bundle.ProjectProblems_Resolved_Warning(projectName);
-            msg = Bundle.ProjectProblems_Resolved_WarningMessage1(pp.getDisplayName(), r.getMessage());
+            msg = Bundle.ProjectProblems_Resolved_WarningMessage1(pp.getDisplayName(), plainMessage);
             type = NotifyDescriptor.WARNING_MESSAGE;
         }
         if (probs > 0) {
diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/indexing/VanillaCompileWorker.java b/java/java.source.base/src/org/netbeans/modules/java/source/indexing/VanillaCompileWorker.java
index d35cf64..a019f79 100644
--- a/java/java.source.base/src/org/netbeans/modules/java/source/indexing/VanillaCompileWorker.java
+++ b/java/java.source.base/src/org/netbeans/modules/java/source/indexing/VanillaCompileWorker.java
@@ -690,6 +690,11 @@
                     //likely a duplicate of another class, don't touch:
                     return null;
                 }
+                if (isOtherClass(csym)) {
+                    // Something went somewhere the csym.type is Type.Unknown,
+                    // do not go any further
+                    return null;
+                }
                 currentClass = csym;
                 Type.ClassType ct = (Type.ClassType) csym.type;
                 if (csym == syms.objectType.tsym) {
@@ -1049,9 +1054,14 @@
         }
         return isErroneousClass(((JCClassDecl) tree).sym);
     }
+
     private boolean isErroneousClass(Element el) {
         return el instanceof ClassSymbol && (((ClassSymbol) el).asType() == null || ((ClassSymbol) el).asType().getKind() == TypeKind.ERROR);
     }
 
+    private boolean isOtherClass(Element el) {
+        return el instanceof ClassSymbol && (((ClassSymbol) el).asType() == null || ((ClassSymbol) el).asType().getKind() == TypeKind.OTHER);
+    }
+
     public static Function<Diagnostic<?>, String> DIAGNOSTIC_TO_TEXT = d -> d.getMessage(null);
 }
diff --git a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementJavadoc.java b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementJavadoc.java
index 1e844f3..1a1b252 100644
--- a/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementJavadoc.java
+++ b/java/java.sourceui/src/org/netbeans/api/java/source/ui/ElementJavadoc.java
@@ -394,7 +394,8 @@
         this.fileObject = compilationInfo.getFileObject();
         this.handle = element == null ? null : ElementHandle.create(element);
         this.cancel = cancel;
-        this.packageName = compilationInfo.getCompilationUnit().getPackageName().toString();
+        this.packageName = compilationInfo.getCompilationUnit().getPackageName() != null ? compilationInfo.getCompilationUnit().getPackageName().toString()
+                                                                                         : "";
         this.imports = compilationInfo.getCompilationUnit().getImports();
         this.className = compilationInfo.getCompilationUnit().getSourceFile().getName().replaceFirst("[.][^.]+$", "");
 
diff --git a/java/libs.nbjavacapi/manifest.mf b/java/libs.nbjavacapi/manifest.mf
index b89c4e5..b43812f 100644
--- a/java/libs.nbjavacapi/manifest.mf
+++ b/java/libs.nbjavacapi/manifest.mf
@@ -2,7 +2,7 @@
 AutoUpdate-Show-In-Client: true
 OpenIDE-Module: org.netbeans.libs.nbjavacapi
 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nbjavac/api/Bundle.properties
-OpenIDE-Module-Specification-Version: 17.1
+OpenIDE-Module-Specification-Version: 18.0
 OpenIDE-Module-Hide-Classpath-Packages: com.sun.javadoc.**, com.sun.source.**, javax.annotation.processing.**, javax.lang.model.**, javax.tools.**, com.sun.tools.javac.** com.sun.tools.javac.**, com.sun.tools.javadoc.**, com.sun.tools.javap.**, com.sun.tools.classfile.**, com.sun.tools.doclint.**
 OpenIDE-Module-Fragment-Host: org.netbeans.libs.javacapi
 OpenIDE-Module-Provides: org.netbeans.libs.nbjavac
diff --git a/java/libs.nbjavacapi/src/org/netbeans/modules/nbjavac/api/Bundle.properties b/java/libs.nbjavacapi/src/org/netbeans/modules/nbjavac/api/Bundle.properties
index 536a074..c5c441a 100644
--- a/java/libs.nbjavacapi/src/org/netbeans/modules/nbjavac/api/Bundle.properties
+++ b/java/libs.nbjavacapi/src/org/netbeans/modules/nbjavac/api/Bundle.properties
@@ -18,6 +18,6 @@
 OpenIDE-Module-Display-Category=Java
 OpenIDE-Module-Long-Description=\
     This library provides a Java language parser for the IDE. \
-    Supports JDK-17 features.
+    Supports JDK-18 features.
 OpenIDE-Module-Name=The nb-javac Java editing support library
 OpenIDE-Module-Short-Description=The nb-javac Java editing support library
diff --git a/nbbuild/notice-stub.txt b/nbbuild/notice-stub.txt
index e0ce5df..48bff50 100644
--- a/nbbuild/notice-stub.txt
+++ b/nbbuild/notice-stub.txt
@@ -1,5 +1,5 @@
 Apache NetBeans
-Copyright 2017-2021 The Apache Software Foundation
+Copyright 2017-2022 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/platform/core.network/src/org/netbeans/core/network/proxy/NbProxySelector.java b/platform/core.network/src/org/netbeans/core/network/proxy/NbProxySelector.java
index 8143984..f83ac1b 100644
--- a/platform/core.network/src/org/netbeans/core/network/proxy/NbProxySelector.java
+++ b/platform/core.network/src/org/netbeans/core/network/proxy/NbProxySelector.java
@@ -55,7 +55,7 @@
         original = ProxySelector.getDefault();
         LOG.log(Level.FINE, "java.net.useSystemProxies has been set to {0}", useSystemProxies());
         if (original == null || original.getClass().getName().equals(DEFAULT_PROXY_SELECTOR_CLASS_NAME)) {
-            NetworkProxyReloader.reloadNetworkProxy();
+            RP.post(() -> NetworkProxyReloader.reloadNetworkProxy());
         }
         ProxySettings.addPreferenceChangeListener(new ProxySettingsListener());
         copySettingsToSystem();
diff --git a/platform/sampler/nbproject/project.properties b/platform/sampler/nbproject/project.properties
index 6a8c641..4c6597b 100644
--- a/platform/sampler/nbproject/project.properties
+++ b/platform/sampler/nbproject/project.properties
@@ -16,6 +16,6 @@
 # under the License.
 is.autoload=true
 javac.source=1.8
-javac.compilerargs=-Xlint -Xlint:-serial
+javac.compilerargs=-Xlint -Xlint:-serial -Werror
 javadoc.arch=${basedir}/arch.xml
 javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/platform/sampler/nbproject/project.xml b/platform/sampler/nbproject/project.xml
index fc081e9..aad0da5 100644
--- a/platform/sampler/nbproject/project.xml
+++ b/platform/sampler/nbproject/project.xml
@@ -44,14 +44,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.api.progress.nb</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>1.40</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.awt</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -60,14 +52,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.dialogs</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.24</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.filesystems</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -76,14 +60,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.loaders</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.61</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.modules</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -92,22 +68,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.nodes</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.26</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>9.3</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/platform/sampler/src/org/netbeans/modules/sampler/CLISampler.java b/platform/sampler/src/org/netbeans/modules/sampler/CLISampler.java
index b9a55b4..d262439 100644
--- a/platform/sampler/src/org/netbeans/modules/sampler/CLISampler.java
+++ b/platform/sampler/src/org/netbeans/modules/sampler/CLISampler.java
@@ -98,7 +98,7 @@
         System.exit(0);
     }
 
-    private CLISampler(ThreadMXBean threadBean, File out) {
+    CLISampler(ThreadMXBean threadBean, File out) {
         super("CLISampler");
         threadMXBean = threadBean;
         output = out;
@@ -111,9 +111,9 @@
 
     @Override
     protected void saveSnapshot(byte[] arr) throws IOException {
-        FileOutputStream os = new FileOutputStream(output);
-        os.write(arr);
-        os.close();
+        try (FileOutputStream os = new FileOutputStream(output)) {
+            os.write(arr);
+        }
     }
 
     @Override
diff --git a/platform/sampler/src/org/netbeans/modules/sampler/InternalSampler.java b/platform/sampler/src/org/netbeans/modules/sampler/InternalSampler.java
index 8e8d3bf..9391891 100644
--- a/platform/sampler/src/org/netbeans/modules/sampler/InternalSampler.java
+++ b/platform/sampler/src/org/netbeans/modules/sampler/InternalSampler.java
@@ -30,12 +30,8 @@
 import java.util.logging.Logger;
 import org.netbeans.api.actions.Openable;
 import org.netbeans.api.progress.ProgressHandle;
-import org.netbeans.api.progress.ProgressHandleFactory;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
-import org.openide.loaders.DataObject;
 import org.openide.modules.Places;
 import org.openide.util.Exceptions;
 import org.openide.util.NbBundle.Messages;
@@ -48,7 +44,6 @@
 final class InternalSampler extends Sampler {
     private static final String SAMPLER_NAME = "selfsampler";  // NOI18N
     private static final String FILE_NAME = SAMPLER_NAME+SamplesOutputStream.FILE_EXT;
-    private static final String UNKNOWN_MIME_TYPE = "content/unknown"; // NOI18N
     private static final String X_DEBUG_ARG = "-Xdebug"; // NOI18N
     private static final String JDWP_DEBUG_ARG = "-agentlib:jdwp"; // NOI18N
     private static final String JDWP_DEBUG_ARG_PREFIX = "-agentlib:jdwp="; // NOI18N
@@ -59,7 +54,7 @@
     private ProgressHandle progress;
 
     static InternalSampler createInternalSampler(String key) {
-        if (SamplesOutputStream.isSupported() && isRunMode()) {
+        if (isRunMode()) {
             return new InternalSampler(key);
         }
         return null;
@@ -112,8 +107,9 @@
         return runMode;
     }
     
-    InternalSampler(String thread) {
+    InternalSampler(String thread) throws LinkageError {
         super(thread);
+        progress = ProgressHandle.createHandle(Save_Progress());
     }
 
     @Override
@@ -122,7 +118,10 @@
     }
 
     @Override
-    @Messages("SelfSamplerAction_SavedFile=Snapshot was saved to {0}")
+    @Messages({
+        "# {0} - the file",
+        "SelfSamplerAction_SavedFile=Snapshot was saved to {0}"
+    })
     protected void saveSnapshot(byte[] arr) throws IOException { // save snapshot
         File outFile = File.createTempFile(SAMPLER_NAME, SamplesOutputStream.FILE_EXT);
         File userDir = Places.getUserDirectory();
@@ -142,12 +141,15 @@
         // open snapshot
         FileObject fo = fs.findResource(FILE_NAME);
         // test for DefaultDataObject
-        if (UNKNOWN_MIME_TYPE.equals(fo.getMIMEType())) {
-            String msg = SelfSamplerAction_SavedFile(outFile.getAbsolutePath());
-            DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg));
+        Openable open = fo.getLookup().lookup(Openable.class);
+        if (open != null) {
+            open.open();
         } else {
-            DataObject dobj = DataObject.find(fo);
-            dobj.getLookup().lookup(Openable.class).open();
+            IOException ex = new IOException("Cannot open " + fo + " with MIME type: " + fo.getMIMEType());
+            String msg = SelfSamplerAction_SavedFile(outFile.getAbsolutePath());
+            Exceptions.attachSeverity(ex, Level.WARNING);
+            Exceptions.attachLocalizedMessage(ex, msg);
+            Exceptions.printStackTrace(ex);
         }
     }
 
@@ -173,7 +175,6 @@
             // log warnining
             return;
         }
-        progress = ProgressHandleFactory.createHandle(Save_Progress());
         progress.start(steps);
     }
 
@@ -183,7 +184,7 @@
             return;
         }
         progress.finish();
-        progress = null;
+        progress = ProgressHandle.createHandle(Save_Progress());
     }
 
     @Override
diff --git a/platform/sampler/src/org/netbeans/modules/sampler/Sampler.java b/platform/sampler/src/org/netbeans/modules/sampler/Sampler.java
index ae2859d..7902c12 100644
--- a/platform/sampler/src/org/netbeans/modules/sampler/Sampler.java
+++ b/platform/sampler/src/org/netbeans/modules/sampler/Sampler.java
@@ -76,7 +76,14 @@
      * is in nonstandard mode or sampling is not supported.
      */
     public static @CheckForNull Sampler createSampler(@NonNull String name) {
-        return InternalSampler.createInternalSampler(name);
+        if (SamplesOutputStream.isSupported()) {
+            try {
+                return InternalSampler.createInternalSampler(name);
+            } catch (LinkageError ex) {
+                return new StandaloneSampler(name);
+            }
+        }
+        return null;
     }
     
     /**
@@ -89,7 +96,11 @@
      */
     public static @CheckForNull Sampler createManualSampler(@NonNull String name) {
         if (SamplesOutputStream.isSupported()) {
-            return new InternalSampler(name);
+            try {
+                return new InternalSampler(name);
+            } catch (LinkageError ex) {
+                return new StandaloneSampler(name);
+            }
         }
         return null;
     }
diff --git a/platform/sampler/src/org/netbeans/modules/sampler/SamplesOutputStream.java b/platform/sampler/src/org/netbeans/modules/sampler/SamplesOutputStream.java
index e769902..9babdd4 100644
--- a/platform/sampler/src/org/netbeans/modules/sampler/SamplesOutputStream.java
+++ b/platform/sampler/src/org/netbeans/modules/sampler/SamplesOutputStream.java
@@ -36,8 +36,6 @@
 
 import javax.management.StandardMBean;
 
-import org.openide.util.Exceptions;
-
 /**
  *
  * @author Tomas Hurka
@@ -267,7 +265,6 @@
                                                              true);
                 return (Object[]) getterBean.getAttribute("Threads");
             } catch (Exception ex) {
-                Exceptions.printStackTrace(ex);
                 return new Object[0];
             }
         }
diff --git a/platform/sampler/src/org/netbeans/modules/sampler/SelfSampleVFS.java b/platform/sampler/src/org/netbeans/modules/sampler/SelfSampleVFS.java
index aa2f730..214ab8f0 100644
--- a/platform/sampler/src/org/netbeans/modules/sampler/SelfSampleVFS.java
+++ b/platform/sampler/src/org/netbeans/modules/sampler/SelfSampleVFS.java
@@ -25,10 +25,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
 import org.openide.filesystems.AbstractFileSystem;
-import org.openide.util.Enumerations;
 
 /** Filesystem that allows to virtually move some files next to each other.
  *
@@ -143,7 +143,7 @@
 
     @Override
     public Enumeration<String> attributes(String name) {
-        return Enumerations.empty();
+        return Collections.emptyEnumeration();
     }
 
     @Override
diff --git a/platform/sampler/src/org/netbeans/modules/sampler/StandaloneSampler.java b/platform/sampler/src/org/netbeans/modules/sampler/StandaloneSampler.java
new file mode 100644
index 0000000..d9bad86
--- /dev/null
+++ b/platform/sampler/src/org/netbeans/modules/sampler/StandaloneSampler.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.sampler;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+
+final class StandaloneSampler extends Sampler {
+    StandaloneSampler(String n) {
+        super(n);
+    }
+
+    @Override
+    ThreadMXBean getThreadMXBean() {
+        return ManagementFactory.getThreadMXBean();
+    }
+
+    @Override
+    void saveSnapshot(byte[] arr) throws IOException {
+    }
+
+    @Override
+    void printStackTrace(Throwable ex) {
+        ex.printStackTrace();
+    }
+
+    @Override
+    void openProgress(int steps) {
+    }
+
+    @Override
+    void closeProgress() {
+    }
+
+    @Override
+    void progress(int i) {
+    }
+}
diff --git a/platform/sampler/test/unit/src/org/netbeans/modules/sampler/AbstractSamplerBase.java b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/AbstractSamplerBase.java
new file mode 100644
index 0000000..f914a4e
--- /dev/null
+++ b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/AbstractSamplerBase.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.sampler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.openide.util.Exceptions;
+
+public abstract class AbstractSamplerBase {
+
+    protected abstract SamplerTest.Handle createManualSampler(String name);
+    protected abstract SamplerTest.Handle createSampler(String name);
+    protected abstract boolean logsMessage();
+
+    protected static abstract class Handle {
+        protected abstract void start();
+        protected abstract void stop();
+        protected abstract void stopAndWriteTo(DataOutputStream dos) throws IOException;
+        protected abstract void cancel();
+    }
+
+    final void longRunningMethod() {
+        for (int i = 0; i < 100; i++) {
+            try {
+                Thread.sleep(30);
+            } catch (InterruptedException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+    }
+
+    /**
+     * Test of cancel method, of class Sampler.
+     */
+    @Test
+    public void testCancel() {
+        SamplerTest.Handle instance = createManualSampler("cancel");
+        instance.start();
+        instance.cancel();
+    }
+
+    /**
+     * Test of createManualSampler method, of class Sampler.
+     */
+    @Test
+    public void testCreateManualSampler() {
+        String name = "gentest";
+        SamplerTest.Handle result = createManualSampler(name);
+        assertNotNull(result);
+    }
+
+    /**
+     * Test of createSampler method, of class Sampler.
+     */
+    @Test
+    public void testCreateSampler() {
+        String name = "test";
+        SamplerTest.Handle result = createSampler(name);
+        assertNotNull(result);
+    }
+
+    /**
+     * Test of stop method, of class Sampler.
+     */
+    @Test
+    public void testStop() {
+        SamplerTest.Handle instance = createManualSampler("stop");
+        assertNotNull(instance);
+        try (final SamplerTest.DD d = new SamplerTest.DD()) {
+            instance.start();
+            longRunningMethod();
+            instance.stop();
+            if (logsMessage()) {
+                assertNotNull("Cancel message has been logged", d.logged);
+            } else {
+                assertNull("CLI handler doesn't use logging", d.logged);
+            }
+        }
+    }
+
+    /**
+     * Test of stopAndWriteTo method, of class Sampler.
+     */
+    @Test
+    public void testStopAndWriteTo() throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (DataOutputStream dos = new DataOutputStream(out)) {
+            SamplerTest.Handle instance = createSampler("cancel");
+            instance.start();
+            instance.stopAndWriteTo(dos);
+        }
+        // there should no data in out, since stopAndWriteTo is
+        // invoked immediately after start
+        assertTrue(out.size() == 0);
+    }
+
+    /**
+     * Test of stopAndWriteTo method, of class Sampler.
+     */
+    @Test
+    public void testStopAndWriteTo1() throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (DataOutputStream dos = new DataOutputStream(out)) {
+            SamplerTest.Handle instance = createSampler("cancel");
+            instance.start();
+            longRunningMethod();
+            instance.stopAndWriteTo(dos);
+        }
+        // make sure we have some sampling data
+        assertTrue(out.size() > 0);
+    }
+
+    public static final class DD extends Handler implements Closeable {
+
+        private final Logger LOG = Logger.getLogger("org.openide.util.Exceptions");
+        LogRecord logged;
+
+        public DD() {
+            super();
+            LOG.addHandler(this);
+            LOG.setLevel(Level.WARNING);
+            LOG.setUseParentHandlers(false);
+            setLevel(Level.WARNING);
+        }
+
+        @Override
+        public void publish(LogRecord record) {
+            logged = record;
+        }
+
+        @Override
+        public void flush() {
+        }
+
+        @Override
+        public void close() throws SecurityException {
+            LOG.removeHandler(this);
+        }
+    } // end of DD
+
+}
diff --git a/platform/sampler/test/unit/src/org/netbeans/modules/sampler/CLISampleTest.java b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/CLISampleTest.java
new file mode 100644
index 0000000..5c8f437
--- /dev/null
+++ b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/CLISampleTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.sampler;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+public class CLISampleTest extends AbstractSamplerBase {
+    @Override
+    protected Handle createManualSampler(String name) {
+        CLISampler sampler = new CLISampler(ManagementFactory.getThreadMXBean(), null) {
+            @Override
+            protected void saveSnapshot(byte[] arr) throws IOException {
+            }
+        };
+        return new DirectSamplerHandle(sampler);
+    }
+
+    @Override
+    protected Handle createSampler(String name) {
+        return createManualSampler(name);
+    }
+
+    @Override
+    protected boolean logsMessage() {
+        return false;
+    }
+}
diff --git a/platform/sampler/test/unit/src/org/netbeans/modules/sampler/DirectSamplerHandle.java b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/DirectSamplerHandle.java
new file mode 100644
index 0000000..a3a82f5
--- /dev/null
+++ b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/DirectSamplerHandle.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.sampler;
+
+import java.io.DataOutputStream;
+
+final class DirectSamplerHandle extends AbstractSamplerBase.Handle {
+    private final Sampler sampler;
+
+    DirectSamplerHandle(Sampler sampler) {
+        this.sampler = sampler;
+    }
+
+    @Override
+    public final synchronized void start() {
+        sampler.start();
+    }
+
+    @Override
+    public final void cancel() {
+        sampler.cancel();
+    }
+
+    @Override
+    public final void stopAndWriteTo(DataOutputStream dos) {
+        sampler.stopAndWriteTo(dos);
+    }
+
+    @Override
+    public final void stop() {
+        sampler.stop();
+    }
+}
diff --git a/platform/sampler/test/unit/src/org/netbeans/modules/sampler/SamplerTest.java b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/SamplerTest.java
index 36f4cf7..9e39804 100644
--- a/platform/sampler/test/unit/src/org/netbeans/modules/sampler/SamplerTest.java
+++ b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/SamplerTest.java
@@ -18,149 +18,24 @@
  */
 package org.netbeans.modules.sampler;
 
-import java.awt.Dialog;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import org.junit.*;
-import static org.junit.Assert.*;
-import org.netbeans.junit.MockServices;
-import org.netbeans.junit.NbTestCase;
-import org.openide.DialogDescriptor;
-import org.openide.DialogDisplayer;
-import org.openide.NotifyDescriptor;
-import org.openide.util.Exceptions;
-
 /**
  *
  * @author Tomas Hurka
  */
-public class SamplerTest {
+public class SamplerTest extends AbstractSamplerBase {
 
-    @BeforeClass
-    public static void setUpClass() throws Exception {
-        //register DialogDisplayer which "pushes" Yes option in the document save dialog
-        MockServices.setServices(DD.class);
+    @Override
+    protected Handle createManualSampler(String name) {
+        return new DirectSamplerHandle(Sampler.createManualSampler(name));
     }
 
-    @AfterClass
-    public static void tearDownClass() throws Exception {
+    @Override
+    protected Handle createSampler(String name) {
+        return new DirectSamplerHandle(Sampler.createSampler(name));
     }
 
-    @Before
-    public void setUp() {
+    @Override
+    protected boolean logsMessage() {
+        return true;
     }
-    
-    @After
-    public void tearDown() {
-    }
-
-    /**
-     * Test of createSampler method, of class Sampler.
-     */
-    @Test
-    public void testCreateSampler() {
-        System.out.println("createSampler");
-        String name = "test";
-        Sampler result = Sampler.createSampler(name);
-        assertNotNull(result);
-    }
-
-    /**
-     * Test of createManualSampler method, of class Sampler.
-     */
-    @Test
-    public void testCreateManualSampler() {
-        System.out.println("createManualSampler");
-        String name = "gentest";
-        Sampler result = Sampler.createManualSampler(name);
-        assertNotNull(result);
-    }
-
-    /**
-     * Test of cancel method, of class Sampler.
-     */
-    @Test
-    public void testCancel() {
-        System.out.println("cancel");
-        Sampler instance = Sampler.createManualSampler("cancel");
-        instance.start();
-        instance.cancel();
-    }
-
-    /**
-     * Test of stopAndWriteTo method, of class Sampler.
-     */
-    @Test
-    public void testStopAndWriteTo() throws IOException {
-        System.out.println("stopAndWriteTo");
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(out);
-        Sampler instance = Sampler.createSampler("cancel");
-        instance.start();
-        instance.stopAndWriteTo(dos);
-        dos.close();
-        // there should no data in out, since stopAndWriteTo is 
-        // invoked immediately after start
-        assertTrue(out.size() == 0);
-    }
-   /**
-     * Test of stopAndWriteTo method, of class Sampler.
-     */
-    @Test
-    public void testStopAndWriteTo1() throws IOException {
-        System.out.println("stopAndWriteTo1");
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        DataOutputStream dos = new DataOutputStream(out);
-        Sampler instance = Sampler.createSampler("cancel");
-        instance.start();
-        longRunningMethod();
-        instance.stopAndWriteTo(dos);
-        dos.close();
-        // make sure we have some sampling data
-        assertTrue(out.size() > 0);
-    }
-
-    private void longRunningMethod() {
-        for (int i=0; i<100;i++) {
-            try {
-                Thread.sleep(30);
-            } catch (InterruptedException ex) {
-                Exceptions.printStackTrace(ex);
-            }
-        }
-    }
-
-    /**
-     * Test of stop method, of class Sampler.
-     */
-    @Test
-    public void testStop() {
-        System.out.println("stop");
-        Sampler instance = Sampler.createManualSampler("stop");
-        DD.hasData = false;
-        instance.start();
-        longRunningMethod();
-        instance.stop();
-        assert(DD.hasData);
-    }
-
-    /** Our own dialog displayer.
-     */
-    public static final class DD extends DialogDisplayer {
-        static boolean hasData;
-        
-        @Override
-        public Dialog createDialog(DialogDescriptor descriptor) {
-            throw new IllegalStateException ("Not implemented");
-        }
-        
-        @Override
-        public Object notify(NotifyDescriptor descriptor) {
-           hasData = true;
-           return null;
-        }
-        
-    } // end of DD    
-    
 }
diff --git a/platform/sampler/test/unit/src/org/netbeans/modules/sampler/StandaloneSamplerTest.java b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/StandaloneSamplerTest.java
new file mode 100644
index 0000000..7e6e128
--- /dev/null
+++ b/platform/sampler/test/unit/src/org/netbeans/modules/sampler/StandaloneSamplerTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.sampler;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class StandaloneSamplerTest extends AbstractSamplerBase {
+    private static Class<?> samplerClass;
+
+    @BeforeClass
+    public static void loadInIsolatedLoader() throws ClassNotFoundException {
+        URLClassLoader l = new URLClassLoader(new URL[] {
+            Sampler.class.getProtectionDomain().getCodeSource().getLocation()
+        }, Sampler.class.getClassLoader().getParent());
+
+        samplerClass = l.loadClass("org.netbeans.modules.sampler.Sampler");
+
+        try {
+            Class<?> lookupClass = l.loadClass("org.openide.util.Lookup");
+            fail("It shouldn't be possible to load Lookup class with this classloader: " + lookupClass);
+        } catch (ClassNotFoundException ok) {
+            assertNotNull(ok);
+        }
+    }
+
+    @Override
+    protected Handle createManualSampler(String name) {
+        try {
+            Object sampler = samplerClass.getMethod("createManualSampler", String.class).invoke(null, name);
+            return new ReflectionHandle(sampler);
+        } catch (ReflectiveOperationException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    @Override
+    protected Handle createSampler(String name) {
+        try {
+            Object sampler = samplerClass.getMethod("createSampler", String.class).invoke(null, name);
+            return new ReflectionHandle(sampler);
+        } catch (ReflectiveOperationException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    @Override
+    protected boolean logsMessage() {
+        return false;
+    }
+
+    private static final class ReflectionHandle extends Handle {
+        private final Object sampler;
+
+        private ReflectionHandle(Object sampler) {
+            this.sampler = sampler;
+        }
+
+        @Override
+        protected void start() {
+            try {
+                samplerClass.getMethod("start").invoke(sampler);
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        protected void stop() {
+            try {
+                samplerClass.getMethod("stop").invoke(sampler);
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        protected void stopAndWriteTo(DataOutputStream dos) throws IOException {
+            try {
+                samplerClass.getMethod("stopAndWriteTo", DataOutputStream.class).invoke(sampler, dos);
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        protected void cancel() {
+            try {
+                samplerClass.getMethod("cancel").invoke(sampler);
+            } catch (ReflectiveOperationException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+}