reconciled with applet
[jalview.git] / src / jalview / bin / Cache.java
index 4e4a021..04add1f 100755 (executable)
  */
 package jalview.bin;
 
-import jalview.datamodel.PDBEntry;
-import jalview.gui.UserDefinedColours;
-import jalview.schemes.ColourSchemeLoader;
-import jalview.schemes.ColourSchemes;
-import jalview.schemes.UserColourScheme;
-import jalview.structure.StructureImportSettings;
-import jalview.urls.IdOrgSettings;
-import jalview.util.ColorUtils;
-import jalview.ws.sifts.SiftsSettings;
-
 import java.awt.Color;
+import java.awt.Dimension;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.net.URL;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Collections;
@@ -52,6 +44,18 @@ import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
 import org.apache.log4j.SimpleLayout;
 
+import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
+import jalview.datamodel.PDBEntry;
+import jalview.gui.Preferences;
+import jalview.schemes.ColourSchemeLoader;
+import jalview.schemes.ColourSchemes;
+import jalview.schemes.UserColourScheme;
+import jalview.structure.StructureImportSettings;
+import jalview.urls.IdOrgSettings;
+import jalview.util.ColorUtils;
+import jalview.util.Platform;
+import jalview.ws.sifts.SiftsSettings;
+
 /**
  * Stores and retrieves Jalview Application Properties Lists and fields within
  * list entries are separated by '|' symbols unless otherwise stated (|) clauses
@@ -206,8 +210,26 @@ import org.apache.log4j.SimpleLayout;
  * @author $author$
  * @version $Revision$
  */
-public class Cache
+public class Cache implements ApplicationSingletonI
 {
+
+  private Cache()
+  {
+    // private singleton
+  }
+
+  /**
+   * In Java, this will be a static field instance, which will be
+   * application-specific; in JavaScript it will be an applet-specific instance
+   * tied to the applet's ThreadGroup.
+   * 
+   * @return
+   */
+  public static Cache getInstance()
+  {
+    return (Cache) ApplicationSingletonProvider.getInstance(Cache.class);
+  }
+
   /**
    * property giving log4j level for CASTOR loggers
    */
@@ -264,7 +286,9 @@ public class Cache
   public static Logger log;
 
   /** Jalview Properties */
-  public static Properties applicationProperties = new Properties()
+  // BH 2019.05.08 was static
+  @SuppressWarnings("serial")
+  public Properties applicationProperties = new Properties()
   {
     // override results in properties output in alphabetical order
     @Override
@@ -275,9 +299,12 @@ public class Cache
   };
 
   /** Default file is ~/.jalview_properties */
-  static String propertiesFile;
+  // BH 2020 private, not static
+  private String propertiesFile;
 
-  private static boolean propsAreReadOnly = false;
+  private static boolean propsAreReadOnly = Platform.isJS();
+
+  private final static String JS_PROPERTY_PREFIX = "jalview_";
 
   public static void initLogger()
   {
@@ -329,50 +356,62 @@ public class Cache
    */
   public static void loadProperties(String propsFile)
   {
+    getInstance().loadPropertiesImpl(propsFile);
+  }
+
+  private void loadPropertiesImpl(String propsFile)
+  {
     propertiesFile = propsFile;
-    if (propsFile == null)
+    if (propsFile == null && !propsAreReadOnly)
     {
       propertiesFile = System.getProperty("user.home") + File.separatorChar
               + ".jalview_properties";
+      }
+      else
+      {
+        // don't corrupt the file we've been given.
+        propsAreReadOnly = true;
+      }
+
+    if (propertiesFile == null)
+    { // BH 2019
+      Platform.readInfoProperties(JS_PROPERTY_PREFIX,
+              applicationProperties);
     }
     else
     {
-      // don't corrupt the file we've been given.
-      propsAreReadOnly = true;
-    }
-
-    try
-    {
-      InputStream fis;
       try
       {
-        fis = new java.net.URL(propertiesFile).openStream();
-        System.out.println(
-                "Loading jalview properties from : " + propertiesFile);
-        System.out.println(
-                "Disabling Jalview writing to user's local properties file.");
-        propsAreReadOnly = true;
+        InputStream fis;
+        try
+        {
+          fis = new java.net.URL(propertiesFile).openStream();
+          System.out.println(
+                  "Loading jalview properties from : " + propertiesFile);
+          System.out.println(
+                  "Disabling Jalview writing to user's local properties file.");
+          propsAreReadOnly = true;
+
+        } catch (Exception ex)
+        {
+          fis = null;
+        }
+        if (fis == null)
+        {
+          fis = new FileInputStream(propertiesFile);
+        }
+        applicationProperties.clear();
+        applicationProperties.load(fis);
 
+        // remove any old build properties
+
+        deleteBuildProperties();
+        fis.close();
       } catch (Exception ex)
       {
-        fis = null;
-      }
-      if (fis == null)
-      {
-        fis = new FileInputStream(propertiesFile);
+        System.out.println("Error reading properties file: " + ex);
       }
-      applicationProperties.clear();
-      applicationProperties.load(fis);
-
-      // remove any old build properties
-
-      deleteBuildProperties();
-      fis.close();
-    } catch (Exception ex)
-    {
-      System.out.println("Error reading properties file: " + ex);
     }
-
     if (getDefault("USE_PROXY", false))
     {
       String proxyServer = getDefault("PROXY_SERVER", ""),
@@ -386,20 +425,25 @@ public class Cache
     }
 
     // LOAD THE AUTHORS FROM THE authors.props file
+    String authorDetails = (Platform.isJS() ? null
+            : resolveResourceURLFor("/authors.props"));
+
     try
     {
-      String authorDetails = "jar:"
-              .concat(Cache.class.getProtectionDomain().getCodeSource()
-                      .getLocation().toString().concat("!/authors.props"));
-
-      java.net.URL localJarFileURL = new java.net.URL(authorDetails);
-
-      InputStream in = localJarFileURL.openStream();
-      applicationProperties.load(in);
-      in.close();
+      if (authorDetails != null)
+      {
+        URL localJarFileURL = new URL(authorDetails);
+        InputStream in = localJarFileURL.openStream();
+        applicationProperties.load(in);
+        in.close();
+      }
     } catch (Exception ex)
     {
       System.out.println("Error reading author details: " + ex);
+      authorDetails = null;
+    }
+    if (authorDetails == null)
+    {
       applicationProperties.remove("AUTHORS");
       applicationProperties.remove("AUTHORFNAMES");
       applicationProperties.remove("YEAR");
@@ -421,12 +465,12 @@ public class Cache
             jalview.bin.Cache.getDefault("sifts_cache_threshold_in_days",
                     DEFAULT_CACHE_THRESHOLD_IN_DAYS));
 
-    IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL",
+    IdOrgSettings.setUrl(getDefault(Preferences.ID_ORG_HOSTURL,
             "http://www.jalview.org/services/identifiers"));
     IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
 
     StructureImportSettings.setDefaultStructureFileFormat(jalview.bin.Cache
-            .getDefault("PDB_DOWNLOAD_FORMAT", PDB_DOWNLOAD_FORMAT));
+            .getDefault(Preferences.PDB_DOWNLOAD_FORMAT, PDB_DOWNLOAD_FORMAT));
     StructureImportSettings
             .setDefaultPDBFileParser(DEFAULT_PDB_FILE_PARSER);
     // StructureImportSettings
@@ -443,8 +487,7 @@ public class Cache
             && (System.getProperty("java.awt.headless") == null || System
                     .getProperty("java.awt.headless").equals("false")))
     {
-
-      class VersionChecker extends Thread
+      new Thread()
       {
         @Override
         public void run()
@@ -492,10 +535,7 @@ public class Cache
 
           setProperty("LATEST_VERSION", remoteVersion);
         }
-      }
-
-      VersionChecker vc = new VersionChecker();
-      vc.start();
+      }.start();
     }
     else
     {
@@ -510,33 +550,60 @@ public class Cache
     }
 
     // LOAD USERDEFINED COLOURS
-    jalview.bin.Cache
-            .initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
+    Cache.initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
     jalview.io.PIRFile.useModellerOutput = Cache.getDefault("PIR_MODELLER",
             false);
   }
 
-  public static void loadBuildProperties(boolean reportVersion)
-  {
-    String codeInstallation = getProperty("INSTALLATION");
-    boolean printV = codeInstallation == null;
-
-    // FIND THE VERSION NUMBER AND BUILD DATE FROM jalview.jar
-    try
+    /**
+     * construct a resource URL for the given absolute resource pathname
+     * 
+     * @param resourcePath
+     * @return
+     */
+    private static String resolveResourceURLFor(String resourcePath)
     {
-      String buildDetails = "jar:".concat(Cache.class.getProtectionDomain()
-              .getCodeSource().getLocation().toString()
-              .concat("!/.build_properties"));
-
-      java.net.URL localJarFileURL = new java.net.URL(buildDetails);
+      String url = null;
+      if (Platform.isJS() || !Cache.class.getProtectionDomain()
+              .getCodeSource().getLocation().toString().endsWith(".jar"))
+      {
+        try
+        {
+          url = Cache.class.getResource(resourcePath).toString();
+        } catch (Exception ex)
+        {
+          System.err.println("Failed to resolve resource " + resourcePath + ": "
+                  + ex.getMessage());
+        }
+      }
+      else
+      {
+        url = "jar:".concat(Cache.class.getProtectionDomain().getCodeSource()
+                .getLocation().toString().concat("!" + resourcePath));
+      }
+      return url;
+    }
 
-      InputStream in = localJarFileURL.openStream();
-      applicationProperties.load(in);
-      in.close();
-    } catch (Exception ex)
+    public static void loadBuildProperties(boolean reportVersion)
     {
-      System.out.println("Error reading build details: " + ex);
-      applicationProperties.remove("VERSION");
+      String codeInstallation = getProperty("INSTALLATION");
+      boolean printVersion = codeInstallation == null;
+
+      /*
+       * read build properties - from the Jalview jar for a Java distribution,
+       * or from codebase file in test or JalviewJS context
+       */
+      try
+      {
+      String buildDetails = resolveResourceURLFor("/.build_properties");
+      URL localJarFileURL = new URL(buildDetails);
+        InputStream in = localJarFileURL.openStream();
+        getInstance().applicationProperties.load(in);
+        in.close();
+      } catch (Exception ex)
+      {
+        System.out.println("Error reading build details: " + ex);
+        getInstance().applicationProperties.remove("VERSION");
     }
     String codeVersion = getProperty("VERSION");
     codeInstallation = getProperty("INSTALLATION");
@@ -553,14 +620,15 @@ public class Cache
     }
     setProperty("VERSION", codeVersion);
     new BuildDetails(codeVersion, null, codeInstallation);
-    if (printV && reportVersion)
+    if (printVersion && reportVersion)
     {
       System.out
             .println("Jalview Version: " + codeVersion + codeInstallation);
     }
   }
 
-  private static void deleteBuildProperties()
+  // BH 2020 was static  
+  private void deleteBuildProperties()
   {
     applicationProperties.remove("LATEST_VERSION");
     applicationProperties.remove("VERSION");
@@ -582,25 +650,47 @@ public class Cache
    */
   public static String getProperty(String key)
   {
-    return applicationProperties.getProperty(key);
+    return getInstance().applicationProperties.getProperty(key);
+
+// old, original idea:
+//    String prop = applicationProperties.getProperty(key);
+//    if (prop == null && Platform.isJS())
+//    {
+//      prop = applicationProperties.getProperty(Platform.getUniqueAppletID()
+//              + "_" + JS_PROPERTY_PREFIX + key);
+//    }
+//    return prop;
+
   }
 
+  
+  //These methods are used when checking if the saved preference is different
+  // to the default setting 
+  
   /**
-   * These methods are used when checking if the saved preference is different
-   * to the default setting
+   * Returns the boolean property value for the given property name. If the
+   * value is absent then the default value is returned instead.
+   * 
+   * @param property
+   * @param def
+   * @return
    */
-
   public static boolean getDefault(String property, boolean def)
   {
     String string = getProperty(property);
-    if (string != null)
-    {
-      def = Boolean.valueOf(string).booleanValue();
-    }
-
-    return def;
+    // BH simplification only    
+    return string == null ? def : Boolean.parseBoolean(string);
   }
 
+  /**
+   * Returns the integer property value for the given property name. If the
+   * value is absent, or malformed (not an integer), then the default value is
+   * returned instead.
+   * 
+   * @param property
+   * @param def
+   * @return
+   */
   public static int getDefault(String property, int def)
   {
     String string = getProperty(property);
@@ -608,7 +698,9 @@ public class Cache
     {
       try
       {
-        def = Integer.parseInt(string);
+          // BH simplification only
+          
+        return Integer.parseInt(string);
       } catch (NumberFormatException e)
       {
         System.out.println("Error parsing int property '" + property
@@ -620,18 +712,17 @@ public class Cache
   }
 
   /**
-   * These methods are used when checking if the saved preference is different
-   * to the default setting
+   * Returns the value of the given property, or the supplied default value if
+   * the property is not set
+   * 
+   * @param property
+   * @param def
+   * @return
    */
   public static String getDefault(String property, String def)
   {
-    String string = getProperty(property);
-    if (string != null)
-    {
-      return string;
-    }
-
-    return def;
+    String value = getProperty(property);
+    return value == null ? def : value;
   }
 
   /**
@@ -646,11 +737,44 @@ public class Cache
    */
   public static Object setProperty(String key, String obj)
   {
+    return getInstance().setPropertyImpl(key, obj, true);
+  }
+
+  /**
+   * 
+   * Sets a property value for the running application, without saving it to the
+   * properties file. 
+   * 
+   * Replaces direct call to Cache.applicationProperties.setProperty
+   * 
+   * Used extensively in AppletParams and Preferences.ok_actionPerformed
+   * 
+   * 
+   * @param key
+   * @param obj
+   */
+  public static void setPropertyNoSave(String key, String obj)
+  {
+    getInstance().setPropertyImpl(key, obj, false);
+  }
+
+
+  /**
+   * Sets a property value, and optionally also saves the current properties to
+   * file
+   * 
+   * @param key
+   * @param obj
+   * @param andSave
+   * @return
+   */
+  private Object setPropertyImpl(String key, String obj, boolean andSave)
+  {
     Object oldValue = null;
     try
     {
       oldValue = applicationProperties.setProperty(key, obj);
-      if (propertiesFile != null && !propsAreReadOnly)
+      if (andSave && !propsAreReadOnly && propertiesFile != null)
       {
         FileOutputStream out = new FileOutputStream(propertiesFile);
         applicationProperties.store(out, "---JalviewX Properties File---");
@@ -665,21 +789,58 @@ public class Cache
   }
 
   /**
-   * remove the specified property from the jalview properties file
+   * Removes the specified property from the jalview properties file
    * 
-   * @param string
+   * @param key
    */
-  public static void removeProperty(String string)
+  public static void removeProperty(String key)
   {
-    applicationProperties.remove(string);
-    saveProperties();
+    getInstance().removePropertyImpl(key, true);
   }
 
   /**
-   * save the properties to the jalview properties path
+   * Removes the named property for the running application, without saving the
+   * properties file.
+   * 
+   * Called by Preferences.ok_actionPerformed specifically for SEQUENCE_LINKS and STORED_LINKS. 
+   * 
+   * 
+   * @param key
+   */
+  public static void removePropertyNoSave(String key)
+  {
+    getInstance().removePropertyImpl(key, false);
+  }
+
+  /**
+   * Removes the named property, and optionally saves the current properties to
+   * file
+   * 
+   * @param key
+   * @param andSave
+   */
+  private void removePropertyImpl(String key, boolean andSave)
+  {
+    applicationProperties.remove(key);
+    if (andSave)
+    {
+      savePropertiesImpl();
+    }
+  }
+
+  /**
+   * Saves the current properties to file
    */
   public static void saveProperties()
   {
+    getInstance().savePropertiesImpl();
+  }
+
+  /**
+   * save the properties to the jalview properties path
+   */
+  private void savePropertiesImpl()
+  {
     if (!propsAreReadOnly)
     {
       try
@@ -694,10 +855,17 @@ public class Cache
     }
   }
 
+  private final static int UNTESTED = -1;
+
+  private final static int TRUE = 1;
+
+  private final static int FALSE = 0;
+
   /**
    * internal vamsas class discovery state
    */
-  private static int vamsasJarsArePresent = -1;
+  private static int vamsasJarsArePresent = (Platform.isJS() ? FALSE
+          : UNTESTED);
 
   /**
    * Searches for vamsas client classes on class path.
@@ -706,7 +874,7 @@ public class Cache
    */
   public static boolean vamsasJarsPresent()
   {
-    if (vamsasJarsArePresent == -1)
+    if (vamsasJarsArePresent == UNTESTED)
     {
       try
       {
@@ -715,7 +883,7 @@ public class Cache
         {
           jalview.bin.Cache.log.debug(
                   "Found Vamsas Classes (uk.ac..vamsas.client.VorbaId can be loaded)");
-          vamsasJarsArePresent = 1;
+          vamsasJarsArePresent = TRUE;
           Logger lvclient = Logger.getLogger("uk.ac.vamsas");
           lvclient.setLevel(Level.toLevel(Cache
                   .getDefault("logs.Vamsas.Level", Level.INFO.toString())));
@@ -726,17 +894,18 @@ public class Cache
         }
       } catch (Exception e)
       {
-        vamsasJarsArePresent = 0;
+        vamsasJarsArePresent = FALSE;
         jalview.bin.Cache.log.debug("Vamsas Classes are not present");
       }
     }
-    return (vamsasJarsArePresent > 0);
+    return (vamsasJarsArePresent == TRUE);
   }
 
   /**
    * internal vamsas class discovery state
    */
-  private static int groovyJarsArePresent = -1;
+  private static int groovyJarsArePresent = (Platform.isJS() ? FALSE
+          : UNTESTED);
 
   /**
    * Searches for vamsas client classes on class path.
@@ -745,7 +914,7 @@ public class Cache
    */
   public static boolean groovyJarsPresent()
   {
-    if (groovyJarsArePresent == -1)
+    if (groovyJarsArePresent == UNTESTED)
     {
       try
       {
@@ -754,7 +923,7 @@ public class Cache
         {
           jalview.bin.Cache.log.debug(
                   "Found Groovy (groovy.lang.GroovyObject can be loaded)");
-          groovyJarsArePresent = 1;
+          groovyJarsArePresent = TRUE;
           Logger lgclient = Logger.getLogger("groovy");
           lgclient.setLevel(Level.toLevel(Cache
                   .getDefault("logs.Groovy.Level", Level.INFO.toString())));
@@ -765,15 +934,15 @@ public class Cache
         }
       } catch (Error e)
       {
-        groovyJarsArePresent = 0;
+        groovyJarsArePresent = FALSE;
         jalview.bin.Cache.log.debug("Groovy Classes are not present", e);
       } catch (Exception e)
       {
-        groovyJarsArePresent = 0;
+        groovyJarsArePresent = FALSE;
         jalview.bin.Cache.log.debug("Groovy Classes are not present");
       }
     }
-    return (groovyJarsArePresent > 0);
+    return (groovyJarsArePresent == TRUE);
   }
 
   /**
@@ -782,15 +951,18 @@ public class Cache
    */
   protected static Object tracker = null;
 
-  protected static Class trackerfocus = null;
+  protected static Class<?> trackerfocus = null;
 
-  protected static Class jgoogleanalyticstracker = null;
+  protected static Class<?> jgoogleanalyticstracker = null;
 
   /**
    * Initialise the google tracker if it is not done already.
    */
   public static void initGoogleTracker()
   {
+
+    // TODO: SwingJS JavaScript tracker?
+
     if (tracker == null)
     {
       if (jgoogleanalyticstracker == null)
@@ -903,7 +1075,10 @@ public class Cache
   }
 
   /**
-   * get the user's default colour if available
+   * Returns the Color value of the given property's value, or the supplied
+   * default colour if no property is found, or it cannot be parsed to a colour.
+   * Colours are normally saved as hex rgb values, though other formats may be
+   * parseable.
    * 
    * @param property
    * @param defcolour
@@ -926,6 +1101,22 @@ public class Cache
   }
 
   /**
+   * Stores a colour as a Jalview property, converted to hex values for rgb.
+   * Properties are not saved to file.
+   * 
+   * 
+   * 
+   * Used extensively in Preferences.ok_actionPerformed
+   * 
+   * @param property
+   * @param colour
+   */
+  public static void setColourPropertyNoSave(String property, Color colour)
+  {
+    setPropertyNoSave(property, jalview.util.Format.getHexString(colour));
+  }
+
+  /**
    * store a colour as a Jalview user default property
    * 
    * @param property
@@ -938,6 +1129,7 @@ public class Cache
 
   /**
    * Stores a formatted date in a jalview property, using a fixed locale.
+   * Updated properties are written out to the properties file.
    * 
    * @param propertyName
    * @param date
@@ -1014,11 +1206,11 @@ public class Cache
     }
     if (value == null || value.trim().length() < 1)
     {
-      Cache.applicationProperties.remove(propName);
+      getInstance().applicationProperties.remove(propName);
     }
     else
     {
-      Cache.applicationProperties.setProperty(propName, value);
+      getInstance().applicationProperties.setProperty(propName, value);
     }
   }
 
@@ -1063,18 +1255,38 @@ public class Cache
     {
       if (coloursFound.toString().length() > 1)
       {
-        setProperty(UserDefinedColours.USER_DEFINED_COLOURS,
+        // BH moved to Preferences for consistency
+        setProperty(Preferences.USER_DEFINED_COLOURS,
                 coloursFound.toString());
       }
       else
       {
-        applicationProperties
-                .remove(UserDefinedColours.USER_DEFINED_COLOURS);
+        // BH moved to Preferences for consistency
+        getInstance().applicationProperties
+                .remove(Preferences.USER_DEFINED_COLOURS);
       }
     }
   }
 
   /**
+   * Initial logging information helper for various versions output
+   * 
+   * @param prefix
+   * @param value
+   * @param defaultValue
+   */
+  private static void appendIfNotNull(StringBuilder sb, String prefix,
+          String value, String suffix, String defaultValue)
+  {
+    if (value == null && defaultValue == null)
+    {
+      return;
+    }
+    String line = prefix + (value != null ? value : defaultValue) + suffix;
+    sb.append(line);
+  }
+
+  /**
    * 
    * @return Jalview version, build details and JVM platform version for console
    */
@@ -1092,18 +1304,17 @@ public class Cache
     sb.append("\n");
     sb.append(System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version"));
     sb.append("\n");
-    System.out.println("Install4j version: "
-            + System.getProperty("sys.install4jVersion"));
-    System.out.println("Install4j template version: "
-            + System.getProperty("installer_template_version"));
-    System.out.println(
-            "Launcher version: " + System.getProperty("launcher_version"));
-
+    appendIfNotNull(sb, "Install4j version: ",
+            System.getProperty("sys.install4jVersion"), "\n", null);
+    appendIfNotNull(sb, "Install4j template version: ",
+            System.getProperty("installer_template_version"), "\n", null);
+    appendIfNotNull(sb, "Launcher version: ",
+            System.getProperty("launcher_version"), "\n", null);
     if (jalview.bin.Cache.getDefault("VERSION", "TEST").equals("DEVELOPMENT")) {
-      sb.append("Getdown appdir: " + System.getProperty("getdownappdir"));
-      sb.append("\n");
-      sb.append("Java home: " + System.getProperty("java.home"));
-      sb.append("\n");
+      appendIfNotNull(sb, "Getdown appdir: ",
+              System.getProperty("getdownappdir"), "\n", null);
+      appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
+              "\n", "unknown");
     }
     return sb.toString();
   }
@@ -1118,4 +1329,40 @@ public class Cache
     // eg 'built from Source' or update channel
     return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
   }
+
+  /**
+   * retrieve a dimension, such as for Jmol
+   * 
+   * @param property
+   * @param def
+   * @return
+   */
+  public static Dimension getDefaultDim(String property, Dimension def)
+  {
+    String s = getProperty(property);
+    if (s != null)
+    {
+      if (s.indexOf(',') < 0)
+      {
+        s = s.trim().replace(' ', ',');
+        if (s.indexOf(',') < 0)
+        {
+          s += "," + s;
+        }
+      }
+      try
+      {
+        int pt = s.indexOf(",");
+        return new Dimension(Integer.parseInt(s.substring(0, pt)),
+                Integer.parseInt(s.substring(pt + 1)));
+      } catch (NumberFormatException e)
+      {
+        System.out.println("Error parsing Dimension property '" + property
+                + "' with value '" + s + "'");
+      }
+    }
+    return def;
+  }
+
+
 }