Merge branch 'develop' into feature/JAL-4274_configurable_bitmap_export_preferences
authorBen Soares <b.soares@dundee.ac.uk>
Thu, 14 Sep 2023 13:16:17 +0000 (14:16 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Thu, 14 Sep 2023 13:16:17 +0000 (14:16 +0100)
27 files changed:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/bin/Cache.java
src/jalview/bin/Commands.java
src/jalview/bin/Jalview.java
src/jalview/bin/argparser/Arg.java
src/jalview/bin/argparser/ArgParser.java
src/jalview/bin/argparser/ArgValues.java
src/jalview/bin/argparser/ArgValuesMap.java
src/jalview/bin/argparser/BootstrapArgs.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AppJmol.java
src/jalview/gui/Desktop.java
src/jalview/gui/ImageExporter.java
src/jalview/gui/JvOptionPane.java
src/jalview/io/NewickFile.java
src/jalview/util/AWTConsole.java
src/jalview/util/FileUtils.java
src/jalview/util/ImageMaker.java
src/jalview/util/imagemaker/BitmapImageSizing.java
test/jalview/bin/CommandsTest.java
test/jalview/bin/HiDPISettingTest2.java
test/jalview/bin/argparser/ArgParserTest.java
test/jalview/util/FileUtilsTest.java
test/jalview/util/ImageMakerTest.java [new file with mode: 0644]
test/jalview/util/imagemaker/BitmapImageSizeTest.java

index 0c4f165..70eeb95 100644 (file)
@@ -1464,3 +1464,7 @@ action.cluster_matrix = Cluster matrix
 action.clustering_matrix_for = Calculating tree for matrix {0} and clustering at {1}
 action.cluster_matrix_tooltip = Computes an average distance tree for the matrix and displays it
 label.all_known_alignment_files = All known alignment files
+label.command_line_arguments = Command Line Arguments
+warning.using_old_command_line_arguments = It looks like you are using old command line arguments.  These are now deprecated and will be removed in a future release of Jalview.\nFind out about the new command line arguments at\n
+warning.using_mixed_command_line_arguments = Jalview cannot use both old (-arg) and new (--arg) command line arguments.  Please check your command line arguments.\ne.g. {0} and {1}
+warning.the_following_errors = The following errors and warnings occurred whilst processing files:
index 1f256cb..a0b9292 100644 (file)
@@ -1438,3 +1438,7 @@ label.nothing_selected = Nada seleccionado
 prompt.analytics_title = Jalview Estadísticas de Uso
 prompt.analytics = ¿Quiere ayudar a mejorar Jalview habilitando la recopilación de estadísticas de uso con análisis Plausible?\nPuede habilitar o deshabilitar el seguimiento de uso en las preferencias.
 label.all_known_alignment_files = Todos los archivos de alineación conocidos
+label.command_line_arguments = Argumentos de línea de comando
+warning.using_old_command_line_arguments = Parece que estás utilizando argumentos antiguos de línea de comando. Estos ahora están en desuso y se eliminarán en una versión futura de Jalview.\nObtenga más información sobre los nuevos argumentos de la línea de comando en\n
+warning.using_mixed_command_line_arguments = Jalview no puede utilizar argumentos de línea de comando antiguos (-arg) y nuevos (--arg). Verifique los argumentos de su línea de comando.\ne.g. {0} y {1}
+warning.the_following_errors = Se produjeron los siguientes errores y advertencias al procesar archivos:
index 038a5a0..9266d2a 100755 (executable)
@@ -141,7 +141,8 @@ import jalview.ws.sifts.SiftsSettings;
  * <li>WRAP_ALIGNMENT</li>
  * <li>EPS_RENDERING (Prompt each time|Lineart|Text) default for EPS rendering
  * style check</li>
- * <li>BITMAP_SCALE - scale factor for PNG export - default 0 - native resolution</li>
+ * <li>BITMAP_SCALE - scale factor for PNG export - default 0.0 - native
+ * resolution</li>
  * <li>BITMAP_HEIGHT - height bound for PNG export or 0 for unbound</li>
  * <li>BITMAP_WIDTH - width bound for PNG export or 0 for unbound</li>
  * <li>SORT_ALIGNMENT (No sort|Id|Pairwise Identity)</li>
@@ -641,8 +642,8 @@ public class Cache
         url = Cache.class.getResource(resourcePath).toString();
       } catch (Exception ex)
       {
-        jalview.bin.Console.errPrintln("Failed to resolve resource " + resourcePath
-                + ": " + ex.getMessage());
+        jalview.bin.Console.errPrintln("Failed to resolve resource "
+                + resourcePath + ": " + ex.getMessage());
       }
     }
     else
@@ -799,6 +800,25 @@ public class Cache
     return def;
   }
 
+  public static float getDefault(String property, float def)
+  {
+    String string = getProperty(property);
+    if (string != null)
+    {
+      try
+      {
+        def = Float.parseFloat(string);
+      } catch (NumberFormatException e)
+      {
+        if (!Jalview.quiet())
+          jalview.bin.Console.outPrintln("Error parsing float property '"
+                  + property + "' with value '" + string + "'");
+      }
+    }
+
+    return def;
+  }
+
   /**
    * Answers the value of the given property, or the supplied default value if
    * the property is not set
@@ -1064,8 +1084,9 @@ public class Cache
         return date_format.parse(val);
       } catch (Exception ex)
       {
-        jalview.bin.Console.errPrintln("Invalid or corrupt date in property '"
-                + propertyName + "' : value was '" + val + "'");
+        jalview.bin.Console
+                .errPrintln("Invalid or corrupt date in property '"
+                        + propertyName + "' : value was '" + val + "'");
       }
     }
     return null;
@@ -1088,8 +1109,8 @@ public class Cache
         return Integer.valueOf(val);
       } catch (NumberFormatException x)
       {
-        jalview.bin.Console.errPrintln("Invalid integer in property '" + property
-                + "' (value was '" + val + "')");
+        jalview.bin.Console.errPrintln("Invalid integer in property '"
+                + property + "' (value was '" + val + "')");
       }
     }
     return null;
@@ -1632,7 +1653,6 @@ public class Cache
   private static final Collection<String> bootstrapProperties = new ArrayList<>(
           Arrays.asList(JALVIEWLOGLEVEL, BOOTSTRAP_TEST));
 
-
   public static Properties bootstrapProperties(String filename)
   {
     Properties bootstrapProps = new Properties();
@@ -1661,8 +1681,9 @@ public class Cache
       return null;
     if (!file.exists())
     {
-      jalview.bin.Console.errPrintln("Could not load bootstrap preferences file '"
-              + filename + "'");
+      jalview.bin.Console
+              .errPrintln("Could not load bootstrap preferences file '"
+                      + filename + "'");
       return null;
     }
 
@@ -1678,8 +1699,9 @@ public class Cache
       }
     } catch (FileNotFoundException e)
     {
-      jalview.bin.Console.errPrintln("Could not find bootstrap preferences file '"
-              + file.getAbsolutePath() + "'");
+      jalview.bin.Console
+              .errPrintln("Could not find bootstrap preferences file '"
+                      + file.getAbsolutePath() + "'");
     } catch (IOException e)
     {
       jalview.bin.Console.errPrintln(
index 8164182..ba72831 100644 (file)
@@ -14,6 +14,7 @@ import java.util.Map;
 
 import jalview.analysis.AlignmentUtils;
 import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Jalview.ExitCode;
 import jalview.bin.argparser.Arg;
 import jalview.bin.argparser.ArgParser;
 import jalview.bin.argparser.ArgParser.Position;
@@ -72,6 +73,8 @@ public class Commands
 
   private boolean argsWereParsed = false;
 
+  private List<String> errors = new ArrayList<>();
+
   public Commands(ArgParser argparser, boolean headless)
   {
     this(Desktop.instance, argparser, headless);
@@ -138,9 +141,18 @@ public class Commands
       }
 
     }
+
+    // report errors
+    Console.warn(
+            "The following errors and warnings occurred whilst processing files:\n"
+                    + errorsToString());
+    // gui errors reported in Jalview
+
     if (argParser.getBoolean(Arg.QUIT))
     {
-      Jalview.getInstance().quit();
+      Jalview.getInstance().exit(
+              "Exiting due to " + Arg.QUIT.argString() + " argument.",
+              ExitCode.OK);
       return true;
     }
     // carry on with jalview.bin.Jalview
@@ -163,7 +175,11 @@ public class Commands
     boolean theseArgsWereParsed = false;
     ArgValuesMap avm = argParser.getLinkedArgs(id);
     if (avm == null)
+    {
       return true;
+    }
+
+    Boolean isError = Boolean.valueOf(false);
 
     // set wrap scope here so it can be applied after structures are opened
     boolean wrap = false;
@@ -216,7 +232,8 @@ public class Commands
           {
             if (!(new File(openFile)).exists())
             {
-              Console.warn("Can't find file '" + openFile + "'");
+              addError("Can't find file '" + openFile + "'");
+              isError = true;
               continue;
             }
           }
@@ -231,7 +248,8 @@ public class Commands
           format = new IdentifyFile().identify(openFile, protocol);
         } catch (FileFormatException e1)
         {
-          Console.error("Unknown file format for '" + openFile + "'");
+          addError("Unknown file format for '" + openFile + "'");
+          isError = true;
           continue;
         }
 
@@ -249,21 +267,25 @@ public class Commands
           Console.debug(
                   "Opening '" + openFile + "' in new alignment frame");
           FileLoader fileLoader = new FileLoader(!headless);
-          boolean xception=false;
-          try {
+          boolean xception = false;
+          try
+          {
             af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
                     format);
           } catch (Throwable thr)
           {
-            xception=true;
-            Console.error("Couldn't open '"+openFile+"' as "+format+" "+thr.getLocalizedMessage()+ " (Enable debug for full stack trace)");
-            Console.debug("Exception when opening '"+openFile+"'",thr);
-          }
-          finally
+            xception = true;
+            addError("Couldn't open '" + openFile + "' as " + format + " "
+                    + thr.getLocalizedMessage()
+                    + " (Enable debug for full stack trace)");
+            isError = true;
+            Console.debug("Exception when opening '" + openFile + "'", thr);
+          } finally
           {
-            if (af==null && !xception)
+            if (af == null && !xception)
             {
-              Console.info("Ignoring '"+openFile+"' - no alignment data found.");
+              addInfo("Ignoring '" + openFile
+                      + "' - no alignment data found.");
               continue;
             }
           }
@@ -279,8 +301,7 @@ public class Commands
 
             if (cs == null && !"None".equals(colour))
             {
-              Console.warn(
-                      "Couldn't parse '" + colour + "' as a colourscheme.");
+              addWarn("Couldn't parse '" + colour + "' as a colourscheme.");
             }
             else
             {
@@ -344,7 +365,8 @@ public class Commands
                       "examples/testdata/uniref50_test_tree", treefile);
             } catch (IOException e)
             {
-              Console.warn("Couldn't add tree " + treefile, e);
+              addError("Couldn't add tree " + treefile, e);
+              isError = true;
             }
           }
 
@@ -410,11 +432,12 @@ public class Commands
       {
         if (headless)
         {
-          Jalview.exit("Could not open any files in headless mode", 1);
+          Jalview.exit("Could not open any files in headless mode",
+                  ExitCode.NO_FILES);
         }
         else
         {
-          Console.warn("No more files to open");
+          Console.info("No more files to open");
         }
       }
       if (progressBarSet && desktop != null)
@@ -444,10 +467,8 @@ public class Commands
 
           if (seq == null)
           {
-            Console.warn("Could not find sequence for argument "
+            addWarn("Could not find sequence for argument "
                     + Arg.STRUCTURE.argString() + "=" + val);
-            // you probably want to continue here, not break
-            // break;
             continue;
           }
           File structureFile = null;
@@ -480,14 +501,14 @@ public class Commands
 
           if (structureFile == null)
           {
-            Console.warn("Not provided structure file with '" + val + "'");
+            addWarn("Not provided structure file with '" + val + "'");
             continue;
           }
 
           if (!structureFile.exists())
           {
-            Console.warn("Structure file '"
-                    + structureFile.getAbsoluteFile() + "' not found.");
+            addWarn("Structure file '" + structureFile.getAbsoluteFile()
+                    + "' not found.");
             continue;
           }
 
@@ -519,7 +540,7 @@ public class Commands
             } catch (IOException e)
             {
               paeFilepath = paeFile.getAbsolutePath();
-              Console.warn("Problem with the PAE file path: '"
+              addWarn("Problem with the PAE file path: '"
                       + paeFile.getPath() + "'");
             }
           }
@@ -562,7 +583,7 @@ public class Commands
                 if (it.hasNext())
                   sb.append(", ");
               }
-              Console.warn(sb.toString());
+              addWarn(sb.toString());
             }
           }
 
@@ -579,7 +600,8 @@ public class Commands
 
           if (sv == null)
           {
-            Console.error("Failed to import and open structure view.");
+            addError("Failed to import and open structure view for file '"
+                    + structureFile + "'.");
             continue;
           }
           try
@@ -597,13 +619,15 @@ public class Commands
             }
             if (tries == 0 && sv.isBusy())
             {
-              Console.warn(
-                      "Gave up waiting for structure viewer to load. Something may have gone wrong.");
+              addWarn("Gave up waiting for structure viewer to load file '"
+                      + structureFile
+                      + "'. Something may have gone wrong.");
             }
           } catch (Exception x)
           {
-            Console.warn("Exception whilst waiting for structure viewer "
+            addError("Exception whilst waiting for structure viewer "
                     + structureFilepath, x);
+            isError = true;
           }
 
           // add StructureViewer to svMap list
@@ -652,7 +676,7 @@ public class Commands
                       typeS.toUpperCase(Locale.ROOT));
             } catch (IllegalArgumentException e)
             {
-              Console.warn("Do not know image format '" + typeS
+              addWarn("Do not know image format '" + typeS
                       + "', using PNG");
               imageType = TYPE.PNG;
             }
@@ -669,6 +693,14 @@ public class Commands
                 AppJmol jmol = (AppJmol) sview;
                 try
                 {
+                  boolean success = this.checksBeforeWritingToFile(avm,
+                          subVals, false, structureImageFilename,
+                          "structure image", isError);
+                  if (!success)
+                  {
+                    continue;
+                  }
+
                   Console.debug("Rendering image to " + structureImageFile);
                   jmol.makePDBImage(structureImageFile, imageType, renderer,
                           userBis);
@@ -677,16 +709,18 @@ public class Commands
 
                 } catch (ImageOutputException ioexc)
                 {
-                  Console.warn("Unexpected error whilst exporting image to "
+                  addError("Unexpected error whilst exporting image to "
                           + structureImageFile, ioexc);
+                  isError = true;
+                  continue;
                 }
 
               }
               break;
             default:
-              Console.warn("Cannot export image for structure viewer "
+              addWarn("Cannot export image for structure viewer "
                       + sv.getViewerType() + " yet");
-              break;
+              continue;
             }
           }
         }
@@ -719,7 +753,7 @@ public class Commands
     }
     */
 
-    return theseArgsWereParsed;
+    return theseArgsWereParsed && !isError;
   }
 
   protected void processGroovyScript(String id)
@@ -729,7 +763,7 @@ public class Commands
 
     if (af == null)
     {
-      Console.warn("Did not have an alignment window for id=" + id);
+      addWarn("Did not have an alignment window for id=" + id);
       return;
     }
 
@@ -753,10 +787,11 @@ public class Commands
 
     if (af == null)
     {
-      Console.warn("Did not have an alignment window for id=" + id);
+      addWarn("Did not have an alignment window for id=" + id);
       return false;
     }
 
+    Boolean isError = Boolean.valueOf(false);
     if (avm.containsArg(Arg.IMAGE))
     {
       for (ArgValue av : avm.getArgValueList(Arg.IMAGE))
@@ -797,6 +832,14 @@ public class Commands
         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
 
         Console.info("Writing " + file);
+
+        boolean success = checksBeforeWritingToFile(avm, subVal, false,
+                fileName, "image", isError);
+        if (!success)
+        {
+          continue;
+        }
+
         try
         {
           switch (type)
@@ -844,17 +887,19 @@ public class Commands
             break;
 
           default:
-            Console.warn(Arg.IMAGE.argString() + " type '" + type
+            addWarn(Arg.IMAGE.argString() + " type '" + type
                     + "' not known. Ignoring");
             break;
           }
         } catch (Exception ioex)
         {
-          Console.warn("Unexpected error during export", ioex);
+          addError("Unexpected error during export to '" + fileName + "'",
+                  ioex);
+          isError = true;
         }
       }
     }
-    return true;
+    return !isError;
   }
 
   protected boolean processOutput(String id)
@@ -864,10 +909,12 @@ public class Commands
 
     if (af == null)
     {
-      Console.warn("Did not have an alignment window for id=" + id);
+      addWarn("Did not have an alignment window for id=" + id);
       return false;
     }
 
+    Boolean isError = Boolean.valueOf(false);
+
     if (avm.containsArg(Arg.OUTPUT))
     {
       for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
@@ -877,24 +924,6 @@ public class Commands
         String fileName = subVals.getContent();
         boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
         File file = new File(fileName);
-        boolean overwrite = ArgParser.getFromSubValArgOrPref(avm,
-                Arg.OVERWRITE, subVals, null, "OVERWRITE_OUTPUT", false);
-        // backups. Use the Arg.BACKUPS or subval "backups" setting first,
-        // otherwise if headless assume false, if not headless use the user
-        // preference with default true.
-        boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
-                subVals, null,
-                Platform.isHeadless() ? null : BackupFiles.ENABLED,
-                !Platform.isHeadless());
-
-        // if backups is not true then --overwrite must be specified
-        if (file.exists() && !(overwrite || backups || stdout))
-        {
-          Console.error("Won't overwrite file '" + fileName + "' without "
-                  + Arg.OVERWRITE.argString() + " or "
-                  + Arg.BACKUPS.argString() + " set");
-          return false;
-        }
 
         String name = af.getName();
         String format = ArgParser.getValueFromSubValOrArg(avm, av,
@@ -944,41 +973,44 @@ public class Commands
               validSB.append(")");
             }
 
-            Jalview.exit("No valid format specified for "
+            addError("No valid format specified for "
                     + Arg.OUTPUT.argString() + ". Valid formats are "
-                    + validSB.toString() + ".", 1);
-            // this return really shouldn't happen
-            return false;
+                    + validSB.toString() + ".");
+            continue;
           }
         }
 
-        String savedBackupsPreference = Cache
-                .getDefault(BackupFiles.ENABLED, null);
-        Console.debug("Setting backups to " + backups);
-        Cache.applicationProperties.put(BackupFiles.ENABLED,
-                Boolean.toString(backups));
+        boolean success = checksBeforeWritingToFile(avm, subVals, true,
+                fileName, ff.getName(), isError);
+        if (!success)
+        {
+          continue;
+        }
+
+        boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
+                subVals, null,
+                Platform.isHeadless() ? null : BackupFiles.ENABLED,
+                !Platform.isHeadless());
 
         Console.info("Writing " + fileName);
 
-        af.saveAlignment(fileName, ff, stdout);
-        Console.debug("Returning backups to " + savedBackupsPreference);
-        if (savedBackupsPreference != null)
-          Cache.applicationProperties.put(BackupFiles.ENABLED,
-                  savedBackupsPreference);
+        af.saveAlignment(fileName, ff, stdout, backups);
         if (af.isSaveAlignmentSuccessful())
         {
           Console.debug("Written alignment '" + name + "' in "
-                  + ff.getName() + " format to " + file);
+                  + ff.getName() + " format to '" + file + "'");
         }
         else
         {
-          Console.warn("Error writing file " + file + " in " + ff.getName()
+          addError("Error writing file '" + file + "' in " + ff.getName()
                   + " format!");
+          isError = true;
+          continue;
         }
 
       }
     }
-    return true;
+    return !isError;
   }
 
   private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
@@ -1044,4 +1076,91 @@ public class Commands
     }
     return svs;
   }
+
+  private void addInfo(String errorMessage)
+  {
+    Console.info(errorMessage);
+    errors.add(errorMessage);
+  }
+
+  private void addWarn(String errorMessage)
+  {
+    Console.warn(errorMessage);
+    errors.add(errorMessage);
+  }
+
+  private void addError(String errorMessage)
+  {
+    addError(errorMessage, null);
+  }
+
+  private void addError(String errorMessage, Exception e)
+  {
+    Console.error(errorMessage, e);
+    errors.add(errorMessage);
+  }
+
+  private boolean checksBeforeWritingToFile(ArgValuesMap avm,
+          SubVals subVal, boolean includeBackups, String filename,
+          String adjective, Boolean isError)
+  {
+    File file = new File(filename);
+
+    boolean overwrite = ArgParser.getFromSubValArgOrPref(avm, Arg.OVERWRITE,
+            subVal, null, "OVERWRITE_OUTPUT", false);
+    boolean stdout = false;
+    boolean backups = false;
+    if (includeBackups)
+    {
+      stdout = ArgParser.STDOUTFILENAME.equals(filename);
+      // backups. Use the Arg.BACKUPS or subval "backups" setting first,
+      // otherwise if headless assume false, if not headless use the user
+      // preference with default true.
+      backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS, subVal,
+              null, Platform.isHeadless() ? null : BackupFiles.ENABLED,
+              !Platform.isHeadless());
+    }
+
+    if (file.exists() && !(overwrite || backups || stdout))
+    {
+      addWarn("Won't overwrite file '" + filename + "' without "
+              + Arg.OVERWRITE.argString()
+              + (includeBackups ? " or " + Arg.BACKUPS.argString() : "")
+              + " set");
+      return false;
+    }
+
+    boolean mkdirs = ArgParser.getFromSubValArgOrPref(avm, Arg.MKDIRS,
+            subVal, null, "MKDIRS_OUTPUT", false);
+
+    if (!FileUtils.checkParentDir(file, mkdirs))
+    {
+      addError("Directory '"
+              + FileUtils.getParentDir(file).getAbsolutePath()
+              + "' does not exist for " + adjective + " file '" + filename
+              + "'."
+              + (mkdirs ? "" : "  Try using " + Arg.MKDIRS.argString()));
+      isError = true;
+      return false;
+    }
+
+    return true;
+  }
+
+  public List<String> getErrors()
+  {
+    return errors;
+  }
+
+  public String errorsToString()
+  {
+    StringBuilder sb = new StringBuilder();
+    for (String error : errors)
+    {
+      if (sb.length() > 0)
+        sb.append("\n");
+      sb.append("- " + error);
+    }
+    return sb.toString();
+  }
 }
index 8e576c3..b7d15e5 100755 (executable)
@@ -76,6 +76,7 @@ import jalview.bin.argparser.BootstrapArgs;
 import jalview.ext.so.SequenceOntology;
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
+import jalview.gui.JvOptionPane;
 import jalview.gui.PromptUserConfig;
 import jalview.gui.QuitHandler;
 import jalview.gui.QuitHandler.QResponse;
@@ -426,7 +427,7 @@ public class Jalview
     // stop now if only after --version
     if (bootstrapArgs.contains(Arg.VERSION))
     {
-      Jalview.exit(null, 0);
+      Jalview.exit(null, ExitCode.OK);
     }
 
     // old ArgsParser
@@ -458,7 +459,7 @@ public class Jalview
       error.printStackTrace();
       String message = "\nEssential logging libraries not found."
               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
-      Jalview.exit(message, 0);
+      Jalview.exit(message, ExitCode.OK);
     }
 
     // register SIGTERM listener
@@ -545,7 +546,7 @@ public class Jalview
                 .getList(Arg.HELP);
         Console.outPrintln(Arg.usage(helpArgs.stream().map(e -> e.getKey())
                 .collect(Collectors.toList())));
-        Jalview.exit(null, 0);
+        Jalview.exit(null, ExitCode.OK);
       }
       if (aparser.contains("help") || aparser.contains("h"))
       {
@@ -554,7 +555,7 @@ public class Jalview
         showUsage();
         */
         Console.outPrintln(Arg.usage());
-        Jalview.exit(null, 0);
+        Jalview.exit(null, ExitCode.OK);
       }
 
       // new CLI
@@ -648,7 +649,7 @@ public class Jalview
       error.printStackTrace();
       String message = "\nEssential logging libraries not found."
               + "\nUse: java -classpath \"$PATH_TO_LIB$/*:$PATH_TO_CLASSES$\" jalview.bin.Jalview";
-      Jalview.exit(message, 0);
+      Jalview.exit(message, ExitCode.OK);
     }
     desktop = null;
 
@@ -673,6 +674,8 @@ public class Jalview
       desktop = new Desktop();
       desktop.setInBatchMode(true); // indicate we are starting up
 
+      mixedCliWarning();
+
       try
       {
         JalviewTaskbar.setTaskbar(this);
@@ -809,6 +812,30 @@ public class Jalview
         }
       }
     }
+    else
+    {
+
+      if (getArgParser().isMixedStyle())
+      {
+        String warning = MessageManager.formatMessage(
+                "warning.using_mixed_command_line_arguments",
+                getArgParser().getMixedExamples());
+        Console.warn(warning);
+        Jalview.exit(
+                "Exiting due to mixed old and new command line arguments",
+                ExitCode.INVALID_ARGUMENT);
+      }
+      if (getArgParser().isOldStyle())
+      {
+        String warning = MessageManager
+                .getString("warning.using_old_command_line_arguments")
+                .replace("\n", " ")
+                + "https://www.jalview.org/help/html/features/commandline.html";
+        Console.warn(warning);
+      }
+
+    }
+
     // Run Commands from cli
     cmds = new Commands(argparser, headlessArg);
     cmds.processArgs();
@@ -827,7 +854,7 @@ public class Jalview
         else
         {
           Jalview.exit("Successfully completed commands in headless mode",
-                  0);
+                  ExitCode.OK);
         }
       }
       Console.info("Successfully completed commands");
@@ -836,7 +863,8 @@ public class Jalview
     {
       if (headlessArg)
       {
-        Jalview.exit("Error when running Commands in headless mode", 1);
+        Jalview.exit("Error when running Commands in headless mode",
+                ExitCode.ERROR_RUNNING_COMMANDS);
       }
       Console.warn("Error when running commands");
     }
@@ -867,7 +895,7 @@ public class Jalview
 
     if (file == null && desktop == null && !commandsSuccess)
     {
-      Jalview.exit("No files to open!", 1);
+      Jalview.exit("No files to open!", ExitCode.NO_FILES);
     }
 
     long progress = -1;
@@ -897,7 +925,8 @@ public class Jalview
             if (headless)
             {
               Jalview.exit(
-                      "Can't find file '" + file + "' in headless mode", 1);
+                      "Can't find file '" + file + "' in headless mode",
+                      ExitCode.FILE_NOT_FOUND);
             }
             Console.warn("Can't find file'" + file + "'");
           }
@@ -1221,6 +1250,8 @@ public class Jalview
       }
       desktop.setInBatchMode(false);
     }
+
+    cliWarning();
   }
 
   private static void setLookAndFeel()
@@ -1769,7 +1800,7 @@ public class Jalview
   public void quit()
   {
     // System.exit will run the shutdownHook first
-    Jalview.exit("Quitting now. Bye!", 0);
+    Jalview.exit("Quitting now. Bye!", ExitCode.OK);
   }
 
   public static AlignFrame getCurrentAlignFrame()
@@ -1787,8 +1818,9 @@ public class Jalview
     return cmds;
   }
 
-  public static void exit(String message, int exitcode)
+  public static void exit(String message, ExitCode ec)
   {
+    int exitcode = ec == ExitCode.OK ? 0 : ec.ordinal() + 1;
     if (Console.log == null)
     {
       // Don't start the logger just to exit!
@@ -1825,6 +1857,14 @@ public class Jalview
     }
   }
 
+  public enum ExitCode
+  {
+    // only add new ones to the end of the list (to preserve ordinal values)
+    OK, FILE_NOT_FOUND, FILE_NOT_READABLE, NO_FILES, INVALID_FORMAT,
+    INVALID_ARGUMENT, INVALID_VALUE, MIXED_CLI_ARGUMENTS,
+    ERROR_RUNNING_COMMANDS;
+  }
+
   /******************************
    * 
    * TEST OUTPUT METHODS
@@ -1844,7 +1884,7 @@ public class Jalview
    * @param a
    *          - Arg currently being processed
    * @param s1
-   *          - expected 
+   *          - expected
    * @param s2
    */
   protected static void testoutput(ArgParser ap, Arg a, String s1,
@@ -1900,7 +1940,8 @@ public class Jalview
   }
 
   /**
-   * conditionally (on @param yes) report that expected value s1 was set during CommandsTest tests
+   * conditionally (on @param yes) report that expected value s1 was set during
+   * CommandsTest tests
    */
   private static void testoutput(boolean yes, Arg a, String s1, String s2)
   {
@@ -1980,7 +2021,75 @@ public class Jalview
 
   public static boolean isBatchMode()
   {
-    return getInstance()!=null && (getInstance().desktop == null || getInstance().desktop.isInBatchMode());
+    return getInstance() != null && (getInstance().desktop == null
+            || getInstance().desktop.isInBatchMode());
+  }
+
+  /**
+   * Warning about old or mixed command line arguments
+   */
+  private void mixedCliWarning()
+  {
+    Jalview j = Jalview.getInstance();
+    boolean mixedStyle = j.getArgParser() != null
+            && j.getArgParser().isMixedStyle();
+    String title = MessageManager.getString("label.command_line_arguments");
+    if (mixedStyle)
+    {
+      String warning = MessageManager.formatMessage(
+              "warning.using_mixed_command_line_arguments",
+              j.getArgParser().getMixedExamples());
+      String quit = MessageManager.getString("action.quit");
+
+      Desktop.instance.nonBlockingDialog(title, warning, quit,
+              JvOptionPane.WARNING_MESSAGE, false, true);
+
+      Jalview.exit(
+              "Exiting due to mixed old and new command line arguments.",
+              ExitCode.MIXED_CLI_ARGUMENTS);
+    }
+  }
+
+  private void cliWarning()
+  {
+    Jalview j = Jalview.getInstance();
+    Commands c = j.getCommands();
+    boolean oldStyle = j.getArgParser() != null
+            && j.getArgParser().isOldStyle();
+    String title = MessageManager.getString("label.command_line_arguments");
+    if (oldStyle)
+    {
+      String warning = MessageManager
+              .getString("warning.using_old_command_line_arguments");
+      String url = "<a href=\"https://www.jalview.org/help/html/features/commandline.html\">https://www.jalview.org/help/html/features/commandline.html</a>";
+      if (Desktop.instance != null)
+      {
+        String cont = MessageManager.getString("label.continue");
+
+        Desktop.instance.nonBlockingDialog(32, 2, title, warning, url, cont,
+                JvOptionPane.WARNING_MESSAGE, false, true, true);
+      }
+    }
+    if (j.getCommands() != null && j.getCommands().getErrors().size() > 0)
+    {
+      if (Desktop.instance != null)
+      {
+        String message = MessageManager
+                .getString("warning.the_following_errors");
+        String ok = MessageManager.getString("action.ok");
+        int shortest = 60;
+        List<String> errors = j.getCommands().getErrors();
+        for (int i = 0; i < errors.size(); i++)
+        {
+          shortest = Math.min(shortest, errors.get(i).length());
+        }
+        Desktop.instance.nonBlockingDialog(
+                Math.max(message.length(), Math.min(60, shortest)),
+                Math.min(errors.size(), 20), title, message,
+                j.getCommands().errorsToString(), ok,
+                JvOptionPane.WARNING_MESSAGE, true, false, true);
+      }
+    }
   }
 
 }
index 7882d7b..9dd5c93 100644 (file)
@@ -19,7 +19,7 @@ public enum Arg
 
   // Initialising arguments (BOOTSTRAP)
   HELP(Type.HELP, "h", "Display basic help", Opt.UNARY, Opt.BOOTSTRAP,
-          Opt.HASTYPE, Opt.MULTI),
+          Opt.HASTYPE, Opt.MULTIVALUE),
   /*
    * Other --help-type Args will be added by the static block.
    */
@@ -62,27 +62,28 @@ public enum Arg
           Opt.BOOTSTRAP, Opt.SECRET),
   QUIET(Type.CONFIG, "q",
           "Stop all output to STDOUT (after the Java Virtual Machine has started). Use ‑‑quiet a second time to stop all output to STDERR.",
-          Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP),
+          Opt.UNARY, Opt.MULTIVALUE, Opt.BOOTSTRAP),
   INITSUBSTITUTIONS(Type.CONFIG,
           "Set ‑‑substitutions to be initially enabled (or initially disabled).",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION, Opt.SECRET),
   P(Type.CONFIG, "Set a Jalview preference value for this session.",
           Opt.PREFIXKEV, Opt.PRESERVECASE, Opt.STRING, Opt.BOOTSTRAP,
-          Opt.MULTI, Opt.NOACTION, Opt.SECRET), // keep this secret for now.
+          Opt.MULTIVALUE, Opt.NOACTION, Opt.SECRET), // keep this secret for
+                                                     // now.
 
   // Opening an alignment
   OPEN(Type.OPENING,
           "Opens one or more alignment files or URLs in new alignment windows.",
-          Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, Opt.MULTI,
-          Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.STORED,
-          Opt.PRIMARY),
+          Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER,
+          Opt.MULTIVALUE, Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT,
+          Opt.STORED, Opt.PRIMARY),
   APPEND(Type.OPENING,
           "Appends one or more alignment files or URLs to the open alignment window (or opens a new alignment if none already open).",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.GLOB,
           Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.PRIMARY),
   TITLE(Type.OPENING,
           "Specifies the title for the open alignment window as string.",
-          Opt.STRING, Opt.LINKED),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   COLOUR(Type.OPENING, "color", // being a bit soft on the Americans!
           "Applies the colour scheme to the open alignment window. Valid values include:\n"
                   + "clustal,\n" + "blosum62,\n" + "pc-identity,\n"
@@ -97,98 +98,104 @@ public enum Arg
                   + "Names of user defined colourschemes will also work,\n"
                   + "and jalview colourscheme specifications like\n"
                   + "--colour=\"D,E=red; K,R,H=0022FF; C,c=yellow\"",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   FEATURES(Type.OPENING, "Add a feature file or URL to the open alignment.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWMULTIID),
   TREE(Type.OPENING, "Add a tree file or URL to the open alignment.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWMULTIID),
   SORTBYTREE(Type.OPENING,
           "Enforces sorting (or not sorting) the open alignment in the order of an attached phylogenetic tree.",
-          true, Opt.LINKED, Opt.BOOLEAN, Opt.ALLOWALL),
+          true, Opt.LINKED, Opt.BOOLEAN, Opt.ALLOWMULTIID),
   ANNOTATIONS(Type.OPENING,
           "Add an annotations file or URL to the open alignment.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWMULTIID),
   SHOWANNOTATIONS(Type.OPENING,
           "Enforces showing (or not showing) alignment annotations.",
-          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWMULTIID, Opt.ALLOWMULTIID),
   WRAP(Type.OPENING,
           "Enforces wrapped (or not wrapped) alignment formatting.",
-          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWMULTIID, Opt.ALLOWMULTIID),
   NOSTRUCTURE(Type.OPENING,
           "Do not open or process any 3D structure in the ‑‑open or ‑‑append files.",
-          Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID, Opt.ALLOWMULTIID),
 
   // Adding a 3D structure
   STRUCTURE(Type.STRUCTURE,
           "Load a structure file or URL associated with a sequence in the open alignment.\n"
                   + "The sequence to be associated with can be specified with a following --seqid argument, or the subval modifier seqid=ID can be used. A subval INDEX can also be used to specify the INDEX-th sequence in the open alignment.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
-          Opt.PRIMARY),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.PRIMARY, Opt.ALLOWMULTIID),
   SEQID(Type.STRUCTURE,
           "Specify the sequence name for the preceding --structure to be associated with.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWMULTIID),
   PAEMATRIX(Type.STRUCTURE,
           "Add a PAE json matrix file to the preceding --structure.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWMULTIID),
   TEMPFAC(Type.STRUCTURE,
           "Set the type of temperature factor. Possible values are:\n"
                   + "default,\n" + "plddt.",
-          Opt.STRING, Opt.LINKED),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   STRUCTUREVIEWER(Type.STRUCTURE,
           "Set the structure viewer to use to open the 3D structure file specified in previous --structure to name. Possible values of name are:\n"
                   + "none,\n" + "jmol,\n" + "chimera,\n" + "chimerax,\n"
                   + "pymol.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWMULTIID),
   NOTEMPFAC(Type.STRUCTURE,
           "Do not show the temperature factor annotation for the preceding --structure.",
-          Opt.UNARY, Opt.LINKED, Opt.ALLOWALL, Opt.SECRET), // keep this secret
-                                                            // until it works!
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID, Opt.SECRET), // keep this
+                                                                // secret
+  // until it works!
   SHOWSSANNOTATIONS(Type.STRUCTURE, null, Opt.BOOLEAN, Opt.LINKED,
-          Opt.ALLOWALL),
+          Opt.ALLOWMULTIID),
 
   // Outputting files
   IMAGE(Type.IMAGE,
           "Output an image of the open alignment window. Format is specified by the subval modifier, a following --type argument or guessed from the file extension. Valid formats/extensions are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
+          Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWMULTIID,
           Opt.REQUIREINPUT, Opt.OUTPUTFILE, Opt.PRIMARY),
   TYPE(Type.IMAGE,
           "Set the image format for the preceding --image. Valid values are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   TEXTRENDERER(Type.IMAGE,
           "Sets whether text in a vector image format (SVG, HTML, EPS) should be rendered as text or vector line-art. Possible values are:\n"
                   + "text,\n" + "lineart.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   SCALE(Type.IMAGE,
           "Sets a scaling for bitmap image format (PNG). Should be given as a floating point number. If used in conjunction with --width and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   WIDTH(Type.IMAGE,
           "Sets a width for bitmap image format (PNG) with the height maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   HEIGHT(Type.IMAGE,
           "Sets a height for bitmap image format (PNG) with the width maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --width then the smallest scaling will be used (scale, width and height provide bounds for the image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   STRUCTUREIMAGE(Type.STRUCTUREIMAGE,
           "Export an image of a 3D structure opened in JMOL", Opt.STRING,
-          Opt.LINKED, Opt.MULTI, Opt.OUTPUTFILE),
+          Opt.LINKED, Opt.MULTIVALUE, Opt.OUTPUTFILE, Opt.ALLOWMULTIID),
   STRUCTUREIMAGETYPE(Type.STRUCTUREIMAGE,
           "Set the structure image format for the preceding --structureimage. Valid values are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   STRUCTUREIMAGETEXTRENDERER(Type.STRUCTUREIMAGE,
           "Sets whether text in a vector structure image format (SVG, EPS) should be rendered as text or vector line-art. Possible values are:\n"
                   + "text,\n" + "lineart.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   STRUCTUREIMAGESCALE(Type.STRUCTUREIMAGE,
           "Sets a scaling for bitmap structure image format (PNG). Should be given as a floating point number. If used in conjunction with --structureimagewidth and --structureimageheight then the smallest scaling will be used (structureimagescale, structureimagewidth and structureimageheight provide bounds for the structure image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   STRUCTUREIMAGEWIDTH(Type.STRUCTUREIMAGE,
           "Sets a width for bitmap structure image format (PNG) with the height maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --structureimagescale and --structureimageheight then the smallest scaling will be used (structureimagescale, structureimagewidth and structureimageheight provide bounds for the structure image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   STRUCTUREIMAGEHEIGHT(Type.STRUCTUREIMAGE,
           "Sets a height for bitmap structure image format (PNG) with the width maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --structureimagescale and --structureimagewidth then the smallest scaling will be used (structureimagescale, structureimagewidth and structureimageheight provide bounds for the structure image).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
 
   OUTPUT(Type.OUTPUT,
           "Export the open alignment to file filename. The format name is specified by the subval modifier format=name, a following --format name argument or guessed from the file extension. Valid format names (and file extensions) are:\n"
@@ -198,32 +205,36 @@ public enum Arg
                   + "pileup (pileup),\n" + "msf (msf),\n"
                   + "clustal (aln),\n" + "phylip (phy),\n"
                   + "jalview (jvp, jar).",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
+          Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWMULTIID,
           Opt.REQUIREINPUT, Opt.OUTPUTFILE, Opt.STDOUT, Opt.PRIMARY),
   FORMAT(Type.OUTPUT,
           "Sets the format for the preceding --output file. Valid formats are:\n"
                   + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n"
                   + "blc,\n" + "amsa,\n" + "json,\n" + "pileup,\n"
                   + "msf,\n" + "clustal,\n" + "phylip,\n" + "jalview.",
-          Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.ALLOWMULTIID),
   GROOVY(Type.PROCESS,
           "Process a groovy script in the file for the open alignment.",
-          Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
-          Opt.ALLOWALL),
+          Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWSUBSTITUTIONS,
+          Opt.ALLOWMULTIID),
   BACKUPS(Type.OUTPUT,
           "Enable (or disable) writing backup files when saving an ‑‑output file. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
-          true, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+          true, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWMULTIID),
   OVERWRITE(Type.OUTPUT,
           "Enable (or disable) overwriting of output files without backups enabled. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
-          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
+          Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWMULTIID),
   CLOSE(Type.OPENING,
           "Close the current open alignment window. This occurs after other output arguments. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
-          Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID),
+  MKDIRS(Type.OUTPUT,
+          "Automatically create directories when outputting a file to a new directory.",
+          Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID),
 
   // controlling flow of arguments
   NEW(Type.FLOW,
           "Move on to a new alignment window. This will ensure --append will start a new alignment window and other linked arguments will apply to the new alignment window.",
-          Opt.UNARY, Opt.MULTI, Opt.NOACTION, Opt.INCREMENTDEFAULTCOUNTER),
+          Opt.UNARY, Opt.MULTIVALUE, Opt.NOACTION,
+          Opt.INCREMENTDEFAULTCOUNTER),
   SUBSTITUTIONS(Type.FLOW,
           "The following argument values allow (or don't allow) subsituting filename parts. This is initially true. Valid substitutions are:\n"
                   + "{basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n"
@@ -233,22 +244,22 @@ public enum Arg
                   + "{n} - the value of the index counter (starting at 0).\n"
                   + "{++n} - increase and substitute the value of the index counter,\n"
                   + "{} - the value of the current alignment window default index.",
-          true, Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+          true, Opt.BOOLEAN, Opt.MULTIVALUE, Opt.NOACTION),
   ARGFILE(Type.FLOW,
           "Open one or more files filename and read, line-by-line, as arguments to Jalview.\n"
                   + "Values in an argfile should be given with an equals sign (\"=\") separator with no spaces.\n"
                   + "Note that if you use one or more --argfile arguments then all other non-initialising arguments will be ignored.",
-          Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
+          Opt.STRING, Opt.MULTIVALUE, Opt.BOOTSTRAP, Opt.GLOB,
           Opt.ALLOWSUBSTITUTIONS),
   NPP(Type.FLOW, "n++",
           "Increase the index counter used in argument value substitutions.",
-          Opt.UNARY, Opt.MULTI, Opt.NOACTION),
+          Opt.UNARY, Opt.MULTIVALUE, Opt.NOACTION),
   ALL(Type.FLOW,
           "Apply the following output arguments to all sets of linked arguments.",
-          Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+          Opt.BOOLEAN, Opt.MULTIVALUE, Opt.NOACTION),
   OPENED(Type.FLOW,
           "Apply the following output arguments to all of the last --open'ed set of linked arguments.",
-          Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+          Opt.BOOLEAN, Opt.MULTIVALUE, Opt.NOACTION),
   QUIT(Type.FLOW,
           "After all files have been opened, appended and output, quit Jalview. In ‑‑headless mode this already happens.",
           Opt.UNARY),
@@ -257,26 +268,27 @@ public enum Arg
           Opt.UNARY, Opt.SECRET),
   ALLSTRUCTURES(Type.FLOW,
           "Apply the following 3D structure formatting arguments to all structures within the open alignment.",
-          Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
+          Opt.BOOLEAN, Opt.MULTIVALUE, Opt.NOACTION),
 
   // secret options
   TESTOUTPUT(Type.CONFIG,
           "Allow specific stdout information.  For testing purposes only.",
           Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user
   SETPROP(Type.CONFIG, "Set an individual Java System property.",
-          Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.SECRET), // not in use yet
+          Opt.STRING, Opt.MULTIVALUE, Opt.BOOTSTRAP, Opt.SECRET), // not in use
+                                                                  // yet
   NIL(Type.FLOW,
           "This argument does nothing on its own, but can be used with linkedIds.",
-          Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION, Opt.SECRET),
+          Opt.UNARY, Opt.LINKED, Opt.MULTIVALUE, Opt.NOACTION, Opt.SECRET),
 
   // private options (inserted during arg processing)
   SETARGFILE(Type.FLOW,
           "Sets the current value of the argfilename.  Inserted before argfilecontents.",
-          Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTI, Opt.PRIVATE,
+          Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTIVALUE, Opt.PRIVATE,
           Opt.NOACTION),
   UNSETARGFILE(Type.FLOW,
           "Unsets the current value of the argfilename.  Inserted after argfile contents.",
-          Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION),
+          Opt.UNARY, Opt.LINKED, Opt.MULTIVALUE, Opt.PRIVATE, Opt.NOACTION),
 
   // these last two have no purpose in the normal Jalview application but are
   // used by jalview.bin.Launcher to set memory settings. They are not used by
@@ -315,7 +327,7 @@ public enum Arg
      * A MULTI Arg can be specified multiple times.
      * Multiple values are stored in the ArgValuesMap (along with their positional index) for each linkedId.
      */
-    MULTI("can be specified multiple times"),
+    MULTIVALUE("can be specified multiple times"),
     /*
      * A Linked Arg can be linked to others through a --arg[linkedId] or --arg[linkedId]=value.
      * If no linkedId is specified then the current default linkedId will be used.
@@ -362,7 +374,7 @@ public enum Arg
     /*
      * An ALLOWALL Arg can use the '*' linkedId to apply to all known linkedIds
      */
-    ALLOWALL("can be used with " + ArgParser.DOUBLEDASH + "all"),
+    ALLOWMULTIID("can be used with " + ArgParser.DOUBLEDASH + "all"),
     /*
      * If an Arg has the INCREMENTDEFAULTCOUNTER option and the default linkedId is used,
      * the defaultLinkedIdCounter is incremented *first*.
index 3862375..7581dde 100644 (file)
@@ -37,6 +37,7 @@ import java.util.Map;
 import jalview.bin.Cache;
 import jalview.bin.Console;
 import jalview.bin.Jalview;
+import jalview.bin.Jalview.ExitCode;
 import jalview.bin.argparser.Arg.Opt;
 import jalview.bin.argparser.Arg.Type;
 import jalview.util.FileUtils;
@@ -150,12 +151,6 @@ public class ArgParser
   private boolean allLinkedIds = false;
 
   /**
-   * flag to say whether the default linkedId is the current default linked id
-   * or OPENED linkedIds
-   */
-  private boolean openedLinkedIds = false;
-
-  /**
    * flag to say whether the structure arguments should be applied to all
    * structures with this linked id
    */
@@ -177,6 +172,15 @@ public class ArgParser
 
   private BootstrapArgs bootstrapArgs = null;
 
+  private boolean oldArguments = false;
+
+  private boolean mixedArguments = false;
+
+  /**
+   * saved examples of mixed arguments
+   */
+  private String[] mixedExamples = new String[] { null, null };
+
   static
   {
     argMap = new HashMap<>();
@@ -241,19 +245,40 @@ public class ArgParser
       if (arg.startsWith(DOUBLEDASH))
       {
         dd = true;
-        break;
+        if (mixedExamples[1] == null)
+        {
+          mixedExamples[1] = arg;
+        }
       }
-      else if (arg.startsWith("-") || arg.equals("open"))
+      else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME))
+              || arg.equals("open"))
       {
         d = true;
+        if (mixedExamples[0] == null)
+        {
+          mixedExamples[0] = arg;
+        }
+      }
+    }
+    if (d)
+    {
+      if (dd)
+      {
+        mixedArguments = true;
+      }
+      else
+      {
+        oldArguments = true;
       }
     }
-    if (d && !dd)
+
+    if (oldArguments || mixedArguments)
     {
       // leave it to the old style -- parse an empty list
       parse(new ArrayList<String>(), false, false);
       return;
     }
+
     if (bsa != null)
       this.bootstrapArgs = bsa;
     else
@@ -379,10 +404,12 @@ public class ArgParser
         {
           // arg not found
           Console.error("Argument '" + arg + "' not recognised.  Exiting.");
-          Jalview.exit("Invalid argument used." + System.lineSeparator()
-                  + "Use" + System.lineSeparator() + "jalview "
-                  + Arg.HELP.argString() + System.lineSeparator()
-                  + "for a usage statement.", 13);
+          Jalview.exit(
+                  "Invalid argument used." + System.lineSeparator() + "Use"
+                          + System.lineSeparator() + "jalview "
+                          + Arg.HELP.argString() + System.lineSeparator()
+                          + "for a usage statement.",
+                  ExitCode.INVALID_ARGUMENT);
           continue;
         }
         if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
@@ -484,12 +511,6 @@ public class ArgParser
         else if (a == Arg.ALL)
         {
           allLinkedIds = !negated;
-          openedLinkedIds = false;
-        }
-        else if (a == Arg.OPENED)
-        {
-          openedLinkedIds = !negated;
-          allLinkedIds = false;
         }
         else if (a == Arg.ALLSTRUCTURES)
         {
@@ -516,35 +537,25 @@ public class ArgParser
         {
           if (linkedId == null)
           {
-            if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
-                    && val.startsWith(MATCHALLLINKEDIDS))
+            if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
+                    && val.contains(MATCHALLLINKEDIDS))
             {
-              // --output=*.ext is shorthand for --all --output {basename}.ext
+              // --output=*.ext is shorthand for --output {basename}.ext
+              // --output=*/*.ext is shorthand for
+              // --output {dirname}/{basename}.ext
               // (or --image=*.ext)
-              allLinkedIds = true;
-              openedLinkedIds = false;
-              linkedId = MATCHALLLINKEDIDS;
-              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
-                      + val.substring(MATCHALLLINKEDIDS.length());
+              linkedId = allLinkedIds ? MATCHALLLINKEDIDS
+                      : MATCHOPENEDLINKEDIDS;
+              val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
+                      LINKEDIDDIRNAME, LINKEDIDBASENAME);
             }
-            else if (a.hasOption(Opt.OUTPUTFILE)
-                    && a.hasOption(Opt.ALLOWALL)
-                    && val.startsWith(MATCHOPENEDLINKEDIDS))
-            {
-              // --output=open*.ext is shorthand for --opened --output
-              // {basename}.ext
-              // (or --image=open*.ext)
-              openedLinkedIds = true;
-              allLinkedIds = false;
-              linkedId = MATCHOPENEDLINKEDIDS;
-              val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
-                      + val.substring(MATCHOPENEDLINKEDIDS.length());
-            }
-            else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
+            else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
             {
               linkedId = MATCHALLLINKEDIDS;
             }
-            else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
+            else if (a.hasOption(Opt.ALLOWMULTIID)
+                    && this.storedLinkedIds != null
+                    && this.storedLinkedIds.size() > 0)
             {
               linkedId = MATCHOPENEDLINKEDIDS;
             }
@@ -668,9 +679,8 @@ public class ArgParser
         // remove the '*' or 'open*' linkedId that should be empty if it was
         // created
         if ((MATCHALLLINKEDIDS.equals(linkedId)
+                || MATCHOPENEDLINKEDIDS.equals(linkedId))
                 && linkedArgs.containsKey(linkedId))
-                || (MATCHOPENEDLINKEDIDS.equals(linkedId)
-                        && linkedArgs.containsKey(linkedId)))
         {
           linkedArgs.remove(linkedId);
         }
@@ -915,7 +925,7 @@ public class ArgParser
       {
         String message = Arg.ARGFILE.argString() + EQUALS + "\""
                 + argFile.getPath() + "\": File does not exist.";
-        Jalview.exit(message, 2);
+        Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
       }
       try
       {
@@ -929,7 +939,7 @@ public class ArgParser
       {
         String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
                 + "\": File could not be read.";
-        Jalview.exit(message, 3);
+        Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
       }
     }
     // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
@@ -955,7 +965,7 @@ public class ArgParser
         String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
                 + "\": File could not be read.";
         Console.debug(message, e);
-        Jalview.exit(message, 3);
+        Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
       }
     }
     return args;
@@ -1202,7 +1212,7 @@ public class ArgParser
     Arg a = avs.arg();
 
     List<String> wildcardLinkedIds = null;
-    if (a.hasOption(Opt.ALLOWALL))
+    if (a.hasOption(Opt.ALLOWMULTIID))
     {
       switch (linkedId)
       {
@@ -1233,7 +1243,9 @@ public class ArgParser
         // skip incorrectly stored wildcard ids!
         if (id == null || MATCHALLLINKEDIDS.equals(id)
                 || MATCHOPENEDLINKEDIDS.equals(id))
+        {
           continue;
+        }
         ArgValuesMap avm = linkedArgs.get(id);
         // don't set an output if there isn't an input
         if (a.hasOption(Opt.REQUIREINPUT)
@@ -1341,4 +1353,18 @@ public class ArgParser
     return linkedArgs.get(linkedId);
   }
 
+  public boolean isOldStyle()
+  {
+    return oldArguments;
+  }
+
+  public boolean isMixedStyle()
+  {
+    return mixedArguments;
+  }
+
+  public String[] getMixedExamples()
+  {
+    return mixedExamples;
+  }
 }
\ No newline at end of file
index f25fc9a..2b9061c 100644 (file)
@@ -151,9 +151,9 @@ public class ArgValues
   protected void addArgValue(ArgValue av, boolean beingSetByWildcard)
   {
     // allow a non-wildcard value to overwrite a wildcard set single value
-    boolean overwrite = !arg.hasOption(Opt.MULTI) && setByWildcard
+    boolean overwrite = !arg.hasOption(Opt.MULTIVALUE) && setByWildcard
             && !beingSetByWildcard;
-    if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
+    if ((!arg.hasOption(Opt.MULTIVALUE) && argValueList.size() > 0)
             && !overwrite)
       return;
     if (arg.hasOption(Opt.NODUPLICATEVALUES)
@@ -182,7 +182,7 @@ public class ArgValues
 
   protected ArgValue getArgValue()
   {
-    if (arg.hasOption(Opt.MULTI))
+    if (arg.hasOption(Opt.MULTIVALUE))
       Console.warn("Requesting single value for multi value argument");
     return argValueList.size() > 0 ? argValueList.get(0) : null;
   }
index ab6fcc1..4aa8570 100644 (file)
@@ -226,7 +226,7 @@ public class ArgValuesMap
    */
   public String getBasename()
   {
-    return getDirBasenameOrExtension(false, false);
+    return getDirBasenameOrExtension(false, false, false);
   }
 
   /*
@@ -235,7 +235,7 @@ public class ArgValuesMap
    */
   public String getExtension()
   {
-    return getDirBasenameOrExtension(false, true);
+    return getDirBasenameOrExtension(false, true, false);
   }
 
   /*
@@ -244,11 +244,11 @@ public class ArgValuesMap
    */
   public String getDirname()
   {
-    return getDirBasenameOrExtension(true, false);
+    return getDirBasenameOrExtension(true, false, false);
   }
 
   public String getDirBasenameOrExtension(boolean dirname,
-          boolean extension)
+          boolean extension, boolean absoluteDirname)
   {
     String filename = null;
     String appendVal = getValue(Arg.APPEND);
index ec62bcd..d32a5b2 100644 (file)
@@ -292,7 +292,7 @@ public class BootstrapArgs
   private void add(Arg a, Type t, String s)
   {
     List<Map.Entry<Type, String>> l = getOrCreateList(a);
-    if (a.hasOption(Opt.MULTI) || l.size() == 0)
+    if (a.hasOption(Opt.MULTIVALUE) || l.size() == 0)
     {
       l.add(entry(t, s));
     }
@@ -301,7 +301,7 @@ public class BootstrapArgs
   private void addAll(Arg a, Type t, List<String> al)
   {
     List<Map.Entry<Type, String>> l = getOrCreateList(a);
-    if (a.hasOption(Opt.MULTI))
+    if (a.hasOption(Opt.MULTIVALUE))
     {
       for (String s : al)
       {
index dba400e..f3cb012 100644 (file)
@@ -1252,10 +1252,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
    */
   public void saveAlignment(String file, FileFormatI format)
   {
-    saveAlignment(file, format, false);
+    saveAlignment(file, format, false, false);
   }
 
-  public void saveAlignment(String file, FileFormatI format, boolean stdout)
+  public void saveAlignment(String file, FileFormatI format, boolean stdout,
+          boolean forceBackup)
   {
     lastSaveSuccessful = true;
     if (!stdout)
@@ -1308,7 +1309,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       else
       {
         // create backupfiles object and get new temp filename destination
-        boolean doBackup = BackupFiles.getEnabled() && !stdout;
+        boolean doBackup = forceBackup
+                || (BackupFiles.getEnabled() && !stdout);
         BackupFiles backupfiles = null;
         if (doBackup)
         {
index ce87c01..7ed5227 100644 (file)
@@ -341,7 +341,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
       if (Jalview.isHeadlessMode())
       {
         AnnotationLabels aal = getAlabels();
-        int stringWidth = aal.drawLabels(null, false, idWidth, false, false, fm);
+        int stringWidth = aal.drawLabels(null, false, idWidth, false, false,
+                fm);
         idWidth = Math.max(idWidth, stringWidth);
       }
       else
@@ -1229,7 +1230,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
           throws ImageOutputException
   {
     makeAlignmentImage(type, file, renderer,
-            BitmapImageSizing.nullBitmapImageSizing());
+            BitmapImageSizing.defaultBitmapImageSizing());
   }
 
   /**
index 950f129..cfbac04 100644 (file)
@@ -28,16 +28,10 @@ import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.io.File;
-import java.lang.reflect.InvocationTargetException;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
 
 import javax.swing.JPanel;
 import javax.swing.JSplitPane;
@@ -371,8 +365,9 @@ public class AppJmol extends StructureViewerBase
       }
       if (waitTotal > waitMax)
       {
-        jalview.bin.Console.errPrintln("Timed out waiting for Jmol to load files after "
-                + waitTotal + "ms");
+        jalview.bin.Console.errPrintln(
+                "Timed out waiting for Jmol to load files after "
+                        + waitTotal + "ms");
         // jalview.bin.Console.errPrintln("finished: " + jmb.isFinishedInit()
         // + "; loaded: " + Arrays.toString(jmb.getPdbFile())
         // + "; files: " + files.toString());
@@ -434,11 +429,14 @@ public class AppJmol extends StructureViewerBase
   @Override
   public void makePDBImage(ImageMaker.TYPE type)
   {
-    try {
-    makePDBImage(null, type, null,
-            BitmapImageSizing.nullBitmapImageSizing());
-    } catch (ImageOutputException ioex) {
-      Console.error("Unexpected error whilst writing "+type.toString(),ioex);
+    try
+    {
+      makePDBImage(null, type, null,
+              BitmapImageSizing.defaultBitmapImageSizing());
+    } catch (ImageOutputException ioex)
+    {
+      Console.error("Unexpected error whilst writing " + type.toString(),
+              ioex);
     }
   }
 
@@ -450,9 +448,9 @@ public class AppJmol extends StructureViewerBase
 
     BitmapImageSizing bis = ImageMaker.getScaleWidthHeight(width, height,
             userBis);
-    float usescale = bis.scale;
-    int usewidth = bis.width;
-    int useheight = bis.height;
+    float usescale = bis.scale();
+    int usewidth = bis.width();
+    int useheight = bis.height();
 
     ImageWriterI writer = new ImageWriterI()
     {
@@ -481,29 +479,33 @@ public class AppJmol extends StructureViewerBase
             .toLowerCase(Locale.ROOT);
     final ImageExporter exporter = new ImageExporter(writer,
             getProgressIndicator(), type, getTitle());
-    
+
     final Throwable[] exceptions = new Throwable[1];
     exceptions[0] = null;
     final AppJmol us = this;
     try
     {
-      Thread runner = Executors.defaultThreadFactory().newThread(new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          try
-          {
-            exporter.doExport(file, us, width, height, view, renderer,
-                    userBis);
-          } catch (Throwable t)
-          {
-            exceptions[0] = t;
-          }
-        }
-      });
+      Thread runner = Executors.defaultThreadFactory()
+              .newThread(new Runnable()
+              {
+                @Override
+                public void run()
+                {
+                  try
+                  {
+                    exporter.doExport(file, us, width, height, view,
+                            renderer, userBis);
+                  } catch (Throwable t)
+                  {
+                    exceptions[0] = t;
+                  }
+                }
+              });
       runner.start();
-      do { Thread.sleep(25); } while (runner.isAlive());
+      do
+      {
+        Thread.sleep(25);
+      } while (runner.isAlive());
     } catch (Throwable e)
     {
       throw new ImageOutputException(
@@ -532,7 +534,8 @@ public class AppJmol extends StructureViewerBase
               .openURL("http://wiki.jmol.org");// http://jmol.sourceforge.net/docs/JmolUserGuide/");
     } catch (Exception ex)
     {
-      jalview.bin.Console.errPrintln("Show Jmol help failed with: " + ex.getMessage());
+      jalview.bin.Console
+              .errPrintln("Show Jmol help failed with: " + ex.getMessage());
     }
   }
 
index 035da25..e785431 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
@@ -87,10 +88,14 @@ import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
 import javax.swing.JLabel;
 import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
 import javax.swing.JTextField;
+import javax.swing.JTextPane;
 import javax.swing.KeyStroke;
 import javax.swing.SwingUtilities;
 import javax.swing.WindowConstants;
@@ -98,6 +103,7 @@ import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkEvent.EventType;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
+import javax.swing.text.JTextComponent;
 
 import org.stackoverflowusers.file.WindowsShortcut;
 
@@ -106,6 +112,7 @@ import jalview.api.AlignmentViewPanel;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
+import jalview.bin.Jalview.ExitCode;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
@@ -635,6 +642,7 @@ public class Desktop extends jalview.jbgui.GDesktop
       // used for jalviewjsTest
       jalview.bin.Console.info("JALVIEWJS: CREATED DESKTOP");
     }
+
   }
 
   /**
@@ -1081,23 +1089,23 @@ public class Desktop extends jalview.jbgui.GDesktop
 
     setKeyBindings(frame);
 
-    // Since the latest FlatLaf patch, we occasionally have problems showing structureViewer frames...
-    int tries=3;
-    boolean shown=false;
-    Exception last=null;
+    // Since the latest FlatLaf patch, we occasionally have problems showing
+    // structureViewer frames...
+    int tries = 3;
+    boolean shown = false;
+    Exception last = null;
     do
     {
       try
       {
         desktop.add(frame);
-        shown=true;
+        shown = true;
       } catch (IllegalArgumentException iaex)
       {
-        last=iaex;
+        last = iaex;
         tries--;
-        jalview.bin.Console.info(
-                "Squashed IllegalArgument Exception (" + tries + " left) for "+frame.getTitle(),
-                iaex);
+        jalview.bin.Console.info("Squashed IllegalArgument Exception ("
+                + tries + " left) for " + frame.getTitle(), iaex);
         try
         {
           Thread.sleep(5);
@@ -1109,7 +1117,9 @@ public class Desktop extends jalview.jbgui.GDesktop
     } while (!shown && tries > 0);
     if (!shown)
     {
-      jalview.bin.Console.error("Serious Problem whilst showing window "+frame.getTitle(),last);
+      jalview.bin.Console.error(
+              "Serious Problem whilst showing window " + frame.getTitle(),
+              last);
     }
 
     windowMenu.add(menuItem);
@@ -1522,7 +1532,7 @@ public class Desktop extends jalview.jbgui.GDesktop
     // this will run the shutdownHook but QuitHandler.getQuitResponse() should
     // not run a second time if gotQuitResponse flag has been set (i.e. user
     // confirmed quit of some kind).
-    Jalview.exit("Desktop exiting.", 0);
+    Jalview.exit("Desktop exiting.", ExitCode.OK);
   }
 
   private void storeLastKnownDimensions(String string, Rectangle jc)
@@ -1635,7 +1645,8 @@ public class Desktop extends jalview.jbgui.GDesktop
       }
     } catch (Exception ex)
     {
-      jalview.bin.Console.errPrintln("Error opening help: " + ex.getMessage());
+      jalview.bin.Console
+              .errPrintln("Error opening help: " + ex.getMessage());
     }
   }
 
@@ -1866,7 +1877,8 @@ public class Desktop extends jalview.jbgui.GDesktop
     boolean autoSave = projectFile != null && !saveAs
             && BackupFiles.getEnabled();
 
-    // jalview.bin.Console.outPrintln("autoSave="+autoSave+", projectFile='"+projectFile+"',
+    // jalview.bin.Console.outPrintln("autoSave="+autoSave+",
+    // projectFile='"+projectFile+"',
     // saveAs="+saveAs+", Backups
     // "+(BackupFiles.getEnabled()?"enabled":"disabled"));
 
@@ -3660,14 +3672,16 @@ public class Desktop extends jalview.jbgui.GDesktop
       Desktop us = Desktop.instance;
       Desktop.instance = null;
       // call dispose in a separate thread - try to avoid indirect deadlocks
-      new Thread(new Runnable() {
+      new Thread(new Runnable()
+      {
         @Override
         public void run()
         {
           ExecutorService dex = us.dialogExecutor;
-          if (dex!=null) {
+          if (dex != null)
+          {
             dex.shutdownNow();
-            us.dialogExecutor=null;
+            us.dialogExecutor = null;
             us.block.drainPermits();
           }
           us.dispose();
@@ -3730,4 +3744,80 @@ public class Desktop extends jalview.jbgui.GDesktop
     alignFrameModalMap.remove(af);
   }
 
+  public void nonBlockingDialog(String title, String message, String button,
+          int type, boolean scrollable, boolean modal)
+  {
+    nonBlockingDialog(32, 2, title, message, null, button, type, scrollable,
+            false, modal);
+  }
+
+  public void nonBlockingDialog(int width, int height, String title,
+          String message, String boxtext, String button, int type,
+          boolean scrollable, boolean html, boolean modal)
+  {
+    if (type < 0)
+    {
+      type = JvOptionPane.WARNING_MESSAGE;
+    }
+    JLabel jl = new JLabel(message);
+
+    JTextComponent jtc = null;
+    if (html)
+    {
+      JTextPane jtp = new JTextPane();
+      jtp.setContentType("text/html");
+      jtp.setEditable(false);
+      jtp.setAutoscrolls(true);
+      jtp.setText(boxtext);
+
+      jtc = jtp;
+    }
+    else
+    {
+      JTextArea jta = new JTextArea(height, width);
+      // jta.setLineWrap(true);
+      jta.setEditable(false);
+      jta.setWrapStyleWord(true);
+      jta.setAutoscrolls(true);
+      jta.setText(boxtext);
+
+      jtc = jta;
+    }
+
+    JScrollPane jsp = scrollable
+            ? new JScrollPane(jtc, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                    JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)
+            : null;
+
+    JvOptionPane jvp = JvOptionPane.newOptionDialog(this);
+
+    JPanel jp = new JPanel();
+    jp.setLayout(new BoxLayout(jp, BoxLayout.Y_AXIS));
+
+    if (message != null)
+    {
+      jl.setAlignmentX(Component.LEFT_ALIGNMENT);
+      jp.add(jl);
+    }
+    if (boxtext != null)
+    {
+      if (scrollable)
+      {
+        jsp.setAlignmentX(Component.LEFT_ALIGNMENT);
+        jp.add(jsp);
+      }
+      else
+      {
+        jtc.setAlignmentX(Component.LEFT_ALIGNMENT);
+        jp.add(jtc);
+      }
+    }
+
+    jvp.setResponseHandler(JOptionPane.YES_OPTION, () -> {
+    });
+    jvp.showDialogOnTopAsync(this, jp, title, JOptionPane.YES_OPTION, type,
+            null, new Object[]
+            { button }, button, modal, null, false);
+  }
+
 }
index 8d28b1b..457ea1f 100644 (file)
@@ -108,7 +108,7 @@ public class ImageExporter
           String imageSource) throws ImageOutputException
   {
     doExport(file, parent, width, height, imageSource, null,
-            BitmapImageSizing.nullBitmapImageSizing());
+            BitmapImageSizing.defaultBitmapImageSizing());
   }
 
   public void doExport(File file, Component parent, int width, int height,
index 5b926c3..3f5de0a 100644 (file)
@@ -722,7 +722,8 @@ public class JvOptionPane extends JOptionPane
 
   private static void outputMessage(Object message)
   {
-    jalview.bin.Console.outPrintln(">>> JOption Message : " + message.toString());
+    jalview.bin.Console
+            .outPrintln(">>> JOption Message : " + message.toString());
   }
 
   public static Object getMockResponse()
@@ -1264,6 +1265,17 @@ public class JvOptionPane extends JOptionPane
           int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
           Object initialValue, boolean modal, JButton[] buttons)
   {
+    showDialogOnTopAsync(dialogParent, label, actionString,
+            JOPTIONPANE_OPTION, JOPTIONPANE_MESSAGETYPE, icon, options,
+            initialValue, modal, buttons, true);
+  }
+
+  public void showDialogOnTopAsync(JFrame dialogParent, Object label,
+          String actionString, int JOPTIONPANE_OPTION,
+          int JOPTIONPANE_MESSAGETYPE, Icon icon, Object[] options,
+          Object initialValue, boolean modal, JButton[] buttons,
+          boolean dispose)
+  {
     if (!isInteractiveMode())
     {
       handleResponse(getMockResponse());
@@ -1285,8 +1297,11 @@ public class JvOptionPane extends JOptionPane
             JOPTIONPANE_MESSAGETYPE, icon, options, initialValue, modal,
             buttons);
 
-    dialogParent.setAlwaysOnTop(false);
-    dialogParent.dispose();
+    if (dispose)
+    {
+      dialogParent.setAlwaysOnTop(false);
+      dialogParent.dispose();
+    }
   }
 
   /**
index 38ee9ca..a43dc42 100755 (executable)
@@ -36,6 +36,7 @@ import java.util.StringTokenizer;
 import com.stevesoft.pat.Regex;
 
 import jalview.bin.Jalview;
+import jalview.bin.Jalview.ExitCode;
 import jalview.datamodel.BinaryNode;
 import jalview.datamodel.SequenceNode;
 import jalview.util.MessageManager;
@@ -324,7 +325,7 @@ public class NewickFile extends FileParse
         {
           c.setRight(new SequenceNode(null, c, null, DefDistance,
                   DefBootstrap, false));
-          c = (BinaryNode) c.right();
+          c = c.right();
         }
         else
         {
@@ -338,7 +339,7 @@ public class NewickFile extends FileParse
 
           c.setLeft(new SequenceNode(null, c, null, DefDistance,
                   DefBootstrap, false));
-          c = (BinaryNode) c.left();
+          c = c.left();
         }
 
         if (realroot == null)
@@ -579,7 +580,7 @@ public class NewickFile extends FileParse
               // Just advance focus, if we need to
               if ((c.left() != null) && (!c.left().isLeaf()))
               {
-                c = (BinaryNode) c.left();
+                c = c.left();
               }
             }
           }
@@ -617,7 +618,7 @@ public class NewickFile extends FileParse
     }
     // THe next line is failing for topali trees - not sure why yet. if
     // (root.right()!=null && root.isDummy())
-    root = (SequenceNode) root.right().detach(); // remove the imaginary root.
+    root = root.right().detach(); // remove the imaginary root.
 
     if (!RootHasDistance)
     {
@@ -947,7 +948,8 @@ public class NewickFile extends FileParse
       if (args == null || args.length != 1)
       {
         Jalview.exit(
-                "Takes one argument - file name of a newick tree file.", 0);
+                "Takes one argument - file name of a newick tree file.",
+                ExitCode.INVALID_ARGUMENT);
       }
 
       File fn = new File(args[0]);
@@ -969,20 +971,24 @@ public class NewickFile extends FileParse
       jalview.bin.Console.outPrintln("Original file :\n");
 
       Regex nonl = new Regex("\n+", "");
-      jalview.bin.Console.outPrintln(nonl.replaceAll(newickfile.toString()) + "\n");
+      jalview.bin.Console
+              .outPrintln(nonl.replaceAll(newickfile.toString()) + "\n");
 
       jalview.bin.Console.outPrintln("Parsed file.\n");
-      jalview.bin.Console.outPrintln("Default output type for original input.\n");
+      jalview.bin.Console
+              .outPrintln("Default output type for original input.\n");
       jalview.bin.Console.outPrintln(trf.print());
       jalview.bin.Console.outPrintln("Without bootstraps.\n");
       jalview.bin.Console.outPrintln(trf.print(false));
       jalview.bin.Console.outPrintln("Without distances.\n");
       jalview.bin.Console.outPrintln(trf.print(true, false));
-      jalview.bin.Console.outPrintln("Without bootstraps but with distanecs.\n");
+      jalview.bin.Console
+              .outPrintln("Without bootstraps but with distanecs.\n");
       jalview.bin.Console.outPrintln(trf.print(false, true));
       jalview.bin.Console.outPrintln("Without bootstraps or distanecs.\n");
       jalview.bin.Console.outPrintln(trf.print(false, false));
-      jalview.bin.Console.outPrintln("With bootstraps and with distances.\n");
+      jalview.bin.Console
+              .outPrintln("With bootstraps and with distances.\n");
       jalview.bin.Console.outPrintln(trf.print(true, true));
     } catch (java.io.IOException e)
     {
index f6145c2..6fa7c5c 100644 (file)
@@ -49,6 +49,7 @@ import java.io.PipedOutputStream;
 import java.io.PrintStream;
 
 import jalview.bin.Jalview;
+import jalview.bin.Jalview.ExitCode;
 
 public class AWTConsole extends WindowAdapter
         implements WindowListener, ActionListener, Runnable
@@ -176,7 +177,7 @@ public class AWTConsole extends WindowAdapter
     } catch (Exception e)
     {
     }
-    Jalview.exit("Window closing. Bye!", 0);
+    Jalview.exit("Window closing. Bye!", ExitCode.OK);
   }
 
   @Override
index e62a7d6..983ba75 100644 (file)
@@ -36,6 +36,8 @@ public class FileUtils
           boolean allowSingleFilenameThatDoesNotExist)
   {
     pattern = substituteHomeDir(pattern);
+    String relativePattern = pattern.startsWith(File.separator) ? null
+            : pattern;
     List<File> files = new ArrayList<>();
     /*
      * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
@@ -60,7 +62,11 @@ public class FileUtils
     {
       String pS = pattern.substring(0, lastFS + 1);
       String rest = pattern.substring(lastFS + 1);
-      Path parentDir = Paths.get(pS).toAbsolutePath();
+      if ("".equals(pS))
+      {
+        pS = ".";
+      }
+      Path parentDir = Paths.get(pS);
       if (parentDir.toFile().exists())
       {
         try
@@ -175,18 +181,94 @@ public class FileUtils
       return null;
 
     String dirname = null;
-    try
-    {
-      File p = file.getParentFile();
-      File d = new File(substituteHomeDir(p.getPath()));
-      dirname = d.getCanonicalPath();
-    } catch (IOException e)
+    File p = file.getParentFile();
+    if (p == null)
     {
-      Console.debug(
-              "Exception when getting dirname of '" + file.getPath() + "'",
-              e);
-      dirname = "";
+      p = new File(".");
     }
+    File d = new File(substituteHomeDir(p.getPath()));
+    dirname = d.getPath();
     return dirname;
   }
+
+  public static String convertWildcardsToPath(String value, String wildcard,
+          String dirname, String basename)
+  {
+    if (value == null)
+    {
+      return null;
+    }
+    StringBuilder path = new StringBuilder();
+    int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
+    int wildcardBeforeIndex = value.indexOf(wildcard);
+    if (lastFileSeparatorIndex > wildcard.length() - 1
+            && wildcardBeforeIndex < lastFileSeparatorIndex)
+    {
+      path.append(value.substring(0, wildcardBeforeIndex));
+      path.append(dirname);
+      path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
+              lastFileSeparatorIndex + 1));
+    }
+    else
+    {
+      path.append(value.substring(0, lastFileSeparatorIndex + 1));
+    }
+    int wildcardAfterIndex = value.indexOf(wildcard,
+            lastFileSeparatorIndex);
+    if (wildcardAfterIndex > lastFileSeparatorIndex)
+    {
+      path.append(value.substring(lastFileSeparatorIndex + 1,
+              wildcardAfterIndex));
+      path.append(basename);
+      path.append(value.substring(wildcardAfterIndex + wildcard.length()));
+    }
+    else
+    {
+      path.append(value.substring(lastFileSeparatorIndex + 1));
+    }
+    return path.toString();
+  }
+
+  public static File getParentDir(File file)
+  {
+    if (file == null)
+    {
+      return null;
+    }
+    File parentDir = file.getAbsoluteFile().getParentFile();
+    return parentDir;
+  }
+
+  public static boolean checkParentDir(File file, boolean mkdirs)
+  {
+    if (file == null)
+    {
+      return false;
+    }
+    File parentDir = getParentDir(file);
+    if (parentDir.exists())
+    {
+      // already exists, nothing to do so nothing to worry about!
+      return true;
+    }
+
+    if (!mkdirs)
+    {
+      return false;
+    }
+
+    Path path = file.toPath();
+    for (int i = 0; i < path.getNameCount(); i++)
+    {
+      Path p = path.getName(i);
+      if ("..".equals(p.toString()))
+      {
+        Console.warn("Cautiously not running mkdirs on " + file.toString()
+                + " because the path to be made contains '..'");
+        return false;
+      }
+    }
+
+    return parentDir.mkdirs();
+  }
 }
index 3306b0d..fa473f3 100755 (executable)
@@ -189,9 +189,9 @@ public class ImageMaker
 
     BitmapImageSizing bis = ImageMaker.getScaleWidthHeight(width, height,
             userBis);
-    float usescale = bis.scale;
-    int usewidth = bis.width;
-    int useheight = bis.height;
+    float usescale = bis.scale();
+    int usewidth = bis.width();
+    int useheight = bis.height();
 
     bi = new BufferedImage(usewidth, useheight, BufferedImage.TYPE_INT_RGB);
     graphics = bi.getGraphics();
@@ -247,14 +247,22 @@ public class ImageMaker
   }
 
   /**
-   * Takes suggested float scale, int width, int height and create a bounding
-   * box returned as a BitmapImageSizing object with consistent scale, width,
-   * height fields.
+   * Takes initial width and height, and suggested float scale, int width, int
+   * height and create a bounding box returned as a BitmapImageSizing object
+   * with consistent scale, width, height fields.
    * 
+   * @param width
+   *          The unscaled image width
+   * @param height
+   *          The unscaled image height
    * @param scale
+   *          The suggested scaling
    * @param bitmapwidth
+   *          The suggested width
    * @param bitmapheight
-   * @return BitmapImageSizing
+   *          The suggested height
+   * @return BitmapImageSizing A consistent scale,width and height for the final
+   *         image
    */
   public static BitmapImageSizing getScaleWidthHeight(int width, int height,
           float scale, int bitmapwidth, int bitmapheight)
@@ -263,6 +271,13 @@ public class ImageMaker
     int usewidth = width;
     int useheight = height;
 
+    if ((width == 0 && bitmapwidth > 0)
+            || (height == 0 && bitmapheight > 0))
+    {
+      // original image is zero sized! Avoid dividing by zero!
+      return BitmapImageSizing.nullBitmapImageSizing();
+    }
+
     // use the smallest positive scale (i.e. fit in the box)
     if (scale > 0.0f)
     {
@@ -290,7 +305,7 @@ public class ImageMaker
         useheight = bitmapheight;
       }
     }
-    return new BitmapImageSizing(usescale, usewidth, useheight);
+    return new BitmapImageSizing(usescale, usewidth, useheight, false);
   }
 
   /**
@@ -304,8 +319,8 @@ public class ImageMaker
   public static BitmapImageSizing getScaleWidthHeight(int width, int height,
           BitmapImageSizing bis)
   {
-    return ImageMaker.getScaleWidthHeight(width, height, bis.scale,
-            bis.width, bis.height);
+    return ImageMaker.getScaleWidthHeight(width, height, bis.scale(),
+            bis.width(), bis.height());
   }
 
   /**
@@ -321,6 +336,13 @@ public class ImageMaker
   public static BitmapImageSizing parseScaleWidthHeightStrings(
           String scaleS, String widthS, String heightS)
   {
+    if (scaleS == null && widthS == null && heightS == null)
+    {
+      // if all items are null (i.e. not provided) we use the dynamic
+      // preferences set BIS
+      return BitmapImageSizing.defaultBitmapImageSizing();
+    }
+
     float scale = 0.0f;
     int width = 0;
     int height = 0;
@@ -359,6 +381,6 @@ public class ImageMaker
       }
     }
 
-    return new BitmapImageSizing(scale, width, height);
+    return new BitmapImageSizing(scale, width, height, false);
   }
 }
index 450b01b..e170f0e 100644 (file)
@@ -4,24 +4,33 @@ import jalview.bin.Cache;
 
 public class BitmapImageSizing
 {
-  public final float scale;
+  private final float scale;
 
-  public final int width;
+  private final int width;
 
-  public final int height;
+  private final int height;
 
-  public BitmapImageSizing(float scale, int width, int height)
+  private boolean isDefault = false;
+
+  public BitmapImageSizing(float scale, int width, int height,
+          boolean isDefault)
   {
     this.scale = scale;
     this.width = width;
     this.height = height;
+    this.isDefault = isDefault;
+  }
+
+  public boolean isNull()
+  {
+    return scale == 0.0f && width == 0 && height == 0;
   }
 
   public static BitmapImageSizing nullBitmapImageSizing()
   {
-    return new BitmapImageSizing(0.0f, 0, 0);
+    return new BitmapImageSizing(0.0f, 0, 0, false);
   }
-  
+
   public static final String BITMAP_SCALE = "BITMAP_SCALE";
 
   public static final String BITMAP_HEIGHT = "BITMAP_HEIGHT";
@@ -34,8 +43,41 @@ public class BitmapImageSizing
    */
   public static BitmapImageSizing defaultBitmapImageSizing()
   {
-    
-    return new BitmapImageSizing(Cache.getDefault(BITMAP_SCALE,0)/10f,Cache.getDefault(BITMAP_WIDTH,0),Cache.getDefault(BITMAP_HEIGHT,0));
-    
+    return new BitmapImageSizing(0f, 0, 0, true);
+  }
+
+  private float defaultScale()
+  {
+    return Cache.getDefault(BITMAP_SCALE, 0f);
+  }
+
+  private int defaultWidth()
+  {
+    return Cache.getDefault(BITMAP_WIDTH, 0);
+  }
+
+  private int defaultHeight()
+  {
+    return Cache.getDefault(BITMAP_HEIGHT, 0);
+  }
+
+  public float scale()
+  {
+    return isDefault() ? defaultScale() : scale;
+  }
+
+  public int width()
+  {
+    return isDefault() ? defaultWidth() : width;
+  }
+
+  public int height()
+  {
+    return isDefault() ? defaultHeight() : height;
+  }
+
+  public boolean isDefault()
+  {
+    return isDefault;
   }
 }
index fe40682..66250d8 100644 (file)
@@ -331,6 +331,8 @@ public class CommandsTest
     }
   }
 
+  private final String deleteDir = "test/deleteAfter";
+
   @Test(
     groups = "Functional",
     dataProvider = "allLinkedIdsData",
@@ -357,6 +359,12 @@ public class CommandsTest
                 "File " + nonfilename + " exists when it shouldn't!");
       }
     }
+
+    File deleteDirF = new File(deleteDir);
+    if (deleteDirF.exists())
+    {
+      deleteDirF.delete();
+    }
   }
 
   @DataProvider(name = "allLinkedIdsData")
@@ -364,8 +372,6 @@ public class CommandsTest
   {
     return new Object[][] {
         //
-        /*
-         */
         { "--gui --open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --output={dirname}/{basename}.stk --close",
             new String[]
             { "test/jalview/bin/argparser/testfiles/test1.stk",
@@ -408,7 +414,7 @@ public class CommandsTest
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", },
             null },
-        { "--gui --open=test/jalview/bin/argparser/**/*.fa --output=*.stk --close",
+        { "--gui --open=test/jalview/bin/argparser/**/*.fa --output=*/*.stk --close",
             new String[]
             { "test/jalview/bin/argparser/testfiles/test1.stk",
                 "test/jalview/bin/argparser/testfiles/test2.stk",
@@ -423,7 +429,7 @@ public class CommandsTest
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", },
             null },
-        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=*.stk --close",
+        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --all --output=*/*.stk --close",
             new String[]
             { "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
@@ -438,7 +444,20 @@ public class CommandsTest
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
-        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=open*.stk --close",
+        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=*/*.stk --close",
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
+        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output={dirname}/{basename}.stk --close",
             new String[]
             { "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
@@ -453,7 +472,7 @@ public class CommandsTest
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
-        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --opened --output={dirname}/{basename}.stk --close",
+        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output={dirname}/{basename}.stk --close",
             new String[]
             { "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
@@ -468,7 +487,7 @@ public class CommandsTest
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", }, },
-        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --output open*.stk --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=open*.aln --close",
+        { "--gui --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --output {dirname}/{basename}.stk --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output={dirname}/{basename}.aln --close",
             new String[]
             { "test/jalview/bin/argparser/testfiles/dir1/test1.stk",
                 "test/jalview/bin/argparser/testfiles/dir1/test2.stk",
@@ -495,6 +514,81 @@ public class CommandsTest
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln",
                 "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln", }, },
+        // --mkdirs
+        { "--headless --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --output "
+                + deleteDir
+                + "/{dirname}/{basename}.stk --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output="
+                + deleteDir
+                + "/{dirname}/{basename}.aln --close --all --mkdirs",
+            new String[]
+            { deleteDir
+                    + "/test/jalview/bin/argparser/testfiles/dir1/test1.stk",
+                deleteDir
+                        + "/test/jalview/bin/argparser/testfiles/dir1/test2.stk",
+                deleteDir
+                        + "/test/jalview/bin/argparser/testfiles/dir2/test1.aln",
+                deleteDir
+                        + "/test/jalview/bin/argparser/testfiles/dir2/test2.aln",
+                deleteDir
+                        + "/test/jalview/bin/argparser/testfiles/dir2/test3.aln", },
+            new String[]
+            { "test/jalview/bin/argparser/testfiles/test1.stk",
+                "test/jalview/bin/argparser/testfiles/test2.stk",
+                "test/jalview/bin/argparser/testfiles/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir2/test3.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk",
+                "test/jalview/bin/argparser/testfiles/test1.aln",
+                "test/jalview/bin/argparser/testfiles/test2.aln",
+                "test/jalview/bin/argparser/testfiles/test3.aln",
+                "test/jalview/bin/argparser/testfiles/dir1/test1.aln",
+                "test/jalview/bin/argparser/testfiles/dir1/test2.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln",
+                "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/test1.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/test2.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/test3.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir2/test1.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir2/test2.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir2/test3.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/test1.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/test2.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/test3.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir1/test1.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir1/test2.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln",
+                deleteDir
+                        + "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln", }, },
         //
     };
   }
index 87a26d1..6812bce 100644 (file)
@@ -171,7 +171,7 @@ public class HiDPISettingTest2
 
     String jvmArgs = HiDPISetting.getScalePropertyArg(scale);
 
-    String appArgs = " -open examples/uniref50.fa -nosplash -nonews -noquestionnaire -nousagestats -nowebservicediscovery";
+    String appArgs = " --open examples/uniref50.fa --nosplash --nonews --noquestionnaire --nousagestats --nowebservicediscovery";
 
     Worker worker = getJalviewDesktopRunner(jvmArgs, appArgs);
     assertNotNull(worker, "worker is null");
@@ -183,7 +183,7 @@ public class HiDPISettingTest2
     {
       while ((ln = worker.getErrorReader().readLine()) != null)
       {
-        if (++count > 100)
+        if (++count > 150)
         {
           break;
         }
index 9beba17..3ba096e 100644 (file)
@@ -279,9 +279,15 @@ public class ArgParserTest
   }
 
   @Test(groups = "Functional", dataProvider = "allLinkedIdsData")
-  public void allLinkedIdsTest(String commandLineArgs, Arg a,
+  public void allLinkedIdsTest(String pwd, String commandLineArgs, Arg a,
           String[] values, String[] nonvalues)
   {
+    String userDir = System.getProperty("user.dir");
+    if (pwd != null)
+    {
+      File pwdFile = new File(pwd);
+      System.setProperty("user.dir", pwdFile.getAbsolutePath());
+    }
     String[] args = commandLineArgs.split("\\s+");
     ArgParser argparser = new ArgParser(args);
 
@@ -306,12 +312,13 @@ public class ArgParserTest
         ArgValues avs = avm.getArgValues(a);
         ArgValue av = avs.getArgValue();
         String v = av.getValue();
-        value = new File(value).getAbsolutePath();
+        value = new File(value).getPath();
         Assert.assertEquals(v, value, "Arg value for " + a.argString()
                 + " not applied correctly to linkedId '" + linkedId + "'");
       }
     }
 
+    System.setProperty("user.dir", userDir);
   }
 
   @DataProvider(name = "allLinkedIdsData")
@@ -321,22 +328,33 @@ public class ArgParserTest
         //
         /*
         */
-        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
+        { null,
+            "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
             Arg.CLOSE, new String[]
             { null, null, null },
             null },
-        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --output={dirname}/{basename}.stk --close",
+        { null,
+            "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --output={dirname}/{basename}.stk --close",
             Arg.OUTPUT, new String[]
             { "test/jalview/bin/argparser/testfiles/test1.stk",
                 "test/jalview/bin/argparser/testfiles/test2.stk",
                 "test/jalview/bin/argparser/testfiles/test3.stk", },
             null },
-        { "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
+        { null,
+            "--open=test/jalview/bin/argparser/testfiles/*.fa --substitutions --all --image={dirname}/{basename}.png --close",
             Arg.IMAGE, new String[]
             { "test/jalview/bin/argparser/testfiles/test1.png",
                 "test/jalview/bin/argparser/testfiles/test2.png",
                 "test/jalview/bin/argparser/testfiles/test3.png", },
             null },
+        /*
+         * Find a way to change pwd reliably -- need to match "*.fa" against some files!
+         { "test/jalview/bin/argparser/testfiles",
+         
+            "--open=*.fa --image={dirname}/{basename}.png --close",
+            Arg.IMAGE, new String[]
+            { "./test1.png", "./test2.png", "./test3.png", }, null },
+            */
         //
     };
   }
@@ -414,4 +432,4 @@ public class ArgParserTest
     };
   }
 
-}
+}
\ No newline at end of file
index 35853b0..666b33b 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.util;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.List;
 
 import org.testng.Assert;
@@ -56,29 +55,26 @@ public class FileUtilsTest
   }
 
   @Test(groups = "Functional", dataProvider = "dirnamesAndBasenames")
-  public void testDirnamesAndBasenames(String filename, int where,
-          String dirname, String basename, String notInDirname)
+  public void testDirnamesAndBasenames(String filename, String dirname,
+          String endsWith, String basename, String notInDirname)
   {
     File file = new File(filename);
     String d = FileUtils.getDirname(file);
     String b = FileUtils.getBasename(file);
     Assert.assertEquals(b, basename);
-    if (where == 0)
-      Assert.assertEquals(d, dirname);
-    else if (where < 0)
-      Assert.assertTrue(d.startsWith(dirname),
-              "getDirname(" + file.getPath() + ")=" + d
-                      + " didn't start with '" + dirname + "'");
-    else if (where > 0)
-      Assert.assertTrue(d.endsWith(dirname), "getDirname(" + file.getPath()
-              + ")=" + d + " didn't end with '" + d + "'");
+    Assert.assertTrue(d.startsWith(dirname), "getDirname(" + file.getPath()
+            + ")=" + d + " didn't start with '" + dirname + "'");
+    Assert.assertTrue(d.endsWith(endsWith), "getDirname(" + file.getPath()
+            + ")=" + d + " didn't end with '" + endsWith + "'");
 
     // ensure dirname doesn't end with basename (which means you can't use same
     // filename as dir in tests!)
-    Assert.assertFalse(d.endsWith(b));
+    Assert.assertFalse(d.endsWith(b), "Processed dirname '" + d
+            + "' ends with '" + b + "' when it shouldn't");
 
     if (notInDirname != null)
-      Assert.assertFalse(d.contains(notInDirname));
+      Assert.assertFalse(d.contains(notInDirname), "Processed directory '"
+              + d + "' contains '" + notInDirname + "' when it shouldn't");
   }
 
   @DataProvider(name = "patternsAndMinNumFiles")
@@ -101,22 +97,64 @@ public class FileUtilsTest
   @DataProvider(name = "dirnamesAndBasenames")
   public Object[][] dirnamesAndBasenames()
   {
-    String homeDir = null;
-    try
-    {
-      homeDir = new File(System.getProperty("user.home"))
-              .getCanonicalPath();
-    } catch (IOException e)
-    {
-      System.err.println("Problem getting canonical home dir");
-      e.printStackTrace();
-    }
+    String homeDir = new File(System.getProperty("user.home")).getPath();
     return new Object[][] { // -1=startsWith, 0=equals, 1=endsWith
-        { "~/hello/sailor", -1, homeDir, "sailor", "~" }, //
-        { "~/hello/sailor", 1, "/hello", "sailor", "~" }, //
-        { "./examples/uniref50.fa", -1, "/", "uniref50", "." }, //
-        { "./examples/uniref50.fa", 1, "/examples", "uniref50", "." }, //
-        { "examples/uniref50.fa", 1, "/examples", "uniref50", ".fa" }, //
+        { "~/hello/sailor", homeDir, "/hello", "sailor", "~" }, //
+        { "./examples/uniref50.fa", "./", "examples", "uniref50", "Users" }, //
+        { "./examples/uniref50.1.fa", "./", "examples", "uniref50.1",
+            "Users" }, //
+        { "examples/uniref50.fa", "examples", "examples", "uniref50",
+            ".fa" }, //
     };
   }
+
+  @Test(groups = "Functional", dataProvider = "convertWildcardsToPathData")
+  public void convertWildcardsToPathTest(String value, String wildcard,
+          String dirname, String basename, String path)
+  {
+
+    Assert.assertEquals(
+            FileUtils.convertWildcardsToPath(value, wildcard, dirname,
+                    basename),
+            path, "Conversion of wildcard output path is not right.");
+
+  }
+
+  @DataProvider(name = "convertWildcardsToPathData")
+  public Object[][] convertWildcardsToPathData()
+  {
+    return new Object[][] {
+        /*
+         * cmdline args
+         * Arg (null if only testing headless)
+         * String value if there is one (null otherwise)
+         * boolean value if String value is null
+         * expected value of isHeadless()
+         */
+        /*
+        */
+        { "*/*", "*", "{dirname}", "{basename}", "{dirname}/{basename}" },
+        { "*/", "*", "{dirname}", "{basename}", "{dirname}/" },
+        { "/*", "*", "{dirname}", "{basename}", "/{basename}" },
+        { "*", "*", "{dirname}", "{basename}", "{basename}" },
+        { "tmp/output/*/file-*.stk", "*", "{dirname}", "{basename}",
+            "tmp/output/{dirname}/file-{basename}.stk" },
+        { "/*.file", "*", "{dirname}", "{basename}", "/{basename}.file" },
+        { "*/file.stk", "*", "{dirname}", "{basename}",
+            "{dirname}/file.stk" },
+        { "tmp/abc*def/file.stk", "*", "{dirname}", "{basename}",
+            "tmp/abc{dirname}def/file.stk" },
+        { "a*b/c*d", "*", "{dirname}", "{basename}",
+            "a{dirname}b/c{basename}d" },
+        { "a*b/c", "*", "{dirname}", "{basename}", "a{dirname}b/c" },
+        { "a/b*c", "*", "{dirname}", "{basename}", "a/b{basename}c" },
+        { "a*b", "*", "{dirname}", "{basename}", "a{basename}b" },
+        { "aSTARb/cSTARd", "STAR", "BEFORE", "AFTER", "aBEFOREb/cAFTERd" },
+        { "aSTARb/c", "STAR", "BEFORE", "AFTER", "aBEFOREb/c" },
+        { "a/bSTARc", "STAR", "BEFORE", "AFTER", "a/bAFTERc" },
+        { "aSTARb", "STAR", "BEFORE", "AFTER", "aAFTERb" },
+        //
+    };
+  }
+
 }
diff --git a/test/jalview/util/ImageMakerTest.java b/test/jalview/util/ImageMakerTest.java
new file mode 100644 (file)
index 0000000..d953f7b
--- /dev/null
@@ -0,0 +1,112 @@
+package jalview.util;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import jalview.bin.Cache;
+import jalview.util.imagemaker.BitmapImageSizing;
+
+public class ImageMakerTest
+{
+  @Test(groups = { "Functional" })
+  public void testParseScaleWidthHeightStrings()
+  {
+    Cache.setPropsAreReadOnly(true);
+    Cache.loadProperties("test/jalview/bin/testProps.jvprops");
+
+    Cache.removeProperty(BitmapImageSizing.BITMAP_SCALE);
+    Cache.removeProperty(BitmapImageSizing.BITMAP_HEIGHT);
+    Cache.removeProperty(BitmapImageSizing.BITMAP_WIDTH);
+
+    BitmapImageSizing bis = null;
+
+    // No defaults set, 3 values given. Should be the 3 values.
+    bis = ImageMaker.parseScaleWidthHeightStrings("1.2", "3", "4");
+    Assert.assertEquals(bis.scale(), 1.2f,
+            "scale not parsed and set to value given");
+    Assert.assertEquals(bis.width(), 3,
+            "width not parsed and set to value given");
+    Assert.assertEquals(bis.height(), 4,
+            "height not parsed and set to value given");
+
+    // No defaults set, 1 value given. Should be the 1 value and 2 0s.
+    bis = ImageMaker.parseScaleWidthHeightStrings("1.2", null, null);
+    Assert.assertEquals(bis.scale(), 1.2f,
+            "scale not parsed and set to value given");
+    Assert.assertEquals(bis.width(), 0, "width not parsed and set to 0");
+    Assert.assertEquals(bis.height(), 0, "height not parsed and set to 0");
+
+    // No defaults set, 1 value given. Should be the 1 value and 2 0s. (checking
+    // the other value)
+    bis = ImageMaker.parseScaleWidthHeightStrings(null, "1", null);
+    Assert.assertEquals(bis.scale(), 0f, "scale not parsed and set to 0f");
+    Assert.assertEquals(bis.width(), 1,
+            "width not parsed and set to value given");
+    Assert.assertEquals(bis.height(), 0, "height not parsed and set to 0");
+
+    // No defaults set, no values given, these should first look at defaults and
+    // then set all to 0
+    bis = ImageMaker.parseScaleWidthHeightStrings(null, null, null);
+    Assert.assertEquals(bis.scale(), 0f,
+            "scale not parsed and set to undefined default 0f");
+    Assert.assertEquals(bis.width(), 0,
+            "width not parsed and set to undefined default 0");
+    Assert.assertEquals(bis.height(), 0,
+            "height not parsed and set to undefined default 0");
+
+    Cache.setProperty(BitmapImageSizing.BITMAP_HEIGHT, "1");
+    // 1 default set, bis should detect this
+    Assert.assertEquals(bis.scale(), 0f,
+            "scale not parsed and set to undefined default 0f");
+    Assert.assertEquals(bis.width(), 0,
+            "width not parsed and set to undefined default 0");
+    Assert.assertEquals(bis.height(), 1,
+            "height not parsed and set to default 1");
+
+    Cache.setProperty(BitmapImageSizing.BITMAP_SCALE, "3.4");
+    Cache.setProperty(BitmapImageSizing.BITMAP_WIDTH, "2");
+    // Now all 3 defaults set, bis should detect this
+    Assert.assertEquals(bis.scale(), 3.4f,
+            "scale not parsed and set to undefined default 3.2f");
+    Assert.assertEquals(bis.width(), 2,
+            "width not parsed and set to undefined default 2");
+    Assert.assertEquals(bis.height(), 1,
+            "height not parsed and set to default 1");
+
+    // 3 defaults set, and 3 values given, should use the 3 values
+    bis = ImageMaker.parseScaleWidthHeightStrings("1.2", "3", "4");
+    Assert.assertEquals(bis.scale(), 1.2f,
+            "scale not parsed and set to value given");
+    Assert.assertEquals(bis.width(), 3,
+            "width not parsed and set to value given");
+    Assert.assertEquals(bis.height(), 4,
+            "height not parsed and set to value given");
+
+    // 3 defaults set, and 1 value given, should use the 1 value and 2 0s
+    bis = ImageMaker.parseScaleWidthHeightStrings("1.2", null, null);
+    Assert.assertEquals(bis.scale(), 1.2f,
+            "scale not parsed and set to value given");
+    Assert.assertEquals(bis.width(), 0,
+            "width not parsed and set to undefined 0");
+    Assert.assertEquals(bis.height(), 0,
+            "height not parsed and set to undefined 0");
+
+    // 3 defaults set, and 1 value given, should use the 1 value and 2 0s
+    bis = ImageMaker.parseScaleWidthHeightStrings(null, null, "5");
+    Assert.assertEquals(bis.scale(), 0f,
+            "scale not parsed and set to undefined 0f");
+    Assert.assertEquals(bis.width(), 0,
+            "width not parsed and set to undefined 0");
+    Assert.assertEquals(bis.height(), 5,
+            "height not parsed and set to value given");
+
+    // 3 defaults set, and no values given, should use the 3 default values
+    bis = ImageMaker.parseScaleWidthHeightStrings(null, null, null);
+    Assert.assertEquals(bis.scale(), 3.4f,
+            "scale not parsed and set to undefined default 3.2f");
+    Assert.assertEquals(bis.width(), 2,
+            "width not parsed and set to undefined default 2");
+    Assert.assertEquals(bis.height(), 1,
+            "height not parsed and set to default 1");
+  }
+}
index 49e1084..016c91f 100644 (file)
@@ -6,38 +6,41 @@ import org.testng.annotations.Test;
 
 import jalview.bin.Cache;
 
-public class BitmapImageSizeTest {
-  @Test(groups = {"Functional"})
-  public void testCacheSettingsRecovery() {
+public class BitmapImageSizeTest
+{
+  @Test(groups = { "Functional" })
+  public void testCacheSettingsRecovery()
+  {
     Cache.setPropsAreReadOnly(true);
     Cache.loadProperties("test/jalview/bin/testProps.jvprops");
-    
+
     Cache.removeProperty(BitmapImageSizing.BITMAP_HEIGHT);
     Cache.removeProperty(BitmapImageSizing.BITMAP_SCALE);
     Cache.removeProperty(BitmapImageSizing.BITMAP_WIDTH);
-    
+
     BitmapImageSizing def = BitmapImageSizing.defaultBitmapImageSizing();
     BitmapImageSizing zero = BitmapImageSizing.nullBitmapImageSizing();
 
-    assertEquals(def.height, zero.height);
-    assertEquals(def.width, zero.width);
-    assertEquals(def.scale, zero.scale);
-    
-    Cache.setProperty(BitmapImageSizing.BITMAP_HEIGHT,"120");
-    Cache.setProperty(BitmapImageSizing.BITMAP_SCALE,"240");
-    Cache.setProperty(BitmapImageSizing.BITMAP_WIDTH,"360");
-    
-    def = BitmapImageSizing.defaultBitmapImageSizing();
-    
-    assertEquals(def.height, 120);
-    assertEquals(def.width, 360);
-    assertEquals(def.scale, 24f);
-    
+    assertEquals(def.height(), zero.height());
+    assertEquals(def.width(), zero.width());
+    assertEquals(def.scale(), zero.scale());
+
+    Cache.setProperty(BitmapImageSizing.BITMAP_HEIGHT, "120");
+    Cache.setProperty(BitmapImageSizing.BITMAP_SCALE, "24");
+    Cache.setProperty(BitmapImageSizing.BITMAP_WIDTH, "360");
+
+    // default now updates dynamically
+    // def = BitmapImageSizing.defaultBitmapImageSizing();
+
+    assertEquals(def.height(), 120);
+    assertEquals(def.width(), 360);
+    assertEquals(def.scale(), 24f);
+
     Cache.removeProperty(BitmapImageSizing.BITMAP_WIDTH);
-    
-    def = BitmapImageSizing.defaultBitmapImageSizing();
-    assertEquals(def.height, 120);
-    assertEquals(def.width, zero.width);
-    assertEquals(def.scale, 24f);    
+
+    // def = BitmapImageSizing.defaultBitmapImageSizing();
+    assertEquals(def.height(), 120);
+    assertEquals(def.width(), zero.width());
+    assertEquals(def.scale(), 24f);
   }
 }