View Javadoc

1   /*
2    * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton,
3    * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
4    *
5    * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
6    * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
7    * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
8    * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
9    * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
10   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
11   * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
12   * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
13   * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
14   * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
15   * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
16   * OR MODIFICATIONS.
17   * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
18   * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
19   * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
20   * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
21   */
22  
23  package de.desy.acop.displayers;
24  
25  import java.awt.AlphaComposite;
26  import java.awt.BorderLayout;
27  import java.awt.Color;
28  import java.awt.Component;
29  import java.awt.Cursor;
30  import java.awt.Dimension;
31  import java.awt.Graphics;
32  import java.awt.Graphics2D;
33  import java.awt.GridBagConstraints;
34  import java.awt.GridBagLayout;
35  import java.awt.GridLayout;
36  import java.awt.Image;
37  import java.awt.Insets;
38  import java.awt.datatransfer.DataFlavor;
39  import java.awt.datatransfer.Transferable;
40  import java.awt.datatransfer.UnsupportedFlavorException;
41  import java.awt.event.ActionEvent;
42  import java.awt.event.ActionListener;
43  import java.awt.event.ComponentAdapter;
44  import java.awt.event.ComponentEvent;
45  import java.awt.event.HierarchyEvent;
46  import java.awt.event.HierarchyListener;
47  import java.awt.event.ItemEvent;
48  import java.awt.event.ItemListener;
49  import java.awt.event.MouseAdapter;
50  import java.awt.event.MouseEvent;
51  import java.awt.event.MouseMotionAdapter;
52  import java.awt.event.WindowAdapter;
53  import java.awt.event.WindowEvent;
54  import java.beans.Beans;
55  import java.io.IOException;
56  import java.io.OutputStream;
57  import java.io.PrintStream;
58  import java.util.ArrayList;
59  import java.util.Date;
60  import java.util.HashMap;
61  import java.util.Vector;
62  
63  import javax.swing.AbstractAction;
64  import javax.swing.Action;
65  import javax.swing.BorderFactory;
66  import javax.swing.ButtonGroup;
67  import javax.swing.DefaultListCellRenderer;
68  import javax.swing.DefaultListModel;
69  import javax.swing.Icon;
70  import javax.swing.ImageIcon;
71  import javax.swing.JButton;
72  import javax.swing.JCheckBox;
73  import javax.swing.JComponent;
74  import javax.swing.JDialog;
75  import javax.swing.JFrame;
76  import javax.swing.JLabel;
77  import javax.swing.JList;
78  import javax.swing.JMenuItem;
79  import javax.swing.JPanel;
80  import javax.swing.JPopupMenu;
81  import javax.swing.JRadioButton;
82  import javax.swing.JScrollPane;
83  import javax.swing.JTabbedPane;
84  import javax.swing.JTextArea;
85  import javax.swing.SwingConstants;
86  import javax.swing.SwingUtilities;
87  import javax.swing.Timer;
88  import javax.swing.TransferHandler;
89  import javax.swing.border.EtchedBorder;
90  import javax.swing.event.ListSelectionEvent;
91  import javax.swing.event.ListSelectionListener;
92  
93  import com.cosylab.gui.components.util.IconHelper;
94  import com.cosylab.gui.components.util.RunnerHelper;
95  import com.cosylab.gui.plugins.VitragePlugIn;
96  
97  import de.desy.acop.launcher.Launcher;
98  import de.desy.acop.launcher.Utilities;
99  import de.desy.acop.transport.ConnectionParameters;
100 import de.desy.tine.addrUtils.FECInfo;
101 import de.desy.tine.client.TLink;
102 import de.desy.tine.client.TLinkFactory;
103 import de.desy.tine.console.TConsole;
104 import de.desy.tine.queryUtils.ServerQuery;
105 import de.desy.tine.queryUtils.TQuery;
106 import de.desy.tine.server.logger.MsgLog;
107 import de.desy.tine.server.logger.MsgLog.MsgEntry;
108 
109 /**
110  * <code>AcopSpider</code> is an error tracking monitor for Acop components.
111  * The component can be installed into any container and will be presented
112  * as a small spider icon.
113  * <p>
114  * Upon constructing this component it will start monitoring all TINE connections.
115  * When there is an error in any of the connections the component will change color
116  * from green to red and will start blinking. Clicking on the icon will open
117  * a dialog with description of the error. If there is an AcopDisplayer in the 
118  * same window as the spider is and that particular error is connected with one
119  * of the displayers, the spider will change the visual appearance of the displayer
120  * to notify the user what the error refers to.
121  * </p>
122  * AcopSpider can also be used for logging the exceptions that occurs during the
123  * operation (see {@link #addError(Throwable)}).
124  * </p>
125  * 
126  * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
127  * @version $Id: Templates.xml,v 1.10 2004/01/13 16:17:13 jbobnar Exp $
128  *
129  */
130 public class AcopSpider extends JLabel {
131 	
132 	/**
133 	 * <code>AcopDebugPanel</code> shows debug data for certain connection point. 
134 	 * This panel can be used in combination with <code>AcopInfoDialog</code>.
135 	 * 
136 	 * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
137 	 * @version $Id: Templates.xml,v 1.10 2004/01/13 16:17:13 jbobnar Exp $
138 	 * @see AcopInfoDialog
139 	 */
140 	protected class SpiderDebugPanel extends JPanel {
141 			
142 		private static final long serialVersionUID = -1597262900075362868L;
143 		private JTextArea textArea;
144 		private ConnectionParameters connectionParameters;
145 		private JCheckBox debugCheckBox;
146 		private boolean showDebug = true;
147 		
148 		private HashMap<String, ServerQuery[]> serverQueryCache = new HashMap<String,ServerQuery[]>();
149 		
150 		/**
151 		 * Constructs new AcopDebugPanel.
152 		 *
153 		 */
154 		public SpiderDebugPanel() {
155 			super();
156 			initialize();
157 		}
158 		
159 		private void initialize() {
160 			this.setLayout(new BorderLayout());
161 			textArea = new JTextArea();
162 			textArea.setBorder(BorderFactory.createTitledBorder("Fec Information"));
163 			this.add(textArea, BorderLayout.CENTER);
164 			JPanel southPanel = new JPanel(new GridBagLayout());
165 			debugCheckBox = new JCheckBox("Debug");
166 			debugCheckBox.addActionListener(new ActionListener() {
167 				public void actionPerformed(ActionEvent e) {
168 					TLinkFactory.getInstance().setDebugLevel(debugCheckBox.isSelected() ? 2 : 0);					
169 				}
170 			});
171 			southPanel.add(debugCheckBox, new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,0,0,0),0,0));
172 			this.add(southPanel, BorderLayout.NORTH);			
173 		}
174 		
175 		/**
176 		 * Sets the ConnectionParameters, which specify the connection point that
177 		 * debug data is shown for.
178 		 *  
179 		 * @param param the remote connection point
180 		 */
181 		public void setConnectionParameters(ConnectionParameters param) {
182 			if (this.connectionParameters != null && this.connectionParameters.equals(param)) return;
183 			this.connectionParameters = param;
184 			if (connectionParameters != null){
185 				textArea.setText("Updating ... Please wait.");
186 				new Thread(new Runnable() {
187 					public void run() {
188 						String srv = connectionParameters.getDeviceGroup();
189 				        String con = connectionParameters.getDeviceContext();
190 				        StringBuffer s = new StringBuffer();
191 				        s.append("Device Server(s): ");  
192 				        
193 				        String[] deviceServers = new String[0];
194 				        ServerQuery[] query = serverQueryCache.get(con);
195 				        if (query == null) {
196 				        	query = TQuery.getDeviceServersEx(con,"ALL", "ALL");
197 				        	serverQueryCache.put(con, query);
198 				        }
199 				        if (query != null) {
200 				        	ServerQuery fecQuery = null;
201     				        for (int i = 0; i < query.length; i++) {
202     				        	if (query[i].getName().equalsIgnoreCase(srv)) {
203     				        		fecQuery = query[i];
204     				        		break;
205     				        	}
206     				        }
207     				        if (fecQuery != null) {
208         				        deviceServers = TQuery.getDeviceServers(con, fecQuery.getXref());
209         				        for (int i = 0; i < deviceServers.length -1; i++) {
210         				        	s.append(deviceServers[i]);
211         				        	s.append(", ");
212         				        }
213         				        if (deviceServers.length > 0) {
214         				        	s.append(deviceServers[deviceServers.length - 1]);
215         				        }
216         				        FECInfo fecInfo = TQuery.getServerInformation(fecQuery.getXref());
217         				        if (fecInfo != null) {
218         				        	s.append("\nResponsible: ");
219         				        	s.append(fecInfo.getResp());
220         				        	s.append("\nLocation: ");
221         				        	s.append(fecInfo.getLoc());
222         				        	s.append("\nOS: ");
223         				        	s.append(fecInfo.getOs());
224         				        	s.append("\nDescription: ");
225         				        	s.append(fecInfo.getDescription());
226         				        	s.append("\n");
227         				        } else {
228         				        	s.append("\nNo FEC info.\n");
229         				        }
230     				        } else {
231     				        	s.append(srv);
232     				        	s.append("\nNo FEC info.\n");
233     				        }
234 				        }
235 				        s.append(TQuery.getModuleAddressInfo(srv, con));
236 				        s.append("\n");
237 				        s.append(TQuery.getTineVersion(con, srv));
238 				        s.append("\n");
239 				        s.append(TQuery.getAppVersion(con, srv));
240 				        s.append("\n");
241 				        s.append(TQuery.getAppDate(con, srv));
242 				        s.append("\n");
243 				        textArea.setText(s.substring(0));
244 										
245 					}
246 				}).start(); 
247 			} else {
248 				textArea.setText("");
249 			}
250 		}
251 		
252 		/**
253 		 * Returns the ConnectionParameters that specify the remote connection point, which
254 		 * is being debugged.
255 		 * 
256 		 * @return
257 		 */
258 		public ConnectionParameters getConnectionParameters() {
259 			return connectionParameters;
260 		}
261 		
262 		/**
263 		 * Sets the debug level for TINE TLinkFactory. It always uses level 2.
264 		 * 
265 		 * @param debug new debug level (0 debug off)
266 		 * @see TLinkFactory#setDebugLevel(int)
267 		 */
268 		public void setDebug(int debug) {
269 			if (debug > 0) {
270 				debugCheckBox.setSelected(true);
271 			} else {
272 				debugCheckBox.setSelected(false);
273 			}
274 		}
275 		
276 		/**
277 		 * Returns the debug level (0 - debug is off, 2 - debug is on).
278 		 * 
279 		 * @return the debug level
280 		 */
281 		public int isDebug() {
282 			return debugCheckBox.isSelected() ? 2 : 0;
283 		}
284 		
285 		/**
286 		 * Sets the visibility of the debug checkbox.
287 		 * 
288 		 * @param showDebug true if the checkbox should be visible, false if not
289 		 */
290 		public void setShowDebug(boolean showDebug) {
291 			if (this.showDebug == showDebug) return;
292 			this.showDebug = showDebug;
293 			debugCheckBox.setVisible(showDebug);
294 			firePropertyChange("showDebug", !showDebug, showDebug);
295 		}
296 		
297 		/**
298 		 * Returns true if the debug check box is visible.
299 		 * @return the debug check box visibile
300 		 */
301 		public boolean isShowDebug() {
302 			return showDebug;
303 		}
304 	}
305 
306 	
307 	/**
308 	 * 
309 	 * <code>SpiderDialog</code> is a dialog popup which appears when user clicks
310 	 * on the spider icon. The spider dialog lists all active links in the system
311 	 * and presents the exception panel if the spider is used as an exception reporter.
312 	 *
313 	 * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
314 	 *
315 	 */
316 	protected class SpiderDialog extends JDialog {
317 		
318 		class ListRenderer extends DefaultListCellRenderer {
319 			
320 			private static final long serialVersionUID = -6412407684875722175L;
321 
322 			@Override
323 			public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
324 				
325 				String name = "";
326 				
327 				if (value instanceof TLink) {
328 					try {
329 						TLink link = (TLink) value;
330 						Date date = new Date(link.getLastTimeStamp());
331 						String device = link.getFullDeviceName()+"/"+link.getProperty();
332 						String error = link.getLastError();
333 						name = date + "       " + error + "       " + device;
334 					} catch (Exception e) {
335 						addError(e);
336 					}
337 					
338 				}
339 				
340 				return super.getListCellRendererComponent(list, name, index, isSelected,
341 						cellHasFocus);				
342 			}
343 			
344 		}
345 		
346 
347 		private static final long serialVersionUID = 2063792587625913979L;
348 		private JList linkList;
349 		private DefaultListModel listModel;
350 		private JTabbedPane tabPane; 
351 		private JPanel contentPanel;
352 		private JPanel buttonPanel;
353 		private JPanel linkPanel;
354 		private JPanel messagePanel;
355 		private JPanel exceptionModePanel;
356 		private JTextArea exceptionArea;
357 		private JList messageList;
358 		private JTextArea messageStackArea;
359 		private DefaultListModel messageModel;
360 		
361 		private JButton customButton;
362 		private JButton closeButton;
363 		private JButton clearButton;
364 		private JButton refreshButton;
365 		private JCheckBox debugCheck;
366 		private JLabel debugLevelLabel;
367 		private JRadioButton debugLevel1;
368 		private JRadioButton debugLevel2;
369 		private ArrayList<Throwable> errors = new ArrayList<Throwable>();
370 		private InfoDialog infoDialog;
371 		private JPopupMenu popup;
372 		private JMenuItem infoDialogMI;
373 		
374 		private long lastMessageTime = 0;
375 		
376 		private PrintStream throwableStream = new PrintStream(new OutputStream(){
377 			public void write(int b) throws IOException {
378 				getStackArea().append(new String(new int[]{b},0,1));
379 			}
380 		});
381 		
382 		/**
383 		 * Sets this dialog visible.
384 		 */
385 		public void setVisible(boolean b) {
386 			if (b) {
387 				update();
388 			}
389 			super.setVisible(b);
390 			if (Beans.isDesignTime()) {
391 				getTabPane().setSelectedIndex(1);
392 			}
393 		}
394 
395 		/**
396 		 * Constructs a new SpiderDialog.
397 		 *
398 		 */
399 		public SpiderDialog() {
400 			super();
401 			initialize();
402 			
403 		}
404 		
405 		private void initialize() {
406 			setSize(600,500);
407 			setContentPane(getContentPanel());
408 			setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
409 			setTitle("Tine Status Viewer");
410 		}
411 		
412 		private JPanel getContentPanel() {
413 			if (contentPanel == null) {
414 				contentPanel = new JPanel(new BorderLayout());
415 				contentPanel.add(getTabPane(), BorderLayout.CENTER);
416 				contentPanel.add(getButtonPanel(), BorderLayout.SOUTH);
417 			}
418 
419 			return contentPanel;
420 		}
421 		
422 		private JTabbedPane getTabPane() {
423 			if (tabPane == null) {
424 				tabPane = new JTabbedPane();
425 				tabPane.add("Links", getLinkPanel());
426 				tabPane.add("Messages", getMessagePanel());
427 				tabPane.add("Exceptions", getExceptionModePanel());
428 				tabPane.setEnabledAt(2, false);
429 			}
430 			return tabPane;
431 		}
432 		
433 		private JTextArea getExceptionArea() {
434 			if (exceptionArea == null) {
435 				exceptionArea = new JTextArea();
436 				exceptionArea.setWrapStyleWord(true);
437 				exceptionArea.setEditable(false);
438 			}
439 			return exceptionArea;
440 		}
441 		
442 		private JList getMessagesArea() {
443 			if (messageList == null) {
444 				messageList = new JList();
445 				messageModel = new DefaultListModel();
446 				messageList.setModel(messageModel);
447 				messageList.setCellRenderer(new DefaultListCellRenderer(){
448 					private static final long serialVersionUID = 1L;
449 					@Override
450 					public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
451 							boolean cellHasFocus) {
452 						JLabel label = (JLabel)super.getListCellRendererComponent(list,value,index,isSelected,cellHasFocus);
453 						if (value != null && value instanceof MsgEntry) {
454 							MsgEntry entry = (MsgEntry)value;
455 							StringBuilder sb = new StringBuilder();
456 							sb.append(entry.getTime());
457 							sb.append(' ');
458 							sb.append(entry.getCode());
459 							sb.append(' ');
460 							sb.append(entry.getMsg());
461 							String text = sb.toString();
462 							label.setText(text);
463 							label.setToolTipText(text);
464 							label.setHorizontalTextPosition(SwingConstants.LEFT);
465 							if (entry.getExecption() != null) {
466 								label.setIcon(IconHelper.createIcon("icons/general/bug.png"));
467 							} else {
468 								label.setIcon(null);
469 							}
470 						}
471 						return label;
472 					}
473 				});
474 				messageList.addListSelectionListener(new ListSelectionListener(){
475 					public void valueChanged(ListSelectionEvent e) {
476 						MsgEntry msg = (MsgEntry)messageList.getSelectedValue();
477 						if (msg == null) {
478 							getStackArea().setText("");
479 						} else {
480 							Throwable t = msg.getExecption();
481 							if (t != null) {
482 								t.printStackTrace(throwableStream);
483 							} else {
484 								getStackArea().setText("");
485 							}
486 						}
487 					}
488 				});
489 				
490 			}
491 			return messageList;
492 		}
493 		
494 		private JTextArea getStackArea() {
495 			if (messageStackArea == null) {
496 				messageStackArea = new JTextArea();
497 				messageStackArea.setWrapStyleWord(true);
498 				messageStackArea.setLineWrap(false);
499 				messageStackArea.setEditable(false);
500 			}
501 			return messageStackArea;
502 		}
503 		
504 		private JPanel getExceptionModePanel() {
505 			if (exceptionModePanel == null) {
506 				exceptionModePanel = new JPanel(new BorderLayout());
507 				JScrollPane areaScroll = new JScrollPane(getExceptionArea(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
508 				exceptionModePanel.add(areaScroll, BorderLayout.CENTER);
509 			}
510 			return exceptionModePanel;
511 		}
512 		
513 		private JList getLinkList() {
514 			if (linkList == null) {
515 				linkList = new JList();
516 				listModel = new DefaultListModel();
517 				linkList.setModel(listModel);
518 				linkList.setCellRenderer(new ListRenderer());
519 				
520 				linkList.addMouseMotionListener(new MouseMotionAdapter() {
521 					@Override
522 					public void mouseDragged(MouseEvent e) {
523 						linkList.getTransferHandler().exportAsDrag(linkList,e,TransferHandler.COPY);
524 					}
525 					
526 				});
527 				
528 				linkList.addMouseListener(new MouseAdapter(){
529 					@Override
530 					public void mouseReleased(MouseEvent e) {
531 						if (SwingUtilities.isRightMouseButton(e) || e.isMetaDown()) {
532 							getPopup().show(linkList, e.getX(), e.getY());
533 						}
534 					}
535 				});
536 						
537 				linkList.setTransferHandler(new TransferHandler() {
538 					private static final long serialVersionUID = 1658117801619157639L;
539 					@Override
540 					public boolean importData(JComponent comp, Transferable t) {
541 						return false;
542 					}
543 					@Override
544 					protected Transferable createTransferable(JComponent c) {
545 						return new Transferable() {
546 							public Object getTransferData(DataFlavor flavor)
547 									throws UnsupportedFlavorException, IOException {
548 								if (flavor.isFlavorTextType()) {
549 									Object objects = getLinkList().getSelectedValue();
550 									return toConnectionParameters((TLink)objects).getRemoteName();
551 								}
552 								throw new UnsupportedFlavorException(flavor);
553 							}				
554 							public boolean isDataFlavorSupported(DataFlavor flavor) {
555 								return flavor.isFlavorTextType();
556 							}
557 							public DataFlavor[] getTransferDataFlavors() {
558 								return new DataFlavor[]{DataFlavor.stringFlavor};
559 							}
560 						};
561 					}
562 					@Override
563 					public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
564 						return false;
565 					}
566 					@Override
567 					public int getSourceActions(JComponent c) {
568 						return TransferHandler.COPY;
569 					}
570 				});
571 			}
572 			return linkList;
573 		}
574 		
575 		
576 		private JPanel getLinkPanel() {
577 			if (linkPanel == null) {
578 				linkPanel = new JPanel(new BorderLayout());
579 				JScrollPane listScroll = new JScrollPane(getLinkList());
580 				linkPanel.add(listScroll, BorderLayout.CENTER);
581 			}
582 
583 			return linkPanel;
584 
585 		}
586 		
587 		private JPanel getMessagePanel() {
588 			if (messagePanel == null) {
589 				messagePanel = new JPanel(new GridLayout(2,1,0,2));
590 				JScrollPane areaScroll = new JScrollPane(getMessagesArea());
591 				messagePanel.add(areaScroll);
592 				JScrollPane stackScroll = new JScrollPane(getStackArea());
593 				messagePanel.add(stackScroll);
594 			}
595 			return messagePanel;
596 		}
597 		
598 		private JPanel getButtonPanel() {
599 			if (buttonPanel == null) {
600 				buttonPanel = new JPanel(new GridBagLayout());
601 				
602 				closeButton = new JButton("Close");
603 				closeButton.addActionListener(new ActionListener() {
604 					public void actionPerformed(ActionEvent e) {
605 						dispose();						
606 					}
607 				});
608 				buttonPanel.add(closeButton, new GridBagConstraints(0,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
609 				clearButton = new JButton("Clear");
610 				clearButton.addActionListener(new ActionListener() {
611 					public void actionPerformed(ActionEvent e) {
612 						listModel.clear();
613 						listedLinks.clear();
614 						messageModel.clear();
615 						lastMessageTime = 0;
616 						clearErrors();
617 						getExceptionArea().setText("");
618 						setError(false);
619 					}
620 				});
621 				buttonPanel.add(clearButton, new GridBagConstraints(1,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
622 				refreshButton = new JButton("Refresh");
623 				refreshButton.addActionListener(new ActionListener() {
624 					public void actionPerformed(ActionEvent e) {
625 						update();		
626 						updateMessages();
627 					}
628 				});
629 				buttonPanel.add(refreshButton, new GridBagConstraints(2,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
630 				
631 				debugCheck = new JCheckBox("Debug");
632 				debugCheck.setSelected(false);
633 				debugCheck.addActionListener(new ActionListener() {
634 					public void actionPerformed(ActionEvent e) {
635 						if (debugCheck.isSelected())
636 							TLinkFactory.setOutputDebugLevel(getDebugLevel());
637 						else 
638 							TLinkFactory.setOutputDebugLevel(0);
639 					}
640 				});
641 				TConsole.getInstance().addWindowListener(new WindowAdapter(){
642 					public void windowClosing(WindowEvent e) {
643 						debugCheck.setSelected(false);
644 					}					
645 				});
646 				buttonPanel.add(debugCheck, new GridBagConstraints(3,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
647 				
648 				debugLevelLabel = new JLabel("Debug level:");
649 				buttonPanel.add(debugLevelLabel, new GridBagConstraints(4,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
650 				
651 				ButtonGroup debugGroup = new ButtonGroup();
652 				debugLevel1 = new JRadioButton("1");
653 				debugLevel1.addItemListener(new ItemListener(){
654 					public void itemStateChanged(ItemEvent e) {
655 						if (debugLevel1.isSelected() && debugCheck.isSelected()) {
656 							TLinkFactory.setOutputDebugLevel(getDebugLevel());
657 						}
658 					}
659 				});
660 				debugGroup.add(debugLevel1);
661 				debugLevel2 = new JRadioButton("2");
662 				debugLevel2.addItemListener(new ItemListener(){
663 					public void itemStateChanged(ItemEvent e) {
664 						if (debugLevel2.isSelected() && debugCheck.isSelected()) {
665 							TLinkFactory.setOutputDebugLevel(getDebugLevel());
666 						}
667 					}
668 				});
669 				debugGroup.add(debugLevel2);
670 				debugLevel1.setSelected(true);
671 				buttonPanel.add(debugLevel1, new GridBagConstraints(5,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
672 				buttonPanel.add(debugLevel2, new GridBagConstraints(6,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
673 				
674 				customButton = new JButton(customAction);
675 				buttonPanel.add(customButton, new GridBagConstraints(7,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(2,2,2,2),0,0));
676 				customButton.setVisible(customAction != null);
677 			}
678 
679 			return buttonPanel;
680 		}
681 		
682 		private ConnectionParameters toConnectionParameters(TLink link) {
683 			String remoteName = link.getFullDeviceName()+"/"+link.getProperty();
684 			if (remoteName.startsWith("/")) {
685 				remoteName = "TINE" + remoteName;
686 			} else if (!remoteName.startsWith("TINE")) {
687 				remoteName = "TINE/" + remoteName;
688 			}
689 			return new ConnectionParameters(remoteName);
690 		}
691 		
692 		private JPopupMenu getPopup() {
693 			if (popup == null) {
694 				popup = new JPopupMenu();
695 				infoDialogMI = new JMenuItem("Info...");
696 				infoDialogMI.addActionListener(new ActionListener(){
697 					public void actionPerformed(ActionEvent e) {
698 						TLink link = (TLink)getLinkList().getSelectedValue();
699 						if (link == null) return;
700 						getInfoDialog().show(toConnectionParameters(link));
701 //						getInfoDialog().show(new ConnectionParameters("TINE/test/test/test/test", AccessMode.POLL, 1000));
702 					}
703 				});
704 				popup.add(infoDialogMI);
705 			}
706 			return popup;
707 		}
708 		
709 		private int getDebugLevel() {
710 			if (debugLevel1.isSelected()) return 1;
711 			else if (debugLevel2.isSelected()) return 2;
712 			else return 0;
713 		}
714 				
715 		/**
716 		 * Updates the spider dialog.
717 		 *
718 		 */
719 		public void update() {
720 			SwingUtilities.invokeLater(new Runnable(){
721 				public void run() {
722 					listModel.clear();
723 					listedLinks.clear();
724 					TLink[] links = TLinkFactory.getInstance().getActiveLinks();
725 					boolean errorExists = false;
726 					if (links != null) {
727 						for (TLink l : links) {
728 							if (l != null) {
729 								listModel.addElement(l);
730 								listedLinks.add(l);
731 								if (l.getLinkStatus() != 0) {
732 									errorExists = true;
733 								}
734 							}
735 						}
736 					}
737 					
738 					synchronized (errors) {
739 						getExceptionArea().setText("");
740 						StringBuilder sb;
741 						for (Throwable e : errors) {
742 							sb = new StringBuilder("Error: \n");
743 							sb.append(e.toString() + "\n");
744 							for (StackTraceElement st : e.getStackTrace()) {
745 								sb.append(st.toString() + "\n");
746 							}
747 							getExceptionArea().append(sb.toString() + " \n \n");
748 						}
749 						errorExists |= errors.size() > 0;
750 						setError(errorExists);
751 					}
752 				}
753 			});		
754 		}
755 		
756 		public void updateMessages() {
757 			SwingUtilities.invokeLater(new Runnable(){
758 				@Override
759 				public void run() {
760 					MsgEntry[] entries;
761 					if (lastMessageTime == 0) {
762 						entries = MsgLog.getMessages(512);
763 					} else {
764 						entries = MsgLog.getMessages(lastMessageTime);
765 					}
766 					lastMessageTime = System.currentTimeMillis();
767 					
768 					if (entries != null) {
769 						for (int i = 0; i < entries.length; i++) {
770 							messageModel.add(0,entries[i]);
771 						}
772 					}
773 					
774 				}
775 			});
776 		}
777 		
778 		/**
779 		 * The spider can be used as an exception reporter. Use this method 
780 		 * to report an exception.
781 		 * 
782 		 * @param e
783 		 */
784 		public void addError(Throwable e) {
785 			synchronized(errors) {
786 				errors.add(e);
787 				getTabPane().setEnabledAt(2, true);
788 			}
789 		}
790 		
791 		/**
792 		 * Clear all exceptions in the exception panel.
793 		 *
794 		 */
795 		public void clearErrors() {
796 			synchronized(errors) {
797 				errors.clear();
798 				getTabPane().setEnabledAt(2, false);
799 			}
800 		}
801 		
802 
803 		/**
804 		 * Returns the InfoDialog which shows the fec information about the selected
805 		 * link.
806 		 * 
807 		 * @return the info dialog
808 		 */
809 		protected InfoDialog getInfoDialog() {
810 			if (infoDialog == null) {
811 				infoDialog = new InfoDialog();
812 			}
813 			return infoDialog;
814 		}
815 	}
816 	
817 	/**
818 	 * 
819 	 * <code>InfoDialog</code> presents the fec info
820 	 *
821 	 * @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
822 	 *
823 	 */
824 	protected class InfoDialog extends JDialog {
825 		
826 		private static final long serialVersionUID = 1L;
827 		private SpiderDebugPanel debugPanel;
828 		private JButton closeButton;
829 		private JPanel contentPanel;
830 		
831 		public InfoDialog() {
832 			super();
833 			initialize();
834 		}
835 		
836 		private void initialize() {
837 			setSize(400,400);
838 			setTitle("Info");
839 			setLocationRelativeTo(AcopSpider.this);
840 			setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
841 			setContentPane(getContentPanel());
842 		}
843 		
844 		private JPanel getContentPanel() {
845 			if (contentPanel == null) {
846 				contentPanel = new JPanel(new GridBagLayout());
847 				contentPanel.add(getDebugPanel(), new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.CENTER,GridBagConstraints.BOTH,new Insets(5,5,5,5),0,0));
848 				contentPanel.add(getCloseButton(), new GridBagConstraints(0,1,1,1,0,0,GridBagConstraints.EAST,GridBagConstraints.NONE,new Insets(0,5,5,5),0,0));
849 			}
850 			return contentPanel;
851 		}
852 		
853 		private JButton getCloseButton() {
854 			if (closeButton == null) {
855 				closeButton = new JButton("Close");
856 				closeButton.addActionListener(new ActionListener(){
857 					public void actionPerformed(ActionEvent e) {
858 						InfoDialog.this.dispose();
859 					}
860 				});
861 			}
862 			return closeButton;
863 			
864 		}
865 		
866 		private SpiderDebugPanel getDebugPanel() {
867 			if (debugPanel == null) {
868 				debugPanel = new SpiderDebugPanel();
869 				debugPanel.setShowDebug(false);
870 			}
871 			return debugPanel;
872 		}
873 		
874 		public void show(ConnectionParameters parameters) {
875 			getDebugPanel().setConnectionParameters(parameters);
876 			setVisible(true);
877 			toFront();
878 		}		
879 	}
880 	
881 	private static final long serialVersionUID = -3394462753800819240L;
882 	
883 	private static final Icon SPIDER_ICON = IconHelper.createIcon("acop/spider.gif");
884 	private SpiderDialog spiderDialog;
885 	private boolean isError = false;
886 	private boolean autoResize = true;
887 	
888 	private VitragePlugIn vitrage;
889 	private boolean paintOverlayDecorations = true;
890 	
891 	private Timer timer;
892 	private boolean paintColor = true;
893 	private Thread linkListenerThread;
894 	private Action customAction;
895 	private Vector<TLink> listedLinks = new Vector<TLink>();
896 		
897 	/**
898 	 * Constructs a new AcopSpider.
899 	 *
900 	 */
901 	public AcopSpider() {
902 		super();
903 		initialize();
904 	}
905 	
906 	private void initialize() {
907 		setOpaque(true);
908 		setHorizontalAlignment(SwingConstants.CENTER);
909 		setVerticalAlignment(SwingConstants.CENTER);
910 		setIgnoreRepaint(true);
911 		setBackground(Color.WHITE);
912 		setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
913 		setIcon(SPIDER_ICON);
914 		addComponentListener(new ComponentAdapter() {
915 			@Override
916 			public void componentResized(ComponentEvent e) {
917 				if (isAutoResize())
918 					rescaleIconToFit();
919 			}
920 		});
921 		addMouseListener(new MouseAdapter(){
922 			@Override
923 			public void mouseClicked(MouseEvent e) {
924 				if (getRootPane() != null) {
925 					getSpiderDialog().setLocationRelativeTo(getRootPane());
926 				}
927 				
928 				getSpiderDialog().setVisible(true);
929 			}
930 		});
931 		
932 		addHierarchyListener(new HierarchyListener() {
933 			public void hierarchyChanged(HierarchyEvent e) {
934 				if (vitrage == null && !Beans.isDesignTime()) {
935 					if (getRootPane() != null) {
936 						installVitragePlugin(getRootPane());
937 					} 
938 				}
939 				if (linkListenerThread == null) {
940 					//it is much more performance efficient to use a stand alone 
941 					//thread and pause it with sleep than using a regular timer.
942 					linkListenerThread = new Thread(new Runnable(){
943 						public void run() {
944 							TLink[] links;
945 							boolean errorExists = false;
946 							while (true) {
947 								links = TLinkFactory.getInstance().getActiveLinks();
948 								if (!isError() && links != null) {
949 									int linkCount = 0;
950 									for (TLink l : links) {
951 										if (l != null){
952 											if (l.getLinkStatus() != 0) {
953 												getSpiderDialog().update();
954 												linkCount = -1;
955 												break;
956 											}
957 											linkCount++;
958 										}
959 									}
960 									if (linkCount != -1 && spiderDialog != null && spiderDialog.listModel != null && linkCount != spiderDialog.listModel.getSize()) {
961 										getSpiderDialog().update();
962 									}
963 								} else if (isError()) {
964 									errorExists = false;
965 									if (links != null) {
966 										for (TLink l : links) {
967 											if (l != null && l.getLinkStatus() != 0) {
968 												errorExists = true;
969 												break;
970 											}
971 										}
972 									}
973 									if (!errorExists) {
974 										getSpiderDialog().update();
975 									}
976 								}								
977 								try {
978 									Thread.sleep(1000);
979 								} catch (InterruptedException e) {
980 									e.printStackTrace();
981 								}
982 							} 
983 						}
984 					},"Acop Spider");
985 					linkListenerThread.start();
986 				}
987 			}
988 		});
989 		
990 		setMaximumSize(new Dimension(80,80));
991 		setMinimumSize(new Dimension(16,16));
992 		setPreferredSize(new Dimension(30,30));
993 		initializeDefaultCustomAction();
994 	}
995 	
996 	/**
997 	 * Initializes the default custom action. This action will start a webstart application
998 	 * defined in the acop/acop.properties file. The application is provided all active links
999 	 * as JVM parameters identified by -C flag.  
1000 	 *
1001 	 */
1002 	protected void initializeDefaultCustomAction() {
1003 		setCustomAction(new AbstractAction("History"){
1004 			private static final long serialVersionUID = 1L;
1005 
1006 			public void actionPerformed(ActionEvent e) {
1007 				final Cursor prevCursor = getSpiderDialog().getCursor();
1008 				getSpiderDialog().setCursor(new Cursor(Cursor.WAIT_CURSOR));
1009 				final TLink[] links = listedLinks.toArray(new TLink[listedLinks.size()]);
1010 				new Thread(new Runnable(){
1011 					public void run() {
1012 						try {
1013 							final String[] args = new String[links.length];
1014 							for (int i = 0; i < args.length; i++) {
1015 								args[i] = "-C\""+links[i].getFullDeviceName()+"/"+links[i].getProperty() +"\"";
1016 							}
1017 							
1018 							String customAction = Utilities.getAcopProperty(Utilities.SPIDER_CUSTOMACTION);
1019 							Launcher.runWebstartApplication(customAction, args, false);
1020 						} catch (Exception e) {
1021 							addError(e);
1022 						} finally {
1023 							getSpiderDialog().setCursor(prevCursor);
1024 						}
1025 					}
1026 				}).start();
1027 			}
1028 		});
1029 	}
1030 		
1031 	/*
1032 	 * (non-Javadoc)
1033 	 * @see javax.swing.JLabel#setIcon(javax.swing.Icon)
1034 	 */
1035 	@Override
1036 	public void setIcon(Icon icon) {
1037 		int height = getHeight() > 0 ? (int)(getHeight()*0.7) : 16;
1038 		int width = getWidth() > 0 ? (int)(getWidth()*0.7) : 16;
1039 		if (icon instanceof ImageIcon) {
1040 			if (width <= 0) width = 1;
1041 			icon = new ImageIcon(((ImageIcon)icon).getImage().getScaledInstance(
1042 					width, -1, Image.SCALE_FAST));
1043 			if (icon.getIconHeight() > height) {
1044 				if (height <= 0) height = 1;
1045 				icon = new ImageIcon(((ImageIcon)icon).getImage().getScaledInstance(
1046 						-1, height, Image.SCALE_FAST));
1047 			}
1048 		} 
1049 		super.setIcon(icon);
1050 	}
1051 	
1052 	/*
1053 	 * (non-Javadoc)
1054 	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
1055 	 */
1056 	@Override
1057 	protected void paintComponent(Graphics g) {
1058 		super.paintComponent(g);
1059 		((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,0.5f));
1060 		if (isError)
1061 			if (paintColor) {
1062 				g.setColor(Color.RED.brighter());
1063 			} else {
1064 				g.setColor(Color.GRAY.brighter());
1065 			}
1066 		else 
1067 			g.setColor(Color.GREEN.darker());
1068 		g.fillRect(0,0, getWidth(), getHeight());
1069 
1070 	}
1071 	
1072 	/**
1073 	 * Puts the spider into the error mode. When in the error mode the spider will
1074 	 * be red and will blink. If not in the error mode the spider will be green.
1075 	 * In general this method should not be called from the outside except from the
1076 	 * spider hierarchy classes if spider handles other types of processes beside
1077 	 * monitoring the active links.
1078 	 * 
1079 	 * @param isError a flag indicating if spider should be in error mode
1080 	 */
1081 	protected void setError(boolean isError) {
1082 		if (this.isError == isError) return;
1083 		this.isError = isError;
1084 		if (isError) {
1085 			getTimer().start();
1086 		} else {
1087 			getTimer().stop();
1088 			repaint();
1089 		}
1090 	}
1091 	
1092 	/**
1093 	 * Returns true if AcopSpider is in error mode. The component is in error
1094 	 * mode when there is a problem with one of the TINE connections or there
1095 	 * has been an exception reported. When in error mode spider will blink
1096 	 * in red color. If not in error mode, the component will be green.
1097 	 * 
1098 	 * @return true if spider is error mode
1099 	 * @see #addError(Throwable)
1100 	 */
1101 	public boolean isError() {
1102 		return this.isError;
1103 	}
1104 	
1105 	/**
1106 	 * Returns the spider dialog which enables inspection of the active links in the 
1107 	 * tine link factory.
1108 	 * 
1109 	 * @return the spider dialog
1110 	 */
1111 	protected SpiderDialog getSpiderDialog() {
1112 		if (spiderDialog == null) {
1113 			spiderDialog = new SpiderDialog();
1114 		}
1115 		return spiderDialog;
1116 	}
1117 
1118 	private void installVitragePlugin(JComponent c) {
1119 		if (vitrage != null) return;
1120 		vitrage = new VitragePlugIn(c);
1121 		vitrage.setVitrageEnabled(isPaintOverlayDecorations());
1122 	}
1123 	
1124 	/**
1125 	 * Returns true if the spider will paint the overlay decorations across the
1126 	 * displayers which are connected with a particular error.
1127 	 * 
1128 	 * @return true if decorations are painted on the displayers
1129 	 */
1130 	public boolean isPaintOverlayDecorations() {
1131 		return paintOverlayDecorations;
1132 	}
1133 
1134 	/**
1135 	 * Enables/disables painting of overlay decorations on the displayers.
1136 	 * 
1137 	 * @param useVitrage
1138 	 * @see #isPaintOverlayDecorations()
1139 	 */
1140 	public void setPaintOverlayDecorations(boolean useVitrage) {
1141 		boolean old = isPaintOverlayDecorations();
1142 		if (old == useVitrage) return;
1143 		if (vitrage != null)
1144 			vitrage.setVitrageEnabled(useVitrage);
1145 		firePropertyChange("paintOverlayDecorations", old, useVitrage);
1146 	}
1147 
1148 	/**
1149 	 * Returns true if icon resizes automatically when the component is resized.
1150 	 * 
1151 	 * @return true if automatical resizing is on
1152 	 */
1153 	public boolean isAutoResize() {
1154 		return autoResize;
1155 	}
1156 
1157 	/**
1158 	 * Sets whether icon should be resized automatically when component is resized.
1159 	 * @param autoResize 
1160 	 */
1161 	public void setAutoResize(boolean autoResize) {
1162 		this.autoResize = autoResize;
1163 	}
1164 	
1165 	/**
1166 	 * Rescales the image icon to fit the current size of the displayer.
1167 	 *
1168 	 */
1169 	public void rescaleIconToFit() {
1170 		setIcon(SPIDER_ICON);
1171 	}
1172 	
1173 	/**
1174 	 * Timer which handles the blinking of the spider.
1175 	 * 
1176 	 * @return
1177 	 */
1178 	private Timer getTimer() {
1179 		if (timer == null) {
1180 			timer = new Timer(700, new ActionListener() {
1181 				public void actionPerformed(ActionEvent e) {
1182 					paintColor = !paintColor;
1183 					AcopSpider.this.repaint();
1184 				}
1185 				
1186 			});
1187 		}
1188 		return timer;
1189 	}
1190 	
1191 	/**
1192 	 * Adds an exception and prints its stack trace in the exception panel.
1193 	 * The spider will automatically switch to error mode when this method is called.
1194 	 * 
1195 	 * @param e reported exception
1196 	 * @see #isError()
1197 	 */
1198 	public void addError(Throwable e) {
1199 		getSpiderDialog().addError(e);
1200 		setError(true);
1201 	}
1202 	
1203 	/**
1204 	 * Sets the custom action for the custom button. If the action is non null
1205 	 * the custom button will become visible in the information dialog and will
1206 	 * execute the given action when pressed.
1207 	 * If null action is set, the button will be invisible.
1208 	 * Default value for the custom action is start of the webstart application
1209 	 * defined in the acop/acop.properties file.
1210 	 * 
1211 	 * @param action the action to be performed on click
1212 	 */
1213 	public void setCustomAction(Action action) {
1214 		if (customAction == action) return;
1215 		Action oldValue = customAction;
1216 		customAction = action;
1217 		getSpiderDialog().customButton.setAction(customAction);
1218 		getSpiderDialog().customButton.setVisible(customAction != null);
1219 		firePropertyChange("customAction", oldValue, customAction);
1220 	}
1221 	
1222 	/**
1223 	 * Returns the currently set custom action.
1224 	 * @return the custom action
1225 	 */
1226 	public Action getCustomAction() {
1227 		return customAction;
1228 	}
1229 	
1230 //	/**
1231 //	 * Returns an array of all active links listed in the spider dialog.
1232 //	 * @return the array of active links
1233 //	 */
1234 //  FIXME commented out because of a palette problem in NetBeans
1235 //	public TLink[] getLinks() {
1236 //		return listedLinks.toArray(new TLink[listedLinks.size()]);
1237 //	}
1238 	
1239 	public static void main(String[] args) {
1240 		final AcopSpider bean = new AcopSpider();
1241 		
1242 		RunnerHelper.runComponent(bean, 300,300);
1243 		SwingUtilities.invokeLater(new Runnable(){
1244 			@Override
1245 			public void run() {
1246 				Exception ex = new Exception(new Exception());
1247 				ex.printStackTrace(bean.getSpiderDialog().throwableStream);
1248 				
1249 			}
1250 		});
1251 		new Thread(new Runnable() {
1252 			public void run() {
1253 				while (true) {
1254 				bean.setError(!bean.isError());
1255 				try {
1256 					Thread.sleep(5000);
1257 				} catch (InterruptedException e) {
1258 					e.printStackTrace();
1259 				}
1260 				}
1261 			}
1262 		}).start();
1263 		System.out.println(bean.getRootPane());
1264 	}
1265 }