001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 https://www.gnu.org/licenses/. 027 */ 028package edu.wisc.ssec.mcidasv.util; 029 030import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 031import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newLinkedHashMap; 032 033import java.io.BufferedReader; 034import java.io.File; 035import java.io.FileNotFoundException; 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.InputStreamReader; 039import java.lang.management.ManagementFactory; 040import java.lang.management.OperatingSystemMXBean; 041 042import java.net.URL; 043import java.nio.file.Path; 044import java.nio.file.Paths; 045import java.text.ParseException; 046import java.text.SimpleDateFormat; 047import java.util.Date; 048import java.util.Enumeration; 049import java.util.HashMap; 050import java.util.List; 051import java.util.Map; 052import java.util.Properties; 053import java.util.TimeZone; 054import java.util.TreeSet; 055 056import java.awt.DisplayMode; 057import java.awt.GraphicsConfiguration; 058import java.awt.GraphicsDevice; 059import java.awt.GraphicsEnvironment; 060import java.awt.Rectangle; 061import java.util.jar.Attributes; 062import java.util.jar.Manifest; 063 064import javax.management.MBeanServer; 065import javax.management.ObjectName; 066import javax.media.j3d.Canvas3D; 067import javax.media.j3d.VirtualUniverse; 068 069import com.google.common.base.Splitter; 070import org.python.core.Py; 071import org.python.core.PySystemState; 072 073import org.python.modules.posix.PosixModule; 074import org.slf4j.Logger; 075import org.slf4j.LoggerFactory; 076 077import ucar.nc2.grib.GribConverterUtility; 078import ucar.unidata.idv.ArgsManager; 079import ucar.unidata.idv.IdvResourceManager.IdvResource; 080import ucar.unidata.util.IOUtil; 081import ucar.unidata.util.ResourceCollection; 082import ucar.visad.display.DisplayUtil; 083 084import edu.wisc.ssec.mcidasv.Constants; 085import edu.wisc.ssec.mcidasv.McIDASV; 086import edu.wisc.ssec.mcidasv.StateManager; 087 088/** 089 * Utility methods for querying the state of the user's machine. 090 */ 091public class SystemState { 092 093 /** Handy logging object. */ 094 private static final Logger logger = 095 LoggerFactory.getLogger(SystemState.class); 096 097 // Don't allow outside instantiation. 098 private SystemState() { } 099 100 public static String escapeWhitespaceChars(final CharSequence sequence) { 101 StringBuilder sb = new StringBuilder(sequence.length() * 7); 102 for (int i = 0; i < sequence.length(); i++) { 103 switch (sequence.charAt(i)) { 104 case '\t': sb.append("\\t"); break; 105 case '\n': sb.append('\\').append('n'); break; 106 case '\013': sb.append("\\013"); break; 107 case '\f': sb.append("\\f"); break; 108 case '\r': sb.append("\\r"); break; 109 case '\u0085': sb.append("\\u0085"); break; 110 case '\u1680': sb.append("\\u1680"); break; 111 case '\u2028': sb.append("\\u2028"); break; 112 case '\u2029': sb.append("\\u2029"); break; 113 case '\u205f': sb.append("\\u205f"); break; 114 case '\u3000': sb.append("\\u3000"); break; 115 } 116 } 117 return sb.toString(); 118 } 119 120 /** 121 * Query an {@link OperatingSystemMXBean} attribute and return the result. 122 * 123 * @param <T> Type of the expected return value and {@code defaultValue}. 124 * @param attrName Name of the {@code OperatingSystemMXBean} attribute to 125 * query. Cannot be {@code null} or empty. 126 * @param defaultValue Value returned if {@code attrName} could not be 127 * queried. 128 * 129 * @return Either the value corresponding to {@code attrName} or 130 * {@code defaultValue}. 131 */ 132 private static <T> T queryPlatformBean(final String attrName, 133 final T defaultValue) 134 { 135 assert attrName != null : "Cannot query a null attribute name"; 136 assert !attrName.isEmpty() : "Cannot query an empty attribute name"; 137 T result = defaultValue; 138 try { 139 final ObjectName objName = 140 new ObjectName("java.lang", "type", "OperatingSystem"); 141 final MBeanServer beanServer = 142 ManagementFactory.getPlatformMBeanServer(); 143 final Object attr = beanServer.getAttribute(objName, attrName); 144 if (attr != null) { 145 // don't suppress warnings because we cannot guarantee that 146 // this cast is correct. 147 result = (T)attr; 148 } 149 } catch (Exception e) { 150 logger.error("Couldn't query attribute: " + attrName, e); 151 } 152 return result; 153 } 154 155 /** 156 * Returns the contents of Jython's registry (basically just Jython-specific 157 * properties) as well as some of the information from Python's 158 * {@literal "sys"} module. 159 * 160 * @return Jython's configuration settings. 161 */ 162 public static Map<Object, Object> queryJythonProps() { 163 Map<Object, Object> properties = newLinkedHashMap(PySystemState.registry); 164 try (PySystemState systemState = Py.getSystemState()) { 165 properties.put("sys.argv", systemState.argv.toString()); 166 properties.put("sys.path", systemState.path); 167 properties.put("sys.platform", systemState.platform.toString()); 168 } 169 properties.put("sys.builtin_module_names", PySystemState.builtin_module_names.toString()); 170 properties.put("sys.byteorder", PySystemState.byteorder); 171 properties.put("sys.isPackageCacheEnabled", PySystemState.isPackageCacheEnabled()); 172 properties.put("sys.version", PySystemState.version); 173 properties.put("sys.version_info", PySystemState.version_info); 174 return properties; 175 } 176 177 /** 178 * Attempts to call methods belonging to 179 * {@code com.sun.management.OperatingSystemMXBean}. If successful, we'll 180 * have the following information: 181 * <ul> 182 * <li>opsys.memory.virtual.committed: virtual memory that is guaranteed to be available</li> 183 * <li>opsys.memory.swap.total: total amount of swap space in bytes</li> 184 * <li>opsys.memory.swap.free: free swap space in bytes</li> 185 * <li>opsys.cpu.time: CPU time used by the process (nanoseconds)</li> 186 * <li>opsys.memory.physical.free: free physical memory in bytes</li> 187 * <li>opsys.memory.physical.total: physical memory in bytes</li> 188 * <li>opsys.load: system load average for the last minute</li> 189 * </ul> 190 * 191 * @return Map of properties that contains interesting information about 192 * the hardware McIDAS-V is using. 193 */ 194 public static Map<String, String> queryOpSysProps() { 195 Map<String, String> properties = newLinkedHashMap(10); 196 long committed = queryPlatformBean("CommittedVirtualMemorySize", Long.MIN_VALUE); 197 long freeMemory = queryPlatformBean("FreePhysicalMemorySize", Long.MIN_VALUE); 198 long freeSwap = queryPlatformBean("FreeSwapSpaceSize", Long.MIN_VALUE); 199 long cpuTime = queryPlatformBean("ProcessCpuTime", Long.MIN_VALUE); 200 long totalMemory = queryPlatformBean("TotalPhysicalMemorySize", Long.MIN_VALUE); 201 long totalSwap = queryPlatformBean("TotalSwapSpaceSize", Long.MIN_VALUE); 202 double loadAvg = queryPlatformBean("SystemLoadAverage", Double.NaN); 203 204 Runtime rt = Runtime.getRuntime(); 205 long currentMem = rt.totalMemory() - rt.freeMemory(); 206 207 properties.put("opsys.cpu.time", Long.toString(cpuTime)); 208 properties.put("opsys.load", Double.toString(loadAvg)); 209 properties.put("opsys.memory.jvm.current", Long.toString(currentMem)); 210 properties.put("opsys.memory.jvm.max", Long.toString(rt.maxMemory())); 211 properties.put("opsys.memory.virtual.committed", Long.toString(committed)); 212 properties.put("opsys.memory.physical.free", Long.toString(freeMemory)); 213 properties.put("opsys.memory.physical.total", Long.toString(totalMemory)); 214 properties.put("opsys.memory.swap.free", Long.toString(freeSwap)); 215 properties.put("opsys.memory.swap.total", Long.toString(totalSwap)); 216 217 return properties; 218 } 219 220 /** 221 * Polls Java for information about the user's machine. We're specifically 222 * after memory statistics, number of processors, and display information. 223 * 224 * @return {@link Map} of properties that describes the user's machine. 225 */ 226 public static Map<String, String> queryMachine() { 227 Map<String, String> props = newLinkedHashMap(); 228 229 // cpu count and whatnot 230 int processors = Runtime.getRuntime().availableProcessors(); 231 props.put("opsys.cpu.count", Integer.toString(processors)); 232 233 // memory: available, used, etc 234 props.putAll(queryOpSysProps()); 235 236 // screen: count, resolution(s) 237 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 238 int displayCount = ge.getScreenDevices().length; 239 240 for (int i = 0; i < displayCount; i++) { 241 String baseId = "opsys.display."+i+'.'; 242 GraphicsDevice dev = ge.getScreenDevices()[i]; 243 DisplayMode mode = dev.getDisplayMode(); 244 props.put(baseId+"name", dev.getIDstring()); 245 props.put(baseId+"depth", Integer.toString(mode.getBitDepth())); 246 props.put(baseId+"width", Integer.toString(mode.getWidth())); 247 props.put(baseId+"height", Integer.toString(mode.getHeight())); 248 props.put(baseId+"refresh", Integer.toString(mode.getRefreshRate())); 249 } 250 return props; 251 } 252 253 /** 254 * Returns a mapping of display number to a {@link java.awt.Rectangle} 255 * that represents the {@literal "bounds"} of the display. 256 * 257 * @return Rectangles representing the {@literal "bounds"} of the current 258 * display devices. 259 */ 260 public static Map<Integer, Rectangle> getDisplayBounds() { 261 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 262 int idx = 0; 263 Map<Integer, Rectangle> map = newLinkedHashMap(ge.getScreenDevices().length * 2); 264 for (GraphicsDevice dev : ge.getScreenDevices()) { 265 for (GraphicsConfiguration config : dev.getConfigurations()) { 266 map.put(idx++, config.getBounds()); 267 } 268 } 269 return map; 270 } 271 272 // TODO(jon): this should really be a polygon 273 public static Rectangle getVirtualDisplayBounds() { 274 Rectangle virtualBounds = new Rectangle(); 275 for (Rectangle bounds : getDisplayBounds().values()) { 276 virtualBounds = virtualBounds.union(bounds); 277 } 278 return virtualBounds; 279 } 280 281 /** 282 * Polls Java 3D for information about its environment. Specifically, we 283 * call {@link VirtualUniverse#getProperties()} and 284 * {@link Canvas3D#queryProperties()}. 285 * 286 * @return As much information as Java 3D can provide. 287 */ 288 @SuppressWarnings("unchecked") // casting to Object, so this should be fine. 289 public static Map<String, Object> queryJava3d() { 290 291 Map<String, Object> universeProps = 292 (Map<String, Object>)VirtualUniverse.getProperties(); 293 294 GraphicsConfiguration config = 295 DisplayUtil.getPreferredConfig(null, true, false); 296 Map<String, Object> c3dMap = new Canvas3D(config).queryProperties(); 297 298 Map<String, Object> props = 299 newLinkedHashMap(universeProps.size() + c3dMap.size()); 300 props.putAll(universeProps); 301 props.putAll(c3dMap); 302 return props; 303 } 304 305 /** 306 * Gets a human-friendly representation of the information embedded within 307 * IDV's {@code build.properties}. 308 * 309 * @return {@code String} that looks like {@literal "IDV version major.minor<b>revision</b> built <b>date</b>"}. 310 * For example: {@code IDV version 2.9u4 built 2011-04-13 14:01 UTC}. 311 */ 312 public static String getIdvVersionString() { 313 Map<String, String> info = queryIdvBuildProperties(); 314 return "IDV version " + info.get("idv.version.major") + '.' + 315 info.get("idv.version.minor") + info.get("idv.version.revision") + 316 " built " + info.get("idv.build.date"); 317 } 318 319 /** 320 * Gets a human-friendly representation of the information embedded within 321 * McIDAS-V's {@code build.properties}. 322 * 323 * @return {@code String} that looks like {@literal "McIDAS-V version major.minor<b>release</b> built <b>date</b>"}. 324 * For example: {@code McIDAS-V version 1.02beta1 built 2011-04-14 17:36}. 325 */ 326 public static String getMcvVersionString() { 327 Map<String, String> info = queryMcvBuildProperties(); 328 return "McIDAS-V version " + info.get(Constants.PROP_VERSION_MAJOR) + '.' + 329 info.get(Constants.PROP_VERSION_MINOR) + info.get(Constants.PROP_VERSION_RELEASE) + 330 " built " + info.get(Constants.PROP_BUILD_DATE); 331 } 332 333 /** 334 * Gets a human-friendly representation of the version information embedded 335 * within VisAD's {@literal "DATE"} file. 336 * 337 * @return {@code String} that looks 338 * {@literal "VisAD version <b>revision</b> built <b>date</b>"}. 339 * For example: {@code VisAD version 5952 built Thu Mar 22 13:01:31 CDT 2012}. 340 */ 341 public static String getVisadVersionString() { 342 Map<String, String> props = queryVisadBuildProperties(); 343 return "VisAD version " + props.get(Constants.PROP_VISAD_REVISION) + " built " + props.get(Constants.PROP_VISAD_DATE); 344 } 345 346 /** 347 * Gets a human-friendly representation of the ncIdv.jar version 348 * information. 349 * 350 * @return {@code String} that looks like 351 * {@literal "netCDF-Java version <b>revision</b> <b>built</b>"}. 352 */ 353 public static String getNcidvVersionString() { 354 Map<String, String> props = queryNcidvBuildProperties(); 355 return "netCDF-Java version " + props.get("version") + " built " + props.get("buildDate"); 356 } 357 358 /** 359 * Open a file for reading. 360 * 361 * @param name File to open. 362 * 363 * @return {@code InputStream} used to read {@code name}, or {@code null} 364 * if {@code name} could not be found. 365 */ 366 private static InputStream resourceAsStream(final String name) { 367 return ClassLoader.getSystemResourceAsStream(name); 368 } 369 370 /** 371 * Returns a {@link Map} containing any relevant version information. 372 * 373 * <p>Currently this information consists of the date visad.jar was built, 374 * as well as the (then-current) Subversion revision number. 375 * 376 * @return {@code Map} of the contents of VisAD's DATE file. 377 */ 378 public static Map<String, String> queryVisadBuildProperties() { 379 Map<String, String> props = newLinkedHashMap(4); 380 381 try (BufferedReader input = new BufferedReader( 382 new InputStreamReader( 383 resourceAsStream("DATE")))) 384 { 385 String contents = input.readLine(); 386 // string should look like: Thu Mar 22 13:01:31 CDT 2012 Rev:5952 387 String splitAt = " Rev:"; 388 int index = contents.indexOf(splitAt); 389 String buildDate = "ERROR"; 390 String revision = "ERROR"; 391 String parseFail = "true"; 392 if (index > 0) { 393 buildDate = contents.substring(0, index); 394 revision = contents.substring(index + splitAt.length()); 395 parseFail = "false"; 396 } 397 props.put(Constants.PROP_VISAD_ORIGINAL, contents); 398 props.put(Constants.PROP_VISAD_PARSE_FAIL, parseFail); 399 props.put(Constants.PROP_VISAD_DATE, buildDate); 400 props.put(Constants.PROP_VISAD_REVISION, revision); 401 } catch (IOException e) { 402 logger.error("could not read from VisAD DATE file", e); 403 } 404 return props; 405 } 406 407 /** 408 * Determine the actual name of the McIDAS-V JAR file. 409 * 410 * <p>This is needed because we're now following the Maven naming 411 * convention, and this means that {@literal "mcidasv.jar"} no longer 412 * exists.</p> 413 * 414 * @param jarDir Directory containing all of the McIDAS-V JARs. 415 * Cannot be {@code null}. 416 * 417 * @return Name (note: not path) of the McIDAS-V JAR file. 418 * 419 * @throws IOException if there was a problem locating the McIDAS-V JAR. 420 */ 421 private static String getMcvJarname(String jarDir) 422 throws IOException 423 { 424 File dir = new File(jarDir); 425 File[] files = dir.listFiles(); 426 if (files == null) { 427 throw new IOException("Could not get list of files within " + 428 "'"+jarDir+"'"); 429 } 430 for (File f : files) { 431 String name = f.getName(); 432 if (name.startsWith("mcidasv-") && name.endsWith(".jar")) { 433 return name; 434 } 435 } 436 throw new FileNotFoundException("Could not find McIDAS-V JAR file"); 437 } 438 439 /** 440 * Return McIDAS-V's classpath. 441 * 442 * <p>This may differ from what is reported by 443 * {@code System.getProperty("java.class.path")}. This is because McIDAS-V 444 * specifies its classpath using {@code META-INF/MANIFEST.MF} in 445 * {@code mcidasv.jar}. 446 * </p> 447 * 448 * <p>This is used by console_init.py to figure out where the VisAD, 449 * IDV, and McIDAS-V JAR files are located.</p> 450 * 451 * @return Either a list of strings containing the path to each JAR file 452 * in the classpath, or an empty list. 453 */ 454 public static List<String> getMcvJarClasspath() { 455 String jarDir = System.getProperty("user.dir"); 456 if (jarDir == null) { 457 jarDir = PosixModule.getcwd().toString(); 458 } 459 460 // 64 chosen because we're currently at 38 JARs. 461 List<String> jars = arrList(64); 462 try { 463 String mcvJar = getMcvJarname(jarDir); 464 Path p = Paths.get(jarDir, mcvJar); 465 String path = "jar:file:"+p.toString()+"!/META-INF/MANIFEST.MF"; 466 InputStream stream = IOUtil.getInputStream(path); 467 if (stream != null) { 468 Manifest manifest = new Manifest(stream); 469 Attributes attrs = manifest.getMainAttributes(); 470 if (attrs != null) { 471 String classpath = attrs.getValue("Class-Path"); 472 Splitter split = Splitter.on(' ') 473 .trimResults() 474 .omitEmptyStrings(); 475 for (String jar : split.split(classpath)) { 476 jars.add(Paths.get(jarDir, jar).toFile().toString()); 477 } 478 } 479 } 480 } catch (IOException | NullPointerException e) { 481 logger.warn("Exception occurred:", e); 482 } 483 return jars; 484 } 485 486 /** 487 * Returns a {@link Map} containing {@code version} and {@code buildDate} 488 * keys. 489 * 490 * @return {@code Map} containing netCDF-Java version and build date. 491 */ 492 public static Map<String, String> queryNcidvBuildProperties() { 493 // largely taken from LibVersionUtil's getBuildInfo method. 494 GribConverterUtility util = new GribConverterUtility(); 495 Map<String, String> buildInfo = new HashMap<>(4); 496 // format from ncidv.jar 497 SimpleDateFormat formatIn = 498 new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); 499 // format of output timestamp 500 SimpleDateFormat formatOut = 501 new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ"); 502 formatOut.setTimeZone(TimeZone.getTimeZone("UTC")); 503 try { 504 Enumeration<URL> resources = 505 util.getClass() 506 .getClassLoader() 507 .getResources("META-INF/MANIFEST.MF"); 508 509 while (resources.hasMoreElements()) { 510 URL resource = resources.nextElement(); 511 if (!resource.toString().contains("ncIdv")) { 512 // skip over all of the JARs in the classpath except for ncIdv. 513 continue; 514 } 515 Manifest manifest = 516 new Manifest(resource.openStream()); 517 Attributes attrs = manifest.getMainAttributes(); 518 if (attrs != null) { 519 buildInfo.put( 520 "version", 521 attrs.getValue("Implementation-Version")); 522 String strDate = attrs.getValue("Built-On"); 523 Date date = formatIn.parse(strDate); 524 buildInfo.put("buildDate", formatOut.format(date)); 525 break; 526 } 527 } 528 } catch (IOException e) { 529 logger.warn("exception occurred:", e); 530 } catch (ParseException pe) { 531 logger.warn("failed to parse build date from ncIdv.jar", pe); 532 } 533 return buildInfo; 534 } 535 536 /** 537 * Returns a {@link Map} of the (currently) most useful contents of 538 * {@code ucar/unidata/idv/resources/build.properties}. 539 * 540 * <p>Consider the output of {@link #getIdvVersionString()}; it's built 541 * with the the following: 542 * <ul> 543 * <li><b>{@code idv.version.major}</b>: currently {@literal "3"}</li> 544 * <li><b>{@code idv.version.minor}</b>: currently {@literal "0"}</li> 545 * <li><b>{@code idv.version.revision}</b>: currently {@literal "u2"}}</li> 546 * <li><b>{@code idv.build.date}</b>: varies pretty frequently, 547 * as it's the build timestamp for idv.jar</li> 548 * </ul> 549 * 550 * @return A {@code Map} of at least the useful parts of build.properties. 551 */ 552 public static Map<String, String> queryIdvBuildProperties() { 553 Map<String, String> versions = newLinkedHashMap(4); 554 InputStream input = null; 555 try { 556 input = resourceAsStream("ucar/unidata/idv/resources/build.properties"); 557 Properties props = new Properties(); 558 props.load(input); 559 String major = props.getProperty("idv.version.major", "no_major"); 560 String minor = props.getProperty("idv.version.minor", "no_minor"); 561 String revision = props.getProperty("idv.version.revision", "no_revision"); 562 String date = props.getProperty("idv.build.date", ""); 563 versions.put("idv.version.major", major); 564 versions.put("idv.version.minor", minor); 565 versions.put("idv.version.revision", revision); 566 versions.put("idv.build.date", date); 567 } catch (Exception e) { 568 logger.error("could not read from IDV build.properties", e); 569 } finally { 570 if (input != null) { 571 try { 572 input.close(); 573 } catch (Exception ex) { 574 logger.error("could not close IDV build.properties", ex); 575 } 576 } 577 } 578 return versions; 579 } 580 581 /** 582 * Returns a {@link Map} of the (currently) most useful contents of 583 * {@code edu/wisc/ssec/mcidasv/resources/build.properties}. 584 * 585 * <p>Consider the output of {@link #getMcvVersionString()}; it's built 586 * with the the following: 587 * <ul> 588 * <li><b>{@code mcidasv.version.major}</b>: 589 * currently {@literal "1"}</li> 590 * <li><b>{@code mcidasv.version.minor}</b>: 591 * currently {@literal "02"}</li> 592 * <li><b>{@code mcidasv.version.release}</b>: currently 593 * {@literal "beta1"}</li> 594 * <li><b>{@code mcidasv.build.date}</b>: varies pretty frequently, as 595 * it's the build timestamp for mcidasv.jar.</li> 596 * </ul> 597 * 598 * @return A {@code Map} of at least the useful parts of build.properties. 599 */ 600 public static Map<String, String> queryMcvBuildProperties() { 601 Map<String, String> versions = newLinkedHashMap(4); 602 InputStream input = null; 603 try { 604 input = resourceAsStream("edu/wisc/ssec/mcidasv/resources/build.properties"); 605 Properties props = new Properties(); 606 props.load(input); 607 String major = props.getProperty(Constants.PROP_VERSION_MAJOR, "0"); 608 String minor = props.getProperty(Constants.PROP_VERSION_MINOR, "0"); 609 String release = props.getProperty(Constants.PROP_VERSION_RELEASE, ""); 610 String date = props.getProperty(Constants.PROP_BUILD_DATE, "Unknown"); 611 versions.put(Constants.PROP_VERSION_MAJOR, major); 612 versions.put(Constants.PROP_VERSION_MINOR, minor); 613 versions.put(Constants.PROP_VERSION_RELEASE, release); 614 versions.put(Constants.PROP_BUILD_DATE, date); 615 } catch (Exception e) { 616 logger.error("could not read from McIDAS-V build.properties!", e); 617 } finally { 618 if (input != null) { 619 try { 620 input.close(); 621 } catch (Exception ex) { 622 logger.error("could not close McIDAS-V build.properties!", ex); 623 } 624 } 625 } 626 return versions; 627 } 628 629 /** 630 * Queries McIDAS-V for information about its state. There's not a good way 631 * to characterize what we're interested in, so let's leave it at 632 * {@literal "whatever seems useful"}. 633 * 634 * @param mcv The McIDASV {@literal "god"} object. 635 * 636 * @return Information about the state of McIDAS-V. 637 */ 638 // need: argsmanager, resource manager 639 public static Map<String, Object> queryMcvState(final McIDASV mcv) { 640 // through some simple verification, props generally has under 250 elements 641 Map<String, Object> props = newLinkedHashMap(250); 642 643 ArgsManager args = mcv.getArgsManager(); 644 props.put("mcv.state.islinteractive", args.getIslInteractive()); 645 props.put("mcv.state.offscreen", args.getIsOffScreen()); 646 props.put("mcv.state.initcatalogs", args.getInitCatalogs()); 647 props.put("mcv.state.actions", mcv.getActionHistory()); 648 props.put("mcv.plugins.installed", args.installPlugins); 649 props.put("mcv.state.commandline", mcv.getCommandLineArgs()); 650 651 // loop through resources 652 List<IdvResource> resources = 653 (List<IdvResource>)mcv.getResourceManager().getResources(); 654 for (IdvResource resource : resources) { 655 String id = resource.getId(); 656 props.put(id+".description", resource.getDescription()); 657 if (resource.getPattern() == null) { 658 props.put(id+".pattern", "null"); 659 } else { 660 props.put(id+".pattern", resource.getPattern()); 661 } 662 663 ResourceCollection rc = 664 mcv.getResourceManager().getResources(resource); 665 int rcSize = rc.size(); 666 List<String> specified = arrList(rcSize); 667 List<String> valid = arrList(rcSize); 668 for (int i = 0; i < rcSize; i++) { 669 String tmpResource = (String)rc.get(i); 670 specified.add(tmpResource); 671 if (rc.isValid(i)) { 672 valid.add(tmpResource); 673 } 674 } 675 676 props.put(id+".specified", specified); 677 props.put(id+".existing", valid); 678 } 679 return props; 680 } 681 682 /** 683 * Builds a (filtered) subset of the McIDAS-V system properties and returns 684 * the results as a {@code String}. 685 * 686 * @param mcv The McIDASV {@literal "god"} object. 687 * 688 * @return The McIDAS-V system properties in the following format: 689 * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard 690 * {@link Properties} file format. 691 * 692 * @see #getStateAsString(edu.wisc.ssec.mcidasv.McIDASV, boolean) 693 */ 694 public static String getStateAsString(final McIDASV mcv) { 695 return getStateAsString(mcv, false); 696 } 697 698 /** 699 * Builds the McIDAS-V system properties and returns the results as a 700 * {@code String}. 701 * 702 * @param mcv The McIDASV {@literal "god"} object. 703 * @param firehose If {@code true}, enables {@literal "unfiltered"} output. 704 * 705 * @return The McIDAS-V system properties in the following format: 706 * {@code KEY=VALUE\n}. This is so we kinda-sorta conform to the standard 707 * {@link Properties} file format. 708 */ 709 public static String getStateAsString(final McIDASV mcv, final boolean firehose) { 710 int builderSize = firehose ? 45000 : 1000; 711 StringBuilder buf = new StringBuilder(builderSize); 712 713 Map<String, String> versions = ((StateManager)mcv.getStateManager()).getVersionInfo(); 714 Properties sysProps = System.getProperties(); 715 Map<String, Object> j3dProps = queryJava3d(); 716 Map<String, String> machineProps = queryMachine(); 717 Map<Object, Object> jythonProps = queryJythonProps(); 718 Map<String, Object> mcvProps = queryMcvState(mcv); 719 720 if (sysProps.contains("line.separator")) { 721 sysProps.put("line.separator", escapeWhitespaceChars((String)sysProps.get("line.separator"))); 722 } 723 724 String maxMem = Long.toString(Long.valueOf(machineProps.get("opsys.memory.jvm.max")) / 1048576L); 725 String curMem = Long.toString(Long.valueOf(machineProps.get("opsys.memory.jvm.current")) / 1048576L); 726 727 buf.append("# Software Versions:") 728 .append("\n# McIDAS-V: ").append(versions.get("mcv.version.general")).append(" (").append(versions.get("mcv.version.build")).append(')') 729 .append("\n# VisAD: ").append(versions.get("visad.version.general")).append(" (").append(versions.get("visad.version.build")).append(')') 730 .append("\n# IDV: ").append(versions.get("idv.version.general")).append(" (").append(versions.get("idv.version.build")).append(')') 731 .append("\n# netcdf-Java: ").append(versions.get("netcdf.version.general")).append(" (").append(versions.get("netcdf.version.build")).append(')') 732 .append("\n\n# Operating System:") 733 .append("\n# Name: ").append(sysProps.getProperty("os.name")) 734 .append("\n# Version: ").append(sysProps.getProperty("os.version")) 735 .append("\n# Architecture: ").append(sysProps.getProperty("os.arch")) 736 .append("\n\n# Java:") 737 .append("\n# Version: ").append(sysProps.getProperty("java.version")) 738 .append("\n# Vendor: ").append(sysProps.getProperty("java.vendor")) 739 .append("\n# Home: ").append(sysProps.getProperty("java.home")) 740 .append("\n\n# JVM Memory") 741 .append("\n# Current: ").append(curMem).append(" MB") 742 .append("\n# Maximum: ").append(maxMem).append(" MB") 743 .append("\n\n# Java 3D:") 744 .append("\n# Renderer: ").append(j3dProps.get("j3d.renderer")) 745 .append("\n# Pipeline: ").append(j3dProps.get("j3d.pipeline")) 746 .append("\n# Vendor: ").append(j3dProps.get("native.vendor")) 747 .append("\n# Version: ").append(j3dProps.get("j3d.version")) 748 .append("\n\n# Jython:") 749 .append("\n# Version: ").append(jythonProps.get("sys.version_info")) 750 .append("\n# python.home: ").append(jythonProps.get("python.home")); 751 752 if (firehose) { 753 buf.append("\n\n\n#Firehose:\n\n# SOFTWARE VERSIONS\n"); 754 for (String key : new TreeSet<>(versions.keySet())) { 755 buf.append(key).append('=').append(versions.get(key)).append('\n'); 756 } 757 758 buf.append("\n# MACHINE PROPERTIES\n"); 759 for (String key : new TreeSet<>(machineProps.keySet())) { 760 buf.append(key).append('=').append(machineProps.get(key)).append('\n'); 761 } 762 763 buf.append("\n# JAVA SYSTEM PROPERTIES\n"); 764 for (Object key : new TreeSet<>(sysProps.keySet())) { 765 buf.append(key).append('=').append(sysProps.get(key)).append('\n'); 766 } 767 768 buf.append("\n# JAVA3D/JOGL PROPERTIES\n"); 769 for (String key : new TreeSet<>(j3dProps.keySet())) { 770 buf.append(key).append('=').append(j3dProps.get(key)).append('\n'); 771 } 772 773 buf.append("\n# JYTHON PROPERTIES\n"); 774 for (Object key : new TreeSet<>(jythonProps.keySet())) { 775 buf.append(key).append('=').append(jythonProps.get(key)).append('\n'); 776 } 777 778 // get idv/mcv properties 779 buf.append("\n# IDV AND MCIDAS-V PROPERTIES\n"); 780 for (String key : new TreeSet<>(mcvProps.keySet())) { 781 buf.append(key).append('=').append(mcvProps.get(key)).append('\n'); 782 } 783 } 784 return buf.toString(); 785 } 786}