001/*
002 * $Id: NPPChooser.java,v 1.10 2011/03/24 16:06:31 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030package edu.wisc.ssec.mcidasv.chooser;
031
032import edu.wisc.ssec.mcidasv.data.hydra.JPSSUtilities;
033
034import java.awt.Component;
035import java.awt.Container;
036
037import java.io.File;
038import java.io.IOException;
039
040import java.util.ArrayList;
041import java.util.StringTokenizer;
042import java.util.Vector;
043
044import javax.swing.JFileChooser;
045import javax.swing.JLabel;
046import javax.swing.JPanel;
047import javax.swing.filechooser.FileFilter;
048
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import ucar.nc2.NetcdfFile;
053
054import ucar.unidata.idv.chooser.IdvChooserManager;
055import ucar.unidata.util.StringUtil;
056
057public class NPPChooser extends FileChooser {
058        
059        private static final String PRODUCT_SEPARATOR = "-";
060        private static final String FIELD_SEPARATOR = "_";
061        
062        private static final long serialVersionUID = 1L;
063        private static final Logger logger = LoggerFactory.getLogger(NPPChooser.class);
064    
065    private NPPFilter nppf = null;
066
067    /**
068     * Create the chooser with the given manager and xml
069     *
070     * @param mgr The manager
071     * @param root The xml
072     *
073     */
074    
075    public NPPChooser(IdvChooserManager mgr, org.w3c.dom.Element root) {
076        super(mgr, root);
077    }
078   
079    /**
080     * Make the file chooser
081     *
082     * @param path   the initial path
083     *
084     * @return  the file chooser
085     */
086    
087    protected JFileChooser doMakeFileChooser(String path) {
088        return new NPPFileChooser(path);
089    }
090    
091    /**
092     * Is this an NPP Product Data file?
093     * 
094     * @param f name of file to test
095     * @return
096     */
097    
098    private boolean isNPPFile(File f) {
099                
100        // This regular expression matches an NPP Data Product as defined by the 
101        // spec in CDFCB-X Volume 1, Page 21
102        String nppRegex =
103                        // Product Id, Multiple (ex: VSSTO-GATMO-VSLTO)
104                        "(\\w\\w\\w\\w\\w-)*" + 
105                        // Product Id, Single (ex: VSSTO)
106                        "\\w\\w\\w\\w\\w" + FIELD_SEPARATOR +
107                        // Spacecraft Id (ex: npp)
108                        "\\w\\w\\w" + FIELD_SEPARATOR +
109                        // Data Start Date (ex: dYYYYMMDD)
110                        "d20[0-3]\\d[0-1]\\d[0-3]\\d" + FIELD_SEPARATOR +
111                        // Data Start Time (ex: tHHMMSSS)
112                        "t[0-2]\\d[0-5]\\d[0-6]\\d\\d" + FIELD_SEPARATOR +
113                        // Data Stop Time (ex: eHHMMSSS)
114                        "e[0-2]\\d[0-5]\\d[0-6]\\d\\d" + FIELD_SEPARATOR +
115                        // Orbit Number (ex: b00015)
116                        "b\\d\\d\\d\\d\\d" + FIELD_SEPARATOR +
117                        // Creation Date (ex: cYYYYMMDDHHMMSSSSSSSS)
118                        "c20[0-3]\\d[0-1]\\d[0-3]\\d[0-2]\\d[0-5]\\d[0-6]\\d\\d\\d\\d\\d\\d\\d" + FIELD_SEPARATOR +
119                        // Origin (ex: navo)
120                        "\\w\\w\\w\\w" + FIELD_SEPARATOR +
121                        // Domain (ex: ops)
122                        "\\w\\w\\w" + 
123                        // HDF5 suffix
124                        ".h5";
125        
126        boolean isNPP = false;
127        
128        String fileNameRelative = f.getName();
129        String fileNameAbsolute = f.getParent() + File.separatorChar + f.getName();
130        logger.trace("examining filename: " + fileNameRelative);
131        
132        // null or empty filename
133        if ((fileNameRelative == null) || (fileNameRelative.equals(""))) return isNPP;
134        
135        // see if relative filename matches the NPP regular expression  
136        if (fileNameRelative.matches(nppRegex)) {
137                isNPP = true;
138                logger.trace(fileNameRelative + " matches NPP regex");
139        // don't go any further if file does not match NPP data product regex
140        } else {
141                return isNPP;
142        }
143        
144        // make sure a geolocation file is present if it does look like a valid NPP data file!
145        
146        // if a geo dataset is embedded in a multi-product file, we can call it good without
147        // having to open any files.  Just look for a geo product id in the filename.
148        // HOWEVER - if it's a single-product GEO-only file, disqualify that
149        String prodStr = fileNameRelative.substring(0, fileNameRelative.indexOf(FIELD_SEPARATOR));
150        StringTokenizer st = new StringTokenizer(prodStr, PRODUCT_SEPARATOR);
151        int numTokens = st.countTokens();
152        logger.trace("check for embedded GEO, tokenizing: " + prodStr);
153        while (st.hasMoreTokens()) {
154                String singleProd = st.nextToken();
155                logger.trace("Next token: " + singleProd);
156                for (int i = 0; i < JPSSUtilities.geoProductIDs.length; i++) {
157                        if (singleProd.equals(JPSSUtilities.geoProductIDs[i])) {
158                                logger.trace("Found embedded GEO: " + singleProd);
159                                // if it's a single-product file, disqualify this as a GEO-only file!
160                                if (numTokens == 1) {
161                                        return false;
162                                } else {
163                                        if (numTokens > 1) {
164                                                return true;
165                                        }
166                                }
167                        }
168                }
169        }
170        
171                // looks like a standalone product - will have to look for separate geo file
172        // first, create the corresponding GEO loc file name
173                String geoProductID = null;
174                
175                boolean noGeo = false;
176                NetcdfFile ncfile = null;
177                try {
178                        logger.info("Trying to open file: " + fileNameAbsolute);
179                        ncfile = NetcdfFile.open(fileNameAbsolute);
180                        ucar.nc2.Attribute a = ncfile.findGlobalAttribute("N_GEO_Ref");
181                        // if no GEO attribute, we can't visualize this NPP data file, don't include it
182                        if (a == null) {
183                                noGeo = true;
184                        } else {
185                        logger.info("Value of GEO global attribute: " + a.getStringValue());
186                        // in the newest data from GRAVITE server, attribute is entire file name
187                        // if this is detected, no translation/mapping needed
188                        if (a.getStringValue().endsWith("h5")) {
189                                geoProductID = a.getStringValue();
190                        } else {
191                                geoProductID = JPSSUtilities.mapGeoRefToProductID(a.getStringValue());
192                                // we may not have a mapping for every product
193                                if (geoProductID == null) noGeo = true;
194                        }
195                        logger.info("Value of corresponding Product ID: " + geoProductID);
196                        }
197                } catch (Exception e) {
198                        logger.error("Exception during open file: " + fileNameAbsolute);
199                        e.printStackTrace();
200                } finally {
201                        try {
202                                ncfile.close();
203                        } catch (IOException ioe) {
204                                ioe.printStackTrace();
205                        }
206                }
207                
208                // if no geolocation global attribute found, skip this file
209                if (noGeo) {
210                        isNPP = false;
211                } else {
212                
213                        // ok, we know what the geo file is supposed to be, but is it present in this directory?
214                        String geoFilename = fileNameAbsolute.substring(0, fileNameAbsolute.lastIndexOf(File.separatorChar) + 1);
215                        // check if we have the whole file name or just the prefix
216                        if (geoProductID.endsWith("h5")) {
217                                geoFilename += geoProductID;
218                        } else {
219                                geoFilename += geoProductID;
220                                geoFilename += fileNameAbsolute.substring(fileNameAbsolute.lastIndexOf(File.separatorChar) + 6);
221                        }
222                        File geoFile = new File(geoFilename);
223                        
224                        if (geoFile.exists()) {
225                                logger.info("GEO file FOUND: " + geoFilename);
226                            isNPP = true;
227                        } else {
228                                logger.info("GEO file NOT found: " + geoFilename);
229                                isNPP = false;
230                        }    
231                        
232                }
233        
234        return isNPP;
235    }
236    
237   
238    /* NPPFilter */
239    public class NPPFilter extends FileFilter {
240
241        // maintain an array of "seen" patterns, so we only identify data
242        // once for a particular type and time (instead of for each segment).
243        ArrayList<String> seenPatterns = new ArrayList<String>();
244        
245        String extraFilter = "";
246        
247        public NPPFilter(String extraFilter) {
248                super();
249                if (extraFilter != null) {
250                        this.extraFilter = extraFilter;
251                }
252        }
253        
254        // Accept all directories and all NPP files.
255        public boolean accept(File f) {
256            if (f.isDirectory()) {
257                return true;
258            }
259
260            if (isNPPFile(f)) {
261                return true;
262            } else {
263                return false;
264            }
265
266        }
267
268        // The description of this filter
269        public String getDescription() {
270            return "NPP Data";
271        }
272        
273        // change the additional filter string
274        public void setExtraFilter(String newFilter) {
275                if (newFilter != null) {
276                        extraFilter = newFilter;
277                        seenPatterns.clear();
278                }
279        }
280        
281    }
282        
283    /**
284     * An extension of JFileChooser
285     *
286     * @author Tommy Jasmin
287     */
288    
289    public class NPPFileChooser extends JFileChooser {
290
291        /**
292                 * default for serializable class
293                 */
294                private static final long serialVersionUID = 1L;
295
296                /**
297         * Create the file chooser
298         *
299         * @param path   the initial path
300         */
301        public NPPFileChooser(String path) {
302            super(path);
303            setControlButtonsAreShown(false);
304            setMultiSelectionEnabled(true);
305            setAcceptAllFileFilterUsed(false);
306            processChildren(this);
307        }
308
309        private void processChildren(Container c) {
310                Component [] components = c.getComponents();
311                if (components != null) {
312                        // loop through all components, looking for the JLabel children of 
313                        // components we want to remove
314                        for (int i = 0; i < components.length; i++) {
315                                if (components[i] instanceof JLabel) {
316                                        String text = ((JLabel) components[i]).getText();
317                                        if (text.equals("File Name:")) {
318                                                hideChildren((Container) components[i].getParent());
319                                                continue;
320                                        }
321                                        if (text.equals("Files of Type:")) {
322                                                hideChildren((Container) components[i].getParent());
323                                                continue;
324                                        }
325                                }
326                                // now check this component for any children
327                                processChildren((Container) components[i]);
328                        }
329                }
330        }
331        
332        private void hideChildren(Container c) {
333                Component [] components = c.getComponents();
334                for (int i = 0; i < components.length; i++) {
335                        components[i].setVisible(false);
336                }
337                c.setVisible(false);
338        }
339
340        /**
341         * Approve the selection
342         */
343        public void approveSelection() {
344            NPPChooser.this.doLoad();
345        }
346
347        /**
348         * Cancel the selection
349         */
350        public void cancelSelection() {
351            closeChooser();
352        }
353
354        /**
355         * Set the selected files
356         *
357         * @param selectedFiles  the selected files
358         */
359        public void setSelectedFiles(File[] selectedFiles) {
360                super.setSelectedFiles(selectedFiles);
361            setHaveData( !((selectedFiles == null) || (selectedFiles.length == 0)));
362        }
363    }
364
365    /**
366     * Handle the selection of the set of files
367     *
368     * @param files The files the user chose
369     * @param directory The directory they chose them from
370     * @return True if the file was successful
371     * @throws Exception
372     */
373    
374    protected boolean selectFilesInner(File[] files, File directory)
375            throws Exception {
376        if ((files == null) || (files.length == 0)) {
377            userMessage("Please select a file");
378            return false;
379        }
380
381        return super.selectFilesInner(files, directory);
382    }
383
384    /**
385     * Convert the given array of File objects
386     * to an array of String file names. Only
387     * include the files that actually exist.
388     *
389     * @param files Selected files
390     * @return Selected files as Strings
391     */
392    
393    protected String[] getFileNames(File[] files) {
394        if (files == null) {
395            return (String[]) null;
396        }
397        Vector<String> v = new Vector<String>();
398        String fileNotExistsError = "";
399
400        // NOTE:  If multiple files are selected, then missing files
401        // are not in the files array.  If one file is selected and
402        // it is not there, then it is in the array and file.exists()
403        // is false
404        for (int i = 0; i < files.length; i++) {
405            if ((files[i] != null) && !files[i].isDirectory()) {
406                if ( !files[i].exists()) {
407                    fileNotExistsError += "File does not exist: " + files[i] + "\n";
408                } else {
409                    v.add(files[i].toString());
410                }
411            }
412        }
413
414        if (fileNotExistsError.length() > 0) {
415            userMessage(fileNotExistsError);
416            return null;
417        }
418
419        return v.isEmpty()
420               ? null
421               : StringUtil.listToStringArray(v);
422    }
423    
424    /**
425     * Get the bottom panel for the chooser
426     * @return the bottom panel
427     */
428    
429    protected JPanel getBottomPanel() {
430        // No bottom panel at present
431        return null;
432    }
433    
434    /**
435     * Get the center panel for the chooser
436     * @return the center panel
437     */
438    
439    protected JPanel getCenterPanel() {
440        JPanel centerPanel = super.getCenterPanel();
441        
442        fileChooser.setAcceptAllFileFilterUsed(false);
443        
444        String extraFilter = "";
445        nppf = new NPPFilter(extraFilter);
446        fileChooser.setFileFilter(nppf);
447
448        return centerPanel;
449    }
450    
451}