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