/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. ****************************************************************/ package org.apache.cayenne.modeler.util; import org.apache.cayenne.CayenneRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * A base class for monitoring progress of long running tasks. It can runshowing the exact * percentage of the task progress or in "indeterminate" mode. * <p> * <i>Warning: If the task started via "startAndWait()", caller must ensure that she is * not running in the Swing EventDispatchThread, otherwise an exception is thrown, as the * EvenDispatchThread will be blocked, preventing LongRunningTask from showing progress * dialog. </i> * </p> * */ public abstract class LongRunningTask<T> { private static final Logger LOGGER = LoggerFactory.getLogger(LongRunningTask.class); protected static final int DEFAULT_MS_TO_DECIDE_TO_POPUP = 500; protected ProgressDialog dialog; protected JFrame frame; protected String title; protected Timer taskPollingTimer; protected boolean canceled; protected int minValue; protected int maxValue; protected boolean finished; protected T result; public LongRunningTask(JFrame frame, String title) { this.frame = frame; this.title = title; } /** * Starts current task, and blocks current thread until the task is done. */ public synchronized T startAndWait() { // running from Event Dispatch Thread is bad, as this will block the timers... if (SwingUtilities.isEventDispatchThread()) { throw new CayenneRuntimeException( "Can't block EventDispatchThread. Call 'startAndWait' from another thread."); } start(); if (finished) { return result; } try { wait(); } catch (InterruptedException e) { setCanceled(true); } notifyAll(); return result; } /** * Configures the task to run in a separate thread, and immediately exits the method. * This method is allowed to be invoked from EventDispatchThread. */ public void start() { // prepare... setCanceled(false); this.finished = false; Thread task = new Thread(new Runnable() { public void run() { internalExecute(); } }); Timer progressDisplayTimer = new Timer( DEFAULT_MS_TO_DECIDE_TO_POPUP, new ActionListener() { public void actionPerformed(ActionEvent e) { showProgress(); } }); progressDisplayTimer.setRepeats(false); // start progressDisplayTimer.start(); task.start(); } /** * Starts progress dialog if the task is not finished yet. */ protected synchronized void showProgress() { LOGGER.debug("will show progress..."); if (finished) { return; } int currentValue = getCurrentValue(); if (!isCanceled() && currentValue < getMaxValue()) { LOGGER.debug("task still in progress, will show progress dialog..."); this.dialog = new ProgressDialog(frame, "Progress...", title); this.dialog.getCancelButton().addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setCanceled(true); } }); dialog.getProgressBar().setMinimum(getMinValue()); dialog.getProgressBar().setMaximum(getMaxValue()); updateProgress(); this.taskPollingTimer = new Timer(500, new ActionListener() { public void actionPerformed(ActionEvent e) { updateProgress(); } }); this.taskPollingTimer.start(); this.dialog.setVisible(true); } } /** * Updates current state of the progress dialog. */ protected void updateProgress() { if (isCanceled()) { stop(); return; } dialog.getStatusLabel().setText(getCurrentNote()); JProgressBar progressBar = dialog.getProgressBar(); if (!isIndeterminate()) { progressBar.setValue(getCurrentValue()); progressBar.setIndeterminate(false); } else { progressBar.setIndeterminate(true); } } protected synchronized void stop() { if (taskPollingTimer != null) { taskPollingTimer.stop(); } if (dialog != null) { dialog.dispose(); } // there maybe other threads waiting on this task to finish... finished = true; notifyAll(); } public boolean isFinished() { return finished; } public boolean isCanceled() { return canceled; } public void setCanceled(boolean b) { if (b) { LOGGER.debug("task canceled"); } this.canceled = b; } protected void internalExecute() { try { execute(); } catch (Throwable th) { setCanceled(true); LOGGER.warn("task error", th); } finally { stop(); } } /** * Runs the actual task, consulting "isCanceled()" state to make sure that the task * wasn't canceled. */ protected abstract void execute(); protected abstract String getCurrentNote(); protected abstract int getCurrentValue(); protected abstract boolean isIndeterminate(); public int getMaxValue() { return maxValue; } public void setMaxValue(int maxValue) { this.maxValue = maxValue; } public int getMinValue() { return minValue; } public void setMinValue(int minValue) { this.minValue = minValue; } }