/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.opensourcephysics.controls.XML;
/**
* A tree model to display files and jar/zip contents.
*
* @author Doug Brown
* @version 1.0
*/
public class JarTreeModel implements TreeModel {
// instance fields
protected File root; // root must be a directory
protected Map<File, JarNode[]> topLevelNodeArrays = new HashMap<File, JarNode[]>(); // maps jar to JarNode[]
protected Map<File, Map<String, JarNode>> pathMaps = new HashMap<File, Map<String, JarNode>>(); // maps jar to Map of relative path to JarNode
/**
* Constructor.
*
* @param root a directory file
*/
public JarTreeModel(File root) {
this.root = root;
}
/**
* Gets the root of this tree model.
*
* @return the root file
*/
public Object getRoot() {
return root;
}
/**
* Returns true if the specified node is a leaf.
*
* @param node the tree node
* @return true if node is a leaf
*/
public boolean isLeaf(Object node) {
if(node instanceof File) {
File file = (File) node;
if(file.getName().endsWith(".jar")) { //$NON-NLS-1$
return false;
}
return((File) node).isFile();
} else if(node instanceof JarNode) {
JarNode treeNode = (JarNode) node;
return treeNode.isLeaf();
}
return true;
}
/**
* Determines the number of child nodes for the specified node.
*
* @param parent the parent node
* @return the number of child nodes
*/
public int getChildCount(Object parent) {
if(parent instanceof File) {
File parentFile = (File) parent;
if(parentFile.getName().endsWith(".jar")) { //$NON-NLS-1$
JarNode[] nodes = getJarNodes(parentFile);
return(nodes==null) ? 0 : nodes.length;
}
String[] children = ((File) parent).list();
return(children==null) ? 0 : children.length;
} else if(parent instanceof JarNode) {
JarNode treeNode = (JarNode) parent;
return treeNode.getChildCount();
}
return 0;
}
/**
* Gets the child node at a specified index. Parent and child may be a
* File or JarNode.
*
* @param parent the parent node
* @param index the index
* @return the child node
*/
public Object getChild(Object parent, int index) {
if(parent instanceof File) {
File parentFile = (File) parent;
// if parent is the launch jar, return a JarNode
if(parentFile.getName().endsWith(".jar")) { //$NON-NLS-1$
JarNode[] nodes = getJarNodes(parentFile);
if((nodes!=null)&&(nodes.length>index)) {
return nodes[index];
}
return "no child found"; //$NON-NLS-1$
}
String[] children = parentFile.list();
if((children==null)||(index>=children.length)) {
return null;
}
return new File(parentFile, children[index]) {
public String toString() {
return getName();
}
};
} else if(parent instanceof JarNode) {
JarNode treeNode = (JarNode) parent;
return treeNode.getChildAt(index);
}
return null;
}
/**
* Gets the index of the specified child node.
*
* @param parent the parent node
* @param child the child node
* @return the index of the child
*/
public int getIndexOfChild(Object parent, Object child) {
if(parent instanceof File) {
File parentFile = (File) parent;
if(parentFile.getName().endsWith(".jar")) { //$NON-NLS-1$
JarNode[] nodes = getJarNodes(parentFile);
if(nodes==null) {
return -1;
}
for(int i = 0; i<nodes.length; i++) {
if(nodes[i].equals(child)) {
return i;
}
}
}
String[] children = ((File) parent).list();
if(children==null) {
return -1;
}
String childname = ((File) child).getName();
for(int i = 0; i<children.length; i++) {
if(childname.equals(children[i])) {
return i;
}
}
} else if(parent instanceof JarNode) {
JarNode treeNode = (JarNode) parent;
return treeNode.getIndex((JarNode) child);
}
return -1;
}
// methods required by TreeModel
// these methods are empty since this is not an editable model
public void valueForPathChanged(TreePath path, Object newvalue) {
/** empty method */
}
public void addTreeModelListener(TreeModelListener l) {
/** empty method */
}
public void removeTreeModelListener(TreeModelListener l) {
/** empty method */
}
/**
* Gets a child node with a given name. Parent and child may be a
* File or JarNode.
*
* @param parent the parent node
* @param name the name
* @return the child node
*/
public Object getChild(Object parent, String name) {
if(parent instanceof File) {
File parentFile = (File) parent;
// if parent is the launch jar, return a JarNode
if(parentFile.getName().endsWith(".jar")) { //$NON-NLS-1$
JarNode[] nodes = getJarNodes(parentFile);
if(nodes!=null) {
for(int i = 0; i<nodes.length; i++) {
if(nodes[i].toString().equals(name)) {
return nodes[i];
}
}
}
}
String[] children = parentFile.list();
if(children!=null) {
for(int i = 0; i<children.length; i++) {
if(children[i].toString().equals(name)) {
return new File(parentFile, children[i]) {
public String toString() {
return getName();
}
};
}
}
}
} else if(parent instanceof JarNode) {
JarNode treeNode = (JarNode) parent;
Enumeration<?> e = treeNode.children();
while(e.hasMoreElements()) {
JarNode next = (JarNode) e.nextElement();
if(next.toString().equals(name)) {
return next;
}
}
}
return null;
}
/**
* Returns all descendant paths for a parent path.
* Descendants include the parent path itself.
*
* @param parentPath the parent Object[] path
* @return a collection of descendant Object[] paths
*/
protected Collection<Object[]> getDescendantPaths(Object[] parentPath) {
Collection<Object[]> c = new ArrayList<Object[]>();
c.add(parentPath);
Object parent = parentPath[parentPath.length-1];
int n = getChildCount(parent);
for(int i = 0; i<n; i++) {
Object child = getChild(parent, i);
// construct new path by adding child
Object[] childPath = new Object[parentPath.length+1];
System.arraycopy(parentPath, 0, childPath, 0, parentPath.length);
childPath[parentPath.length] = child;
Collection<Object[]> childPaths = getDescendantPaths(childPath);
c.addAll(childPaths);
}
return c;
}
// JarNode class represents a compressed file in a jar
class JarNode extends DefaultMutableTreeNode {
String name;
/**
* Constructor JarNode
* @param path
*/
public JarNode(String path) {
name = XML.getName(path);
if(name.equals("")) { //$NON-NLS-1$
name = XML.getName(path.substring(0, path.length()-1));
}
}
public String toString() {
return name;
}
}
// returns the JarNode associated with a path in a given jar file
public JarNode getJarNode(File jarFile, String path) {
Map<String, JarNode> pathMap = pathMaps.get(jarFile);
if(pathMap==null) {
readJar(jarFile);
pathMap = pathMaps.get(jarFile);
}
return pathMap.get(path);
}
// returns the top-level JarNodes in the specified jar
public JarNode[] getJarNodes(File jarFile) {
JarNode[] array = topLevelNodeArrays.get(jarFile);
if(array==null) {
readJar(jarFile);
array = topLevelNodeArrays.get(jarFile);
}
return array;
}
// reads the specified jar
private void readJar(File jarFile) {
// get all jar entries
Collection<String> entries = getJarEntries(jarFile);
ArrayList<JarNode> topLevelNodes = new ArrayList<JarNode>();
Map<String, JarNode> nodes = new HashMap<String, JarNode>(); // maps String to JarNode
for(Iterator<String> it = entries.iterator(); it.hasNext(); ) {
String path = XML.forwardSlash(it.next().toString());
// don't include meta-inf files
if(path.startsWith("META-INF")) { //$NON-NLS-1$
continue;
}
// for each path, make and connect JarNodes in the path
JarNode parent = null;
String parentPath = ""; //$NON-NLS-1$
while(path!=null) {
int n = path.indexOf("/"); //$NON-NLS-1$
if(n>-1) { // found directory
parentPath += path.substring(0, n+1);
JarNode node = nodes.get(parentPath);
if(node==null) {
node = new JarNode(parentPath);
nodes.put(parentPath, node);
if(parent!=null) {
parent.add(node);
} else { // this is a top level node
topLevelNodes.add(node);
}
}
path = path.substring(n+1);
parent = node;
} else { // found file
path = parentPath+path;
JarNode node = nodes.get(path);
if(node==null) {
node = new JarNode(path);
nodes.put(path, node);
if(parent!=null) {
parent.add(node);
} else { // this is a top level node
topLevelNodes.add(node);
}
}
path = null;
}
}
}
JarNode[] array = topLevelNodes.toArray(new JarNode[0]);
topLevelNodeArrays.put(jarFile, array);
pathMaps.put(jarFile, nodes);
}
// returns all JarEntries in the specified jar
private Collection<String> getJarEntries(File jarFile) {
// create a JarFile
JarFile jar = null;
try {
jar = new JarFile(jarFile);
} catch(Exception ex) {
ex.printStackTrace();
}
if(jar!=null) {
TreeSet<String> entries = new TreeSet<String>();
for(Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
entries.add(entry.getName());
}
try {
jar.close();
} catch (IOException e) {
//e.printStackTrace();
}
return entries;
}
return null;
}
}
/*
* Open Source Physics software is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
*
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be
* released under the GNU GPL license.
*
* This software 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.
*
* You should have received a copy of the GNU General Public License along with
* this; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston MA 02111-1307 USA or view the license online at
* http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/