我們通常會在應用中碰到樹形結構的內容,比如 文件夾/文件模型, 部門組織結構,目錄樹等等,通常在設計模式中叫做 compose 模式。
在數據庫中常常這樣表示: 我們以Catalog (分級目錄) 爲例子
Catalog (分級目錄) |
|||
字段名稱 |
字段 |
類型 |
備註 |
目錄ID |
catalog_id |
varchar(36) |
pk, not null |
目錄名稱 |
catalog_name |
varchar(50) |
not null |
父目錄ID |
parent_id |
varchar(36) |
fk |
創建時間 |
create_datetime |
datetime |
not null |
目錄描述 |
description |
varchar(200) |
我們考慮在數據庫中一次將所有數據讀入內存,然後在內存中生成一個Tree,這樣可以減少數據庫的訪問,增加性能,並且只有的數據方式改變的時候,全部重新從數據庫中生成Tree,否則一直保持在內存中。
我們使用標準的DAO模式先生成 POJO類(Catalog)和DAO類(CatalogDAO)。
然後我們建立相對通用的 Tree 和 TreeNode 類。
Tree.java
package
com.humpic.helper.tree;
import
java.util.
*
;
import
org.apache.commons.lang.StringUtils;
import
org.apache.commons.logging.Log;
import
org.apache.commons.logging.LogFactory;
public
abstract
class
Tree {
protected
static
Log log
=
LogFactory.getLog(Tree.
class
);
private
Map treeNodeMaps
=
new
Hashtable();
private
TreeNode root;
/**
* root if it's parent is empty
*/
protected
void
reload(List nodes) {
log.info(
"
tree will start reload all data
"
);
synchronized
(
this
) {
//
initialize
treeNodeMaps.clear();
root
=
null
;
List treeNodes
=
new
Vector(nodes.size());
for
(
int
i
=
0
; i
<
nodes.size(); i
++
) {
TreeNode node
=
this
.transform(nodes.get(i));
//
transform
treeNodes.add(node);
node.setTree(
this
);
treeNodeMaps.put(node.getNodeId(), node);
}
for
(
int
i
=
0
; i
<
treeNodes.size(); i
++
) {
TreeNode node
=
(TreeNode) treeNodes.get(i);
String parentId
=
node.getParentId();
if
(this
.
isRootNode(node)) {
if
(root
==
null
) {
root
=
node;
}
else
{
log.error(
"
find more then one root node. ignore.
"
);
}
}
else
{
TreeNode parent
=
(TreeNode) treeNodeMaps.get(parentId);
if
(parent
!=
null
) {
parent.addChild(node);
node.setParent(parent);
}
else
{
log.warn(
"
node [id=
"
+
node.getNodeId()
+
"
]: missing parent node.
"
);
}
}
}
}
if
(root
==
null
) {
log.error(
"
the root node is not be defined
"
);
}
}
protected
boolean isRootNode(TreeNode node
) {
return
StringUtils.isBlank(node.getParentId()
)
;
}
public
TreeNode getRootNode() {
return
root;
}
public
TreeNode getTreeNode(String nodeId) {
return
(TreeNode) treeNodeMaps.get(nodeId);
}
public
void
addTreeNode(TreeNode node) {
synchronized
(
this
) {
treeNodeMaps.put(node.getNodeId(), node);
String parentId
=
node.getParentId();
if
(StringUtils.isNotBlank(parentId)) {
TreeNode parent
=
getTreeNode(parentId);
if
(parent
!=
null
) {
parent.addChild(node);
node.setParent(parent);
}
else
{
log.error(
"
parent cannot be found:
"
+
node.getParentId());
}
}
else
{
if
(root
==
null
) {
root
=
node;
}
else
{
log.error(
"
find more then one root node. ignore.
"
);
}
}
}
}
public
void
deleteTreeNode(String nodeId) {
synchronized
(
this
) {
TreeNode node
=
getTreeNode(nodeId);
if
(node
==
null
)
throw
new
IllegalArgumentException(nodeId
+
"
cannot be found.
"
);
if
(node.getParent()
==
null
) {
root
=
null
;
treeNodeMaps.clear();
log.warn(
"
the root node has been removed.
"
);
}
else
{
node.getParent().getChildren().remove(node);
treeNodeMaps.remove(nodeId);
List children
=
node.getAllChildren();
for
(
int
i
=
0
; i
<
children.size(); i
++
) {
TreeNode n
=
(TreeNode) children.get(i);
treeNodeMaps.remove(n.getNodeId());
}
}
}
}
/**
* <pre>
* Usage: Office ->
*
* public TreeNode transform(Object info) {
* OfficeInfo office_info = (OfficeInfo) info;
* TreeNode node = new TreeNode();
* node.setNodeId(office_info.getOfficeId());
* node.setParentId(office_info.getParentId());
* node.setBindData(office_info);
* return node;
* }
* </pre>
*/
protected
abstract
TreeNode transform(Object info);
}
TreeNode.java
package
com.humpic.helper
.tree;
import
java.util.List;
import
java.util.Vector;
import
org.apache.commons.collections.Predicate;
import
org.apache.commons.lang.ObjectUtils;
public
class
TreeNode {
private
Tree tree;
private
TreeNode parent;
private
List children
=
new
Vector();
private
List childrenGroup
=
new
Vector();
private
String nodeId;
private
String parentId;
private
Object bindData;
public
String getNodeId() {
return
nodeId;
}
public
void
setNodeId(String nodeId) {
this
.nodeId
=
nodeId;
}
public
String getParentId() {
return
parentId;
}
public
void
setParentId(String parentId) {
this
.parentId
=
parentId;
}
public
Object getBindData() {
return
bindData;
}
public
void
setBindData(Object bindData) {
this
.bindData
=
bindData;
}
public
Tree getTree() {
return
tree;
}
public
void
setTree(Tree tree) {
this
.tree
=
tree;
}
public
void
setParent(TreeNode parent) {
this
.parent
=
parent;
}
public
TreeNode getParent() {
return
this
.parent;
}
public
List getChildren() {
return
this
.children;
}
public
void
addChild(TreeNode node) {
children.add(node);
}
/**
* get all children, and chilren's children
*/
public
List getAllChildren() {
if
(
this
.childrenGroup.isEmpty()) {
synchronized
(
this
.tree) {
for
(
int
i
=
0
; i
<
this
.children.size(); i
++
) {
TreeNode node
=
(TreeNode)
this
.children.get(i);
this
.childrenGroup.add(node);
this
.childrenGroup.addAll(node.getAllChildren());
}
}
}
return
this
.childrenGroup;
}
/**
* get all children, and chilren's children
*/
public
List getAllChildren(Predicate predicate) {
List groups
=
new
Vector();
fillAllChildren(groups, predicate);
return
groups;
}
private
void
fillAllChildren(List groups, Predicate predicate) {
for
(
int
i
=
0
; i
<
this
.children.size(); i
++
) {
TreeNode node
=
(TreeNode)
this
.children.get(i);
if
(predicate.evaluate(node)) {
groups.add(node);
node.fillAllChildren(groups, predicate);
}
}
}
/**
* get all parents, and parent's parent
*/
public
List getParents() {
List results =
new
Vector();
TreeNode parent =
this
.
getParent();
while
(parent !=
null
)
{
results.add(parent);
parent = parent.getParent();
}
return
results;
}
/**
* A.isMyParent(B) == B is A' parent ? <br>
* root.isMyParent(null) == true; <br>
* root.isMyParent(*) == false <br>
* *.isMyParent(null) == false
*/
public
boolean
isMyParent(String nodeId) {
TreeNode target
=
tree.getTreeNode(nodeId);
TreeNode parent
=
this
.getParent();
if
(parent
==
null
) {
return
target
==
null
;
}
else
{
return
parent.equals(target);
}
}
/**
* A.isMyAncestor(B) == B is A' ancestor ? <br>
* *.isMyAncestor(null) == true;
*/
public
boolean
isMyAncestor(String nodeId) {
TreeNode target
=
tree.getTreeNode(nodeId);
if
(target
==
null
)
return
true
;
return
target.getAllChildren().contains(
this
);
}
/**
* A.isMyBrother(B) == B is A' brother ? <br>
* *.isMyBrother(null) == false
*/
public
boolean
isMyBrother(String nodeId) {
TreeNode target
=
tree.getTreeNode(nodeId);
if
(target
==
null
)
return
false
;
TreeNode p1
=
this
.getParent();
TreeNode p2
=
target.getParent();
return
ObjectUtils.equals(p1, p2);
}
}
然後建立業務 Tree
CatalogTree.java
import java.util.List;
import org.apache.commons.collections.Predicate;
public class CatalogTree extends Tree {
private static CatalogTree instance = null ;
private CatalogTree() {}
public static synchronized CatalogTree getInstance() {
if (instance == null ) {
instance = new CatalogTree();
instance.reloadCatalogs();
}
return instance;
}
protected TreeNode transform(Object info) {
Catalog catalog = (Catalog) info;
TreeNode node = new TreeNode();
node.setNodeId(catalog.getCatalogId());
node.setParentId(catalog.getParentId());
node.setBindData(catalog);
return node;
}
public void reloadCatalogs() {
List nodes = CatalogDAO.getInstance().findAll();
super .reload(nodes);
}
public Catalog getCatalogNode(String catalogId) {
TreeNode node = super .getTreeNode(catalogId);
return node == null ? null : (Catalog) node.getBindData();
}
}
最後,我們只要使用以下的語句就可以了:
1. CatalogTree.getInstance().getTreeNode(...)
2. CatalogTree.getInstance().getCatalogNode(...)
3. CatalogTree.getInstance().getRootNode()
然後通過 TreeNode,就可以得到 parent, parents 和 children, allChildren