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