/*
* Copyright (C) 2014 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.productlibrary.rcp.toolviews;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.db.DBQuery;
import org.esa.snap.engine_utilities.db.ProductDB;
import org.esa.snap.engine_utilities.db.ProductEntry;
import org.esa.snap.engine_utilities.db.SQLUtils;
import org.esa.snap.graphbuilder.rcp.utils.DialogUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.ui.UIUtils;
import org.jdesktop.swingx.JXDatePicker;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
*/
public final class DatabasePane extends JPanel {
private final JTextField nameField = new JTextField();
private final JList missionJList = new JList();
private final JList productTypeJList = new JList();
private final JComboBox acquisitionModeCombo = new JComboBox(new String[]{DBQuery.ALL_MODES});
private final JComboBox passCombo = new JComboBox(new String[]{
DBQuery.ALL_PASSES, DBQuery.ASCENDING_PASS, DBQuery.DESCENDING_PASS});
private final JTextField trackField = new JTextField();
private final JXDatePicker startDateBox = new JXDatePicker();
private final JXDatePicker endDateBox = new JXDatePicker();
private final JComboBox polarizationCombo = new JComboBox(new String[]{
DBQuery.ANY, DBQuery.QUADPOL, DBQuery.DUALPOL, DBQuery.HHVV, DBQuery.HHHV, DBQuery.VVVH, "HH", "VV", "HV", "VH"});
private final JComboBox calibrationCombo = new JComboBox(new String[]{
DBQuery.ANY, DBQuery.CALIBRATED, DBQuery.NOT_CALIBRATED});
private final JComboBox orbitCorrectionCombo = new JComboBox(new String[]{
DBQuery.ANY, DBQuery.ORBIT_PRELIMINARY, DBQuery.ORBIT_PRECISE, DBQuery.ORBIT_VERIFIED});
private final JComboBox metadataNameCombo = new JComboBox();
private final JTextField metdataValueField = new JTextField();
private final JTextArea metadataArea = new JTextArea();
private final JButton addMetadataButton = new JButton("+");
private final JButton updateButton = new JButton(UIUtils.loadImageIcon("icons/ViewRefresh16.png"));
private final JTextArea productText = new JTextArea();
private ProductDB db;
private DBQuery dbQuery = new DBQuery();
private ProductEntry[] productEntryList = null;
boolean modifyingCombos = false;
private final List<DatabaseQueryListener> listenerList = new ArrayList<>(1);
public DatabasePane() {
try {
missionJList.setFixedCellWidth(100);
createPanel();
missionJList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
if (modifyingCombos || event.getValueIsAdjusting()) return;
updateProductTypeCombo();
queryDatabase();
}
});
productTypeJList.setFixedCellWidth(100);
productTypeJList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
if (modifyingCombos || event.getValueIsAdjusting()) return;
queryDatabase();
}
});
addComboListener(acquisitionModeCombo);
addComboListener(passCombo);
addComboListener(polarizationCombo);
addComboListener(calibrationCombo);
addComboListener(orbitCorrectionCombo);
addMetadataButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addMetadataText();
}
});
updateButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
queryDatabase();
}
});
} catch (Throwable t) {
handleException(t);
}
}
private void addComboListener(final JComboBox combo) {
combo.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent event) {
if (modifyingCombos || event.getStateChange() == ItemEvent.DESELECTED) return;
queryDatabase();
}
});
}
/**
* Adds a <code>DatabasePaneListener</code>.
*
* @param listener the <code>DatabasePaneListener</code> to be added.
*/
public void addListener(final DatabaseQueryListener listener) {
if (!listenerList.contains(listener)) {
listenerList.add(listener);
}
}
/**
* Removes a <code>DatabasePaneListener</code>.
*
* @param listener the <code>DatabasePaneListener</code> to be removed.
*/
public void removeListener(final DatabaseQueryListener listener) {
listenerList.remove(listener);
}
private void notifyQuery() {
for (final DatabaseQueryListener listener : listenerList) {
listener.notifyNewEntryListAvailable();
}
}
public interface DatabaseQueryListener {
void notifyNewEntryListAvailable();
}
private static void handleException(Throwable t) {
t.printStackTrace();
final SnapApp app = SnapApp.getDefault();
if (app != null) {
Dialogs.showError(t.getMessage());
}
}
private void createPanel() {
setLayout(new GridBagLayout());
final GridBagConstraints gbc = DialogUtils.createGridBagConstraints();
JLabel label;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 0;
gbc.gridy = 0;
this.add(new JLabel("Mission:"), gbc);
gbc.gridx = 1;
this.add(new JLabel("Product Type:"), gbc);
gbc.gridy++;
gbc.gridx = 0;
this.add(new JScrollPane(missionJList), gbc);
gbc.gridx = 1;
this.add(new JScrollPane(productTypeJList), gbc);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Product Name:", nameField);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Acquisition Mode:", acquisitionModeCombo);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Pass:", passCombo);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Track:", trackField);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Start Date:", startDateBox);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "End Date:", endDateBox);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Polarization:", polarizationCombo);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Calibration:", calibrationCombo);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
label = DialogUtils.addComponent(this, gbc, "Orbit Correction:", orbitCorrectionCombo);
label.setHorizontalAlignment(JLabel.RIGHT);
gbc.gridy++;
gbc.gridx = 0;
gbc.gridwidth = 2;
this.add(createFreeSearchPanel(), gbc);
gbc.gridy++;
final JPanel productDetailsPanel = new JPanel(new BorderLayout());
productDetailsPanel.setBorder(BorderFactory.createTitledBorder("Product Details"));
productText.setLineWrap(true);
productText.setRows(4);
productText.setBackground(getBackground());
productDetailsPanel.add(productText, BorderLayout.CENTER);
this.add(productDetailsPanel, gbc);
//DialogUtils.fillPanel(this, gbc);
}
private JPanel createFreeSearchPanel() {
final JPanel freeSearchPanel = new JPanel(new GridBagLayout());
freeSearchPanel.setBorder(BorderFactory.createTitledBorder("Metadata SQL Query"));
final GridBagConstraints gbc = DialogUtils.createGridBagConstraints();
freeSearchPanel.add(metadataNameCombo, gbc);
metadataNameCombo.setPrototypeDisplayValue("123456789012");
gbc.gridx = 1;
freeSearchPanel.add(metdataValueField, gbc);
metdataValueField.setColumns(10);
gbc.gridx = 2;
freeSearchPanel.add(addMetadataButton, gbc);
gbc.gridy++;
gbc.gridx = 0;
gbc.gridwidth = 2;
freeSearchPanel.add(metadataArea, gbc);
metadataArea.setBorder(new LineBorder(Color.BLACK));
metadataArea.setLineWrap(true);
metadataArea.setRows(4);
metadataArea.setToolTipText("Use AND,OR,NOT and =,<,>,<=,>-");
gbc.gridx = 2;
gbc.gridwidth = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
freeSearchPanel.add(updateButton, gbc);
DialogUtils.fillPanel(freeSearchPanel, gbc);
return freeSearchPanel;
}
private void connectToDatabase() throws Exception {
db = ProductDB.instance();
refresh();
}
public ProductDB getDB() {
if (db == null) {
queryDatabase();
}
return db;
}
public void refresh() {
try {
if(!db.isReady())
return;
boolean origState = lockCombos(true);
if (metadataNameCombo.getItemCount() == 0) {
final String[] metadataNames = db.getMetadataNames();
for (String name : metadataNames) {
metadataNameCombo.insertItemAt(name, metadataNameCombo.getItemCount());
}
}
updateMissionCombo();
lockCombos(origState);
} catch (Throwable t) {
handleException(t);
}
}
private boolean lockCombos(boolean flag) {
final boolean origState = modifyingCombos;
modifyingCombos = flag;
return origState;
}
private void updateMissionCombo() throws SQLException {
boolean origState = lockCombos(true);
try {
missionJList.removeAll();
missionJList.setListData(SQLUtils.prependString(DBQuery.ALL_MISSIONS, db.getAllMissions()));
} finally {
lockCombos(origState);
}
}
private void updateProductTypeCombo() {
boolean origState = lockCombos(true);
try {
productTypeJList.removeAll();
acquisitionModeCombo.removeAllItems();
final String selectedMissions[] = toStringArray(missionJList.getSelectedValuesList());
String[] productTypeList;
String[] acquisitionModeList;
if (StringUtils.contains(selectedMissions, DBQuery.ALL_MISSIONS)) {
productTypeList = db.getAllProductTypes();
acquisitionModeList = db.getAllAcquisitionModes();
} else {
productTypeList = db.getProductTypes(selectedMissions);
acquisitionModeList = db.getAcquisitionModes(selectedMissions);
}
productTypeJList.setListData(SQLUtils.prependString(DBQuery.ALL_PRODUCT_TYPES, productTypeList));
final String[] modeItems = SQLUtils.prependString(DBQuery.ALL_MODES, acquisitionModeList);
for (String item : modeItems) {
acquisitionModeCombo.addItem(item);
}
} catch (Throwable t) {
handleException(t);
} finally {
lockCombos(origState);
}
}
private static String[] toStringArray(List<String> list) {
return list.toArray(new String[list.size()]);
}
public void setBaseDir(final File dir) {
dbQuery.setBaseDir(dir);
if (db != null)
queryDatabase();
}
private void addMetadataText() {
final String name = (String) metadataNameCombo.getSelectedItem();
final String value = metdataValueField.getText();
if (!name.isEmpty() && !value.isEmpty()) {
if (metadataArea.getText().length() > 0) {
metadataArea.append(" AND ");
}
if(value.matches("-?\\d+(\\.\\d+)?")) { // isNumeric
metadataArea.append(name + "=" + value + " ");
} else {
metadataArea.append(name + "='" + value + "' ");
}
}
}
private static Calendar getDate(final JXDatePicker dateField) {
final Date date = dateField.getDate();
if(date == null)
return null;
final Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
private void setData() {
dbQuery.setSelectedMissions(toStringArray(missionJList.getSelectedValuesList()));
dbQuery.setSelectedProductTypes(toStringArray(productTypeJList.getSelectedValuesList()));
dbQuery.setSelectedName(nameField.getText());
dbQuery.setSelectedAcquisitionMode((String) acquisitionModeCombo.getSelectedItem());
dbQuery.setSelectedPass((String) passCombo.getSelectedItem());
dbQuery.setSelectedTrack(trackField.getText());
dbQuery.setStartEndDate(getDate(startDateBox), getDate(endDateBox));
dbQuery.setSelectedPolarization((String) polarizationCombo.getSelectedItem());
dbQuery.setSelectedCalibration((String) calibrationCombo.getSelectedItem());
dbQuery.setSelectedOrbitCorrection((String) orbitCorrectionCombo.getSelectedItem());
dbQuery.clearMetadataQuery();
dbQuery.setFreeQuery(metadataArea.getText());
}
public void queryDatabase() {
if (db == null) {
try {
connectToDatabase();
} catch (Throwable t) {
handleException(t);
}
}
if(metadataNameCombo.getItemCount() == 0) {
refresh();
}
setData();
if (productEntryList != null) {
ProductEntry.dispose(productEntryList);
}
try {
if(db.isReady()) {
productEntryList = dbQuery.queryDatabase(db);
notifyQuery();
}
} catch (Throwable t) {
handleException(t);
}
}
public void setSelectionRect(final GeoPos[] selectionBox) {
dbQuery.setSelectionRect(selectionBox);
dbQuery.setReturnAllIfNoIntersection(true);
queryDatabase();
}
public ProductEntry[] getProductEntryList() {
return productEntryList;
}
public DBQuery getDBQuery() {
setData();
return dbQuery;
}
public void findSlices(final int dataTakeId) {
metadataArea.setText(AbstractMetadata.data_take_id+"="+dataTakeId);
dbQuery.setSelectionRect(null);
queryDatabase();
metadataArea.setText("");
}
public void setDBQuery(final DBQuery query) throws Exception {
if (query == null) return;
dbQuery = query;
if (db == null) {
connectToDatabase();
}
boolean origState = lockCombos(true);
try {
missionJList.setSelectedIndices(findIndices(missionJList, dbQuery.getSelectedMissions()));
updateProductTypeCombo();
productTypeJList.setSelectedIndices(findIndices(productTypeJList, dbQuery.getSelectedProductTypes()));
acquisitionModeCombo.setSelectedItem(dbQuery.getSelectedAcquisitionMode());
passCombo.setSelectedItem(dbQuery.getSelectedPass());
if(dbQuery.getStartDate() != null) {
startDateBox.setDate(dbQuery.getStartDate().getTime());
}
if(dbQuery.getEndDate() != null) {
endDateBox.setDate(dbQuery.getEndDate().getTime());
}
polarizationCombo.setSelectedItem(dbQuery.getSelectedPolarization());
calibrationCombo.setSelectedItem(dbQuery.getSelectedCalibration());
orbitCorrectionCombo.setSelectedItem(dbQuery.getSelectedOrbitCorrection());
metadataArea.setText(dbQuery.getFreeQuery());
} finally {
lockCombos(origState);
}
}
private static int[] findIndices(final JList list, final String[] values) {
final int size = list.getModel().getSize();
final List<Integer> indices = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
final String str = (String) list.getModel().getElementAt(i);
if (StringUtils.contains(values, str)) {
indices.add(i);
}
}
final int[] intIndices = new int[indices.size()];
for (int i = 0; i < indices.size(); ++i) {
intIndices[i] = indices.get(i);
}
return intIndices;
}
public void updateProductSelectionText(final ProductEntry[] selections) {
if (selections != null && selections.length == 1) {
final ProductEntry entry = selections[0];
final StringBuilder text = new StringBuilder(255);
final MetadataElement absRoot = entry.getMetadata();
final String sampleType = absRoot.getAttributeString(AbstractMetadata.SAMPLE_TYPE, AbstractMetadata.NO_METADATA_STRING);
final ProductData.UTC acqTime = absRoot.getAttributeUTC(AbstractMetadata.first_line_time, AbstractMetadata.NO_METADATA_UTC);
final int absOrbit = absRoot.getAttributeInt(AbstractMetadata.ABS_ORBIT, AbstractMetadata.NO_METADATA);
final int relOrbit = absRoot.getAttributeInt(AbstractMetadata.REL_ORBIT, AbstractMetadata.NO_METADATA);
final String map = absRoot.getAttributeString(AbstractMetadata.map_projection, AbstractMetadata.NO_METADATA_STRING).trim();
final int cal = absRoot.getAttributeInt(AbstractMetadata.abs_calibration_flag, AbstractMetadata.NO_METADATA);
final int tc = absRoot.getAttributeInt(AbstractMetadata.is_terrain_corrected, AbstractMetadata.NO_METADATA);
final int coreg = absRoot.getAttributeInt(AbstractMetadata.coregistered_stack, AbstractMetadata.NO_METADATA);
text.append(entry.getName());
text.append("\n\n");
text.append(entry.getMission());
text.append("\n");
text.append(entry.getAcquisitionMode() + " " + entry.getProductType() +" "+ sampleType + '\n');
text.append(acqTime.format());
text.append('\n');
text.append("Orbit: " + absOrbit);
if (relOrbit != AbstractMetadata.NO_METADATA)
text.append(" Track: " + relOrbit);
text.append('\n');
text.append("Size: "+getSizeString(entry.getFileSize()));
text.append('\n');
if (!map.isEmpty()) {
text.append(map);
text.append('\n');
}
if (cal == 1)
text.append("Calibrated ");
if (coreg == 1)
text.append("Coregistered ");
if (tc == 1)
text.append("Terrain Corrected ");
productText.setText(text.toString());
} else if(selections != null && selections.length > 1) {
long totalSize = 0;
for(ProductEntry entry : selections) {
totalSize += entry.getFileSize();
}
final StringBuilder text = new StringBuilder(255);
text.append(selections.length + " products");
text.append("\n");
text.append("Total: "+getSizeString(totalSize));
productText.setText(text.toString());
} else {
productText.setText("");
}
}
private final static double MB = 1024*1024, GB = 1024, TB = 1024*1024;
private final static DecimalFormat df = new DecimalFormat("#.00");
private static String getSizeString(long bytes) {
double mb = bytes/MB;
String unit;
double value;
if(mb > TB) {
value = mb/TB;
unit = "TB";
} else if(mb > GB) {
value = mb/GB;
unit = "GB";
} else {
value = mb;
unit = "MB";
}
return df.format(value) + " " + unit;
}
}