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