package debugger;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.MatteBorder;
import observer.PseucoObservable;
import observer.PseucoObserver;
import resources.ResourceLoader;
import debugger.debuggerComponents.StackTraceFrame;
import debugger.debuggerComponents.ThreadActionStatus;
import debugger.debuggerComponents.ThreadStatus;
import debugger.debuggerComponents.ThreadStatus.ThreadState;
/**
* Stores informations and methods about a specific agent (thread) and
* displays them on a frame.
*
* Warning: It is not guaranteed that this component will have a parent (or ever had one).
* @author Markus
*
*/
public class ManagedThreadPane extends JPanel implements PseucoObservable {
private static final long serialVersionUID = 1L;
private static final int height = 30;
private static final int height2 = 30;
private static boolean lineMapperInited= false;
private static HashMap<String, HashMap<Integer, HashSet<Integer>>> lineNumberMapping;
private Set<PseucoObserver> observers= new HashSet<PseucoObserver>();
private Thread thread;
private Thread originThread;
private DebuggerHandler dbhandler;
private ManagedThreadPane mtp = this;
private boolean expanded = false;
private JPanel line1, line2;
private ThreadStatus status;
private JLabel threadname;
private JLabel startedBy;
private JButton btnResume;
private JButton btnSuspend;
private JButton btnStop;
private JButton btnSingleStep;
private final Dimension spacer = new Dimension(10,0);
private final Color odd_color = new Color(0xfff0ffff);
/**
* Create a new ManagedThreadPane if a new thread starts.
* @param thread
*/
public ManagedThreadPane(final DebuggerHandler dbhandler, final Thread thread, final Thread starter) {
this.thread = thread;
this.dbhandler = dbhandler;
setPreferredSize(new Dimension(0,height));
setMaximumSize(new Dimension(Short.MAX_VALUE, height));
setBorder(new MatteBorder(0, 0, 1, 0, new Color(0xffdddddd)));
setBackground(Color.WHITE);
setOpaque(true);
line1 = new JPanel();
line2 = new JPanel();
line2.setVisible(expanded);
makeLine1(starter);
makeLine2();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(line1);
add(line2);
add(Box.createVerticalGlue());
//Init
setThreadState(ThreadState.RUNNING);
hideSuspendedInfos();
refreshCommandCount();
addMouseListener(new MouseAdapter(){
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1){
dbhandler.expand(mtp);
}
}
});
}
//private JLabel lblInternalState;
private JLabel lblState;
private JLabel lblCommandCount;
private ThreadActionStatus taStatus;
private JButton btnStackTrace;
private void makeLine2() {
line2.setLayout(new BoxLayout(line2, BoxLayout.X_AXIS));
line2.setPreferredSize(new Dimension(0,height2));
line2.setMaximumSize(new Dimension(Short.MAX_VALUE, height2));
line2.setOpaque(false);
line2.add(Box.createRigidArea(spacer));
//State
lblState = new JLabel();
lblState.setText("State: ");
lblState.setToolTipText("The state of an agent can only be determined, if the agent is suspended. ");
line2.add(lblState);
taStatus = new ThreadActionStatus(thread);
line2.add(taStatus);
line2.add(Box.createHorizontalGlue());
lblCommandCount = new JLabel();
lblCommandCount.setToolTipText("<html>The number of commands this agent has already executed. <br>" +
"More technically, this is the count of <code>Simulate.HWInterrupt()</code>, <br>" +
"a statement that the PseuCo-Compiler places after every single command. <br>" +
"That means for example: <code>while(true){}</code> does not execute a single command, <br>" +
"but <code>while(true){ i=i; }</code> does. <br>" +
"ATTENTION: The compiler might place more than one call after a single command, so <br>" +
"don't see this as an absolute value.");
line2.add(lblCommandCount);
line2.add(Box.createHorizontalGlue());
/*lblInternalState = new JLabel();
line2.add(lblInternalState);*/
btnStackTrace = new JButton(actionStackTrace);
line2.add(btnStackTrace);
line2.add(Box.createRigidArea(spacer));
}
private void makeLine1(Thread starter) {
line1.setLayout(new BoxLayout(line1, BoxLayout.X_AXIS));
line1.setPreferredSize(new Dimension(0,height));
line1.setMaximumSize(new Dimension(Short.MAX_VALUE, height));
line1.setOpaque(false);
line1.add(Box.createRigidArea(spacer));
//Status
status = new ThreadStatus(thread);
line1.add(status);
line1.add(Box.createRigidArea(spacer));
//Name
threadname = new JLabel(thread.getName());
line1.add(threadname);
line1.add(Box.createRigidArea(spacer));
//started by
startedBy = new JLabel();
line1.add(startedBy);
setOriginThread(starter);
line1.add(Box.createHorizontalGlue());
//Buttons
btnSingleStep = makeButton(actionSingleStep);
btnResume = makeButton(actionResume);
btnSuspend = makeButton(actionSuspend);
btnStop = makeButton(actionStop);
line1.add(btnSingleStep);
line1.add(btnResume);
line1.add(btnSuspend);
line1.add(btnStop);
line1.add(Box.createRigidArea(spacer));
}
private JButton makeButton(Action a){
JButton result = new JButton(a);
result.setPreferredSize(new Dimension(28,28));
result.setSize(result.getPreferredSize());
result.setMaximumSize(result.getSize());
result.setIconTextGap(0);
return result;
}
private static final Icon iconResume = ResourceLoader.loadIcon("resumebtn.png");
private static final Icon iconSuspend = ResourceLoader.loadIcon("suspendbtn.png");
private static final Icon iconStop = ResourceLoader.loadIcon("stop_small.png");
private static final Icon iconStacktrace = ResourceLoader.loadIcon("stackframe.gif");
private static final Icon iconSingleStep = ResourceLoader.loadIcon("stepover.gif");
/**
* Resumes the thread
*/
private Action actionResume = new ActionResume("", iconResume);
private class ActionResume extends AbstractAction{
private static final long serialVersionUID = 1L;
public ActionResume(String string, Icon icon) {
super(string, icon);
putValue(SHORT_DESCRIPTION, "Resumes this agent");
}
@Override
public void actionPerformed(ActionEvent e) {
dbhandler.resumeThreadFromEDT(mtp);
}
};
/**
* Suspends the thread
*/
private Action actionSuspend = new ActionSuspend("", iconSuspend);
private class ActionSuspend extends AbstractAction {
private static final long serialVersionUID = 1L;
public ActionSuspend(String string, Icon icon) {
super(string, icon);
putValue(SHORT_DESCRIPTION, "Suspends this agent");
}
@Override
public void actionPerformed(ActionEvent e) {
dbhandler.suspendThreadFromEDT(mtp);
}
};
/**
* Stops the thread
*/
private Action actionStop = new ActionStop("", iconStop);
private class ActionStop extends AbstractAction {
private static final long serialVersionUID = 1L;
public ActionStop(String string, Icon icon) {
super(string, icon);
putValue(SHORT_DESCRIPTION, "Terminates this agent");
}
@Override
public void actionPerformed(ActionEvent e) {
dbhandler.stopThreadFromEDT(mtp);
}
};
private Action actionStackTrace = new ActionStacktrace("Java-Stacktrace",iconStacktrace);
private class ActionStacktrace extends AbstractAction {
private static final long serialVersionUID = 1L;
public ActionStacktrace(String string, Icon icon) {
super(string, icon);
putValue(SHORT_DESCRIPTION, "Shows a stacktrace of this thread. The stacktrace corresponds to the java version. ");
}
@Override
public void actionPerformed(ActionEvent arg0) {
new StackTraceFrame(thread.getStackTrace(), thread);
}
};
private Action actionSingleStep = new ActionSingleStep("", iconSingleStep);
private class ActionSingleStep extends AbstractAction {
private static final long serialVersionUID = 1L;
public ActionSingleStep(String string, Icon icon) {
super(string, icon);
putValue(SHORT_DESCRIPTION, "<html>Does a <b>single step</b> with this agent and suspends it afterwards. <br>" +
"More technically, this allows the agent to run to the next call of <code>Simulate.HWInterrupt()</code>, <br>" +
"a statement that the PseuCo-Compiler places after every single command. <br>" +
"That means for example: <code>while(true){}</code> does not execute such a command and will not <br>" +
"be suspended, but <code>while(true){ i=i; }</code> does, so you can do a single step in this loop. <br>" +
"<b>Attention</b>: The compiler might place more than one call after a single command, so <br>" +
"you might have to do multiple steps until a single PseuCo command is executed.");
}
@Override
public void actionPerformed(ActionEvent arg0) {
setSingleStep(true);
if (status.getState() == ThreadState.SUSPENDED)
dbhandler.resumeThreadFromEDT(mtp);
}
};
/**
* Will be called when the thread stops
*/
public void threadStopped(){
setThreadState(ThreadState.TERMINATED);
btnStackTrace.setEnabled(false);
showSuspendedInfos();
refreshCommandCount();
notifyThreadStopping();
}
/**
* Will be called when the thread gets suspended
*/
public void threadSuspended(){
setThreadState(ThreadState.SUSPENDED);
showSuspendedInfos();
refreshCommandCount();
notifyCodeLineChanges();
}
/**
* Will be called when the thread gets resumed
*/
public void threadResumed(){
setThreadState(ThreadState.RUNNING);
hideSuspendedInfos();
}
public ThreadState getThreadState(){
return status.getState();
}
public void setThreadState(ThreadState s){
status.setState(s);
btnSingleStep.setEnabled(s == ThreadState.SUSPENDED || s == ThreadState.RUNNING);
btnResume.setEnabled(s == ThreadState.SUSPENDED);
btnSuspend.setEnabled(s == ThreadState.RUNNING);
btnStop.setEnabled(s == ThreadState.SUSPENDED || s == ThreadState.RUNNING);
}
public Thread getThread(){
return thread;
}
public void removeSelf(){
Container c = getParent();
if (c != null){
c.remove(this);
c.validate();
validate();
c.repaint(50L);
}
}
public Thread getOriginThread() {
return originThread;
}
public void setOriginThread(Thread originThread) {
this.originThread = originThread;
startedBy.setText("(started by "+this.originThread.getName()+")");
}
//private Thread.State internalState = Thread.State.NEW;
/**
* Warning: not called in EDT. Do not do any gui operation!
* @param state
*/
/*public void setThreadInternalState(State state) {
internalState = state;
}*/
private void showSuspendedInfos(){
/*lblInternalState.setText("Status: "+internalState.toString());
String hint ="";
switch (internalState) {
case NEW: hint="A new, not startet agent, ready to run. "; break;
case RUNNABLE: hint="The agent is running, not blocked or waiting. "; break;
case BLOCKED: hint="The agent is blocked, because he waits for a lock or a monitor. "; break;
case TERMINATED: hint="The agent has terminated. "; break;
case WAITING: hint="The agent is waiting for another agent to perform an action. " +
"Examples are waiting for a signal or for another agent's termination (join). "; break;
case TIMED_WAITING: hint="The agent is waiting for something, but has a timeout (sleep, join with timeout, ...). "; break;
default:
break;
}
lblInternalState.setToolTipText(hint);*/
//taStatus.setVisible(true);
//lblState.setText("State: ");
taStatus.determineState();
}
private void hideSuspendedInfos(){
/*lblInternalState.setText("Status: ?");
lblInternalState.setToolTipText("The status can only be monitored if the agent is suspended. ");*/
//taStatus.setVisible(false);
//lblState.setText("State: -?-");
}
public void expand(){
expanded = true;
setPreferredSize(new Dimension(0,height+height2));
setMaximumSize(new Dimension(Short.MAX_VALUE, height+height2));
line2.setVisible(expanded);
invalidate();
}
public void collapse(){
expanded = false;
setPreferredSize(new Dimension(0,height));
setMaximumSize(new Dimension(Short.MAX_VALUE, height));
line2.setVisible(expanded);
invalidate();
}
public void refreshState() {
taStatus.determineState();
}
public void setLastThreadJoined(Thread joined){
taStatus.setLastThreadJoined(joined);
}
private int commandCount = 0;
private ReentrantLock lockCommandCount = new ReentrantLock();
public void incCommandCount(){
lockCommandCount.lock();
try{
commandCount++;
}finally{ lockCommandCount.unlock(); }
}
public int getCommandCount(){
int res;
lockCommandCount.lock();
try{
res = commandCount;
}finally{ lockCommandCount.unlock(); }
return res;
}
private void notifyThreadStopping() {
notifyObservers(null, -1);
}
@SuppressWarnings("unchecked")
private void notifyCodeLineChanges() {
Runnable runningCode= null;
try {
Field fTarget = Thread.class.getDeclaredField("target");
fTarget.setAccessible(true);
runningCode = (Runnable) fTarget.get(thread);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (runningCode == null)
runningCode= thread;
Class<?> settingsClass;
try {
settingsClass = Class.forName("include.Settings");
Method[] methods= settingsClass.getDeclaredMethods();
Method lineMethod= null;
Method javaFilesMethod= null;
Method initMaps= null;
for (Method mthd : methods) {
if (mthd.getName().equals("getPseuCoLineNumber"))
lineMethod= mthd;
if (mthd.getName().equals("getListOfExternJavaFiles"))
javaFilesMethod= mthd;
if (mthd.getName().equals("initLineNumberMapping"))
initMaps= mthd;
}
if (lineMethod == null || javaFilesMethod == null || initMaps == null)
throw new NoSuchMethodException();
if (!lineMapperInited) {
initMaps.invoke(null);
lineNumberMapping = (HashMap<String, HashMap<Integer, HashSet<Integer>>>)
Class.forName("include.Settings").getDeclaredField("lineNumberMapping").get(null);
lineMapperInited= true;
}
StackTraceElement[] stacktrace = thread.getStackTrace();
// two steps, first look for the HWInterrupt call, then backtrack to the enclosing method (and possibly
// backtrack lines until finding a relevant one)
int simulatePos= -1;
for (int i = 0; i < stacktrace.length; i++) {
String javaClass = stacktrace[i].getClassName();
String javaMethod= stacktrace[i].getMethodName();
if (javaClass.equals("include.Simulate") && javaMethod.equals("HWInterrupt")) {
// possible code line change
simulatePos= i;
break;
}
}
Set<Integer> pseucoLines= null;
String javaClass= "";
if (simulatePos > 0) {
javaClass= stacktrace[simulatePos+1].getClassName();
int line= stacktrace[simulatePos+1].getLineNumber();
String[] tokenized= javaClass.split("\\.");
javaClass= tokenized[tokenized.length-1];
HashMap<Integer, HashSet<Integer>> mappingForClass = lineNumberMapping.get(javaClass);
if (mappingForClass != null) {
while (line > 0) {
if (mappingForClass.containsKey(line)) {
pseucoLines= mappingForClass.get(line);
if (pseucoLines.size() > 0)
break;
}
line--;
}
}
}
if (pseucoLines != null && pseucoLines.size() > 0) {
List<Integer> list= new ArrayList<Integer>(pseucoLines);
Integer[] lineArray= list.toArray(new Integer[0]);
Arrays.sort(lineArray);
int currentLine= lineArray[lineArray.length - 1];
notifyObservers(javaClass + ".java", currentLine);
}
} catch (ClassNotFoundException e) {
//
} catch (NoSuchMethodException e) {
//
} catch (IllegalAccessException e) {
//e.printStackTrace();
} catch (InvocationTargetException e) {
e.getCause().printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
public void refreshCommandCount() {
lblCommandCount.setText("#Commands = "+getCommandCount());
}
private boolean doSingleStep = false;
public boolean singleStep(){
boolean res = doSingleStep;
if (doSingleStep){
if (SwingUtilities.isEventDispatchThread()) setSingleStep(false);
else SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setSingleStep(false);
}
});
}
return res;
}
public void setSingleStep(boolean s){
if (s == doSingleStep) return;
doSingleStep = s;
btnSingleStep.setEnabled(! doSingleStep);
}
private boolean isOdd = false;
public void setOdd(boolean odd){
if (odd == isOdd) return;
isOdd = odd;
setBackground(isOdd ? odd_color : Color.WHITE);
repaint(50L);
}
@Override
public void registerObserver(PseucoObserver obs) {
observers.add(obs);
}
@Override
public void deregisterObserver(PseucoObserver obs) {
observers.remove(obs);
}
@Override
public void notifyObservers(Object ... args) {
for (PseucoObserver obs : observers) {
obs.update(this, args);
}
}
}