/*
 * $File: //depot/projects/jarbrowser/conexus/util/JarInventory.java $    $Revision: #3 $
 * $Date: 2003/07/01 $    $Author: scottp $
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
package conexus.util;

// Conexus imports

// Thirdparty imports

// Java imports
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.text.*;
import java.text.*;
import javax.swing.text.Position;
import javax.swing.event.*;

/** This little utility inventories all jar, class, and zip files under
 * a given directory and provides both a tree view of all classes and
 * their locations, and let's you search the inventory.
 *
 * @author scottp
 */

public class JarInventory {
    JFrame _top;
    String _rootDir;
    JTextField _dirEntry, _search;
    JTree _jarTree;
    JTree _classTree;
    JTree _classpathTree;
    DateFormat _dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
    String _lastSearch = "";
    JTabbedPane _tabs;
    TreePath _lastSearchPath;
    JLabel _addressLabel;
    ImageIcon _jarIcon, _classIcon;
    Highlighter.HighlightPainter _searchPainter = 
        new DefaultHighlighter.DefaultHighlightPainter(new Color(115,255,253));
    
    public static void main(String[] args) {
        new JarInventory();
    }
    
    public JarInventory() { 
        _rootDir = "c:/";
        readConfig();
        
        try {
            UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        } catch( Exception e ) { }
        
        _top = new JFrame("Jar在庫");
	_top.setIconImage(getImage("Jar24.gif").getImage());
        _top.addWindowListener(new WindowAdapter() {
            public void windowClosed(WindowEvent event) {
                close();
            }
        });
        _top.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        
        JPanel panel = new JPanel(new BorderLayout());
        
        JPanel topPanel = new JPanel(new BorderLayout());
        JPanel dirPanel = new JPanel(new FlowLayout());
        dirPanel.add(new JLabel("ルーツディレクトリ："));
        _dirEntry = new JTextField();
        _dirEntry.setColumns(40);
        _dirEntry.setText(_rootDir);
        _dirEntry.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                _rootDir = _dirEntry.getText();
                rescan();
            }
        });
        dirPanel.add(_dirEntry);
        
        JButton browse = new JButton("参照");
        browse.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                browseDirectory();
            }
        });
        dirPanel.add(browse);
        
        _addressLabel = new JLabel("  アドレス：");
        //_addressLabel.setBackground(Color.LIGHT_GRAY);
        //_addressLabel.setOpaque(true);
        _addressLabel.setBorder(BorderFactory.createLineBorder(Color.black));
        _addressLabel.setPreferredSize(new Dimension(500, 25));
        
        JButton closeTabButton = new JButton("x");
        closeTabButton.setMargin(new Insets(0, 6, 0, 6));
        closeTabButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                if( _tabs.getSelectedIndex() > 2 ){
                    Component c = _tabs.getSelectedComponent();
                    if( c != null ){
                        _tabs.remove(c);
                    }
                }
            }
        });
        JPanel addressPanel = new JPanel(new BorderLayout());
        addressPanel.add(_addressLabel);
        addressPanel.add(closeTabButton, BorderLayout.EAST);
        
        topPanel.add(addressPanel, BorderLayout.SOUTH);
        
        topPanel.add(dirPanel, BorderLayout.NORTH);
        
        panel.add(topPanel, BorderLayout.NORTH);
        
        _tabs = new JTabbedPane();
        
        MouseListener treeMouseListener = new MouseAdapter() {
            public void mouseClicked(MouseEvent event) {
                if( event.getClickCount() == 2 ){
                    JTree tree = (JTree)event.getSource();
                    TreePath path = tree.getClosestPathForLocation(event.getX(), event.getY());
                    if( path != null ){
                        Object node = path.getLastPathComponent();
                        if( node instanceof ClassNode ){
                            showDecompiledClass((ClassNode)node);
                        }
                    }
                }
            }
        };
        _jarIcon = getImage("Object16.gif");
        _classIcon = getImage("process16.gif");
        
        TreeCellRenderer cellRenderer = new DefaultTreeCellRenderer() {
            public Component getTreeCellRendererComponent(JTree tree,
                                                          Object value,
                                                          boolean sel,
                                                          boolean expanded,
                                                          boolean leaf,
                                                          int row,
                                                          boolean hasFocus) {
                super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
                if( value instanceof ClassNode ){
                    setIcon(_classIcon);
                } else if( value instanceof JarNode ){
                    setIcon(_jarIcon);
                }
                return this;
            }
        };
        
        _jarTree = new JTree(new DefaultTreeModel(new TextNode(null, "jarファイルを検索する場合は「検索」ボタンを押してください")));
        _jarTree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent event) {
                TreePath path = event.getNewLeadSelectionPath();
                if( path != null ){
                    setAddress(calcPathLabel(path));
                }
            }
        });
        _jarTree.addMouseListener(treeMouseListener);
        _jarTree.setCellRenderer(cellRenderer);
        
        _classTree = new JTree(); //new ClassTreeModel());
        _classTree.addMouseListener(treeMouseListener);
        _classTree.setCellRenderer(cellRenderer);
        
        _classpathTree = new JTree(new DefaultTreeModel(new ClasspathTreeNode()));
        _classpathTree.addMouseListener(treeMouseListener);
        _classpathTree.setCellRenderer(cellRenderer);
        
        _tabs.add("Jarカタログ", new JScrollPane(_jarTree));
        _tabs.add("クラスカタログ", new JScrollPane(_classTree));
        _tabs.add("CLASSPATHカタログ", new JScrollPane(_classpathTree));
        panel.add(_tabs);
        
        JPanel buttons = new JPanel(new FlowLayout());
        JButton scan = new JButton("検索");
        scan.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                rescan();
            }
        });
        buttons.add(scan);
        
        buttons.add(new JLabel("  クラス名："));
        _search = new JTextField();
        _search.setColumns(45);
        _search.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                search();
            }
        });
        buttons.add(_search);
        
        JButton close = new JButton("終了");
        close.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                close();
            }
        });
        buttons.add(close);
        
        panel.add(buttons, BorderLayout.SOUTH);
        
        _top.setContentPane(panel);
        _top.setSize(new Dimension(700, 600));
        
        _top.setVisible(true);
    }
    
    void showDecompiledClass(ClassNode node) {
        try {
            File temp = File.createTempFile("foo", ".class");
            FileOutputStream out = new FileOutputStream(temp);
            out.write(node.getData());
            out.close();
            String dir = temp.getParentFile().getAbsolutePath();
            String[] cmd = new String[] {"jad.exe", "-p", new File(dir, temp.getName()).getAbsolutePath()};
            Process p = Runtime.getRuntime().exec(cmd);
            showOutputInNewTab(p.getInputStream(), node.getClassName());
        } catch( IOException e ){
            JOptionPane.showMessageDialog(_top, e.toString());
        }
    }
    
    void showOutputInNewTab(InputStream in, String className) {
        //JTextArea text = new JTextArea();
        SyntaxHighlightText textHighlight = new SyntaxHighlightText();
        JTextPane text = textHighlight.getTextPane();
        
        text.setEditable(false);
        text.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent event) {
                if( event.isPopupTrigger() ){
                    showMenu(event);
                }
            }
            public void mouseReleased(MouseEvent event) {
                if( event.isPopupTrigger() ){
                    showMenu(event);
                }
            }
            void showMenu(MouseEvent event) {
                JPopupMenu menu = new JPopupMenu();
                JMenuItem save = new JMenuItem("保存...");
                menu.add(save);
                SaveListener listener = new SaveListener((JTextArea)event.getSource());
                save.addActionListener(listener);
                menu.show((Component)event.getSource(), event.getX(), event.getY());
            }
        });
        
        JScrollPane scroll = new JScrollPane(text);
        _tabs.add(className, scroll);
        new ISCopier(in, textHighlight).start();
        
        text.setCaretPosition(0);
        _tabs.setSelectedComponent(scroll);
    }
   
    static class ISCopier extends Thread {
        InputStream _is;
        SyntaxHighlightText _text;
        char[] _buf = new char[200];
        int _len;
        
        public ISCopier(InputStream is, SyntaxHighlightText text) {
            _is = is;
            _text = text;
        }
        
        public void run() {
            try {
                Reader reader = new InputStreamReader(_is);
                while( (_len = reader.read(_buf)) >= 0 ){
                    try {
                        SwingUtilities.invokeAndWait(new Runnable() {
                            public void run() {
                                _text.addText(new String(_buf, 0, _len));
                                //_text.append(new String(_buf, 0, _len));
                            }
                        });
                    } catch( Exception e ) { }
                }
            } catch( IOException e ){ 
            }
        }
    }
    
    class SaveListener implements ActionListener {
        JTextArea _text;
        
        public SaveListener(JTextArea text) {
            _text = text;
        }
        
        public void actionPerformed(ActionEvent event) {
            JFileChooser chooser = new JFileChooser(_rootDir);
            if( chooser.showSaveDialog(_top) == JFileChooser.APPROVE_OPTION ){
                java.io.File f = chooser.getSelectedFile();
                try {
                    FileWriter writer = new FileWriter(f);
                    writer.write(_text.getText());
                    writer.close();
                } catch( IOException e ){
                    e.printStackTrace();
                }
            }
        }
    }
            
    void browseDirectory() {
        JFileChooser chooser = new JFileChooser(_rootDir);
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        if( chooser.showOpenDialog(_top) == JFileChooser.APPROVE_OPTION ){
            java.io.File f = chooser.getSelectedFile();
            if( f != null ){
                _rootDir = f.getAbsolutePath();
                _dirEntry.setText(_rootDir);
                rescan();
            }
        }
    }
      
    void close() {
        saveConfig();
        System.exit(0);
    }
    
    void setAddress(String path) {
        _addressLabel.setText("  アドレス： " + path);
    }
    
    void rescan() {
        saveConfig();

        TextNode root = new TextNode(null, "Jar一覧");
        TreeMap results = new TreeMap();
        File rootDir = new File(_rootDir);
        scanDir(rootDir, results, root);
        
        Iterator iter = results.values().iterator();
        while( iter.hasNext() ){
            JarNode node = (JarNode)iter.next();
            node.setShowFullName(true, rootDir);
            root.add(node);
        }
        
        _jarTree.setModel(new DefaultTreeModel(root));
    }

    void scanDir(File dir, Map results, TreeNode rootNode) {
        File[] list = dir.listFiles(new JarFilter(false));
        for( int i = 0; i < list.length; i++ ){
            File next = list[i];
            if( next.isDirectory() ){
                scanDir(next, results, rootNode);
            } else {
                results.put(next.getName(), new JarNode(rootNode, next));
            }
        }
    }
    
    
    void search() {
        JTree tree = null;
        switch( _tabs.getSelectedIndex() ){
            case 0: 
                tree = _jarTree;
                break;
            case 1:
                tree = _classTree;
                break;
            case 2:
                tree = _classpathTree;
                break;
            default:
                // searching in text of a decompiled class
                searchClassText();
                return;
        }
        String search = _search.getText();
        if( search.length() > 0 ){
            int start = 0;
            if( search.equals(_lastSearch) ){
                int[] rows = tree.getSelectionRows();
                if( rows != null && rows.length > 0 ){
                    start = rows[0]+1;
                }
            }
            TreePath path = tree.getNextMatch(search, start, Position.Bias.Forward);
            if( path == null || path.equals(_lastSearchPath) ||
                tree.getRowForPath(path) < start ){
                path = findNextTreeMatch(tree, search, start);
            }
            if( path != null ){
                tree.scrollPathToVisible(path);
                tree.setSelectionPath(path);
            }
            _lastSearch = search;
            _lastSearchPath = path;
        }
    }
    
    TreePath findNextTreeMatch(JTree tree, String prefix, int startRow) {
        TreeModel model = tree.getModel();
        return _findNextTreeMatch((TreeNode)model.getRoot(), tree, prefix.toLowerCase(), startRow);
    }
    
    TreePath _findNextTreeMatch(TreeNode node, JTree tree, String prefix, int startRow) {
        if( node.toString().toLowerCase().startsWith(prefix) ){
            System.out.println("一致したノード： " + node);
            TreePath path = calcTreePath(node);
            TreePath visiblePath = path;
            while( !tree.isVisible(visiblePath) ){
                visiblePath = visiblePath.getParentPath();
            }
            int row = tree.getRowForPath(visiblePath);
            if( row >= startRow ){
                return path;
            }
        }
        //System.out.println(node.toString().toLowerCase() + " compared to " + prefix);
        int count = node.getChildCount();
        for( int i = 0; i < count; i++ ){
            TreeNode child = node.getChildAt(i);
            TreePath res = _findNextTreeMatch(child, tree, prefix, startRow);
            if( res != null ){
                return res;
            }
        }
        return null;
    }
    
    void searchClassText() {
        if( _tabs.getSelectedComponent() instanceof JScrollPane ){
            JTextComponent textArea = (JTextComponent)(((JScrollPane)_tabs.getSelectedComponent()).getViewport().getView());
            String text = textArea.getText().toLowerCase();
            int startPos = 0;
            if( textArea.getSelectionEnd() > 0 ){
                startPos = textArea.getSelectionEnd()+1;
            }
            String search = _search.getText().toLowerCase();
            for( int i = 0; i < 2; i++ ){
                int idx = text.indexOf(search, startPos);
                if( idx >= 0 ){
                    textArea.getHighlighter().removeAllHighlights();
                    try {
                        textArea.getHighlighter().addHighlight(idx, idx + search.length(), _searchPainter);
                    } catch( Exception e ){ }
                    textArea.getCaret().setDot(idx);
                    textArea.getCaret().moveDot(idx + search.length());
                    break;
                } else {
                    if( startPos > 0 ){
                        startPos = 0;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
    static TreePath calcTreePath(TreeNode leaf) {
        Vector elts = new Vector();
        while( leaf != null ){
            elts.add(0, leaf);
            leaf = leaf.getParent();
        }
        if( elts.size() > 0 ){
            return new TreePath(elts.toArray());
        } else {
            return null;
        }
    }
    
    static String calcPathLabel(TreePath leaf) {
        Vector elts = new Vector();
        while( leaf != null ){
            elts.add(0, leaf.getLastPathComponent());
            leaf = leaf.getParentPath();
        }
        
        StringBuffer result = new StringBuffer();
        for( int i = 0; i < elts.size(); i++ ){
            if( i > 0 ){
                result.append("/");
            }
            result.append(elts.get(i).toString());
        }
        return result.toString();
    }
    
    void readConfig() {
        Properties props = new Properties();
        try {
            FileInputStream in = new FileInputStream(getConfigFile());
            props.load(in);
            _rootDir = props.getProperty("root");
            in.close();
        } catch( IOException e ){
        }
    }
    
    void saveConfig() {
        Properties props = new Properties();
        try {
            FileOutputStream out = new FileOutputStream(getConfigFile());
            props.setProperty("root", _rootDir);
            props.store(out, "Jar在庫の設定");
            out.close();
        } catch( IOException e ){
        }
    }
    
    File getConfigFile() {
        return new File(System.getProperty("user.home"), "jarinv.props");
    }
    
    class ClasspathTreeNode implements TreeNode {
        Vector _children;
        
        public ClasspathTreeNode() {
            _children = new Vector();
            String cp = System.getProperty("java.class.path");
            StringTokenizer tokens = new StringTokenizer(cp, File.pathSeparator);
            while( tokens.hasMoreTokens() ){
                String item = (String)tokens.nextToken();
                File f = new File(item);
                if( f.isDirectory() ){
                    _children.add(new FileNode(this, f, true));
                } else {
                    _children.add(new JarNode(this, f));
                }
            }
        }
        
        public java.util.Enumeration children() {
            return _children.elements();
        }
        
        public boolean getAllowsChildren() {
            return true;
        }
        
        public javax.swing.tree.TreeNode getChildAt(int idx) {
            return (TreeNode)_children.get(idx);
        }
        
        public int getChildCount() {
            return _children.size();
        }
        
        public int getIndex(javax.swing.tree.TreeNode treeNode) {
            return _children.indexOf(treeNode);
        }
        
        public javax.swing.tree.TreeNode getParent() {
            return null;
        }
        
        public boolean isLeaf() {
            return false;
        }
        
        public String toString() {
            return "CLASSPATH";
        }
    } // ClasspathTreeNode
    
    class FileNode implements TreeNode {
        File _file;
        Vector _children;
        TreeNode _parent;
        boolean _showFullName;
        
        public FileNode(TreeNode parent, File file, boolean showFullName) {
            _file = file;
            _parent = parent;
            _showFullName = showFullName;
        }
        
        void calcChildren() {
            if( _children == null ){
                _children = new Vector();
                File[] items = _file.listFiles(new JarFilter(true));
                for( int i = 0; items != null && i < items.length; i++ ){
                    if( items[i].getName().endsWith(".jar") ||
                        items[i].getName().endsWith(".zip") ){
                        _children.add(new JarNode(this, items[i]));
                    } else {
                        _children.add(new FileNode(this, items[i], false));
                    }
                }
            }
        }
        
        public java.util.Enumeration children() {
            calcChildren();
            return _children.elements();
        }
        
        public boolean getAllowsChildren() {
            return( _file.isDirectory() );
        }
        
        public javax.swing.tree.TreeNode getChildAt(int idx) {
            calcChildren();
            return (TreeNode)_children.get(idx);
        }
        
        public int getChildCount() {
            calcChildren();
            return _children.size();
        }
        
        public int getIndex(javax.swing.tree.TreeNode treeNode) {
            calcChildren();
            return _children.indexOf(treeNode);
        }
        
        public javax.swing.tree.TreeNode getParent() {
            return _parent;
        }
        
        public boolean isLeaf() {
            return _file.isFile();
        }
        
        public String toString() {
            if( _showFullName ){
                return _file.getPath();
            } else {
                return _file.getName();
            }
        }
    }

    public static ImageIcon getImage(String path) {
        if( !path.startsWith("/") ){
            path = "/conexus/images/" + path;
        }
        java.net.URL imgURL = path.getClass().getResource(path);
        if( imgURL != null ){
            ImageIcon result = new ImageIcon(imgURL);
            //waitForImageToLoad(result);
            return result;
        } else {
            System.err.println("画像がありません： " + path);
            return null;
        }
    }
    
    static class JarFilter implements FileFilter {
        boolean _includeClassFiles;
        
        public JarFilter(boolean includeClassFiles) {
            _includeClassFiles = includeClassFiles;
        }
        public boolean accept(File f) {
            boolean res = ( f.isDirectory() || (_includeClassFiles && f.getName().endsWith(".class")) ||
                            f.getName().endsWith(".jar") ||
                            f.getName().endsWith(".zip") );
            return res;
        }
    }
    
    class JarNode implements TreeNode {
        File _file;
        TreeNode _parent;
        JarFile _jar;
        Vector _children;
        boolean _showFullName;
        File _root;
        
        public JarNode(TreeNode parent, File file) {
            _file = file;
            _parent = parent;
            try {
                if( file.exists() ){
                    _jar = new JarFile(file);
                }
            } catch( IOException e ){
                System.err.println(e);
            }
        }
        
        public void setShowFullName(boolean flag, File root) {
            _showFullName = flag;
            _root = root;
        }
        
        void calcChildren() {
            if( _children == null ){
                _children = new Vector();

                if( _jar == null ){
                    return;
                }
                
                // Map of qualified folder names to TreeMap with the contents
                Map folders = new HashMap();

                String root = "__$_ROOT__$";
                TreeMap rootFolder = new TreeMap();
                folders.put(root, rootFolder);
                
                Enumeration entries = _jar.entries();
                while( entries.hasMoreElements() ){
                    JarEntry entry = (JarEntry)entries.nextElement();
                    if( entry.isDirectory() ){
                        continue;
                    }
                    String folder = root;
                    String name = entry.getName();
                    int idx = name.lastIndexOf('/');
                    if( idx > 0 ){
                        folder = name.substring(0, idx);
                        name = name.substring(idx+1);
                    }
                    TreeMap items = (TreeMap)folders.get(folder);
                    if( items == null ){
                        registerItemsWithParent(folders, folder, rootFolder);
                        items = (TreeMap)folders.get(folder);
                    }
                    items.put(name, entry);
                }
                addChildren(_children, rootFolder);
            }
            
        }
        
        private void registerItemsWithParent(Map folders, String folderName,
                                            TreeMap root) {
            StringTokenizer tokens = new StringTokenizer(folderName, "/");
            TreeMap parent = root;
            String item = folderName;
            TreeMap child = null;
            while( tokens.hasMoreTokens() ){
                item = (String)tokens.nextToken();
                child = (TreeMap)parent.get(item);
                if( child == null ){
                    child = new TreeMap();
                    parent.put(item, child);
                }
                parent = child;
            }
            folders.put(folderName, child);
        }
        
        private void addChildren(Object node, TreeMap folder) {
            Iterator iter = folder.entrySet().iterator();
            while( iter.hasNext() ){
                Map.Entry entry = (Map.Entry)iter.next();
                Object item = entry.getValue();
                if( item instanceof JarEntry ){
                    JarEntry jentry = (JarEntry)item;
                    if( node instanceof TextNode ){
                        ((TextNode)node).add(new ClassNode((TextNode)node, jentry, _jar));
                    } else {
                        ((Vector)node).add(new ClassNode(this, jentry, _jar));
                    }
                } else if( item instanceof TreeMap ){
                    TreeNode parent = (node instanceof TreeNode ? (TreeNode)node : this);
                    TextNode child = new TextNode(parent, (String)entry.getKey());
                    addChildren(child, (TreeMap)item);
                    if( node instanceof TextNode ){
                        ((TextNode)node).add(child);
                    } else {
                        ((Vector)node).add(child);
                    }
                }
            }
        }
        
        public java.util.Enumeration children() {
            calcChildren();
            return _children.elements();
        }
        
        public javax.swing.tree.TreeNode getChildAt(int idx) {
            calcChildren();
            return (TreeNode)_children.get(idx);
        }
        
        public int getChildCount() {
            calcChildren();
            return _children.size();
        }
        
        public int getIndex(javax.swing.tree.TreeNode treeNode) {
            calcChildren();
            return _children.indexOf(treeNode);
        }
        
        public boolean getAllowsChildren() {
            return false;
        }
        
        public javax.swing.tree.TreeNode getParent() {
            return _parent;
        }
        
        public boolean isLeaf() {
            return false;
        }
        
        public String toString() {
            String name = _showFullName ? _file.getPath() : _file.getName();
            if( _showFullName && _root != null ){
                String pathName = _root.getPath();
                if( name.startsWith(pathName) ){
                    name = name.substring(pathName.length()+1);
                }
            }
            name = name + " [JAR]";
            if( _jar == null ){
                return name + " [がありません]";
            } else {
                return name;
            }
        }
    }
    
    class TextNode implements TreeNode {
        TreeNode _parent;
        String _label;
        Vector _children;
        
        public TextNode(TreeNode parent, String label) {
            _parent = parent;
            _label = label;
            _children = new Vector();
        }
        
        public void add(TreeNode child) {
            _children.add(child);
        }
        
        public java.util.Enumeration children() {
            return _children.elements();
        }
        
        public boolean getAllowsChildren() {
            return true;
        }
        
        public javax.swing.tree.TreeNode getChildAt(int param) {
            return (TreeNode)_children.get(param);
        }
        
        public int getChildCount() {
            return _children.size();
        }
        
        public int getIndex(javax.swing.tree.TreeNode treeNode) {
            return _children.indexOf(treeNode);
        }
        
        public javax.swing.tree.TreeNode getParent() {
            return _parent;
        }
        
        public boolean isLeaf() {
            return _children.size() == 0;
        }
        
        public String toString() {
            return _label;
        }
        
        public void setLabel(String val) {
            _label = val;
        }
        
    } // TextNode
    
    class ClassNode extends TextNode {
        JarEntry _entry;
        JarFile _jar;
        String _className;
        
        public ClassNode(TreeNode parent, JarEntry entry, JarFile jar) {
            super(parent, "");
        
            _entry = entry;
            _jar = jar;
            
            String name = entry.getName();
            int idx = name.lastIndexOf('/');
            if( idx >= 0 ){
                name = name.substring(idx+1);
            }
            _className = name;
            setLabel(name + " [" + _dateFormat.format(new Date(entry.getTime())) + "]");
        }
        
        public byte[] getData() throws IOException {
            InputStream fis = _jar.getInputStream(_entry);
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();

            byte[] buf = new byte[1024];
            int count = 0;
            while( (count = fis.read(buf, 0, 1024)) >= 0 ){
                buffer.write(buf, 0, count);
            }
            fis.close();
            byte[] data = buffer.toByteArray();
            buffer.close();
            return data;
        }
        public String getClassName() {
            return _className;
        }
    }
    
} // class JarInventory



