add javadoc
[jSite2.git] / src / net / pterodactylus / jsite / gui / FileManager.java
1 /*
2  * jSite2 - FileManager.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.jsite.gui;
21
22 import java.awt.BorderLayout;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
25 import java.io.File;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import javax.swing.JDialog;
32 import javax.swing.JFrame;
33 import javax.swing.JPanel;
34 import javax.swing.JScrollPane;
35 import javax.swing.JTree;
36 import javax.swing.event.TreeModelListener;
37 import javax.swing.tree.TreeModel;
38 import javax.swing.tree.TreePath;
39
40 import net.pterodactylus.jsite.i18n.I18n;
41 import net.pterodactylus.jsite.i18n.I18nable;
42 import net.pterodactylus.jsite.project.Entry;
43 import net.pterodactylus.jsite.project.Project;
44 import net.pterodactylus.util.data.Node;
45 import net.pterodactylus.util.data.Tree;
46 import net.pterodactylus.util.logging.Logging;
47 import net.pterodactylus.util.swing.SwingUtils;
48
49 /**
50  * Manages physical and virtual files in a project.
51  * 
52  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
53  */
54 public class FileManager extends JDialog implements I18nable {
55
56         /** Logger. */
57         private static final Logger logger = Logging.getLogger(FileManager.class.getName());
58
59         /** The project whose files to manage. */
60         private final Project project;
61
62         /** The tree model for the project files. */
63         private final FileTreeModel fileTreeModel;
64
65         /** The tree that shows the files. */
66         private JTree fileTree;
67
68         /**
69          * Creates a new file manager.
70          * 
71          * @param parent
72          *            The parent frame
73          * @param project
74          *            The project whose files to manage
75          */
76         public FileManager(JFrame parent, Project project) {
77                 super(parent, I18n.get("fileManager.title", project.getName()), true);
78                 logger.log(Level.FINEST, "project: " + project);
79                 this.project = project;
80                 fileTreeModel = new FileTreeModel();
81                 initComponents();
82                 SwingUtils.repackCentered(this);
83         }
84
85         //
86         // PRIVATE ACTIONS
87         //
88
89         /**
90          * Initializes all components.
91          */
92         private void initComponents() {
93                 JPanel contentPanel = new JPanel(new BorderLayout(12, 12));
94
95                 fileTree = new JTree(fileTreeModel);
96                 fileTree.setShowsRootHandles(false);
97                 contentPanel.add(new JScrollPane(fileTree), BorderLayout.CENTER);
98
99                 setContentPane(contentPanel);
100         }
101
102         //
103         // INTERFACE I18nable
104         //
105
106         /**
107          * {@inheritDoc}
108          */
109         public void updateI18n() {
110                 setTitle(I18n.get("fileManager.title", project.getName()));
111         }
112
113         /**
114          * Model for the tree of files.
115          * 
116          * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
117          */
118         private class FileTreeModel implements TreeModel, PropertyChangeListener {
119
120                 /** Tree model listeners. */
121                 private final List<TreeModelListener> treeModelListeners = new ArrayList<TreeModelListener>();
122
123                 /** The tree of files. */
124                 private final Tree<FileTreePath> fileTreePathTree = new Tree<FileTreePath>();
125
126                 /**
127                  * Creates a new file tree model.
128                  */
129                 FileTreeModel() {
130                         buildTree();
131                 }
132
133                 //
134                 // EVENT MANAGEMENT
135                 //
136
137                 /**
138                  * {@inheritDoc}
139                  */
140                 public void addTreeModelListener(TreeModelListener treeModelListener) {
141                         treeModelListeners.add(treeModelListener);
142                 }
143
144                 /**
145                  * {@inheritDoc}
146                  */
147                 public void removeTreeModelListener(TreeModelListener treeModelListener) {
148                         treeModelListeners.remove(treeModelListener);
149                 }
150
151                 //
152                 // ACCESSORS
153                 //
154
155                 /**
156                  * {@inheritDoc}
157                  */
158                 @SuppressWarnings("synthetic-access")
159                 public Object getChild(Object parent, int index) {
160                         logger.log(Level.FINEST, "getChild(" + parent + ", " + index + ")");
161                         Node<FileTreePath> parentNode = findNode(parent);
162                         if (parentNode != null) {
163                                 return parentNode.getChild(index).getElement();
164                         }
165                         return null;
166                 }
167
168                 /**
169                  * {@inheritDoc}
170                  */
171                 @SuppressWarnings("synthetic-access")
172                 public int getChildCount(Object parent) {
173                         logger.log(Level.FINEST, "getChildCount(" + parent + ")");
174                         Node<FileTreePath> parentNode = findNode(parent);
175                         if (parentNode != null) {
176                                 logger.log(Level.FINEST, "getChildCount(" + parent + "): " + parentNode.size());
177                                 return parentNode.size();
178                         }
179                         return -1;
180                 }
181
182                 /**
183                  * {@inheritDoc}
184                  */
185                 @SuppressWarnings("synthetic-access")
186                 public int getIndexOfChild(Object parent, Object child) {
187                         logger.log(Level.FINEST, "getIndexOfChild(" + parent + ", " + child + ")");
188                         Node<FileTreePath> parentNode = findNode(parent);
189                         if (parentNode != null) {
190                                 return parentNode.getIndexOfChild((FileTreePath) child);
191                         }
192                         return -1;
193                 }
194
195                 /**
196                  * {@inheritDoc}
197                  */
198                 @SuppressWarnings("synthetic-access")
199                 public Object getRoot() {
200                         logger.log(Level.FINEST, "getRoot()");
201                         return fileTreePathTree.getRootNode().getChild(0).getElement();
202                 }
203
204                 /**
205                  * {@inheritDoc}
206                  */
207                 @SuppressWarnings("synthetic-access")
208                 public boolean isLeaf(Object node) {
209                         logger.log(Level.FINEST, "isLeaf(" + node + ")");
210                         Node<FileTreePath> parentNode = findNode(node);
211                         if (parentNode != null) {
212                                 return parentNode.size() == 0;
213                         }
214                         return true;
215                 }
216
217                 //
218                 // ACTIONS
219                 //
220
221                 /**
222                  * {@inheritDoc}
223                  */
224                 public void valueForPathChanged(TreePath path, Object newValue) {
225                         /* TODO - implement */
226                 }
227
228                 //
229                 // PRIVATE METHODS
230                 //
231
232                 /**
233                  * Finds the node for the given object. This method is quite necessary
234                  * because the element for the root node of the JTree is
235                  * <code>null</code>
236                  * 
237                  * @param node
238                  *            The element whose node to return
239                  * @return The node, or <code>null</code> if no node could be found
240                  */
241                 private Node<FileTreePath> findNode(Object node) {
242                         if (node == null) {
243                                 return fileTreePathTree.getRootNode().getChild(0);
244                         }
245                         return fileTreePathTree.getRootNode().getChild(0).findChild((FileTreePath) node);
246                 }
247
248                 /**
249                  * Builds the tree from the project’s file entries.
250                  */
251                 @SuppressWarnings("synthetic-access")
252                 private void buildTree() {
253                         Tree<String> pathTree = new Tree<String>();
254                         Node<String> pathRootNode = pathTree.getRootNode().addChild(File.separator);
255                         logger.log(Level.FINEST, "project: " + project);
256                         buildTree(pathRootNode, project.getBasePathEntries());
257                         buildTree(pathRootNode, project.getVirtualEntries());
258                         /* now convert to a tree suitable for the JTree. */
259                         Node<FileTreePath> fileTreePathRootNode = fileTreePathTree.getRootNode();
260                         fileTreePathRootNode.removeAllChildren();
261                         convertTree(File.separator, pathRootNode, fileTreePathRootNode.addChild(new FileTreePath(File.separator, project.getName())));
262                         /* TODO - now add entries to all file tree path tree nodes. */
263                 }
264
265                 /**
266                  * Traverses the tree of path nodes and converts all paths to
267                  * {@link FileTreePath} objects, suitable for the JTree.
268                  * 
269                  * @param completePath
270                  *            The base path of the current root node
271                  * @param pathRootNode
272                  *            The root node of the path tree
273                  * @param fileTreePathRootNode
274                  *            The root node of the file tree path tree.
275                  */
276                 private void convertTree(String completePath, Node<String> pathRootNode, Node<FileTreePath> fileTreePathRootNode) {
277                         for (Node<String> pathChild: pathRootNode) {
278                                 String currentFilePath = completePath + pathChild.getElement();
279                                 Node<FileTreePath> newNode = fileTreePathRootNode.addChild(new FileTreePath(currentFilePath));
280                                 convertTree(currentFilePath, pathChild, newNode);
281                         }
282                         fileTreePathRootNode.sortChildren();
283                 }
284
285                 /**
286                  * Builds a tree matching the directory structure of the given entries.
287                  * 
288                  * @param pathRootNode
289                  *            The root node of the tree
290                  * @param entries
291                  *            The entries
292                  */
293                 private void buildTree(Node<String> pathRootNode, List<Entry> entries) {
294                         for (Entry basePathEntry: entries) {
295                                 String entryName = basePathEntry.getName();
296                                 String[] directories = entryName.split("\\" + File.separator);
297                                 Node<String> currentPathNode = pathRootNode;
298                                 for (String directory: directories) {
299                                         if (!currentPathNode.hasChild(directory)) {
300                                                 currentPathNode = currentPathNode.addChild(directory);
301                                         } else {
302                                                 currentPathNode = currentPathNode.getChild(directory);
303                                         }
304                                 }
305                         }
306                 }
307
308                 //
309                 // INTERFACE PropertyChangeListener
310                 //
311
312                 /**
313                  * {@inheritDoc}
314                  */
315                 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
316                         if (propertyChangeEvent.getSource() instanceof Project) {
317                                 if (propertyChangeEvent.getPropertyName().equals(Project.PROPERTY_BASE_PATH_ENTRIES)) {
318                                         buildTree();
319                                 }
320                         }
321                 }
322
323         }
324
325         /**
326          * Container that is used to back the {@link FileTreeModel}. Each
327          * FileTreePath contains a complete path name, a filename, and the
328          * associated {@link Entry}, if any.
329          * 
330          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
331          */
332         private static class FileTreePath implements Comparable<FileTreePath> {
333
334                 /** The complete file path. */
335                 private final String filePath;
336
337                 /** The file name. */
338                 private final String fileName;
339
340                 /** The file entry, if any. */
341                 private Entry fileEntry;
342
343                 /**
344                  * Creates a new file tree path with an auto-detected file name. The
345                  * file name is everything after the last separator in the complete
346                  * path, or the complete path itself if it does not contain any
347                  * separators.
348                  * 
349                  * @param filePath
350                  *            The complete file path
351                  */
352                 public FileTreePath(String filePath) {
353                         this(filePath, null);
354                 }
355
356                 /**
357                  * Creates a new file tree path with the given file path and file name.
358                  * 
359                  * @param filePath
360                  *            The complete file path
361                  * @param fileName
362                  *            The file name
363                  */
364                 public FileTreePath(String filePath, String fileName) {
365                         this.filePath = filePath;
366                         if (fileName == null) {
367                                 if (filePath.indexOf(File.separatorChar) != -1) {
368                                         this.fileName = filePath.substring(filePath.lastIndexOf(File.separatorChar) + 1);
369                                 } else {
370                                         this.fileName = filePath;
371                                 }
372                         } else {
373                                 this.fileName = fileName;
374                         }
375                 }
376
377                 /**
378                  * Returns the complete file path.
379                  * 
380                  * @return The file path
381                  */
382                 public String getFilePath() {
383                         return filePath;
384                 }
385
386                 /**
387                  * Returns the file name, i.e. everything after the last
388                  * {@link File#separatorChar}.
389                  * 
390                  * @return The file name
391                  */
392                 public String getFileName() {
393                         return fileName;
394                 }
395
396                 /**
397                  * Returns the file entry associated with this path, if any.
398                  * 
399                  * @return The file entry associated with this path, or
400                  *         <code>null</code> if this path denotes a directory
401                  */
402                 public Entry getFileEntry() {
403                         return fileEntry;
404                 }
405
406                 /**
407                  * Sets the entry associated with this path.
408                  * 
409                  * @param fileEntry
410                  *            The entry
411                  */
412                 public void setFileEntry(Entry fileEntry) {
413                         this.fileEntry = fileEntry;
414                 }
415
416                 /**
417                  * {@inheritDoc}
418                  */
419                 @Override
420                 public boolean equals(Object object) {
421                         if ((object == null) || !(object instanceof FileTreePath)) {
422                                 return false;
423                         }
424                         FileTreePath fileTreePath = (FileTreePath) object;
425                         return fileTreePath.filePath.equals(filePath);
426                 }
427
428                 /**
429                  * {@inheritDoc}
430                  */
431                 @Override
432                 public int hashCode() {
433                         return filePath.hashCode();
434                 }
435
436                 /**
437                  * {@inheritDoc}
438                  */
439                 @Override
440                 public String toString() {
441                         return fileName;
442                 }
443
444                 //
445                 // INTERFACE Comparable
446                 //
447
448                 /**
449                  * {@inheritDoc}
450                  */
451                 public int compareTo(FileTreePath otherFileTreePath) {
452                         return filePath.compareTo(otherFileTreePath.filePath);
453                 }
454
455         }
456
457 }