/*
* Copyright (C) 2015 by Array Systems Computing Inc. http://www.array.ca
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.graphbuilder.rcp.dialogs;
import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.core.SubProgressMonitor;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.gpf.GPF;
import org.esa.snap.core.gpf.graph.GraphException;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.engine_utilities.db.CommonReaders;
import org.esa.snap.engine_utilities.db.ProductEntry;
import org.esa.snap.engine_utilities.gpf.ProcessTimeMonitor;
import org.esa.snap.engine_utilities.util.MemUtils;
import org.esa.snap.engine_utilities.util.ResourceUtils;
import org.esa.snap.graphbuilder.rcp.dialogs.support.FileTable;
import org.esa.snap.graphbuilder.rcp.dialogs.support.GraphDialog;
import org.esa.snap.graphbuilder.rcp.dialogs.support.GraphExecuter;
import org.esa.snap.graphbuilder.rcp.dialogs.support.GraphNode;
import org.esa.snap.graphbuilder.rcp.dialogs.support.GraphsMenu;
import org.esa.snap.graphbuilder.rcp.progress.LabelBarProgressMonitor;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.ui.AppContext;
import org.esa.snap.ui.FileChooserFactory;
import org.esa.snap.ui.ModelessDialog;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTabbedPane;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Provides the dialog for executing a graph on a list of products
*/
public class BatchGraphDialog extends ModelessDialog implements GraphDialog, LabelBarProgressMonitor.ProgressBarListener {
protected final static Path defaultGraphPath = ResourceUtils.getGraphFolder("");
private final AppContext appContext;
private final String baseTitle;
protected final List<GraphExecuter> graphExecutorList = new ArrayList<>(10);
private final List<BatchProcessListener> listenerList = new ArrayList<>(1);
protected ProductSetPanel productSetPanel;
private JTabbedPane tabbedPane;
private JLabel statusLabel, bottomStatusLabel;
private JPanel progressPanel;
private JProgressBar progressBar;
private JLabel progressMsgLabel;
private LabelBarProgressMonitor progBarMonitor;
private Map<File, File[]> slaveFileMap;
private final boolean closeOnDone;
private boolean skipExistingTargetFiles;
private boolean isProcessing;
protected File graphFile;
protected boolean openProcessedProducts;
public BatchGraphDialog(final AppContext theAppContext, final String title, final String helpID,
final boolean closeOnDone) {
super(theAppContext.getApplicationWindow(), title, ID_YES | ID_APPLY_CLOSE_HELP, helpID);
this.appContext = theAppContext;
this.baseTitle = title;
this.closeOnDone = closeOnDone;
openProcessedProducts = true;
setContent(createUI());
if (getJDialog().getJMenuBar() == null) {
final GraphsMenu operatorMenu = new GraphsMenu(getJDialog(), this);
getJDialog().setJMenuBar(operatorMenu.createDefaultMenu());
}
super.getJDialog().setMinimumSize(new Dimension(400, 300));
}
private JPanel createUI() {
final JPanel mainPanel = new JPanel(new BorderLayout(4, 4));
tabbedPane = new JTabbedPane();
tabbedPane.addChangeListener(new ChangeListener() {
public void stateChanged(final ChangeEvent e) {
ValidateAllNodes();
}
});
mainPanel.add(tabbedPane, BorderLayout.CENTER);
// status
statusLabel = new JLabel("");
statusLabel.setForeground(new Color(255, 0, 0));
mainPanel.add(statusLabel, BorderLayout.NORTH);
bottomStatusLabel = new JLabel("");
getButtonPanel().add(bottomStatusLabel, 0);
// progress Bar
progressBar = new JProgressBar();
progressBar.setName(getClass().getName() + "progressBar");
progressBar.setStringPainted(true);
progressPanel = new JPanel();
progressPanel.setLayout(new BorderLayout(2, 2));
progressPanel.add(progressBar, BorderLayout.CENTER);
progressMsgLabel = new JLabel();
progressPanel.add(progressMsgLabel, BorderLayout.NORTH);
progBarMonitor = new LabelBarProgressMonitor(progressBar, progressMsgLabel);
progBarMonitor.addListener(this);
final JButton progressCancelBtn = new JButton("Cancel");
progressCancelBtn.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
CancelProcessing();
}
});
progressPanel.add(progressCancelBtn, BorderLayout.EAST);
progressPanel.setVisible(false);
mainPanel.add(progressPanel, BorderLayout.SOUTH);
productSetPanel = new ProductSetPanel(appContext, null, new FileTable(), true, true);
tabbedPane.add("I/O Parameters", productSetPanel);
getButton(ID_APPLY).setText("Run");
getButton(ID_YES).setText("Load Graph");
return mainPanel;
}
@Override
public int show() {
return super.show();
}
@Override
public void hide() {
if (progBarMonitor != null) {
progBarMonitor.setCanceled(true);
}
notifyMSG(BatchProcessListener.BatchMSG.CLOSE);
super.hide();
}
@Override
public void onApply() {
if (isProcessing) return;
productSetPanel.onApply();
skipExistingTargetFiles = productSetPanel.isSkippingExistingTargetFiles();
try {
DoProcessing();
} catch (Exception e) {
statusLabel.setText(e.getMessage());
bottomStatusLabel.setText("");
}
}
public boolean isProcessing() {
return isProcessing;
}
public void addListener(final BatchProcessListener listener) {
if (!listenerList.contains(listener)) {
listenerList.add(listener);
}
}
public void removeListener(final BatchProcessListener listener) {
listenerList.remove(listener);
}
private void notifyMSG(final BatchProcessListener.BatchMSG msg, final String text) {
for (final BatchProcessListener listener : listenerList) {
listener.notifyMSG(msg, text);
}
}
private void notifyMSG(final BatchProcessListener.BatchMSG msg) {
for (final BatchProcessListener listener : listenerList) {
listener.notifyMSG(msg, productSetPanel.getFileList(), getAllBatchProcessedTargetProducts());
}
}
/**
* OnLoad
*/
@Override
protected void onYes() {
LoadGraph();
}
public void setInputFiles(final File[] productFileList) {
productSetPanel.setProductFileList(productFileList);
}
public void setInputFiles(final ProductEntry[] productEntryList) {
productSetPanel.setProductEntryList(productEntryList);
}
public void setTargetFolder(final File path) {
productSetPanel.setTargetFolder(path);
}
public void LoadGraph() {
if (isProcessing) return;
final File file = getFilePath(this.getContent(), "Graph File");
if (file != null) {
LoadGraph(file);
}
}
public void LoadGraph(final File file) {
try {
graphFile = file;
initGraphs();
addGraphTabs("", true);
setTitle(file.getName());
} catch (Exception e) {
SnapApp.getDefault().handleError("Unable to load graph " + file.toString(), e);
}
}
@Override
public void setTitle(final String title) {
super.setTitle(baseTitle + " : " + title);
}
public boolean canSaveGraphs() {
return false;
}
public void SaveGraph() {
}
public String getGraphAsString() throws GraphException, IOException {
if (!graphExecutorList.isEmpty()) {
return graphExecutorList.get(0).getGraphAsString();
}
return "";
}
private static File getFilePath(Component component, String title) {
final File graphPath = new File(getPref("batch.last_graph_path", defaultGraphPath.toFile().getAbsolutePath()));
final JFileChooser chooser = FileChooserFactory.getInstance().createFileChooser(graphPath);
chooser.setMultiSelectionEnabled(false);
chooser.setDialogTitle(title);
if (chooser.showDialog(component, "ok") == JFileChooser.APPROVE_OPTION) {
final File file = chooser.getSelectedFile();
setPref("batch.last_graph_path", file.getAbsolutePath());
return file;
}
return null;
}
private static String getPref(final String id, final String defaultStr) {
return SnapApp.getDefault().getPreferences().get(id, defaultStr);
}
private static void setPref(final String id, final String value) {
SnapApp.getDefault().getPreferences().put(id, value);
}
@Override
protected void onClose() {
CancelProcessing();
super.onClose();
}
void initGraphs() {
try {
deleteGraphs();
createGraphs();
} catch (Exception e) {
statusLabel.setText(e.getMessage());
bottomStatusLabel.setText("");
}
}
/**
* Validates the input and then call the GPF to execute the graph
*/
private void DoProcessing() {
if (ValidateAllNodes()) {
MemUtils.freeAllMemory();
progressBar.setValue(0);
final SwingWorker processThread = new ProcessThread(progBarMonitor);
processThread.execute();
} else {
if (statusLabel.getText() != null && !statusLabel.getText().isEmpty())
showErrorDialog(statusLabel.getText());
}
}
public void notifyProgressStart() {
progressPanel.setVisible(true);
}
public void notifyProgressDone() {
progressPanel.setVisible(false);
}
private void CancelProcessing() {
if (progBarMonitor != null)
progBarMonitor.setCanceled(true);
}
private void deleteGraphs() {
for (GraphExecuter gex : graphExecutorList) {
gex.ClearGraph();
}
graphExecutorList.clear();
}
/**
* Loads a new graph from a file
*
* @param executer the GraphExcecuter
* @param graphFile the graph file to load
* @param addUI add a user interface
*/
protected void LoadGraph(final GraphExecuter executer, final File file, final boolean addUI) {
try {
executer.loadGraph(new FileInputStream(file), file, addUI, true);
} catch (Exception e) {
showErrorDialog(e.getMessage());
}
}
private boolean ValidateAllNodes() {
if (isProcessing) return false;
if (productSetPanel == null)
return false;
if (graphExecutorList.isEmpty())
return false;
boolean result;
statusLabel.setText("");
try {
cloneGraphs();
assignParameters();
// first graph must pass
result = graphExecutorList.get(0).InitGraph();
} catch (Exception e) {
statusLabel.setText(e.getMessage());
bottomStatusLabel.setText("");
result = false;
}
return result;
}
private void openTargetProducts() {
final File[] fileList = getAllBatchProcessedTargetProducts();
if (fileList != null && fileList.length > 0) {
for (File file : fileList) {
try {
final Product product = CommonReaders.readProduct(file);
if (product != null) {
appContext.getProductManager().addProduct(product);
}
} catch (IOException e) {
showErrorDialog(e.getMessage());
}
}
}
}
protected ProductSetPanel getProductSetPanel() {
return productSetPanel;
}
public void setTargetProductNameSuffix(final String suffix) {
productSetPanel.setTargetProductNameSuffix(suffix);
}
void createGraphs() throws GraphException {
try {
final GraphExecuter graphEx = new GraphExecuter();
LoadGraph(graphEx, graphFile, true);
graphExecutorList.add(graphEx);
} catch (Exception e) {
throw new GraphException(e.getMessage());
}
}
private void addGraphTabs(final String title, final boolean addUI) {
if (graphExecutorList.isEmpty()) {
return;
}
tabbedPane.setSelectedIndex(0);
while (tabbedPane.getTabCount() > 1) {
tabbedPane.remove(tabbedPane.getTabCount() - 1);
}
final GraphExecuter graphEx = graphExecutorList.get(0);
for (GraphNode n : graphEx.GetGraphNodes()) {
if (n.GetOperatorUI() == null)
continue;
if (n.getNode().getOperatorName().equals("Read") || n.getNode().getOperatorName().equals("Write")
|| n.getNode().getOperatorName().equals("ProductSet-Reader")) {
n.setOperatorUI(null);
continue;
}
if (addUI) {
String tabTitle = title;
if (tabTitle.isEmpty())
tabTitle = n.getOperatorName();
tabbedPane.addTab(tabTitle, null,
n.GetOperatorUI().CreateOpTab(n.getOperatorName(), n.getParameterMap(), appContext),
n.getID() + " Operator");
}
}
}
public void setSlaveFileMap(Map<File, File[]> fileMap) {
slaveFileMap = fileMap;
}
protected void assignParameters() {
final File[] fileList = productSetPanel.getFileList();
int graphIndex = 0;
for (File f : fileList) {
String name;
final Object o = productSetPanel.getValueAt(graphIndex, 0);
if (o instanceof String)
name = (String) o;
else
name = FileUtils.getFilenameWithoutExtension(f);
final File targetFolder = productSetPanel.getTargetFolder();
if (!targetFolder.exists()) {
if (!targetFolder.mkdirs()) {
SystemUtils.LOG.severe("Unable to create folders in " + targetFolder);
}
}
final File targetFile = new File(targetFolder, name);
final String targetFormat = productSetPanel.getTargetFormat();
setIO(graphExecutorList.get(graphIndex),
"Read", f,
"Write", targetFile, targetFormat);
if (slaveFileMap != null) {
final File[] slaveFiles = slaveFileMap.get(f);
if (slaveFiles != null) {
setSlaveIO(graphExecutorList.get(graphIndex),
"ProductSet-Reader", f, slaveFiles);
}
}
++graphIndex;
}
}
protected static void setIO(final GraphExecuter graphEx,
final String readID, final File readPath,
final String writeID, final File writePath,
final String format) {
final GraphNode readNode = graphEx.getGraphNodeList().findGraphNodeByOperator(readID);
if (readNode != null) {
graphEx.setOperatorParam(readNode.getID(), "file", readPath.getAbsolutePath());
}
if (writeID != null) {
final GraphNode writeNode = graphEx.getGraphNodeList().findGraphNodeByOperator(writeID);
if (writeNode != null) {
if (format != null)
graphEx.setOperatorParam(writeNode.getID(), "formatName", format);
graphEx.setOperatorParam(writeNode.getID(), "file", writePath.getAbsolutePath());
}
}
}
/**
* For coregistration
*
* @param graphEx the graph executer
* @param productSetID the product set reader
* @param masterFile master file
* @param slaveFiles slave file list
*/
protected static void setSlaveIO(final GraphExecuter graphEx, final String productSetID,
final File masterFile, final File[] slaveFiles) {
final GraphNode productSetNode = graphEx.getGraphNodeList().findGraphNodeByOperator(productSetID);
if (productSetNode != null) {
StringBuilder str = new StringBuilder(masterFile.getAbsolutePath());
for (File slaveFile : slaveFiles) {
str.append(',');
str.append(slaveFile.getAbsolutePath());
}
graphEx.setOperatorParam(productSetNode.getID(), "fileList", str.toString());
}
}
protected void cloneGraphs() throws Exception {
final GraphExecuter graphEx = graphExecutorList.get(0);
for (int graphIndex = 1; graphIndex < graphExecutorList.size(); ++graphIndex) {
final GraphExecuter cloneGraphEx = graphExecutorList.get(graphIndex);
cloneGraphEx.ClearGraph();
}
graphExecutorList.clear();
graphExecutorList.add(graphEx);
final File[] fileList = productSetPanel.getFileList();
for (int graphIndex = 1; graphIndex < fileList.length; ++graphIndex) {
final GraphExecuter cloneGraphEx = new GraphExecuter();
LoadGraph(cloneGraphEx, graphFile, false);
graphExecutorList.add(cloneGraphEx);
// copy UI parameter to clone
final List<GraphNode> cloneGraphNodes = cloneGraphEx.GetGraphNodes();
for (GraphNode cloneNode : cloneGraphNodes) {
final GraphNode node = graphEx.getGraphNodeList().findGraphNode(cloneNode.getID());
if (node != null)
cloneNode.setOperatorUI(node.GetOperatorUI());
}
}
}
public File[] getAllBatchProcessedTargetProducts() {
final List<File> targetFileList = new ArrayList<>();
for (GraphExecuter graphEx : graphExecutorList) {
targetFileList.addAll(graphEx.getProductsToOpenInDAT());
}
return targetFileList.toArray(new File[targetFileList.size()]);
}
void cleanUpTempFiles() {
}
/////
private class ProcessThread extends SwingWorker<Boolean, Object> {
private final ProgressMonitor pm;
private ProcessTimeMonitor timeMonitor = new ProcessTimeMonitor();
private boolean errorOccured = false;
final List<String> errMsgs = new ArrayList<>();
public ProcessThread(final ProgressMonitor pm) {
this.pm = pm;
}
@Override
protected Boolean doInBackground() throws Exception {
pm.beginTask("Processing Graph...", graphExecutorList.size());
try {
timeMonitor.start();
isProcessing = true;
final File[] existingFiles = productSetPanel.getTargetFolder().listFiles();
final File[] fileList = productSetPanel.getFileList();
int graphIndex = 0;
for (GraphExecuter graphEx : graphExecutorList) {
if (pm.isCanceled()) break;
final String nOfm = String.valueOf(graphIndex + 1) + " of " + graphExecutorList.size() + ' ';
final String statusText = "Processing " + nOfm + fileList[graphIndex].getName();
statusLabel.setText(statusText);
notifyMSG(BatchProcessListener.BatchMSG.UPDATE, statusText);
if (shouldSkip(graphEx, existingFiles)) {
pm.worked(1);
continue;
}
try {
MemUtils.freeAllMemory();
graphEx.InitGraph();
graphEx.executeGraph(SubProgressMonitor.create(pm, 1));
graphEx.disposeGraphContext();
} catch (Exception e) {
SystemUtils.LOG.severe(e.getMessage());
String filename = fileList[graphIndex].getName();
errMsgs.add(filename + " -> " + e.getMessage());
}
graphEx = null;
++graphIndex;
// calculate time remaining
final long duration = timeMonitor.getCurrentDuration();
final double timePerGraph = duration / (double) graphIndex;
final long timeLeft = (long) (timePerGraph * (graphExecutorList.size() - graphIndex));
if (timeLeft > 0) {
String remainingStr = "Estimated " + ProcessTimeMonitor.formatDuration(timeLeft) + " remaining";
if (!errMsgs.isEmpty())
remainingStr += " (Errors occurred)";
bottomStatusLabel.setText(remainingStr);
}
}
MemUtils.freeAllMemory();
} catch (Exception e) {
SystemUtils.LOG.severe(e.getMessage());
if (e.getMessage() != null && !e.getMessage().isEmpty())
statusLabel.setText(e.getMessage());
else
statusLabel.setText(e.toString());
errorOccured = true;
} finally {
statusLabel.setText("Batch processing complete");
isProcessing = false;
pm.done();
}
return true;
}
@Override
public void done() {
if (!errorOccured) {
final long duration = timeMonitor.stop();
statusLabel.setText("Processing completed in " + ProcessTimeMonitor.formatDuration(duration));
bottomStatusLabel.setText("");
if (openProcessedProducts) {
openTargetProducts();
}
}
if (!errMsgs.isEmpty()) {
final StringBuilder msg = new StringBuilder("The following errors occurred:\n");
for (String errStr : errMsgs) {
msg.append(errStr);
msg.append('\n');
}
showErrorDialog(msg.toString());
}
cleanUpTempFiles();
notifyMSG(BatchProcessListener.BatchMSG.DONE);
if (closeOnDone)
close();
if (SnapApp.getDefault().getPreferences().getBoolean(GPF.BEEP_AFTER_PROCESSING_PROPERTY, false)) {
Toolkit.getDefaultToolkit().beep();
}
}
private boolean shouldSkip(final GraphExecuter graphEx, final File[] existingFiles) {
if (skipExistingTargetFiles) {
final File[] targetFiles = graphEx.getPotentialOutputFiles();
if (existingFiles != null) {
boolean allTargetsExist = true;
for (File targetFile : targetFiles) {
boolean fileExists = false;
for (File existingFile : existingFiles) {
if (existingFile.getAbsolutePath().startsWith(targetFile.getAbsolutePath())) {
fileExists = true;
break;
}
}
if (!fileExists) {
allTargetsExist = false;
break;
}
}
return allTargetsExist;
}
}
return false;
}
}
public interface BatchProcessListener {
public enum BatchMSG {DONE, UPDATE, CLOSE}
public void notifyMSG(final BatchMSG msg, final File[] inputFileList, final File[] targetFileList);
public void notifyMSG(final BatchMSG msg, final String text);
}
}