JAL-629 bootstrap args and properties. Remember index of args for 'previous structure...
authorBen Soares <b.soares@dundee.ac.uk>
Thu, 23 Feb 2023 16:21:19 +0000 (16:21 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Thu, 23 Feb 2023 16:21:19 +0000 (16:21 +0000)
src/jalview/bin/ArgParser.java
src/jalview/bin/Cache.java
src/jalview/bin/Commands.java
src/jalview/bin/Console.java
src/jalview/bin/Jalview.java
src/jalview/log/JLogger.java

index 338d130..d150db2 100644 (file)
@@ -23,6 +23,7 @@ package jalview.bin;
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Enumeration;
@@ -59,7 +60,7 @@ public class ArgParser
     USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
     VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
     TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
-    NOSTRUCTURE, STRUCTURE, IMAGE, QUIT;
+    NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, DEBUG("d");
 
     static
     {
@@ -111,6 +112,7 @@ public class ArgParser
       WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
       IMAGE.setOptions(Opt.STRING, Opt.LINKED);
       QUIT.setOptions(Opt.UNARY);
+      DEBUG.setOptions(Opt.BOOLEAN);
     }
 
     private final String[] argNames;
@@ -119,6 +121,8 @@ public class ArgParser
 
     private boolean defaultBoolValue = false;
 
+    private int argIndex = -1;
+
     public String toLongString()
     {
       StringBuilder sb = new StringBuilder();
@@ -201,6 +205,16 @@ public class ArgParser
     {
       return defaultBoolValue;
     }
+
+    private void setArgIndex(int i)
+    {
+      this.argIndex = i;
+    }
+
+    protected int getArgIndex()
+    {
+      return this.argIndex;
+    }
   }
 
   public static class ArgValues
@@ -213,12 +227,16 @@ public class ArgParser
 
     private boolean negated = false;
 
-    private List<String> argsList;
+    private int singleArgIndex = -1;
+
+    private List<Integer> argsIndexes;
+
+    private List<ArgValue> argsList;
 
     protected ArgValues(Arg a)
     {
       this.arg = a;
-      this.argsList = new ArrayList<String>();
+      this.argsList = new ArrayList<ArgValue>();
       this.boolValue = arg.getDefaultBoolValue();
     }
 
@@ -272,8 +290,9 @@ public class ArgParser
       {
         sb.append("Values:");
         boolean first = true;
-        for (String v : argsList)
+        for (ArgValue av : argsList)
         {
+          String v = av.getValue();
           if (!first)
             sb.append(",");
           sb.append("\n  '");
@@ -288,25 +307,24 @@ public class ArgParser
 
     protected void addValue()
     {
-      addValue(null);
+      addValue(null, -1);
     }
 
-    protected void addValue(String val)
+    protected void addValue(String val, int argIndex)
     {
-      addValue(val, false);
+      addValue(val, argIndex, false);
     }
 
-    protected void addValue(String val, boolean noDuplicates)
+    protected void addValue(String val, int argIndex, boolean noDuplicates)
     {
       if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
               || (noDuplicates && argsList.contains(val)))
         return;
       if (argsList == null)
       {
-        Console.warn("** inst");
-        argsList = new ArrayList<String>();
+        argsList = new ArrayList<ArgValue>();
       }
-      argsList.add(val);
+      argsList.add(new ArgValue(val, argIndex));
     }
 
     protected boolean hasValue(String val)
@@ -314,14 +332,22 @@ public class ArgParser
       return argsList.contains(val);
     }
 
-    protected String getValue()
+    protected ArgValue getArgValue()
     {
       if (arg.hasOption(Opt.MULTI))
         Console.warn("Requesting single value for multi value argument");
       return argsList.size() > 0 ? argsList.get(0) : null;
     }
 
-    protected List<String> getValues()
+    /*
+    protected String getValue()
+    {
+    ArgValue av = getArgValue();
+    return av == null ? null : av.getValue();
+    }
+    */
+
+    protected List<ArgValue> getArgValueList()
     {
       return argsList;
     }
@@ -454,6 +480,7 @@ public class ArgParser
 
     // new style
     Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
+    int argIndex = 0;
     while (argE.hasMoreElements())
     {
       String arg = argE.nextElement();
@@ -555,7 +582,7 @@ public class ArgParser
         // store appropriate value
         if (a.hasOption(Opt.STRING))
         {
-          values.addValue(val);
+          values.addValue(val, argIndex);
         }
         else if (a.hasOption(Opt.BOOLEAN))
         {
@@ -675,23 +702,29 @@ public class ArgParser
     return m == null ? null : m.get(a);
   }
 
-  public static List<String> getValues(Map<Arg, ArgValues> m, Arg a)
+  public static List<ArgValue> getArgValueList(Map<Arg, ArgValues> m, Arg a)
   {
     ArgValues av = getArgValues(m, a);
-    return av == null ? null : av.getValues();
+    return av == null ? null : av.getArgValueList();
   }
 
-  public static String getValue(Map<Arg, ArgValues> m, Arg a)
+  public static ArgValue getArgValue(Map<Arg, ArgValues> m, Arg a)
   {
-    List<String> vals = getValues(m, a);
+    List<ArgValue> vals = getArgValueList(m, a);
     return (vals == null || vals.size() == 0) ? null : vals.get(0);
   }
 
+  public static String getValue(Map<Arg, ArgValues> m, Arg a)
+  {
+    ArgValue av = getArgValue(m, a);
+    return av == null ? null : av.getValue();
+  }
+
   public static boolean hasValue(Map<Arg, ArgValues> m, Arg a)
   {
     if (!m.containsKey(a))
       return false;
-    return getValue(m, a) != null;
+    return getArgValue(m, a) != null;
   }
 
   public static boolean getBoolean(Map<Arg, ArgValues> m, Arg a)
@@ -700,9 +733,35 @@ public class ArgParser
     return av == null ? false : av.getBoolean();
   }
 
-  public static SubVal getSubVal(String item)
+  public static SubVals getSubVals(String item)
   {
-    return new SubVal(item);
+    return new SubVals(item);
+  }
+
+  /**
+   * A helper class to keep an index of argument position with argument values
+   */
+  public static class ArgValue
+  {
+    private int argIndex;
+
+    private String value;
+
+    protected ArgValue(String value, int argIndex)
+    {
+      this.value = value;
+      this.argIndex = argIndex;
+    }
+
+    protected String getValue()
+    {
+      return value;
+    }
+
+    protected int getArgIndex()
+    {
+      return argIndex;
+    }
   }
 
   /**
@@ -711,50 +770,60 @@ public class ArgParser
    * the strings keyName and keyValue, and the content after the square brackets
    * (if present). Values not set `will be -1 or null.
    */
-  public static class SubVal
+  public static class SubVals
   {
     private static int NOTSET = -1;
 
-    protected int index = NOTSET;
+    private int index = NOTSET;
 
-    protected String keyName = null;
+    private Map<String, String> subVals = null;
 
-    protected String keyValue = null;
+    private static char SEPARATOR = ';';
 
-    protected String content = null;
+    private String content = null;
 
-    public SubVal(String item)
+    public SubVals(String item)
     {
-      this.parseVal(item);
+      this.parseVals(item);
     }
 
-    public void parseVal(String item)
+    public void parseVals(String item)
     {
       if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
       {
         int openBracket = item.indexOf('[');
         int closeBracket = item.indexOf(']');
-        String indexString = item.substring(openBracket + 1, closeBracket);
+        String subvalsString = item.substring(openBracket + 1,
+                closeBracket);
         this.content = item.substring(closeBracket + 1);
-        int equals = indexString.indexOf('=');
-        if (equals > -1)
+        boolean setIndex = false;
+        for (String subvalString : subvalsString
+                .split(Character.toString(SEPARATOR)))
         {
-          this.keyName = indexString.substring(0, equals);
-          this.keyValue = indexString.substring(equals + 1);
-          this.index = -1;
-        }
-        else
-        {
-          try
+          int equals = subvalString.indexOf('=');
+          if (equals > -1)
           {
-            this.index = Integer.parseInt(indexString);
-          } catch (NumberFormatException e)
+            if (subVals == null)
+              subVals = new HashMap<>();
+            subVals.put(subvalString.substring(0, equals),
+                    subvalString.substring(equals + 1));
+          }
+          else
           {
-            Console.warn("Failed to obtain subvalue or index from '" + item
-                    + "'. Setting index=0 and using content='" + content
-                    + "'.");
+            try
+            {
+              this.index = Integer.parseInt(subvalString);
+              setIndex = true;
+            } catch (NumberFormatException e)
+            {
+              Console.warn("Failed to obtain subvalue or index from '"
+                      + item + "'. Setting index=0 and using content='"
+                      + content + "'.");
+            }
           }
         }
+        if (!setIndex)
+          this.index = NOTSET;
       }
       else
       {
@@ -765,7 +834,60 @@ public class ArgParser
     public boolean notSet()
     {
       // notSet is true if content present but nonsensical
-      return index == NOTSET && keyName == null && keyValue == null;
+      return index == NOTSET && subVals == null;
+    }
+
+    public String get(String key)
+    {
+      return subVals == null ? null : subVals.get(key);
+    }
+
+    public boolean has(String key)
+    {
+      return subVals == null ? false : subVals.containsKey(key);
+    }
+
+    public int getIndex()
+    {
+      return index;
+    }
+
+    public String getContent()
+    {
+      return content;
+    }
+  }
+
+  private static final Collection<String> bootstrapArgs = new ArrayList(
+          Arrays.asList("props", "debug"));
+
+  public static Map<String, String> bootstrapArgs(String[] args)
+  {
+    Map<String, String> argMap = new HashMap<>();
+    if (args == null)
+      return argMap;
+    Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
+    while (argE.hasMoreElements())
+    {
+      String arg = argE.nextElement();
+      String argName = null;
+      String val = null;
+      if (arg.startsWith("--"))
+      {
+        int equalPos = arg.indexOf('=');
+        if (equalPos > -1)
+        {
+          argName = arg.substring(2, equalPos);
+          val = arg.substring(equalPos + 1);
+        }
+        else
+        {
+          argName = arg.substring(2);
+        }
+        if (bootstrapArgs.contains(argName))
+          argMap.put(argName, val);
+      }
     }
+    return argMap;
   }
 }
\ No newline at end of file
index a75a27f..698cbb8 100755 (executable)
@@ -24,7 +24,9 @@ import java.awt.Color;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
@@ -34,7 +36,9 @@ import java.net.PasswordAuthentication;
 import java.net.URL;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
@@ -320,6 +324,8 @@ public class Cache
   /** Default file is ~/.jalview_properties */
   static String propertiesFile;
 
+  private static final String fallbackPropertiesFile = ".jalview_properties";
+
   private static boolean propsAreReadOnly = Platform.isJS();
 
   public static boolean isPropsAreReadOnly()
@@ -347,7 +353,7 @@ public class Cache
     {
       String channelPrefsFilename = ChannelProperties
               .getProperty("preferences.filename");
-      String releasePrefsFilename = ".jalview_properties";
+      String releasePrefsFilename = fallbackPropertiesFile;
       propertiesFile = System.getProperty("user.home") + File.separatorChar
               + channelPrefsFilename;
       releasePropertiesFile = System.getProperty("user.home")
@@ -1614,4 +1620,61 @@ public class Cache
     String appbase = getGetdownAppbase();
     return appbase + "/" + getdownDistDir + "/build_properties";
   }
+
+  private static final Collection<String> bootstrapProperties = new ArrayList<>(
+          Arrays.asList(JALVIEWLOGLEVEL));
+
+  public static Properties bootstrapProperties(String filename)
+  {
+    Properties bootstrapProps = new Properties();
+    File file = null;
+    if (filename != null)
+    {
+      file = new File(filename);
+    }
+    if (file == null || !file.exists())
+    {
+      String channelPrefsFilename = ChannelProperties
+              .getProperty("preferences.filename");
+      String propertiesFilename = System.getProperty("user.home")
+              + File.separatorChar + channelPrefsFilename;
+      file = new File(propertiesFilename);
+    }
+    if (file == null || !file.exists())
+    {
+      String releasePrefsFilename = fallbackPropertiesFile;
+      String releasePropertiesFilename = System.getProperty("user.home")
+              + File.separatorChar + releasePrefsFilename;
+      file = new File(releasePropertiesFilename);
+    }
+
+    if (filename == null || !file.exists())
+    {
+      System.err.println("Could not load bootstrap preferences file '"
+              + filename + "'");
+      return null;
+    }
+
+    try
+    {
+      FileInputStream in = new FileInputStream(file.getAbsoluteFile());
+      Properties props = new Properties();
+      props.load(in);
+      for (String prop : bootstrapProperties)
+      {
+        if (props.containsKey(prop))
+          bootstrapProps.put(prop, props.getProperty(prop));
+      }
+    } catch (FileNotFoundException e)
+    {
+      System.err.println("Could not find bootstrap preferences file '"
+              + file.getAbsolutePath() + "'");
+    } catch (IOException e)
+    {
+      System.err.println(
+              "IOException when loading bootstrap preferences file '"
+                      + file.getAbsolutePath() + "'");
+    }
+    return bootstrapProps;
+  }
 }
index faf2c9c..a0a3e59 100644 (file)
@@ -14,8 +14,9 @@ import java.util.Map;
 import jalview.analysis.AlignmentUtils;
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.ArgParser.Arg;
+import jalview.bin.ArgParser.ArgValue;
 import jalview.bin.ArgParser.ArgValues;
-import jalview.bin.ArgParser.SubVal;
+import jalview.bin.ArgParser.SubVals;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
@@ -133,8 +134,9 @@ public class Commands
 
       boolean first = true;
       AlignFrame af;
-      for (String openFile : ArgParser.getValues(m, Arg.OPEN))
+      for (ArgValue av : ArgParser.getArgValueList(m, Arg.OPEN))
       {
+        String openFile = av.getValue();
         if (openFile == null)
           continue;
 
@@ -219,8 +221,9 @@ public class Commands
           {
             try
             {
-              tempfacType = StructureImportSettings.TFType.valueOf(ArgParser
-                      .getValue(m, Arg.TEMPFAC).toUpperCase(Locale.ROOT));
+              tempfacType = StructureImportSettings.TFType
+                      .valueOf(ArgParser.getArgValue(m, Arg.TEMPFAC)
+                              .getValue().toUpperCase(Locale.ROOT));
               Console.debug("Obtained Temperature Factor type of '"
                       + tempfacType + "'");
             } catch (IllegalArgumentException e)
@@ -362,9 +365,10 @@ public class Commands
       AlignFrame af = afMap.get(id);
       if (ArgParser.getArgValues(m, Arg.STRUCTURE) != null)
       {
-        for (String val : ArgParser.getValues(m, Arg.STRUCTURE))
+        for (ArgValue av : ArgParser.getArgValueList(m, Arg.STRUCTURE))
         {
-          SubVal subId = new SubVal(val);
+          String val = av.getValue();
+          SubVals subId = new SubVals(val);
           SequenceI seq = getSpecifiedSequence(af, subId);
           if (seq == null)
           {
@@ -375,9 +379,10 @@ public class Commands
             continue;
           }
           File structureFile = null;
-          if (subId.content != null && subId.content.length() != 0)
+          if (subId.getContent() != null
+                  && subId.getContent().length() != 0)
           {
-            structureFile = new File(subId.content);
+            structureFile = new File(subId.getContent());
             Console.debug("Using structure file (from argument) '"
                     + structureFile.getAbsolutePath() + "'");
           }
@@ -416,15 +421,16 @@ public class Commands
     }
 
     // load a pAE file if given
-    if (ArgParser.getValues(m, Arg.PAEMATRIX) != null)
+    if (ArgParser.getArgValueList(m, Arg.PAEMATRIX) != null)
     {
       AlignFrame af = afMap.get(id);
       if (af != null)
       {
-        for (String val : ArgParser.getValues(m, Arg.PAEMATRIX))
+        for (ArgValue av : ArgParser.getArgValueList(m, Arg.PAEMATRIX))
         {
-          SubVal subVal = ArgParser.getSubVal(val);
-          File paeFile = new File(subVal.content);
+          String val = av.getValue();
+          SubVals subVals = ArgParser.getSubVals(val);
+          File paeFile = new File(subVals.getContent());
           String paePath = null;
           try
           {
@@ -435,36 +441,34 @@ public class Commands
             Console.warn(
                     "Problem with the PAE file path: '" + paePath + "'");
           }
-          String structId = "structid".equals(subVal.keyName)
-                  ? subVal.keyValue
-                  : null;
-          if (subVal.notSet())
+          String structId = subVals.get("structid");
+          if (subVals.notSet())
           {
             // take structid from pdbfilename
           }
-          if ("structfile".equals(subVal.keyName))
+          if (subVals.has("structfile"))
           {
             Console.info("***** Attaching paeFile '" + paePath + "' to "
-                    + subVal.keyName + "=" + subVal.keyValue);
+                    + "structfile=" + subVals.get("structfile"));
             EBIAlfaFold.addAlphaFoldPAEToStructure(
                     af.getCurrentView().getAlignment(), paeFile,
-                    subVal.index, subVal.keyValue, false);
+                    subVals.getIndex(), subVals.get("structfile"), false);
           }
-          else if ("structid".equals(subVal.keyName))
+          else if (subVals.has("structid"))
           {
             Console.info("***** Attaching paeFile '" + paePath + "' to "
-                    + subVal.keyName + "=" + subVal.keyValue);
+                    + "structid=" + subVals.get("structid"));
             EBIAlfaFold.addAlphaFoldPAEToStructure(
                     af.getCurrentView().getAlignment(), paeFile,
-                    subVal.index, subVal.keyValue, true);
+                    subVals.getIndex(), subVals.get("structid"), true);
           }
           else
           {
             Console.debug("***** Attaching paeFile '" + paePath
-                    + "' to sequence index " + subVal.index);
+                    + "' to sequence index " + subVals.getIndex());
             EBIAlfaFold.addAlphaFoldPAEToSequence(
                     af.getCurrentView().getAlignment(), paeFile,
-                    subVal.index, null);
+                    subVals.getIndex(), null);
             // required to readjust the height and position of the pAE
             // annotation
           }
@@ -503,17 +507,18 @@ public class Commands
       return;
     }
 
-    if (ArgParser.getValues(m, Arg.IMAGE) != null)
+    if (ArgParser.getArgValueList(m, Arg.IMAGE) != null)
     {
-      for (String val : ArgParser.getValues(m, Arg.IMAGE))
+      for (ArgValue av : ArgParser.getArgValueList(m, Arg.IMAGE))
       {
-        SubVal subVal = new SubVal(val);
+        String val = av.getValue();
+        SubVals subVal = new SubVals(val);
         String type = "png"; // default
-        String fileName = subVal.content;
+        String fileName = subVal.getContent();
         File file = new File(fileName);
-        if ("type".equals(subVal.keyName))
+        if (subVal.has("type"))
         {
-          type = subVal.keyValue;
+          type = subVal.get("type");
         }
         else if (fileName != null)
         {
@@ -552,16 +557,17 @@ public class Commands
     }
   }
 
-  private SequenceI getSpecifiedSequence(AlignFrame af, SubVal subId)
+  private SequenceI getSpecifiedSequence(AlignFrame af, SubVals subId)
   {
     AlignmentI al = af.getCurrentView().getAlignment();
-    if (-1 < subId.index && subId.index < al.getSequences().size())
+    if (-1 < subId.getIndex()
+            && subId.getIndex() < al.getSequences().size())
     {
-      return al.getSequenceAt(subId.index);
+      return al.getSequenceAt(subId.getIndex());
     }
-    else if ("id".equals(subId.keyName))
+    else if (subId.has("seqid"))
     {
-      return al.findName(subId.keyValue);
+      return al.findName(subId.get("seqid"));
     }
     return null;
   }
index b85a4d2..b868e7b 100644 (file)
@@ -212,29 +212,47 @@ public class Console
 
   public static JLogger.LogLevel getCachedLogLevel(String key)
   {
-    return JLogger.toLevel(Cache.getDefault(key, "INFO"));
+    return getLogLevel(Cache.getDefault(key, "INFO"));
+  }
+
+  public static JLogger.LogLevel getLogLevel(String level)
+  {
+    return JLogger.toLevel(level);
   }
 
   public static boolean initLogger()
   {
+    return initLogger(null);
+  }
+
+  public static boolean initLogger(String providedLogLevel)
+  {
     if (log != null)
     {
       return true;
     }
     try
     {
-      JLogger.LogLevel cachedLevel = getCachedLogLevel();
+      JLogger.LogLevel logLevel = JLogger.LogLevel.INFO;
+
+      if (JLogger.isLevel(providedLogLevel))
+        logLevel = Console.getLogLevel(providedLogLevel);
+      else
+        logLevel = getCachedLogLevel();
+
       if (!Platform.isJS())
       {
-        Log4j.init(cachedLevel);
+        System.err
+                .println("Setting initial log level to " + logLevel.name());
+        Log4j.init(logLevel);
       }
       // log output
       // is laxis used? Does getLogger do anything without a Logger object?
       // Logger laxis = Log4j.getLogger("org.apache.axis", myLevel);
-      JLoggerLog4j.getLogger("org.apache.axis", cachedLevel);
+      JLoggerLog4j.getLogger("org.apache.axis", logLevel);
 
       // The main application logger
-      log = JLoggerLog4j.getLogger(Cache.JALVIEW_LOGGER_NAME, cachedLevel);
+      log = JLoggerLog4j.getLogger(Cache.JALVIEW_LOGGER_NAME, logLevel);
     } catch (NoClassDefFoundError e)
     {
       System.err.println("Could not initialise the logger framework");
index 402235b..71c6cf0 100755 (executable)
@@ -40,6 +40,7 @@ import java.security.Policy;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Vector;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
@@ -278,30 +279,11 @@ public class Jalview
     if (!Platform.isJS())
     {
       System.setSecurityManager(null);
-
-      Runtime.getRuntime().addShutdownHook(new Thread()
-      {
-        public void run()
-        {
-          Console.debug("Running shutdown hook");
-          if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
-          {
-            // Got to here by a SIGTERM signal.
-            // Note we will not actually cancel the quit from here -- it's too
-            // late -- but we can wait for saving files.
-            Console.debug("Checking for saving files");
-            QuitHandler.getQuitResponse(false);
-          }
-          else
-          {
-            Console.debug("Nothing more to do");
-          }
-          Console.debug("Exiting, bye!");
-          // shutdownHook cannot be cancelled, JVM will now halt
-        }
-      });
     }
 
+    // get args needed before proper ArgParser
+    Map<String, String> bootstrapArgs = ArgParser.bootstrapArgs(args);
+
     System.out
             .println("Java version: " + System.getProperty("java.version"));
     System.out.println("Java Home: " + System.getProperty("java.home"));
@@ -330,6 +312,10 @@ public class Jalview
       System.setProperty("flatlaf.uiScale", "1");
     }
 
+    // get bootstrap properties (mainly for the logger level)
+    Properties bootstrapProperties = Cache
+            .bootstrapProperties(bootstrapArgs.get("props"));
+
     // report Jalview version
     Cache.loadBuildProperties(true);
 
@@ -340,7 +326,12 @@ public class Jalview
 
     try
     {
-      Console.initLogger();
+      String logLevel = bootstrapArgs.containsKey("debug") ? "DEBUG" : null;
+      if (logLevel == null && !(bootstrapProperties == null))
+      {
+        logLevel = bootstrapProperties.getProperty(Cache.JALVIEWLOGLEVEL);
+      }
+      Console.initLogger(logLevel);
     } catch (NoClassDefFoundError error)
     {
       error.printStackTrace();
@@ -349,23 +340,41 @@ public class Jalview
       System.exit(0);
     }
 
-    String usrPropsFile = aparser.getValue("props");
-    Cache.loadProperties(usrPropsFile); // must do this
-                                        // before
+    // register SIGTERM listener
+    Runtime.getRuntime().addShutdownHook(new Thread()
+    {
+      public void run()
+      {
+        Console.debug("Running shutdown hook");
+        if (QuitHandler.gotQuitResponse() == QResponse.CANCEL_QUIT)
+        {
+          // Got to here by a SIGTERM signal.
+          // Note we will not actually cancel the quit from here -- it's too
+          // late -- but we can wait for saving files.
+          Console.debug("Checking for saving files");
+          QuitHandler.getQuitResponse(false);
+        }
+        else
+        {
+          Console.debug("Nothing more to do");
+        }
+        Console.debug("Exiting, bye!");
+        // shutdownHook cannot be cancelled, JVM will now halt
+      }
+    });
+
+    String usrPropsFile = bootstrapArgs.containsKey("props")
+            ? bootstrapArgs.get("props")
+            : aparser.getValue("props");
+    Cache.loadProperties(usrPropsFile);
     if (usrPropsFile != null)
     {
       System.out.println(
               "CMD [-props " + usrPropsFile + "] executed successfully!");
     }
 
-    // set log level from cache properties
-    Console.setLogLevel(Cache.getDefault(Cache.JALVIEWLOGLEVEL, "INFO"));
-
     // new ArgParser
-    ArgParser argparser = new ArgParser(args); // do this after
-                                               // Console.initLogger, but TODO
-                                               // want --props before then
-                                               // CATCH22
+    ArgParser argparser = new ArgParser(args);
 
     if (argparser.isSet(Arg.HEADLESS))
       headless = argparser.getBool(Arg.HEADLESS);
index f9859f5..714b0de 100644 (file)
@@ -46,6 +46,16 @@ public abstract class JLogger implements JLoggerI
   protected abstract void loggerLogMessage(LogLevel level, String message,
           Throwable t);
 
+  public static boolean isLevel(String levelString)
+  {
+    for (LogLevel l : LogLevel.values())
+    {
+      if (l.name().equals(levelString))
+        return true;
+    }
+    return false;
+  }
+
   public static LogLevel toLevel(String levelString)
   {
     try