package jadex.bpmn.tools; import jadex.bpmn.model.MActivity; import jadex.bpmn.model.MLane; import jadex.bpmn.model.MPool; import jadex.bpmn.runtime.BpmnInterpreter; import jadex.bpmn.runtime.HistoryEntry; import jadex.bpmn.runtime.ProcessThread; import jadex.bpmn.runtime.ThreadContext; import jadex.commons.ChangeEvent; import jadex.commons.IBreakpointPanel; import jadex.commons.IChangeListener; import jadex.commons.jtable.ResizeableTableHeader; import jadex.commons.jtable.TableSorter; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; /** * Panel for showing / manipulating the Rete agenda. */ public class ProcessViewPanel extends JPanel { //------- attributes -------- /** The agenda. */ protected BpmnInterpreter instance; /** The change listener. */ protected IChangeListener listener; /** Local copy of activations. */ protected ProcessThreadInfo[] threads_clone; /** Local copy of agenda history. */ protected Object[] history_clone; /** Local copy of next action. */ protected Object next; /** The list model for the activations. */ protected ProcessThreadModel ptmodel; /** The list model for the history. */ protected HistoryModel hmodel; /** The list for the activations. */ protected JTable threads; /** The list for the history. */ protected JTable history; /** The breakpoint panel. */ protected IBreakpointPanel bpp; //------- constructors -------- /** * Create an agenda panel. */ public ProcessViewPanel(final BpmnInterpreter instance, IBreakpointPanel bpp) { this.instance = instance; this.bpp = bpp; this.ptmodel = new ProcessThreadModel(); this.hmodel = new HistoryModel(); // todo: problem should be called on process execution thread! instance.setHistoryEnabled(true); // Todo: Disable history on close? threads_clone = getThreadInfos(); history_clone = instance.getHistory().toArray(); TableSorter sorter = new TableSorter(ptmodel); this.threads = new JTable(sorter); ResizeableTableHeader header = new ResizeableTableHeader(threads.getColumnModel()); header.setIncludeHeaderWidth(true); // threads.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); threads.setTableHeader(header); threads.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); sorter.setTableHeader(header); threads.getColumnModel().setColumnMargin(10); sorter = new TableSorter(hmodel); this.history = new JTable(sorter); header = new ResizeableTableHeader(history.getColumnModel()); header.setIncludeHeaderWidth(true); // history.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); history.setTableHeader(header); history.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); sorter.setTableHeader(header); history.getColumnModel().setColumnMargin(10); this.listener = new IChangeListener() { ProcessThreadInfo[] threads_clone; Object[] history_clone; Object next; boolean invoked; public void changeOccurred(ChangeEvent event) { synchronized(ProcessViewPanel.this) { List his = instance.getHistory(); threads_clone = getThreadInfos(); history_clone = his!=null? his.toArray(): new Object[0]; // next = instance.getNextActivation(); } if(!invoked) { invoked = true; SwingUtilities.invokeLater(new Runnable() { public void run() { invoked = false; synchronized(ProcessViewPanel.this) { ProcessViewPanel.this.threads_clone = threads_clone; ProcessViewPanel.this.history_clone = history_clone; ProcessViewPanel.this.next = next; } updateViews(); } }); } } }; instance.addChangeListener(listener); SwingUtilities.invokeLater(new Runnable() { public void run() { updateViews(); } }); JButton clear = new JButton("Clear"); clear.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // todo: invoke on agent thread with invoke later // works because history is synchronized. List his = instance.getHistory(); if(his!=null) { his.clear(); history_clone = new Object[0]; history.repaint(); } } }); final JCheckBox hon = new JCheckBox("Store History"); hon.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // todo: invoke on agent thread with invoke later instance.setHistoryEnabled(hon.isSelected()); } }); hon.setSelected(true); JPanel procp = new JPanel(new BorderLayout()); procp.add(new JScrollPane(threads)); procp.setBorder(BorderFactory.createTitledBorder("Processes")); JPanel historyp = new JPanel(new BorderLayout()); historyp.add(new JScrollPane(history)); historyp.setBorder(BorderFactory.createTitledBorder("History")); JPanel buts = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buts.add(hon); buts.add(clear); historyp.add(buts, BorderLayout.SOUTH); JSplitPane tmp = new JSplitPane(JSplitPane.VERTICAL_SPLIT); tmp.add(procp); tmp.add(historyp); tmp.setDividerLocation(200); // Hack?! setLayout(new BorderLayout()); add(tmp, BorderLayout.CENTER); } //-------- methods -------- /** * Dispose the panel and remove any listeners. */ public void dispose() { // instance.removeChangeListener(listener); } //-------- helper methods -------- /** * Update views. */ protected void updateViews() { ptmodel.fireTableDataChanged(); hmodel.fireTableDataChanged(); // if(ptmodel.getRowCount()>0) // ((ResizeableTableHeader)threads.getTableHeader()).resizeAllColumns(); // if(hmodel.getRowCount()>0) // ((ResizeableTableHeader)history.getTableHeader()).resizeAllColumns(); threads.repaint(); history.repaint(); if(bpp!=null) { List sel_bps = new ArrayList(); for(int i=0; i<threads_clone.length; i++) { if(threads_clone[i].getActivity()!=null) sel_bps.add(threads_clone[i].getActivity().getBreakpointId()); } bpp.setSelectedBreakpoints((String[])sel_bps.toArray(new String[sel_bps.size()])); } } /** * Must be called on process execution thread! * Gets infos about the current threads. */ protected ProcessThreadInfo[] getThreadInfos() { List ret = null; ThreadContext tc = instance.getThreadContext(); Set threads = tc.getAllThreads(); if(threads!=null) { ret = new ArrayList(); for(Iterator it=threads.iterator(); it.hasNext(); ) { ProcessThread pt = (ProcessThread)it.next(); ret.add(new ProcessThreadInfo(pt.getId(), pt.getActivity(), //pt.getLastEdge(), pt.getException(), pt.isWaiting(), pt.getData()==null? new HashMap(): new HashMap(pt.getData()))); } } return (ProcessThreadInfo[])ret.toArray(new ProcessThreadInfo[ret.size()]); } //-------- helper classes -------- /** * List model for activations. */ protected class ProcessThreadModel extends AbstractTableModel { protected String[] colnames = new String[]{"Process-Id", "Activity", "Pool", "Lane", "Exception", "Data", "Status"}; public String getColumnName(int column) { return colnames[column]; } public int getColumnCount() { return 7; } public int getRowCount() { return threads_clone.length; } public Object getValueAt(int row, int column) { Object ret = null; ProcessThreadInfo pti = (ProcessThreadInfo)threads_clone[row]; if(column==0) { ret = pti.getId(); } else if(column==1) { ret = pti.getActivity().getBreakpointId(); } else if(column==2) { MPool pool = pti.getActivity().getPool(); if(pool!=null) ret = pool.getName(); } else if(column==3) { MLane lane = pti.getActivity().getLane(); if(lane!=null) ret = lane.getName(); } else if(column==4) { ret = pti.getException(); } else if(column==5) { ret = pti.getData(); } else if(column==6) { ret = pti.isWaiting()? "waiting": "ready"; } return ret; } } /** * List model for history. */ protected class HistoryModel extends AbstractTableModel { protected String[] colnames = new String[]{"Step", "Process-Id", "Activity", "Pool", "Lane"}; public String getColumnName(int column) { return colnames[column]; } public int getColumnCount() { return 5; } public int getRowCount() { return history_clone.length; } public Object getValueAt(int row, int column) { Object ret = null; HistoryEntry he = (HistoryEntry)history_clone[row]; if(column==0) { ret = ""+he.getStepNumber(); } else if(column==1) { ret = he.getThreadId(); } else if(column==2) { ret = he.getActivity().getBreakpointId(); } else if(column==3) { MPool pool = he.getActivity().getPool(); if(pool!=null) ret = pool.getName(); } else if(column==4) { MLane lane = he.getActivity().getLane(); if(lane!=null) ret = lane.getName(); } return ret; } } } /** * Visualization data about a process thread. */ class ProcessThreadInfo { //-------- attributes -------- /** The id. */ protected String id; /** The next activity. */ protected MActivity activity; /** The exception that has just occurred in the process (if any). */ protected Exception exception; /** Is the process in a waiting state. */ protected boolean waiting; /** The data of the process. */ protected Map data; //-------- constructors -------- /** * Create a new info. */ public ProcessThreadInfo(String id, MActivity activity, Exception exception, boolean waiting, Map data) { this.id = id; this.activity = activity; this.exception = exception; this.waiting = waiting; this.data = data; } //-------- methods -------- /** * Get the id. * @return The id. */ public String getId() { return this.id; } /** * Get the activity. * @return The activity. */ public MActivity getActivity() { return this.activity; } /** * Get the exception. * @return The exception. */ public Exception getException() { return this.exception; } /** * Get the waiting. * @return The waiting. */ public boolean isWaiting() { return this.waiting; } /** * Get the data. * @return The data. */ public Map getData() { return this.data; } /** * Get the string representation. */ public String toString() { return "ProcessThreadInfo(activity=" + this.activity + ", exception=" + this.exception + ", waiting=" + this.waiting + ")"; } }