0%

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

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

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

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

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

1
/**
2
 * Created by fangyuzhong on 17-7-16.
3
 */
4
public  interface BrowserTreeEventListener
5
        extends EventListener
6
{
7
      /**
8
       * 通知消息类型
9
       */
10
      final Topic<BrowserTreeEventListener> TOPIC = Topic.create("Browser tree event", BrowserTreeEventListener.class);
11
12
      /**
13
       * 树节点Node改变事件触发的方法
14
       * @param paramBrowserTreeNode Node接口
15
       * @param paramTreeEventType 事件类型枚举
16
       */
17
      void nodeChanged(FileSystemBrowserTreeNode paramBrowserTreeNode, TreeEventType paramTreeEventType);
18
19
      /**
20
       * 树节点Node选中改变事件触发的方法
21
       */
22
      void selectionChanged();
23
}

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

读取文件系统对象后,在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
 * 定义树节点模型TreeModel抽象类
3
 * Created by fangyuzhong on 17-7-16.
4
 */
5
public abstract class BrowserTreeModel
6
        implements TreeModel, Disposable
7
{
8
    private Set<TreeModelListener> treeModelListeners = new HashSet();
9
    private FileSystemBrowserTreeNode root;
10
    private boolean isDisposed = false;
11
    private final Set<LoadInProgressTreeNode> loadInProgressNodes = new THashSet();
12
    /**
13
     * 初始化 树节点模型
14
     * @param root
15
     */
16
    protected BrowserTreeModel(FileSystemBrowserTreeNode root)
17
    {
18
        this.root = root;
19
        EventUtil.subscribe(root.getProject(), this, BrowserTreeEventListener.TOPIC, this.browserTreeEventListener);
20
    }
21
    /**
22
     * 注册 Node 模型监听事件
23
     * @param listener
24
     */
25
    public void addTreeModelListener(TreeModelListener listener)
26
    {
27
        this.treeModelListeners.add(listener);
28
    }
29
    /**
30
     * 移除Node模型监听事件
31
     * @param listener
32
     */
33
    public void removeTreeModelListener(TreeModelListener listener)
34
    {
35
        this.treeModelListeners.remove(listener);
36
    }
37
38
    /**
39
     * 通知节点修改了,需要处理相关事件
40
     * @param treeNode 树节点
41
     * @param eventType 事件类型
42
     */
43
    public void notifyListeners(FileSystemBrowserTreeNode treeNode, TreeEventType eventType)
44
    {
45
        if ((FailsafeUtil.softCheck(this)) && (FailsafeUtil.softCheck(treeNode)))
46
        {
47
            TreePath treePath = FileSystemBrowserUtils.createTreePath(treeNode);
48
            //通知节点修改,按照事件类型,重新处理
49
            TreeUtil.notifyTreeModelListeners(this, this.treeModelListeners, treePath, eventType);
50
        }
51
    }
52
    /**
53
     * 获取当前的Project
54
     * @return
55
     */
56
    public Project getProject()
57
    {
58
        return getRoot().getProject();
59
    }
60
    /**
61
     * 检查是否包含指定的Node
62
     * @param paramBrowserTreeNode
63
     * @return
64
     */
65
    public abstract boolean contains(FileSystemBrowserTreeNode paramBrowserTreeNode);
66
67
    /**
68
     * 注册加载显示节点(使用定时器多线程加载)
69
     * @param node
70
     */
71
    private void registerLoadInProgressNode(LoadInProgressTreeNode node)
72
    {
73
        synchronized (this.loadInProgressNodes)
74
        {
75
            boolean startTimer = this.loadInProgressNodes.size() == 0;
76
            this.loadInProgressNodes.add(node);
77
            if (startTimer)
78
            {
79
                Timer reloader = new Timer("Hadoop Browser (load in progress reload timer)");
80
                reloader.schedule(new LoadInProgressRefreshTask(), 0L, 50L);
81
            }
82
        }
83
    }
84
    /**
85
     * 加载等待显示节点的线程类
86
     */
87
    private class LoadInProgressRefreshTask
88
            extends TimerTask
89
    {
90
        int iterations = 0;
91
92
        private LoadInProgressRefreshTask()
93
        {
94
        }
95
96
        public void run()
97
        {
98
            synchronized (loadInProgressNodes)
99
            {
100
                Iterator<LoadInProgressTreeNode> loadInProgressNodesIterator =loadInProgressNodes.iterator();
101
                while (loadInProgressNodesIterator.hasNext())
102
                {
103
                    LoadInProgressTreeNode loadInProgressTreeNode = loadInProgressNodesIterator.next();
104
                    try
105
                    {
106
                        if (loadInProgressTreeNode.isDisposed())
107
                        {
108
                            loadInProgressNodesIterator.remove();
109
                        } else
110
                        {
111
                           notifyListeners(loadInProgressTreeNode, TreeEventType.NODES_CHANGED);
112
                        }
113
                    } catch (ProcessCanceledException e)
114
                    {
115
                        loadInProgressNodesIterator.remove();
116
                    }
117
                }
118
                if (loadInProgressNodes.isEmpty())
119
                {
120
                    cancel();
121
                }
122
            }
123
            this.iterations += 1;
124
        }
125
    }
126
    /**
127
     * 获取根节点
128
     * @return
129
     */
130
    public FileSystemBrowserTreeNode getRoot()
131
    {
132
        return  FailsafeUtil.get(this.root);
133
    }
134
    /**
135
     * 获取父节点的子节点
136
     * @param parent
137
     * @param index
138
     * @return
139
     */
140
    public Object getChild(Object parent, int index)
141
    {
142
        FileSystemBrowserTreeNode treeChild = ((FileSystemBrowserTreeNode) parent).getChildAt(index);
143
        if ((treeChild instanceof LoadInProgressTreeNode))
144
        {
145
            registerLoadInProgressNode((LoadInProgressTreeNode) treeChild);
146
        }
147
        return treeChild;
148
    }
149
    /**
150
     * 获取父节点的子节点的数量
151
     * @param parent
152
     * @return
153
     */
154
    public int getChildCount(Object parent)
155
    {
156
        return ((FileSystemBrowserTreeNode) parent).getChildCount();
157
    }
158
    /**
159
     * 判断节点是否是叶子节点
160
     * @param node
161
     * @return
162
     */
163
    public boolean isLeaf(Object node)
164
    {
165
        return ((FileSystemBrowserTreeNode) node).isLeaf();
166
    }
167
    /**
168
     * 获取子节点所在的索引
169
     * @param parent
170
     * @param child
171
     * @return
172
     */
173
    public int getIndexOfChild(Object parent, Object child)
174
    {
175
        return ((FileSystemBrowserTreeNode) parent).getIndex((FileSystemBrowserTreeNode) child);
176
    }
177
    public void valueForPathChanged(TreePath path, Object newValue)
178
    {
179
    }
180
    /**
181
     *
182
     */
183
    public void dispose()
184
    {
185
        if (!this.isDisposed)
186
        {
187
            this.isDisposed = true;
188
            this.treeModelListeners.clear();
189
            this.loadInProgressNodes.clear();
190
            this.root = null;
191
        }
192
    }
193
    /**
194
     * 树节点事件监听处理
195
     */
196
    private BrowserTreeEventListener browserTreeEventListener = new BrowserTreeEventAdapter()
197
    {
198
        public void nodeChanged(FileSystemBrowserTreeNode node, TreeEventType eventType)
199
        {
200
            if (contains(node))
201
            {
202
               notifyListeners(node, eventType);
203
            }
204
        }
205
    };
206
}

TreeModel的具体实现

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

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

  SimpleBrowserTreeModel ,继承抽象类BrowserTreeModel ,有两个构造函数,一个是无参构造,另一个传入 工程Project对象和文件系统连接集合对象(作为树的根节点),代码如下:
1
/**
2
 * 定义简单的TreeModel的实现
3
 * Created by fangyuzhong on 17-7-16.
4
 */
5
6
public class SimpleBrowserTreeModel
7
        extends BrowserTreeModel
8
{
9
    /**
10
     * 初始化
11
     */
12
    public SimpleBrowserTreeModel()
13
    {
14
        this(FailsafeUtil.DUMMY_PROJECT, null);
15
    }
16
    /**
17
     * 初始化
18
     * @param project
19
     * @param connectionBundle
20
     */
21
    public SimpleBrowserTreeModel(Project project, ConnectionBundle connectionBundle)
22
    {
23
        super(new SimpleBrowserTreeRoot(project, connectionBundle));
24
    }
25
    /**
26
     * 判断是否包含指定的TreeNode
27
     * @param node
28
     * @return
29
     */
30
    public boolean contains(FileSystemBrowserTreeNode node)
31
    {
32
        return true;
33
    }
34
    /**
35
     *
36
     */
37
    public void dispose()
38
    {
39
        super.dispose();
40
    }
41
}

总结

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

----------------------- 本文结束 感谢阅读 -----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!