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 }