001/*
002 * $Id: LocalAddeEntry.java,v 1.25 2011/03/24 16:06:34 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
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 */
030package edu.wisc.ssec.mcidasv.servermanager;
031
032import java.util.Collections;
033import java.util.List;
034import java.util.Map;
035import java.util.Random;
036
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType;
041
042/**
043 * 
044 * 
045 *
046 */
047public class LocalAddeEntry implements AddeEntry {
048
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    public static final List<LocalAddeEntry> INVALID_ENTRIES = Collections.singletonList(INVALID_ENTRY);
056
057    /** */
058    private static final String CYGWIN_PREFIX = "/cygdrive/";
059
060    /** */
061    private static final int CYGWIN_PREFIX_LEN = CYGWIN_PREFIX.length();
062
063    /** */
064    private EntryStatus entryStatus = EntryStatus.INVALID;
065
066    /** N1 */
067    private final String group;
068
069    /** N2 */
070    private final String descriptor;
071
072    /** RT */
073    private final boolean realtime;
074
075    /** MCV */
076    private final AddeFormat format;
077
078    /** R1 */
079    private final String start;
080
081    /** R2 */
082    private final String end;
083
084    /** MASK */
085    private final String fileMask;
086
087    /** C */
088    private final String name;
089    
090    
091    private String asStringId;
092
093    public enum ServerName {
094        AREA, AMSR, AMRR, GINI, FSDX, OMTP, LV1B, MODS, MODX, MOD4, MOD8, 
095        MODR, MSGT, MTST, SMIN, TMIN, INVALID;
096    }
097
098    /**
099     * The various kinds of local ADDE data understood by McIDAS-V, along with
100     * some helpful metadata.
101     * 
102     * <p><ul>
103     * <li>{@literal "Human readable"} format names ({@link #friendlyName}).</li>
104     * <li>Optional tooltip description ({@link #tooltip}).</li>
105     * <li>Type of data ({@link #type}).</li>
106     * <li>File naming pattern {@link #fileFilter}.</li>
107     * </ul>
108     * 
109     * <p>None of {@code AddeFormat}'s fields should contain {@code null}.
110     */
111    public enum AddeFormat {
112        INVALID(ServerName.INVALID, "", "", EntryType.INVALID),
113        MCIDAS_AREA(ServerName.AREA, "McIDAS AREA"),
114        AMSRE_L1B(ServerName.AMSR, "AMSR-E L 1b", "AMSR-E Level 1b"),
115        AMSRE_RAIN_PRODUCT(ServerName.AMRR, "AMSR-E Rain Product"),
116        GINI(ServerName.GINI, "GINI"),
117        LRIT_GOES9(ServerName.FSDX, "LRIT GOES-9", "EUMETCast LRIT GOES-9"),
118        LRIT_GOES10(ServerName.FSDX, "LRIT GOES-10", "EUMETCast LRIT GOES-10"),
119        LRIT_GOES11(ServerName.FSDX, "LRIT GOES-11", "EUMETCast LRIT GOES-11"),
120        LRIT_GOES12(ServerName.FSDX, "LRIT GOES-12", "EUMETCast LRIT GOES-12"),
121        LRIT_MET5(ServerName.FSDX, "LRIT MET-5", "EUMETCast LRIT MET-5"),
122        LRIT_MET7(ServerName.FSDX, "LRIT MET-7", "EUMETCast LRIT MET-7"),
123        LRIT_MTSAT1R(ServerName.FSDX, "LRIT MTSAT-1R", "EUMETCast LRIT MTSAT-1R"),
124        METEOSAT_OPENMTP(ServerName.OMTP, "Meteosat OpenMTP"),
125        METOP_AVHRR_L1B(ServerName.LV1B, "Metop AVHRR L 1b", "Metop AVHRR Level 1b"),
126        MODIS_L1B_MOD02(ServerName.MODS, "MODIS MOD 02 - Level-1B Calibrated Geolocated Radiances", "MODIS Level 1b"),
127        MODIS_L2_MOD06(ServerName.MODX, "MODIS MOD 06 - Cloud Product", "MODIS Level 2 (Cloud Top Properties)"),
128        MODIS_L2_MOD07(ServerName.MODX, "MODIS MOD 07 - Atmospheric Profiles", "MODIS Level 2 (Atmospheric Profile)"),
129        MODIS_L2_MOD35(ServerName.MODX, "MODIS MOD 35 - Cloud Mask", "MODIS Level 2 (Cloud Mask)"),
130        MODIS_L2_MOD04(ServerName.MOD4, "MODIS MOD 04 - Aerosol Product", "MODIS Level 2 (Aerosol)"),
131        MODIS_L2_MOD28(ServerName.MOD8, "MODIS MOD 28 - Sea Surface Temperature", "MODIS Level 2 (Sea Surface Temperature)"),
132        MODIS_L2_MODR(ServerName.MODR, "MODIS MOD R - Corrected Reflectance", "MODIS Level 2 (Corrected Reflectance)"),
133        MSG_HRIT_FD(ServerName.MSGT, "MSG HRIT FD", "MSG HRIT (Full Disk)"),
134        MSG_HRIT_HRV(ServerName.MSGT, "MSG HRIT HRV", "MSG HRIT (High Resolution Visible)"),
135        MTSAT_HRIT(ServerName.MTST, "MTSAT HRIT"),
136        NOAA_AVHRR_L1B(ServerName.LV1B, "NOAA AVHRR L 1b", "NOAA AVHRR Level 1b"),
137        SSMI(ServerName.SMIN, "SSMI", "Terrascan netCDF (SMIN)"),
138        TRMM(ServerName.TMIN, "TRMM", "Terrascan netCDF (TMIN)");
139
140        /** Name of the server (should be four characters). */
141        private final ServerName servName;
142
143        /** {@literal "Human readable"} format name. This is returned by {@link #toString()}. */
144        private final String friendlyName;
145
146        /** Description of the format. */
147        private final String tooltip;
148
149        /** Data type. Corresponds to {@code TYPE} in {@literal "RESOLV.SRV"}. */
150        private final EntryType type;
151
152        /** 
153         * Filename pattern used when listing files in a directory. 
154         * If {@link #servName} is {@link ServerName#MSGT} then 
155         * {@literal "*PRO*"} is used, otherwise {@literal "*"}. 
156         */
157        private final String fileFilter;
158
159        /**
160         * Builds an {@literal "ADDE format"} and its associated metadata in 
161         * a typesafe way.
162         * 
163         * @param servName {@link ServerName} that McIDAS-X uses for this format. 
164         * @param friendlyName {@literal "Human readable"} name of the format; returned by {@link #toString()}.
165         * @param tooltip If non-empty, this is used as a tooltip in the local entry editor.
166         * @param type Only use {@link EntryType#IMAGE} for the time being?
167         */
168        private AddeFormat(final ServerName servName, final String friendlyName, final String tooltip, final EntryType type) {
169            this.servName = servName;
170            this.friendlyName = friendlyName;
171            this.tooltip = tooltip;
172            this.type = type;
173            this.fileFilter = (servName != ServerName.MSGT) ? "*" : "*PRO*";
174        }
175
176        /**
177         * Builds an {@literal "ADDE Format"} <b>without</b> a tooltip.
178         */
179        private AddeFormat(final ServerName servName, final String friendlyName) {
180            this(servName, friendlyName, "", EntryType.IMAGE);
181        }
182
183        /**
184         * Builds an {@literal "ADDE Format"} <b>with</b> a tooltip.
185         */
186        private AddeFormat(final ServerName servName, final String friendlyName, final String tooltip) {
187            this(servName, friendlyName, tooltip, EntryType.IMAGE);
188        }
189
190        public ServerName getServerName() { return servName; }
191        public String getTooltip() { return tooltip; }
192        public EntryType getType() { return type; }
193        public String getFileFilter() { return fileFilter; }
194        @Override public String toString() { return friendlyName; }
195    }
196
197    private LocalAddeEntry(final Builder builder) {
198        this.group = builder.group;
199        this.descriptor = builder.descriptor;
200        this.realtime = builder.realtime;
201        this.format = builder.format;
202        this.fileMask = builder.mask;
203        this.name = builder.name;
204        this.start = builder.start;
205        this.end = builder.end;
206        this.entryStatus = builder.status;
207        logger.debug("created local: {}", this);
208    }
209
210    @Override public AddeAccount getAccount() {
211        return RemoteAddeEntry.DEFAULT_ACCOUNT;
212    }
213
214    @Override public String getAddress() {
215        return "localhost";
216    }
217
218    @Override public EntrySource getEntrySource() {
219        return EntrySource.USER;
220    }
221
222    @Override public EntryStatus getEntryStatus() {
223        return entryStatus;
224    }
225
226    @Override public String getEntryText() {
227        return "localhost/"+getGroup();
228    }
229
230    @Override public EntryType getEntryType() {
231        return format.getType();
232    }
233
234    @Override public EntryValidity getEntryValidity() {
235        return (isValid()) ? EntryValidity.VERIFIED : EntryValidity.INVALID;
236    }
237
238    // TODO(jon): fix this noop
239    @Override public String getEntryAlias() {
240        return "";
241    }
242
243    // TODO(jon): fix this noop
244    @Override public void setEntryAlias(final String newAlias) {
245        if (newAlias == null) {
246            throw new NullPointerException("Null aliases are not allowable.");
247        }
248    }
249
250    @Override public void setEntryStatus(EntryStatus newStatus) {
251        entryStatus = newStatus;
252    }
253
254    @Override public String getGroup() {
255        return group;
256    }
257
258    public String getDescriptor() {
259        return descriptor;
260    }
261
262    public AddeFormat getFormat() {
263        return format;
264    }
265
266    public String getMask() {
267        return fileMask;
268    }
269
270    public String getFileMask() {
271        return fileMask;
272    }
273
274    @Override public String getName() {
275        return name;
276    }
277
278    public boolean getRealtime() {
279        return realtime;
280    }
281
282    public String getStart() {
283        return start;
284    }
285
286    public String getEnd() {
287        return end;
288    }
289
290    public boolean isValid() {
291        if ((group.length() == 0) || (descriptor.length() == 0) || (name.length() == 0)) {
292            return false;
293        }
294        return true;
295    }
296
297    public String getRealtimeAsString() {
298        if (realtime) {
299            return "Y";
300        }
301        return "N";
302    }
303
304    @Override public int hashCode() {
305        final int prime = 31;
306        int result = 1;
307        result = prime * result
308            + ((fileMask == null) ? 0 : fileMask.hashCode());
309        result = prime * result + ((format == null) ? 0 : format.hashCode());
310        result = prime * result + ((group == null) ? 0 : group.hashCode());
311        result = prime * result + ((name == null) ? 0 : name.hashCode());
312        return result;
313    }
314
315    @Override public boolean equals(Object obj) {
316        if (this == obj) {
317            return true;
318        }
319        if (obj == null) {
320            return false;
321        }
322        if (!(obj instanceof LocalAddeEntry)) {
323            return false;
324        }
325        LocalAddeEntry other = (LocalAddeEntry) obj;
326        if (fileMask == null) {
327            if (other.fileMask != null) {
328                return false;
329            }
330        } else if (!fileMask.equals(other.fileMask)) {
331            return false;
332        }
333        if (format == null) {
334            if (other.format != null) {
335                return false;
336            }
337        } else if (!format.equals(other.format)) {
338            return false;
339        }
340        if (group == null) {
341            if (other.group != null) {
342                return false;
343            }
344        } else if (!group.equals(other.group)) {
345            return false;
346        }
347        if (name == null) {
348            if (other.name != null) {
349                return false;
350            }
351        } else if (!name.equals(other.name)) {
352            return false;
353        }
354        return true;
355    }
356
357    @Override public String asStringId() {
358        if (asStringId == null) {
359            asStringId = "localhost!"+group+'!'+EntryType.IMAGE.name()+'!'+name;
360        }
361        return asStringId;
362    }
363
364    @Override public String toString() {
365        return String.format(
366            "[LocalAddeEntry@%x: name=%s, group=%s, fileMask=\"%s\", descriptor=%s, serverName=%s, format=%s, description=%s, type=%s, status=%s]", 
367            hashCode(), name, group, fileMask, descriptor, format.getServerName().name(), format.name(), format.getTooltip(), format.getType(), entryStatus.name());
368        
369    }
370
371    public static class Builder {
372        private static final Random random = new Random();
373
374        // required
375        private final String group;
376        private final String name;
377        private final AddeFormat format;
378        private final String mask;
379
380        // optional
381        private String descriptor = "ENTRY"+random.nextInt(999999);
382        private boolean realtime = false;
383        private String start = "1";
384        private String end = "999999";
385        private EntryStatus status = EntryStatus.INVALID;
386
387        private EntryType type = EntryType.IMAGE;
388        private String kind = "NOT_SET";
389        private ServerName safeKind = ServerName.INVALID;
390
391        public Builder(final Map<String, String> map) {
392            if (!map.containsKey("C") || !map.containsKey("N1") || !map.containsKey("MASK") || !map.containsKey("MCV")) {
393                throw new IllegalArgumentException("");
394            }
395
396            this.name = map.get("C");
397            this.group = map.get("N1");
398            this.mask = map.get("MASK");
399            this.format = EntryTransforms.strToAddeFormat(map.get("MCV"));
400
401            descriptor(map.get("N2"));
402            type(EntryTransforms.strToEntryType(map.get("TYPE")));
403            kind(map.get("K").toUpperCase());
404            realtime(map.get("RT"));
405            start(map.get("R1"));
406            end(map.get("R2"));
407        }
408
409        
410        public Builder(final String name, final String group, final String mask, final AddeFormat format) {
411            this.name = name;
412            this.group = group;
413            this.mask = mask;
414            this.format = format;
415        }
416
417        public Builder descriptor(final String descriptor) {
418            if (descriptor != null) {
419                this.descriptor = descriptor;
420            }
421            return this;
422        }
423
424        // looks like mcidasx understands ("Y"/"N"/"A")
425        // should probably ignore case and accept "YES"/"NO"/"ARCHIVE"
426        // in addition to the normal boolean conversion from String
427        public Builder realtime(final String realtimeAsStr) {
428            if (realtimeAsStr == null) {
429                return this;
430            }
431
432            if ("Y".equalsIgnoreCase(realtimeAsStr) || "YES".equalsIgnoreCase(realtimeAsStr)) {
433                this.realtime = true;
434            } else {
435                this.realtime = Boolean.valueOf(realtimeAsStr);
436            }
437            return this;
438        }
439
440        public Builder realtime(final boolean realtime) {
441            this.realtime = realtime;
442            return this;
443        }
444
445        // my assumption is that if "format" is known, you can infer "type"
446        public Builder type(final EntryType type) {
447            if (type != null) {
448                this.type = type;
449            }
450            return this;
451        }
452
453        // my assumption is that if "format" is known, you can infer "kind"
454        public Builder kind(final String kind) {
455            if (kind == null) {
456                return this;
457            }
458
459            this.kind = kind;
460            try {
461                this.safeKind = ServerName.valueOf(kind);
462            } catch (IllegalArgumentException e) { 
463                this.safeKind = ServerName.INVALID;
464            }
465            return this;
466        }
467
468        public Builder start(final String start) {
469            if (start != null) {
470                this.start = start;
471            }
472            return this;
473        }
474
475        public Builder end(final String end) {
476            if (end != null) {
477                this.end = end;
478            }
479            return this;
480        }
481
482        public Builder range(final String start, final String end) {
483            if (start != null && end != null) {
484                this.start = start;
485                this.end = end;
486            }
487            return this;
488        }
489
490        public Builder status(final String status) {
491            if (status != null && status.length() > 0) {
492                this.status = EntryTransforms.strToEntryStatus(status);
493            }
494            return this;
495        }
496
497        public Builder status(final EntryStatus status) {
498            if (status != null) {
499                this.status = status;
500            }
501            return this;
502        }
503
504        public LocalAddeEntry build() {
505//            if (format.getType() != type || format.getServerName() != safeKind || safeKind == ServerName.INVALID)
506//                System.err.println("oddity: name="+name+" mask="+mask+" group="+group+" descriptor="+descriptor+" realtime="+realtime+" format="+format+" type="+type+" kind="+kind+" safeKind="+safeKind);
507
508            // apparently need to hack up the descriptor for certain formats
509            switch (format) {
510                case MSG_HRIT_FD: this.descriptor = "FD"; break;
511                case MSG_HRIT_HRV: this.descriptor = "HRV"; break;
512                case LRIT_GOES9: this.descriptor = "GOES9"; break;
513                case LRIT_GOES10: this.descriptor = "GOES10"; break;
514                case LRIT_GOES11: this.descriptor = "GOES11"; break;
515                case LRIT_GOES12: this.descriptor = "GOES12"; break;
516                case LRIT_MET5: this.descriptor = "MET5"; break;
517                case LRIT_MET7: this.descriptor = "MET7"; break;
518                case LRIT_MTSAT1R: this.descriptor = "MTSAT1R"; break;
519            }
520            return new LocalAddeEntry(this);
521        }
522    }
523
524}