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