ProcessListPane.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2014,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 
016 package org.qedeq.gui.se.pane;
017 
018 import java.awt.Color;
019 import java.awt.Dimension;
020 import java.awt.Font;
021 import java.awt.event.ComponentAdapter;
022 import java.awt.event.ComponentEvent;
023 import java.awt.event.MouseEvent;
024 import java.lang.reflect.InvocationTargetException;
025 import java.util.Iterator;
026 import java.util.Set;
027 import java.util.TreeSet;
028 
029 import javax.swing.BorderFactory;
030 import javax.swing.Icon;
031 import javax.swing.JLabel;
032 import javax.swing.JPanel;
033 import javax.swing.JScrollPane;
034 import javax.swing.JTable;
035 import javax.swing.ListSelectionModel;
036 import javax.swing.SwingUtilities;
037 import javax.swing.event.ListSelectionEvent;
038 import javax.swing.event.ListSelectionListener;
039 import javax.swing.table.AbstractTableModel;
040 import javax.swing.table.DefaultTableCellRenderer;
041 import javax.swing.table.JTableHeader;
042 import javax.swing.table.TableColumnModel;
043 import javax.swing.text.SimpleAttributeSet;
044 import javax.swing.text.StyleConstants;
045 
046 import org.qedeq.base.io.IoUtility;
047 import org.qedeq.base.trace.Trace;
048 import org.qedeq.base.utility.DateUtility;
049 import org.qedeq.base.utility.YodaUtility;
050 import org.qedeq.gui.se.util.GuiHelper;
051 import org.qedeq.kernel.bo.common.ModuleServiceCall;
052 import org.qedeq.kernel.bo.common.QedeqBo;
053 import org.qedeq.kernel.bo.common.QedeqBoSet;
054 import org.qedeq.kernel.bo.common.ServiceJob;
055 
056 import com.jgoodies.forms.builder.DefaultFormBuilder;
057 import com.jgoodies.forms.layout.CellConstraints;
058 import com.jgoodies.forms.layout.FormLayout;
059 import com.jgoodies.forms.layout.RowSpec;
060 
061 /**
062  * Shows QEDEQ module specific error pane.
063  *
064  @author  Michael Meyling
065  */
066 
067 public class ProcessListPane extends JPanel  {
068 
069     /** This class. */
070     private static final Class CLASS = ProcessListPane.class;
071 
072     /** Currently selected process. */
073     private final Set selectedProcesses;
074 
075     /** Start automatic refresh thread? */
076     private boolean automaticRefresh = true;
077 
078     /** Table model. */
079     private ProcessListModel model = new ProcessListModel();
080 
081     /** This table holds the error descriptions. */
082     private final JTable list = new JTable(model) {
083 
084         /**
085          * Just return the service process of the row.
086          *
087          @param  e   Mouse event.
088          @return Tool tip text.
089          */
090         public String getToolTipText(final MouseEvent e) {
091             String tip = null;
092             java.awt.Point p = e.getPoint();
093             final int row = rowAtPoint(p);
094             final int col = columnAtPoint(p);
095             try {
096                 final ServiceJob process = model.getServiceProcess(row);
097                 switch (col) {
098                 case 0:
099                      if (process.isBlocked()) {
100                          tip = "Process is waiting";
101                      else if (process.isRunning()) {
102                          tip = "Process is running";
103                      else if (process.wasFailure()) {
104                          tip = "Process was stopped.";
105                      else if (process.wasSuccess()) {
106                          tip = "Process has finished";
107                      }
108                      break;
109                 case 1: tip = (process.getModuleServiceCall() != null
110                      ? process.getModuleServiceCall().getService().getServiceDescription()
111                      : process.getExecutionActionDescription());
112                      break;
113                 case 2:
114                 case 3:
115                 case 4:
116                 case 5: tip = getUrlList(process.getCurrentlyProcessedModules());
117                      break;
118                 case 7: tip = GuiHelper.getToolTipText(process.getExecutionActionDescription());
119                      break;
120                 case 8: tip = GuiHelper.getToolTipText(process.getModuleServiceCall() != null
121                      ? process.getModuleServiceCall().getParametersString() "; "
122                        + process.getModuleServiceCall().getParametersString() "");
123                      break;
124                 default: tip = "";
125                 }
126             catch (RuntimeException ex) {
127                 return super.getToolTipText(e);
128             }
129             return tip;
130         }
131 
132     };
133 
134     private String getUrlList(final QedeqBo[] list) {
135         final StringBuffer result = new StringBuffer();
136         for (int i = 0; i < list.length; i++) {
137             if (i > 0) {
138                 result.append(", ");
139             }
140             result.append(list[i].getUrl());
141         }
142         return result.toString();
143     }
144 
145     /** Write with this font attributes. */
146     private final SimpleAttributeSet errorAttrs = new SimpleAttributeSet();
147 
148     /** Our scroll area. */
149     private JScrollPane scrollPane;
150 
151     /**
152      * Creates new panel.
153      */
154     public ProcessListPane() {
155         super(false);
156         selectedProcesses = new TreeSet();
157         setupView();
158     }
159 
160     /**
161      * Send selection events.
162      */
163 //    private void selectLine() {
164 //        Trace.param(CLASS, this, "selectLine", "selectedLine", selectedLine);
165 //    }
166 
167     /**
168      * Assembles the GUI components of the panel.
169      */
170     private final void setupView() {
171         list.setFont(new Font("Lucida Sans Unicode", Font.PLAIN, list.getFont().getSize()));
172         list.addComponentListener(new ComponentAdapter() {
173             public void componentResized(final ComponentEvent e) {
174                 list.scrollRectToVisible(list.getCellRect(list.getRowCount() 10true));
175             }
176         });
177 
178         final FormLayout layout = new FormLayout(
179             "min:grow",
180             "0:grow");
181         final DefaultFormBuilder builder = new DefaultFormBuilder(layout, this);
182         builder.setBorder(BorderFactory.createEmptyBorder());
183         builder.setRowGroupingEnabled(true);
184 
185         final CellConstraints cc = new CellConstraints();
186         builder.appendRow(new RowSpec("0:grow"));
187 
188         list.setDefaultRenderer(Icon.class, new IconCellRenderer());
189 
190         final ListSelectionModel rowSM = list.getSelectionModel();
191         rowSM.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
192 
193         // if selection changes remember the process number (= selected row (starting with 0))
194         rowSM.addListSelectionListener(new ListSelectionListener() {
195             public void valueChanged(final ListSelectionEvent e) {
196                 if (e.getValueIsAdjusting()) {
197                     return;
198                 }
199                 selectedProcesses.clear();
200                 if (!rowSM.isSelectionEmpty()) {
201                     int minIndex = rowSM.getMinSelectionIndex();
202                     int maxIndex = rowSM.getMaxSelectionIndex();
203                     for (int i = minIndex; i <= maxIndex; i++) {
204                         if (rowSM.isSelectedIndex(i)) {
205                             final ServiceJob process = model.getServiceProcess(i);
206                             if (process != null) {
207                                 selectedProcesses.add(process);
208                             }
209                         }
210                     }
211                 }
212             }
213         });
214 /*
215         // doing a click shall open the edit window
216         list.addMouseListener(new MouseAdapter()  {
217             public void mouseClicked(final MouseEvent e) {
218                 if (e.getClickCount() == 2) {
219                     Trace.trace(CLASS, this, "setupView$vmouseClicked", "doubleClick");
220                 }
221                 selectLine();
222             }
223         });
224 
225         // pressing the enter key shall open the edit window
226         list.getActionMap().put("selectNextRowCell", new AbstractAction() {
227             public void actionPerformed(final ActionEvent event) {
228                 Trace.param(CLASS, this, "setupView$actionPerformed", "event" , event);
229                 selectLine();
230             }
231         });
232 */
233         scrollPane = new JScrollPane(list);
234         builder.add(scrollPane,
235             cc.xywh(builder.getColumn(), builder.getRow()12"fill, fill"));
236 
237         StyleConstants.setForeground(this.errorAttrs, Color.red);
238         // because we override getScrollableTracksViewportWidth:
239         scrollPane.getViewport().setBackground(Color.white);
240         StyleConstants.setBackground(this.errorAttrs, Color.white);  // TODO mime 20080124: testing
241 
242 
243         // the default table cell renderer uses a JLabel to render the heading and we want to
244         // left align the header columns
245         // TODO mime 20080415: left align with small spaces would be better
246         final JTableHeader header = list.getTableHeader();
247         ((JLabelheader.getDefaultRenderer()).setHorizontalAlignment(JLabel.LEFT);
248         //set height of header correctly
249         header.setPreferredSize(new Dimension(list.getTableHeader().getWidth(),
250             (int) (1.1 this.getFontMetrics(getFont()).getHeight())));
251 
252         changeHeaderWidth();
253         final Thread refresh = new Thread() {
254             public void run() {
255                 while (automaticRefresh) {
256                     updateView();
257                     IoUtility.sleep(5000);  // refresh every 5 seconds
258                 }
259             }
260         };
261         refresh.setDaemon(true);
262         refresh.start();
263     }
264 
265     /**
266      * Make 2. column smaller.
267      */
268     private void changeHeaderWidth() {
269         TableColumnModel columnModel = list.getColumnModel();
270         columnModel.getColumn(0).setPreferredWidth(24);
271         columnModel.getColumn(0).setMinWidth(24);
272         columnModel.getColumn(1).setPreferredWidth(200);
273         columnModel.getColumn(1).setMinWidth(150);
274         columnModel.getColumn(2).setPreferredWidth(200);
275         columnModel.getColumn(2).setMinWidth(150);
276         columnModel.getColumn(3).setPreferredWidth(100);
277         columnModel.getColumn(3).setMinWidth(100);
278         columnModel.getColumn(4).setPreferredWidth(100);
279         columnModel.getColumn(4).setMinWidth(100);
280         columnModel.getColumn(5).setPreferredWidth(100);
281         columnModel.getColumn(5).setMinWidth(100);
282         columnModel.getColumn(6).setPreferredWidth(60);
283         columnModel.getColumn(6).setMinWidth(60);
284         columnModel.getColumn(7).setPreferredWidth(2000);
285         columnModel.getColumn(7).setMinWidth(100);
286         columnModel.getColumn(8).setPreferredWidth(200);
287         columnModel.getColumn(8).setMinWidth(100);
288     }
289 
290     /**
291      * Update from model.
292      */
293     public synchronized void updateView() {
294         final String method = "updateView";
295         Trace.begin(CLASS, this, method);
296         ((AbstractTableModellist.getModel()).fireTableDataChanged();
297         list.invalidate();
298         this.revalidate();
299 // TODO 20130228 remove me if refresh works
300 //        list.repaint();
301 //        this.repaint();
302     }
303 
304     public void refreshStates() {
305         Runnable stateChanged = new Runnable() {
306             public void run() {
307                 updateView();
308             }
309         };
310         SwingUtilities.invokeLater(stateChanged);
311     }
312 
313     /**
314      * We render our cells with this class.
315      *
316      @author  Michael Meyling
317      */
318     private static class IconCellRenderer extends DefaultTableCellRenderer {
319 
320         protected void setValue(final Object value) {
321             if (value instanceof Icon) {
322                 setIcon((Iconvalue);
323                 super.setValue(null);
324             else {
325                 setIcon(null);
326                 super.setValue(value);
327             }
328         }
329 
330     }
331 
332     public void setRunningOnly(final boolean runningOnly) {
333         model.setOnlyRunning(runningOnly);
334         selectedProcesses.clear();
335         list.getSelectionModel().clearSelection();
336         refreshStates();
337     }
338 
339     public void stopSelected() {
340         Iterator iterator = selectedProcesses.iterator();
341         while (iterator.hasNext()) {
342             final ServiceJob process = (ServiceJobiterator.next();
343             if (process != null && process.isRunning()) {
344                 process.interrupt();
345             }
346         }
347     }
348 
349     /**
350      * Print stack trace of selected service processes to new window if the
351      * method <code>Thread.getStackTrace()</code> is supported form the currently running java
352      * version.
353      */
354     public void stackTraceSelected() {
355         if (!selectedProcesses.isEmpty()) {
356             StringBuffer result = new StringBuffer();
357             Iterator iterator = selectedProcesses.iterator();
358             while (iterator.hasNext()) {
359                 final ServiceJob process = (ServiceJobiterator.next();
360                 if (process != null && process.isRunning()
361                             && YodaUtility.existsMethod(Thread.class, "getStackTrace"new Class[] {})) {
362                     StackTraceElement[] trace = new StackTraceElement[] {};
363                     result.append("id ").append(process.getId());
364                     try {
365                         trace = (StackTraceElement[]) YodaUtility.executeMethod(
366                             process.getThread()"getStackTrace"new Class[] {}new Object[] {});
367                     catch (NoSuchMethodException e) {
368                         // ignore
369                     catch (InvocationTargetException e) {
370                         // ignore
371                     }
372                     for (int i = 0; i < trace.length; i++)  {
373                         result.append(trace[i]);
374                         result.append("\n");
375                     }
376                     result.append("\n\n");
377                 }
378             }
379             (new TextPaneWindow("Stacktrace",
380                 GuiHelper.readImageIcon("tango/16x16/devices/video-display.png"),
381                 result.toString())).setVisible(true);
382         }
383     }
384 
385 
386     /**
387      * Print details of selected service processes new window.
388      */
389     public void detailsSelected() {
390         if (!selectedProcesses.isEmpty()) {
391             StringBuffer result = new StringBuffer();
392             Iterator iterator = selectedProcesses.iterator();
393             while (iterator.hasNext()) {
394                 final ServiceJob process = (ServiceJobiterator.next();
395                 if (process != null) {
396                     result.append("id ").append(process.getId());
397                     String tip = "";
398                     if (process.isBlocked()) {
399                         tip = "Process is waiting";
400                     else if (process.isRunning()) {
401                         tip = "Process is running";
402                     else if (process.wasFailure()) {
403                         tip = "Process was stopped.";
404                     else if (process.wasSuccess()) {
405                         tip = "Process has finished";
406                     }
407                     result.append("\n\tStatus:     ").append(tip);
408                     result.append("\n\tAction:     ").append(process.getActionName());
409                     result.append("\n\tModule:     ").append(process.getQedeqName());
410                     result.append("\n\tURL:        ").append(process.getQedeqUrl());
411                     result.append("\n\tStart:      ").append(DateUtility.getIsoTime(process.getStart()));
412                     result.append("\n\tStop:       ").append((process.getStop() != 0
413                         ? DateUtility.getIsoTime(process.getStop()) ""));
414                     result.append("\n\tPercentage: ").append(process.getExecutionPercentage());
415                     result.append("\n\tDescription:").append(process.getExecutionActionDescription());
416                     result.append("\n\tBlocked:   ");
417                     final QedeqBoSet blocked = process.getBlockedModules();
418                     Iterator bIterator = blocked.iterator();
419                     while (bIterator.hasNext()) {
420                         final QedeqBo qedeq = (QedeqBobIterator.next();
421                         result.append(" ").append(qedeq.getName());
422                     }
423                     result.append("\n\tCalls:   ");
424                     ModuleServiceCall parent = process.getModuleServiceCall();
425                     while (parent != null) {
426                         result.append("\n\t\t ").append(parent.getId());
427                         tip = "";
428                         if (!parent.isRunning()) {
429                             tip = "Call is finished";
430                         else if (parent.isPaused()) {
431                             tip = "Call is paused";
432                         else {
433                             tip = "Call is running";
434                         }
435                         result.append("\n\t\t Status:     ").append(tip);
436                         result.append("\n\t\t Module:     ").append(parent.getQedeq().getName());
437                         result.append("\n\t\t Plugin:     ").append(parent.getService().getServiceAction());
438                         result.append("\n\t\t Begin:      ").append(DateUtility.getIsoTime(parent.getBeginTime()));
439                         result.append("\n\t\t End:        ").append((!parent.isRunning()
440                             ? DateUtility.getIsoTime(parent.getEndTime()) ""));
441                         result.append("\n\t\t Percentage: ").append(parent.getExecutionPercentage());
442                         result.append("\n\t\t Description:").append(parent.getAction());
443                         result.append("\n\t\t Config Par.:").append(parent.getConfigParametersString());
444                         result.append("\n\t\t Parameter:  ").append(parent.getParametersString());
445 
446                         parent = parent.getParentServiceCall();
447                     }
448                     result.append("\n");
449                 }
450             }
451             (new TextPaneWindow("Service Process Details",
452                 GuiHelper.readImageIcon("tango/16x16/devices/video-display.png"),
453                 result.toString())).setVisible(true);
454         }
455     }
456 
457 
458 }