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 2017/01/20 21:58:24 UTC

svn commit: r1779683 - in /uima/uimaj/trunk/uimaj-core/src: main/java/org/apache/uima/resource/ main/java/org/apache/uima/util/impl/ main/resources/org/apache/uima/ test/data/ test/java/org/apache/uima/analysis_engine/impl/ test/resources/TextAnalysisE...

Author: burn
Date: Fri Jan 20 21:58:24 2017
New Revision: 1779683

URL: http://svn.apache.org/viewvc?rev=1779683&view=rev
Log:
UIMA-5273 Check for circular external settings. Added Junit tests and also some for 5208

Added:
    uima/uimaj/trunk/uimaj-core/src/test/data/
    uima/uimaj/trunk/uimaj-core/src/test/data/testExternalOverride4.settings
Modified:
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/ResourceConfigurationException.java
    uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java
    uima/uimaj/trunk/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties
    uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/AnalysisEngine_implTest.java
    uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/TestAnnotator2.java
    uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride.settings
    uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride2.settings

Modified: uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/ResourceConfigurationException.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/ResourceConfigurationException.java?rev=1779683&r1=1779682&r2=1779683&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/ResourceConfigurationException.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/java/org/apache/uima/resource/ResourceConfigurationException.java Fri Jan 20 21:58:24 2017
@@ -107,6 +107,11 @@ public class ResourceConfigurationExcept
   public static final String EXTERNAL_OVERRIDE_NUMERIC_ERROR = "external_override_numeric_error";
   
   /**
+   * Message key for a standard UIMA exception message: External override variable "{0}" has a circular reference to itself
+   */
+  public static final String EXTERNAL_OVERRIDE_CIRCULAR_REFERENCE = "external_override_circular_reference";
+  
+  /**
    * Creates a new exception with a null message.
    */
   public ResourceConfigurationException() {

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=1779683&r1=1779682&r2=1779683&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 Fri Jan 20 21:58:24 2017
@@ -60,6 +60,13 @@ public class Settings_impl implements Se
 
   private Map<String, String> map;
   
+  // Thread-local map of properties being resolved +for detecting circular references.
+  private ThreadLocal<HashMap<String, Integer>> tlResolving = new ThreadLocal<HashMap<String, Integer>>() {
+    protected synchronized HashMap<String, Integer> initialValue() {
+      return new HashMap<String, Integer>();
+    }
+  };
+
   /*
    * Regex that matches ${...}
    * non-greedy so stops on first '}' -- hence key cannot contain '}'
@@ -176,10 +183,10 @@ public class Settings_impl implements Se
   
   /**
    * 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 {
+   * Recursively evaluate the value replacing references ${key} with the value of the key.
+   * Nested references such as ${name-${suffix}} are supported. 
+   * Exceptions are thrown for circular references and undefined references.
+   * To avoid evaluation and get ${key} in the output escape the $ or {, e.g. \${key}
    * Arrays are returned as a comma-separated string, e.g. "[elem1,elem2]" 
    * Note: escape characters are not removed as they may affect array separators. 
    * 
@@ -190,36 +197,84 @@ public class Settings_impl implements Se
    * @throws ResourceConfigurationException if the value references an undefined property
    */
   public String lookUp(String name) throws ResourceConfigurationException {
-    String value;
-    if ((value = map.get(name)) == null) {
+    return lookUp(name, name);
+  }
+  
+  private String lookUp(String from, String name) throws ResourceConfigurationException {
+    // Maintain a set of variables being expanded so can recognize infinite recursion
+    // Needs to be thread-local as multiple threads may be evaluating properties
+    HashMap<String, Integer> resolving = tlResolving.get();
+    if (resolving.containsKey(name)) {
+      System.err.println("Circular evaluation of property: '" + name + "' - definitions are:");
+      for (String s : resolving.keySet()) {
+        System.err.println(resolving.get(s) + ": " + s + " = " + map.get(s));
+      }
+      // Circular reference to external override variable "{0}" when evaluating "{1}"
+      throw new ResourceConfigurationException(ResourceConfigurationException.EXTERNAL_OVERRIDE_CIRCULAR_REFERENCE,
+              new Object[] { name, from });
+    }
+
+    // Add the name for the duration of the lookup
+    resolving.put(name, new Integer(resolving.size()));
+    try {
+      return resolve(from, map.get(name));
+    } finally {
+      resolving.remove(name);
+    }
+  }
+  
+  /**
+   * Replace variable references in a string.
+   * 
+   * @param value - String to scan for variable references
+   * @return - value with all references resolved and escapes processed
+   * @throws Exception
+   */
+  public String resolve(String value) throws Exception {
+    return unescape(resolve(value, value));
+  }
+
+  private String resolve(String from, String value) throws ResourceConfigurationException {
+    if (value == null) {
       return null;
     }
     Matcher matcher = evalPattern.matcher(value);
+    if (!matcher.find()) {
+      return 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}"
+
+    // If this ${ is escaped then simply remove the \ and expand everything after the ${
+    if (isEscaped(value, matcher.start())) {
+      result.append(value.substring(0, matcher.start() - 1));
+      result.append("${");
+      result.append(resolve(from, value.substring(matcher.start() + 2)));
+      return result.toString();
+    }
+
+    // Find start of variable, expand all that follows, and then look for the end
+    // so that nested entries are supported, e.g. ${name${suffix}}
+    result.append(value.substring(0, matcher.start()));
+    String remainder = resolve(from, value.substring(matcher.start() + 2));
+    int end = remainder.indexOf('}');
+    // If ending } missing leave the ${ as-is
+    // If there is no variable treat as if omitted, i.e. '${}' => ''
+    if (end < 0) {
+      result.append("${");
+      result.append(remainder);
+    } else {
+      String key = remainder.substring(0, end);
+      if (end > 0) {
+        String val = lookUp(from, key);
+        if (val == null) { // Undefined reference to external override variable "{0}" when evaluating "{1}"
           throw new ResourceConfigurationException(ResourceConfigurationException.EXTERNAL_OVERRIDE_INVALID,
-                  new Object[] { name, key });
+                  new Object[] { key, from });
         }
         result.append(val);
       }
+      result.append(remainder.substring(end + 1));
     }
-    if (lastEnd == 0) {
-      return value;
-    } else {
-      result.append(value.substring(lastEnd));
-      return result.toString();
-    }
+    return result.toString();
   }
   
   /**
@@ -227,7 +282,7 @@ public class Settings_impl implements Se
    */
   @Override
   public String getSetting(String name) throws ResourceConfigurationException {
-    String value = lookUp(name);
+    String value = lookUp(name, name);
     if (value == null) {
       return null;
     }
@@ -238,7 +293,7 @@ public class Settings_impl implements Se
       throw new ResourceConfigurationException(ResourceConfigurationException.EXTERNAL_OVERRIDE_TYPE_MISMATCH, 
               new Object[] { name });
     }
-    return value;
+    return unescape(value);  // Process escape characters after checking for array syntax
   }
 
   /**
@@ -246,7 +301,7 @@ public class Settings_impl implements Se
    */
   @Override
   public String[] getSettingArray(String name) throws ResourceConfigurationException {
-    String value = lookUp(name);
+    String value = lookUp(name, name);
     if (value == null) {
       return null;
     }
@@ -278,14 +333,14 @@ public class Settings_impl implements Se
     int i = 0;
     for (String token : tokens) {
       if (token != null) {
-        result[i++] = escape(token.trim());
+        result[i++] = unescape(token.trim());
       }
     }
     return result;
   }
 
   // Final step is to process any escapes by replacing \x by x
-  private String escape(String token) {
+  private String unescape(String token) {
     int next = token.indexOf('\\');
     if (next < 0) {
       return token;

Modified: uima/uimaj/trunk/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties?rev=1779683&r1=1779682&r2=1779683&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties (original)
+++ uima/uimaj/trunk/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties Fri Jan 20 21:58:24 2017
@@ -130,7 +130,7 @@ config_setting_absent = Configuration se
 directory_not_found = Invalid value for parameter "{0}" in component "{1}" -- \
 	 directory "{2}" does not exist.
 
-external_override_invalid = External override variable "{0}" references the undefined variable "{1}"
+external_override_invalid = Undefined reference to external override variable "{0}" when evaluating "{1}"
 
 external_override_error = Error loading external overrides from "{0}"
 
@@ -138,6 +138,8 @@ external_override_type_mismatch = Extern
 
 external_override_numeric_error = External override value "{0}" is not an integer
 
+external_override_circular_reference = Circular reference to external override variable "{0}" when evaluating "{1}"
+
 #--------------------------------
 #ResourceProcessException
 #--------------------------------

Added: uima/uimaj/trunk/uimaj-core/src/test/data/testExternalOverride4.settings
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/test/data/testExternalOverride4.settings?rev=1779683&view=auto
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/test/data/testExternalOverride4.settings (added)
+++ uima/uimaj/trunk/uimaj-core/src/test/data/testExternalOverride4.settings Fri Jan 20 21:58:24 2017
@@ -0,0 +1,19 @@
+ #  ***************************************************************
+ #  * Licensed to the Apache Software Foundation (ASF) under one
+ #  * or more contributor license agreements.  See the NOTICE file
+ #  * distributed with this work for additional information
+ #  * regarding copyright ownership.  The ASF licenses this file
+ #  * to you under the Apache License, Version 2.0 (the
+ #  * "License"); you may not use this file except in compliance
+ #  * with the License.  You may obtain a copy of the License at
+ #  *
+ #  *   http://www.apache.org/licenses/LICENSE-2.0
+ #  * 
+ #  * Unless required by applicable law or agreed to in writing,
+ #  * software distributed under the License is distributed on an
+ #  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ #  * KIND, either express or implied.  See the License for the
+ #  * specific language governing permissions and limitations
+ #  * under the License.
+ #  ***************************************************************
+test.externalInteger         43

Modified: uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/AnalysisEngine_implTest.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/AnalysisEngine_implTest.java?rev=1779683&r1=1779682&r2=1779683&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/AnalysisEngine_implTest.java (original)
+++ uima/uimaj/trunk/uimaj-core/src/test/java/org/apache/uima/analysis_engine/impl/AnalysisEngine_implTest.java Fri Jan 20 21:58:24 2017
@@ -35,6 +35,7 @@ import java.util.Map;
 import java.util.Set;
 
 import org.junit.Assert;
+
 import junit.framework.TestCase;
 
 import org.apache.uima.Constants;
@@ -63,6 +64,7 @@ import org.apache.uima.cas.text.Annotati
 import org.apache.uima.cas.text.AnnotationIndex;
 import org.apache.uima.examples.SourceDocumentInformation;
 import org.apache.uima.jcas.JCas;
+import org.apache.uima.resource.RelativePathResolver;
 import org.apache.uima.resource.Resource;
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.ResourceManager;
@@ -322,9 +324,13 @@ public class AnalysisEngine_implTest ext
       ae.destroy();
 
       // descriptor with configuration parameter external overrides
-      // implicitly load settings values from the system property UimaExternalSettings
+      // implicitly load settings values from the 3 files in the system property UimaExternalOverrides
+      // Load 1st from filesystem, 2nd from classpath, and 3rd from datapath
+      
+      String prevDatapath = System.setProperty(RelativePathResolver.UIMA_DATAPATH_PROP, "src/test/data");
       String resDir = "src/test/resources/TextAnalysisEngineImplTest/";
-      System.setProperty("UimaExternalOverrides", resDir+"testExternalOverride.settings,"+resDir+"testExternalOverride2.settings");
+      System.setProperty("UimaExternalOverrides", 
+              resDir+"testExternalOverride.settings,TextAnalysisEngineImplTest/testExternalOverride2.settings,testExternalOverride4.settings");
       in = new XMLInputSource(JUnitExtension.getFile("TextAnalysisEngineImplTest/AnnotatorWithExternalOverrides.xml"));
       desc = UIMAFramework.getXMLParser().parseAnalysisEngineDescription(in);
       ae1 = new PrimitiveAnalysisEngine_impl();
@@ -344,6 +350,11 @@ public class AnalysisEngine_implTest ext
       Integer intValue = (Integer) ae1.getUimaContext().getConfigParameterValue("IntegerParam");
       Assert.assertEquals(43,  intValue.intValue());  // Will be 42 if external override not defined
       System.clearProperty("UimaExternalOverrides");
+      if (prevDatapath == null) {
+        System.clearProperty(RelativePathResolver.UIMA_DATAPATH_PROP);
+      } else {
+        System.setProperty(RelativePathResolver.UIMA_DATAPATH_PROP, prevDatapath);
+      }
       
       ae1.destroy();
       

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=1779683&r1=1779682&r2=1779683&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 Fri Jan 20 21:58:24 2017
@@ -19,15 +19,9 @@
 
 package org.apache.uima.analysis_engine.impl;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.List;
-import java.util.Map.Entry;
-
-import org.junit.Assert;
 
 import org.apache.uima.UIMAFramework;
 import org.apache.uima.UimaContext;
@@ -41,7 +35,7 @@ import org.apache.uima.resource.Resource
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.util.Settings;
 import org.apache.uima.util.UimaContextHolderTest;
-import org.apache.uima.util.impl.Settings_impl;
+import org.junit.Assert;
 
 /**
  * Annotator class used for testing.
@@ -76,7 +70,6 @@ public class TestAnnotator2 extends CasA
     // Note: this annotator launched with external overrides loaded from testExternalOverride2.settings 
     String contextName = ((UimaContext_ImplBase) aContext).getQualifiedContextName();
     if ("/ExternalOverrides/".equals(contextName)) {
-      String expected = "Context Holder Test";
       String[] actuals = null;
       try {
         actuals = UimaContextHolder.getContext().getSharedSettingArray("test.externalFloatArray");
@@ -85,8 +78,7 @@ public class TestAnnotator2 extends CasA
       }
       Assert.assertEquals(0, actuals.length);
       
-      // prefix-suffix     Prefix-${suffix}
-      // suffix = should be ignored
+      String expected = "Context Holder Test";
       String actual = null;
       try {
         actual = UimaContextHolder.getContext().getSharedSettingValue("context-holder");
@@ -111,7 +103,12 @@ public class TestAnnotator2 extends CasA
 
       // Test a stand-alone settings object
       Settings testSettings = UIMAFramework.getResourceSpecifierFactory().createSettings();
-      String lines = "foo = ${bar} \n bar : [ok \n OK] \n bad = ${missing}";
+      String lines = "foo = ${bar} \n" +
+                "bar : [ok \n OK] \n" +
+                "bad = ${missing} \n" +
+                "loop1 = one ${loop2} \n" +
+                "loop2 = two ${loop3} \n" +
+                "loop3 = three ${loop1} \n" ;
       InputStream is;
       try {
         is = new ByteArrayInputStream(lines.getBytes("UTF-8"));
@@ -125,6 +122,12 @@ public class TestAnnotator2 extends CasA
         } catch (ResourceConfigurationException e) {
           System.err.println("Expected exception: " + e.toString());
         }
+        try {
+          val = testSettings.lookUp("loop2");
+          Assert.fail("\"loop2\" should create an error");
+        } catch (ResourceConfigurationException e) {
+          System.err.println("Expected exception: " + e.toString());
+        }
       } catch (Exception e) {
         Assert.fail(e.toString());
       }

Modified: uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride.settings
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride.settings?rev=1779683&r1=1779682&r2=1779683&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride.settings (original)
+++ uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride.settings Fri Jan 20 21:58:24 2017
@@ -1,24 +1,23 @@
- #  ***************************************************************
- #  * Licensed to the Apache Software Foundation (ASF) under one
- #  * or more contributor license agreements.  See the NOTICE file
- #  * distributed with this work for additional information
- #  * regarding copyright ownership.  The ASF licenses this file
- #  * to you under the Apache License, Version 2.0 (the
- #  * "License"); you may not use this file except in compliance
- #  * with the License.  You may obtain a copy of the License at
- #  *
- #  *   http://www.apache.org/licenses/LICENSE-2.0
- #  * 
- #  * Unless required by applicable law or agreed to in writing,
- #  * software distributed under the License is distributed on an
- #  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- #  * KIND, either express or implied.  See the License for the
- #  * specific language governing permissions and limitations
- #  * under the License.
- #  ***************************************************************
-prefix    Prefix
-suffix    Suffix
-test.externalStringArray = [${prefix},\-,${suffix},->,${prefix-suffix}]
-test.externalIntegerArray : [1 , 22 , 3\33,4444]  
-test.externalInteger         43
-
+ #  ***************************************************************
+ #  * Licensed to the Apache Software Foundation (ASF) under one
+ #  * or more contributor license agreements.  See the NOTICE file
+ #  * distributed with this work for additional information
+ #  * regarding copyright ownership.  The ASF licenses this file
+ #  * to you under the Apache License, Version 2.0 (the
+ #  * "License"); you may not use this file except in compliance
+ #  * with the License.  You may obtain a copy of the License at
+ #  *
+ #  *   http://www.apache.org/licenses/LICENSE-2.0
+ #  * 
+ #  * Unless required by applicable law or agreed to in writing,
+ #  * software distributed under the License is distributed on an
+ #  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ #  * KIND, either express or implied.  See the License for the
+ #  * specific language governing permissions and limitations
+ #  * under the License.
+ #  ***************************************************************
+prefix    Prefix
+suffix    Suffix
+test.externalStringArray = [${prefix},\-,${suffix},->,${prefix-suffix}]
+test.externalIntegerArray : [1 , 22 , 3\33,4444]  
+

Modified: uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride2.settings
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride2.settings?rev=1779683&r1=1779682&r2=1779683&view=diff
==============================================================================
--- uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride2.settings (original)
+++ uima/uimaj/trunk/uimaj-core/src/test/resources/TextAnalysisEngineImplTest/testExternalOverride2.settings Fri Jan 20 21:58:24 2017
@@ -1,24 +1,24 @@
- #  ***************************************************************
- #  * Licensed to the Apache Software Foundation (ASF) under one
- #  * or more contributor license agreements.  See the NOTICE file
- #  * distributed with this work for additional information
- #  * regarding copyright ownership.  The ASF licenses this file
- #  * to you under the Apache License, Version 2.0 (the
- #  * "License"); you may not use this file except in compliance
- #  * with the License.  You may obtain a copy of the License at
- #  *
- #  *   http://www.apache.org/licenses/LICENSE-2.0
- #  * 
- #  * Unless required by applicable law or agreed to in writing,
- #  * software distributed under the License is distributed on an
- #  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- #  * KIND, either express or implied.  See the License for the
- #  * specific language governing permissions and limitations
- #  * under the License.
- #  ***************************************************************
-prefix-suffix     Prefix-${suffix}
-# The following key should have already been set by an earlier import
-suffix = should be ignored
-# Empty array
-test.externalFloatArray =  []
-context-holder = Context Holder Test
+ #  ***************************************************************
+ #  * Licensed to the Apache Software Foundation (ASF) under one
+ #  * or more contributor license agreements.  See the NOTICE file
+ #  * distributed with this work for additional information
+ #  * regarding copyright ownership.  The ASF licenses this file
+ #  * to you under the Apache License, Version 2.0 (the
+ #  * "License"); you may not use this file except in compliance
+ #  * with the License.  You may obtain a copy of the License at
+ #  *
+ #  *   http://www.apache.org/licenses/LICENSE-2.0
+ #  * 
+ #  * Unless required by applicable law or agreed to in writing,
+ #  * software distributed under the License is distributed on an
+ #  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ #  * KIND, either express or implied.  See the License for the
+ #  * specific language governing permissions and limitations
+ #  * under the License.
+ #  ***************************************************************
+prefix-suffix     Prefix-${suffix}
+# The following key should have already been set by an earlier import
+suffix = should be ignored
+# Empty array
+test.externalFloatArray =  []
+context-holder = Context Holder Test