/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.repository.gui;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.repository.Entry;
import com.rapidminer.repository.Folder;
import com.rapidminer.repository.Repository;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryListener;
import com.rapidminer.repository.RepositoryManager;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.Observable;
import com.rapidminer.tools.Observer;
/** Model representing {@link Entry}s as a tree.
*
* @author Simon Fischer
*
*/
public class RepositoryTreeModel implements TreeModel {
private final RepositoryManager root;
private final EventListenerList listeners = new EventListenerList();
private final RepositoryListener repositoryListener = new RepositoryListener() {
private TreeModelEvent makeChangeEvent(Entry entry) {
TreePath path = getPathTo(entry.getContainingFolder());
int index;
if (entry instanceof Repository) {
index = RepositoryManager.getInstance(null).getRepositories().indexOf(entry);
} else {
index = getIndexOfChild(entry.getContainingFolder(), entry);
}
return new TreeModelEvent(RepositoryTreeModel.this, path, new int[] { index }, new Object[] { entry });
}
@Override
public void entryAdded(Entry newEntry, Folder parent) {
TreeModelEvent e = makeChangeEvent(newEntry);
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeNodesInserted(e);
}
}
@Override
public void entryRemoved(Entry removedEntry, Folder parent, int index) {
TreePath path = getPathTo(parent);
TreeModelEvent e = new TreeModelEvent(RepositoryTreeModel.this, path, new int[] { index }, new Object[] { removedEntry });
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeNodesRemoved(e);
}
}
@Override
public void entryRenamed(Entry entry) {
TreeModelEvent e = makeChangeEvent(entry);
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeNodesChanged(e);
}
}
@Override
public void folderRefreshed(Folder folder) {
TreeModelEvent e = makeChangeEvent(folder);
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeStructureChanged(e);
}
}
};
private boolean onlyFolders = false;
public RepositoryTreeModel(final RepositoryManager root) {
this(root, false);
}
public RepositoryTreeModel(final RepositoryManager root, final boolean onlyFolders) {
this.root = root;
this.onlyFolders = onlyFolders;
for (Repository repository : root.getRepositories()) {
repository.addRepositoryListener(repositoryListener);
}
root.addObserver(new Observer<Repository>() {
@Override
public void update(Observable<Repository> observable, Repository arg) {
for (Repository repository : root.getRepositories()) {
repository.removeRepositoryListener(repositoryListener);
repository.addRepositoryListener(repositoryListener);
}
TreeModelEvent e = new TreeModelEvent(this, new TreePath(root));
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeStructureChanged(e);
}
}
}, true);
}
TreePath getPathTo(Entry entry) {
if (entry == null) {
return new TreePath(root);
}
if (entry.getContainingFolder() == null) {
return new TreePath(root).pathByAddingChild(entry);
} else {
return getPathTo(entry.getContainingFolder()).pathByAddingChild(entry);
}
}
@Override
public void addTreeModelListener(TreeModelListener l) {
listeners.add(TreeModelListener.class, l);
}
@Override
public void removeTreeModelListener(TreeModelListener l) {
listeners.remove(TreeModelListener.class, l);
}
@Override
public Object getChild(Object parent, int index) {
if (parent instanceof RepositoryManager) {
return ((RepositoryManager)parent).getRepositories().get(index);
} else if (parent instanceof Folder) {
Folder folder = (Folder)parent;
if (folder.willBlock()) {
unblock(folder);
return "Pending...";
} else {
try {
int numFolders = folder.getSubfolders().size();
if (index < numFolders) {
return folder.getSubfolders().get(index);
} else if (onlyFolders) {
return null;
} else {
return folder.getDataEntries().get(index - numFolders);
}
} catch (RepositoryException e) {
LogService.getRoot().log(Level.WARNING, "Cannot get children of "+folder.getName()+": "+e, e);
return null;
}
}
} else {
return null;
}
}
private final Set<Folder> pendingFolders = new HashSet<Folder>();
/** Asynchronously fetches data from the folder so it
* will no longer block and then notifies listeners on the EDT. */
private void unblock(final Folder folder) {
if (pendingFolders.contains(folder)) {
return;
}
pendingFolders.add(folder);
new Thread("wait-for-"+folder.getName()) {
@Override
public void run() {
final List<Entry> children = new LinkedList<Entry>();
try {
children.addAll(folder.getSubfolders()); // this may take some time
children.addAll(folder.getDataEntries()); // this may take some time
} catch (Exception e) {
SwingTools.showSimpleErrorMessage("error_fetching_folder_contents_from_server", e);
} finally {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
TreeModelEvent removeEvent = new TreeModelEvent(RepositoryTreeModel.this, getPathTo(folder), new int[] {0}, new Object[] { "Pending..." });
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeNodesRemoved(removeEvent);
}
int index[] = new int[children.size()];
for (int i = 0; i < index.length; i++) {
index[i] = i;
}
Object[] childArray = children.toArray();
TreeModelEvent insertEvent = new TreeModelEvent(RepositoryTreeModel.this, getPathTo(folder), index, childArray);
for (TreeModelListener l : listeners.getListeners(TreeModelListener.class)) {
l.treeNodesInserted(insertEvent);
}
}
});
}
}
}.start();
}
@Override
public int getChildCount(Object parent) {
if (parent instanceof RepositoryManager) {
return ((RepositoryManager)parent).getRepositories().size();
} else if (parent instanceof Folder) {
Folder folder = (Folder)parent;
if (folder.willBlock()) {
unblock(folder);
return 1; // "Pending...."
} else {
try {
if (onlyFolders) {
return folder.getSubfolders().size();
} else {
return folder.getSubfolders().size() + folder.getDataEntries().size();
}
} catch (RepositoryException e) {
LogService.getRoot().log(Level.WARNING, "Cannot get child count for "+folder.getName()+": "+e, e);
return 0;
}
}
} else {
return 0;
}
}
@Override
public int getIndexOfChild(Object parent, Object child) {
if (parent instanceof RepositoryManager) {
return ((RepositoryManager)parent).getRepositories().indexOf(child);
} else if (parent instanceof Folder) {
Folder folder = (Folder)parent;
try {
if (child instanceof Folder) {
return folder.getSubfolders().indexOf(child);
} else if ((child instanceof Entry) && !onlyFolders) {
return folder.getDataEntries().indexOf(child) + folder.getSubfolders().size();
} else {
return -1;
}
} catch (RepositoryException e) {
LogService.getRoot().log(Level.WARNING, "Cannot get child index for "+folder.getName()+": "+e, e);
return -1;
}
} else {
return -1;
}
}
@Override
public Object getRoot() {
return root;
}
@Override
public boolean isLeaf(Object node) {
return !(node instanceof Folder) && !(node instanceof RepositoryManager);
}
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
try {
((Entry)path.getLastPathComponent()).rename(newValue.toString());
} catch (Exception e) {
SwingTools.showSimpleErrorMessage("error_rename", e, e.toString());
}
}
}