001/*
002 * $Id: FileSystemModel.java 3927 2011-02-22 16:34:11Z kleopatra $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021
022package org.jdesktop.swingx.treetable;
023
024import java.io.File;
025import java.util.Arrays;
026import java.util.Date;
027
028/**
029 * A tree table model to simulate a file system.
030 * <p>
031 * This tree table model implementation extends {@code AbstractTreeTableModel}.
032 * The file system metaphor demonstrates that it is often easier to directly
033 * implement tree structures directly instead of using intermediaries, such as
034 * {@code TreeTableNode}.
035 * <p>
036 * A comparison of this class with {@code SimpleFileSystemModel}, shows that
037 * extending {@code AbstractTreeTableModel} is often easier than creating a model
038 * from scratch.
039 * <p>
040 * A "full" version of this model might allow editing of file names, the
041 * deletion of files, and the movement of files. This simple implementation does
042 * not intend to tackle such problems, but this implementation may be extended
043 * to handle such details.
044 * 
045 * @author Ramesh Gupta
046 * @author Karl Schaefer
047 */
048public class FileSystemModel extends AbstractTreeTableModel {
049    // The the returned file length for directories.
050    private static final Long DIRECTORY = 0L;
051
052    /**
053     * Creates a file system model using the root directory as the model root.
054     */
055    public FileSystemModel() {
056        this(new File(File.separator));
057    }
058
059    /**
060     * Creates a file system model using the specified {@code root}.
061     * 
062     * @param root
063     *            the root for this model; this may be different than the root
064     *            directory for a file system.
065     */
066    public FileSystemModel(File root) {
067        super(root);
068    }
069
070    private boolean isValidFileNode(Object file) {
071        boolean result = false;
072        
073        if (file instanceof File) {
074            File f = (File) file;
075            
076            while (!result && f != null) {
077                result = f.equals(root);
078                
079                f = f.getParentFile();
080            }
081        }
082        
083        return result;
084    }
085    
086    /**
087     * {@inheritDoc}
088     */
089    @Override
090    public File getChild(Object parent, int index) {
091        if (!isValidFileNode(parent)) {
092            throw new IllegalArgumentException("parent is not a file governed by this model");
093        }
094        
095        File parentFile = (File) parent;
096        String[] children = parentFile.list();
097        
098        if (children != null) {
099            return new File(parentFile, children[index]);
100        }
101        
102        return null;
103    }
104
105    /**
106     * {@inheritDoc}
107     */
108    @Override
109    public int getChildCount(Object parent) {
110        if (parent instanceof File) {
111            String[] children = ((File) parent).list();
112            
113            if (children != null) {
114                return children.length;
115            }
116        }
117
118        return 0;
119    }
120
121    /**
122     * {@inheritDoc}
123     */
124    @Override
125    public Class<?> getColumnClass(int column) {
126        switch (column) {
127        case 0:
128            return String.class;
129        case 1:
130            return Long.class;
131        case 2:
132            return Boolean.class;
133        case 3:
134            return Date.class;
135        default:
136            return super.getColumnClass(column);
137        }
138    }
139
140    @Override
141    public int getColumnCount() {
142        return 4;
143    }
144
145    @Override
146    public String getColumnName(int column) {
147        switch (column) {
148        case 0:
149            return "Name";
150        case 1:
151            return "Size";
152        case 2:
153            return "Directory";
154        case 3:
155            return "Modification Date";
156        default:
157            return super.getColumnName(column);
158        }
159    }
160
161    @Override
162    public Object getValueAt(Object node, int column) {
163        if (node instanceof File) {
164            File file = (File) node;
165            switch (column) {
166            case 0:
167                return file.getName();
168            case 1:
169                return isLeaf(node) ? file.length() : DIRECTORY;
170            case 2:
171                return file.isDirectory();
172            case 3:
173                return new Date(file.lastModified());
174            }
175        }
176
177        return null;
178    }
179
180    /**
181     * {@inheritDoc}
182     */
183    @Override
184    public int getIndexOfChild(Object parent, Object child) {
185        if (parent instanceof File && child instanceof File) {
186            File parentFile = (File) parent;
187            File[] files = parentFile.listFiles();
188            
189            Arrays.sort(files);
190            
191            for (int i = 0, len = files.length; i < len; i++) {
192                if (files[i].equals(child)) {
193                    return i;
194                }
195            }
196        }
197        
198        return -1;
199    }
200
201    /**
202     * {@inheritDoc}
203     */
204    @Override
205    public File getRoot() {
206        return (File) root;
207    }
208
209    /**
210     * Sets the root for this tree table model. This method will notify
211     * listeners that a change has taken place.
212     * 
213     * @param root
214     *            the new root node to set
215     */
216    public void setRoot(File root) {
217        this.root = root;
218        
219        modelSupport.fireNewRoot();
220    }
221
222    /**
223     * {@inheritDoc}
224     */
225    @Override
226    public boolean isLeaf(Object node) {
227        if (node instanceof File) {
228            //do not use isFile(); some system files return false
229            return ((File) node).list() == null;
230        }
231        
232        return true;
233    }
234}