磁盘文件、XML文档和SQL中的表(有一定要求)都可以生成目录树。
此代码可以任意使用修改,如有修改、建议或发现错误,请发信到flyxxxxx@163.com,谢谢。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
/**
* 目录树. <br/>
* 提供由其它对象生成目录树的支持。<br/>
* 如磁盘目录:Tree t=Tree.getTree(new java.io.File("D:/"),new FileContainer());<br/>
* XML文件:Document doc=...<br/>
* Tree t=Tree.getTree(doc,new DocumentContainer());<br/>
* 如果有File或Document以外的对象要生成目录树,请实现{@link TreeNode}或{@link Container}接口。
* 对于SQL中的表,建议字段要求id(标识)、parentId(上级目录标识),其它字段自定,然后对查询到的每一
* 条记录生成一个实现TreeNode接口的对象。
* 如果实现了TreeNode接口:<br/>
* class Group implements TreeNode{}<br/>
* Group[] groups=...<br/>
* Tree t=Tree.getTree(groups,0);//其中0为根结点的ID<br/>,并且groups不要求是同一对象,只要它们都实现了
* 接口TreeNode,并且ID不同,这样可以在生成目录树的过程中根据对象类型自行处理。
* <p>Copyright: Copyright (c) 2004</p>
* <p> </p>
* @author flyxxxxx
* @version 1.0
*/
final public class Tree
extends Node
{
/**
* 结点最大标识
*/
private static int maxId = 0;
private Tree()
{
super(getMaxId(), null);
}
private Tree(int id)
{
super(id, null);
maxId = id++;
}
private static int getMaxId()
{
return maxId++;
}
/**
* 创建空目录树.
* @return Tree 空目录树
*/
public static Tree getTree()
{
return new Tree();
}
/**
* 向目录树添加一个结点.
* 目录树中所有结点的类型最好是相同的。
* @param parent Node 父结点
* @param value Object 结点的值
* @return Node 添加的结点
*/
public Node addNode(Node parent, Object value)
{
Node rs = new Node(getMaxId(), parent);
rs.setValue(value);
return rs;
}
/**
* 创建目录树.
* 如果对象实现了接口{@link Container},可以通过此方法加入目录树。<br/>
* 通过此方法创建的目录树,所有结点的ID由系统生成。obj将直接做为根结点,对根结点调用方法
* {@link Node#getValue()}将得到obj。<br/>
* @param obj Object 目录树的根结点
* @param container Container 得到对象的子对象的接口
* @return Tree 目录树
*/
public static Tree getTree(Object obj, Container container)
{
Tree rs = new Tree();
rs.setValue(obj);
Object[] o = container.getChilds(obj);
for (int i = 0; i < o.length; i++)
{
addNode(rs, o[i], container);
}
return rs;
}
private static void addNode(Node n, Object obj, Container container)
{
Node node = new Node(getMaxId(), n);
node.setValue(obj);
Object[] o = container.getChilds(obj);
for (int i = 0; i < o.length; i++)
{
addNode(node, o[i], container);
}
}
/**
* 创建目录树.
* 如果对象实现了接口{@link Container},可以通过此方法加入目录树。<br/>
* 通过此方法创建的目录树,所有结点的ID由系统生成,对根结点调用方法{@link Node#getValue()}
* 将得到null。<br/>
* obj数组中的每一个,将直接做为根结点的直接子结点。
* @param obj Object 目录树的根结点的直接子结点.
* @param container Container 得到对象的子对象的接口
* @return Tree 目录树
*/
public static Tree getTree(Object obj[], Container container)
{
Tree rs = new Tree();
for (int i = 0; i < obj.length; i++)
{
addNode(rs, obj[i], container);
}
return rs;
}
/**
* 创建目录树.
* 只要一组对象实现接口{@link TreeNode},并且每个对象的ID不同,就可以将它们加入目录树。<br/>
* 通过此方法得到的目录树,它的根结点ID值为rootId,其它结点的值为实现接口TreeNode的对象的ID。<br/>
* 如果treeNode中包含了根结点,根结点的值可以通过方法{@link Node#getValue()}得到,返之得到的是null。<br/>
* treeNode可以没有顺序,但父结点的ID一定大于子结点的。
* @param treeNode TreeNode[] 构成目录树的结点
* @param rootId int 根结点的ID
* @return Tree 创建目录树
*/
public static Tree getTree(TreeNode[] treeNode, int rootId)
{
Tree rs = new Tree(rootId);
ArrayList list = new ArrayList();
for (int i = 0; i < treeNode.length; i++)
{
list.add(treeNode[i]);
}
Collections.sort(list, new Compare()); //排序
Node last = rs;
for (int i = 0; i < treeNode.length; i++)
{
TreeNode tnode = (TreeNode) list.get(i);
if (i == 0 && tnode.getId() == rootId)
{ //是否根结点
rs.setValue(tnode);
}
else
{
Node parent = null; //寻找父结点
if ( ( (TreeNode) last.getValue()).getId() == tnode.getParentId())
{
parent = last;
}
else
{
parent = rs.getNode(tnode.getParentId());
}
if (parent == null)
{ //未找到
throw new NullPointerException("Node " + tnode.getParentId() +
" not found.");
}
else
{ //找到
Node n = new Node(tnode.getId(), parent);
n.setValue(tnode);
last = parent;
}
}
}
return rs;
}
/**
* 从目录树中查找标识为id的结点.
* @param id String 结点标识
* @return Node 标识为id的结点(未找到返回null)
*/
public Node getNode(int id)
{
if (id == getId())
{
return this;
}
return getNode(getChilds(), id);
}
private static Node getNode(Iterator it, int id)
{ //查找结点
while (it.hasNext())
{
Node n = (Node) it.next();
if (n.getId() == id)
{
return n;
}
if (n.getChildsNumber() > 0)
{
n = getNode(n.getChilds(), id);
if (n != null)
{
return n;
}
}
}
return null;
}
/**
* 对目录树进行排序
* @param com Comparator 排序接口
*/
public void sort(Comparator com)
{
sort(childs, com);
}
private void sort(ArrayList childs, Comparator com)
{ //对子结点排序
Collections.sort(childs, com);
for (int i = 0; i < childs.size(); i++)
{
Node n = (Node) childs.get(i);
if (n.getChildsNumber() > 1)
{
sort(n.childs, com);
}
}
}
/**
* 得到满足条件的结点列表.
* @param filter NodeFilter 结点过滤器
* @return Iterator 结点列表(存储Node对象)
*/
public Iterator getNodeList(NodeFilter filter)
{
ArrayList rs = new ArrayList();
getNodeList(childs, filter, rs);
return rs.iterator();
}
private void getNodeList(ArrayList childs, NodeFilter filter,
ArrayList rs)
{ //检索满足条件的结点
for (int i = 0; i < childs.size(); i++)
{
Node n = (Node) childs.get(i);
if (filter.accept(n))
{
rs.add(n);
}
if (n.hasChilds())
{
getNodeList(n.childs, filter, rs);
}
}
}
}
class Compare
implements Comparator //对结点按ID排序
{
public Compare()
{}
public int compare(Object obj1, Object obj2)
{
int id1 = ( (TreeNode) obj1).getId();
int id2 = ( (TreeNode) obj2).getId();
return id1 - id2;
}
}
import java.util.ArrayList;
import java.util.Iterator;
/**
* 目录树的一个结点. <br/>
* 它的主要属性有结点标识、父结点、它在目录树中的层次(根结点为0)、结点的值、子结点。
* <p>Copyright: Copyright (c) 2004</p>
* @author flyxxxxx
* @version 1.0
*/
public class Node
{
private int id;
private Node parent;
private int level;
private Object value;
protected ArrayList childs = new ArrayList();
/**
* 构造方法
* @param id int 结点ID
* @param parent Node 父结点
*/
Node(int id, Node parent)
{
this.id = id;
if (parent != null)
{
this.parent = parent;
parent.childs.add(this);
this.level = parent.getLevel() + 1;
}
else
{
level = 0;
}
}
/**
* 得到结点ID.
* @return int 结点ID
*/
public int getId()
{
return id;
}
/**
* 得到结点在目录树中的层次.
* 其中根结点为0,根结点的子结点为1,依次类推
* @return int 结点在目录树中的层次
*/
final public int getLevel()
{
return level;
}
/**
* 得到结点的值.
* 也就是TreeNode接口所引用的对象
* @return Object 结点的值
*/
final public Object getValue()
{
return value;
}
/**
* 设定结点的值.
*/
final void setValue(Object value)
{
this.value = value;
}
/**
* 得到子结点列表.
* Iterator中存储的是Node对象
* @return Iterator 子结点列表
*/
final public Iterator getChilds()
{
return childs.iterator();
}
/**
* 得到子结点数量.
* @return int 子结点数量
*/
final public int getChildsNumber()
{
return childs.size();
}
/**
* 是否有子结点.
* @return boolean 有子结点返回true
*/
final public boolean hasChilds()
{
return childs.size() > 0;
}
/**
* 得到父结点.
* 如果结点为根结点,返回null
* @return Node 父结点
*/
final public Node getParent()
{
return parent;
}
/**
* 得到第level级父结点.
* @param level int 父结点的层次(level大于等于0,小于此结点的层次)
* @return Node 第level级父结点
*/
final public Node getParent(int level)
{
if (level < 0 || level >= this.level)
{
throw new ArrayIndexOutOfBoundsException("level is error.");
}
Node n = parent;
for (int i = 1; i < level; i++)
{
n = n.getParent();
}
return n;
}
/**
* 得到结点在同级结点的相对位置.
* @return int 结点在同级结点的相对位置
*/
final public int getPosition()
{
if (parent == null)
{
return 0;
}
return parent.childs.indexOf(this);
}
/**
* 结点是否是同级结点的最后一个.
* @return boolean 是返回true
*/
final public boolean isLast()
{
if (parent == null)
{
return true;
}
return getPosition() == parent.childs.size() - 1;
}
/**
* 结点是否同级结点的第一个.
* @return boolean 是返回true
*/
final public boolean isFirst()
{
return getPosition() == 0;
}
/**
* 得到目录树中下一个结点.
* 如果此结点是目录树最后一个结点则返回null
* @return Node 下一个结点
*/
final public Node getNext()
{
if (childs.size() > 0)
{
return (Node) childs.get(0);
}
Node n = parent;
while (n != null)
{
Node node = n.getNextSibling();
if (node != null)
{
return node;
}
n = n.getParent();
}
return null;
}
/**
* 得到下一个同级结点.
* 没有下一个同级结点返回null
* @return Node 下一个同级结点
*/
final public Node getNextSibling()
{
if (parent == null)
{
return null;
}
int k = getPosition();
if (k == parent.getChildsNumber() - 1)
{
return null;
}
return (Node) parent.childs.get(k + 1);
}
/**
* 得到前一个同级结点.
* 没有前一个同级结点返回null
* @return Node 前一个同级结点
*/
final public Node getPreviousSibling()
{
int k = getPosition();
if (k == 0)
{
return null;
}
return (Node) parent.childs.get(k - 1);
}
/**
* 得到前一个结点.
* 根结点的前一个结点为null
* @return Node 前一个结点
*/
final public Node getPrevious()
{
Node n = getPreviousSibling();
if (n != null)
{
return n;
}
return parent;
}
}
/**
* 结点过滤接口. <br/>
* 此接口用于从目录树中查找符合一定条件的结点。<br/>
* <p>Copyright: Copyright (c) 2004</p>
* @author flyxxxxx
* @version 1.0
*/
public interface NodeFilter
{
/**
* 判断结点是否满足一定条件.
* @param n Node 要判断的结点
* @return boolean 满足条件返回true
*/
public boolean accept(Node n);
}
/**
* 目录树的一个结点. <br/>
* 要将一组对象转化为目录树,它必须实现此接口或{@link Container}接口。<br/>
* 通过实现此接口的目录树,目录树的每个结点的标识等于实现此接口的相应对象的标识。<br/>
* 一个结点最重要的属性是它的标识和它上级目录的标识。<br/>
* <p>Copyright: Copyright (c) 2004</p>
* @author flyxxxxx
* @version 1.0
*/
public interface TreeNode
{
/**
* 得到此结点的标识
* @return String 结点的标识
*/
public int getId();
/**
* 得到父结点的标识
* @return String 父结点的标识
*/
public int getParentId();
}
/**
* 容器接口. <br/>
* 要将一组或一个对象转化为目录树,它必须实现此接口或{@link TreeNode}接口。<br/>
* 容器能够包含子结点,它通过方法{@link #getChilds(java.lang.Object)}得到一个对象的子结点。<br/>
* 目录树中的每一个结点的值将引用这个容器所指向的对象,也就是说:通过调用方法
* {@link Node#getValue()}将得到这个对象。<br/>
* 如:Tree t=Tree.getTree(new File("D:/"),new FileContainer());<br/>
* 在t中的每个结点,对它调用方法{@link Node#getValue()}都将得到一个File对象。<br/>
* <p>Copyright: Copyright (c) 2004</p>
* <p> </p>
* @author flyxxxxx
* @version 1.0
*/
public interface Container
{
/**
* 得到将对象的所有子对象.
* @param obj Object 父对象
* @return Object[] 子对象列表
*/
public Object[] getChilds(Object obj);
}
import java.util.ArrayList;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
/**
* XML文档容器. <br/>
* 将一个XML文件转化为目录树,此目录树中的每个结点将对应XML文件中的一个Element,也就是Document
* 结点对应目录树的根结点,依次类推。<br/>
* 目录树的每个结点的值({@link Node#getValue()}均为Element,结点的ID由系统产生,根结点为0。<br/>
* 使用方法:Tree t=new Tree(Document document,new DocumentContainer());<br/>
* 其中document为XML文档元素。<br/>
* <p>Copyright: Copyright (c) 2004</p>
* <p> </p>
* @author flyxxxxx
* @version 1.0
*/
final public class DocumentContainer
implements Container
{
/**
* XML文档容器构造方法
*/
public DocumentContainer()
{
}
/**
* 得到将对象的所有子对象.
* @param obj Object 父对象(类型Element)
* @return Object[] 子对象列表(类型Element[])
*/
public Object[] getChilds(Object obj)
{
if (obj instanceof Element)
{
ArrayList rs = new ArrayList();
NodeList list = ( (Element) obj).getChildNodes();
for (int i = 0; i < list.getLength(); i++)
{
Node n = list.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE)
{
rs.add(n);
}
}
return rs.toArray();
}
throw new IllegalArgumentException(
"Required param type is org.w3c.dom.Element.");
}
}
import java.io.FileFilter;
import java.io.File;
/**
* 文件容器. <br/>
* 将一个磁盘目录转化为目录树,此目录树中的每个结点将对应磁盘中的一个目录或文件。<br/>
* 目录树的每个结点的值({@link Node#getValue()}均为File,结点的ID由系统产生,根结点为0。<br/>
* 使用方法:Tree t=new Tree(File f,new FileContainer());<br/>
* <p>Copyright: Copyright (c) 2004</p>
* <p> </p>
* @author flyxxxxx
* @version 1.0
*/
final public class FileContainer
implements Container
{
private FileFilter filter;
/**
* 默认容器构造方法
*/
public FileContainer()
{
}
/**
* 带文件过滤器的构造方法.
* 通过此方法,将目录树中不会有不满足条件的目录或文件
* @param filter FileFilter 文件过滤器
*/
public FileContainer(FileFilter filter)
{
this.filter = filter;
}
/**
* 得到将对象的所有子对象.
* @param obj Object 父对象(类型File)
* @return Object[] 子对象列表(类型File[])
*/
public Object[] getChilds(Object obj)
{
if (obj instanceof File)
{
File f = (File) obj;
if (f.isFile())
{
return new Object[0];
}
if (filter == null)
{
return f.listFiles();
}
else
{
return f.listFiles(filter);
}
}
throw new IllegalArgumentException("Required param type is java.io.File.");
}
}
JSP部分参考
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="..."%>//未导入包
<%!
static Hashtable images=new Hashtable();
static Hashtable actions=new Hashtable();
static String script;
static {
images.put("IMAGE_PLUS", "images/plus.gif");
images.put("IMAGE_PLUS_LAST", "images/pluslast.gif");
images.put("IMAGE_MINUS", "images/minus.gif");
images.put("IMAGE_MINUS_LAST", "images/minuslast.gif");
images.put("IMAGE_MIDBLK", "images/midblk.gif");
images.put("IMAGE_BLANK", "images/blank.gif");
images.put("IMAGE_LASTBLK", "images/lastblk.gif");
images.put("IMAGE_LINE", "images/line.gif");
images.put("IMAGE_FOLDER", "images/folder.gif");
images.put("IMAGES_FOLDER_OPEN","images/folderopen.gif");
StringBuffer sc=new StringBuffer("<script type=/"text/javascript/">/r/n");
Iterator imgs=images.values().iterator();
int k=0;
while(imgs.hasNext()){
sc.append("var image"+k+"=new Image();/r/n");
sc.append("image"+k+".src=/""+(String)imgs.next()+"/";/r/n");
k++;
}
sc.append("</script>/r/n");
script=sc.toString();
actions.put("CLICK_FOLDER","clickFolder");
actions.put("CLICK_DOCUMENT","clickDocument");
actions.put("CLICK_FOLDER_IMG","openFolder");
actions.put("MOUSEOVER","overMouse");
actions.put("MOUSEOUT","outMouse");
}
void paintChilds(Iterator childs,Writer w) throws IOException{
while(childs.hasNext()){
paintNode((Node)childs.next(),w);
}
}
void paintNode(Node n,Writer w) throws IOException{
w.write("<table border=/"0/" cellspacing=/"0/" cellpadding=/"0/"><tr>");
int level=n.getLevel();
int id=n.getId();
Node parent=null;
String name=((File)n.getValue()).getName();
boolean last=false;
for(int i=level-1;i>0;i--){
parent=n.getParent(i);
last=parent.isLast();
w.write("<td><img src=/"" +
(String) images.get(last ? "IMAGE_BLANK":"IMAGE_LINE" ) +
"/" border=/"0/"></td>");
}
last=n.isLast();
if(n.hasChilds()){
w.write("<td id=/"plus" + id + "/" style=/"cursor:hand/" onClick=/""+
((String)actions.get("CLICK_FOLDER_IMG")+"(document,"+id+")")+"/"><img src=/"" +
(String) images.get(last ? "IMAGE_PLUS_LAST" : "IMAGE_PLUS") +
"/" border=/"0/"></td>");
}
else {
w.write("<td id=/"plus" + id + "/"><img src=/"" +
(String) images.get(last ? "IMAGE_MINUS_LAST" : "IMAGE_MINUS") +
"/" border=/"0/"></td>");
}
w.write("<td id=/"f" + id + "/"><img src=/"" +
(String) images.get("IMAGE_FOLDER") + "/" border=/"0/"></td>");
if(n.hasChilds()){
w.write("<td id=/"td" + id + "/" style=/"cursor:hand/" onClick=/""+
(String)actions.get("CLICK_FOLDER")+"(document,"+id+"),"+
(String)actions.get("CLICK_FOLDER_IMG")+"(document,"+id+")"+"/">" +
name + "</td>");
}
else{
w.write("<td id=/"td" + id + "/" style=/"cursor:hand/" onClick=/""+
(String)actions.get("CLICK_FOLDER")+"(document,"+id+")"+"/">" +
name + "</td>");
}
w.write("</tr></table>");
if (n.hasChilds()) {
w.write("<div id=/"div" + id + "/" style=/"display:none/">");
paintChilds(n.getChilds(), w);
w.write("</div>");
}
w.flush();
}
%>
<html>
<head>
<title>
tree
</title>
<style type="text/css">
td{font:13px/16px;}
A:link {text-decoration: none;font-size: 12px; color: #0000ff}
A:visited {text-decoration: none;font-size: 12px; color: #0000ff}
A:active {text-decoration: none;font-size: 12px}
A:hover {text-decoration: underline;font-size: 12px}
img{vertical-align: bottom;}
</style>
<%=script%>
<script type="text/javascript">
function changeColor(doc,k){
var old=doc.thisForm.selectedNode.value;
if(old!=k){
if(old!=""){
doc.all("td"+old).style.backgroundColor=doc.thisForm.bgColor.value;
}
doc.all("td"+k).style.backgroundColor=doc.thisForm.selectedColor.value;
doc.thisForm.selectedNode.value=k;
}
}
function clickDocument(doc,k){
changeColor(doc,k);
alert("Click document "+doc.all("td"+k).innerText+".");
}
function openFolder(doc,k){
var o=doc.all("div"+k);
if(o.style.display=="none"){
o.style.display="block";
}
else{
o.style.display="none";
}
replaceImg(doc,k);
var k=0;
while((o=o.parentElement)!=doc&&o!=null){
k=o.id.indexOf("div");
if(k!=-1){
if(o.style.display=="none"){
o.style.display="block";
replaceImg(doc,o.id.substring(k));
}
}
}
}
function clickFolder(doc,k){
changeColor(doc,k);
alert("Click folder "+doc.all("td"+k).innerText+".");
}
function replaceImg(doc,k){
}
</script>
</head>
<body>
<%
out.flush();
File f=new File(this.getServletConfig().getServletContext().getRealPath("/tree")).getParentFile();
Tree t=Tree.getTree(f,new FileContainer());
paintChilds(t.getChilds(),response.getWriter());
%>
<form name="thisForm">
<input type="hidden" name="selectedNode">
<input type="hidden" name="bgColor" value="#FFFFFF">
<input type="hidden" name="selectedColor" value="#9999FF">
</form>
</body>
</html>
致歉:
刚刚帮一网友调试的过程中发现一Bug。
这种情况只在方法参数TreeNode[] treeNode中没有根结点时发生(包含了根结点的不会发生错误)。
类Tree中if ( (TreeNode) last.getValue()).getId() == tnode.getParentId())
这一行应该改为if ( last.getValue()!=null&&( (TreeNode) last.getValue()).getId() == tnode.getParentId())
在此向大家表示歉意,2004-07-21 08:41:00 以前发过的,我会将更正过的发给大家。之后发的将是正确的。