You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by bu...@apache.org on 2012/04/26 16:20:38 UTC

svn commit: r1330865 - in /uima/uimaj/trunk/uimaj-core/src: main/java/org/apache/uima/ main/java/org/apache/uima/impl/ main/java/org/apache/uima/resource/impl/ main/java/org/apache/uima/resource/metadata/impl/ main/java/org/apache/uima/util/ main/java/...

Author: burn
Date: Thu Apr 26 14:20:37 2012
New Revision: 1330865

URL: http://svn.apache.org/viewvc?rev=1330865&view=rev
Log:
[UIMA-2378] Add interface Settings and move the lookup method to Settings_impl so it can be used outside UIMA annotators.
 

Added:
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/Settings.java   (with props)
Modified:
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/UimaContext.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/impl/UimaContext_ImplBase.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurationManager_impl.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/ExternalOverrideSettings_impl.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java
    uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/TestAnnotator2.java

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/UimaContext.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/UimaContext.java?rev=1330865&r1=1330864&r2=1330865&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/UimaContext.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/UimaContext.java Thu Apr 26 14:20:37 2012
@@ -27,6 +27,7 @@ import org.apache.uima.cas.AbstractCas;
 import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.SofaID;
 import org.apache.uima.resource.ResourceAccessException;
+import org.apache.uima.resource.ResourceConfigurationException;
 import org.apache.uima.resource.Session;
 import org.apache.uima.util.InstrumentationFacility;
 import org.apache.uima.util.Logger;
@@ -125,8 +126,9 @@ public interface UimaContext {
    * @return the value of the parameter with the given name, as a String.
    *         Returns <code>null</code> if the parameter does not exist.
    *         Returns an empty string (<code>""</code>) if the parameter was not assigned a value.
+   * @throws ResourceConfigurationException if the parameter refers to an undefined variable
    */
-  public String getExternalParameterValue(String aParamName);
+  public String getExternalParameterValue(String aParamName) throws ResourceConfigurationException;
   
   /**
    * Gets the <code>Logger</code> to which log output will be sent. UIMA components should use

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/impl/UimaContext_ImplBase.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/impl/UimaContext_ImplBase.java?rev=1330865&r1=1330864&r2=1330865&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/impl/UimaContext_ImplBase.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/impl/UimaContext_ImplBase.java Thu Apr 26 14:20:37 2012
@@ -51,6 +51,7 @@ import org.apache.uima.cas.impl.CASImpl;
 import org.apache.uima.jcas.JCas;
 import org.apache.uima.resource.CasManager;
 import org.apache.uima.resource.ResourceAccessException;
+import org.apache.uima.resource.ResourceConfigurationException;
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.impl.ConfigurationManager_impl;
 import org.apache.uima.resource.metadata.ConfigurationGroup;
@@ -478,8 +479,9 @@ public abstract class UimaContext_ImplBa
   /**
    * Lookup and evaluate an arbitrary (string) parameter from the External Override Settings
    * Lets annotator configuration bypass the descriptor parameters. 
+   * @throws ResourceConfigurationException 
    */
-  public String getExternalParameterValue(String name) {
+  public String getExternalParameterValue(String name) throws ResourceConfigurationException {
     ConfigurationManager_impl cfgmgr = (ConfigurationManager_impl) getConfigurationManager();
     return cfgmgr.getExternalParameter(mQualifiedContextName, name);
   }

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurationManager_impl.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurationManager_impl.java?rev=1330865&r1=1330864&r2=1330865&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurationManager_impl.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurationManager_impl.java Thu Apr 26 14:20:37 2012
@@ -322,14 +322,11 @@ public class ConfigurationManager_impl e
    * @param context - UIMA Context
    * @param name    - variable to look up
    * @return        - value of variable OR an exception message if definition is invalid
+   * @throws ResourceConfigurationException 
    */
-  public String getExternalParameter(String context, String name) {
+  public String getExternalParameter(String context, String name) throws ResourceConfigurationException {
     ExternalOverrideSettings settings = getExternalOverrideSettings(context);
-    try {
-      String value = settings == null ? null : settings.resolveExternalName(name);
-      return value == null ? null : escape(value);
-    } catch (ResourceConfigurationException e) {
-      return "*ERROR* " + e.getMessage();
-    }
+    String value = settings == null ? null : settings.resolveExternalName(name);
+    return value == null ? null : escape(value);
   }
 }

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/ExternalOverrideSettings_impl.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/ExternalOverrideSettings_impl.java?rev=1330865&r1=1330864&r2=1330865&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/ExternalOverrideSettings_impl.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/ExternalOverrideSettings_impl.java Thu Apr 26 14:20:37 2012
@@ -9,8 +9,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.Arrays;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.apache.uima.UIMAFramework;
 import org.apache.uima.resource.ResourceConfigurationException;
@@ -48,11 +46,6 @@ public class ExternalOverrideSettings_im
    */
   private boolean multipleEntries = false;
 
-  /*
-   * Regex that matches ${...}
-   * non-greedy so stops on first '}' -- hence key cannot contain '}'
-   */
-  private Pattern evalPattern = Pattern.compile("\\$\\{.*?\\}");
 
   /* (non-Javadoc)
    * @see org.apache.uima.resource.metadata.ExternalOverrideSettings#getImport()
@@ -94,49 +87,6 @@ public class ExternalOverrideSettings_im
     mImports = aImports;
   }
 
-  /* 
-   * Look up value for external name from the external override settings.
-   * Perform one substitution pass on ${key} substrings. If key is undefined throw an exception.
-   * Recursively evaluate the value to be substituted.  NOTE: infinite loops not detected!
-   * To avoid evaluation and get ${key} in the output use a property to generate the $, e.g. 
-   *   $   = $
-   *   key = ${$}{key}
-   * or escape the $
-   *   key = \${key}
-   */
-  public String resolveExternalName(String name) throws ResourceConfigurationException {
-    String value;
-    if (mProperties == null || (value = mProperties.getProperty(name)) == null) {
-      return null;
-    }
-    Matcher matcher = evalPattern.matcher(value);
-    StringBuilder result = new StringBuilder(value.length() + 100);
-    int lastEnd = 0;
-    while (matcher.find()) {
-      // Check if the $ is escaped
-      if (mProperties.isEscaped(value, matcher.start())) {
-        result.append(value.substring(lastEnd, matcher.start() + 1));
-        lastEnd = matcher.start() + 1; // copy the escaped $ and restart after it
-      } else {
-        result.append(value.substring(lastEnd, matcher.start()));
-        lastEnd = matcher.end();
-        String key = value.substring(matcher.start() + 2, lastEnd - 1);
-        String val = resolveExternalName(key);
-        if (val == null) { // External override variable "{0}" references the undefined variable "{1}"
-          throw new ResourceConfigurationException(ResourceConfigurationException.EXTERNAL_OVERRIDE_INVALID,
-                  new Object[] { name, key });
-        }
-        result.append(val);
-      }
-    }
-    if (lastEnd == 0) {
-      return value;
-    } else {
-      result.append(value.substring(lastEnd));
-      return result.toString();
-    }
-  }
-
   /* (non-Javadoc)
    * @see org.apache.uima.resource.metadata.ExternalOverrideSettings#resolveImports()
    */
@@ -217,6 +167,11 @@ public class ExternalOverrideSettings_im
     }
   }
   
+  @Override
+  public String resolveExternalName(String name) throws ResourceConfigurationException {
+    return mProperties == null ? null : mProperties.lookUp(name);
+  }
+
   protected XmlizationInfo getXmlizationInfo() {
     return XMLIZATION_INFO;
   }

Added: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/Settings.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/Settings.java?rev=1330865&view=auto
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/Settings.java (added)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/Settings.java Thu Apr 26 14:20:37 2012
@@ -0,0 +1,60 @@
+package org.apache.uima.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+import org.apache.uima.resource.ResourceConfigurationException;
+
+/**
+ * A <code>Settings</code> object holds the properties used for external parameter overrides.
+ * 
+ * Similar to java.util.Properties but: 
+ *  - supports UTF-8 (so \\uXXXX escapes are not needed or supported)
+ *  - keys must be valid Java identifiers (actually must not contain '=' ':' '}' or white-space)
+ *  - reverses priority in that duplicate entries are ignored, i.e. once set values cannot be changed
+ *  - multiple files can be loaded
+ *  - values can contain references to other values, e.g. name = .... ${key} ....
+ *  - arrays are represented as strings, e.g. '[elem1,elem2]', and can span multiple lines
+ *  - '\' can be used in values to escape '$' '{' '[' ',' ']' 
+ *   
+ * @author burn
+ * 
+ */
+
+public interface Settings {
+  
+  /**
+   * Load properties from an input stream.  
+   * Existing properties are not changed and a warning is logged if the new value is different.
+   * May be called multiple times, so effective search is in load order.
+   * Arrays are enclosed in [] and the elements may be separated by <code>,</code> or new-line, so 
+   *   can span multiple lines without using a final \ 
+   * 
+   * @param in - Stream holding properties
+   * @throws IOException
+   */
+  public void load(InputStream in) throws IOException;
+
+  /**
+   * Look up the value for a property.
+   * Perform one substitution pass on ${key} substrings replacing them with the value for key.
+   * Recursively evaluate the value to be substituted.  NOTE: infinite loops not detected!
+   * If the key variable has not been defined, an exception is thrown.
+   * To avoid evaluation and get ${key} in the output escape the $ or {
+   * Arrays are returned as a comma-separated string, e.g. "[elem1,elem2]" 
+   * Note: escape characters are not removed as they may affect array separators. 
+   * 
+   * @param name - name to look up
+   * @return     - value of property
+   * @throws ResourceConfigurationException
+   */
+  public String lookUp(String name) throws ResourceConfigurationException;
+
+  /**
+   * Return a set of keys of all properties loaded
+   * 
+   * @return - set of strings
+   */
+  public Set<String> getKeys();
+}

Propchange: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/Settings.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java?rev=1330865&r1=1330864&r2=1330865&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java Thu Apr 26 14:20:37 2012
@@ -7,9 +7,13 @@ import java.io.InputStreamReader;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.uima.UIMAFramework;
+import org.apache.uima.resource.ResourceConfigurationException;
 import org.apache.uima.util.Level;
+import org.apache.uima.util.Settings;
 
 /**
  * Class that reads properties files containing external parameter overrides used by the ExternalOverrideSettings_impl
@@ -25,7 +29,7 @@ import org.apache.uima.util.Level;
  * 
  */
 
-public class Settings_impl {
+public class Settings_impl implements Settings {
 
   protected static final String LOG_RESOURCE_BUNDLE = "org.apache.uima.impl.log_messages";
 
@@ -33,35 +37,40 @@ public class Settings_impl {
 
   private Map<String, String> map;
 
+  /*
+   * Regex that matches ${...}
+   * non-greedy so stops on first '}' -- hence key cannot contain '}'
+   */
+  private Pattern evalPattern = Pattern.compile("\\$\\{.*?\\}");
+
   public Settings_impl() {
     map = new HashMap<String, String>();
   }
 
   /**
-   * Get the value of a property with the specified key
-   */
-  public String getProperty(String key) {
-    return map.get(key);
-  }
-
-  /*
    * Return a set of keys of all properties in the map
+   * 
+   * @return - set of strings
    */
   public Set<String> getKeys() {
     return map.keySet();
   }
 
-  /*
-   * Load properties from an input stream Existing properties are not replaced (unlike java.util.Properties) May be
-   * called multiple times
+  /**
+   * Load properties from an input stream.  
+   * Existing properties are not replaced (unlike java.util.Properties).
+   * May be called multiple times.
+   * 
+   * @param in - Stream holding properties
+   * @throws IOException
    */
   public void load(InputStream in) throws IOException {
     // Process each logical line (after blanks & comments removed and continuations extended)
     rdr = new BufferedReader(new InputStreamReader(in, "UTF-8"));
     String line;
     while ((line = getLine()) != null) {
-      // Split into two -- on '=' or ':' or white-space
-      String[] parts = line.trim().split("\\s*[:=\\s]\\s*", 2);
+      // Remove surrounding white-space and split on first '=' or ':' or white-space
+      String[] parts = line.split("\\s*[:=\\s]\\s*", 2);
       String name = parts[0];
       String value;
       // When RHS is empty get a split only for the := separators
@@ -76,32 +85,80 @@ public class Settings_impl {
       if (!map.containsKey(name)) {
         map.put(name, value);
       } else {
-        // Key {0} already in use ... ignoring value "{1}"
-        UIMAFramework.getLogger(this.getClass()).logrb(Level.WARNING, this.getClass().getName(), "load",
-                LOG_RESOURCE_BUNDLE, "UIMA_external_override_ignored__WARNING", new Object[] { name, value });
+        if (!value.equals(map.get(name))) {
+          // Key {0} already in use ... ignoring value "{1}"
+          UIMAFramework.getLogger(this.getClass()).logrb(Level.WARNING, this.getClass().getName(), "load",
+                  LOG_RESOURCE_BUNDLE, "UIMA_external_override_ignored__WARNING", new Object[] { name, value });
+        }
       }
     }
   }
 
+  /**
+   * Look up the value for a property.
+   * Perform one substitution pass on ${key} substrings. If key is undefined throw an exception.
+   * Recursively evaluate the value to be substituted.  NOTE: infinite loops not detected!
+   * To avoid evaluation and get ${key} in the output use a property to generate the $, e.g. 
+   *   $   = $
+   *   key = ${$}{key}
+   * or escape the $
+   *   key = \${key}
+   * 
+   * @param name - name to look up
+   * @return     - value of property
+   * @throws ResourceConfigurationException
+   */
+  public String lookUp(String name) throws ResourceConfigurationException {
+    String value;
+    if ((value = map.get(name)) == null) {
+      return null;
+    }
+    Matcher matcher = evalPattern.matcher(value);
+    StringBuilder result = new StringBuilder(value.length() + 100);
+    int lastEnd = 0;
+    while (matcher.find()) {
+      // Check if the $ is escaped
+      if (isEscaped(value, matcher.start())) {
+        result.append(value.substring(lastEnd, matcher.start() + 1));
+        lastEnd = matcher.start() + 1; // copy the escaped $ and restart after it
+      } else {
+        result.append(value.substring(lastEnd, matcher.start()));
+        lastEnd = matcher.end();
+        String key = value.substring(matcher.start() + 2, lastEnd - 1);
+        String val = lookUp(key);
+        if (val == null) { // External override variable "{0}" references the undefined variable "{1}"
+          throw new ResourceConfigurationException(ResourceConfigurationException.EXTERNAL_OVERRIDE_INVALID,
+                  new Object[] { name, key });
+        }
+        result.append(val);
+      }
+    }
+    if (lastEnd == 0) {
+      return value;
+    } else {
+      result.append(value.substring(lastEnd));
+      return result.toString();
+    }
+  }
+
   /*
    * Create a string representing an array from one or more logical lines
    * Assert: line length > 0
    */
   private String getArray(String line) throws IOException {
-
     int iend = line.indexOf(']');
     while (iend >= 0 && isEscaped(line, iend)) {
       iend = line.indexOf(']', iend + 1);
     }
     if (iend >= 0) {
       // Found the closing ']' - remainder of line must be empty
-      if (line.substring(iend + 1, line.length()).trim().length() > 0) {
-        throw new IOException("Syntax error - invalid characters after ']'");
+      if (iend + 1 < line.length()) {
+        throw new IOException("Syntax error - invalid character(s) '" +
+                line.substring(iend + 1, line.length()) + "' after end of array");
       }
       return line;
     }
 
-    // Trim each logical line as may be a single array element
     // If line doesn't end with a , add one and append the next line(s)
     // Don't add a , if line has only '[' or ']'
     String nextline = getLine();
@@ -110,53 +167,60 @@ public class Settings_impl {
     }
     iend = line.length() - 1;
     if ((line.charAt(iend) == ',' && !isEscaped(line, iend)) || 
-            line.equals("[") || nextline.trim().charAt(0) == ']') {
-      return line + getArray(nextline.trim());
+            line.equals("[") || nextline.charAt(0) == ']') {
+      return line + getArray(nextline);
     } else {
-      return line + "," + getArray(nextline.trim());
+      return line + "," + getArray(nextline);
     }
   }
 
   /*
-   * Reads a logical line from the input stream following the Java Properties class rules Ignore blank lines or comments
-   * (first non-blank is '#' or '!') An un-escaped final '\' marks a continuation line
+   * Reads a logical line from the input stream following the Java Properties class rules.
+   * Ignore blank lines or comments (first non-blank is '#' or '!').
+   * An un-escaped final '\' marks a continuation line.
+   * Leading and trailing whitespace is removed from each physical line, and hence from the logical line.
    */
   private String getLine() throws IOException {
     String line = rdr.readLine();
     if (line == null) {
       return null;
     }
-    // If line is blank or a comment get another & check it again
+    // If line is blank or a comment discard it and get another
     String trimmed = line.trim();
     if (trimmed.length() == 0 || trimmed.charAt(0) == '#' || trimmed.charAt(0) == '!') {
       return getLine();
     }
-    // Append further lines if should be continued
-    // Don't trim as could change what a final \ is escaping
-    return extendLine(line);
+    // Check the untrimmed line to see if it should be continued
+    if (!isEscaped(line, line.length())) {
+      return trimmed;
+    }
+    return extendLine(trimmed);
   }
 
   /*
-   * If line should be continued read another and append it
+   * Remove final \ and append another line (or lines)
    */
   private String extendLine(String line) throws IOException {
-    // Check if line-end is escaped
-    if (!isEscaped(line, line.length())) {
-      return line;
-    }
+
     // Line must be continued ... remove the final \ and append the next line, etc.
     int ilast = line.length() - 1;
     String next = rdr.readLine();
     if (next == null) {
-      return line.substring(0, ilast);
+      next = "";
+    }
+    // Append the trimmed line but check the untrimmed line for a final \
+    line = line.substring(0, ilast) + next.trim();
+    if (!isEscaped(next, next.length())) {
+      return line.trim();               // Complete line may need more trimming
     }
-    return line.substring(0, ilast) + extendLine(next);
+    return extendLine(line);
   }
 
   /*
    * Check if a character in the string is escaped, i.e. preceded by an odd number of '\'s
+   * Correctly returns false if ichar <= 0
    */
-  public boolean isEscaped(String line, int ichar) {
+  private boolean isEscaped(String line, int ichar) {
     int i = ichar - 1;
     while (i >= 0 && line.charAt(i) == '\\') {
       --i;

Modified: uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/TestAnnotator2.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/TestAnnotator2.java?rev=1330865&r1=1330864&r2=1330865&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/TestAnnotator2.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/TestAnnotator2.java Thu Apr 26 14:20:37 2012
@@ -19,6 +19,9 @@
 
 package org.apache.uima.analysis_engine.impl;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
 import junit.framework.Assert;
 
 import org.apache.uima.UimaContext;
@@ -27,7 +30,10 @@ import org.apache.uima.analysis_engine.R
 import org.apache.uima.cas.CAS;
 import org.apache.uima.cas.TypeSystem;
 import org.apache.uima.impl.UimaContext_ImplBase;
+import org.apache.uima.resource.ResourceConfigurationException;
 import org.apache.uima.resource.ResourceInitializationException;
+import org.apache.uima.util.Settings;
+import org.apache.uima.util.impl.Settings_impl;
 
 /**
  * Annotator class used for testing.
@@ -59,9 +65,33 @@ public class TestAnnotator2 extends CasA
     // Check if can get an arbitrary external parameter from the override settings
     String contextName = ((UimaContext_ImplBase) aContext).getQualifiedContextName();
     if ("/ExternalOverrides/".equals(contextName)) {
-      String actual = aContext.getExternalParameterValue("test.externalStringArray");
+      String actual = null;
+      try {
+        actual = aContext.getExternalParameterValue("test.externalStringArray");
+      } catch (ResourceConfigurationException e) {
+        Assert.fail(e.getMessage());
+      }
       String expected = "[prefix_from_import,-,suffix_from_inline,->,prefix_from_import-suffix_from_inline]";
       Assert.assertEquals(expected, actual);
+      
+      // Test a stand-alone settings object
+      Settings testSettings = new Settings_impl();
+      String lines = "foo = ${bar} \n bar : [ok \n OK] \n bad = ${missing}";
+      InputStream is;
+      try {
+        is = new ByteArrayInputStream(lines.getBytes("UTF-8"));
+        testSettings.load(is);
+        String val = testSettings.lookUp("foo");
+        Assert.assertEquals("[ok,OK]", val);
+        try {
+          val = testSettings.lookUp("bad");
+          Assert.fail("\"bad\" should create an error");
+        } catch (ResourceConfigurationException e) {
+          System.err.println("Expected exception: " + e.toString());
+        }
+      } catch (Exception e) {
+        Assert.fail(e.toString());
+      }
     }
   }