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