/*
 * @(#)ZipTreeModel.java  1.1  2006-07-20
 *
 * Copyright (c) 2002-2006 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.view;

import ch.randelshofer.activation.*;
import ch.randelshofer.gui.tree.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.*;
import javax.activation.*;
import javax.swing.tree.*;
/**
 *
 * @author  Werner Randelshofer
 * @version 1.1 2006-07-20 Reworked for Java 1.5. 
 * <br>1.0 2002-02-11 Created.
 */
public class ZipTreeModel extends DefaultMutableTreeModel {
    private File file;
    private ZipFile zipFile;
    private ZipInputStream zipInputStream;
    
    public static class ZipNode {
        private ZipEntry zipEntry;
        private String name;
        /**
         * When the zip model has an input stream as
         * a data source, then the data of the zip
         * entry is stored here. data is null, when
         * an File is used as a data source.
         */
        private byte[] data;
        public void setZipEntry(ZipEntry z) {
            zipEntry = z;
        }
        public ZipEntry getZipEntry() {
            return zipEntry;
        }
        public void setName(String n) {
            name = n;
        }
        public String getName() {
            return name;
        }
        public String toString() {
            return name;
        }
        public void setData(byte[] data) {
            this.data = data;
        }
        public byte[] getData() {
            return data;
        }
    }
    
    /** Creates a new instance of ZipTreeModel */
    public ZipTreeModel() {
        super(new DefaultMutableTreeNode());
    }
    
    /**
     * Returns the zip file used as a data source.
     * This method returns null when the model has
     * either no data source or when the data source
     * used is an InputStream.
     */
    public ZipFile getZipFile() {
        return zipFile;
    }
    
    /**
     * Sets the zip file as the data source of the tree model.
     */
    public void setModel(File file)
    throws IOException {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) getRoot();
        this.file = file;
        this.zipInputStream = null;
        ZipFile zipFile = new ZipFile(file);
        Enumeration enm = zipFile.entries();
        while (enm.hasMoreElements()) {
            ZipEntry entry = (ZipEntry) enm.nextElement();
            
            // Is the item located zipInputStream a subdirectory?
            // ...Put it into a corresponding subtree.
            String[] path = getPathElements(entry.getName());
            
            DefaultMutableTreeNode parent = createParent(path);
            ZipNode zn = new ZipNode();
            zn.setZipEntry(entry);
            zn.setName(path[path.length - 1]);
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(zn);
            node.setAllowsChildren(entry.isDirectory());
            parent.add(node);
        }
    }
    /**
     * Sets the zip input stream as the data source of the tree model.
     */
    public void setModel(InputStream inputStream)
    throws IOException {
        byte[] buf = new byte[512];
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) getRoot();
        this.file = null;
        this.zipInputStream = new ZipInputStream(inputStream);
        ZipEntry entry;
        while ((entry = zipInputStream.getNextEntry()) != null) {
            // Is the item located zipInputStream a subdirectory?
            // ...Put it into a corresponding subtree.
            String[] path = getPathElements(entry.getName());
            
            DefaultMutableTreeNode parent = createParent(path);
            ZipNode zn = new ZipNode();
            zn.setZipEntry(entry);
            zn.setName(path[path.length - 1]);
            
            if (! entry.isDirectory()) {
                ByteArrayOutputStream bout = new ByteArrayOutputStream((int) Math.max(0, entry.getSize()));
                int count;
                while ((count = zipInputStream.read(buf, 0, 512)) != -1) {
                    bout.write(buf, 0, count);
                }
                zn.setData(bout.toByteArray());
            }
            
            DefaultMutableTreeNode node = new DefaultMutableTreeNode(zn);
            node.setAllowsChildren(entry.isDirectory());
            parent.add(node);
        }
    }
    
    public void dispose() {
        if (zipFile != null) {
            try { zipFile.close(); } catch (IOException e) {}
            zipFile = null;
        }
        if (zipInputStream != null) {
            try { zipInputStream.close(); } catch (IOException e) {}
            zipInputStream = null;
        }
    }
    
    
    /**
     * Returns an array of path elements for the given pathname,
     * of which each path component has to be delimited by a '/' character.
     */
    private String[] getPathElements(String pathname) {
        StringTokenizer scanner = new StringTokenizer(pathname, "/");
        String[] elements = new String[scanner.countTokens()];
        for (int i=0; i < elements.length; i++) {
            elements[i] = scanner.nextToken();
        }
        return elements;
    }
    /**
     * Creates a parent or returns an existing parent for the indicated path.
     *
     * @param path A string of path elements created by #getPathElements.
     */
    private DefaultMutableTreeNode createParent(String[] path) {
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) getRoot();
        String currentPath = "";
        DefaultMutableTreeNode node;
        depthLoop: for (int i=0; i < path.length - 1; i++) {
            Enumeration children = parent.children();
            
            breadthLoop: while (children.hasMoreElements()) {
                node = (DefaultMutableTreeNode) children.nextElement();
                if (node.toString().equals(path[i])) {
                    parent = node;
                    continue depthLoop;
                }
            }
            ZipNode zn = new ZipNode();
            zn.setName(path[i]);
            node = new DefaultMutableTreeNode(zn);
            node.setAllowsChildren(true);
            parent.add(node);
            parent = node;
        }
        return (DefaultMutableTreeNode) parent;
    }
    
    /**
     * Creates a JavaBeans Activation Framework data source
     * for the given zip node.
     */
    public DataSource createDataSource(ZipNode zipNode) {
        if (file != null) {
            try {
                return new FileTypeMappedURLDataSource(new URL("jar:"+file.toURI().toURL()+"!/"+zipNode.getZipEntry().getName()));
            } catch (MalformedURLException e) {
                return new ZipEntryDataSource(getZipFile(), zipNode.getZipEntry());
            }
        } else {
            return new ZipEntryDataSource(zipNode.getData(), zipNode.getZipEntry());
        }
    }
}
