001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
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    package edu.wisc.ssec.mcidasv.chooser;
029    
030    import java.awt.BorderLayout;
031    import java.io.File;
032    
033    import java.text.ParseException;
034    import java.text.SimpleDateFormat;
035    import java.util.Date;
036    import java.util.Vector;
037    
038    import javax.swing.JFileChooser;
039    import javax.swing.JOptionPane;
040    import javax.swing.JPanel;
041    import javax.swing.filechooser.FileFilter;
042    
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    import ucar.unidata.idv.chooser.IdvChooserManager;
047    import ucar.unidata.util.StringUtil;
048    
049    public class SuomiNPPChooser extends FileChooser {
050            
051            private static final long serialVersionUID = 1L;
052            private static final Logger logger = LoggerFactory.getLogger(SuomiNPPChooser.class);
053            private static final long CONSECUTIVE_GRANULE_MAX_GAP_MS = 60000;
054            
055            // date formatter for converting Suomi NPP day/time from file name for consecutive granule check
056        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmSSS");
057    
058        /**
059         * Create the chooser with the given manager and xml
060         *
061         * @param mgr The manager
062         * @param root The xml
063         *
064         */
065        
066        public SuomiNPPChooser(IdvChooserManager mgr, org.w3c.dom.Element root) {
067            super(mgr, root);
068        }
069       
070        /**
071         * Make the file chooser
072         *
073         * @param path   the initial path
074         *
075         * @return  the file chooser
076         */
077        
078        protected JFileChooser doMakeFileChooser(String path) {
079            if (fileChooser == null) {
080                    logger.debug("Creating Suomi NPP File Chooser...");
081                    fileChooser = new SuomiNPPFileChooser(path, this);
082            } else {
083                    logger.debug("2nd call to doMakeFileChooser, why?");
084            }
085            return fileChooser;
086        }
087    
088        /**
089         * Handle the selection of the set of files
090         *
091         * @param files The files the user chose
092         * @param directory The directory they chose them from
093         * @return True if the file was successful
094         * @throws Exception
095         */
096        
097        protected boolean selectFilesInner(File[] files, File directory)
098                throws Exception {
099            if ((files == null) || (files.length == 0)) {
100                userMessage("Please select a file");
101                return false;
102            }
103    
104            // At present, Suomi NPP chooser only allows selecting sets of consecutive granules
105            boolean granulesAreConsecutive = testConsecutiveGranules(files);
106            if (granulesAreConsecutive) {
107                    return super.selectFilesInner(files, directory);
108            } else {
109                    // throw up a dialog to tell user the problem
110                    JOptionPane.showMessageDialog(this, "When selecting multiple granules, they must be consecutive.");
111            }
112            return false;
113        }
114    
115        /**
116         * Test whether a set of files are consecutive Suomi NPP granules,
117         * any sensor.
118         * @param files
119         * @return true if consecutive tests pass for all files
120         */
121        
122        private boolean testConsecutiveGranules(File[] files) {
123            boolean testResult = false;
124            if (files == null) return testResult;
125                    // compare start time of current granule with end time of previous
126            // difference should be very small - under a second
127            long prvTime = -1;
128            testResult = true;
129            for (int i = 0; i < files.length; i++) {
130                if ((files[i] != null) && !files[i].isDirectory()) {
131                    if (files[i].exists()) {
132                            String fileName = files[i].getName(); 
133                            int dateIndex = fileName.lastIndexOf("_d2") + 2;
134                            int timeIndexStart = fileName.lastIndexOf("_t") + 2;
135                            int timeIndexEnd = fileName.lastIndexOf("_e") + 2;
136                            String dateStr = fileName.substring(dateIndex, dateIndex + 8);
137                            String timeStrStart = fileName.substring(timeIndexStart, timeIndexStart + 7);
138                            String timeStrEnd = fileName.substring(timeIndexEnd, timeIndexEnd + 7);
139                            // sanity check on file name lengths
140                            int fnLen = fileName.length();
141                            if ((dateIndex > fnLen) || (timeIndexStart > fnLen) || (timeIndexEnd > fnLen)) {
142                                    logger.warn("unexpected file name length for: " + fileName);
143                                    testResult = false;
144                                    break;
145                            }
146                        // pull start and end time out of file name
147                        Date dS = null;
148                        Date dE = null;
149                        try {
150                                                    dS = sdf.parse(dateStr + timeStrStart);
151                                                    // due to nature of Suomi NPP file name encoding, we need a special
152                                                    // check here - end time CAN roll over to next day, while day part 
153                                                    // does not change.  if this happens, we tweak the date string
154                                                    String endDateStr = dateStr;
155                                                    String startHour = timeStrStart.substring(0, 2);
156                                                    String endHour = timeStrEnd.substring(0, 2);
157                                                    if ((startHour.equals("23")) && (endHour.equals("00"))) {
158                                                            // temporarily convert date to integer, increment, convert back
159                                                            int tmpDate = Integer.parseInt(dateStr);
160                                                            tmpDate++;
161                                                            endDateStr = "" + tmpDate;
162                                                            logger.info("Granule time spanning days case handled ok...");
163                                                    }
164                                                    dE = sdf.parse(endDateStr + timeStrEnd);
165                                            } catch (ParseException e) {
166                                                    logger.error("Not recognized as valid Suomi NPP file name: " + fileName);
167                                                    testResult = false;
168                                                    break;
169                                            }
170                                            long curTime = dS.getTime();
171                                            long endTime = dE.getTime();
172                                            // only check current with previous
173                                            if (prvTime > 0) {
174                                                    // make sure time diff does not exceed allowed threshold
175                                                    // consecutive granules should be less than 1 minute apart
176                                                    if ((curTime - prvTime) > CONSECUTIVE_GRANULE_MAX_GAP_MS) {
177                                                            testResult = false;
178                                                            break;
179                                                    }
180                                            }
181                                            prvTime = endTime;
182                    }
183                }
184            }
185                    return testResult;
186            }
187    
188            /**
189         * Convert the given array of File objects
190         * to an array of String file names. Only
191         * include the files that actually exist.
192         *
193         * @param files Selected files
194         * @return Selected files as Strings
195         */
196        
197        protected String[] getFileNames(File[] files) {
198            if (files == null) {
199                return (String[]) null;
200            }
201            Vector<String> v = new Vector<String>();
202            String fileNotExistsError = "";
203    
204            // NOTE:  If multiple files are selected, then missing files
205            // are not in the files array.  If one file is selected and
206            // it is not there, then it is in the array and file.exists()
207            // is false
208            for (int i = 0; i < files.length; i++) {
209                if ((files[i] != null) && !files[i].isDirectory()) {
210                    if ( !files[i].exists()) {
211                        fileNotExistsError += "File does not exist: " + files[i] + "\n";
212                    } else {
213                        v.add(files[i].toString());
214                    }
215                }
216            }
217    
218            if (fileNotExistsError.length() > 0) {
219                userMessage(fileNotExistsError);
220                return null;
221            }
222    
223            return v.isEmpty()
224                   ? null
225                   : StringUtil.listToStringArray(v);
226        }
227        
228        /**
229         * Get the bottom panel for the chooser
230         * @return the bottom panel
231         */
232        
233        protected JPanel getBottomPanel() {
234            // No bottom panel at present
235            return null;
236        }
237        
238        /**
239         * Get the center panel for the chooser
240         * @return the center panel
241         */
242        
243        protected JPanel getCenterPanel() {
244            JPanel centerPanel = super.getCenterPanel();
245    
246            JPanel jp = new JPanel(new BorderLayout()) {
247                    public void paint(java.awt.Graphics g) {
248                            FileFilter ff = fileChooser.getFileFilter();
249                            if (! (ff instanceof SuomiNPPFilter)) {
250                                    fileChooser.setAcceptAllFileFilterUsed(false);
251                                    fileChooser.setFileFilter(new SuomiNPPFilter());
252                            }
253                            super.paint(g);
254                    }
255            };
256            jp.add(centerPanel);
257    
258            return jp; 
259        }
260        
261    }