+ /**
+ * TreeModel that is based on {@link Project#getBaseFile()}.
+ *
+ * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
+ */
+ private class ProjectFileTreeModel implements TreeModel, PropertyChangeListener {
+
+ /** Tree model listeners. */
+ private final List<TreeModelListener> treeModelListeners = Collections.synchronizedList(new ArrayList<TreeModelListener>());
+
+ /** The base project file. */
+ private ProjectFile baseProjectFile;
+
+ /** Maps path names to project files. */
+ private final Map<String, ProjectFile> pathProjectFiles = Collections.synchronizedMap(new HashMap<String, ProjectFile>());
+
+ /** Maps project files to wrappers. */
+ private final Map<ProjectFile, ProjectFileWrapper> projectFileWrappers = Collections.synchronizedMap(new HashMap<ProjectFile, ProjectFileWrapper>());
+
+ /**
+ * Empty constructor.
+ */
+ ProjectFileTreeModel() {
+ /* do nothing. */
+ }
+
+ //
+ // EVENT MANAGEMENT
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addTreeModelListener(TreeModelListener treeModelListener) {
+ treeModelListeners.add(treeModelListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void removeTreeModelListener(TreeModelListener treeModelListener) {
+ treeModelListeners.remove(treeModelListener);
+ }
+
+ /**
+ * Notifies all listeners that a node has changed.
+ *
+ * @param changedProjectFileWrapper
+ * The wrapper around the changed project file
+ */
+ protected void fireTreeNodesChanged(ProjectFileWrapper changedProjectFileWrapper) {
+ ProjectFile changedProjectFile = changedProjectFileWrapper.getProjectFile();
+ ProjectFile changedProjectFileParent = changedProjectFile.getParent();
+ ProjectFile currentProjectFileParent = changedProjectFile;
+ List<ProjectFileWrapper> parentProjectFileWrappers = new ArrayList<ProjectFileWrapper>();
+ do {
+ parentProjectFileWrappers.add(0, projectFileWrappers.get(currentProjectFileParent));
+ currentProjectFileParent = currentProjectFileParent.getParent();
+ } while (currentProjectFileParent != null);
+ TreeModelEvent treeModelEvent = new TreeModelEvent(this, parentProjectFileWrappers.toArray(), new int[] { getIndexOfChild(projectFileWrappers.get(changedProjectFileParent), changedProjectFileWrapper) }, new Object[] { changedProjectFileWrapper });
+ for (TreeModelListener treeModelListener: treeModelListeners) {
+ treeModelListener.treeNodesChanged(treeModelEvent);
+ }
+ }
+
+ /**
+ * Notifies all listeners that the tree structure has changed
+ * significantly.
+ *
+ * @see TreeModelListener#treeStructureChanged(TreeModelEvent)
+ * @param newRootNode
+ */
+ protected void fireTreeStructureChanged(ProjectFileWrapper newRootNode) {
+ for (TreeModelListener treeModelListener: treeModelListeners) {
+ treeModelListener.treeStructureChanged(new TreeModelEvent(this, new Object[] { newRootNode }));
+ }
+ }
+
+ //
+ // ACCESSORS
+ //
+
+ /**
+ * Sets the new base project file. This causes the model to reload.
+ *
+ * @param baseProjectFile
+ * The new base project file
+ */
+ @SuppressWarnings("synthetic-access")
+ public synchronized void setBaseProjectFile(ProjectFile baseProjectFile) {
+ this.baseProjectFile = baseProjectFile;
+ projectFileWrappers.clear();
+ pathProjectFiles.clear();
+ createWrappers(baseProjectFile);
+ projectFileWrappers.get(baseProjectFile).setNameOverride(project.getName());
+ fireTreeStructureChanged(projectFileWrappers.get(baseProjectFile));
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Creates {@link ProjectFileWrapper}s for all files below the given
+ * project file.
+ *
+ * @param projectFile
+ * The base project file for all project files to create
+ * wrappers for
+ */
+ private void createWrappers(ProjectFile projectFile) {
+ projectFileWrappers.put(projectFile, new ProjectFileWrapper(projectFile));
+ pathProjectFiles.put(projectFile.getCompletePath(), projectFile);
+ for (ProjectFile projectFileChild: projectFile.getFiles()) {
+ if (projectFileChild.isDirectory()) {
+ createWrappers(projectFileChild);
+ }
+ projectFileWrappers.put(projectFileChild, new ProjectFileWrapper(projectFileChild));
+ pathProjectFiles.put(projectFileChild.getCompletePath(), projectFileChild);
+ }
+ }
+
+ //
+ // INTERFACE TreeModel
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getRoot() {
+ return projectFileWrappers.get(baseProjectFile);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("synthetic-access")
+ public Object getChild(Object parent, int index) {
+ if (!(parent instanceof ProjectFileWrapper)) {
+ logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
+ return null;
+ }
+ ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
+ ProjectFile projectFile = projectFileWrapper.getProjectFile();
+ return projectFileWrappers.get(projectFile.getFiles().get(index));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("synthetic-access")
+ public int getChildCount(Object parent) {
+ if (!(parent instanceof ProjectFileWrapper)) {
+ logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
+ return -1;
+ }
+ ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
+ ProjectFile projectFile = projectFileWrapper.getProjectFile();
+ return projectFile.getFiles().size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("synthetic-access")
+ public int getIndexOfChild(Object parent, Object child) {
+ if (!(parent instanceof ProjectFileWrapper)) {
+ logger.log(Level.SEVERE, "parent is not a ProjectFileWrapper!");
+ return -1;
+ }
+ if (!(child instanceof ProjectFileWrapper)) {
+ logger.log(Level.SEVERE, "child is not a ProjectFileWrapper!");
+ return -1;
+ }
+ ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) parent;
+ ProjectFile projectFile = projectFileWrapper.getProjectFile();
+ return projectFile.getFiles().indexOf(((ProjectFileWrapper) child).getProjectFile());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("synthetic-access")
+ public boolean isLeaf(Object node) {
+ if (!(node instanceof ProjectFileWrapper)) {
+ logger.log(Level.SEVERE, "node is not a ProjectFileWrapper!");
+ return true;
+ }
+ ProjectFileWrapper projectFileWrapper = (ProjectFileWrapper) node;
+ return projectFileWrapper.getProjectFile().isFile();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void valueForPathChanged(TreePath path, Object newValue) {
+ /* ignore, items will not be modified in tree. */
+ }
+
+ //
+ // INTERFACE PropertyChangeListener
+ //
+
+ /**
+ * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+ */
+ @SuppressWarnings("synthetic-access")
+ public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
+ if (Project.PROPERTY_DEFAULT_FILE.equals(propertyChangeEvent.getPropertyName())) {
+ if (propertyChangeEvent.getOldValue() != null) {
+ String oldCompletePath = (String) propertyChangeEvent.getOldValue();
+ ProjectFile oldProjectFile = pathProjectFiles.get(oldCompletePath);
+ ProjectFileWrapper oldProjectFileWrapper = projectFileWrappers.get(oldProjectFile);
+ System.out.println("oldProjectFileWrapper: " + oldProjectFileWrapper);
+ fireTreeNodesChanged(oldProjectFileWrapper);
+ }
+ String newCompletePath = (String) propertyChangeEvent.getNewValue();
+ ProjectFile newProjectFile = pathProjectFiles.get(newCompletePath);
+ ProjectFileWrapper newProjectFileWrapper = projectFileWrappers.get(newProjectFile);
+ System.out.println("newProjectFileWrapper: " + newProjectFileWrapper);
+ fireTreeNodesChanged(newProjectFileWrapper);
+ /* HACK - swing sucks a bit. */
+ fileTree.setShowsRootHandles(false);
+ }
+ }
+
+ }
+
+ /**
+ * Wrapper around a {@link ProjectFile} that overwrites
+ * {@link Object#toString()} to return the project file’s name.
+ *
+ * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
+ */
+ private static class ProjectFileWrapper {
+
+ /** The wrapped project file. */
+ private final ProjectFile projectFile;
+
+ /** The override name. */
+ private String nameOverride;
+
+ /**
+ * Creates a new wrapper around a project file.
+ *
+ * @param projectFile
+ * The project file to wrap
+ */
+ public ProjectFileWrapper(ProjectFile projectFile) {
+ this.projectFile = projectFile;
+ }
+
+ /**
+ * Returns the wrapped project file.
+ *
+ * @return The wrapped project file
+ */
+ public ProjectFile getProjectFile() {
+ return projectFile;
+ }
+
+ /**
+ * Sets the name override. If the name override is not <code>null</code>
+ * it will be shown insted of the project file’s name.
+ *
+ * @param nameOverride
+ * The name override
+ */
+ void setNameOverride(String nameOverride) {
+ this.nameOverride = nameOverride;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return (nameOverride != null) ? nameOverride : projectFile.getName();
+ }
+
+ }
+