001    /*
002     * $Id: OptionMaster.java,v 1.16 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    
031    package edu.wisc.ssec.mcidasv.startupmanager.options;
032    
033    import java.io.BufferedReader;
034    import java.io.BufferedWriter;
035    import java.io.File;
036    import java.io.FileReader;
037    import java.io.FileWriter;
038    import java.io.IOException;
039    import java.util.ArrayList;
040    import java.util.Collection;
041    import java.util.Collections;
042    import java.util.HashMap;
043    import java.util.Map;
044    import java.util.Set;
045    
046    import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
047    import edu.wisc.ssec.mcidasv.startupmanager.StartupManager.Platform;
048    
049    public enum OptionMaster {
050        /** The lone OptionMaster instance. */
051        INSTANCE;
052    
053        // TODO(jon): write CollectionHelpers.zip() and CollectionHelpers.zipWith()
054        public final Object[][] blahblah = {
055            { "HEAP_SIZE", "Memory", "512m", Type.MEMORY, OptionPlatform.ALL, Visibility.VISIBLE },
056            { "JOGL_TOGL", "Enable JOGL", "1", Type.BOOLEAN, OptionPlatform.UNIXLIKE, Visibility.VISIBLE },
057            { "USE_3DSTUFF", "Enable 3D controls", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
058            { "DEFAULT_LAYOUT", "Load default layout", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
059            { "STARTUP_BUNDLE", "Defaults", "", Type.DIRTREE, OptionPlatform.ALL, Visibility.VISIBLE },
060            /**
061             * SLIDER_TEST seems unnecessary...?
062             */
063    //        { "SLIDER_TEST", "Slider Test", "50P", Type.SLIDER, OptionPlatform.ALL, Visibility.VISIBLE },
064            /**
065             * TODO: DAVEP: TomW's windows machine needs SET D3DREND= to work properly.
066             * Not sure why, but it shouldn't hurt other users.  Investigate after Alpha10
067             */
068            { "D3DREND", "Enable Direct3D", "", Type.BOOLEAN, OptionPlatform.WINDOWS, Visibility.VISIBLE },
069            // mcidasv enables this (the actual property is "visad.java3d.geometryByRef")
070            // by default in mcidasv.properties.
071            { "USE_GEOBYREF", "Enable access to geometry by reference", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
072            { "USE_IMAGEBYREF", "Enable access to image data by reference", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
073            { "USE_NPOT", "Enable Non-Power of Two (NPOT) textures", "0", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
074            // temp bandaid for people suffering from permgen problems.
075            { "USE_CMSGC", "Enable concurrent mark-sweep garbage collector", "0", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
076        };
077    
078        /**
079         * {@link Option}s can be either platform-specific or applicable to all
080         * platforms. Options that are platform-specific still appear in the 
081         * UI, but their component is not enabled.
082         */
083        public enum OptionPlatform { ALL, UNIXLIKE, WINDOWS };
084    
085        /**
086         * The different types of {@link Option}s.
087         * @see TextOption
088         * @see BooleanOption
089         * @see MemoryOption
090         */
091        public enum Type { TEXT, BOOLEAN, MEMORY, DIRTREE, SLIDER };
092    
093        /** 
094         * Different ways that an {@link Option} might be displayed.
095         */
096        public enum Visibility { VISIBLE, HIDDEN };
097    
098        /** Maps an option ID to the corresponding object. */
099        private final Map<String, Option> optionMap;
100    
101        OptionMaster() {
102            normalizeUserDirectory();
103            optionMap = buildOptions(blahblah);
104    //        readStartup();
105        }
106    
107        /**
108         * Creates the specified options and returns a mapping of the option ID
109         * to the actual {@link Option} object.
110         * 
111         * @param options An array specifying the {@code Option}s to be built.
112         * 
113         * @return Mapping of ID to {@code Option}.
114         * 
115         * @throws AssertionError if the option array contained an entry that
116         * this method cannot build.
117         */
118        private Map<String, Option> buildOptions(final Object[][] options) {
119            // TODO(jon): seriously, get that zip stuff working! this array 
120            // stuff is BAD.
121            Map<String, Option> optMap = new HashMap<String, Option>();
122    
123            for (Object[] arrayOption : options) {
124                String id = (String)arrayOption[0];
125                String label = (String)arrayOption[1];
126                String defaultValue = (String)arrayOption[2];
127                Type type = (Type)arrayOption[3];
128                OptionPlatform platform = (OptionPlatform)arrayOption[4];
129                Visibility visibility = (Visibility)arrayOption[5];
130    
131                Option newOption;
132                switch (type) {
133                    case TEXT:
134                        newOption = new TextOption(id, label, defaultValue, 
135                            platform, visibility);
136                        break;
137                    case BOOLEAN:
138                        newOption = new BooleanOption(id, label, defaultValue, 
139                            platform, visibility);
140                        break;
141                    case MEMORY:
142                        newOption = new MemoryOption(id, label, defaultValue, 
143                            platform, visibility);
144                        break;
145                    case DIRTREE:
146                        newOption = new DirectoryOption(id, label, defaultValue, platform, visibility);
147                        break;
148                    case SLIDER:
149                        newOption = new SliderOption(id, label, defaultValue, platform, visibility);
150                        break;
151                    default:
152                         throw new AssertionError(type + 
153                             " is not known to OptionMaster.buildOptions()");
154                }
155                optMap.put(id, newOption);
156            }
157            return optMap;
158        }
159    
160        /**
161         * Converts a {@link Platform} to its corresponding 
162         * {@link OptionPlatform} type.
163         * 
164         * @return The current platform as a {@code OptionPlatform} type.
165         * 
166         * @throws AssertionError if {@link StartupManager#getPlatform()} 
167         * returned something that this method cannot convert.
168         */
169        // a lame-o hack :(
170        protected OptionPlatform convertToOptionPlatform() {
171            Platform platform = StartupManager.INSTANCE.getPlatform();
172            switch (platform) {
173                case WINDOWS: return OptionPlatform.WINDOWS;
174                case UNIXLIKE: return OptionPlatform.UNIXLIKE;
175                default: 
176                    throw new AssertionError("Unknown platform: " + platform);
177            }
178        }
179    
180        /**
181         * Returns the {@link Option} mapped to {@code id}.
182         * 
183         * @param id The ID whose associated {@code Option} is to be returned.
184         * 
185         * @return Either the {@code Option} associated with {@code id}, or 
186         * {@code null} if there was no association.
187         */
188        public Option getOption(final String id) {
189            return optionMap.get(id);
190        }
191    
192        // TODO(jon): getAllOptions and optionsBy* really need some work.
193        // I want to eventually do something like:
194        // Collection<Option> = getOpts().byPlatform(WINDOWS, ALL).byType(BOOLEAN).byVis(HIDDEN)
195        public Collection<Option> getAllOptions() {
196            return Collections.unmodifiableCollection(optionMap.values());
197        }
198    
199        public Collection<Option> optionsByPlatform(
200            final Set<OptionPlatform> platforms) 
201        {
202            if (platforms == null)
203                throw new NullPointerException();
204    
205            Collection<Option> allOptions = getAllOptions();
206            Collection<Option> filteredOptions = 
207                new ArrayList<Option>(allOptions.size());
208            for (Option option : allOptions)
209                if (platforms.contains(option.getOptionPlatform()))
210                    filteredOptions.add(option);
211    //      return Collections.unmodifiableCollection(filteredOptions);
212            return filteredOptions;
213        }
214    
215        public Collection<Option> optionsByType(final Set<Type> types) {
216            if (types == null)
217                throw new NullPointerException();
218    
219            Collection<Option> allOptions = getAllOptions();
220            Collection<Option> filteredOptions = 
221                new ArrayList<Option>(allOptions.size());
222            for (Option option : allOptions)
223                if (types.contains(option.getOptionType()))
224                    filteredOptions.add(option);
225    //      return Collections.unmodifiableCollection(filteredOptions);
226            return filteredOptions;
227        }
228    
229        public Collection<Option> optionsByVisibility(
230            final Set<Visibility> visibilities) 
231        {
232            if (visibilities == null)
233                throw new NullPointerException();
234    
235            Collection<Option> allOptions = getAllOptions();
236            Collection<Option> filteredOptions = 
237                new ArrayList<Option>(allOptions.size());
238            for (Option option : allOptions)
239                if (visibilities.contains(option.getOptionVisibility()))
240                    filteredOptions.add(option);
241    //        return Collections.unmodifiableCollection(filteredOptions);
242            return filteredOptions;
243        }
244    
245        private void normalizeUserDirectory() {
246            Platform platform = StartupManager.INSTANCE.getPlatform();
247            File dir = new File(platform.getUserDirectory());
248            File prefs = new File(platform.getUserPrefs());
249    
250            if (!dir.exists())
251                dir.mkdir();
252    
253            if (!prefs.exists()) {
254                try {
255                    File defaultPrefs = new File(platform.getDefaultPrefs());
256                    StartupManager.INSTANCE.copy(defaultPrefs, prefs);
257                } catch (IOException e) {
258                    System.err.println("Non-fatal error copying user preference template: "+e.getMessage());
259                }
260            }
261        }
262    
263        public void readStartup() {
264            String contents;
265            String line;
266    
267            File script = 
268                new File(StartupManager.INSTANCE.getPlatform().getUserPrefs());
269            System.err.println("reading "+script);
270            if (script.getPath().length() == 0)
271                return;
272    
273            try {
274                BufferedReader br = new BufferedReader(new FileReader(script));
275                while ((line = br.readLine()) != null) {
276                    if (line.startsWith("#"))
277                        continue;
278    
279                    contents = line.replace("=\"", "=");
280                    String[] chunks = contents.replace("SET ", "").split("=");
281                    if (chunks.length == 2) {
282                        Option option = getOption(chunks[0]);
283                        if (option != null)
284                            option.fromPrefsFormat(line);
285                    }
286                }
287                br.close();
288            } catch (IOException e) {
289                System.err.println("Non-fatal error reading the user preferences: "+e.getMessage());
290            }
291        }
292    
293        public void writeStartup() {
294            File script = 
295                new File(StartupManager.INSTANCE.getPlatform().getUserPrefs());
296            if (script.getPath().length() == 0)
297                return;
298    
299            // TODO(jon): use filters when you've made 'em less stupid
300            String newLine = 
301                StartupManager.INSTANCE.getPlatform().getNewLine();
302            OptionPlatform currentPlatform = convertToOptionPlatform();
303            StringBuilder contents = new StringBuilder();
304            for (Object[] arrayOption : blahblah) {
305                Option option = getOption((String)arrayOption[0]);
306                OptionPlatform platform = option.getOptionPlatform();
307                if (platform == OptionPlatform.ALL || platform == currentPlatform)
308                    contents.append(option.toPrefsFormat() + newLine);
309            }
310    
311            try {
312                BufferedWriter out = 
313                    new BufferedWriter(new FileWriter(script));
314                out.write(contents.toString());
315                out.close();
316            } catch (IOException e) {
317                e.printStackTrace();
318            }
319        }
320    }