Logo Search packages:      
Sourcecode: cacao-oj6 version File versions

TestEnvironment.java

/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest;

import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import com.sun.javatest.util.DynamicArray;
import com.sun.javatest.util.I18NResourceBundle;
import com.sun.javatest.util.StringArray;

/**
 * This class provides "environments", as embodied by groups of related properties.
 * Environments have a name, and consist of those properties provided whose names 
 * do not begin with "<code>env.</code>", and in addition, those
 * properties provided whose names begin "<code>env.</code><i>env-name</i><code>.</code>".
 * In addition, an environment may inherit the properties of another environment
 * by defining a property <code>env.</code><i>env-name</i><code>inherits=</code><i>inherited-env-name</i>
 * The values of the environment's properties are split into words and various 
 * substitutions are performed.  
 *
 * <p>The preferred way to make an environment is via a configuration interview,
 * avoiding the use of the <code>env.</code><i>env-name</i> prefix, which is
 * retained for backwards compatibility with older test suites that read environments
 * from environment (.jte) files.
 */
00056 public class TestEnvironment
{
    /**
     * This exception is used to report resolving values in an environment.
     */
00061     public static class Fault extends Exception
    {
      Fault(I18NResourceBundle i18n, String s) {
          super(i18n.getString(s));
      }

      Fault(I18NResourceBundle i18n, String s, Object o) {
          super(i18n.getString(s, o));
      }

      Fault(I18NResourceBundle i18n, String s, Object[] o) {
          super(i18n.getString(s, o));
      }
    }

    /**
     * Add a default set of properties to be included when environments are 
     * created.
     * @param name a name for this collection or properties, so that the
     * source of the properties can be identified when browing an environment
     * @param propTable a table of properties to be included when environments 
     * are created
     * @see #clearDefaultPropTables
     * @throws NullPointerException if either name or propTable is null.
     */
00086     public static synchronized void addDefaultPropTable(String name, Map propTable) {
      if (name == null || propTable == null)
          throw new NullPointerException();

      //System.err.println("TEC: add default propTable " + name);
      defaultPropTableNames = (String[]) (DynamicArray.append(defaultPropTableNames, name));
      defaultPropTables = (Map[]) (DynamicArray.append(defaultPropTables, propTable));
    }

    /**
     * Remove all previously registered default property tables.
     * @see #addDefaultPropTable
     */
00099     public static synchronized void clearDefaultPropTables() {
      defaultPropTableNames = new String[0];
      defaultPropTables = new Map[0];
    }

    static String[] defaultPropTableNames = { };
    static Map[] defaultPropTables = { };

    /**
     * Construct an environment for a named group of properties.
     * @param name      The name by which to identify the group of properties
     *                  for this environment
     * @param propTable Dictionaries containing (but not limited to) the
     *                  properties for this environment. 
     * @param propTableName
     *                  The name of the property table, for use in diagnostics etc
     * @throws TestEnvironment.Fault if there is an error in the table
     *
     */
00118     public TestEnvironment(String name, Map propTable, String propTableName) 
            throws Fault {
      this(name, (new Map[] {propTable}), (new String[] {propTableName}));
    }

    /**
     * Construct an environment for a named group of properties.
     * @param name      The name by which to identify the group of properties
     *                  for this environment
     * @param propTables      Dictionaries containing (but not limited to) the
     *                  properties for this environment. They should be ordered 
     *                  so that values specified in later tables override those
     *                  specified in subsequent tables.
     * @param propTableNames
     *                  The names of the property tables, for use in diagnostics etc
     * @throws TestEnvironment.Fault if there is an error in the given tables
     *
     */
00136     public TestEnvironment(String name, Map[] propTables, String[] propTableNames) 
      throws Fault 
    {
      this.name = name;
      if (defaultPropTables != null && defaultPropTables.length > 0) {
          propTables = (Map[]) (DynamicArray.join(defaultPropTables, propTables));
          propTableNames = (String[]) (DynamicArray.join(defaultPropTableNames, propTableNames));
      }

      // First, figure out the inheritance chain
      Vector v = new Vector();
      for (String n = name, inherit = null; n != null && n.length() > 0; n = inherit, inherit = null) {
          if (v.contains(n)) 
            throw new Fault(i18n, "env.loop", name);
          
          v.addElement(n);
          String prefix = "env." + n + ".";
          for (int i = propTables.length - 1; i >= 0 && inherit == null; i--) {
            inherit = (String)(propTables[i].get("env." + n + ".inherits"));
          }
      }
      inherits = new String[v.size()];
      v.copyInto(inherits);

      // for this environment, and its inherited environments, scan for
      // properties of the form env.NAME.KEY=value and add KEY=value into the 
      // environment's table
      for (int inheritIndex = 0; inheritIndex < inherits.length; inheritIndex++) {
          String prefix = "env." + inherits[inheritIndex] + ".";
          for (int propIndex = propTables.length - 1; propIndex >= 0; propIndex--) {
            Map propTable = propTables[propIndex];
            for (Iterator i = propTable.keySet().iterator(); i.hasNext(); ) {
                String prop = (String) (i.next());
                if (prop.startsWith(prefix)) {
                  String key = prop.substring(prefix.length());
                  if (!table.containsKey(key)) {
                      Element elem = new Element(key, 
                                           (String)(propTable.get(prop)),
                                           inherits[inheritIndex],
                                           propTableNames[propIndex]);
                      table.put(key, elem);
                  }
                }
            }
          }
      }

      // finally, add in any top-level names (not beginning with env.)
      for (int propIndex = propTables.length - 1; propIndex >= 0; propIndex--) {
          Map propTable = propTables[propIndex];
          for (Iterator i = propTable.keySet().iterator(); i.hasNext(); ) {
            String key = (String) (i.next());
            if (!key.startsWith("env.")) {
                if (!table.containsKey(key)) {
                  Element elem = new Element(key, 
                                       (String)(propTable.get(key)),
                                       null,
                                       propTableNames[propIndex]);
                  table.put(key, elem);
                }
            }
          }
      }
    }


    /**
     * Create a copy of the current environment.  
     * @return a copy of the current environment
     */
00206     public TestEnvironment copy() {
      return new TestEnvironment(this);
    }

    /** 
     * Get the distinguishing name for the properties of this environment.
     * @return    The name used to distinguish the properties of this environment
     */
00214     public String getName() {
      return name;
    }

    /**
     * Get the description of this environment, as given by the "description" entry.
     * @return the description of this environment, or null if not given
     */
00222     public String getDescription() {
          if (table == null || ! table.containsKey("description")) {
            return null;
        }
        
        return ((Element)table.get("description")).getValue();
    }

    /** 
     * Get the list of names of inherited environments, including this environment,
     * in reverse  order or inheritance (ie this one, parent, grandparent etc).
     * @return an array containing the names of inherited environments
     */
00235     public String[] getInherits() {
      return inherits;
    }

    /**
     * A backdoor method to add global properties to the environment. The value is
     * not subject to any substitutions.
     * @param name      The name of the property to be written
     * @param value     The value of the property to be written
     */
00245     public void put(String name, String value) {
      // used to save values without subjecting them to any $ or # processing
      // Note further that the main props table is considered IMMUTABLE,
      // because it is shared amongst the clones.
      String[] v = {value};
      extras.put(name, v);
    }

    /**
     * A backdoor method to add global properties to the environment. The value is
     * not subject to any substitutions.
     * @param name      The name of the property to be written
     * @param value     The value of the property to be written
     */
00259     public void put(String name, String[] value) {
      // used to save values without subjecting them to any $ or # processing
      // Note further that the main props table is considered IMMUTABLE,
      // because it is shared amongst the clones.
      extras.put(name, value);
    }

    /**
     * A backdoor method to add global properties to the environment that have a
     * value that might be desired as both a file and a URL.  The URL form is
     * installed as a property with "URL" appended to the given property name.
     * The values are not subject to any substitutions.
     * URL result constructed using the following expression -
     * f.toURI().toASCIIString(); 
     * @param name      The name of the property to be written
     * @param f         The file indicating the value to be stored.
     */
00276     public void putUrlAndFile(String name, File f) {
      String filePath = f.getPath();

      if (filePath.endsWith(File.separator))
          filePath = filePath.substring(0, filePath.length() - File.separator.length());

      String url = f.toURI().toASCIIString();

      put(name, filePath);

        // should upgrade to re-encode using UTF-8 perhaps
        put(name + "URL", url);
    }

    /**
     * Lookup a named property in the environment. 
     * @param key The name of the property to look up
     * @return          The resolved value of the property
     * @throws    TestEnvironment.Fault is thrown if there is a problem resolving the value 
     *                  of the property
     * @see #resolve
     */
00298     public String[] lookup(String key) throws Fault {
      return lookup(key, null);
    }

    private String[] lookup(String key, Vector activeKeys) throws Fault {
      String[] v = (String[])extras.get(key);
      if (v != null)
          return v;


      Element elem = (Element)table.get(key);
      if (elem != null) {
          cache.put(key, elem);
          if (activeKeys == null)
            activeKeys = new Vector();
          else if (activeKeys.contains(key)) 
            throw new Fault(i18n, "env.recursive",
                        new Object[] {key, elem.getDefinedInFile()});

          activeKeys.addElement(key);
          try {
            return resolve(elem.getValue(), activeKeys);
          }
          catch (Fault e) {
            throw new Fault(i18n, "env.badName", 
                        new Object[] {key, elem.getDefinedInFile(), e.getMessage()});
          }
          finally {
            activeKeys.removeElement(key);
          }
      }
      
      return EMPTY_STRING_ARRAY;
    }

    /**
     * Resolve a value in the environment by splitting it into words and performing
     * various substitutions on it. White-space separates words except inside
     * quoted strings.
     * `<code>$<em>name</em></code>' and `<code>${<em>name</em>}</code>' are 
     * replaced by the result of calling `lookup(<em>name</em>)'. 
     * `<code>$/</code>' is replaced by the platform-specific file separator;
     * `<code>$:</code>' is replaced by the platform-specific path separator; and
     * `<code>$$</code>' is replaced by a single `$'.
     * No substitutions are performed inside single-quoted strings; $ substitutions
     * are performed in double-quoted strings.  
     * 
     * @param s   The string to be resolved
     * @return    An array of strings containing the words of the argument, after
     *            substitutions have been performed.
     * @throws TestEnvironment.Fault 
     *            This is thrown if there is a problem resolving the value 
     *            of the argument.
     */
00352     public String[] resolve(String s) throws Fault {
      return resolve(s, null);
    }

    private String[] resolve(String s, Vector activeKeys) throws Fault {
      Vector v = new Vector();
      StringBuffer current = new StringBuffer(64);
      char term = 0;

  loop:
      for (int i = 0; i < s.length(); i++) {
          char c = s.charAt(i);
          switch (c) {
            case '#':
              // # at top level introduces comment to end of line and terminates
              //command (if found); otherwise, it goes into the current word
            if (term == 0 || term ==' ')
                break loop;
            else
                current.append(c);
            break;

            case '\'':
            case '\"':
            // string quotes at top level begin/end a matched pair; otherwise they
            // are part of it
            if (term == 0 || term == ' ') {
                term = c;           // start matched pair
            } else if (term == c) 
                term = ' ';         // end matched pair
            else 
                current.append(c);  // put character in string
            break;

            case '$':
            // dollar introduces a name to be substituted, provided it does not
            // appear in single quotes. Special values: $/ is File.separatorChar,
            // $: is File.pathSeparatorChar, and $$ is $
            if (term != '\'') {
                StringBuffer buf = new StringBuffer();
                String name = null;
                String[] nameArgs = null;
                try {
                    c = s.charAt(++i);
                  switch (c) {
                  case '/':
                      current.append(File.separatorChar);
                      continue loop;

                  case ':':
                      current.append(File.pathSeparatorChar);
                      continue loop;

                  case '$':
                      current.append('$');
                      continue loop;

                  case '{':
                      c = s.charAt(++i);
                      while (c != ':' && c != '}') {
                        buf.append(c);
                        c = s.charAt(++i);
                      }
                      name=convertToName(resolve(buf.toString()));

                      // pick up optional nameArgs after embedded ':'
                      if (c == ':') {
                        buf = new StringBuffer();
                        c = s.charAt(++i);
                        while (c != '}') {
                            buf.append(c);
                            c = s.charAt(++i);
                        }
                        nameArgs = StringArray.split(buf.toString());
                      }

                      break;

                  default:
                      if (isNameChar(c)) {
                        while (i < s.length() && isNameChar(s.charAt(i))) {
                            buf.append(s.charAt(i++));
                        }
                        i--;
                      } else
                        throw new Fault(i18n, "env.badExprChar", new Character(c));
                      name = buf.toString();
                  }

                  String[] val = lookup(name, activeKeys);

                  // apply nameArgs, if any
                  if (nameArgs != null) {
                      for (int argi = 0; argi < nameArgs.length; argi++) {
                        String arg = nameArgs[argi];
                        if (arg.startsWith("FS=") && arg.length() == 4)
                            substituteChar(val, File.separatorChar, arg.charAt(3));
                        else if (arg.startsWith("PS=") && arg.length() == 4)
                            substituteChar(val, File.pathSeparatorChar, arg.charAt(3));
                        else if (arg.startsWith("MAP="))
                            substituteMap(val, lookup("map."+arg.substring(4), activeKeys));
                        else if (arg.equals("MAP")) 
                            substituteMap(val, lookup("map", activeKeys));
                        else
                            throw new Fault(i18n, "env.badOption", arg);
                      }
                  }

                  if (val != null && val.length > 0) {
                      // only start a new word if there is something to substitute
                      if (term == 0)
                        term = ' ';
                      for (int vi = 0; vi < val.length; vi++) {
                        if (vi == 0)
                            current.append(val[vi]);
                        else if (term == '"') {
                            current.append(' ');
                            current.append(val[vi]);
                        }
                        else {
                            v.addElement(current.toString());
                            current.setLength(0);
                            current.append(val[vi]);
                        }
                      }
                  }
                }
                catch (IndexOutOfBoundsException e) {
                  throw new Fault(i18n, "env.badExpr");
                }
            } else
                current.append(c);
            break;

            case ' ':
            case '\t':
            // space or tab are skipped if not in a word; if in a word and
            // term is space, they terminate it; otherwise they go into the
            // current word
            if (term != 0) {
                if (term == ' ') {
                    v.addElement(current.toString());
                  current.setLength(0);
                  term = 0;
                } else
                  current.append(c);
            }
            break;


            default:
            // other characters start a word if needed, then go into the word
            if (term == 0) 
                term = ' ';
            current.append(c);
            break;
          }
      }

      // we've reached the end; if a word has been started, finish it
      if (term != 0)
         v.addElement(current.toString());

      String[] result = new String[v.size()];
      v.copyInto(result);
      return result;
    }

    /**
     * Check if the environment has any undefined values. These are entries containing
     * the text VALUE_NOT_DEFINED.
     * @return true if and only if there are any entries containing the text
     * VALUE_NOT_DEFINED.
     */
00526     public boolean hasUndefinedValues() {
      for (Iterator i = elements().iterator(); i.hasNext(); ) {
          TestEnvironment.Element entry = (TestEnvironment.Element) (i.next());
          if (entry.value.indexOf("VALUE_NOT_DEFINED") >= 0) 
            return true;
      }
      return false;
    }

    private void substituteChar(String[] v, char from, char to) {
      for (int i = 0; i < v.length; i++)
          v[i] = v[i].replace(from, to);
    }

    private void substituteMap(String[] v, String[] map) {
      if (map == null)
          return;
      
      // this algorithm is directly based on the "map" algorithm in
      // Slave.Map, which it supercedes
      for (int i = 0; i < v.length; i++) {
          String word = v[i];
          for (int j = 0; j+1 < map.length; j+=2) {
            String f = map[j];
            String t = map[j+1];
            for (int index = word.indexOf(f); 
                 index != -1; 
                 index = word.indexOf(f, index + t.length())) {
                word = word.substring(0, index) + t + word.substring(index + f.length());
            }
          }
          v[i] = word;
      }
    }

    private String convertToName(String[] v) {
      String s = "";
      for (int i = 0; i < v.length; i++) {
          if (i > 0)
            s += '_';
          for (int j = 0; j < v[i].length(); j++) {
            char c = v[i].charAt(j);
            s += (isNameChar(c) ? c : '_');
          }
      }
      return s;
    }

    /**
     * Identifies the characters recognized for $ names
     */
00577     private static boolean isNameChar(char c) {
      return (Character.isUpperCase(c) 
            || Character.isLowerCase(c) 
            || Character.isDigit(c)
            || (c == '_') 
            || (c == '.'));
    }

    /**
     * Enumerate the keys for this environment, including any inherited keys.
     * Use `lookup' to find the values of the individual keys.
     *
     * @return    An enumeration that yields the various keys, explicit or inherited,
     *            that are available in this environment. The keys do <em>not</em>
     *            include the `env.<em>environment-name</em>.' prefix of the corresponding
     *            property names.
     */
00594     public Set keys() {
      return table.keySet();
    }

    /**
     * Get a collection containing those entries in this environment that have been
     * referenced, either directly via lookup, or indirectly via the $ syntax in
     * other entries.
     * @return a collection of those entries in this environment that have been
     * referenced.
     * @see #resetElementsUsed
     */
00606     public Collection elementsUsed() {
      return cache.values();
    }

    /**
     * Reset the record of entries in this environment that have been referenced.
     * @see #elementsUsed
     */
00614     public void resetElementsUsed() {
      cache.clear();
    }

    /**
     * Enumerate the elements for this environment, including any inherited elements.
     *
     * @return    An enumeration that yields the various elements, explicit or inherited,
     *            that are available in this environment. 
     */
00624     public Collection elements() {
      return table.values();
    }


    protected TestEnvironment(TestEnvironment o) {
      name = o.name;
      inherits = o.inherits;
      table = o.table;
      extras = (HashMap)(o.extras.clone());
    }

    /**
     * A class representing an entry in a test environment.
     */
00639     public class Element {
      /**
       * Create an entry for a test environment.
       * @param key The name of the entry
       * @param value The unresolved value of the entry
       * @param definedInEnv The name of the environment that defines this entry
       * @param definedInFile The name of the file (or table) that defines this entry
       */
00647       Element(String key, String value, String definedInEnv, String definedInFile) {
          this.key = key;
          this.value = value;
          this.definedInEnv = definedInEnv;
          this.definedInFile = definedInFile;
      }

      /**
       * Get the name of this entry.
       * @return the name of this entry
       */
00658       public String getKey() { return key; }

      /**
       * Get the (unresolved) value of this entry.
       * @return the (unresolved) value of this entry
       */
00664       public String getValue() { return value; }

      /**
       * Get the name of the environment that defines this entry.
       * @return the name of the environment that defines this entry
       */
00670       public String getDefinedInEnv() { return definedInEnv; }

      /**
       * Get the name of the file (or table) that defines this entry.
       * @return the name of the file (or table) that defines this entry
       */
00676       public String getDefinedInFile() { return definedInFile; }

      String key;
      String value;
      String definedInEnv;
      String definedInFile;
    }
    
    private String name;
    private String[] inherits;
    private HashMap table = new HashMap();
    private HashMap extras = new HashMap();
    private HashMap cache = new HashMap();

    private static final String[] EMPTY_STRING_ARRAY = {};
    private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(TestEnvironment.class);
}

Generated by  Doxygen 1.6.0   Back to index