/* * Copyright (C) 2011 Peransin Nicolas. * Use is subject to license terms. */ package org.mypsycho.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; /** * XXX Doc * <p>Detail ... </p> * @author Peransin Nicolas */ public class ZipEntryTreeModel implements TreeModel { public static final String ROOT_TOKEN = "root"; public static final String ROOT_ID = "-1"; public static final String PARENT_TOKEN = "parent."; public static final String ENTRY_TOKEN = "entry."; public static final String SIZE_TOKEN = "size."; // long, null <=> dir final File source; // Key may be null Map<String, ZipEntry[]> childrenByDirname = new HashMap<String, ZipEntry[]>(); static final String ROOT_PATH = ""; public ZipEntryTreeModel(File zipSource) throws IOException { source = zipSource; ZipInputStream zis = new ZipInputStream(new FileInputStream(zipSource)); Map<String, List<ZipEntry>> buildMap = new HashMap<String, List<ZipEntry>>(); try { for (ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()) { String name = entry.getName(); String path = ROOT_PATH; int start = 0; // We build ZipEntry for each directory for (int index = name.indexOf(CompressUtil.ZIP_FILE_SEPARATOR, start); index != -1; index = name.indexOf(CompressUtil.ZIP_FILE_SEPARATOR, start)) { List<ZipEntry> children = buildMap.get(path); if (children == null) { children = new ArrayList<ZipEntry>(); buildMap.put(path, children); } String subPathName = name.substring(start, index); path = path + subPathName + CompressUtil.ZIP_FILE_SEPARATOR; boolean exist = false; for (int iChild=0; iChild<children.size() && !exist; iChild++) { exist = path.equals(((ZipEntry) children.get(iChild)).getName()); } if (!exist) { children.add(new ZipEntry(path)); } start = path.length(); } if (entry.isDirectory()) // <=> name.endsWith(ZIP_FILE_SEPARATOR) continue; // already added !!! List<ZipEntry> children = buildMap.get(path); if (children == null) { children = new ArrayList<ZipEntry>(); buildMap.put(path, children); } children.add(entry); } } finally { zis.close(); } optimizeChildren(buildMap); } public ZipEntryTreeModel(Properties coded) { this(coded, ""); } public ZipEntryTreeModel(Properties coded, String prefix) { source = new File(coded.getProperty(prefix + ROOT_TOKEN)); int id = 0; Map<String, List<ZipEntry>> buildMap = new HashMap<String, List<ZipEntry>>(); for (String name = coded.getProperty(prefix + ENTRY_TOKEN + id); name != null; name = coded.getProperty(prefix + ENTRY_TOKEN + id)) { ZipEntry entry = new ZipEntry(name); if (coded.getProperty(prefix + SIZE_TOKEN + id) != null) { entry.setSize(Long.parseLong(coded.getProperty(prefix + SIZE_TOKEN + id))); } String parentCode = coded.getProperty(prefix + PARENT_TOKEN + id); String path = ROOT_PATH; if (!ROOT_ID.equals(parentCode)) { path = coded.getProperty(prefix + ENTRY_TOKEN + parentCode); } List<ZipEntry> children = buildMap.get(path); if (children == null) { children = new ArrayList<ZipEntry>(); buildMap.put(path, children); } children.add(entry); id++; } optimizeChildren(buildMap); } private void optimizeChildren(Map<String, List<ZipEntry>> build) { // We trim the list to array => Save space for (String path : build.keySet()) { List<ZipEntry> childList = build.get(path); ZipEntry[] children = new ZipEntry[childList.size()]; childList.toArray(children); Arrays.sort(children, ZIP_ENTRY_COMPARATOR); childrenByDirname.put(path, children); } } public Properties encode() { return encode(""); } public Properties encode(String prefix) { Properties result = new Properties(); result.put(prefix + ROOT_TOKEN, source.getPath()); int nextId = 0; ZipEntry[] children = getChildren(source); if (children!=null) { for (int iChild=0; iChild<children.length; iChild++) { result.put(prefix + PARENT_TOKEN + nextId, ROOT_ID); nextId = encode(children[iChild], result, nextId, prefix); } } return result; } public int encode(ZipEntry node, Properties store, int id, String prefix) { store.put(prefix + ENTRY_TOKEN + id, node.getName()); int nextId = id+1; // Note : file <=> length not null if (node.isDirectory()) { // null when no child ZipEntry[] children = getChildren(node); if (children!=null) { for (int iChild=0; iChild<children.length; iChild++) { store.put(prefix + PARENT_TOKEN + nextId, Integer.toString(id)); nextId = encode(children[iChild], store, nextId, prefix); } } } else { store.put(prefix + SIZE_TOKEN + id, Long.toString(node.getSize())); } return nextId; } /** * Note : * Multi-thread safe */ static final Comparator<ZipEntry> ZIP_ENTRY_COMPARATOR = new Comparator<ZipEntry>() { public int compare(ZipEntry o1, ZipEntry o2) { // Directories are before files int t1 = o1.isDirectory() ? 0 : 1; int t2 = o2.isDirectory() ? 0 : 1; if (t1 != t2) return t1 - t2; // Ignore case ? => Need to know if system is case-sensitive return CompressUtil.getEntryShortName(o1).compareToIgnoreCase( CompressUtil.getEntryShortName(o2)); } }; ZipEntry[] getChildren(Object parent) { String path = source.equals(parent) ? ROOT_PATH : ((ZipEntry) parent).getName(); return (ZipEntry[]) childrenByDirname.get(path); } public Object getChild(Object parent, int index) { ZipEntry[] children = getChildren(parent); return children[index]; } public int getChildCount(Object parent) { ZipEntry[] children = getChildren(parent); return (children != null) ? children.length : 0; } public int getIndexOfChild(Object parent, Object child) { ZipEntry[] children = getChildren(parent); for (int iChild=0; iChild<children.length; iChild++) { if (children[iChild] == child) return iChild; } return -1; } public Object getRoot() { return source; } public boolean isLeaf(Object node) { if (getRoot().equals(node)) return false; return !((ZipEntry) node).isDirectory(); } // Not modifiable => no need to have listeners public void addTreeModelListener(TreeModelListener l) {} public void removeTreeModelListener(TreeModelListener l) {} public void valueForPathChanged(TreePath path, Object newValue) {} }