package org.oddjob.swing;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.inject.Inject;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;
import javax.swing.border.EtchedBorder;
import org.oddjob.FailedToStopException;
import org.oddjob.Iconic;
import org.oddjob.OddjobServices;
import org.oddjob.OddjobShutdownThread;
import org.oddjob.Resetable;
import org.oddjob.Stateful;
import org.oddjob.Stoppable;
import org.oddjob.Structural;
import org.oddjob.Version;
import org.oddjob.arooa.deploy.annotations.ArooaComponent;
import org.oddjob.arooa.design.view.ScreenPresence;
import org.oddjob.arooa.registry.ServiceProvider;
import org.oddjob.arooa.registry.Services;
import org.oddjob.framework.SimpleService;
import org.oddjob.images.IconEvent;
import org.oddjob.images.IconListener;
import org.oddjob.input.InputHandler;
import org.oddjob.state.StateConditions;
import org.oddjob.state.StateEvent;
import org.oddjob.state.StateListener;
import org.oddjob.structural.ChildHelper;
import org.oddjob.structural.StructuralListener;
/**
* Provide a simple panel with buttons on, that run Oddjob jobs.
* <p>
*
* @author rob
*
* @since 1.3
*
*/
public class OddjobPanel extends SimpleService
implements ServiceProvider, Services, Serializable, Stoppable, Structural {
private static final long serialVersionUID = 2012091900L;
protected transient ChildHelper<Object> childHelper;
/** The executor to use. */
private volatile transient ExecutorService executorService;
/** The job threads. */
private volatile transient List<JobButtonAction> actions;
// These will be serialised so frame settings are preserved.
private ScreenPresence screen;
private int columns = 2;
/** The frame */
private volatile transient FrameWithStatus frame;
/**
* Constructor.
*/
public OddjobPanel() {
completeConstruction();
ScreenPresence whole = ScreenPresence.wholeScreen();
screen = whole.smaller(0.33);
}
private void completeConstruction() {
childHelper = new ChildHelper<Object>(this);
}
/**
* Set the {@link ExecutorService}.
*
* @oddjob.property executorService
* @oddjob.description The ExecutorService to use. This will
* be automatically set by Oddjob.
* @oddjob.required No.
*
* @param child A child
*/
@Inject
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
protected JComponent createPanel() {
actions = new ArrayList<JobButtonAction>();
Object[] jobs = childHelper.getChildren();
int rows = (int) Math.ceil((double) jobs.length / columns);
JPanel panel = new JPanel(new GridLayout(rows, columns, 10, 10));
for (Object child : jobs) {
if (! (child instanceof Runnable)) {
logger().info("Child [" + child +
"] is not Runnable - ignoring.");
continue;
}
final Runnable job = (Runnable) child;
JobButtonAction action = new JobButtonAction(job);
actions.add(action);
JButton button = new JButton(action);
panel.add(button);
if (job instanceof Stateful) {
((Stateful) job).addStateListener(new StateListener() {
@Override
public void jobStateChange(StateEvent event) {
if (StateConditions.FINISHED.test(
event.getState())) {
String status = job.toString() + ": " +
event.getState();
if (event.getException() != null) {
status += " " + event.getException();
}
frame.setStatus(status);
}
}
});
}
}
JPanel padding = new JPanel();
padding.add(panel);
return padding;
}
@Override
protected void onStart() throws Throwable {
if (executorService == null) {
throw new NullPointerException("No Executor Service.");
}
JComponent panel = createPanel();
JScrollPane scroll = new JScrollPane(panel);
frame = new FrameWithStatus();
screen.fit(frame);
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
screen = new ScreenPresence(e.getComponent());
}
@Override
public void componentResized(ComponentEvent e) {
screen = new ScreenPresence(e.getComponent());
}
});
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
// Because we DO_NOTHING_ON_CLOSE it's up to us to
// close the window.
try {
stop();
} catch (FailedToStopException e1) {
logger().error(e);
}
}
public void windowClosed(WindowEvent e) {
logger().debug("Panel window closed.");
}
});
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setTitle(this.toString());
scroll.setBorder(BorderFactory.createEtchedBorder(
EtchedBorder.RAISED));
frame.getContentPane().add(scroll, BorderLayout.CENTER);
frame.setVisible(true);
logger().debug("Panel started.");
}
@Override
protected void onStop() throws FailedToStopException {
final JFrame frame = this.frame;
if (frame != null) {
if (!(Thread.currentThread() instanceof OddjobShutdownThread)) {
frame.dispose();
}
this.frame = null;
}
for (JobButtonAction action : actions) {
action.externalStop();
}
logger().debug("Panel closed.");
}
/**
* Add a listener. The listener will immediately receive add
* notifications for all existing children.
*
* @param listener The listener.
*/
public void addStructuralListener(StructuralListener listener) {
stateHandler().assertAlive();
childHelper.addStructuralListener(listener);
}
/**
* Remove a listener.
*
* @param listener The listener.
*/
public void removeStructuralListener(StructuralListener listener) {
childHelper.removeStructuralListener(listener);
}
/**
* Add a child.
*
* @oddjob.property jobs
* @oddjob.description The child jobs.
* @oddjob.required No, but pointless if missing.
*
* @param child A child
*/
@ArooaComponent
public void setJobs(int index, Runnable child) {
if (child == null) {
childHelper.removeChildAt(index);
}
else {
childHelper.insertChild(index, child);
}
}
@Override
public Services getServices() {
return this;
}
@Override
public Object getService(String serviceName)
throws IllegalArgumentException {
if (OddjobServices.INPUT_HANDLER.equals(serviceName)) {
if (frame == null) {
return null;
}
else {
return new SwingInputHandler(frame);
}
}
else {
throw new IllegalArgumentException("No service " + serviceName);
}
}
@Override
public String serviceNameFor(Class<?> theClass, String flavour) {
if (theClass.isAssignableFrom(InputHandler.class)) {
return OddjobServices.INPUT_HANDLER;
}
else {
return null;
}
}
class JobButtonAction extends AbstractAction {
private final static long serialVersionUID = 2012091900L;
private final Runnable job;
private volatile Future<?> future;
private volatile Runnable clickTask;
private volatile Runnable stopTask;
JobButtonAction(Runnable job) {
super(job.toString());
this.job = job;
if (job instanceof Iconic) {
((Iconic) job).addIconListener(new IconListener() {
@Override
public void iconEvent(IconEvent e) {
putValue(SMALL_ICON,
e.getSource().iconForId(e.getIconId()));
}
});
}
resetActions();
}
void resetActions() {
clickTask = new RunAction();
stopTask = new NoopAction();
}
@Override
public void actionPerformed(ActionEvent e) {
clickTask.run();
}
void externalStop() {
stopTask.run();
}
class RunAction implements Runnable {
@Override
public void run() {
future = executorService.submit(
new Runnable() {
@Override
public void run() {
if (job instanceof Resetable) {
((Resetable) job).hardReset();
}
job.run();
synchronized (RunAction.this) {
resetActions();
}
}
});
synchronized (this) {
if (clickTask == this) {
clickTask = new Stop();
stopTask = clickTask;
}
}
}
}
class NoopAction implements Runnable {
@Override
public void run() {
// No operation.
}
}
class Stop implements Runnable {
@Override
public void run() {
if (future != null) {
future.cancel(false);
}
if (job instanceof Stoppable) {
try {
((Stoppable) job).stop();
} catch (FailedToStopException e) {
logger().error(e);
}
}
}
}
}
public int getColumns() {
return columns;
}
public void setColumns(int cols) {
this.columns = cols;
}
public ScreenPresence getScreen() {
return screen;
}
/**
* Custom serialisation.
*/
private void writeObject(ObjectOutputStream s)
throws IOException {
s.defaultWriteObject();
}
/**
* Custom serialisation.
*/
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
completeConstruction();
}
static class FrameWithStatus extends JFrame {
private static final long serialVersionUID = 2012092800L;
private final JLabel status = new JLabel(Version.getCurrentFullBuildMessage());
public FrameWithStatus() {
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(status, BorderLayout.SOUTH);
}
public void setStatus(String status) {
this.status.setText(status);
}
}
}