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 */
028package edu.wisc.ssec.mcidasv.servermanager;
029
030import java.util.Collections;
031import java.util.List;
032import java.util.Map;
033
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037/**
038 *
039 */
040public class LocalAddeEntry implements AddeEntry {
041
042    /** Friendly neighborhood logging object. */
043    static final Logger logger = LoggerFactory.getLogger(LocalAddeEntry.class);
044
045    /** Represents a {@literal "bad"} local ADDE entry. */
046    // seriously, don't use null unless you REALLY need it.
047    public static final LocalAddeEntry INVALID_ENTRY = new Builder("INVALID", "INVALID", "/dev/null", AddeFormat.INVALID).build();
048
049    /** Represents a {@literal "bad"} collection of local ADDE entries. */
050    public static final List<LocalAddeEntry> INVALID_ENTRIES = Collections.singletonList(INVALID_ENTRY);
051
052    /** This prefix is Cygwin abstraction that allows access to arbitrary drives. */
053    private static final String CYGWIN_PREFIX = "/cygdrive/";
054
055    /** Length of {@link #CYGWIN_PREFIX}. */
056    private static final int CYGWIN_PREFIX_LEN = CYGWIN_PREFIX.length();
057
058    /** Status of this entry. */
059    private EntryStatus entryStatus = EntryStatus.INVALID;
060
061    // RESOLV.SRV FIELDS
062    /** N1 */
063    private final String group;
064
065    /** N2 */
066    // this value is built in a non-obvious way. plz to be dox.
067    private final String descriptor;
068
069    /** RT */
070    private final boolean realtime;
071
072    /** MCV */
073    private final AddeFormat format;
074
075    /** R1 */
076    private final String start;
077
078    /** R2 */
079    private final String end;
080
081    /** MASK */
082    private final String fileMask;
083
084    /** C */
085    private final String name;
086    // END RESOLV.SRV FIELDS
087
088    private String asStringId;
089
090    /** Whether or not this entry is temporary. */
091    private final boolean isTemporary;
092
093    /** Allows the user to refer to this entry with an arbitrary name. */
094    private String entryAlias;
095
096    public enum ServerName {
097        // note: if you are add a new server you may need to edit the
098        // AddeFormat enum below, the "formats" field in both
099        // LocalEntryEditor and LocalEntryShortcut, and the _formats dictionary
100        // in mcvadde.py.
101        AREA, AMSE, AMSR, AMRR, GINI, FSDX, OMTP, LV1B, MODS, MODX, MOD4, MOD8, 
102        MODR, MSGT, MTST, SMIN, TMIN, MD, INST, WARI, INVALID;
103    }
104
105    /**
106     * The various kinds of local ADDE data understood by McIDAS-V, along with
107     * some helpful metadata.
108     * 
109     * <p><ul>
110     * <li>{@literal "Human readable"} format names ({@link #friendlyName}).</li>
111     * <li>Optional tooltip description ({@link #tooltip}).</li>
112     * <li>Type of data ({@link #type}).</li>
113     * <li>File naming pattern {@link #fileFilter}.</li>
114     * </ul></p>
115     * 
116     * <p>None of {@code AddeFormat}'s fields should contain {@code null}.</p>
117     */
118    public enum AddeFormat {
119        // note: if you are adding a new value to this list, you may need to
120        // edit the ServerName enum, the "formats" field in both
121        // LocalEntryEditor and LocalEntryShortcut, and the _formats dictionary
122        // in mcvadde.py.
123        // sorry. :(
124        MCIDAS_AREA(ServerName.AREA, "McIDAS AREA"),
125        MCIDAS_MD(ServerName.MD, "McIDAS MD", "McIDAS MD", EntryType.POINT),
126        AMSRE_L1B(ServerName.AMSR, "AMSR-E L 1b", "AMSR-E Level 1b"),
127        AMSRE_L2A(ServerName.AMSE, "AMSR-E L 2a", "AMSR-E Level 2a"),
128        AMSRE_RAIN_PRODUCT(ServerName.AMRR, "AMSR-E Rain Product"),
129        GINI(ServerName.GINI, "GINI"),
130        
131        // TJJ Apr 2015 - temporarily comment out INSAT-3D, since the ADDE
132        // servers had not passed testing and been released prior to the
133        // McIDAS-V 1.5 release
134        
135        // INSAT3D_IMAGER(ServerName.INST, "INSAT-3D Imager", "INSAT-3D Imager"),
136        // INSAT3D_SOUNDER(ServerName.INST, "INSAT-3D Sounder", "INSAT-3D Sounder"),
137        
138        // TJJ Apr 2015 - temporarily comment out Himawari-8, since the ADDE
139        // servers had not passed testing and been released prior to the
140        // McIDAS-V 1.5 release
141        
142        // HIMAWARI8(ServerName.WARI, "Himawari 8", "Himawari 8"),
143        
144        LRIT_GOES9(ServerName.FSDX, "LRIT GOES-9", "EUMETCast LRIT GOES-9"),
145        LRIT_GOES10(ServerName.FSDX, "LRIT GOES-10", "EUMETCast LRIT GOES-10"),
146        LRIT_GOES11(ServerName.FSDX, "LRIT GOES-11", "EUMETCast LRIT GOES-11"),
147        LRIT_GOES12(ServerName.FSDX, "LRIT GOES-12", "EUMETCast LRIT GOES-12"),
148        LRIT_MET5(ServerName.FSDX, "LRIT MET-5", "EUMETCast LRIT MET-5"),
149        LRIT_MET7(ServerName.FSDX, "LRIT MET-7", "EUMETCast LRIT MET-7"),
150        LRIT_MTSAT1R(ServerName.FSDX, "LRIT MTSAT-1R", "EUMETCast LRIT MTSAT-1R"),
151        METEOSAT_OPENMTP(ServerName.OMTP, "Meteosat OpenMTP"),
152        METOP_AVHRR_L1B(ServerName.LV1B, "Metop AVHRR L 1b", "Metop AVHRR Level 1b"),
153        MODIS_L1B_MOD02(ServerName.MODS, "MODIS MOD 02 - Level-1B Calibrated Geolocated Radiances", "MODIS Level 1b"),
154        MODIS_L2_MOD06(ServerName.MODX, "MODIS MOD 06 - Cloud Product", "MODIS Level 2 (Cloud Top Properties)"),
155        MODIS_L2_MOD07(ServerName.MODX, "MODIS MOD 07 - Atmospheric Profiles", "MODIS Level 2 (Atmospheric Profile)"),
156        MODIS_L2_MOD35(ServerName.MODX, "MODIS MOD 35 - Cloud Mask", "MODIS Level 2 (Cloud Mask)"),
157        MODIS_L2_MOD04(ServerName.MOD4, "MODIS MOD 04 - Aerosol Product", "MODIS Level 2 (Aerosol)"),
158        MODIS_L2_MOD28(ServerName.MOD8, "MODIS MOD 28 - Sea Surface Temperature", "MODIS Level 2 (Sea Surface Temperature)"),
159        MODIS_L2_MODR(ServerName.MODR, "MODIS MOD R - Corrected Reflectance", "MODIS Level 2 (Corrected Reflectance)"),
160        MSG_HRIT_FD(ServerName.MSGT, "MSG HRIT FD", "MSG HRIT (Full Disk)"),
161        MSG_HRIT_HRV(ServerName.MSGT, "MSG HRIT HRV", "MSG HRIT (High Resolution Visible)"),
162        MTSAT_HRIT(ServerName.MTST, "MTSAT HRIT"),
163        NOAA_AVHRR_L1B(ServerName.LV1B, "NOAA AVHRR L 1b", "NOAA AVHRR Level 1b"),
164        SSMI(ServerName.SMIN, "SSMI", "Terrascan netCDF (SMIN)"),
165        TRMM(ServerName.TMIN, "TRMM", "Terrascan netCDF (TMIN)"),
166        INVALID(ServerName.INVALID, "", "", EntryType.INVALID);
167
168        /** Name of the McIDAS-X server. */
169        private final ServerName servName;
170
171        /** {@literal "Human readable"} format name. This is returned by {@link #toString()}. */
172        private final String friendlyName;
173
174        /** Description of the format. */
175        private final String tooltip;
176
177        /** Data type. Corresponds to {@code TYPE} in {@literal "RESOLV.SRV"}. */
178        private final EntryType type;
179
180        /** 
181         * Filename pattern used when listing files in a directory. 
182         * If {@link #servName} is {@link ServerName#MSGT} then 
183         * {@literal "*PRO*"} is used, otherwise {@literal "*"}. 
184         */
185        private final String fileFilter;
186
187        /**
188         * Builds an {@literal "ADDE format"} and its associated metadata in 
189         * a typesafe way.
190         * 
191         * @param servName {@link ServerName} that McIDAS-X uses for this format. 
192         * @param friendlyName {@literal "Human readable"} name of the format; returned by {@link #toString()}.
193         * @param tooltip If non-empty, this is used as a tooltip in the local entry editor.
194         * @param type {@link EntryType} used by this format.
195         */
196        AddeFormat(final ServerName servName, final String friendlyName, final String tooltip, final EntryType type) {
197            this.servName = servName;
198            this.friendlyName = friendlyName;
199            this.tooltip = tooltip;
200            this.type = type;
201            this.fileFilter = (servName != ServerName.MSGT) ? "*" : "*PRO*";
202        }
203
204        /**
205         * Builds an {@literal "imagery ADDE Format"} <b>without</b> a tooltip.
206         *
207         * @param servName {@link ServerName} that McIDAS-X uses for this format.
208         * @param friendlyName {@literal "Human readable"} name of the format; returned by {@link #toString()}.
209         */
210        AddeFormat(final ServerName servName, final String friendlyName) {
211            this(servName, friendlyName, "", EntryType.IMAGE);
212        }
213
214        /**
215         * Builds an {@literal "imagery ADDE Format"} <b>with</b> a tooltip.
216         *
217         * @param servName {@link ServerName} that McIDAS-X uses for this format.
218         * @param friendlyName {@literal "Human readable"} name of the format; returned by {@link #toString()}.
219         * @param tooltip If non-empty, this is used as a tooltip in the local entry editor.
220         */
221        AddeFormat(final ServerName servName, final String friendlyName, final String tooltip) {
222            this(servName, friendlyName, tooltip, EntryType.IMAGE);
223        }
224
225        /**
226         * Gets the McIDAS-X {@link ServerName} for this format.
227         *
228         * @return Either the name of this format's McIDAS-X server, or
229         * {@link ServerName#INVALID}.
230         */
231        public ServerName getServerName() {
232            return servName;
233        }
234
235        /**
236         * Gets the tooltip text to use in the server manager GUI for this
237         * format.
238         *
239         * @return Text to use as a GUI tooltip. Cannot be {@code null}, though
240         * empty {@code String} values are permitted.
241         */
242        public String getTooltip() {
243            return tooltip;
244        }
245
246        /**
247         * Gets the type of data used by this format. This value dictates the
248         * chooser(s) where this format can appear.
249         *
250         * @return One of {@link AddeEntry.EntryType EntryType}, or
251         * {@link AddeEntry.EntryType#INVALID INVALID}.
252         */
253        public EntryType getType() {
254            return type;
255        }
256
257        /**
258         * Gets the string used to filter out files that match this format.
259         *
260         * @return Either a specialized {@code String}, like {@literal "*PRO*"}
261         * or {@literal "*"}.
262         */
263        public String getFileFilter() {
264            return fileFilter;
265        }
266
267        /**
268         * Gets the {@code String} representation of this format.
269         *
270         * @return the value of {@link #friendlyName}.
271         */
272        @Override public String toString() {
273            return friendlyName;
274        }
275    }
276
277    /**
278     *
279     *
280     * @param builder
281     * 
282     * @see LocalAddeEntry.Builder
283     */
284    private LocalAddeEntry(final Builder builder) {
285        this.group = builder.group;
286        this.descriptor = builder.descriptor;
287        this.realtime = builder.realtime;
288        this.format = builder.format;
289        this.fileMask = builder.mask;
290        this.name = builder.name;
291        this.start = builder.start;
292        this.end = builder.end;
293        this.entryStatus = builder.status;
294        this.isTemporary = builder.temporary;
295        this.entryAlias = builder.alias;
296        logger.debug("created local: {}", this);
297    }
298
299    @Override public AddeAccount getAccount() {
300        return RemoteAddeEntry.DEFAULT_ACCOUNT;
301    }
302
303    @Override public String getAddress() {
304        return "localhost";
305    }
306
307    @Override public EntrySource getEntrySource() {
308        return EntrySource.USER;
309    }
310
311    @Override public EntryStatus getEntryStatus() {
312        return entryStatus;
313    }
314
315    @Override public String getEntryText() {
316        return "localhost/"+getGroup();
317    }
318
319    @Override public EntryType getEntryType() {
320        return format.getType();
321    }
322
323    @Override public EntryValidity getEntryValidity() {
324        return (isValid()) ? EntryValidity.VERIFIED : EntryValidity.INVALID;
325    }
326
327    // TODO(jon): fix this noop
328    @Override public String getEntryAlias() {
329        String tmp = entryAlias;
330        if (entryAlias == null) {
331            tmp = "";
332        }
333        return tmp;
334    }
335
336    // TODO(jon): fix this noop
337    @Override public void setEntryAlias(final String newAlias) {
338        if (newAlias == null) {
339            throw new NullPointerException("Null aliases are not allowable.");
340        }
341        this.entryAlias = newAlias;
342    }
343
344    @Override public void setEntryStatus(EntryStatus newStatus) {
345        entryStatus = newStatus;
346    }
347
348    @Override public boolean isEntryTemporary() {
349        return isTemporary;
350    }
351
352    @Override public String getGroup() {
353        return group;
354    }
355
356    @Override public String getName() {
357        return name;
358    }
359
360    /**
361     * Gets the ADDE descriptor for the current local ADDE entry.
362     * 
363     * @return ADDE descriptor (corresponds to the {@literal "N2"} section of
364     * a RESOLV.SRV entry).
365     */
366    public String getDescriptor() {
367        return descriptor;
368    }
369
370    /**
371     * Gets the ADDE dataset format for the current local ADDE entry.
372     * 
373     * @return ADDE format (corresponds to the {@literal "MCV"} section of a
374     * RESOLV.SRV entry).
375     */
376    public AddeFormat getFormat() {
377        return format;
378    }
379
380    /**
381     * Gets the ADDE file mask for the current local ADDE entry.
382     * 
383     * @return ADDE file mask (corresponds to the {@literal "MASK"} section
384     * of a RESOLV.SRV entry).
385     */
386    public String getMask() {
387        return fileMask;
388    }
389
390    /**
391     * Gets the ADDE file mask for the current local ADDE entry.
392     * 
393     * @return ADDE file mask (corresponds to the {@literal "MASK"} section
394     * of a RESOLV.SRV entry).
395     */
396    public String getFileMask() {
397        return fileMask;
398    }
399
400    /**
401     * Gets the ADDE realtime status of the current local ADDE entry.
402     * 
403     * @return Whether or not the current dataset is {@literal "realtime"}.
404     * Corresponds to the {@literal "RT"} section of a RESOLV.SRV entry.
405     */
406    public boolean getRealtime() {
407        return realtime;
408    }
409
410    /**
411     * Gets the starting number of the current local ADDE dataset.
412     * 
413     * @return Corresponds to the {@literal "R1"} section of a RESOLV.SRV entry.
414     */
415    public String getStart() {
416        return start;
417    }
418
419    /**
420     * Gets the ending number of the current local ADDE dataset.
421     * 
422     * @return Corresponds to the {@literal "R2"} section of a RESOLV.SRV entry.
423     */
424    public String getEnd() {
425        return end;
426    }
427
428    /**
429     * Tests the current local ADDE dataset for validity.
430     * 
431     * @return {@code true} iff {@link #group} and {@link #name} are not empty.
432     */
433    public boolean isValid() {
434//        return !((group.isEmpty()) || (descriptor.isEmpty()) || (name.isEmpty()));
435        return !(group.isEmpty() || name.isEmpty());
436    }
437
438    /**
439     * Gets the local ADDE dataset's realtime status as a value suitable for
440     * RESOLV.SRV (one of {@literal "Y"} or {@literal "N"}).
441     * 
442     * @return RESOLV.SRV-friendly representation of the current realtime status.
443     */
444    public String getRealtimeAsString() {
445        return realtime ? "Y" : "N";
446    }
447
448    /**
449     * @see LocalAddeEntry#generateHashCode(String, String, String, String, boolean, AddeFormat)
450     */
451    @Override public int hashCode() {
452        return generateHashCode(name, group, fileMask, entryAlias, isTemporary, format);
453    }
454
455    /**
456     * Checks a given object for equality with the current {@code LocalAddeEntry}
457     * instance.
458     * 
459     * @param obj Object to check. {@code null} values allowed.
460     * 
461     * @return {@code true} if {@code obj} is {@literal "equal"} to the current
462     * {@code LocalAddeEntry} instance.
463     */
464    @Override public boolean equals(Object obj) {
465        if (this == obj) {
466            return true;
467        }
468        if (obj == null) {
469            return false;
470        }
471        if (!(obj instanceof LocalAddeEntry)) {
472            return false;
473        }
474        LocalAddeEntry other = (LocalAddeEntry) obj;
475        if (fileMask == null) {
476            if (other.fileMask != null) {
477                return false;
478            }
479        } else if (!fileMask.equals(other.fileMask)) {
480            return false;
481        }
482        if (format == null) {
483            if (other.format != null) {
484                return false;
485            }
486        } else if (!format.toString().equals(other.format.toString())) {
487            return false;
488        }
489        if (group == null) {
490            if (other.group != null) {
491                return false;
492            }
493        } else if (!group.equals(other.group)) {
494            return false;
495        }
496        if (name == null) {
497            if (other.name != null) {
498                return false;
499            }
500        } else if (!name.equals(other.name)) {
501            return false;
502        }
503        if (entryAlias == null) {
504            if (other.entryAlias != null) {
505                return false;
506            }
507        } else if (!entryAlias.equals(other.entryAlias)) {
508            return false;
509        }
510        if (isTemporary != other.isTemporary) {
511            return false;
512        }
513        return true;
514    }
515
516    @Override public String asStringId() {
517        if (asStringId == null) {
518            asStringId = "localhost!"+group+'!'+EntryType.IMAGE.name()+'!'+name;
519        }
520        return asStringId;
521    }
522
523    @Override public String toString() {
524        return String.format(
525            "[LocalAddeEntry@%x: name=%s, group=%s, fileMask=\"%s\", descriptor=%s, serverName=%s, format=%s, description=%s, type=%s, status=%s, temporary=%s, alias=%s]", 
526            hashCode(), name, group, fileMask, descriptor, format.getServerName().name(), format.name(), format.getTooltip(), format.getType(), entryStatus.name(), isTemporary, entryAlias);
527        
528    }
529
530    public static int generateHashCode(final LocalAddeEntry entry) {
531        return generateHashCode(entry.getName(), entry.getGroup(), entry.getMask(), entry.getEntryAlias(), entry.isEntryTemporary(), entry.getFormat());
532    }
533
534    public static int generateHashCode(String name, String group, String fileMask, String entryAlias, boolean isTemporary, AddeFormat format) {
535        final int prime = 31;
536        int result = 1;
537        result = prime * result
538            + ((fileMask == null) ? 0 : fileMask.hashCode());
539        result = prime * result + ((format == null) ? 0 : format.toString().hashCode());
540        result = prime * result + ((group == null) ? 0 : group.hashCode());
541        result = prime * result + ((name == null) ? 0 : name.hashCode());
542        result = prime * result + ((entryAlias == null) ? 0 : entryAlias.hashCode());
543        result = prime * result + (isTemporary ? 1231 : 1237);
544        return result;
545    }
546
547    /**
548     * A builder of (mostly) immutable {@link LocalAddeEntry} instances.
549     * 
550     * <p>Usage example: <pre>    {@code
551     *     LocalAddeEntry entry = new LocalAddeEntry
552     *         .Builder(group, name, format, mask)
553     *         .realtime("Y")
554     *         .range(start, end)
555     *         .type(EntryType.POINT)
556     *         .build();}</pre>
557     * 
558     * Only the values required by the Builder constructor are required.
559     */
560    public static class Builder {
561        // required
562        /** Corresponds to RESOLV.SRV's {@literal "N1"} section. */
563        private final String group;
564
565        /** Corresponds to RESOLV.SRV's {@literal "C"} section. */
566        private final String name;
567
568        /** Corresponds to RESOLV.SRV's {@literal "MCV"} section. */
569        private final AddeFormat format;
570
571        /** Corresponds to RESOLV.SRV's {@literal "MASK"} section. */
572        private final String mask;
573
574        // generated
575        private String descriptor;
576
577        // optional
578        /**
579         * Corresponds to RESOLV.SRV's {@literal "RT"} section.
580         * Defaults to {@code false}.
581         */
582        private boolean realtime = false;
583
584        /**
585         * Corresponds to RESOLV.SRV's {@literal "R1"} section.
586         * Defaults to {@literal "1"}.
587         */
588        private String start = "1";
589
590        /**
591         * Corresponds to RESOLV.SRV's {@literal "R2"} section.
592         * Defaults to {@literal "999999"}.
593         */
594        private String end = "999999";
595
596        /**
597         * Defaults to {@link AddeEntry.EntryStatus#INVALID}.
598         */
599        private EntryStatus status = EntryStatus.INVALID;
600
601        /**
602         * Corresponds to RESOLV.SRV's {@literal "TYPE"} section.
603         * Defaults to {@link AddeEntry.EntryType#IMAGE IMAGE}.
604         */
605        private EntryType type = EntryType.IMAGE;
606
607        /**
608         * Corresponds to RESOLV.SRV's {@literal "K"} section.
609         * Defaults to {@literal "NOT_SET"}.
610         */
611        private String kind = "NOT_SET";
612
613        /**
614         * Defaults to {@link ServerName#INVALID}.
615         */
616        private ServerName safeKind = ServerName.INVALID;
617
618        /** */
619        private boolean temporary = false;
620
621        /** */
622        private String alias = "";
623
624        public Builder(final Map<String, String> map) {
625            if (!map.containsKey("C") || !map.containsKey("N1") || !map.containsKey("MASK") || !map.containsKey("MCV")) {
626                throw new IllegalArgumentException("Cannot build a LocalAddeEntry without the following keys: C, N1, MASK, and MCV.");
627            }
628
629            this.name = map.get("C");
630            this.group = map.get("N1");
631            this.mask = map.get("MASK");
632            this.format = EntryTransforms.strToAddeFormat(map.get("MCV"));
633
634//            descriptor(map.get("N2"));
635            type(EntryTransforms.strToEntryType(map.get("TYPE")));
636            kind(map.get("K").toUpperCase());
637            realtime(map.get("RT"));
638            start(map.get("R1"));
639            end(map.get("R2"));
640            
641            if (map.containsKey("TEMPORARY")) {
642                temporary(map.get("TEMPORARY"));
643            }
644        }
645
646        /**
647         * Creates a new {@code LocalAddeEntry} {@literal "builder"} with the 
648         * required fields for a {@code LocalAddeEntry} object.
649         * 
650         * @param name 
651         * @param group 
652         * @param mask 
653         * @param format
654         */
655        public Builder(final String name, final String group, final String mask, final AddeFormat format) {
656            this.name = name;
657            this.group = group;
658            this.mask = mask;
659            this.format = format;
660        }
661
662        /**
663         * This method is currently a no-op.
664         *
665         * @param descriptor
666         *
667         * @return {@code LocalAddeEntry.Builder} with ADDE descriptor.
668         */
669        public Builder descriptor(final String descriptor) {
670//            if (descriptor != null) {
671//                this.descriptor = descriptor;
672//            }
673            return this;
674        }
675
676        /**
677         *
678         *
679         * @param realtimeAsStr
680         *
681         * @return {@code LocalAddeEntry.Builder} with ADDE realtime flag.
682         */
683        // looks like mcidasx understands ("Y"/"N"/"A")
684        // should probably ignore case and accept "YES"/"NO"/"ARCHIVE"
685        // in addition to the normal boolean conversion from String
686        public Builder realtime(final String realtimeAsStr) {
687            if (realtimeAsStr == null) {
688                return this;
689            }
690
691            if ("Y".equalsIgnoreCase(realtimeAsStr) || "YES".equalsIgnoreCase(realtimeAsStr)) {
692                this.realtime = true;
693            } else {
694                this.realtime = Boolean.valueOf(realtimeAsStr);
695            }
696            return this;
697        }
698
699        /**
700         *
701         *
702         * @param realtime
703         *
704         * @return {@code LocalAddeEntry.Builder} with ADDE realtime flag.
705         */
706        public Builder realtime(final boolean realtime) {
707            this.realtime = realtime;
708            return this;
709        }
710
711        /**
712         *
713         *
714         * @param type
715         *
716         * @return {@code LocalAddeEntry.Builder} with ADDE data type.
717         */
718        // my assumption is that if "format" is known, you can infer "type"
719        public Builder type(final EntryType type) {
720            if (type != null) {
721                this.type = type;
722            }
723            return this;
724        }
725
726        /**
727         *
728         *
729         * @param kind
730         *
731         * @return {@code LocalAddeEntry.Builder} with ADDE kind.
732         */
733        // my assumption is that if "format" is known, you can infer "kind"
734        public Builder kind(final String kind) {
735            if (kind == null) {
736                return this;
737            }
738
739            this.kind = kind;
740            try {
741                this.safeKind = ServerName.valueOf(kind);
742            } catch (IllegalArgumentException e) { 
743                this.safeKind = ServerName.INVALID;
744            }
745            return this;
746        }
747
748        /**
749         *
750         *
751         * @param start
752         *
753         * @return {@code LocalAddeEntry.Builder} with ADDE dataset {@literal "start"}.
754         */
755        public Builder start(final String start) {
756            if (start != null) {
757                this.start = start;
758            }
759            return this;
760        }
761
762        /**
763         *
764         *
765         * @param end
766         *
767         * @return {@code LocalAddeEntry.Builder} with ADDE dataset {@literal "end"}.
768         */
769        public Builder end(final String end) {
770            if (end != null) {
771                this.end = end;
772            }
773            return this;
774        }
775
776        /**
777         *
778         *
779         * @param start
780         * @param end
781         *
782         * @return {@code LocalAddeEntry.Builder} with ADDE dataset {@literal "start" and "end"} values.
783         */
784        public Builder range(final String start, final String end) {
785            if (start != null && end != null) {
786                this.start = start;
787                this.end = end;
788            }
789            return this;
790        }
791
792        /**
793         *
794         *
795         * @param status
796         *
797         * @return {@code LocalAddeEntry.Builder} with {@link AddeEntry.EntryStatus}.
798         */
799        public Builder status(final String status) {
800            if (status != null && status.length() > 0) {
801                this.status = EntryTransforms.strToEntryStatus(status);
802            }
803            return this;
804        }
805
806        /**
807         * 
808         * 
809         * @param status
810         * 
811         * @return {@code LocalAddeEntry.Builder} with {@link AddeEntry.EntryStatus}.
812         */
813        public Builder status(final EntryStatus status) {
814            if (status != null) {
815                this.status = status;
816            }
817            return this;
818        }
819
820        /**
821         * 
822         * 
823         * @param temporary
824         * 
825         * @return {@code LocalAddeEntry.Builder} with the specified temporary status.
826         */
827        public Builder temporary(final boolean temporary) {
828            this.temporary = temporary;
829            return this;
830        }
831
832        public Builder temporary(final String temporary) {
833            this.temporary = Boolean.valueOf(temporary);
834            return this;
835        }
836
837        /**
838         * 
839         * 
840         * @param alias 
841         * 
842         * @return {@code LocalAddeEntry.Builder} with the specified alias.
843         */
844        public Builder alias(final String alias) {
845            this.alias = alias;
846            return this;
847        }
848
849        /**
850         * 
851         * 
852         * @return New {@code LocalAddeEntry} instance.
853         */
854        public LocalAddeEntry build() {
855            // apparently need to hack up the descriptor for certain formats
856            switch (format) {
857                case MSG_HRIT_FD: this.descriptor = "FD"; break;
858                case MSG_HRIT_HRV: this.descriptor = "HRV"; break;
859                case LRIT_GOES9: this.descriptor = "GOES9"; break;
860                case LRIT_GOES10: this.descriptor = "GOES10"; break;
861                case LRIT_GOES11: this.descriptor = "GOES11"; break;
862                case LRIT_GOES12: this.descriptor = "GOES12"; break;
863                case LRIT_MET5: this.descriptor = "MET5"; break;
864                case LRIT_MET7: this.descriptor = "MET7"; break;
865                case LRIT_MTSAT1R: this.descriptor = "MTSAT1R"; break;
866                default:
867                    this.descriptor = Integer.toHexString(generateHashCode(name, group, mask, alias, temporary, format));
868                    break;
869            }
870            return new LocalAddeEntry(this);
871        }
872    }
873}