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; 030 031import java.awt.Component; 032import java.awt.Dimension; 033import java.awt.Font; 034import java.awt.GraphicsEnvironment; 035import java.rmi.RemoteException; 036 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.List; 040 041import javax.swing.JComponent; 042import javax.swing.JLabel; 043import javax.swing.JOptionPane; 044import javax.swing.JPanel; 045import javax.swing.JScrollPane; 046import javax.swing.JTextArea; 047 048import edu.wisc.ssec.mcidasv.startupmanager.options.FileOption; 049import visad.VisADException; 050 051import ucar.unidata.idv.IdvConstants; 052 053import ucar.unidata.idv.ArgsManager; 054import ucar.unidata.idv.IntegratedDataViewer; 055import ucar.unidata.util.GuiUtils; 056import ucar.unidata.util.IOUtil; 057import ucar.unidata.util.Msg; 058import ucar.unidata.util.LogUtil; 059import ucar.unidata.util.PatternFileFilter; 060import ucar.unidata.util.StringUtil; 061 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 066 067/** 068 * McIDAS-V needs to handle a few command line flags/options that the IDV does 069 * not. Only the ability to force the Aqua look and feel currently exists. 070 * 071 * @author McIDAS-V Developers 072 */ 073public class ArgumentManager extends ArgsManager { 074 075 private static final Logger helpLogger = LoggerFactory.getLogger("mcvstdout"); 076 077 /** McIDAS-V flag that signifies everything that follows is a Jython argument. */ 078 public static final String ARG_JYTHONARGS = "-scriptargs"; 079 080 public static final String ARG_LOGPATH = "-logpath"; 081 082 /** Usage message. */ 083 public static final String USAGE_MESSAGE = 084 "Usage: runMcV [OPTIONS] <bundle/script files, e.g., .mcv, .mcvz, .py>"; 085 086 /** {@literal "__name__"} to use when no Jython/Python script has been provided at startup. */ 087 public static final String NO_PYTHON_MODULE = "<none>"; 088 089 /** Jython arguments, if any. */ 090 private List<String> jythonArguments; 091 092 /** Jython script to execute, or {@literal "<none>"} if one was not given. */ 093 private String jythonScript; 094 095 /** Given by the "-user" argument. Alternative user path for bundles, resources, etc. */ 096 String defaultUserDirectory = StartupManager.getInstance().getPlatform().getUserDirectory(); 097 098 /** 099 * Just bubblin' on up the inheritance hierarchy. 100 * 101 * @param idv The IDV instance. 102 * @param args The command line arguments that were given. 103 */ 104 public ArgumentManager(IntegratedDataViewer idv, String[] args) { 105 super(idv, args); 106 jythonArguments = new ArrayList<>(args.length); 107 jythonScript = NO_PYTHON_MODULE; 108 } 109 110 private static List<String> extractJythonArgs(int index, String... args) { 111 List<String> jythonArgs = new ArrayList<>(args.length); 112 for (int i = index; i < args.length; i++) { 113 jythonArgs.add(args[i]); 114 } 115 return jythonArgs; 116 } 117 118 /** 119 * Currently we're only handling the {@code -forceaqua} flag so we can 120 * mitigate some overlay issues we've been seeing on OS X Leopard. 121 * 122 * @param arg The current argument we're examining. 123 * @param args The actual array of arguments. 124 * @param idx The index of {@code arg} within {@code args}. 125 * 126 * @return The idx of the last value in the args array we look at. i.e., 127 * if the flag arg does not require any further values in the args array 128 * then don't increment idx. If arg requires one more value then 129 * increment idx by one. etc. 130 * 131 * @throws Exception Throw bad things off to something that can handle 'em! 132 */ 133 protected int parseArg(String arg, String[] args, int idx) 134 throws Exception { 135 136 if ("-forceaqua".equals(arg)) { 137 // unfortunately we can't simply set the look and feel here. If I 138 // were to do so, the loadLookAndFeel in the IdvUIManager would 139 // eventually get loaded and then set the look and feel to whatever 140 // the preferences dictate. 141 // instead I use the boolean toggle to signal to McV's 142 // UIManager.loadLookAndFeel that it should simply ignore the user's 143 // preference is and load the Aqua L&F from there. 144 McIDASV.useAquaLookAndFeel = true; 145 } else if (ARG_HELP.equals(arg)) { 146 String msg = USAGE_MESSAGE + "\n" + getUsageMessage(); 147 if (McIDASV.isWindows() && !GraphicsEnvironment.isHeadless()) { 148 userMessage(msg, false); 149 } else { 150 helpLogger.info(System.getProperty("line.separator") + msg); 151 } 152 ((McIDASV)getIdv()).exit(1); 153 } else if (checkArg(arg, "-script", args, idx, 1) || checkArg(arg, "-pyfile", args, idx, 1)) { 154 String scriptArg = args[idx++]; 155 jythonScript = scriptArg; 156 scriptingFiles.add(scriptArg); 157 if (!getIslInteractive()) { 158 setIsOffScreen(true); 159 } 160 } else if ("-console".equals(arg)) { 161 System.err.println("*** WARNING: console flag is likely to go away soon!"); 162 } else if (ARG_JYTHONARGS.equals(arg)) { 163 if (scriptingFiles.isEmpty()) { 164 System.err.println("*** WARNING: Jython script arguments will be ignored unless you provide a Jython script to execute!"); 165 } else { 166 jythonArguments.addAll(extractJythonArgs(idx, args)); 167 168 // jump to end of args to halt further idv processing. 169 return args.length; 170 } 171 } else if (checkArg(arg, ARG_LOGPATH, args, idx, 1)) { 172 String argValue = args[idx++]; 173 persistentCommandLineArgs.add(ARG_LOGPATH); 174 persistentCommandLineArgs.add(argValue); 175 } else if (checkArg(arg, ARG_BUNDLE, args, idx, 1)) { 176 String argValue = args[idx++]; 177 String[] results = FileOption.parseFormat(argValue); 178 if (FileOption.booleanFromFormat(results[0])) { 179 argXidvFiles.add(results[1]); 180 } 181 System.err.println("result[0]: "+FileOption.booleanFromFormat(results[0])); 182 System.err.println("result[1]: '"+results[1]+'\''); 183 } else { 184 if (ARG_ISLINTERACTIVE.equals(arg) || ARG_B64ISL.equals(arg) || ARG_ISLFILE.equals(arg) || isIslFile(arg)) { 185 System.err.println("*** WARNING: ISL is being deprecated!"); 186 } else if (arg.startsWith("-D")) { 187 List<String> l = StringUtil.split(arg.substring(2), "="); 188 if (l.size() == 2) { 189 System.setProperty(l.get(0), l.get(1)); 190 } 191 } 192 return super.parseArg(arg, args, idx); 193 } 194 return idx; 195 } 196 197 /** 198 * Get the {@link JComponent} that displays the given message. 199 * 200 * @param msg Message to display. 201 * @param breakLines Whether or not {@literal "long"} lines should be broken up. 202 * 203 * @return {@code JComponent} that displays {@code msg}. 204 */ 205 private static JComponent getMessageComponent(String msg, boolean breakLines) { 206 if (msg.startsWith("<html>")) { 207 Component[] comps = GuiUtils.getHtmlComponent(msg, null, 500, 400); 208 return (JScrollPane)comps[1]; 209 } 210 211 int msgLength = msg.length(); 212 if (msgLength < 50) { 213 return new JLabel(msg); 214 } 215 216 StringBuilder sb = new StringBuilder(msgLength * 2); 217 if (breakLines) { 218 for (String line : StringUtil.split(msg, "\n")) { 219 line = StringUtil.breakText(line, "\n", 50); 220 sb.append(line).append('\n'); 221 } 222 } else { 223 sb.append(msg).append('\n'); 224 } 225 226 JTextArea textArea = new JTextArea(sb.toString()); 227 textArea.setFont(textArea.getFont().deriveFont(Font.BOLD)); 228 textArea.setBackground(new JPanel().getBackground()); 229 textArea.setEditable(false); 230 JScrollPane textSp = GuiUtils.makeScrollPane(textArea, 400, 200); 231 textSp.setPreferredSize(new Dimension(400, 200)); 232 return textSp; 233 } 234 235 /** 236 * Show a dialog containing a message. 237 * 238 * @param msg Message to display. 239 * @param breakLines If {@code true}, long lines are split. 240 */ 241 public static void userMessage(String msg, boolean breakLines) { 242 msg = Msg.msg(msg); 243 if (LogUtil.showGui()) { 244 LogUtil.consoleMessage(msg); 245 JComponent msgComponent = getMessageComponent(msg, breakLines); 246 GuiUtils.addModalDialogComponent(msgComponent); 247 JOptionPane.showMessageDialog(LogUtil.getCurrentWindow(), msgComponent); 248 GuiUtils.removeModalDialogComponent(msgComponent); 249 } else { 250 System.err.println(msg); 251 } 252 } 253 254 /** 255 * Show a dialog containing an error message. 256 * 257 * @param msg Error message to display. 258 * @param breakLines If {@code true}, long lines are split. 259 */ 260 public static void userErrorMessage(String msg, boolean breakLines) { 261 msg = Msg.msg(msg); 262 if (LogUtil.showGui()) { 263 LogUtil.consoleMessage(msg); 264 JComponent msgComponent = getMessageComponent(msg, breakLines); 265 GuiUtils.addModalDialogComponent(msgComponent); 266 JOptionPane.showMessageDialog(LogUtil.getCurrentWindow(), 267 msgComponent, "Error", JOptionPane.ERROR_MESSAGE); 268 GuiUtils.removeModalDialogComponent(msgComponent); 269 } else { 270 System.err.println(msg); 271 } 272 } 273 274 /** 275 * Print out the command line usage message and exit 276 * 277 * @param err The usage message 278 */ 279 @Override public void usage(String err) { 280 List<String> chunks = StringUtil.split(err, ":"); 281 if (chunks.size() == 2) { 282 err = chunks.get(0) + ": " + chunks.get(1) + '\n'; 283 } 284 String msg = USAGE_MESSAGE; 285 msg = msg + '\n' + getUsageMessage(); 286 userErrorMessage(err + '\n' + msg, false); 287 ((McIDASV)getIdv()).exit(1); 288 } 289 290 /** 291 * Format a line in the {@literal "usage message"} output. The chief 292 * difference between this method and 293 * {@link ArgsManager#msg(String, String)} is that this method prefixes 294 * each line with four {@literal "space"} characters, rather than a single 295 * {@literal "tab"} character. 296 * 297 * @param arg Commandline argument. 298 * @param desc Description of the argument. 299 * 300 * @return Formatted line (suitable for {@link #getUsageMessage()}. 301 */ 302 @Override protected String msg(String arg, String desc) { 303 return " " + arg + ' ' + desc + '\n'; 304 } 305 306 /** 307 * Append some McIDAS-V specific command line options to the default IDV 308 * usage message. 309 * 310 * @return Usage message. 311 */ 312 protected String getUsageMessage() { 313 return msg(ARG_HELP, "(this message)") 314 + msg("-forceaqua", "Forces the Aqua look and feel on OS X") 315 + msg(ARG_PROPERTIES, "<property file>") 316 + msg("-Dpropertyname=value", "(Define the property value)") 317 + msg(ARG_INSTALLPLUGIN, "<plugin jar file or url to install>") 318 + msg(ARG_PLUGIN, "<plugin jar file, directory, url for this run>") 319 + msg(ARG_NOPLUGINS, "Don't load plugins") 320 + msg(ARG_CLEARDEFAULT, "(Clear the default bundle)") 321 + msg(ARG_NODEFAULT, "(Don't read in the default bundle file)") 322 + msg(ARG_DEFAULT, "<.mcv/.mcvz file>") 323 + msg(ARG_BUNDLE, "<bundle file or url>") 324 + msg(ARG_B64BUNDLE, "<base 64 encoded inline bundle>") 325 + msg(ARG_SETFILES, "<datasource pattern> <semi-colon delimited list of files> (Use the list of files for the bundled datasource)") 326 + msg(ARG_ONEINSTANCEPORT, "<port number> (Check if another version of McIDAS-V is running. If so pass command line arguments to it and shutdown)") 327 + msg(ARG_NOONEINSTANCE, "(Don't do the one instance port)") 328 + msg(ARG_NOPREF, "(Don't read in the user preferences)") 329 + msg(ARG_USERPATH, "<user directory to use>") 330 + msg("-tempuserpath", "(Starts McIDAS-V with a randomly generated temporary userpath)") 331 + msg(ARG_LOGPATH, "<path to log file>") 332 + msg(ARG_SITEPATH, "<url path to find site resources>") 333 + msg(ARG_NOGUI, "(Don't show the main window gui)") 334 + msg(ARG_DATA, "<data source> (Load the data source)") 335 + msg(ARG_DISPLAY, "<parameter> <display>") 336// + msg("<scriptfile.isl>", "(Run the IDV script in batch mode)") 337 + msg("-script", "<jython script file to evaluate>") 338 + msg("-pyfile", "<jython script file to evaluate>") 339 + msg(ARG_JYTHONARGS, "All arguments after this flag will be considered Jython arguments.") 340// + msg(ARG_B64ISL, "<base64 encoded inline isl> This will run the isl in interactive mode") 341// + msg(ARG_ISLINTERACTIVE, "run any isl files in interactive mode") 342 + msg(ARG_IMAGE, "<image file name> (create a jpeg image and then exit)") 343 + msg(ARG_MOVIE, "<movie file name> (create a quicktime movie and then exit)") 344 + msg(ARG_IMAGESERVER, "<port number or .properties file> (run McIDAS-V in image generation server mode. Support http requests on the given port)") 345 + msg(ARG_CATALOG, "<url to a chooser catalog>") 346 + msg(ARG_CONNECT, "<collaboration hostname to connect to>") 347 + msg(ARG_SERVER, "(Should McIDAS-V run in collaboration server mode)") 348 + msg(ARG_PORT, "<Port number collaboration server should listen on>") 349 + msg(ARG_CHOOSER, "(show the data chooser on start up) ") 350 + msg(ARG_PRINTJNLP, "(Print out any embedded bundles from jnlp files)") 351 + msg(ARG_CURRENTTIME, "<dttm> (Override current time for background processing)") 352// + msg(ARG_CURRENTTIME, "<dttm> (Override current time for ISL processing)") 353 + msg(ARG_LISTRESOURCES, "<list out the resource types") 354 + msg(ARG_DEBUG, "(Turn on debug print)") 355 + msg(ARG_MSG_DEBUG, "(Turn on language pack debug)") 356 + msg(ARG_MSG_RECORD, "<Language pack file to write missing entries to>") 357 + msg(ARG_TRACE, "(Print out trace messages)") 358 + msg(ARG_NOERRORSINGUI, "(Don't show errors in gui)") 359 + msg(ARG_TRACEONLY, "<trace pattern> (Print out trace messages that match the pattern)") 360 + msg("-console", "[ fix for getting the console functionality in install4j launcher ]"); 361 } 362 363 /** 364 * Determine whether or not the user has provided any arguments for a 365 * Jython script. 366 * 367 * @return {@code true} if the user has provided Jython arguments, 368 * {@code false} otherwise. 369 */ 370 public boolean hasJythonArguments() { 371 return !jythonArguments.isEmpty(); 372 } 373 374 /** 375 * Returns Jython arguments. <b>Note:</b> this does not include the Jython 376 * script that will be executed. 377 * 378 * @return Either a {@link List} of {@link String Strings} containing the 379 * arguments or an empty {@code List} if there were no arguments given. 380 */ 381 public List<String> getJythonArguments() { 382 return jythonArguments; 383 } 384 385 /** 386 * Returns the name of the Jython script the user has provided. 387 * 388 * @return Either the path to a Jython file or {@literal "<none>"} if the 389 * user did not provide a script. 390 */ 391 public String getJythonScript() { 392 return jythonScript; 393 } 394 395 /** 396 * Gets called by the IDV to process the set of initial files, e.g., 397 * default bundles, command line bundles, jnlp files, etc. 398 * 399 * <p>Overridden by McIDAS-V to remove bundle file paths that are zero 400 * characters long. This was happening because {@code runMcV.bat} was 401 * always passing {@literal '-bundle ""'} on the command line (for Windows). 402 * 403 * @throws VisADException When something untoward happens 404 * @throws RemoteException When something untoward happens 405 */ 406 @Override protected void processInitialBundles() 407 throws VisADException, RemoteException 408 { 409 for (int i = 0; i < argXidvFiles.size(); i++) { 410 String path = (String)argXidvFiles.get(i); 411 if (path.isEmpty()) { 412 argXidvFiles.remove(i); 413 } 414 } 415 super.processInitialBundles(); 416 } 417 418 /** 419 * @see ArgsManager#getBundleFileFilters() 420 */ 421 @Override public List<PatternFileFilter> getBundleFileFilters() { 422 List<PatternFileFilter> filters = new ArrayList<>(10); 423 Collections.addAll(filters, getXidvFileFilter(), getZidvFileFilter(), FILTER_JNLP); 424 return filters; 425 } 426 427 /** 428 * Returns a list of {@link PatternFileFilter}s that can be used to determine 429 * if a file is a bundle. 430 * 431 * <p>If {@code fromOpen} is {@code true}, the 432 * returned list will contain {@code PatternFileFilter}s for bundles as 433 * well as JNLP and ISL files. If {@code false}, the returned list will 434 * only contain filters for XML and zipped bundles. 435 * 436 * @param fromOpen Whether or not this has been called from an 437 * {@literal "open file"} dialog. 438 * 439 * @return Filters for bundles. 440 */ 441 public List<PatternFileFilter> getBundleFilters(final boolean fromOpen) { 442 List<PatternFileFilter> filters; 443 if (fromOpen) { 444 filters = new ArrayList<>(10); 445 Collections.addAll(filters, getXidvZidvFileFilter(), FILTER_JNLP, FILTER_ISL, super.getXidvZidvFileFilter()); 446 } else { 447 filters = new ArrayList<>(getBundleFileFilters()); 448 } 449 return filters; 450 } 451 452 /** 453 * @see ArgsManager#getXidvFileFilter() 454 */ 455 @Override public PatternFileFilter getXidvFileFilter() { 456 return Constants.FILTER_MCV; 457 } 458 459 /** 460 * @see ArgsManager#getZidvFileFilter() 461 */ 462 @Override public PatternFileFilter getZidvFileFilter() { 463 return Constants.FILTER_MCVZ; 464 } 465 466 /** 467 * @see ArgsManager#getXidvZidvFileFilter() 468 */ 469 @Override public PatternFileFilter getXidvZidvFileFilter() { 470 return Constants.FILTER_MCVMCVZ; 471 } 472 473 /* 474 * There's some internal IDV file opening code that relies on this method. 475 * We've gotta override if we want to use .zidv bundles. 476 */ 477 @Override public boolean isZidvFile(final String name) { 478 return isZippedBundle(name); 479 } 480 481 /* same story as isZidvFile! */ 482 @Override public boolean isXidvFile(final String name) { 483 return isXmlBundle(name); 484 } 485 486 /** 487 * Tests to see if {@code name} has a known XML bundle extension. 488 * 489 * @param name Name of the bundle. 490 * 491 * @return Whether or not {@code name} has an XML bundle suffix. 492 */ 493 public static boolean isXmlBundle(final String name) { 494 return IOUtil.hasSuffix(name, Constants.FILTER_MCV.getPreferredSuffix()) 495 || IOUtil.hasSuffix(name, IdvConstants.FILTER_XIDV.getPreferredSuffix()); 496 } 497 498 /** 499 * Tests to see if {@code name} has a known zipped bundle extension. 500 * 501 * @param name Name of the bundle. 502 * 503 * @return Whether or not {@code name} has zipped bundle suffix. 504 */ 505 public static boolean isZippedBundle(final String name) { 506 return IOUtil.hasSuffix(name, Constants.FILTER_MCVZ.getPreferredSuffix()) 507 || IOUtil.hasSuffix(name, IdvConstants.FILTER_ZIDV.getPreferredSuffix()); 508 } 509 510 /** 511 * Tests {@code name} to see if it has a known bundle extension. 512 * 513 * @param name Name of the bundle. 514 * 515 * @return Whether or not {@code name} has a bundle suffix. 516 */ 517 public static boolean isBundle(final String name) { 518 return isXmlBundle(name) || isZippedBundle(name); 519 } 520 521 /** 522 * Clears out the automatic display creation arguments by setting {@link #initParams} and {@link #initDisplays} to 523 * {@link Collections#emptyList()}. 524 */ 525 protected void clearAutomaticDisplayArgs() { 526 initParams = Collections.emptyList(); 527 initDisplays = Collections.emptyList(); 528 } 529}