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 }