HadoopIntellijPlugin插件HDFS文件系统浏览器设计和实现1

  本节说明文件系统树节点的设计与实现。IntelliJ Plugin for Hadoop 插件 在开发和设计上,很大程度上借鉴了DB Navigator插件的设计和实现。 DB Navigator 是一款基于 IntelliJ IDEA 数据库管理的优秀插件,该插件目前好像是开源的,地址在https://plugins.jetbrains.com/plugin/1800-database-navigator 官方插件库,提供的源码不是最新的(源码无法编译),但可以下载下来进行参考。
  文件系统对象,在界面上使用树状结构进行展示,是最好的选择。可以使用 javax.swing.tree 控件进行展示。IDEA 本身也有一套扩展的Tree,也是继承了javax.swing.tree的控件,不过在界面主题上或者节点的事件处理上做了一次封装,因此我们树的主控件,将会继承IDEA的Tree。后续我们再讨论其具体的实现方式。本节我们来讨论一下 树控件节点Node的设计。那么这里涉及到 树节点Node的接口定义、节点模型类设计(Tree 控件使用 TreeModel 类操作相关节点)、节点Node的相关事件监听对象设计、加载数据等待的显示节点设计、树的展示方式设计等。 文件系统树节点的整体设计类图如下:

树节点的FileSystemBrowserTreeNode接口定义和其抽象实现

  FileSystemBrowserTreeNode 继承TreeNode 接口,主要功能包括初始化树的相关元素、获取节点的深度、节点是否可以展开、树节点是否已经加载、构建该节点的子节点、获取该节点的父节点、刷新节点、获取节点图标、获取节点文本(展示使用)等等。另外节点可以进行导航设置(跟踪节点浏览的位置),因此需要继承 NavigationItem, ItemPresentation 接口实现导航功能。鼠标悬停,会进行节点内容的相关提示,因此还要继承 自定义 工具提示 接口 ToolTipProvider。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 定义HDFS目录树节点接口<p>
* Created by fangyuzhong on 17-7-15.
*/
public interface FileSystemBrowserTreeNode
extends TreeNode, NavigationItem, ItemPresentation, ToolTipProvider,GenericFileSystemElement
{
/**
* 初始化树的元素
*/
void initTreeElement();
/**
* 是否展开
* @return
*/
boolean canExpand();
/**
* 获取树的深度
* @return
*/
int getTreeDepth();
/**
* 树结构是否加载
* @return
*/
boolean isTreeStructureLoaded();
/**
* 或节点的子节点
* @return
*/
List<? extends FileSystemBrowserTreeNode> getChildren();
/**
* 刷新子节点
* @param
*/
void refreshTreeChildren();
/**
* 重构该节点的子节点
*/
void rebuildTreeChildren();
/**
* 获取节点图标
* @param paramInt
* @return
*/
Icon getIcon(int paramInt);
/**
*获取节点文本
* @return
*/
String getPresentableText();
/**
* 获取节点文本详细信息
* @return
*/
String getPresentableTextDetails();
/**
*
* @return
*/
String getPresentableTextConditionalDetails();
/**
* 获取子节点
* @param paramInt
* @return
*/
FileSystemBrowserTreeNode getChildAt(int paramInt);
/**
* 获取父节点
* @return
*/
FileSystemBrowserTreeNode getParent();

/**
* 获取节点所在的索引
* @param paramBrowserTreeNode
* @return
*/
int getIndex(FileSystemBrowserTreeNode paramBrowserTreeNode);
}

  该接口,有个抽象的实现,在其抽象实现中,有个重要的方法 getLocationString(),该方法表示节点所在的路径字符串,在具体的节点实现类中需要重写该方法。抽象类 FileSystemBrowserTreeNodeBase 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Created by fangyuzhong on 17-7-15.
*/
public abstract class FileSystemBrowserTreeNodeBase extends DisposableBase
implements FileSystemBrowserTreeNode
{
/**
* 获取当前位置字符串
* @return
*/
@Nullable
public String getLocationString()
{
return null;
}
/**
* 获取子对象枚举
* @return
*/
public Enumeration children()
{
return Collections.enumeration(getChildren());
}
/**
* 获取子节点所在的索引
* @param child
* @return
*/
public int getIndex(TreeNode child)
{
return getIndex((FileSystemBrowserTreeNode) child);
}
public boolean getAllowsChildren()
{
return !isLeaf();
}
}

树节点Node的事件监听接口的设计和实现

  树节点Node 事件监听器BrowserTreeEventListener接口,定义了Node 改变、选中改变触发的相关方法,继承了 EventListener 接口。IDEA 框架本身定义了一套事件或者消息通知机制,因此借助IDEA来实现事件消息通知。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Created by fangyuzhong on 17-7-16.
*/
public interface BrowserTreeEventListener
extends EventListener
{
/**
* 通知消息类型
*/
final Topic<BrowserTreeEventListener> TOPIC = Topic.create("Browser tree event", BrowserTreeEventListener.class);

/**
* 树节点Node改变事件触发的方法
* @param paramBrowserTreeNode Node接口
* @param paramTreeEventType 事件类型枚举
*/
void nodeChanged(FileSystemBrowserTreeNode paramBrowserTreeNode, TreeEventType paramTreeEventType);

/**
* 树节点Node选中改变事件触发的方法
*/
void selectionChanged();
}

BrowserTreeEventAdapter 类为其事件监听处理的抽象实现,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Created by fangyuzhong on 17-7-16.
*/
public abstract class BrowserTreeEventAdapter
implements BrowserTreeEventListener
{
public void nodeChanged(FileSystemBrowserTreeNode node, TreeEventType eventType)
{
}
public void selectionChanged()
{
}
}

读取文件系统对象后,在Tree上进行展示

  读取文件系统对象后,在Tree上进行展示或者在读取文件系统对象过程中,数据量多的时候,需要显示正在加载的这样一个树节点,以确保良好的用户体验。IDEA本身在工程加载的时候,也有这样的特点。因此要实现一个正在等待加载的节点,实现 FIleSystemBrowTreeNode 接口即可,比较简单,这里就不再贴代码了。

树节点Node 的模型抽象类及其具体实现的设计和实现

  Tree的控件,不支持直接操作Node,需要使用TreeModel 来操作节点。由于可能存在多个连接,连接到HDFS,因此会存在多个文件系统Tree,如何展示,是以Tab列表的方式还是以单个Tree的方式展示,用户是可选择的。因此需要实现两个TreeModel。如果用户使用Tab列表展示,那就使用 TabbedBrowserTreeModel 进行操作,如果选择单个Tree进行展示,那就使用SimpleBrowserTreeModel进行操作,因此需要对TreeModel进行抽象设计。

抽象类BrowserTreeModel 实现了TreeModel接口。

  抽象类BrowserTreeModel 实现了TreeModel接口,该类中处理以下几个方面:
  ①、注册通知BrowserTreeEventListener事件,一当接收到需要更新Node,触发通知进行Node的更新操作
  ②、注册(显示)等待加载的Node对象。多线程进行操作
  ③、实现TreeModel接口必须实现的方法
  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
* 定义树节点模型TreeModel抽象类
* Created by fangyuzhong on 17-7-16.
*/
public abstract class BrowserTreeModel
implements TreeModel, Disposable
{
private Set<TreeModelListener> treeModelListeners = new HashSet();
private FileSystemBrowserTreeNode root;
private boolean isDisposed = false;
private final Set<LoadInProgressTreeNode> loadInProgressNodes = new THashSet();
/**
* 初始化 树节点模型
* @param root
*/
protected BrowserTreeModel(FileSystemBrowserTreeNode root)
{
this.root = root;
EventUtil.subscribe(root.getProject(), this, BrowserTreeEventListener.TOPIC, this.browserTreeEventListener);
}
/**
* 注册 Node 模型监听事件
* @param listener
*/
public void addTreeModelListener(TreeModelListener listener)
{
this.treeModelListeners.add(listener);
}
/**
* 移除Node模型监听事件
* @param listener
*/
public void removeTreeModelListener(TreeModelListener listener)
{
this.treeModelListeners.remove(listener);
}

/**
* 通知节点修改了,需要处理相关事件
* @param treeNode 树节点
* @param eventType 事件类型
*/
public void notifyListeners(FileSystemBrowserTreeNode treeNode, TreeEventType eventType)
{
if ((FailsafeUtil.softCheck(this)) && (FailsafeUtil.softCheck(treeNode)))
{
TreePath treePath = FileSystemBrowserUtils.createTreePath(treeNode);
//通知节点修改,按照事件类型,重新处理
TreeUtil.notifyTreeModelListeners(this, this.treeModelListeners, treePath, eventType);
}
}
/**
* 获取当前的Project
* @return
*/
public Project getProject()
{
return getRoot().getProject();
}
/**
* 检查是否包含指定的Node
* @param paramBrowserTreeNode
* @return
*/
public abstract boolean contains(FileSystemBrowserTreeNode paramBrowserTreeNode);

/**
* 注册加载显示节点(使用定时器多线程加载)
* @param node
*/
private void registerLoadInProgressNode(LoadInProgressTreeNode node)
{
synchronized (this.loadInProgressNodes)
{
boolean startTimer = this.loadInProgressNodes.size() == 0;
this.loadInProgressNodes.add(node);
if (startTimer)
{
Timer reloader = new Timer("Hadoop Browser (load in progress reload timer)");
reloader.schedule(new LoadInProgressRefreshTask(), 0L, 50L);
}
}
}
/**
* 加载等待显示节点的线程类
*/
private class LoadInProgressRefreshTask
extends TimerTask
{
int iterations = 0;

private LoadInProgressRefreshTask()
{
}

public void run()
{
synchronized (loadInProgressNodes)
{
Iterator<LoadInProgressTreeNode> loadInProgressNodesIterator =loadInProgressNodes.iterator();
while (loadInProgressNodesIterator.hasNext())
{
LoadInProgressTreeNode loadInProgressTreeNode = loadInProgressNodesIterator.next();
try
{
if (loadInProgressTreeNode.isDisposed())
{
loadInProgressNodesIterator.remove();
} else
{
notifyListeners(loadInProgressTreeNode, TreeEventType.NODES_CHANGED);
}
} catch (ProcessCanceledException e)
{
loadInProgressNodesIterator.remove();
}
}
if (loadInProgressNodes.isEmpty())
{
cancel();
}
}
this.iterations += 1;
}
}
/**
* 获取根节点
* @return
*/
public FileSystemBrowserTreeNode getRoot()
{
return FailsafeUtil.get(this.root);
}
/**
* 获取父节点的子节点
* @param parent
* @param index
* @return
*/
public Object getChild(Object parent, int index)
{
FileSystemBrowserTreeNode treeChild = ((FileSystemBrowserTreeNode) parent).getChildAt(index);
if ((treeChild instanceof LoadInProgressTreeNode))
{
registerLoadInProgressNode((LoadInProgressTreeNode) treeChild);
}
return treeChild;
}
/**
* 获取父节点的子节点的数量
* @param parent
* @return
*/
public int getChildCount(Object parent)
{
return ((FileSystemBrowserTreeNode) parent).getChildCount();
}
/**
* 判断节点是否是叶子节点
* @param node
* @return
*/
public boolean isLeaf(Object node)
{
return ((FileSystemBrowserTreeNode) node).isLeaf();
}
/**
* 获取子节点所在的索引
* @param parent
* @param child
* @return
*/
public int getIndexOfChild(Object parent, Object child)
{
return ((FileSystemBrowserTreeNode) parent).getIndex((FileSystemBrowserTreeNode) child);
}
public void valueForPathChanged(TreePath path, Object newValue)
{
}
/**
*
*/
public void dispose()
{
if (!this.isDisposed)
{
this.isDisposed = true;
this.treeModelListeners.clear();
this.loadInProgressNodes.clear();
this.root = null;
}
}
/**
* 树节点事件监听处理
*/
private BrowserTreeEventListener browserTreeEventListener = new BrowserTreeEventAdapter()
{
public void nodeChanged(FileSystemBrowserTreeNode node, TreeEventType eventType)
{
if (contains(node))
{
notifyListeners(node, eventType);
}
}
};
}

TreeModel的具体实现

  说完了TreeModel的抽象类,下面说说TreeModel的具体实现。TreeModel的具体实现有两种,TabbedBrowserTreeModel 和 SimpleBrowserTreeModel。当用户选择文件系统树以Tab列表的方式展示时,树的Node操作使用的是 TabbedBrowserTreeModel,当用户选择以单个树展示时,树的Node操作使用SimpleBrowserTreeModel。
TabbedBrowserTreeModel 类,继承抽象类BrowserTreeModel。构造函数中使用 文件系统连接对象ConnectionHandler 作为参数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
*文件系统树以Tab列表的方式展示时,树的Node操作使用的是 TabbedBrowserTreeModel
* Created by fangyuzhong on 17-7-16.
*/
public class TabbedBrowserTreeModel
extends BrowserTreeModel
{
/**
* 初始化TreeModel
* @param connectionHandler 文件系统连接处理接口
*/
public TabbedBrowserTreeModel(ConnectionHandler connectionHandler)
{
super(connectionHandler.getObjectBundle());
}
/**
* 判断是否包含指定的节点对象
* @param node 指定的TreeNode
* @return
*/
public boolean contains(FileSystemBrowserTreeNode node)
{
return getConnectionHandler() == node.getConnectionHandler();
}
/**
* 获取文件连接处理对象
* @return
*/
public ConnectionHandler getConnectionHandler()
{
return getRoot().getConnectionHandler();
}
}

  SimpleBrowserTreeModel ,继承抽象类BrowserTreeModel ,有两个构造函数,一个是无参构造,另一个传入 工程Project对象和文件系统连接集合对象(作为树的根节点),代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 定义简单的TreeModel的实现
* Created by fangyuzhong on 17-7-16.
*/

public class SimpleBrowserTreeModel
extends BrowserTreeModel
{
/**
* 初始化
*/
public SimpleBrowserTreeModel()
{
this(FailsafeUtil.DUMMY_PROJECT, null);
}
/**
* 初始化
* @param project
* @param connectionBundle
*/
public SimpleBrowserTreeModel(Project project, ConnectionBundle connectionBundle)
{
super(new SimpleBrowserTreeRoot(project, connectionBundle));
}
/**
* 判断是否包含指定的TreeNode
* @param node
* @return
*/
public boolean contains(FileSystemBrowserTreeNode node)
{
return true;
}
/**
*
*/
public void dispose()
{
super.dispose();
}
}

总结

  文件系统树节点的设计关系到整个文件系统对象的是如何展示的,定义文件树Tree的TreeNode接口,并实现了等待加载对象的等待节点TreeNode的实现。抽象了TreeNode的操作TreeModel。下节将结合本节的TreeNode和TreeModel ,实现Tree的开发。