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    
029    package edu.wisc.ssec.mcidasv.startupmanager.options;
030    
031    import java.io.BufferedReader;
032    import java.io.BufferedWriter;
033    import java.io.File;
034    import java.io.FileReader;
035    import java.io.FileWriter;
036    import java.io.IOException;
037    import java.util.ArrayList;
038    import java.util.Collection;
039    import java.util.Collections;
040    import java.util.HashMap;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.Set;
044    
045    import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
046    import edu.wisc.ssec.mcidasv.startupmanager.Platform;
047    
048    public class OptionMaster {
049        
050        // TODO(jon): write CollectionHelpers.zip() and CollectionHelpers.zipWith()
051        public final Object[][] blahblah = {
052            { "HEAP_SIZE", "Memory", "512m", Type.MEMORY, OptionPlatform.ALL, Visibility.VISIBLE },
053            { "JOGL_TOGL", "Enable JOGL", "1", Type.BOOLEAN, OptionPlatform.UNIXLIKE, Visibility.VISIBLE },
054            { "USE_3DSTUFF", "Enable 3D controls", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
055            { "DEFAULT_LAYOUT", "Load default layout", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
056            { "STARTUP_BUNDLE", "Defaults", "", Type.DIRTREE, OptionPlatform.ALL, Visibility.VISIBLE },
057            /**
058             * SLIDER_TEST seems unnecessary...?
059             */
060    //        { "SLIDER_TEST", "Slider Test", "50P", Type.SLIDER, OptionPlatform.ALL, Visibility.VISIBLE },
061            /**
062             * TODO: DAVEP: TomW's windows machine needs SET D3DREND= to work properly.
063             * Not sure why, but it shouldn't hurt other users.  Investigate after Alpha10
064             */
065            { "D3DREND", "Enable Direct3D", "", Type.BOOLEAN, OptionPlatform.WINDOWS, Visibility.VISIBLE },
066            // mcidasv enables this (the actual property is "visad.java3d.geometryByRef")
067            // by default in mcidasv.properties.
068            { "USE_GEOBYREF", "Enable access to geometry by reference", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
069            { "USE_IMAGEBYREF", "Enable access to image data by reference", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
070            { "USE_NPOT", "Enable Non-Power of Two (NPOT) textures", "0", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
071            // temp bandaid for people suffering from permgen problems.
072            { "USE_CMSGC", "Enable concurrent mark-sweep garbage collector", "0", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
073            { "LOG_LEVEL", "Log Level", "INFO", Type.LOGLEVEL, OptionPlatform.ALL, Visibility.VISIBLE },
074        };
075        
076        /**
077         * {@link Option}s can be either platform-specific or applicable to all
078         * platforms. Options that are platform-specific still appear in the 
079         * UI, but their component is not enabled.
080         */
081        public enum OptionPlatform { ALL, UNIXLIKE, WINDOWS };
082        
083        /**
084         * The different types of {@link Option}s.
085         * 
086         * @see TextOption
087         * @see BooleanOption
088         * @see MemoryOption
089         * @see DirectoryOption
090         * @see SliderOption
091         * @see LoggerLevelOption
092         */
093        public enum Type { TEXT, BOOLEAN, MEMORY, DIRTREE, SLIDER, LOGLEVEL };
094        
095        /** 
096         * Different ways that an {@link Option} might be displayed.
097         */
098        public enum Visibility { VISIBLE, HIDDEN };
099        
100        /** Maps an option ID to the corresponding object. */
101        private Map<String, ? extends Option> optionMap;
102        
103        private static OptionMaster instance;
104        
105        public OptionMaster() {
106            normalizeUserDirectory();
107            optionMap = buildOptions(blahblah);
108    //        readStartup();
109        }
110        
111        public static OptionMaster getInstance() {
112            if (instance == null) {
113                instance = new OptionMaster();
114            }
115            return instance;
116        }
117        
118        /**
119         * Creates the specified options and returns a mapping of the option ID
120         * to the actual {@link Option} object.
121         * 
122         * @param options An array specifying the {@code Option}s to be built.
123         * 
124         * @return Mapping of ID to {@code Option}.
125         * 
126         * @throws AssertionError if the option array contained an entry that
127         * this method cannot build.
128         */
129        private Map<String, Option> buildOptions(final Object[][] options) {
130            // TODO(jon): seriously, get that zip stuff working! this array 
131            // stuff is BAD.
132            Map<String, Option> optMap = new HashMap<String, Option>(options.length);
133            
134            for (Object[] arrayOption : options) {
135                String id = (String)arrayOption[0];
136                String label = (String)arrayOption[1];
137                String defaultValue = (String)arrayOption[2];
138                Type type = (Type)arrayOption[3];
139                OptionPlatform platform = (OptionPlatform)arrayOption[4];
140                Visibility visibility = (Visibility)arrayOption[5];
141                
142                switch (type) {
143                    case TEXT:
144                        optMap.put(id, new TextOption(id, label, defaultValue, platform, visibility));
145                        break;
146                    case BOOLEAN:
147                        optMap.put(id, new BooleanOption(id, label, defaultValue, platform, visibility));
148                        break;
149                    case MEMORY:
150                        optMap.put(id, new MemoryOption(id, label, defaultValue, platform, visibility));
151                        break;
152                    case DIRTREE:
153                        optMap.put(id, new DirectoryOption(id, label, defaultValue, platform, visibility));
154                        break;
155                    case SLIDER:
156                        optMap.put(id, new SliderOption(id, label, defaultValue, platform, visibility));
157                        break;
158                    case LOGLEVEL:
159                        optMap.put(id, new LoggerLevelOption(id, label, defaultValue, platform, visibility));
160                        break;
161                    default:
162                         throw new AssertionError(type + 
163                             " is not known to OptionMaster.buildOptions()");
164                }
165            }
166            return optMap;
167        }
168        
169        /**
170         * Converts a {@link Platform} to its corresponding 
171         * {@link OptionPlatform} type.
172         * 
173         * @return The current platform as a {@code OptionPlatform} type.
174         * 
175         * @throws AssertionError if {@link StartupManager#getPlatform()} 
176         * returned something that this method cannot convert.
177         */
178        // a lame-o hack :(
179        protected OptionPlatform convertToOptionPlatform() {
180            Platform platform = StartupManager.getInstance().getPlatform();
181            switch (platform) {
182                case WINDOWS: 
183                    return OptionPlatform.WINDOWS;
184                case UNIXLIKE: 
185                    return OptionPlatform.UNIXLIKE;
186                default: 
187                    throw new AssertionError("Unknown platform: " + platform);
188            }
189        }
190        
191        /**
192         * Returns the {@link Option} mapped to {@code id}.
193         * 
194         * @param id The ID whose associated {@code Option} is to be returned.
195         * 
196         * @return Either the {@code Option} associated with {@code id}, or 
197         * {@code null} if there was no association.
198         * 
199         * 
200         * @see #getMemoryOption
201         * @see #getBooleanOption
202         * @see #getDirectoryOption
203         * @see #getSliderOption
204         * @see #getTextOption
205         * @see #getLoggerLevelOption
206         */
207        private Option getOption(final String id) {
208            return optionMap.get(id);
209        }
210        
211        /**
212         * Searches {@link #optionMap} for the {@link MemoryOption} that 
213         * corresponds with the given {@code id}.
214         * 
215         * @param id Identifier for the desired {@code MemoryOption}. 
216         * Should not be {@code null}.
217         * 
218         * @return Either the {@code MemoryOption} that corresponds to {@code id} 
219         * or {@code null}.
220         */
221        public MemoryOption getMemoryOption(final String id) {
222            return (MemoryOption)optionMap.get(id);
223        }
224        
225        /**
226         * Searches {@link #optionMap} for the {@link BooleanOption} that 
227         * corresponds with the given {@code id}.
228         * 
229         * @param id Identifier for the desired {@code BooleanOption}. 
230         * Should not be {@code null}.
231         * 
232         * @return Either the {@code BooleanOption} that corresponds to {@code id} 
233         * or {@code null}.
234         */
235        public BooleanOption getBooleanOption(final String id) {
236            return (BooleanOption)optionMap.get(id);
237        }
238        
239        /**
240         * Searches {@link #optionMap} for the {@link DirectoryOption} that 
241         * corresponds with the given {@code id}.
242         * 
243         * @param id Identifier for the desired {@code DirectoryOption}. 
244         * Should not be {@code null}.
245         * 
246         * @return Either the {@code DirectoryOption} that corresponds to 
247         * {@code id} or {@code null}.
248         */
249        public DirectoryOption getDirectoryOption(final String id) {
250            return (DirectoryOption)optionMap.get(id);
251        }
252        
253        /**
254         * Searches {@link #optionMap} for the {@link SliderOption} that 
255         * corresponds with the given {@code id}.
256         * 
257         * @param id Identifier for the desired {@code SliderOption}. 
258         * Should not be {@code null}.
259         * 
260         * @return Either the {@code SliderOption} that corresponds to {@code id} 
261         * or {@code null}.
262         */
263        public SliderOption getSliderOption(final String id) {
264            return (SliderOption)optionMap.get(id);
265        }
266        
267        /**
268         * Searches {@link #optionMap} for the {@link TextOption} that 
269         * corresponds with the given {@code id}.
270         * 
271         * @param id Identifier for the desired {@code TextOption}. 
272         * Should not be {@code null}.
273         * 
274         * @return Either the {@code TextOption} that corresponds to {@code id} 
275         * or {@code null}.
276         */
277        public TextOption getTextOption(final String id) {
278            return (TextOption)optionMap.get(id);
279        }
280        
281        /**
282         * Searches {@link #optionMap} for the {@link LoggerLevelOption} that 
283         * corresponds with the given {@code id}.
284         * 
285         * @param id Identifier for the desired {@code LoggerLevelOption}. 
286         * Should not be {@code null}.
287         * 
288         * @return Either the {@code LoggerLevelOption} that corresponds to {@code id} 
289         * or {@code null}.
290         */
291        public LoggerLevelOption getLoggerLevelOption(final String id) {
292            return (LoggerLevelOption)optionMap.get(id);
293        }
294        
295        // TODO(jon): getAllOptions and optionsBy* really need some work.
296        // I want to eventually do something like:
297        // Collection<Option> = getOpts().byPlatform(WINDOWS, ALL).byType(BOOLEAN).byVis(HIDDEN)
298        /**
299         * Returns all the available startup manager options.
300         * 
301         * @return Either all available startup manager options or an empty 
302         * {@link Collection}.
303         */
304        public Collection<Option> getAllOptions() {
305            return Collections.unmodifiableCollection(optionMap.values());
306        }
307        
308        /**
309         * Returns the {@link Option Options} applicable to the given 
310         * {@link OptionPlatform OptionPlatforms}.
311         * 
312         * @param platforms Desired platforms. Cannot be {@code null}.
313         * 
314         * @return Either a {@link List} of {code Option}-s applicable to
315         * {@code platforms} or an empty {@code List}.
316         */
317        public List<Option> optionsByPlatform(
318            final Collection<OptionPlatform> platforms) 
319        {
320            if (platforms == null) {
321                throw new NullPointerException();
322            }
323            Collection<Option> allOptions = getAllOptions();
324            List<Option> filteredOptions = 
325                new ArrayList<Option>(allOptions.size());
326            for (Option option : allOptions) {
327                if (platforms.contains(option.getOptionPlatform())) {
328                    filteredOptions.add(option);
329                }
330            }
331            return filteredOptions;
332        }
333        
334        /**
335         * Returns the {@link Option Options} that match the given 
336         * {@link Type Types}. 
337         * 
338         * @param types Desired {@code Option} types. Cannot be {@code null}.
339         * 
340         * @return Either the {@code List} of {@code Option}-s that match the given 
341         * types or an empty {@code List}.
342         */
343        public List<Option> optionsByType(final Collection<Type> types) {
344            if (types == null) {
345                throw new NullPointerException();
346            }
347            Collection<Option> allOptions = getAllOptions();
348            List<Option> filteredOptions = 
349                new ArrayList<Option>(allOptions.size());
350            for (Option option : allOptions) {
351                if (types.contains(option.getOptionType())) {
352                    filteredOptions.add(option);
353                }
354            }
355            return filteredOptions;
356        }
357        
358        /**
359         * Returns the {@link Option Options} that match the given levels of 
360         * {@link Visibility visibility}.
361         * 
362         * @param visibilities Desired visibility levels. Cannot be {@code null}.
363         * 
364         * @return Either the {@code List} of {@code Option}-s that match the given 
365         * visibility levels or an empty {@code List}. 
366         */
367        public List<Option> optionsByVisibility(
368            final Collection<Visibility> visibilities) 
369        {
370            if (visibilities == null) {
371                throw new NullPointerException();
372            }
373            Collection<Option> allOptions = getAllOptions();
374            List<Option> filteredOptions = 
375                new ArrayList<Option>(allOptions.size());
376            for (Option option : allOptions) {
377                if (visibilities.contains(option.getOptionVisibility())) {
378                    filteredOptions.add(option);
379                }
380            }
381            return filteredOptions;
382        }
383        
384        private void normalizeUserDirectory() {
385            StartupManager startup = StartupManager.getInstance();
386            Platform platform = startup.getPlatform();
387            File dir = new File(platform.getUserDirectory());
388            File prefs = new File(platform.getUserPrefs());
389            
390            if (!dir.exists()) {
391                dir.mkdir();
392            }
393            if (!prefs.exists()) {
394                try {
395                    File defaultPrefs = new File(platform.getDefaultPrefs());
396                    startup.copy(defaultPrefs, prefs);
397                } catch (IOException e) {
398                    System.err.println("Non-fatal error copying user preference template: "+e.getMessage());
399                }
400            }
401        }
402        
403        public void readStartup() {
404            String contents;
405            String line;
406            
407            File script = 
408                new File(StartupManager.getInstance().getPlatform().getUserPrefs());
409            System.err.println("reading "+script);
410            if (script.getPath().isEmpty()) {
411                return;
412            }
413            try {
414                BufferedReader br = new BufferedReader(new FileReader(script));
415                while ((line = br.readLine()) != null) {
416                    if (line.startsWith("#")) {
417                        continue;
418                    }
419                    contents = line.replace("=\"", "=");
420                    String[] chunks = contents.replace("SET ", "").split("=");
421                    if (chunks.length == 2) {
422                        Option option = getOption(chunks[0]);
423                        if (option != null) {
424                            option.fromPrefsFormat(line);
425                        }
426                    }
427                }
428                br.close();
429            } catch (IOException e) {
430                System.err.println("Non-fatal error reading the user preferences: "+e.getMessage());
431            }
432        }
433        
434        public void writeStartup() {
435            File script = 
436                new File(StartupManager.getInstance().getPlatform().getUserPrefs());
437            if (script.getPath().isEmpty()) {
438                return;
439            }
440            // TODO(jon): use filters when you've made 'em less stupid
441            String newLine = 
442                    StartupManager.getInstance().getPlatform().getNewLine();
443            OptionPlatform currentPlatform = convertToOptionPlatform();
444            StringBuilder contents = new StringBuilder();
445            for (Object[] arrayOption : blahblah) {
446                Option option = getOption((String)arrayOption[0]);
447                OptionPlatform platform = option.getOptionPlatform();
448                if (platform == OptionPlatform.ALL || platform == currentPlatform) {
449                    contents.append(option.toPrefsFormat() + newLine);
450                }
451            }
452            
453            try {
454                BufferedWriter out = 
455                    new BufferedWriter(new FileWriter(script));
456                out.write(contents.toString());
457                out.close();
458            } catch (IOException e) {
459                e.printStackTrace();
460            }
461        }
462    }