001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.chooser;
030
031import java.awt.BorderLayout;
032import java.io.File;
033
034import java.text.ParseException;
035import java.text.SimpleDateFormat;
036import java.util.ArrayList;
037import java.util.Date;
038import java.util.Vector;
039
040import javax.swing.JFileChooser;
041import javax.swing.JOptionPane;
042import javax.swing.JPanel;
043import javax.swing.filechooser.FileFilter;
044
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import edu.wisc.ssec.mcidasv.data.hydra.JPSSUtilities;
049
050import ucar.unidata.idv.chooser.IdvChooserManager;
051import ucar.unidata.util.StringUtil;
052
053public class SuomiNPPChooser extends FileChooser {
054        
055        private static final long serialVersionUID = 1L;
056        private static final Logger logger = LoggerFactory.getLogger(SuomiNPPChooser.class);
057        private static final long CONSECUTIVE_GRANULE_MAX_GAP_MS = 60000;
058        
059        // date formatter for converting Suomi NPP day/time from file name for consecutive granule check
060    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmSSS");
061
062    /**
063     * Create the chooser with the given manager and xml
064     *
065     * @param mgr The manager
066     * @param root The xml
067     *
068     */
069    
070    public SuomiNPPChooser(IdvChooserManager mgr, org.w3c.dom.Element root) {
071        super(mgr, root);
072    }
073   
074    /**
075     * Make the file chooser
076     *
077     * @param path   the initial path
078     *
079     * @return  the file chooser
080     */
081    
082    protected JFileChooser doMakeFileChooser(String path) {
083        if (fileChooser == null) {
084                logger.debug("Creating Suomi NPP File Chooser...");
085                fileChooser = new SuomiNPPFileChooser(path, this);
086        } else {
087                logger.debug("2nd call to doMakeFileChooser, why?");
088        }
089        return fileChooser;
090    }
091
092    /**
093     * Handle the selection of the set of files
094     *
095     * @param files The files the user chose
096     * @param directory The directory they chose them from
097     * @return True if the file was successful
098     * @throws Exception
099     */
100    
101    protected boolean selectFilesInner(File[] files, File directory)
102            throws Exception {
103        if ((files == null) || (files.length == 0)) {
104            userMessage("Please select a file");
105            return false;
106        }
107
108        // make a list of just the file names
109        ArrayList<String> fileNames = new ArrayList<String>();
110        for (int i = 0; i < files.length; i++) {
111                fileNames.add(files[i].getName());
112        }
113        
114        // ensure these files make sense as a set to create a single SNPP data source
115        if (! JPSSUtilities.isValidSet(fileNames)) {
116                JOptionPane.showMessageDialog(null, 
117                        "Unable to group selected data as a single data source.");
118                return false;
119        }
120        
121        // ensure these files make sense as a set to create a single SNPP data source
122        if (! JPSSUtilities.hasCommonGeo(fileNames, directory)) {
123                JOptionPane.showMessageDialog(null, 
124                        "Unable to group selected data as a single data source.");
125                return false;
126        }
127        
128        // At present, Suomi NPP chooser only allows selecting sets of consecutive granules
129        boolean granulesAreConsecutive = testConsecutiveGranules(files);
130        if (granulesAreConsecutive) {
131                return super.selectFilesInner(files, directory);
132        } else {
133                // throw up a dialog to tell user the problem
134                JOptionPane.showMessageDialog(this, "When selecting multiple granules, they must be consecutive.");
135        }
136        return false;
137    }
138
139    /**
140     * Test whether a set of files are consecutive Suomi NPP granules,
141     * any sensor. NOTE: This method works when the file list contains
142     * multiple products ONLY because once we've validate one product,
143     * the time check will be a negative number when comparing the FIRST
144     * granule of product 2 with the LAST granule of product 1. A better
145     * implementation would be to pass in the filename map like the 
146     * one generated in SuomiNPPDataSource constructor.
147     * 
148     * @param files
149     * @return true if consecutive tests pass for all files
150     */
151    
152    private boolean testConsecutiveGranules(File[] files) {
153        boolean testResult = false;
154        if (files == null) return testResult;
155                // compare start time of current granule with end time of previous
156        // difference should be very small - under a second
157        long prvTime = -1;
158        testResult = true;
159        for (int i = 0; i < files.length; i++) {
160            if ((files[i] != null) && !files[i].isDirectory()) {
161                if (files[i].exists()) {
162                        String fileName = files[i].getName(); 
163                        int dateIndex = fileName.lastIndexOf("_d2") + 2;
164                        int timeIndexStart = fileName.lastIndexOf("_t") + 2;
165                        int timeIndexEnd = fileName.lastIndexOf("_e") + 2;
166                        String dateStr = fileName.substring(dateIndex, dateIndex + 8);
167                        String timeStrStart = fileName.substring(timeIndexStart, timeIndexStart + 7);
168                        String timeStrEnd = fileName.substring(timeIndexEnd, timeIndexEnd + 7);
169                        // sanity check on file name lengths
170                        int fnLen = fileName.length();
171                        if ((dateIndex > fnLen) || (timeIndexStart > fnLen) || (timeIndexEnd > fnLen)) {
172                                logger.warn("unexpected file name length for: " + fileName);
173                                testResult = false;
174                                break;
175                        }
176                    // pull start and end time out of file name
177                    Date dS = null;
178                    Date dE = null;
179                    try {
180                                                dS = sdf.parse(dateStr + timeStrStart);
181                                                // due to nature of Suomi NPP file name encoding, we need a special
182                                                // check here - end time CAN roll over to next day, while day part 
183                                                // does not change.  if this happens, we tweak the date string
184                                                String endDateStr = dateStr;
185                                                String startHour = timeStrStart.substring(0, 2);
186                                                String endHour = timeStrEnd.substring(0, 2);
187                                                if ((startHour.equals("23")) && (endHour.equals("00"))) {
188                                                        // temporarily convert date to integer, increment, convert back
189                                                        int tmpDate = Integer.parseInt(dateStr);
190                                                        tmpDate++;
191                                                        endDateStr = "" + tmpDate;
192                                                        logger.info("Granule time spanning days case handled ok...");
193                                                }
194                                                dE = sdf.parse(endDateStr + timeStrEnd);
195                                        } catch (ParseException e) {
196                                                logger.error("Not recognized as valid Suomi NPP file name: " + fileName);
197                                                testResult = false;
198                                                break;
199                                        }
200                                        long curTime = dS.getTime();
201                                        long endTime = dE.getTime();
202                                        // only check current with previous
203                                        if (prvTime > 0) {
204                                                // make sure time diff does not exceed allowed threshold
205                                                // consecutive granules should be less than 1 minute apart
206                                                if ((curTime - prvTime) > CONSECUTIVE_GRANULE_MAX_GAP_MS) {
207                                                        testResult = false;
208                                                        break;
209                                                }
210                                        }
211                                        prvTime = endTime;
212                }
213            }
214        }
215                return testResult;
216        }
217
218        /**
219     * Convert the given array of File objects
220     * to an array of String file names. Only
221     * include the files that actually exist.
222     *
223     * @param files Selected files
224     * @return Selected files as Strings
225     */
226    
227    protected String[] getFileNames(File[] files) {
228        if (files == null) {
229            return (String[]) null;
230        }
231        Vector<String> v = new Vector<String>();
232        String fileNotExistsError = "";
233
234        // NOTE:  If multiple files are selected, then missing files
235        // are not in the files array.  If one file is selected and
236        // it is not there, then it is in the array and file.exists()
237        // is false
238        for (int i = 0; i < files.length; i++) {
239            if ((files[i] != null) && !files[i].isDirectory()) {
240                if ( !files[i].exists()) {
241                    fileNotExistsError += "File does not exist: " + files[i] + "\n";
242                } else {
243                    v.add(files[i].toString());
244                }
245            }
246        }
247
248        if (fileNotExistsError.length() > 0) {
249            userMessage(fileNotExistsError);
250            return null;
251        }
252
253        return v.isEmpty()
254               ? null
255               : StringUtil.listToStringArray(v);
256    }
257    
258    /**
259     * Get the bottom panel for the chooser
260     * @return the bottom panel
261     */
262    
263    protected JPanel getBottomPanel() {
264        // No bottom panel at present
265        return null;
266    }
267    
268    /**
269     * Get the center panel for the chooser
270     * @return the center panel
271     */
272    
273    protected JPanel getCenterPanel() {
274        JPanel centerPanel = super.getCenterPanel();
275
276        JPanel jp = new JPanel(new BorderLayout()) {
277                public void paint(java.awt.Graphics g) {
278                        FileFilter ff = fileChooser.getFileFilter();
279                        if (! (ff instanceof SuomiNPPFilter)) {
280                                fileChooser.setAcceptAllFileFilterUsed(false);
281                                fileChooser.setFileFilter(new SuomiNPPFilter());
282                        }
283                        super.paint(g);
284                }
285        };
286        jp.add(centerPanel);
287
288        return jp; 
289    }
290    
291}