package org.esa.snap.opendap.ui;
import com.bc.ceres.binding.ValidationException;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.opendap.datamodel.OpendapLeaf;
import org.esa.snap.opendap.utils.PatternProvider;
import org.esa.snap.rcp.util.DateTimePicker;
import org.esa.snap.util.StringUtils;
import org.esa.snap.util.TimeStampExtractor;
import org.jdesktop.swingx.JXDatePicker;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.units.DateRange;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Logger;
public class TimeRangeFilter implements FilterComponent {
public static final Logger LOG = Logger.getLogger(TimeRangeFilter.class.getName());
private JComboBox<String> datePatternComboBox;
private JComboBox<String> fileNamePatternComboBox;
private JXDatePicker startDateButton;
private JXDatePicker stopDateButton;
private JButton applyButton;
private JCheckBox filterCheckBox;
TimeStampExtractor timeStampExtractor;
List<FilterChangeListener> listeners;
List<JLabel> labels;
Date startDate;
Date endDate;
public TimeRangeFilter(final JCheckBox filterCheckBox) {
this.filterCheckBox = filterCheckBox;
final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Calendar utc = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Date date = utc.getTime();
startDateButton = new JXDatePicker(date);
startDateButton.setFormats(dateFormat);
stopDateButton = new JXDatePicker(date);
stopDateButton.setFormats(dateFormat);
final int width = 150;
final Dimension ps = startDateButton.getPreferredSize();
final Dimension comboBoxDimension = new Dimension(width, ps.height);
setJComponentSize(comboBoxDimension, startDateButton);
setJComponentSize(comboBoxDimension, stopDateButton);
datePatternComboBox = new JComboBox<>();
setJComponentSize(comboBoxDimension, datePatternComboBox);
datePatternComboBox.setEditable(true);
fileNamePatternComboBox = new JComboBox<>();
setJComponentSize(comboBoxDimension, fileNamePatternComboBox);
fileNamePatternComboBox.setEditable(true);
initPatterns();
final UIUpdater uiUpdater = new UIUpdater();
filterCheckBox.addActionListener(e -> {
updateUIState();
if (timeStampExtractor != null) {
fireFilterChangedEvent();
}
});
startDateButton.addPropertyChangeListener(DateTimePicker.COMMIT_KEY, evt -> updateUIState());
startDateButton.addPropertyChangeListener(DateTimePicker.COMMIT_KEY, evt -> validateDateChooser(startDateButton));
stopDateButton.addPropertyChangeListener(DateTimePicker.COMMIT_KEY, evt -> updateUIState());
stopDateButton.addPropertyChangeListener(DateTimePicker.COMMIT_KEY, evt -> validateDateChooser(stopDateButton));
datePatternComboBox.addActionListener(uiUpdater);
fileNamePatternComboBox.addActionListener(uiUpdater);
listeners = new ArrayList<>();
labels = new ArrayList<>();
applyButton = new JButton("Apply");
applyButton.addActionListener(e -> {
if (StringUtils.isNotNullAndNotEmpty(datePatternComboBox.getEditor().getItem().toString())
&& StringUtils.isNotNullAndNotEmpty(fileNamePatternComboBox.getEditor().getItem().toString())) {
timeStampExtractor = new TimeStampExtractor(datePatternComboBox.getSelectedItem().toString(),
fileNamePatternComboBox.getSelectedItem().toString());
} else {
timeStampExtractor = null;
}
startDate = startDateButton.getDate();
endDate = stopDateButton.getDate();
updateUIState();
applyButton.setEnabled(false);
fireFilterChangedEvent();
});
}
private void updateUIState() {
final boolean isSelected = filterCheckBox.isSelected();
datePatternComboBox.setEnabled(isSelected);
fileNamePatternComboBox.setEnabled(isSelected);
startDateButton.setEnabled(isSelected);
stopDateButton.setEnabled(isSelected);
for (JLabel label : labels) {
label.setEnabled(isSelected);
}
final String datePattern = datePatternComboBox.getSelectedItem().toString();
final String fileNamePattern = fileNamePatternComboBox.getSelectedItem().toString();
final boolean hasStartDate = startDateButton.getDate() != null;
final boolean hasEndDate = stopDateButton.getDate() != null;
final boolean patternProvided = !("".equals(datePattern) || "".equals(fileNamePattern));
if (isSelected && (patternProvided || (!hasStartDate && !hasEndDate))) {
applyButton.setEnabled(true);
} else {
applyButton.setEnabled(false);
}
}
private void initPatterns() {
datePatternComboBox.addItem("");
fileNamePatternComboBox.addItem("");
for (String datePattern : PatternProvider.DATE_PATTERNS) {
datePatternComboBox.addItem(datePattern);
}
for (String fileNamePattern : PatternProvider.FILENAME_PATTERNS) {
fileNamePatternComboBox.addItem(fileNamePattern);
}
}
private void setJComponentSize(Dimension comboBoxDimension, JComponent comboBox) {
comboBox.setPreferredSize(comboBoxDimension);
comboBox.setMinimumSize(comboBoxDimension);
}
@Override
public JComponent getUI() {
final JPanel filterUI = new JPanel(new GridBagLayout());
final GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 0;
gbc.insets.bottom = 4;
gbc.insets.right = 4;
gbc.anchor = GridBagConstraints.WEST;
JLabel datePatternLabel = new JLabel("Date pattern:");
filterUI.add(datePatternLabel, gbc);
gbc.gridx++;
filterUI.add(datePatternComboBox, gbc);
gbc.gridx = 0;
gbc.gridy++;
JLabel filenamePatternLabel = new JLabel("Filename pattern:");
filterUI.add(filenamePatternLabel, gbc);
gbc.gridx++;
filterUI.add(fileNamePatternComboBox, gbc);
gbc.gridx = 0;
gbc.gridy++;
JLabel startDateLabel = new JLabel("Start date:");
filterUI.add(startDateLabel, gbc);
gbc.gridx++;
filterUI.add(startDateButton, gbc);
gbc.gridx = 0;
gbc.gridy++;
JLabel stopDateLabel = new JLabel("Stop date:");
filterUI.add(stopDateLabel, gbc);
gbc.gridx++;
filterUI.add(stopDateButton, gbc);
gbc.gridy++;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.EAST;
filterUI.add(applyButton, gbc);
labels.add(datePatternLabel);
labels.add(filenamePatternLabel);
labels.add(startDateLabel);
labels.add(stopDateLabel);
updateUIState();
return filterUI;
}
@Override
public boolean accept(OpendapLeaf leaf) {
DateRange timeCoverage = leaf.getDataset().getTimeCoverage();
if (timeCoverage != null) {
return fitsToServerSpecifiedTimeRange(timeCoverage);
}
return timeStampExtractor == null || fitsToUserSpecifiedTimeRange(leaf);
}
private boolean fitsToServerSpecifiedTimeRange(DateRange dateRange) {
if (startDate == null && endDate == null) {
return true;
} else if (startDate == null) {
return endsAtOrBeforeEndDate(dateRange);
} else if (endDate == null) {
return startsAtOrAfterStartDate(dateRange);
}
return startsAtOrAfterStartDate(dateRange) && endsAtOrBeforeEndDate(dateRange);
}
private boolean endsAtOrBeforeEndDate(DateRange dateRange) {
return dateRange.getEnd().getCalendarDate().equals(CalendarDate.of(endDate)) || dateRange.getEnd().before(endDate);
}
private boolean startsAtOrAfterStartDate(DateRange dateRange) {
return dateRange.getStart().getCalendarDate().equals(CalendarDate.of(startDate)) || dateRange.getStart().after(startDate);
}
private boolean fitsToUserSpecifiedTimeRange(OpendapLeaf leaf) {
try {
final ProductData.UTC[] timeStamps = timeStampExtractor.extractTimeStamps(leaf.getName());
final boolean startDateEqualsEndDate =
timeStamps[0].getAsDate().getTime() == timeStamps[1].getAsDate().getTime();
if (startDateEqualsEndDate) {
timeStamps[1] = null;
}
boolean fileHasEndDate = timeStamps[1] != null;
boolean userHasStartDate = startDate != null;
boolean userHasEndDate = endDate != null;
if (userHasStartDate) {
if (startDate.after(timeStamps[0].getAsDate())) {
return false;
}
}
if (userHasEndDate) {
if (fileHasEndDate) {
if (endDate.before(timeStamps[1].getAsDate())) {
return false;
}
} else {
if (endDate.before(timeStamps[0].getAsDate())) {
return false;
}
}
}
return true;
} catch (ValidationException e) {
return true;
}
}
@Override
public void addFilterChangeListener(FilterChangeListener listener) {
listeners.add(listener);
}
private void fireFilterChangedEvent() {
for (FilterChangeListener listener : listeners) {
listener.filterChanged();
}
}
private class UIUpdater implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
updateUIState();
}
}
private void validateDateChooser(JXDatePicker button) {
Date startDate = startDateButton.getDate();
Date endDate = stopDateButton.getDate();
if (startDate == null || endDate == null) {
return;
}
if (startDate.after(endDate)) {
if (button.equals(startDateButton)) {
startDateButton.setDate(endDate);
LOG.info("Start date after end date: Set start date to end date.");
} else if (button.equals(stopDateButton)) {
stopDateButton.setDate(startDate);
LOG.info("Start date after end date: Set end date to start date.");
}
}
}
}