View Javadoc

1   /*
2    *  XNap Commons
3    *
4    *  Copyright (C) 2005  Felix Berger
5    *  Copyright (C) 2005  Steffen Pingel
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2.1 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free Software
19   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   */
21  package org.xnap.commons.gui.tree;
22  
23  import java.io.File;
24  import java.io.FileFilter;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Comparator;
28  import java.util.Hashtable;
29  import java.util.List;
30  import javax.swing.event.TreeModelEvent;
31  import javax.swing.event.TreeModelListener;
32  
33  public class FileTreeModel extends AbstractTreeModel
34  {
35  
36      private List<Object> subRoots;
37      private Hashtable<String,List<FileNode>> subChildren = new Hashtable<String,List<FileNode>>();
38      private File cachedDir;
39      private boolean cacheSorted;
40      private File[] cache;
41      private FileComparator comparator;
42      private FileFilter filter = new DefaultFilter();
43      private boolean sort = true;
44  
45      public FileTreeModel(String root, File[] roots)
46      {
47  		super(root);
48  
49  		if (roots != null) {
50  			subRoots = new ArrayList<Object>(roots.length);
51  			for (int i = 0; i < roots.length; i++) {
52  				addSubRoot(roots[i]);
53  			}
54  		}
55  		else {
56  			subRoots = new ArrayList<Object>();
57  		}
58      }
59  
60      public FileTreeModel(String root)
61      {
62          this(root, null);
63      }
64  
65      public boolean isLeaf(Object node)
66      {
67          return false;
68      }
69  
70      public int getChildCount(Object node)
71      {
72  		if (node instanceof File && ((File)node).canRead()) {
73  			return getSubDirs((File)node, false).length;
74  		} 
75  		else if (node instanceof String) {
76  			if (node.equals(root)) {
77  				return subRoots.size();
78  			}
79  			return subChildren.get(node).size();
80  		}
81  		else {
82  			return 0;
83  		}
84      }
85      
86      public Object getChild(Object parent, int index)
87      {
88  		if (parent instanceof File) {
89  			File[] children = getSubDirs((File)parent, sort);
90  
91  			if (index >= children.length) { 
92  				return null;
93  			}
94  			return new FileNode(children[index]);
95  		} 
96  		else if (parent instanceof String) {
97  			if (parent.equals(root) && index < subRoots.size())
98  				return subRoots.get(index);
99  	    
100 			List<FileNode> v = subChildren.get(parent);
101 			return (index < v.size()) ? v.get(index) : null;
102 		}
103 		else {
104 			return null;
105 		}
106     }
107     
108     public int getIndexOfChild(Object parent, Object child)
109     {
110 		if (parent instanceof File) {
111 			File[] children = getSubDirs((File)parent, sort);
112 
113 			if (children == null) 
114 				return -1;
115 
116 			for (int i = 0; i < children.length; i++) {
117 				if (children[i] == child)
118 					return i;
119 			}
120 		}
121 		else if (parent instanceof String) {
122 			if (parent.equals(root)) {
123 				return subRoots.indexOf(child);
124 			}
125 			return ((List)subChildren.get(parent)).indexOf(child);
126 		}
127 
128 		return -1; 
129     }
130 
131     /***
132      * Guaranteed to not return null.
133      * @param f
134      * @param doSort
135      * @return
136      */
137     private File[] getSubDirs(File f, boolean doSort)
138     {
139 		if (f == cachedDir && cacheSorted == doSort)
140 			return cache;
141 
142 		File[] children = f.listFiles(filter); 
143 		if (children == null) {
144 			cache = new File[0];
145 		}
146 		else {
147 			cache = children;
148 			if (doSort) {
149 				Arrays.sort(cache, getComparator());
150 			}
151 		}
152 		cachedDir = f;
153 		cacheSorted = doSort;
154 		return cache;
155     }
156 
157     public void addSubRoot(String name)
158     {
159 		if (subRoots.contains(name))
160 			return;
161 	
162 		subRoots.add(name);
163 	
164 		/* add List for children of this node */
165 		subChildren.put(name, new ArrayList<FileNode>());
166 
167 		Object[] path = { root };
168 		int[] indices = { subRoots.size() - 1 };
169 		Object[] children = { name };
170 		fireTreeNodesInserted(new TreeModelEvent(this, path, indices,
171 												 children));
172 	
173     }
174 
175     public void addSubRoot(File f)
176     {
177 		if (subRoots.contains(f))
178 			return;
179 	
180 		subRoots.add(f);
181 	
182 		Object[] path = { root };
183 		int[] indices = { subRoots.size() - 1 };
184 		Object[] children = { f };
185 		fireTreeNodesInserted(new TreeModelEvent(this, path, indices, 
186 												 children));
187     }
188 
189     public void removeSubRoots()
190     {
191 		/* remove respective Lists in hash tree */
192 		for (int i = 0; i < subRoots.size(); i++)
193 			subChildren.remove(subRoots.get(i));
194 	
195 		subRoots.clear();
196 	
197 		Object[] path = { root };
198 		fireTreeStructureChanged(new TreeModelEvent(this, path));
199     }
200     
201     public void removeChildrenOfSubRoot(String s)
202     {
203 		if (!subRoots.contains(s))
204 			return;
205 
206 		subChildren.get(s).clear();
207 		
208 		Object[] path = { root, s };
209 		fireTreeStructureChanged(new TreeModelEvent(this, path));
210     }
211 
212     public void addChildOfSubRoot(File file, String subRoot)
213     {
214 		addChildOfSubRoot(file, subRoot, null);
215     }
216 
217     public void addChildOfSubRoot(File file, String subRoot, String label)
218     {
219 		addSubRoot(subRoot);
220 	
221 		List<FileNode> children = subChildren.get(subRoot);
222 		FileNode node = new FileNode(file, true, label);
223 	
224 		if (children.contains(node))
225 			return;
226 		children.add(node);
227 		
228 		fireTreeNodesInserted(new TreeModelEvent(this, 
229 				new Object[] { root, subRoot },
230 				new int[]    { children.size() - 1 },
231 				new Object[] { file }));
232     }
233 
234     public void removeChildOfSubRoot(File file, String subRoot)
235     {
236 		if (!subRoots.contains(subRoot))
237 			return;
238 	    
239 		List<FileNode> children = subChildren.get(subRoot);
240 	
241 		FileNode node = new FileNode(file, true);
242 		int index = children.indexOf(node);
243 		if (index == -1)
244 			return;
245 		children.remove(index);
246 
247 		fireTreeNodesRemoved(new TreeModelEvent(this, 
248 				new Object[] { root, subRoot }, 
249 				new int[]    { index },		
250 				new Object[] { node }));
251     }
252 
253     /***
254      * Create comparator lazily.
255      */
256     private FileComparator getComparator()
257     {
258     	if (comparator == null) {
259     		comparator = new FileComparator();
260     	}
261     	return comparator;
262     }
263     
264     // raised visibilibity for testing
265     static class FileComparator implements Comparator<File>
266     {
267     
268     	public int compare(File o1, File o2)
269     	{
270     		return o1.getAbsolutePath().compareToIgnoreCase(o2.getAbsolutePath());
271     	}
272     }
273 
274     /***
275      * Sets a file filter that is used for listing directories in the tree.
276      * <p>
277      * The default filter excludes hidden files and files that are not directories.
278      * <p>
279      * @param filter can be <code>null</code>
280      */
281 	public void setFileFilter(FileFilter filter) 
282 	{
283 			this.filter = filter;
284 	}
285 
286 	/***
287 	 * Returns the currently set file filter.
288 	 * @return can be <code>null</code>
289 	 */
290 	public FileFilter getFileFilter() 
291 	{
292 		return filter;
293 	}
294 	
295 	private class DefaultFilter implements FileFilter
296 	{
297 		public boolean accept(File file)
298 		{
299 			return file.isDirectory() && !file.isHidden();
300 		}
301 	}
302 
303 	/***
304 	 * Sets whether the listed files of a directory should be sorted.
305 	 * <p>
306 	 * Fires {@link TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent)}
307 	 * with the root node as parameter to make sure the whole tree is updated
308 	 * correctly.
309 	 * @param sort
310 	 */
311 	public void setSortListedFiles(boolean sort) 
312 	{
313 		if (this.sort != sort) {
314 			this.sort = sort;
315 			fireTreeStructureChanged(new TreeModelEvent(this, new Object[] { getRoot() }));
316 		}
317 	}
318 
319 	/***
320 	 * Returns whether the listed files of a directory are being sorted. 
321 	 */
322 	public boolean getSortListedFiles() 
323 	{
324 		return sort;
325 	}
326 }