/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Copyright (C) 2011 Nicolas Peransin.
* Use is subject to license terms.
*/
package examples;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import org.mypsycho.swing.app.Action;
import org.mypsycho.swing.app.Application;
import org.mypsycho.swing.app.SingleFrameApplication;
import org.mypsycho.swing.app.task.Task;
import org.mypsycho.swing.app.utils.SwingHelper;
/**
* An {@code @Action} that executes a background {@code Task}.
* <p>
* This example demonstates the use of a background {@code Task}.
* If an {@code @Action} returns a {@code Task}, it's executed
* on a worker thread, and monitored by the application framework.
* <p>
* When executed, the {@code ListFilesTask} {@code Task} class
* recursively lists all of the files beginning with some root, and
* {@link Task#publish publishes} the files it finds, 10 at a time. A
* private subclass of {@code ListFilesTask} overrides the {@code
* Task.process} method to update a JList's ListModel with the new
* files:
* <pre>
* private class DoListFiles extends <b>ListFilesTask</b> {
* public DoListFiles(File root) {
* super(root);
* listModel.clear();
* }
* @Override protected void process(List<File> files) {
* if (!isCancelled()) {
* listModel.addAll(files);
* }
* }
* }
* </pre>
* <p>
* The example's {@code go} {@code @Action}, keeps a reference to the
* {@code DoListFiles} background {@code Task}
* so that the {@code stop} {@code @Action} can cancel it:
* <pre>
* private Task doListFiles = null;
* @Action public Task go() {
* stop(); // maybe cancel pending Task
* doListFiles = new DoListFiles(getRootFile());
* setStopEnabled(true);
* return doListFiles;
* }
* @Action(enabledProperty = "stopEnabled") public void stop() {
* if ((doListFiles != null) && (doListFiles.cancel(true))) {
* setStopEnabled(false);
* }
* }
* </pre>
* The {@code Action's} resources are initialized from a
* ResourceBundle, as with {@link ActionExample2 ActionExample2}.
* Additionally, the {@code ListFilesTask's} {@code title} and
* {@code description} properties are initialized from the
* {@code resources/ListFilesTask.properties} ResourceBundle:
* <pre>
* ListFilesTask.title = List Files
* ListFilesTask.description = List all of the files accessible from some root directory
* ListFilesTask.directoryMessage = Listing files in {0}
* </pre>
* The {@code directoryMessage} resource is used by {@code ListFilesTask}
* to format a {@link Task#message message} each time a new directory is listed.
*
* @see Action
* @see Task
* @author Hans Muller (Hans.Muller@Sun.COM)
* @author Peransin Nicolas
*/
public class ActionExample4 extends SingleFrameApplication {
private final PropertyChangeListener listFilesTaskPCL = new ListFilesTaskPCL();
private JTextField rootTextField = null;
private JLabel messageLabel = null;
private FileListModel listModel = null;
private Task<?, ?> doListFiles = null;
private boolean stopEnabled = false;
/* This GUI-specific subclass of ListFilesTask deals with updating
* the listModel (the list of Files found so far) each time another
* buffer of them is delivered to the process method. Note that it's
* important to check Task.isCancelled() here because even though
* this Task has been cancelled, there may be many pending process
* requests on the event dispatching thread. Both the DoListFiles
* constructor, and the process method run on the EDT.
*/
private class DoListFiles extends ListFilesTask {
public DoListFiles(File root) {
super(ActionExample4.this, root);
listModel.clear();
}
@Override
protected void process(List<File> files) {
if (!isCancelled()) {
listModel.addAll(files);
}
}
/*
* (non-Javadoc)
*
* @see com.psycho.swing.app.task.Task#finished()
*/
@Override
protected void finished() {
if (doListFiles == this) {
super.finished();
setStopEnabled(false);
}
}
}
/**
* The {@code go} {@code @Action}.
* <p>
* Cancel the pending DoListFiles Task and then return a new one.
* We add a PropertyChangeListener to the new Task so that we can
* monitor its "message" property.
*
* @return the new background Task
* @see #stop
*/
public Task<?, ?> go() {
stop();
File root = new File(rootTextField.getText());
doListFiles = new DoListFiles(root);
doListFiles.addPropertyChangeListener(listFilesTaskPCL);
setStopEnabled(true);
return doListFiles;
}
/**
* The {@code stop} {@code @Action}.
* <p>
* Cancel the pending DoListFiles Task, if there is one.
*
* @see #go
*/
@Action(enabled = "stopEnabled")
public void stop() {
if ((doListFiles != null) && !doListFiles.isCancelled()) {
if (doListFiles.cancel(true)) {
doListFiles.removePropertyChangeListener(listFilesTaskPCL);
doListFiles = null;
setStopEnabled(false);
}
}
}
public boolean isStopEnabled() {
return stopEnabled;
}
public void setStopEnabled(boolean stopEnabled) {
boolean oldValue = this.stopEnabled;
this.stopEnabled = stopEnabled;
firePropertyChange("stopEnabled", oldValue, this.stopEnabled);
}
@Override
protected void startup() {
String javaHome = System.getProperty("java.home");
File defaultRoot = new File(javaHome);
rootTextField = new JTextField(defaultRoot.toString(), 32);
SwingHelper helper = new SwingHelper(getMainFrame());
helper.with("buttons", new FlowLayout(), BorderLayout.PAGE_START)
.add("root", rootTextField)
.add("go", new JButton())
.add("stop", new JButton())
.back();
listModel = new FileListModel();
JList filesList = new JList(listModel);
filesList.setPrototypeCellValue(defaultRoot);
filesList.setVisibleRowCount(12);
helper.add("list", new JScrollPane(filesList), BorderLayout.CENTER);
messageLabel = new JLabel(" ");
// messageLabel.setBorder(border);
helper.add("message", messageLabel, BorderLayout.PAGE_END);
show(getMainView());
}
/* Monitor the background tasks's "message" property.
*/
private class ListFilesTaskPCL implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if ("message".equals(propertyName)) {
messageLabel.setText(e.getNewValue().toString());
}
}
}
/* Trivial ListModel that represents all of the files found so far.
*/
@SuppressWarnings("serial")
private static class FileListModel extends AbstractListModel {
private final ArrayList<File> files = new ArrayList<File>();
public void addAll(List<File> newFiles) {
if (newFiles.size() > 0) {
files.addAll(newFiles);
int lastIndex = files.size() - 1;
int firstIndex = lastIndex - (newFiles.size() - 1);
fireIntervalAdded(this, firstIndex, lastIndex);
}
}
public void clear() {
if (files.size() > 0) {
int lastListIndex = files.size() - 1;
files.clear();
fireIntervalRemoved(this, 0, lastListIndex);
}
}
public int getSize() {
return files.size();
}
public Object getElementAt(int index) {
return files.get(index);
}
}
/**
* A Task that explores the file tree beginning with root
* and publishes all of the Files it finds, 10 at a time.
* Calls Task.message() each time a new directory is opened:
* message("directoryMessage", "<directory name>");
* Requires a resource defined like this:
* ListFilesTask.directoryMessage = Opening new directory {0}
*/
private static class ListFilesTask extends Task<Void, File> {
private final File root;
private final int bufferSize;
private final List<File> buffer;
public ListFilesTask(Application app, File root) {
this.root = root;
bufferSize = 10;
buffer = new ArrayList<File>(bufferSize);
}
private void expand(File root) {
if (isCancelled()) {
return;
}
if (root.isDirectory()) {
message("dir", root);
File[] children = root.listFiles();
if (children != null) { // null when no access right
for (File file : children) {
expand(file);
}
}
} else {
buffer.add(root);
if (buffer.size() >= bufferSize) {
File bufferFiles[] = new File[buffer.size()];
publish(buffer.toArray(bufferFiles));
buffer.clear();
}
}
}
@Override
public Void doInBackground() {
expand(root);
if (!isCancelled()) {
File bufferFiles[] = new File[buffer.size()];
publish(bufferFiles);
}
return null;
}
/* (non-Javadoc)
* @see org.mypsycho.swing.app.task.Task#finished()
*/
@Override
protected void finished() {
message("done", root);
}
}
public static void main(String[] args) {
new ActionExample4().launch(args);
}
}