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