0%

Hadoop-Intellij-Plugin 插件设计和源码分析----HDFS文件系统浏览器设计和实现2

  本节,将详细说明一下文件系统树UI层的展示设计和实现。文件系统对象以树的方式展示,在IntelliJ 框架内,文件树依附于浮动面板,即IntelliJ 的ToolWindow 插件。UI需要有层次感,IntelliJ 的 ToolWindow 作为文件系统UI的最底层,由IDEA 框架去维护和控制;在ToolWindow上面,需要有个主窗体ToolWindowForm,该窗体上将会呈现文件系统Tree的UI和文件系统对象的相关属性UI;由于可能存在多个连接,连接到HDFS,因此会存在多个文件系统Tree,因此文件系统Tree 的UI将分为两种方式呈现,一种以Tab列表的方式平铺,另一种,以单个树根节点的方式呈现,因此,需要有TabbedBrowsForm 和 SimpleBrowserForm 两种UI,在这两种UI之上,才真正显示出文件系统的Tree控件。因此本节也将从这几个方面来介绍。
文件系统树UI整体设计的类图如下:

文件系统树FileSystemBrowserTree及其渲染类FileSystemBrowserTreeCellRenderer

  FileSystemBrowserTree 类为树的控件类,继承了 FileSystemTree 类,FileSystemTree继承了IDEA本身的Tree。FileSystemBrowserTree 类定义了对文件系统Tree的相关操作,包括获取TreeModel、进行树节点TreeNode的导航(前一节点、后一节点)、树节点TreeNode的折叠和展开、鼠标悬停TreeNode的提示文字、获取选中的TreeNode、TreeNode选中的事件通知、TreeNode的鼠标事件、TreeNode的键盘事件等。下面就Tree的初始化和右键菜单做详细说明。

FileSystemBrowserTree的初始化方法

  初始化中包括鼠标、键盘、选择事件的注册、设置根节点的可见性、设置Tree的相关滚动条、设置Tree渲染方式,初始化代码如下:

1
/**
2
 * 初始化树控件
3
 * @param treeModel
4
 */
5
public FileSystemBrowserTree(BrowserTreeModel treeModel)
6
{
7
    super(treeModel);
8
    //注册监听事件
9
    addKeyListener(this.keyListener);
10
    addMouseListener(this.mouseListener);
11
    addTreeSelectionListener(this.treeSelectionListener);
12
    setToggleClickCount(0);
13
    setRootVisible(treeModel instanceof TabbedBrowserTreeModel);
14
    setShowsRootHandles(true);
15
    setAutoscrolls(true);
16
    //设置TreeNode的渲染器
17
    FileSystemBrowserTreeCellRenderer browserTreeCellRenderer = new FileSystemBrowserTreeCellRenderer(treeModel.getProject());
18
    setCellRenderer(browserTreeCellRenderer);
19
    FileSystemBrowserTreeSpeedSearch speedSearch = new FileSystemBrowserTreeSpeedSearch(this);
20
    //注册相关需要释放的资源对象
21
    Disposer.register(this, speedSearch);
22
    Disposer.register(this, treeModel);
23
    Disposer.register(this, this.navigationHistory);
24
}

TreeNode的右键菜单设计

  当选中树的某个节点,根据其节点类型,显示不同的右键菜单项。目前节点只有两种类型,一是文件系统对象的集合类型,即FIleSystemObjectBundle,该类型特指整个HDFS根目录下(“/“)的所有对象(包括目录和文件);二是文件系统的对象,目录或者文件,这里为简单起见,文件不区分具体的类型。每种类型显示哪些右键菜单,在本节后面介绍。右键菜单展示,需要在TreeNode的鼠标选中节点右键释放后处理。在处理过程中使用了多线程操作。定义内部类MouseListener 封装了 整个鼠标事件处理,其中方法 mouseReleased(final MouseEvent event) 实现了右键菜单的展示,代码如下:

1
/**
2
     *鼠标释放事件
3
     * @param event
4
     */
5
    public void mouseReleased(final MouseEvent event)
6
    {
7
        if (event.getButton() == 3)
8
        {
9
            TreePath path = FileSystemBrowserTree.this.getPathForLocation(event.getX(), event.getY());
10
            if (path != null)
11
            {
12
                final FileSystemBrowserTreeNode lastPathEntity = (FileSystemBrowserTreeNode) path.getLastPathComponent();
13
                if (lastPathEntity.isDisposed())
14
                {
15
                    return;
16
                }
17
                //开启一个右键菜单判断操作线程
18
                new ModalTask(lastPathEntity.getProject(), "Loading fsobject information", true)
19
                {
20
                    //进行TreeNode的类型判断,并实例化菜单的Action
21
                    protected void execute(@NotNull ProgressIndicator progressIndicator)
22
                    {
23
                        ActionGroup actionGroup = null;
24
                        if ((lastPathEntity instanceof FileSystemObject))
25
                        {
26
                            //文件系统对象节点,右键菜单
27
                            FileSystemObject object = (FileSystemObject) lastPathEntity;
28
                            actionGroup = new ObjectActionGroup(object);
29
                        }
30
                        else if ((lastPathEntity instanceof FileSystemObjectBundle))
31
                        {
32
                            //文件系统对象集合节点
33
                            FileSystemObjectBundle objectsBundle = (FileSystemObjectBundle) lastPathEntity;
34
                            ConnectionHandler connectionHandler = objectsBundle.getConnectionHandler();
35
                            actionGroup = new ConnectionActionGroup(connectionHandler);
36
                        }
37
                        if ((actionGroup != null) && (!progressIndicator.isCanceled()))
38
                        {
39
                            //开启显示右键菜单操作线程
40
                            ActionPopupMenu actionPopupMenu = ActionManager.getInstance().createActionPopupMenu("", actionGroup);
41
                            popupMenu = actionPopupMenu.getComponent();
42
                            new SimpleLaterInvocator()
43
                            {
44
                                protected void execute()
45
                                {
46
                                    if (FileSystemBrowserTree.this.isShowing())
47
                                    {
48
                                        popupMenu.show(FileSystemBrowserTree.this,event.getX(),event.getY());
49
                                    }
50
                                }
51
                            }.start();
52
                        } else
53
                        {
54
                            FileSystemBrowserTree.this.popupMenu = null;
55
                        }
56
                    }
57
                }.start();
58
            }
59
        }
60
    }
61
};

TreeNode的渲染器类 FileSystemBrowserTreeCellRenderer

  如果要改变TreeNode的显示方式要使用到TreeCellRender,通过对它的实现才能够得到不同显示方式的Tree。这里自定义继承 TreeCellRender 类,实现了TreeNode 节点的图标显示、文字显示等。在FileSystemBrowserTreeCellRenderer 类中重写了 Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) 方法。该方法返回一个Component这个控件,也就是你要设置的树中节点的显示风格,当然在实现的时候你可以继承一个Jcomponent类的子类,也可以在类中设置一个私有变量然后返回。
  在这个方法中有很多参数,首先是Jtree 这个就是你要设置的树,对应的对象,然后是value ,这个其实是节点,通过他你可以获得节点的数据,以及对应的子节点父节点等。接着是selected表示如果被选中时该如何显示。Expanded则表示如果出于扩展状态如何显示节点,然后是leaf,叶子节点的显示方式可以通过这个条件设置;row这个参数,如果有伸缩的话,row是随时改变的,最后一个hasFocus是是否拥有焦点,设置拥有焦点时的显示方式。实现了这个方法后,用setCellRende()方法设置一下这个类的实例就行了。详细可参见该类的代码…./src/main/java/com/fangyuzhong/intelliJ/hadoop/fsbrowser/ui/FileSystemBrowserTreeCellRenderer.java

文件系统主窗体设计

  文件系统主窗体UI设计分为三个部分,一个是ToolWindow 上呈现的UI:BrowserToolWindowForm,该窗体为容器,这个窗体上,将构建Tree的显示UI和文件系统对象的属性显示UI;第二个是 展现Tree的UI窗体TabbedBrowserForm和SimpleBrowserForm;第三部分是文件对象属性展示UI:ObjectPropertiesForm。三部分都是UI界面,IDEA开发UI界面使用 GUI Form的开发方式。主界面UI采用XML来描述,类的文件名后面加”.form”构成,UI界面可以使用控件进行拖拽,UI背后的业务逻辑 使用和该类同名的java 文件描述。IDEA这么做,无非就是想摆脱繁琐的Swing代码,微软的WPF技术也是同样使用这种方法。

展现Tree的UI窗体TabbedBrowserForm和SimpleBrowserForm

  当用户选择以Tab列表的方式展现多个HDFS连接,那么使用TabbedBrowserForm来展示,当用户选择以单个树控件多个根节点方式展现多个HDFS连接,那么使用SimpleBrowserForm方式来展示,TabbedBrowserForm展示方法借助于SimpleBrowserForm,两个窗体都继承抽象FileSystemBrowserFrom。
  抽象类FileSystemBrowserFrom 继承了项目中自定义的窗体基类。在FileSystemBrowserFrom 中有三个重要的抽象方法:
①、public abstract FileSystemBrowserTree getBrowserTree() :获取文件系统树Tree控件对象
②、public abstract void selectElement(FileSystemBrowserTreeNode paramBrowserTreeNode, boolean paramBoolean1, boolean paramBoolean2); 设置选中的Tree的元素
③、public abstract void rebuildTree(); 构建Tree控件
三个抽象方法均在TabbedBrowserForm和SimpleBrowserForm 中具体实现。

SimpleBrowserForm类

  该类比较简单,在UI上,有个主面板Jpanel ,放置FileSystemBrowserTree控件,这是UI最基础的部分,Tree控件始终是在这个UI上的,TabbedBrowserForm也是借助该窗体进行展示,只不过会根据HDFS连接个数 new 这个窗体,放到Tab页上而已。使用SimpleBrowserTreeModel实例化一个FileSystemBrowserTree控件,初始化代码如下:

1
/**
2
 * 使用SimpleBrowserTreeModel,初始化
3
 * @param parentComponent
4
 */
5
public SimpleBrowserForm(DisposableProjectComponent parentComponent)
6
{
7
    this(parentComponent, new SimpleBrowserTreeModel(parentComponent.getProject(), ConnectionManager.getInstance(parentComponent.getProject()).getConnectionBundle()));
8
}
9
/**
10
 * TabbedBrowserTreeModel,初始化
11
 * @param parentComponent
12
 * @param connectionHandler
13
 */
14
public SimpleBrowserForm(DisposableProjectComponent parentComponent, ConnectionHandler connectionHandler)
15
{
16
    this(parentComponent, new TabbedBrowserTreeModel(connectionHandler));
17
}
18
/**
19
 *初始化,使用BrowserTreeModel 创建文件树控件对象FileSystemBrowserTree
20
 * @param parentComponent
21
 * @param treeModel
22
 */
23
private SimpleBrowserForm(DisposableProjectComponent parentComponent, BrowserTreeModel treeModel)
24
{
25
    super(parentComponent);
26
    this.browserTree = new FileSystemBrowserTree(treeModel);
27
    this.browserScrollPane.setViewportView(this.browserTree);
28
    this.browserScrollPane.setBorder(new EmptyBorder(1, 0, 0, 0));
29
    ToolTipManager.sharedInstance().registerComponent(this.browserTree);
30
    Disposer.register(this, this.browserTree);
31
}

重写了抽象类的3个抽象方法:

1
/**
2
 * 获取FileSystemBrowserTree对象
3
 * @return
4
 */
5
public FileSystemBrowserTree getBrowserTree()
6
{
7
    return this.browserTree;
8
}
9
/**
10
 * 设置选中树元素
11
 */
12
public void selectElement(FileSystemBrowserTreeNode treeNode, boolean focus, boolean scroll)
13
{
14
    this.browserTree.selectElement(treeNode, focus);
15
}
16
/**
17
 * 构建Tree
18
 */
19
public void rebuildTree()
20
{
21
    this.browserTree.getModel().getRoot().rebuildTreeChildren();
22
}

TabbedBrowserForm类

  该类,将用户创建的多个HDFS连接,以Tab页(列表)的方式呈现。因此界面UI有两个控件,一个是主面板mainPanel,一个是自定义开发 tab 控件 TabbedPane 。

该类的初始化。

  首先创建一个TabbedPane 控件,注册相关事件监听器;然后调用initTabs()方法根据连接数创建各个连接的Tab页。initTabs()方法中,通过HDFS连接管理类,获取当前HDFS连接集合,然后遍历连接集合,读取每个连接,创建一个Tab页面,并且实例化一个SimpleBrowserForm窗体,放到tab页面上;最后,将TabbedPane 加入到主面板中,完成整个UI的构建。初始化代码如下:

1
/**
2
 * 初始化
3
 * @param parentComponent
4
 */
5
public TabbedBrowserForm(BrowserToolWindowForm parentComponent)
6
{
7
    super(parentComponent);
8
    this.connectionTabs = new TabbedPane(this);
9
    initTabs();
10
    this.connectionTabs.addListener(new TabsListener() {
11
       public void selectionChanged(TabInfo oldSelection, TabInfo newSelection){}
12
       public void beforeSelectionChanged(TabInfo oldSelection, TabInfo newSelection){}
13
       public void tabRemoved(TabInfo tabInfo) {}
14
       public void tabsMoved(){}});
15
    Disposer.register(this, this.connectionTabs);
16
}
17
18
/**
19
 * 初始化Tab 列表
20
 */
21
private void initTabs()
22
{
23
    Project project = getProject();
24
    TabbedPane oldConnectionTabs = this.connectionTabs;
25
    this.connectionTabs = new TabbedPane(this);
26
    ConnectionManager connectionManager = ConnectionManager.getInstance(project);
27
    //获取HDFS连接集合,遍历连接集合,读取每个连接,创建一个Tab页面
28
    ConnectionBundle connectionBundle = connectionManager.getConnectionBundle();
29
    for (ConnectionHandler connectionHandler : connectionBundle.getConnectionHandlers())
30
    {
31
        //根据每个连接,实例化一个 SimpleBrowserForm,放到一个新的Tab页上
32
        SimpleBrowserForm browserForm = new SimpleBrowserForm(this, connectionHandler);
33
        JComponent component = browserForm.getComponent();
34
        TabInfo tabInfo = new TabInfo(component);
35
        tabInfo.setText(CommonUtil.nvl(connectionHandler.getName(), "[unnamed fsconnection]"));
36
        tabInfo.setObject(browserForm);
37
        this.connectionTabs.addTab(tabInfo);
38
    }
39
    //添加connectiontab 到主面板控件mainPanel
40
    if (this.connectionTabs.getTabCount() == 0)
41
    {
42
        this.mainPanel.removeAll();
43
        this.mainPanel.add(new JBList(new ArrayList()), "Center");
44
    }
45
    else if (this.mainPanel.getComponentCount() > 0)
46
    {
47
        Component component = this.mainPanel.getComponent(0);
48
        if (component != this.connectionTabs)
49
        {
50
            this.mainPanel.removeAll();
51
            this.mainPanel.add(this.connectionTabs, "Center");
52
        }
53
    }
54
    else
55
    {
56
        this.mainPanel.add(this.connectionTabs, "Center");
57
    }
58
    DisposerUtil.dispose(oldConnectionTabs);
59
}
重写基类的抽象方法

  由于是以Tab列表的方式展现Tree,在任何时候,只有一个Tab页是激活的,因此,在获取FileSystemBrowserTree对象,有两种方法,一是获取当前激活的Tab上的Tree,二是,获取指定HDFS连接所在的Tree。选中树元素和构建树,就可以调用SimpleBrowserForm 的相关方法了。具体代码如下:

1
/**
2
 * 获取当前活动的Tree
3
 * @return
4
 */
5
@Nullable
6
public FileSystemBrowserTree getBrowserTree()
7
{
8
    return getActiveBrowserTree();
9
}
10
/**
11
 * 获取指定连接的Tree
12
 * @param connectionHandler
13
 * @return
14
 */
15
@Nullable
16
public FileSystemBrowserTree getBrowserTree(ConnectionHandler connectionHandler)
17
{
18
    SimpleBrowserForm browserForm = getBrowserForm(connectionHandler);
19
    return browserForm == null ? null : browserForm.getBrowserTree();
20
}
21
/**
22
 * 获取当前激活的Tree
23
 * @return
24
 */
25
@Nullable
26
public FileSystemBrowserTree getActiveBrowserTree()
27
{
28
    TabInfo tabInfo = this.connectionTabs.getSelectedInfo();
29
    if (tabInfo != null)
30
    {
31
        SimpleBrowserForm browserForm = (SimpleBrowserForm) tabInfo.getObject();
32
        return browserForm.getBrowserTree();
33
    }
34
    return null;
35
}
36
/**
37
 * 设置选中元素
38
 * @param treeNode
39
 * @param focus
40
 * @param scroll
41
 */
42
public void selectElement(FileSystemBrowserTreeNode treeNode, boolean focus, boolean scroll)
43
{
44
    ConnectionHandler connectionHandler = treeNode.getConnectionHandler();
45
    SimpleBrowserForm browserForm = getBrowserForm(connectionHandler);
46
    if (browserForm != null)
47
    {
48
        this.connectionTabs.select(browserForm.getComponent(), focus);
49
        if (scroll)
50
        {
51
            browserForm.selectElement(treeNode, focus, scroll);
52
        }
53
    }
54
}
55
/**
56
 * 构建树
57
 */
58
public void rebuildTree()
59
{
60
    for (TabInfo tabInfo : this.connectionTabs.getTabs())
61
    {
62
        SimpleBrowserForm browserForm = (SimpleBrowserForm) tabInfo.getObject();
63
        browserForm.rebuildTree();
64
    }
65
}

主窗体UI,BrowserToolWindowForm。

   该类,将Tree的UI和文件系统对象属性UI及其工具栏的Action组合起来。界面UI上,有个主面板 mainPanel、存放FileSystemBrowserForm面板 browserPanel、存放ObjectProperties的面板 objectPropertiesPanel。在初始化中,先注册显示方式设置的监听,然后调用 rebuild()方法,构建Tree 的UI组件;最后添加工具栏的Action,初始化代码如下:

1
/**
2
 * 初始化
3
 * @param project
4
 */
5
public BrowserToolWindowForm(Project project)
6
{
7
    super(project);
8
9
    /*
10
    注册显示方式设置监听
11
     */
12
    this.displayModeSettingsListener = new DisplayModeSettingsListener()
13
    {
14
        public void displayModeChanged(BrowserDisplayMode displayMode)
15
        {
16
            if (BrowserToolWindowForm.this.getDisplayMode() != displayMode)
17
            {
18
                BrowserToolWindowForm.this.setDisplayMode(displayMode);
19
                BrowserToolWindowForm.this.rebuild();
20
            }
21
        }
22
    };
23
    //构建UI
24
    rebuild();
25
    //添加工具栏
26
    ActionToolbar actionToolbar = ActionUtil.createActionToolbar("", true,
27
            "HadoopNavigator.ActionGroup.Browser.Controls");
28
    actionToolbar.setTargetComponent(this.actionsPanel);
29
    this.actionsPanel.add(actionToolbar.getComponent());
30
    //添加文件对象属性显示UI
31
    this.objectPropertiesPanel.setVisible(true);
32
    this.objectPropertiesForm = new ObjectPropertiesForm(this);
33
    this.objectPropertiesPanel.add(this.objectPropertiesForm.getComponent());
34
    GuiUtils.replaceJSplitPaneWithIDEASplitter(this.mainPanel);
35
    GUIUtil.updateSplitterProportion(this.mainPanel, 0.7F);
36
    //通知显示方式设置修改
37
    EventUtil.subscribe(project, this, DisplayModeSettingsListener.TOPIC, this.displayModeSettingsListener);
38
}
39
40
/**
41
 * 构建界面UI
42
 */
43
public void rebuild()
44
{
45
    //获取文件树展示方式
46
    this.displayMode = GeneralProjectSettings.getInstance(getProject()).
47
            getBrowserSettings().getBrowserDisplayMode();
48
    this.browserPanel.removeAll();
49
    DisposerUtil.dispose(this.browserForm);
50
    //按照显示方式,创建Tree显示窗体
51
    this.browserForm = (this.displayMode == BrowserDisplayMode.SINGLE ?
52
            new SimpleBrowserForm(this) :
53
            this.displayMode == BrowserDisplayMode.TABBED ?
54
                    new TabbedBrowserForm(this) : null);
55
    this.browserPanel.add(this.browserForm.getComponent(), "Center");
56
    this.browserPanel.revalidate();
57
    this.browserPanel.repaint();
58
    Disposer.register(this, this.browserForm);
59
    if (objectPropertiesForm != null)
60
    {
61
        objectPropertiesForm.cleanObjectPropertiesShow();
62
    }
63
}

文件属性对象展示窗体ObjectPropertiesForm

  对于HDFS文件系统对象目录或者文件,显示其所有者、创建日期、文件或者目录大小、文件或者目录的路径、文件或目录备份数 。ObjectPropertiesForm窗体代码在fsobject 包中。到后续讲到FileSystemObject对象的时候再说吧,也比较简单。

文件系统界面上的工具栏Action。

  工具栏功能包括,HDFS连接设置、TreeNode导航(前一节点、后一节点)、TreeNode的折叠和展开、隐藏和显示属性对象UI。所有的工具栏功能按钮都是Action插件对象。代码组织如下:

总结

  本节,从文件系统树的UI展示上做了说明,介绍了主窗体UI、Tab窗体UI、Simple窗体UI、文件系统对象属性ObjectProperties UI等。这部分主要集中在UI界面开发这块。前面说过,IDEA开发UI界面使用 GUI Form的开发方式,UI对象的布局保存在XML中,那么IDEA框架在运行插件的时候,怎么把UI布局的XML转换为Swing对象?这里需要注意一下:GUI布局文件,采用的是IDEA本身的编译器编译的,在IDEA设置中,找到GUI Designer 项,在Generate GUI into 选中 Java source Code ,如下下图,这样编译后,框架会把UI的XML布局自动生成代码插入到源码文件中:

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