package lcm.spy;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
import lcm.util.*;
import java.lang.reflect.*;
import lcm.lcm.*;
/** Spy main class. **/
public class Spy
{
    JFrame       jf;
    LCM          lcm;
    JDesktopPane jdp;
    LCMTypeDatabase handlers;
    HashMap<String,  ChannelData> channelMap = new HashMap<String, ChannelData>();
    ArrayList<ChannelData>        channelList = new ArrayList<ChannelData>();
    ChannelTableModel _channelTableModel = new ChannelTableModel();
    TableSorter  channelTableModel = new TableSorter(_channelTableModel);
    JTable channelTable = new JTable(channelTableModel);
    ArrayList<SpyPlugin> plugins = new ArrayList<SpyPlugin>();
    JButton clearButton = new JButton("Clear");
    public Spy(String lcmurl) throws IOException
    {
        jf = new JFrame("LCM Spy");
        jdp = new JDesktopPane();
        jdp.setBackground(new Color(0, 0, 160));
        jf.setLayout(new BorderLayout());
        jf.add(jdp, BorderLayout.CENTER);
        jf.setSize(1024, 768);
        jf.setVisible(true);
        //	sortedChannelTableModel.addMouseListenerToHeaderInTable(channelTable);
        channelTableModel.setTableHeader(channelTable.getTableHeader());
        channelTableModel.setSortingStatus(0, TableSorter.ASCENDING);
        handlers = new LCMTypeDatabase();
        TableColumnModel tcm = channelTable.getColumnModel();
        tcm.getColumn(0).setMinWidth(140);
        tcm.getColumn(1).setMinWidth(140);
        tcm.getColumn(2).setMaxWidth(100);
        tcm.getColumn(3).setMaxWidth(100);
        tcm.getColumn(4).setMaxWidth(100);
        tcm.getColumn(5).setMaxWidth(100);
        tcm.getColumn(6).setMaxWidth(100);
        JInternalFrame jif = new JInternalFrame("Channels", true);
        jif.setLayout(new BorderLayout());
        jif.add(channelTable.getTableHeader(), BorderLayout.PAGE_START);
        // XXX weird bug, if clearButton is added after JScrollPane, we get an error.
        jif.add(clearButton, BorderLayout.SOUTH);
        jif.add(new JScrollPane(channelTable), BorderLayout.CENTER);
        jif.setSize(800,600);
        jif.setVisible(true);
        jdp.add(jif);
        if(null == lcmurl)
            lcm = new LCM();
        else
            lcm = new LCM(lcmurl);
        lcm.subscribeAll(new MySubscriber());
        new HzThread().start();
        clearButton.addActionListener(new ActionListener()
	    {
            public void actionPerformed(ActionEvent e)
            {
                channelMap.clear();
                channelList.clear();
                channelTableModel.fireTableDataChanged();
            }
	    });
        channelTable.addMouseListener(new MouseAdapter()
	    {
            public void mouseClicked(MouseEvent e)
            {
                int mods=e.getModifiersEx();
                if (e.getButton()==3)
                {
                    showPopupMenu(e);
                }
                else if (e.getClickCount() == 2)
                {
                    Point p = e.getPoint();
                    int row = rowAtPoint(p);
                    ChannelData cd = channelList.get(row);
                    boolean got_one = false;
                    for (SpyPlugin plugin : plugins)
                    {
                        if (!got_one && plugin.canHandle(cd.fingerprint)) {
                            plugin.getAction(jdp, cd).actionPerformed(null);
                            got_one = true;
                        }
                    }
                    if (!got_one)
                        createViewer(channelList.get(row));
                }
            }
	    });
        jf.addWindowListener(new WindowAdapter()
	    {
            public void windowClosing(WindowEvent e)
            {
                System.out.println("Spy quitting");
                System.exit(0);
            }
	    });
        ClassDiscoverer.findClasses(new PluginClassVisitor());
        System.out.println("Found "+plugins.size()+" plugins");
        for (SpyPlugin plugin : plugins) {
            System.out.println(" "+plugin);
        }
    }
    class PluginClassVisitor implements ClassDiscoverer.ClassVisitor
    {
        public void classFound(String jar, Class cls)
        {
            Class interfaces[] = cls.getInterfaces();
            for (Class iface : interfaces) {
                if (iface.equals(SpyPlugin.class)) {
                    try {
                        Constructor c = cls.getConstructor(new Class[0]);
                        SpyPlugin plugin = (SpyPlugin) c.newInstance(new Object[0]);
                        plugins.add(plugin);
                    } catch (Exception ex) {
                        System.out.println("ex: "+ex);
                    }
                }
            }
        }
    }
    void createViewer(ChannelData cd)
    {
        if (cd.viewerFrame != null && !cd.viewerFrame.isVisible())
	    {
            cd.viewerFrame.dispose();
            cd.viewer = null;
	    }
        if (cd.viewer == null) {
            cd.viewerFrame = new JInternalFrame(cd.name, true, true);
            cd.viewer = new ObjectPanel(cd.name);
            cd.viewer.setObject(cd.last);
            //	cd.viewer = new ObjectViewer(cd.name, cd.cls, null);
            cd.viewerFrame.setLayout(new BorderLayout());
            cd.viewerFrame.add(new JScrollPane(cd.viewer), BorderLayout.CENTER);
            jdp.add(cd.viewerFrame);
            cd.viewerFrame.setSize(500,400);
            cd.viewerFrame.setVisible(true);
        } else {
            cd.viewerFrame.setVisible(true);
            cd.viewerFrame.moveToFront();
        }
    }
    static final long utime_now()
    {
        return System.nanoTime()/1000;
    }
    class ChannelTableModel extends AbstractTableModel
    {
        public int getColumnCount()
        {
            return 8;
        }
        public int getRowCount()
        {
            return channelList.size();
        }
        public Object getValueAt(int row, int col)
        {
            ChannelData cd = channelList.get(row);
            if (cd == null)
                return "";
            switch (col)
            {
                case 0:
                    return cd.name;
                case 1:
                    if (cd.cls == null)
                        return String.format("?? %016x", cd.fingerprint);
                    String s = cd.cls.getName();
                    return s.substring(s.lastIndexOf('.')+1);
                case 2:
                    return ""+cd.nreceived;
                case 3:
                    return String.format("%6.2f", cd.hz);
                case 4:
                    return String.format("%6.2f ms",1000.0/cd.hz); // cd.max_interval/1000.0);
                case 5:
                    return String.format("%6.2f ms",(cd.max_interval - cd.min_interval)/1000.0);
                case 6:
                    return String.format("%6.2f KB/s", (cd.bandwidth/1024.0));
                case 7:
                    return ""+cd.nerrors;
            }
            return "???";
        }
        public String getColumnName(int col)
        {
            switch (col)
            {
                case 0:
                    return "Channel";
                case 1:
                    return "Type";
                case 2:
                    return "Num Msgs";
                case 3:
                    return "Hz";
                case 4:
                    return "1/Hz";
                case 5:
                    return "Jitter";
                case 6:
                    return "Bandwidth";
                case 7:
                    return "Undecodable";
            }
            return "???";
        }
    }
    class MySubscriber implements LCMSubscriber
    {
        public void messageReceived(LCM lcm, String channel, LCMDataInputStream dins)
        {
            Object o = null;
            ChannelData cd = channelMap.get(channel);
            int msg_size = 0;
            try {
                msg_size = dins.available();
                long fingerprint = (msg_size >=8) ? dins.readLong() : -1;
                dins.reset();
                Class cls = handlers.getClassByFingerprint(fingerprint);


Answer:                 if (cd == null) {