4
\$\begingroup\$

The purpose of this short program is to:

  • Detect all accessible local drives and USB drives
  • List all the accessible directories for each drive which do not require administrator privileges or are hidden by the system
  • Display all the data in a friendly UI that is simple to use and nice to look at

I have noticed once the list starts to become longer the performance of the list scrolling and animations decreases. Therefore I would particularly be interested in your thoughts on the efficiency of the program and if any improvements can be made to it. I would also like to know if you think the program does what it is meant to do properly.

Dropbox Folder with source code and icons

Example of the program
Example of the program

Edit

I know there is a lot of code for what seems like a simple component so I will add the performance issues that I hinted at earlier on in my question and what I think might be causing them.

As the list grows:

  • The scrolling becomes jumpy and sometimes lags. This is while there are only a few rows on the screen is ~250 on my system.
  • The expand control fade animation becomes slow with ~30 rows on my system. I believe this is being caused by the way the timers work in TreeHandleUI.fadeHandles(boolean show, int msDur)
  • The "rollover" tracking when the mouse one of the rows or expand controls for a row can have a significant delay of almost half a second if the number of rows is ~200 on my system.

Another performance issue that I noted is when a directory has a large amount of subdirectories in it and files it can take a while to open. An example in Windows would be C:\Program Files. This is because the method ComputerDirectoryTreeModel.listDirectories(File path) has to check every file to see if it is a directory. Then for paint controls and to determine what icons should be used there is also ComputerDirectoryTreeModel.hasDirectory(File path) and FileNameRenderer.hasDirectory(File path). This is a method with improved efficiency over listDirectories as it stops as soon as it finds one directory in the directory it is searching. The problem is if the directory is large and does not actually contain a directory or the directory is a far way down the list then this can still take a while to run.


JFileTreeIE.java - Implementation of program

package tree;

import java.awt.EventQueue;
import java.awt.GridLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import tree.design.DesignTree;
import tree.model.ComputerDirectoryTreeModel;

/*
 * Short implementation class to show the program
 */

@SuppressWarnings("serial")
public class JFileTreeIE extends JFrame {
    private JFileTreeIE() {
        super("Directory Explorer");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new GridLayout());
        createPanel();
        setSize(500, 800);
        setVisible(true);
    }

    private void createPanel() {
        DesignTree tree = new DesignTree(new ComputerDirectoryTreeModel());
        JScrollPane scroll = new JScrollPane(tree);
        JPanel panel = new JPanel(new GridLayout(1, 1));
        panel.add(scroll);
        getContentPane().add(panel);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> new JFileTreeIE());
    }
}

DesignTree.java - The component class

package tree.design;

import javax.swing.JTree;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeSelectionModel;

import tree.listeners.MouseHandler;
import tree.render.FileNameRenderer;
import tree.ui.TreeHandleUI;

/*
 * Class to set a range of custom UIs, renderers, and handlers
 */

@SuppressWarnings("serial")
public class DesignTree extends JTree {
    public DesignTree(TreeModel model) {
        super(model);

        setUI(new TreeHandleUI(this));
        setCellRenderer(new FileNameRenderer());

        setRootVisible(false); //Allows for each drive to act as a root
        setShowsRootHandles(true); //Shows handles for each individual drive

        getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        addMouseListener(new MouseHandler(this));
    }
}

AlphaImageIcon.java - Slightly edited version of code from this post - Used for icon animations

package tree.icon;

import java.awt.AlphaComposite;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.ImageObserver;
import javax.swing.Icon;
import javax.swing.ImageIcon;

/**
 * An Icon  wrapper that paints the contained icon with a specified transparency.
 * <P>
 * This class is suitable for wrapping an <CODE>ImageIcon</CODE>
 * that holds an animated image.  To show a non-animated Icon with transparency,
 * the companion class {@link AlphaIcon} is a lighter alternative.
 *
 * @version 1.0 08/16/10
 * @author Darryl
 * 
 * https://tips4java.wordpress.com/2010/08/22/alpha-icons/
 */

@SuppressWarnings("serial")
public class AlphaImageIcon extends ImageIcon {
    private Icon icon;
    private Image image;
    private float alpha;

    public AlphaImageIcon(Icon icon, float alpha) {
        this.icon = icon;
        this.alpha = alpha;
    }

    @Override
    public Image getImage() {
        return image;
    }

    @Override
    public void setImage(Image image) {
        if (icon instanceof ImageIcon)
            ((ImageIcon) icon).setImage(image);
    }

    @Override
    public int getImageLoadStatus() {
        if (icon instanceof ImageIcon)
            return ((ImageIcon) icon).getImageLoadStatus();
        return 0;
    }

    @Override
    public ImageObserver getImageObserver() {
        if (icon instanceof ImageIcon)
            return ((ImageIcon) icon).getImageObserver();
        return null;
    }

    @Override
    public void setImageObserver(ImageObserver observer) {
        if (icon instanceof ImageIcon)
            ((ImageIcon) icon).setImageObserver(observer);
    }

    public float getAlpha() {
        return alpha;
    }

    public Icon getIcon() {
        return icon;
    }

    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if (icon instanceof ImageIcon)
            image = ((ImageIcon) icon).getImage();
        else
            image = null;
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setComposite(AlphaComposite.SrcAtop.derive(alpha));
        icon.paintIcon(c, g2, x, y);
        g2.dispose();
    }

    @Override
    public int getIconWidth() {
        return icon.getIconWidth();
    }

    @Override
    public int getIconHeight() {
        return icon.getIconHeight();
    }
}

MouseHandler.java - Main MouseListener for the tree

package tree.listeners;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JTree;
import javax.swing.SwingUtilities;

import tree.ui.TreeHandleUI;

/*
 * Class which controls expand / collapse handle animations
 * and other mouse related functions
 */

public class MouseHandler extends MouseAdapter {
    JTree t = null;
    TreeHandleUI tUI = null;

    public MouseHandler(JTree tree) {
        t = tree;
        tUI = (TreeHandleUI) tree.getUI();
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        tUI.fadeHandles(true, 300); //Fades handles in
    }

    @Override
    public void mouseExited(MouseEvent e) {
        tUI.fadeHandles(false, 300); //Fades handles out
    }

    @Override
    public void mousePressed(MouseEvent e) {
        //Clears selection when mouse is pressed outside of on of the nodes
        if((SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isRightMouseButton(e))
                && t.getRowForLocation(e.getX(), e.getY()) == -1)
            t.clearSelection();
    }
}

ComputerDirectoryTreeModel.java - The TreeModel that detects the drives and automatically generates the list of directories for each drive

package tree.model;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;

import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;

/*
 * Class that automatically generates the tree
 * so that it contains all directories accessible drives.
 * 
 * Does not access hidden directories or directories
 * that require administrator privileges.
 */

public class ComputerDirectoryTreeModel implements TreeModel {
    private DefaultMutableTreeNode root = new DefaultMutableTreeNode();

    public ComputerDirectoryTreeModel() {
        createRootLayout();
    }

    private void createRootLayout() {
        for(File path : File.listRoots()) { //Loops through the "root" directories on a computer - Determined by system
            if(path.exists())
                if(addable(path))
                    if(listDirectories(path).length != 0)
                        root.add(new DefaultMutableTreeNode(path));
        }
    }

    //Checks whether the drive can be added
    //Only adds local drives or USBs
    private boolean addable(File path) {
        FileSystemView fsv = FileSystemView.getFileSystemView();
        String type = fsv.getSystemTypeDescription(path);
        //System.out.println(type); //For Debugging Purposes
        if(type != null) {
            if(type.equalsIgnoreCase("Local Disk"))
                return true;
            if(type.equalsIgnoreCase("USB Drive"))
                return true;
            return true; //Temporary fix for users who don't use windows
        }
        return false;
    }

    //Not used
    @Override
    public void addTreeModelListener(javax.swing.event.TreeModelListener l) {}

    //Overrides the original getChild so that the method returns the correct directory
    @Override
    public Object getChild(Object parent, int index) {
        if(parent != root && parent instanceof DefaultMutableTreeNode) {
            File f = (File) (((DefaultMutableTreeNode) parent).getUserObject());
            return listDirectories(f)[index];
        } else if(parent != root) {
            File f = (File) parent;
            return listDirectories(f)[index];
        }
        return root.getChildAt(index);
    }

    //Overrides the original getChildCount so that
    //the method returns the correct value
    @Override
    public int getChildCount(Object parent) {
        if(parent != root && parent instanceof DefaultMutableTreeNode) {
            File f = (File) (((DefaultMutableTreeNode) parent).getUserObject());
            if (!f.isDirectory())
                return 0;
            else
                return listDirectories(f).length;
        } else if(parent != root) {
            File f = (File) parent;
            if (!f.isDirectory())
                return 0;
            else
                return listDirectories(f).length;
        }
        return root.getChildCount();
    }

    //Overrides the original hasChildren so that
    //the method returns the correct value
    public boolean hasChildren(Object parent) {
        if(parent != root && parent instanceof DefaultMutableTreeNode) {
            File f = (File) (((DefaultMutableTreeNode) parent).getUserObject());
            if (!f.isDirectory())
                return false;
            else
                return hasDirectory(f);
        } else if(parent != root) {
            File f = (File) parent;
            if (!f.isDirectory())
                return false;
            else
                return hasDirectory(f);
        }
        return root.getChildCount() != 0 ? true : false;
    }

    //Overrides the original getIndexOfChild so that
    //the method returns the correct value
    @Override
    public int getIndexOfChild(Object parent, Object child) {
        if(parent != root && parent instanceof DefaultMutableTreeNode) {
            File par = (File) (((DefaultMutableTreeNode) parent).getUserObject());
            File ch = (File) child;
            return Arrays.asList(listDirectories(par)).indexOf(ch);
        } else if(parent != root) {
            File par = (File) parent;
            File ch = (File) child;
            return Arrays.asList(listDirectories(par)).indexOf(ch);
        }

        return root.getIndex((TreeNode) child);
    }

    @Override
    public Object getRoot() {
        return root;
    }

    //There should technically be no leaves as every directory
    //could potentially have a sub directory in it.
    @Override
    public boolean isLeaf(Object node) {
        return false;
    }

    //Not used
    @Override
    public void removeTreeModelListener(javax.swing.event.TreeModelListener l) {}

    //Not used
    @Override
    public void valueForPathChanged(javax.swing.tree.TreePath path, Object newValue) {}

    //Lists all the sub directories of the given directory
    //if it is accessible. This will not work for folders
    //that require administrator privilages to view.
    private File[] listDirectories(File path) {
        ArrayList<File> arrayList = new ArrayList<File>();
        for(File temp : path.listFiles()) {
            if(temp.isDirectory() && !temp.isHidden())
                if(temp.listFiles() != null)
                    arrayList.add(temp);
        }

        return arrayList.toArray(new File[0]);
    }

    //Written to improve performance of listDirectories(path) != 0
    private boolean hasDirectory(File path) {
        for(File temp : path.listFiles()) {
            if(temp.isDirectory() && !temp.isHidden())
                if(temp.listFiles() != null)
                    return true;
        }

        return false;
    }
}

FileNameRenderer.java - Makes sure that the rows are rendered correctly

package tree.render;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.io.File;
import java.util.Arrays;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;

/*
 * This class handles the rendering of the tree nodes.
 * This includes handling rollover functions, and naming conventions.
 */

@SuppressWarnings("serial")
public class FileNameRenderer extends DefaultTreeCellRenderer {
    public FileNameRenderer() {
        setBackgroundSelectionColor(new Color(205, 232, 255));
        setHoverSelectionColor(new Color(229, 243, 255));
    }

    private boolean mOver = false;
    private boolean selected = false;
    private Color rolloverColor = null;

    public void setHoverSelectionColor(Color rollover) {
        rolloverColor = rollover;
    }

    public Color getHoverSelectionColor() {
        return rolloverColor;
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
        return getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus, false);
    }

    //Replaces the node text from the path of the directory to just the directory name
    //Also update the what node is selected and whether the mouse is over it for other
    //functions in this class
    //Sets some of the icons of the nodes on the tree. Ie if the directory is expanded
    //the icon will change
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus, boolean mouseOver) {
        File holder = null;
        if(value instanceof File) {
            holder = (File) value;
            if(!Arrays.asList(File.listRoots()).contains((File) value)) {
                value = ((File) value).getName();
            }
        }

        JComponent c = (JComponent) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);

        selected = sel;
        mOver = mouseOver;

        if(c instanceof JLabel)
            ((JLabel) c).setText("<html><p style=\"padding: 3px 0;\">" + value + "<p/>");

        if(value instanceof DefaultMutableTreeNode)
            setIcon(new ImageIcon("resources/root.png"));
        else if(expanded && holder != null && hasDirectory(holder))
            setIcon(new ImageIcon("resources/open-folder.png"));
        else
            setIcon(new ImageIcon("resources/folder.png"));

        return c;
    }

    //Checks whether the directory has any sub directories.
    //This determines if a handle will be painted next to
    //the node
    private boolean hasDirectory(File path) {
        for(File temp : path.listFiles()) {
            if(temp.isDirectory() && !temp.isHidden())
                if(temp.listFiles() != null)
                    return true;
        }

        return false;
    }

    //Paints the node
    //Changes background colour of the node if the mouse is over it
    //or if it is selected
    //Offsets paint by so much so that the node works with the icons
    @Override
    public void paintComponent(Graphics g) {
        Color bColor;

        if (selected)
            bColor = getBackgroundSelectionColor();
        else if(mOver)
            bColor = getHoverSelectionColor();
        else {
            bColor = getBackgroundNonSelectionColor();
            if (bColor == null)
                bColor = getBackground();
        }

        int imageOffset = -1;
        if (bColor != null) {
            imageOffset = getLabelStart();
            g.setColor(bColor);
            if(getComponentOrientation().isLeftToRight())
                g.fillRect(imageOffset, 0, getWidth() - imageOffset, getHeight());
            else
                g.fillRect(0, 0, getWidth() - imageOffset, getHeight());
        }

        super.paintComponent(g);
    }

    //Used for getting the image offset for the paintComponent function
    private int getLabelStart() {
        Icon currentI = getIcon();
        if(currentI != null && getText() != null)
            return currentI.getIconWidth() + Math.max(0, getIconTextGap() - 1);
        return 0;
    }
}

TreeHandleUI.java - Look and feel of the tree

package tree.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.TreePath;

import tree.icon.AlphaImageIcon;
import tree.model.ComputerDirectoryTreeModel;
import tree.render.FileNameRenderer;

/*
 * This mainly handles the look and feel of the JTree
 * It effects areas such as the expand controls of nodes,
 * the way the nodes look, and how lines are drawn on the
 * tree if they are drawn at all
 */

public class TreeHandleUI extends BasicTreeUI {
    ///Variables
    private JTree t = null;
    private boolean lines = false;
    private boolean lineTypeDashed = true;
    private Icon rolloverIcon = null;
    private boolean iconRolloverEnabled = false;
    private ArrayList<Timer> timers = new ArrayList<Timer>(); //For preventing any nasty glitching if fade method is still running
    private UpdateHandler uH = null;
    private ComputerDirectoryTreeModel tM; //Used so that we can quickly check if the file contains any children
    private boolean isLeftToRight(Component c) {
        return c.getComponentOrientation().isLeftToRight();
    }

    private int count = 0;
    private float val = 0F;

    ///Constructors
    //Sets most of the icons for the JTree and changes the default mouse handler
    public TreeHandleUI(JTree tree) {
        t = tree;
        uH = new UpdateHandler(t);
        t.addMouseMotionListener(uH);

        EventQueue.invokeLater(() -> tM = (ComputerDirectoryTreeModel) treeModel); //Is like this so that tM does not return an NPE
        EventQueue.invokeLater(() -> setCollapsedIcon(new AlphaImageIcon(new ImageIcon("resources/closed.png"), 0F)));
        EventQueue.invokeLater(() -> setExpandedIcon(new AlphaImageIcon(new ImageIcon("resources/open.png"), 0F)));
        setRolloverIcon(new AlphaImageIcon(new ImageIcon("resources/rollover.png") , 1F));
        removeLines(true); //Removes all the visible lines on the JTree
    }

    ///Methods
    //Methods used for rollover icon
    public void setRolloverIcon(Icon rolloverG) {
        Icon oldValue = rolloverIcon;
        rolloverIcon = rolloverG;
        setIconRolloverEnabled(true);
        if (rolloverG != oldValue) {
            t.repaint();
        }
    }

    private void setIconRolloverEnabled(boolean handleRolloverEnabled) {
        boolean oldValue = iconRolloverEnabled;
        iconRolloverEnabled = handleRolloverEnabled;
        if (handleRolloverEnabled != oldValue) {
            t.repaint();
        }
    }

    //Paints the correct icon for the expand control
    //Ie when the mouse is over the collapsed icon it
    //changes to the rollover icon
    @Override
    protected void paintExpandControl(Graphics g,
                                      Rectangle clipBounds, Insets insets,
                                      Rectangle bounds, TreePath path,
                                      int row, boolean isExpanded,
                                      boolean hasBeenExpanded,
                                      boolean isLeaf) {
        Object value = path.getLastPathComponent();

        if(tM.hasChildren(value)) {
            int middleXOfKnob;
            if(isLeftToRight(t))
                middleXOfKnob = bounds.x - getRightChildIndent() + 1;
            else
                middleXOfKnob = bounds.x + bounds.width + getRightChildIndent() - 1;
            int middleYOfKnob = bounds.y + (bounds.height / 2);

            if(isExpanded) {
                Icon expandedIcon = getExpandedIcon();
                if(expandedIcon != null)
                  drawCentered(tree, g, expandedIcon, middleXOfKnob, middleYOfKnob );
            } else if(isLocationInExpandControl(path, uH.getXPos(), uH.getYPos())
                    & !isExpanded && iconRolloverEnabled) {
                if(row == uH.getRowHandle()) {
                    if(rolloverIcon != null)
                        drawCentered(tree, g, rolloverIcon, middleXOfKnob, middleYOfKnob);
                } else {
                    Icon collapsedIcon = getCollapsedIcon();
                    if(collapsedIcon != null)
                      drawCentered(tree, g, collapsedIcon, middleXOfKnob, middleYOfKnob);
                }
            } else {
                Icon collapsedIcon = getCollapsedIcon();
                if(collapsedIcon != null)
                  drawCentered(tree, g, collapsedIcon, middleXOfKnob, middleYOfKnob);
            }
        }
    }

    //Paints the row, updates background if mouse over row
    @Override
    protected void paintRow(Graphics g, Rectangle clipBounds,
                            Insets insets, Rectangle bounds, TreePath path,
                            int row, boolean isExpanded,
                            boolean hasBeenExpanded, boolean isLeaf) {
        // Don't paint the renderer if editing this row.
        if(editingComponent != null && editingRow == row)
            return;

        int leadIndex;

        if(tree.hasFocus())
            leadIndex = getLeadSelectionRow();
        else
            leadIndex = -1;

        Component component = ((FileNameRenderer) t.getCellRenderer()).getTreeCellRendererComponent
                      (tree, path.getLastPathComponent(),
                       tree.isRowSelected(row), isExpanded, isLeaf, row,
                       (leadIndex == row), (row == uH.getRow()));

        rendererPane.paintComponent(g, component, tree, bounds.x, bounds.y,
                                    bounds.width, bounds.height, true);
    }

    //Small utility class used for retrieving information so that
    //tasks such as adding a rollover icon or rollover background
    //can be completed
    private class UpdateHandler extends BasicTreeUI.MouseHandler {
        private JTree t = null;
        private int xPos = 0;
        private int yPos = 0;

        public UpdateHandler(JTree tree) {
            t = tree;
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            xPos = e.getX();
            yPos = e.getY();
            t.repaint();
        }

        public int getXPos() {
            return xPos;
        }

        public int getYPos() {
            return yPos;
        }

        public int getRow() {
            return t.getRowForLocation(xPos, yPos);
        }

        public int getRowHandle() {
            return getRowForPath(t, getClosestPathForLocation(t, xPos, yPos));
        }
    }

    //Method used for fade handles in or out
    public void fadeHandles(boolean show, int msDur) {
        Timer time = new Timer((int) Math.floor(msDur / 25), new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(show)
                    count++;
                else
                    count--;

                val = count * 0.04F;
                setCollapsedIcon(new AlphaImageIcon(new ImageIcon("resources/closed.png") , val));
                setExpandedIcon(new AlphaImageIcon(new ImageIcon("resources/open.png") , val));
                setRolloverIcon(new AlphaImageIcon(new ImageIcon("resources/rollover.png") , val));

                if(count == 0 || count == 25)
                    ((javax.swing.Timer) e.getSource()).stop();
                t.repaint();
            }
        });

        timers.add(time);
        for(javax.swing.Timer timer : timers) {
            timer.stop();
        }

        time.start();
    }

    //Methods used for remove lines
    public void removeLines(boolean ShowLines) {
        lines = ShowLines;
    }

    public void removeLines(boolean ShowLines, boolean dashedLines) {
        lines = ShowLines;
        lineTypeDashed = dashedLines;
    }

    @Override
    protected void paintDropLine(Graphics g) {
        if(!lines) {
            JTree.DropLocation loc = tree.getDropLocation();
            if (!isDropLine(loc))
                return;

            Color c = UIManager.getColor("Tree.dropLineColor");
            if (c != null) {
                g.setColor(c);
                Rectangle rect = getDropLineRect(loc);
                g.fillRect(rect.x, rect.y, rect.width, rect.height);
            }
        }
    }

    @Override
    protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left, int right) {
        if(!lines) {
            if (lineTypeDashed)
                drawDashedHorizontalLine(g, y, left, right);
            else
                g.drawLine(left, y, right, y);
        }
    }

    @Override
    protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) {
        if(!lines) {
            if (lineTypeDashed)
                drawDashedVerticalLine(g, x, top, bottom);
            else
                g.drawLine(x, top, x, bottom);
        }
    }
}

Any other comments, tips or suggestions are welcomed and appreciated.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ Very clean code. \$\endgroup\$
    – coderodde
    Commented Aug 14, 2017 at 17:16
  • \$\begingroup\$ I am not going to downvote your question or something, but when I run it, I only get a window, but no action. I use Mac OS. \$\endgroup\$
    – coderodde
    Commented Aug 14, 2017 at 17:26
  • \$\begingroup\$ @coderodde I apologise. I wrote it on Windows. The problem will reside with ComputerDirectoryTreeModel.java - private boolean addable(File path) where the descriptions decide whether a drive is added. In Windows it is Local Drive and USB Drive. I don't know what it is in Mac \$\endgroup\$
    – Dan
    Commented Aug 14, 2017 at 17:31
  • 1
    \$\begingroup\$ @coderodde Whilst this does work I did not want it to work in this way as it also detects network drives which I did not want it to do for my purposes \$\endgroup\$
    – Dan
    Commented Aug 14, 2017 at 18:18
  • 1
    \$\begingroup\$ ArrayList<Timer> timers = new ArrayList<Timer>(); I suggest you use List on the left hand side. What's important is that timers will have list functionality, not its implementation. \$\endgroup\$ Commented Aug 17, 2017 at 17:58

2 Answers 2

1
\$\begingroup\$

Very clean code, I don't have much bad to say about it. As a cherry on top of the cake, you could center out the window:

private void moveToCenter() {
    int width = getWidth();
    int height = getHeight();
    Dimension screenDimension = Toolkit.getDefaultToolkit()
                                       .getScreenSize();
    setLocation((screenDimension.width - width) / 2, 
                (screenDimension.height - height) / 2);
}

Call it in the constructor of JFileTreeIE.

Also, if you plan to work on GUI software in Java, I suggest you migrate to JavaFX.

Edit: Mac OS X

I made it work on Mac by returning unconditionally true in ComputerDirectoryTreeModel.addable().

Advice 1

If the if or else statement is a one-liner you omit the curly braces. I, however, suggest that you add them. That will add up to the maintainability of your code, since if the maintainer wants to add a statement or two to an, say, if-block, he/she don't have to type them themselves.

Hope that helps.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ window.setLocationRelativeTo(null) should have the same effect as your moveToCenter method. \$\endgroup\$
    – Marco13
    Commented Aug 14, 2017 at 17:54
  • \$\begingroup\$ Thank you for the answer and advice. It helps knowing that the program is not cross platform. I will start to add code to support that \$\endgroup\$
    – Dan
    Commented Aug 14, 2017 at 17:57
  • \$\begingroup\$ @Marco13 Does not seem to work flawlessly. \$\endgroup\$
    – coderodde
    Commented Aug 14, 2017 at 17:58
  • \$\begingroup\$ @coderodde The linked question might just have been caused by calling it before determining the size of the frame. It has to be called after the size has been set (via setSize or pack). (But of course, I can't test it on all OSes/Window managers right now...) \$\endgroup\$
    – Marco13
    Commented Aug 14, 2017 at 18:00
  • \$\begingroup\$ @Marco13 You are correct, sir. Seems to use some nonsensical width/height values. \$\endgroup\$
    – coderodde
    Commented Aug 14, 2017 at 18:06
0
\$\begingroup\$

It's been a while since I posted this question. But there are a few performance issues with the original code. The main ones being with methods such as getChild, getChildCount, hasDirectory, listDirectories.

The issue with hasDirectory and listDirectories was about how the files were listed and the method of verification used for checking if a directory had any sub directories. The big performance restriction was file.listFiles(); especially, with some of the bigger files. This was replaced with the methodology below.

//Written to improve performance of listDirectories(path) != 0
private boolean hasDirectory(File path) {
    //System.out.println("Testing: " + path.getAbsolutePath());
    Path dir = Paths.get(path.getAbsolutePath());
    DirectoryStream<Path> stream;
    try {
        stream = Files.newDirectoryStream(dir);
        for(Path p : stream) {
            //System.out.println("Testing for Children: " + p.toString());
            DirectoryStream<Path> tempStream;
            try {
                tempStream = Files.newDirectoryStream(p);
                tempStream.close();
                return true;
            } catch (IOException e) {
                //System.out.println("Not a accessible dir");
            }
        }
        stream.close();
        return false;
    } catch (IOException e) {
        //System.out.println("Not a accessible dir");
        return false;
    }
}

The issue with getChild and getChildCount was they called very expensive functions such as listDirectories very often. To fix this I created a way of storing the results of listDirectories. In other words I used a TreeMap. This made searching for already existing results very quick. The method now looks a bit like this.

private File[] listDirectories(File path) {
    File[] files = DataStore.DATA_STORE.get(path.getAbsolutePath());

    if(files == null) {
        List<File> directories = new ArrayList<File>();
        for(File temp : path.listFiles()) {
            Path dir = Paths.get(temp.getAbsolutePath());
            DirectoryStream<Path> stream;
            try {
                stream = Files.newDirectoryStream(dir);
                stream.close();
                directories.add(temp);
            } catch (IOException e) {
                //System.out.println("Not a accessible dir");
            }
        }

        files = directories.toArray(new File[0]);
        DataStore.DATA_STORE.put(path.getAbsolutePath(), files);
        return files;
    }

    return files;
}

The full code can be found on this Bitbucket Repository.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.