package com.opendoorlogistics.studio.scripts.execution; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.logging.Logger; import javax.swing.SwingUtilities; import javax.swing.Timer; import com.opendoorlogistics.api.ExecutionReport; import com.opendoorlogistics.api.ODLApi; import com.opendoorlogistics.api.components.ProcessingApi; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLDatastoreAlterable; import com.opendoorlogistics.api.tables.ODLDatastoreUndoable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.core.utils.LoggerUtils; import com.opendoorlogistics.core.utils.ui.SwingUtils; import com.opendoorlogistics.studio.appframe.AbstractAppFrame; import com.opendoorlogistics.studio.dialogs.ProgressDialog; import com.opendoorlogistics.studio.internalframes.HasInternalFrames.FramePlacement; import com.opendoorlogistics.studio.internalframes.ProgressFrame; import com.opendoorlogistics.studio.panels.ProgressPanel.ProgressReporter; public abstract class DatastoreModifierTask { private final static Logger LOGGER = Logger.getLogger(DatastoreModifierTask.class.getName()); private volatile ExecutionReport result; private final AbstractAppFrame appFrame; private volatile ODLDatastoreAlterable<? extends ODLTableAlterable> workingDatastoreCopy; protected volatile ProgressReporter progressReporter; private File referenceFile; /** * Execute the entire task on a background thread. This method uses the EDT thread when needed. */ public void executeNonEDT() { ExecutionUtils.throwIfEDT(); if(!initialiseNonEDTExecution()){ return; } // Take copy on EDT try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { // Copy the datastore in EDT so we never get a half-written copy workingDatastoreCopy = getEDTDatastore().deepCopyWithShallowValueCopy(true); LOGGER.info(LoggerUtils.addPrefix(" - took deep copy of datastore for script execution.")); // Also get the reference file if(appFrame.getLoadedDatastore()!=null){ referenceFile = appFrame.getLoadedDatastore().getFile(); } } }); } catch (Exception e) { throw new RuntimeException(e); } // Create the progress on EDT as well but don't wait for it as it blocks the execution.. // If we're doing an automatic refresh wait longer to show the progress dialog, but still show // it in-case the refresh takes a long time.... if (isAllowsUserInteraction()) { Timer timer = new Timer(getDelayMillisBeforeProgress(), new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startProgress(); } }); timer.setRepeats(false); timer.start(); } else { // launch on EDT without delay, but don't wait for method to finish as the progress dialog blocks the EDIT if modal SwingUtilities.invokeLater(new Runnable() { @Override public void run() { startProgress(); } }); } result = executeNonEDTAfterInitialisation(); // Finish up on the EDT try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { finishOnEDT(result); } }); } catch (Exception e) { throw new RuntimeException(e); } } DatastoreModifierTask(AbstractAppFrame appFrame) { this.appFrame = appFrame; } protected int getDelayMillisBeforeProgress() { return 0; } protected boolean isAllowsUserInteraction() { return false; } /** * */ protected void startProgress() { ExecutionUtils.throwIfNotOnEDT(); // only start progress if (a) we haven't already finished, (b) we're not in a modal and (c) haven't already launched it if (result == null && isProgressShowable() && progressReporter == null) { String title = getProgressTitle(); if (isAllowsUserInteraction()) { // modeless ProgressFrame progressFrame = new ProgressFrame(title, true,true); appFrame.addInternalFrame(progressFrame, FramePlacement.CENTRAL_RANDOMISED); try { // start it minimised so its visible but non-intrusive if(isProgressMinimised()){ progressFrame.setIcon(true); } } catch (Exception e) { } progressFrame.getProgressPanel().start(); progressReporter = progressFrame; } else { // modal ProgressDialog<Void> dlg = new ProgressDialog<>(appFrame, title, true,true); dlg.setLocationRelativeTo(appFrame); progressReporter = dlg; dlg.start(); } } } protected boolean isProgressMinimised(){ return true; } protected boolean isProgressShowable(){ return true; } protected abstract String getProgressTitle(); protected abstract ExecutionReport executeNonEDTAfterInitialisation(); protected boolean initialiseNonEDTExecution() { return true; } protected void finishOnEDT(ExecutionReport result) { ExecutionUtils.throwIfNotOnEDT(); finishOnEDTBeforeDatastoreMerge(result); // Try merging the script result back into the primary datastore. // The primary datastore is only modified on the EDT. if (!result.isFailed()) { // merge has a transaction so don't need to start one here if (!mergeResultWithEDTDatastore()) { result.setFailed("Failed to merge the result of running the script with the datastore." + System.lineSeparator() + "This may happen if the data or tables change whilst a script is running."); } } finishOnEDTAfterDatastoreMerge(result); // close the progress dialog if (progressReporter != null) { progressReporter.dispose(); } // show message if failed if (result.isFailed()) { ExecutionUtils.showScriptFailureBox(appFrame, false, getFailureWindowTitle(), result); } finishOnEDTAfterExecutionReportShown(result); } protected void finishOnEDTAfterExecutionReportShown(ExecutionReport report){ } protected abstract boolean mergeResultWithEDTDatastore(); protected abstract String getFailureWindowTitle(); protected void finishOnEDTAfterDatastoreMerge(ExecutionReport result) { } protected void finishOnEDTBeforeDatastoreMerge(ExecutionReport result) { } final protected ODLDatastoreUndoable<? extends ODLTableAlterable> getEDTDatastore() { if(appFrame.getLoadedDatastore()!=null){ return appFrame.getLoadedDatastore().getDs(); } return null; } protected ODLDatastoreAlterable<? extends ODLTableAlterable> getNonEDTDatastoreCopy(){ return workingDatastoreCopy; } protected ProcessingApi createProcessingApi(){ return new ProcessingApi() { @Override public ODLApi getApi() { return appFrame.getApi(); } @Override public boolean isFinishNow() { if(progressReporter!=null){ return progressReporter.getProgressPanel().isFinishedNow(); } return false; } @Override public boolean isCancelled() { if(progressReporter!=null){ return progressReporter.getProgressPanel().isCancelled(); } return false; } @Override public void postStatusMessage(String s) { SwingUtils.invokeLaterOnEDT(new Runnable() { @Override public void run() { if (progressReporter != null) { progressReporter.getProgressPanel().setText(s); } } }); } @Override public void logWarning(String warning) { // TODO Auto-generated method stub } }; } protected ODLApi getApi(){ return appFrame.getApi(); } protected File getReferenceFile(){ return referenceFile; } }