001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
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.adde;
030
031import static javax.swing.GroupLayout.DEFAULT_SIZE;
032import static javax.swing.GroupLayout.PREFERRED_SIZE;
033import static javax.swing.GroupLayout.Alignment.BASELINE;
034import static javax.swing.GroupLayout.Alignment.LEADING;
035import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
036
037import java.awt.IllegalComponentStateException;
038import java.awt.Point;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.text.SimpleDateFormat;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collections;
045import java.util.Date;
046import java.util.Enumeration;
047import java.util.Hashtable;
048import java.util.List;
049import java.util.SortedSet;
050import java.util.TreeSet;
051
052import javax.swing.GroupLayout;
053import javax.swing.JButton;
054import javax.swing.JComboBox;
055import javax.swing.JComponent;
056import javax.swing.JDialog;
057import javax.swing.JLabel;
058import javax.swing.JPanel;
059import javax.swing.ListSelectionModel;
060
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063import org.w3c.dom.Element;
064
065import edu.wisc.ssec.mcidas.McIDASUtil;
066import edu.wisc.ssec.mcidas.adde.AddePointDataReader;
067import edu.wisc.ssec.mcidas.adde.DataSetInfo;
068
069import ucar.unidata.data.DataSelection;
070import visad.DateTime;
071import visad.VisADException;
072
073import ucar.unidata.data.AddeUtil;
074import ucar.unidata.data.point.AddePointDataSource;
075import ucar.unidata.idv.chooser.IdvChooserManager;
076import ucar.unidata.idv.chooser.adde.AddeServer;
077import ucar.unidata.ui.DateTimePicker;
078import ucar.unidata.ui.symbol.StationModel;
079import ucar.unidata.ui.symbol.StationModelManager;
080import ucar.unidata.util.GuiUtils;
081import ucar.unidata.util.Misc;
082import ucar.unidata.util.TwoFacedObject;
083import ucar.visad.UtcDate;
084
085import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
086import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width;
087
088/**
089 * Selection widget for ADDE point data
090 * 
091 * @version $Revision$ $Date$
092 */
093public class AddePointDataChooser extends AddeChooser {
094
095    /** Logging object. Use it! */
096    private static final Logger logger = LoggerFactory.getLogger(AddePointDataChooser.class);
097
098    /**
099     * Property for the dataset name key.
100     * @see edu.wisc.ssec.mcidasv.chooser.adde.AddeChooser#getDataSetName()
101     */
102    public static String DATASET_NAME_KEY = "name";
103
104    /** Property for the data type. */
105    public static String DATA_TYPE = "ADDE.POINT.V";
106
107    /** Are we currently reading times */
108    private Object readTimesTask;
109    
110    /** box and label for the relative time */
111    protected JLabel relTimeIncLabel;
112    protected JComboBox relTimeIncBox;
113
114    /** the relative time increment */
115    private float relativeTimeIncrement = 1.0f;
116    
117    /** archive date */
118    protected String archiveDay = null;
119
120    /** archive day button and label */
121    protected JLabel archiveDayLabel;
122    protected JButton archiveDayBtn;
123
124    /** archive date formatter */
125    private SimpleDateFormat archiveDayFormatter;
126
127    /** station model manager */
128    private StationModelManager stationModelManager;
129    
130    /** allowed descriptor prefix */
131    protected String descriptorsAllowPrefix = "";
132        
133    protected boolean firstTime = true;
134    protected boolean retry = true;
135    
136    /** Possibly ask for times a second time if the first sampling doesn't get any */
137    private boolean gotObs = false;
138    protected boolean tryWithoutSampling = false;
139        
140    /**
141     * Create a chooser for Adde POINT data
142     *
143     * @param mgr The chooser manager
144     * @param root The chooser.xml node
145     */
146    public AddePointDataChooser(IdvChooserManager mgr, Element root) {
147        super(mgr, root);
148                                
149        this.stationModelManager = getIdv().getStationModelManager();
150
151        relTimeIncLabel = new JLabel(" Interval:");
152        relTimeIncBox = new JComboBox();
153        relTimeIncBox.setToolTipText("Set the increment between relative times");
154        relTimeIncBox.addActionListener(new ActionListener() {
155            @Override public void actionPerformed(ActionEvent ae) {
156                JComboBox box = (JComboBox) ae.getSource();
157                if (GuiUtils.anySelected(box)) {
158                    setRelativeTimeIncrement(getRelBoxValue());
159                }
160            }
161        });
162        McVGuiUtils.setComponentWidth(relTimeIncBox, Width.ONEHALF);
163        
164        descriptorsAllowPrefix = "";
165        
166        archiveDayBtn = GuiUtils.makeImageButton(
167            "/auxdata/ui/icons/calendar_edit.png", this, "getArchiveDay", null,
168            true);
169        archiveDayBtn.setToolTipText("Select a day for archive datasets");
170        archiveDayLabel = new JLabel("Select day:");
171        archiveDayFormatter = new SimpleDateFormat(UtcDate.YMD_FORMAT);
172    }
173    
174    /**
175     * Do server connection stuff... override this with type-specific methods
176     */
177    @Override protected void readFromServer() {
178        archiveDay = null;
179        if (archiveDayLabel != null) {
180            archiveDayLabel.setText("Select day:");
181        }
182        super.readFromServer();
183    }
184
185    /**
186     *  Generate a list of image descriptors for the descriptor list.
187     */
188    @Override protected void readDescriptors() {
189        try {
190            StringBuffer buff = getGroupUrl(REQ_DATASETINFO, getGroup());
191            buff.append("&type=").append(getDataType());
192            DataSetInfo dsinfo = new DataSetInfo(buff.toString());
193            descriptorTable = dsinfo.getDescriptionTable();
194            
195            // Only show descriptorsAllowPrefix if set
196            for (Enumeration e = descriptorTable.keys(); e.hasMoreElements();) {
197                Object key = e.nextElement();
198                String str = (String)descriptorTable.get(key);
199                if (!descriptorsAllowPrefix.isEmpty() && str.indexOf(descriptorsAllowPrefix) != 0) {
200                    descriptorTable.remove(key);
201                }
202            }
203            
204            String[] names = new String[descriptorTable.size()];
205            Enumeration enumeration = descriptorTable.keys();
206            for (int i = 0; enumeration.hasMoreElements(); i++) {
207                Object thisElement = enumeration.nextElement();
208                if (!isLocalServer()) {
209                    names[i] = descriptorTable.get(thisElement).toString() + nameSeparator + thisElement.toString();
210                } else {
211                    names[i] = thisElement.toString();
212                }
213            }
214            Arrays.sort(names);
215            setDescriptors(names);
216            setState(STATE_CONNECTED);
217        } catch (Exception e) {
218            handleConnectionError(e);
219        }
220    }
221
222    /**
223     * Load in an ADDE point data set based on the {@code PropertyChangeEvent}.
224     */
225    @Override public void doLoadInThread() {
226        showWaitCursor();
227        try {
228            StationModel selectedStationModel = getSelectedStationModel();
229            String source = getRequestUrl();
230
231            // make properties Hashtable to hand the station name
232            // to the AddeProfilerDataSource
233            Hashtable ht = new Hashtable();
234            getDataSourceProperties(ht);
235            ht.put(DataSelection.PROP_CHOOSERTIMEMATCHING, getDoTimeDrivers());
236            ht.put(AddePointDataSource.PROP_STATIONMODELNAME,
237                   selectedStationModel.getName());
238            ht.put(DATASET_NAME_KEY, getDescriptor());
239            ht.put(DATA_NAME_KEY, getDataName());
240            if (source.contains(AddeUtil.RELATIVE_TIME)) {
241                ht.put(AddeUtil.NUM_RELATIVE_TIMES, getRelativeTimeIndices());
242                ht.put(AddeUtil.RELATIVE_TIME_INCREMENT, getRelativeTimeIncrement());
243            }
244
245            if (getDoAbsoluteTimes()) {
246              ht.put(AddeUtil.ABSOLUTE_TIMES, getSelectedAbsoluteTimes());
247            }
248
249            makeDataSource(source, DATA_TYPE, ht);
250            saveServerState();
251        } catch (Exception excp) {
252            logException("Unable to open ADDE point dataset", excp);
253        }
254        showNormalCursor();
255        // uncheck the check box every time click the add source button
256        drivercbx.setSelected(false);
257        enableTimeWidgets();
258        setDoTimeDrivers(false);
259    }
260        
261    /**
262     * Show the archive dialog. This method is not meant to be called but is
263     * public by reason of implementation (or insanity).
264     */
265    public void getArchiveDay() {
266        final JDialog dialog = GuiUtils.createDialog("Set Archive Day", true);
267        final DateTimePicker dtp = new DateTimePicker((Date) null, false);
268        if (archiveDay != null) {
269            if (archiveDayFormatter == null) {
270                archiveDayFormatter = new SimpleDateFormat(UtcDate.YMD_FORMAT);
271            }
272            Date d = null;
273            try {
274                d = archiveDayFormatter.parse(archiveDay);
275                dtp.setDate(d);
276            } catch (Exception e) {
277                logException("parsing archive day " + archiveDay, e);
278            }
279        }
280
281        ActionListener listener = new ActionListener() {
282            public void actionPerformed(ActionEvent ae) {
283                String cmd = ae.getActionCommand();
284                if (cmd.equals(GuiUtils.CMD_REMOVE)) {
285                    archiveDay = null;
286                    archiveDayLabel.setText("Select day:");
287                    setDoAbsoluteTimes(true);
288                    descriptorChanged();
289                } else if (cmd.equals(GuiUtils.CMD_OK)) {
290                    try {
291                        DateTime dt = new DateTime(dtp.getDate());
292                        String myDay = UtcDate.getYMD(dt);
293                        // archiveDayLabel.setText(UtcDate.formatUtcDate(dt,
294                        // "MMM dd, yyyy"));
295                        archiveDayLabel.setText(myDay);
296                    } catch (Exception e) {
297                        logger.error("problem while setting archive day label", e);
298                    }
299                    // System.out.println("archiveDay = " + archiveDay);
300                    setDoAbsoluteTimes(true);
301                    descriptorChanged();
302                }
303                dialog.dispose();
304            }
305        };
306
307        JPanel buttons = GuiUtils.makeButtons(listener, new String[] {
308                GuiUtils.CMD_OK, GuiUtils.CMD_REMOVE, GuiUtils.CMD_CANCEL });
309
310        JComponent contents = GuiUtils.topCenterBottom(GuiUtils.inset(GuiUtils
311                .lLabel("Please select a day for this dataset:"), 10), GuiUtils
312                .inset(dtp, 10), buttons);
313        Point p = new Point(200, 200);
314        if (archiveDayBtn != null) {
315            try {
316                p = archiveDayBtn.getLocationOnScreen();
317            } catch (IllegalComponentStateException ice) {
318                logger.error("archive day button in illegal state", ice);
319            }
320        }
321        dialog.setLocation(p);
322        dialog.getContentPane().add(contents);
323        dialog.pack();
324        dialog.setVisible(true);
325    }
326
327    /**
328     * Get the selected station model.
329     *
330     * @return StationModel to use: defined by defaultModels list in ctor
331     */
332    public StationModel getSelectedStationModel() {
333        StationModel returnModel = null;
334        if (isUpperAir()) {
335            returnModel = this.stationModelManager.getStationModel("Observations>Upper Air");
336        } else if (isSynoptic()) {
337            returnModel = this.stationModelManager.getStationModel("Observations>SYNOP");
338        } else {
339            returnModel = this.stationModelManager.getStationModel("Observations>METAR");
340        }
341        return returnModel;
342    }
343    
344    /**
345     * Add the interval selector to the component.
346     * @return superclass component with extra stuff
347     */
348    @Override protected JPanel makeTimesPanel() {
349        JComponent extra1 = getExtraTimeComponentRelative();
350        GuiUtils.enableTree(extra1, false);
351        JComponent extra2 = getExtraTimeComponentAbsolute();
352        JPanel timesPanel = super.makeTimesPanel(extra1, extra2);
353        return timesPanel;
354    }
355    
356    /**
357     * Get the extra time widget, but built in a different way.
358     * Designed to be put into a GroupLayout
359     *
360     * @return Extra time widget
361     */
362    protected JComponent getExtraTimeComponentRelative() {
363        TwoFacedObject[] intervals = { 
364            new TwoFacedObject("30 minute", .5f),
365            new TwoFacedObject("Hourly", 1f),
366            new TwoFacedObject("Three hourly", 3f),
367            new TwoFacedObject("Six hourly", 6f),
368            new TwoFacedObject("12 hourly", 12f),
369            new TwoFacedObject("24 hourly", 24f)
370        };
371
372        GuiUtils.setListData(relTimeIncBox, intervals);
373        if (relTimeIncBox.getItemCount()>=2) relTimeIncBox.setSelectedIndex(1);
374        
375        return McVGuiUtils.makeLabeledComponent(relTimeIncLabel, relTimeIncBox, McVGuiUtils.Position.LEFT);
376    }
377
378    /**
379     * Overridden in McIDAS-V to get a nicer set of interval combo box options.
380     *
381     * @return {@code JPanel} containing a label and the interval combo box.
382     */
383    @Override protected JComponent getExtraRelativeTimeComponent() {
384        return getExtraTimeComponentRelative();
385    }
386
387    /**
388     * Get the time popup widget
389     * 
390     * @return a widget for selecing the day
391     */
392    protected JComponent getExtraTimeComponentAbsolute() {
393        return null;
394//      return McVGuiUtils.makeLabeledComponent(archiveDayLabel, archiveDayBtn);
395    }
396        
397    /**
398     * Get the value from the relative increment box
399     *
400     * @return the selected value or a default
401     */
402    protected float getRelBoxValue() {
403        float value = relativeTimeIncrement;
404        if (relTimeIncBox != null) {
405            Object o = relTimeIncBox.getSelectedItem();
406            if (o != null) {
407                String val = TwoFacedObject.getIdString(o);
408                value = (float) Misc.parseNumber(val);
409            }
410        }
411        return value;
412    }
413    
414    /**
415     * Get the string from the relative increment box
416     *
417     * @return the selected string or a default
418     */
419    public String getRelBoxString() {
420        String value = "";
421        if (relTimeIncBox != null) {
422            Object o = relTimeIncBox.getSelectedItem();
423            if (o != null) {
424                value = TwoFacedObject.getIdString(o);
425            }
426        }
427        return value;
428    }
429
430    /**
431     * Get the request URL
432     *
433     * @return the request URL
434     */
435    public String getRequestUrl() {
436        StringBuffer request = getGroupUrl(REQ_POINTDATA, getGroup());
437        appendKeyValue(request, PROP_USER, getLastAddedUser());
438        appendKeyValue(request, PROP_PROJ, getLastAddedProj());
439        appendKeyValue(request, PROP_DESCR, getDescriptor());
440        appendRequestSelectClause(request);
441        appendKeyValue(request, PROP_NUM, "ALL");
442        appendKeyValue(request, PROP_POS, getDoRelativeTimes() ? "ALL" : "0");
443        return request.toString();
444    }
445        
446    /**
447     * Get the select clause for the adde request specific to this
448     * type of data.
449     *
450     * @param buf The buffer to append to
451     */
452    protected void appendRequestSelectClause(StringBuffer buf) {
453        StringBuilder selectValue = new StringBuilder(1024);
454        selectValue.append('\'');
455        selectValue.append(getDayTimeSelectString());
456        //TODO: why is SFCHOURLY explicit here?  better way to do it?
457        if ("SFCHOURLY".equalsIgnoreCase(getDescriptor())) {
458            selectValue.append(";type 0");
459        }
460        selectValue.append(';');
461
462        if (isUpperAir()){
463            selectValue.append(AddeUtil.LEVEL);
464            selectValue.append(';');
465        }
466        selectValue.append(AddeUtil.LATLON_BOX);
467        selectValue.append('\'');
468        appendKeyValue(buf, PROP_SELECT, selectValue.toString());
469    }
470    
471    /**
472     * Check if we are ready to read times
473     *
474     * @return  true if times can be read
475     */
476    protected boolean canReadTimes() {
477        return haveDescriptorSelected();
478    }
479        
480    /**
481     * Enable or disable the GUI widgets based on what has been
482     * selected.
483     */
484    @Override protected void enableWidgets() {
485        boolean descriptorState = ((getState() == STATE_CONNECTED)
486                                   && canReadTimes());
487
488        for (int i = 0; i < compsThatNeedDescriptor.size(); i++) {
489            JComponent comp = (JComponent) compsThatNeedDescriptor.get(i);
490            GuiUtils.enableTree(comp, descriptorState);
491        }
492
493        boolean timesOk = timesOk();
494        
495        // Require times to be selected
496        GuiUtils.enableTree(loadButton, descriptorState && timesOk);
497
498        checkTimesLists();
499        
500        enableAbsoluteTimesList(getDoAbsoluteTimes() && descriptorState);
501
502        getRelativeTimesChooser().setEnabled( !getDoAbsoluteTimes()
503                && descriptorState);
504
505        if (drivercbx != null) {
506//            logger.trace("set drivercbx={}", anyTimeDrivers() && descriptorState);
507            drivercbx.setEnabled(anyTimeDrivers() && descriptorState);
508        }
509
510        revalidate();
511    }
512    
513    /**
514     * Do we have times selected. Either we are doing absolute
515     * times and there are some selected in the list. Or we
516     * are doing relative times and we have done a connect to the
517     * server
518     *
519     * @return Do we have times
520     */
521    public boolean timesOk() {
522        if (getDoAbsoluteTimes() && !haveTimeSelected()) {
523            return false;
524        }
525        return true;
526    }
527    
528    /**
529     * Return {@code true} if selected descriptor is for SYNOPTIC data.
530     *
531     * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()}
532     * is {@literal "SYNOP"}.
533     */
534    protected boolean isSynoptic() {
535        return "SYNOP".equals(getDescriptor());
536    }
537    
538    /**
539     * Return {@code true} if selected descriptor is for upper air.
540     *
541     * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()}
542     * is {@literal "UPPERMAND"}.
543     */
544    protected boolean isUpperAir() {
545        return "UPPERMAND".equals(getDescriptor());
546
547    }
548    
549    /**
550     * Return {@code true} if selected descriptor is for profiler.
551     *
552     * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()}
553     * is {@literal "PROF"}.
554     */
555    protected boolean isProfiler() {
556        return "PROF".equals(getDescriptor());
557    }
558        
559    /**
560     * Update the widget with the latest data.
561     *
562     * @throws Exception On badness
563     */
564    @Override public void handleUpdate() throws Exception {
565        if (getState() != STATE_CONNECTED) {
566            //If not connected then update the server list
567            updateServerList();
568        } else {
569            //If we are already connected then update the rest of the chooser
570            descriptorChanged();
571        }
572        updateStatus();
573    }
574        
575    /**
576     * Get the request string for times particular to this chooser
577     *
578     * @return request string
579     */
580    protected String getTimesRequest() {
581        StringBuffer buf = getGroupUrl(REQ_POINTDATA, getGroup());
582        appendKeyValue(buf, PROP_USER, getLastAddedUser());
583        appendKeyValue(buf, PROP_PROJ, getLastAddedProj());
584        appendKeyValue(buf, PROP_DESCR, getDescriptor());
585        if (!isUpperAir() && !tryWithoutSampling) {
586//            appendKeyValue(buf, PROP_POS, "0");
587            appendKeyValue(buf, PROP_POS, "ALL");
588            appendKeyValue(buf, PROP_SELECT, "'DAY "+getJulianDay()+";LAT 38 42;LON 70 75'");
589        }
590        else {
591            appendKeyValue(buf, PROP_SELECT, "'DAY "+getJulianDay()+"'");
592            appendKeyValue(buf, PROP_POS, "ALL");
593        }
594        if (getDoAbsoluteTimes()) {
595            appendKeyValue(buf, PROP_NUM, "ALL");
596        }
597        appendKeyValue(buf, PROP_PARAM, "DAY TIME");
598        return buf.toString();
599    }
600    
601    /**
602     * Get the current  Julian day as a String
603     *
604     * @return the current day as a string (yyyyDDD)
605     */
606    // TODO: This should really be more accessible
607    private String getJulianDay() {
608        String retString = "";
609        try {
610            DateTime dt = new DateTime();
611            retString = UtcDate.formatUtcDate(dt, "yyyyDDD");
612        } catch (VisADException ve) {
613            logger.error("error building julian date", ve);
614        }
615        return retString;
616    }
617
618    /**
619     * This allows derived classes to provide their own name for labeling, etc.
620     *
621     * @return  the dataset name
622     */
623    @Override public String getDataName() {
624        return "Point Data";
625    }
626    
627    /**
628     * _more_
629     */
630    @Override public void doCancel() {
631        readTimesTask = null;
632        setState(STATE_UNCONNECTED);
633        super.doCancel();
634    }
635
636    /** locking mutex */
637    private final Object MUTEX = new Object();
638    
639    /**
640     *  Read the set of image times available for the current server/group/type
641     *  This method is a wrapper, setting the wait cursor and wrapping the
642     *  call to {@link #readTimesInner()}; in a try/catch block
643     */
644    @Override public void readTimes() {
645        clearTimesList();
646        if (!canReadTimes()) {
647            return;
648        }
649        Misc.run(new Runnable() {
650            @Override public void run() {
651                updateStatus();
652                showWaitCursor();
653                try {
654                    gotObs = false;
655                    tryWithoutSampling = false;
656                    readTimesInner();
657                    // Try again, this time not sampling by LAT/LON
658                    if (haveDescriptorSelected() && !gotObs) {
659                        tryWithoutSampling = true;
660                        readTimesInner();
661                    }
662                } catch (Exception e) {
663                    handleConnectionError(e);
664                }
665                showNormalCursor();
666                updateStatus();
667            }
668        });
669    }
670
671    /**
672     * Set the list of dates/times based on the image selection
673     */
674    protected void readTimesInner() {
675        SortedSet<DateTime> uniqueTimes = Collections.synchronizedSortedSet(new TreeSet<DateTime>());
676
677        readTimesTask = startTask();
678        updateStatus();
679        Object task = readTimesTask;
680        try {
681            AddePointDataReader apr = new AddePointDataReader(getTimesRequest());
682            //Make sure no other loads are  occurred
683            boolean ok = stopTaskAndIsOk(task);
684            if (!Misc.equals(readTimesTask, task) || !ok) {
685                return;
686            }
687            readTimesTask = null;
688
689            synchronized (MUTEX) {
690                int[][] data = apr.getData();
691                String[] units = apr.getUnits();
692                if ( !"CYD".equals(units[0]) || !"HMS".equals(units[1])) {
693                    throw new Exception("can't handle date/time units");
694                }
695                int numObs = data[0].length;
696                //System.out.println("found " + numObs + " obs");
697                // loop through and find all the unique times
698                try {
699                    for (int i = 0; i < numObs; i++) {
700                        DateTime dt =
701                            new DateTime(McIDASUtil.mcDayTimeToSecs(data[0][i],
702                                data[1][i]));
703                        uniqueTimes.add(dt);
704                    }
705                } catch (Exception e) {
706                    logger.error("problem building list of unique times", e);
707                }
708                //System.out.println(
709                //      "found " + uniqueTimes.size() + " unique times");
710                if (getDoAbsoluteTimes()) {
711                    if (!uniqueTimes.isEmpty()) {
712                        setAbsoluteTimes(new ArrayList<DateTime>(uniqueTimes));
713                        getTimesList().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
714                    }
715
716                    //   Select the last n hours 
717                    int selectedIndex = getAbsoluteTimes().size() - 1;
718                    int firstIndex = Math.max(0, selectedIndex
719                            - getDefaultRelativeTimeIndex());
720                    if (selectedIndex >= 0)
721                        setSelectedAbsoluteTime(selectedIndex, firstIndex);
722                }
723                if (numObs>0) {
724                    gotObs = true;
725                }
726            }
727            setState(STATE_CONNECTED);
728        } catch (Exception excp) {
729            stopTask(task);
730            readTimesTask = null;
731            handleConnectionError(excp);
732            if (!retry) {
733                return;
734            }
735            try {
736                handleUpdate();
737            } catch (Exception e) {
738                logger.error("problem handling update", e);
739            }
740        }
741    }
742
743    /**
744     * Show the given error to the user. If it was an Adde exception
745     * that was a bad server error then print out a nice message.
746     *
747     * @param e The exception
748     */
749    @Override protected void handleConnectionError(Exception e) {
750        retry = false;
751        super.handleConnectionError(e);
752    }
753
754    /**
755     * Are there any times selected.
756     *
757     * @return Any times selected.
758     */
759    @Override protected boolean haveTimeSelected() {
760        return !getDoAbsoluteTimes() || getHaveAbsoluteTimesSelected();
761    }
762
763    /**
764     * Create the date time selection string for the "select" clause
765     * of the ADDE URL.
766     *
767     * @return the select day and time strings
768     */
769    protected String getDayTimeSelectString() {
770        StringBuilder buf = new StringBuilder(1024);
771        if (getDoAbsoluteTimes()) {
772            List times = getSelectedAbsoluteTimes();
773
774            // no time selection is permitted as a valid choice -
775            // will then use all times today by default.
776            if (times.isEmpty()) {
777                return "";
778            }
779
780            //check for the "no times available" message
781            if (times.get(0) instanceof String) {
782                return "";
783            }
784
785            if (archiveDay!=null) {
786                logger.trace("archiveDay: {}", archiveDay);
787                try {
788                    Date d = archiveDayFormatter.parse(archiveDay);
789                    DateTime dt = new DateTime(d);
790                    logger.trace("parsed to: {}", dt.toString());
791                    buf.append("day ").append(UtcDate.getIYD(dt)).append(';');
792                } catch (Exception e) {
793                    logger.error("archiveDay parse error", e);
794                }
795            }
796            else {
797                logger.trace("archiveDay is null!");
798            }
799
800            buf.append("time ");
801            for (int i = 0; i < times.size(); i++) {
802                DateTime dt = (DateTime) times.get(i);
803                buf.append(UtcDate.getHMS(dt));
804                if (i != times.size() - 1) {
805                    buf.append(',');
806                }
807            }
808        } else {
809            buf.append(AddeUtil.RELATIVE_TIME);
810        }
811        return buf.toString();
812    }
813
814    /**
815     * Get the data type for this chooser
816     *
817     * @return  the type
818     */
819    @Override public String getDataType() {
820        return "POINT";
821    }
822
823    /**
824     * Get the increment between times for relative time requests
825     *
826     * @return time increment (hours)
827     */
828    @Override public float getRelativeTimeIncrement() {
829        return relativeTimeIncrement;
830    }
831
832    /**
833     * Set the increment between times for relative time requests
834     *
835     * @param increment time increment (hours)
836     */
837    public void setRelativeTimeIncrement(float increment) {
838        relativeTimeIncrement = increment;
839        if (relTimeIncBox != null) {
840            relTimeIncBox.setSelectedItem(relativeTimeIncrement);
841        }
842    }
843
844    /**
845     * Update labels, enable widgets, etc.
846     */
847    @Override protected void updateStatus() {
848        super.updateStatus();
849        if (readTimesTask != null) {
850            if (taskOk(readTimesTask)) {
851                setStatus("Reading available times from server");
852            }
853        } else if (getDoAbsoluteTimes() && !haveTimeSelected()) {
854            setStatus(MSG_TIMES);
855        }
856        enableWidgets();
857    }
858
859                
860    /**
861     * Get the descriptor widget label.
862     *
863     * @return  label for the descriptor  widget
864     */
865    @Override public String getDescriptorLabel() {
866        return "Point Type"; 
867    }
868    
869    /**
870     * get the adde server grup type to use
871     *
872     * @return group type
873     */
874    @Override protected String getGroupType() {
875        return AddeServer.TYPE_POINT;
876    }
877    
878    /**
879     * Make the UI for this selector.
880     * 
881     * @return The gui
882     */   
883    @Override public JComponent doMakeContents() {
884        JPanel myPanel = new JPanel();
885        
886        McVGuiUtils.setComponentWidth(descriptorComboBox, Width.DOUBLEDOUBLE);
887                
888        JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:");
889        addServerComp(stationLabel);
890        
891        JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:");
892        addDescComp(timesLabel);
893        
894        JPanel timesPanel = makeTimesPanel();
895        timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
896        addDescComp(timesPanel);
897                
898        GroupLayout layout = new GroupLayout(myPanel);
899        myPanel.setLayout(layout);
900        layout.setHorizontalGroup(
901            layout.createParallelGroup(LEADING)
902            .addGroup(layout.createSequentialGroup()
903                .addGroup(layout.createParallelGroup(LEADING)
904                    .addGroup(layout.createSequentialGroup()
905                        .addComponent(descriptorLabel)
906                        .addGap(GAP_RELATED)
907                        .addComponent(descriptorComboBox))
908                    .addGroup(layout.createSequentialGroup()
909                        .addComponent(timesLabel)
910                        .addGap(GAP_RELATED)
911                        .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))
912        );
913        layout.setVerticalGroup(
914            layout.createParallelGroup(LEADING)
915            .addGroup(layout.createSequentialGroup()
916                .addGroup(layout.createParallelGroup(BASELINE)
917                    .addComponent(descriptorLabel)
918                    .addComponent(descriptorComboBox))
919                .addPreferredGap(RELATED)
920                .addGroup(layout.createParallelGroup(LEADING)
921                    .addComponent(timesLabel)
922                    .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))
923        );
924        
925        setInnerPanel(myPanel);
926        return super.doMakeContents();
927    }
928
929    public JComponent doMakeContents(boolean doesOverride) {
930        if (doesOverride) {
931            return super.doMakeContents();
932        } else {
933            return doMakeContents();
934        }
935    }
936}