blob: 4e7e439ea49eaf21d651f69b548261d6ee169cff [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.lsp.server.debugging;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import org.eclipse.lsp4j.debug.StoppedEventArguments;
import org.eclipse.lsp4j.debug.ThreadEventArguments;
import org.netbeans.api.debugger.DebuggerEngine;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.DebuggerManagerAdapter;
import org.netbeans.api.debugger.Session;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.spi.debugger.ui.DebuggingView.DVSupport;
import org.netbeans.spi.debugger.ui.DebuggingView.DVThread;
/**
*
* @author martin
*/
public final class NbThreads {
private int lastId = 1;
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final Map<Integer, DVThread> threads = new HashMap<>();
private final Map<DVThread, Integer> threadIds = new HashMap<>();
private final Map<JPDAThread, Integer> jpdaThreadIds = new HashMap<>();
private final ThreadObjects threadObjects = new ThreadObjects();
public void initialize(DebugAdapterContext context, Map<String, Object> options) {
DebuggerManager.getDebuggerManager().addDebuggerListener(DebuggerManager.PROP_SESSIONS, new DebuggerManagerAdapter() {
@Override
public void sessionAdded(Session session) {
DebuggerManager.getDebuggerManager().removeDebuggerListener(DebuggerManager.PROP_SESSIONS, this);
JPDADebugger debugger = session.lookupFirst(null, JPDADebugger.class);
initThreads(context, debugger);
}
});
}
private void initThreads(DebugAdapterContext context, JPDADebugger debugger) {
DebuggerEngine engine = debugger.getSession().getCurrentEngine();
if (engine == null) {
debugger.getSession().addPropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
DebuggerEngine currentEngine = debugger.getSession().getCurrentEngine();
if (currentEngine != null) {
debugger.getSession().removePropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, this);
if (!initialized.getAndSet(true)) {
initThreads(context, currentEngine);
}
}
}
});
engine = debugger.getSession().getCurrentEngine();
}
if (engine != null && !initialized.getAndSet(true)) {
initThreads(context, engine);
}
}
private void initThreads(DebugAdapterContext context, DebuggerEngine engine) {
DVSupport dvSupport = engine.lookupFirst(null, DVSupport.class);
dvSupport.addPropertyChangeListener(evt -> {
switch (evt.getPropertyName()) {
case DVSupport.PROP_THREAD_STARTED:
DVThread dvThread = (DVThread) evt.getNewValue();
int id;
synchronized (threads) {
Integer idInteger = threadIds.get(dvThread);
if (idInteger == null) {
id = lastId++;
threads.put(id, dvThread);
threadIds.put(dvThread, id);
jpdaThreadIds.put(getJPDAThread(dvThread), id);
} else {
// It could be among all threads already
id = idInteger;
}
}
ThreadEventArguments threadStartEvent = new ThreadEventArguments();
threadStartEvent.setReason("started");
threadStartEvent.setThreadId(id);
context.getClient().thread(threadStartEvent);
break;
case DVSupport.PROP_THREAD_DIED:
dvThread = (DVThread) evt.getOldValue();
id = 0;
synchronized (threads) {
Integer idObject = threadIds.remove(dvThread);
jpdaThreadIds.remove(getJPDAThread(dvThread));
if (idObject != null) {
id = idObject;
threads.remove(id);
}
}
if (id > 0) {
ThreadEventArguments threadDeathEvent = new ThreadEventArguments();
threadDeathEvent.setReason("exited");
threadDeathEvent.setThreadId(id);
context.getClient().thread(threadDeathEvent);
}
break;
case DVSupport.PROP_THREAD_SUSPENDED:
dvThread = (DVThread) evt.getNewValue();
id = getId(dvThread);
assert id > 0: "Unknown ID for thread " + dvThread;
if (id > 0) {
String eventName;
if (dvThread.isInStep()) {
eventName = "step";
} else if (dvThread.getCurrentBreakpoint() != null) {
context.getBreakpointManager().notifyBreakpointHit(id, dvThread.getCurrentBreakpoint());
eventName = "breakpoint";
} else {
eventName = "pause";
}
StoppedEventArguments stoppedEvent = new StoppedEventArguments();
stoppedEvent.setReason(eventName);
stoppedEvent.setThreadId(id);
context.getClient().stopped(stoppedEvent);
}
break;
case DVSupport.PROP_THREAD_RESUMED:
dvThread = (DVThread) evt.getNewValue();
id = getId(dvThread);
assert id > 0: "Unknown ID for thread " + dvThread;
context.getBreakpointManager().notifyBreakpointHit(id, null);
// Should not sponaneously resume, the client should request resume and thus knows about it.
break;
}
});
// Assure that all threads are added:
synchronized (threads) {
for (DVThread dvThread : dvSupport.getAllThreads()) {
if (!threadIds.containsKey(dvThread)) { // We could get it twice if thread start event comes now
int id = lastId++;
threads.put(id, dvThread);
threadIds.put(dvThread, id);
jpdaThreadIds.put(getJPDAThread(dvThread), id);
}
}
}
}
/**
* Get the thread ID, or <code>0</code> if the thread was not found.
*/
public int getId(DVThread thread) {
int id = 0;
Integer idObject;
synchronized (threads) {
idObject = threadIds.get(thread);
}
if (idObject != null) {
id = idObject;
}
return id;
}
/**
* Get thread by its ID.
* @return the thread, or <code>null</code> when no thread with that ID exists.
*/
public DVThread getThread(int id) {
synchronized (threads) {
return threads.get(id);
}
}
/**
* Get the thread ID, or <code>0</code> if the thread was not found.
*/
public int getId(JPDAThread thread) {
int id = 0;
Integer idObject;
synchronized (threads) {
idObject = jpdaThreadIds.get(thread);
}
if (idObject != null) {
id = idObject;
}
return id;
}
private JPDAThread getJPDAThread(DVThread dvThread) {
// JPDA implementation implements Supplier.
return ((Supplier<JPDAThread>) dvThread).get();
}
public void visitThreads(BiConsumer<Integer, DVThread> threadsConsumer) {
synchronized (threads) {
for (Map.Entry<Integer, DVThread> entry : threads.entrySet()) {
threadsConsumer.accept(entry.getKey(), entry.getValue());
}
}
}
public ThreadObjects getThreadObjects() {
return threadObjects;
}
}