/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.config;

import com.sun.jini.logging.Levels;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.export.Exporter;
import net.jini.security.ProxyPreparer;
import net.jini.security.Security;

/**
 * Supplies objects needed to configure applications, such as {@link Exporter}
 * or {@link ProxyPreparer} instances, or application-specific objects,
 * constructed from data in a configuration source and override options, as
 * well as data supplied in the call to <code>getEntry</code>. The
 * configuration source is specified with a file or URL location, or as a
 * character input stream. The contents of the configuration source consist of
 * optional import statements followed by entries, grouped by component, that
 * specify configuration objects using a subset of expression syntax in the
 * Java(TM) programming language. Additional options specify values for
 * individual entries, overriding any matching entries supplied in the
 * configuration source. <p>
 *
 * Applications should normally use {@link ConfigurationProvider} to obtain
 * {@link Configuration} instances, rather than referencing this class
 * directly, so that the interpretation of configuration options can be
 * customized without requiring code modifications. <p>
 *
 * The syntax of a configuration source is as follows, using the same grammar
 * notation that is used in <i>The Java Language Specification (JLS)</i>:
 *
 * <pre>
 * <i>Source</i>:
 *   <i>Imports</i><sub>opt</sub> <i>Components</i><sub>opt</sub>
 *
 * <i>Imports</i>:
 *   <i>Import</i>
 *   <i>Imports</i> <i>Import</i>
 *
 * <i>Import</i>:
 *   import <i>PackageName</i> . * ;
 *   import <i>PackageName</i> . <i>ClassName</i> . * ;
 *   import <i>PackageName</i> . <i>ClassName</i> ;
 *
 * <i>PackageName</i>:
 *   <i>QualifiedIdentifier</i>
 *
 * <i>ClassName</i>:
 *   <i>QualifiedIdentifier</i>
 *
 * <i>Components</i>:
 *   <i>Component</i>
 *   <i>Components</i> <i>Component</i>
 *
 * <i>Component</i>:
 *   <i>QualifiedIdentifier</i> { <i>Entries</i><sub>opt</sub> }
 *
 * <i>Entries</i>:
 *   <i>Entry</i>
 *   <i>Entries</i> <i>Entry</i>
 *
 * <i>Entry</i>:
 *   <i>EntryModifiers</i><sub>opt</sub> <i>Identifier</i> = <i>Expr</i> ;
 *
 * <i>EntryModifiers</i>:
 *   static
 *   private
 *   static private
 *   private static
 *
 * <i>Expr</i>:
 *   <i>Literal</i>
 *   <i>TypeName</i> . class
 *   <i>EntryName</i>
 *   <i>ThisReference</i>
 *   <i>FieldName</i>
 *   <i>Cast</i>
 *   <i>NewExpr</i>
 *   <i>MethodCall</i>
 *   <i>Data</i>
 *   <i>Loader</i>
 *
 * <i>Literal</i>:
 *   <i>IntegerLiteral</i>
 *   <i>FloatingPointLiteral</i>
 *   <i>BooleanLiteral</i>
 *   <i>CharacterLiteral</i>
 *   <i>StringLiteral</i>
 *   <i>NullLiteral</i>
 *
 * <i>TypeName</i>:
 *   <i>ClassName</i>
 *   <i>ClassName</i> [ ]
 *   <i>PrimitiveType</i>
 *   <i>PrimitiveType</i> [ ]
 *
 * <i>EntryName</i>:
 *   <i>QualifiedIdentifier</i>
 *
 * <i>ThisReference</i>:
 *   this
 *
 * <i>FieldName</i>:
 *   <i>QualifiedIdentifier</i> . <i>Identifier</i>
 *
 * <i>Cast</i>:
 *   ( <i>TypeName</i> ) <i>Expr</i>
 *
 * <i>NewExpr</i>:
 *   new <i>QualifiedIdentifier</i> ( <i>ExprList</i><sub>opt</sub> )
 *   new <i>QualifiedIdentifier</i> [ ] { <i>ExprList</i><sub>opt</sub> }
 *
 * <i>MethodCall</i>:
 *   <i>StaticMethodName</i> ( <i>ExprList</i><sub>opt</sub> )
 *
 * <i>StaticMethodName</i>:
 *   <i>QualifiedIdentifier</i> . <i>Identifier</i>
 *
 * <i>ExprList</i>:
 *   <i>Expr</i>
 *   <i>ExprList</i> , <i>Expr</i>
 *
 * <i>Data</i>:
 *   $data
 *
 * <i>Loader</i>:
 *   $loader
 * </pre>
 *
 * The syntax of each override option is as follows:
 *
 * <pre>
 * <i>Override</i>:
 *   <i>EntryModifiers</i><sub>opt</sub> <i>FullyQualifiedEntryName</i> = <i>Expr</i>
 *
 * <i>FullyQualifiedEntryName</i>:
 *   <i>QualifiedIdentifier</i> . <i>Identifier</i>
 * </pre> <p>
 *
 * For example, a simple configuration source file might look like the
 * following:
 *
 * <pre>
 * import java.util.HashSet;
 *
 * com.acme.ContainerUtility {
 *     container = new HashSet(containerSize);
 *     containerSize = 33;
 * }
 * </pre> <p>
 *
 * The productions for <i>BooleanLiteral</i>, <i>CharacterLiteral</i>,
 * <i>FloatingPointLiteral</i>, <i>Identifier</i>, <i>IntegerLiteral</i>,
 * <i>NullLiteral</i>, <i>PrimitiveType</i>, <i>QualifiedIdentifier</i>, and
 * <i>StringLiteral</i> are the same as the ones used in the
 * JLS. <i>StringLiteral</i>s can refer to the values of system properties by
 * using the syntax <code>${<i>propertyName</i>}</code> within the
 * <i>StringLiteral</i>, and can refer to the file name separator character by
 * using the syntax <code>${/}</code>. System property references cannot be
 * nested. Expansion of system properties occurs when the entry is evaluated
 * and, if there is a security manager, will result in its {@link
 * SecurityManager#checkPropertyAccess checkPropertyAccess} method being called
 * with the property name as its argument. Both <i>StringLiteral</i>s and
 * <i>CharacterLiteral</i>s can use character and Unicode escape
 * sequences. Standard comment syntax can also be used throughout. <p>
 *
 * Each <i>Import</i> specifies a class or group of classes which may be
 * referred to using simple names, as specified in the JLS. Classes in the
 * <code>java.lang</code> package are imported by default. <p>
 *
 * Each <i>Component</i> includes <i>Entries</i> which specify expressions to
 * evaluate and return when <code>getEntry</code> is called with the associated
 * component and entry name. More than one <i>Component</i> is allowed to
 * specify the same name; all contribute entries for that component. For a
 * given component, each entry name must be unique. If <i>EntryModifiers</i>
 * contains the <code>static</code> keyword, then the entry is only evaluated
 * once when it is first referenced. Otherwise, entries are evaluated at each
 * reference, including each time an entry is referred to by another
 * entry. Because static entries are only evaluated once (in the access control
 * context of the first caller), care should be taken when passing instances of
 * this class to callers with different access control contexts. If
 * <i>EntryModifiers</i> contains the <code>private</code> keyword, then the
 * entry may be referred to in other entries, but will not be considered by
 * calls to <code>getEntry</code>, which will treat the entry name as not being
 * defined. Entries may have reference, primitive, or <code>null</code>
 * values. Entry values are converted to the requested type by <i>assignment
 * conversion</i>, as defined in the JLS, with the restriction that the value
 * is only considered a constant expression if it is either a
 * <i>StringLiteral</i> with no system property references, another kind of
 * <i>Literal</i>, or an <i>EntryName</i> that refers to another entry whose
 * value is a constant expression. In particular, this restriction means that
 * narrowing primitive conversions are not applied when the value is a
 * reference to a static field, even if the field is a constant. <p>
 *
 * Override options are specified as the second and following elements of the
 * <code>options</code> argument in this class's constructors. Each
 * <i>Override</i> specifies a single entry, using the fully qualified name of
 * the entry (<code><i>component</i>.<i>name</i></code>). The override replaces
 * the matching entry in the configuration source, if any, including both its
 * value and entry modifiers. Each <i>Override</i> option must specify a
 * different <i>FullyQualifiedEntryName</i>. The contents of the <i>Expr</i>
 * are evaluated in the context of any <i>Imports</i> defined in the
 * configuration source. <p>
 *
 * The <i>Expr</i> for each <i>Entry</i> may be specified using a subset of the
 * expression syntax in the Java programming language, supporting literals,
 * references to static fields, casts, class instance creation (using the
 * standard method invocation conversion and selection semantics, but not
 * including creation of anonymous class instances), single dimensional array
 * creation with an array initializer (but not multi-dimensional arrays or
 * arrays declared with an explicit size), and static method invocation using a
 * class name (also using standard method invocation conversion and selection
 * semantics, but not permitting methods with a <code>void</code> return
 * type). Expressions are interpreted in the unnamed package, although only
 * public members may be accessed. The <code>this</code> expression may be used
 * to refer to the containing <code>ConfigurationFile</code> instance
 * itself. <p>
 *
 * The <code>ConfigurationFile</code> class provides built-in support for two
 * <i>special entry expressions</i>, which are <i>Identifier</i>s that
 * start with <code>'$'</code>. The <code>$data</code> expression, of type
 * {@link Object}, may be used to refer to the <code>data</code> argument
 * specified in a call to <code>getEntry</code>. Only non-static entries may
 * refer to <code>$data</code> or other entries that refer to
 * <code>$data</code>. Calling <code>getEntry</code> without specifying
 * <code>$data</code> results in a <code>ConfigurationException</code> being
 * thrown if the associated entry refers to <code>$data</code>. The
 * <code>$loader</code> expression, of type {@link ClassLoader}, may be used to
 * refer to the <code>ClassLoader</code> specified when creating the
 * <code>ConfigurationFile</code>. If the <code>ConfigurationFile</code> was
 * created using the context class loader either by not specifying a class
 * loader or by specifying <code>null</code> for the class loader, then the
 * caller must be granted {@link
 * RuntimePermission}<code>("getClassLoader")</code> in order to evaluate an
 * entry that refers to <code>$loader</code>. Subclasses can provide support
 * for additional special entry expressions by supplying implementations of
 * {@link #getSpecialEntryType getSpecialEntryType} and {@link #getSpecialEntry
 * getSpecialEntry}. <p>
 *
 * Entry expressions may also refer to other entries by name, using the simple
 * entry name for entries within the same component, and the fully qualified
 * entry name for entries in any component. A fully qualified name for which
 * there is both an entry and a valid static field is interpreted as referring
 * to the entry. An unqualified entry name for which there is an entry within
 * the same component and is specified as a special entry expression will be
 * interpreted as referring to the entry within that component. <p>
 *
 * Calls to the following methods are prohibited in order to avoid incorrect
 * behavior because of their reliance on determining the
 * <code>ClassLoader</code> or <code>AccessControlContext</code> of the caller:
 *
 * <ul>
 * <li> <code>java.lang.Class.forName</code>
 * <li> <code>java.lang.ClassLoader.getSystemClassLoader</code>
 * <li> <code>java.lang.Package.getPackage</code>
 * <li> <code>java.lang.Package.getPackages</code>
 * <li> <code>java.lang.System.load</code>
 * <li> <code>java.lang.System.loadLibrary</code>
 * <li> <code>java.security.AccessController.doPrivileged</code>
 * <li> <code>java.sql.DriverManager.deregisterDriver</code>
 * <li> <code>java.sql.DriverManager.getConnection</code>
 * <li> <code>java.sql.DriverManager.getDriver</code>
 * <li> <code>java.sql.DriverManager.getDrivers</code>
 * <li> <code>net.jini.security.Security.doPrivileged</code>
 * </ul>
 *
 * Attempting to evaluate an entry that calls any of these methods results in
 * <code>ConfigurationException</code> being thrown. Additional prohibited
 * methods may be specified by providing a resource named
 * "net/jini/config/resources/ConfigurationFile.moreProhibitedMethods". Each
 * line in the resource file should specify an additional prohibited method,
 * represented by the fully qualified class name, a <code>'.'</code>, and the
 * method name for an additional prohibited method, with no spaces. The
 * resource file must be encoded in UTF-8. <p>
 *
 * Any syntax error or problem reading from the configuration source or an
 * override option results in a <code>ConfigurationException</code> being
 * thrown. <p>
 *
 * If there is a security manager, the configuration source refers to the
 * members of a class, and the class is in a named package, then this class
 * calls the security manager's {@link SecurityManager#checkPackageAccess
 * checkPackageAccess} method with the class's package. Making this call in
 * <code>ConfigurationFile</code> insures that the check is made despite any
 * decisions within reflection to skip the check based on the class of the
 * caller, which in this case will be <code>ConfigurationFile</code> rather
 * than its caller. Note that implementations are permitted to make calls to
 * reflection to access class members at arbitrary stack depths relative to
 * that of the caller of <code>ConfigurationFile</code>; applications using
 * security managers with custom implementations of the {@link
 * SecurityManager#checkMemberAccess checkMemberAccess} method should take this
 * behavior into account.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * This implementation uses the {@link Logger} named
 * <code>net.jini.config</code> to log information at the following logging
 * levels: <p>
 *
 * <table border="1" cellpadding="5" summary="Describes logging performed by
 *	  the ConfigurationFile class at different logging levels">
 *
 * <caption halign="center" valign="top"><b><code>
 *	    net.jini.config</code></b></caption>
 *
 * <tr> <th scope="col"> Level <th scope="col"> Description
 *
 * <tr> <td> {@link Level#INFO INFO} <td> problems adding new prohibited
 *	methods
 *
 * <tr> <td> {@link Levels#FAILED FAILED} <td> problems getting entries,
 *	including getting entries that are not found
 *
 * <tr> <td> {@link Level#FINE FINE} <td> returning default values
 *
 * <tr> <td> {@link Level#FINER FINER} <td> creating an instance of this class,
 *	getting existing entries, or adding new prohibited methods
 *
 * </table>
 */
public class ConfigurationFile extends AbstractConfiguration {

    /* -- Fields -- */

    /**
     * The primitive types, in increasing order with respect to widening
     * conversions.
     */
    private static final Class[] primitives = {
	Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE,
	Long.TYPE, Float.TYPE, Double.TYPE
    };

    /** Index of Integer.TYPE in primitives. */
    private static final int INT_INDEX = 4;

    /** Index of Byte.TYPE in primitives. */
    private static final int BYTE_INDEX = 1;

    /**
     * The binary names of public, static methods that may not be called from
     * within a configuration source, for all overloads.  These methods are
     * prohibited because they depend on knowing the class loader or access
     * control context of their caller, which will be those of
     * ConfigurationFile rather than of the caller of the ConfigurationFile
     * constructor or getEntry.
     *
     * XXX: Update list if new methods like these are added.  Find these
     * methods by searching for ClassLoader.getCallerClassLoader,
     * System.getCallerClass or Reflection.getCallerClass, which are used to
     * make permission checks based on the caller's class loader.  Note that
     * there would be more kinds of security holes if ConfigurationFile
     * permitted calls to instance methods.  -tjb[5.Aug.2002]
     */
    private static final Set prohibitedMethods =
	new HashSet(Arrays.asList(new String[] {
	    "java.lang.Class.forName",
	    "java.lang.ClassLoader.getSystemClassLoader",
	    "java.lang.Package.getPackage",
	    "java.lang.Package.getPackages",
	    "java.lang.System.load",
	    "java.lang.System.loadLibrary",
	    "java.security.AccessController.doPrivileged",
	    "java.sql.DriverManager.deregisterDriver",
	    "java.sql.DriverManager.getConnection",
	    "java.sql.DriverManager.getDriver",
	    "java.sql.DriverManager.getDrivers",
	    "net.jini.security.Security.doPrivileged" }));

    /** The name of the resource that specifies more prohibited methods. */
    private static final String moreProhibitedMethods =
	"net/jini/config/resources/ConfigurationFile.moreProhibitedMethods";

    /* Add names of more prohibited methods. */
    static {
	InputStream in = null;
	try {
	    ClassLoader cl = ConfigurationFile.class.getClassLoader();
	    if (cl == null) {
		cl = Utilities.bootstrapResourceLoader;
	    }
	    in = cl.getResourceAsStream(moreProhibitedMethods);
	    if (in != null) {
		BufferedReader reader =
		    new BufferedReader(new InputStreamReader(in, "utf-8"));
		while (true) {
		    String line = reader.readLine();
		    if (line == null) {
			break;
		    }
		    logger.log(Level.FINER, "Adding prohibited method: {0}",
			       line);
		    prohibitedMethods.add(line);
		}
	    }
	} catch (IOException e) {
	    logger.log(
		Level.INFO, "Problem reading prohibited methods resource", e);
	} finally {
	    if (in != null) {
		try {
		    in.close();
		} catch (IOException e) {
		}
	    }
	}
    }

    /** Returns the current context class loader. */
    private static final PrivilegedAction contextClassLoader =
	new PrivilegedAction() {
	    public Object run() {
		return Thread.currentThread().getContextClassLoader();
	    }
	};

    /** Permission needed to get the context class loader. */
    static final RuntimePermission getClassLoaderPermission =
	new RuntimePermission("getClassLoader");

    /** Map from entry names to Entry instances. */
    final Map entries = new HashMap(11);

    /**
     * Map of simple class names to the full class names that were explicitly
     * imported.
     */
    final Map classImports = new HashMap(1);

    /**
     * List of packages or classes whose member classes should be imported on
     * demand.
     */
    final List onDemandImports = new ArrayList(1);

    /**
     * The location of the configuration source, or null if not specified.
     */
    private String location;

    /**
     * The override being parsed, or zero if not parsing an override.
     */
    private int override = 0;

    /** The class loader to use to resolve classes. */
    private final ClassLoader cl;

    /** Whether a non-null class loader was supplied. */
    private final boolean nonNullLoaderSupplied;

    /** Lock for synchronizing calls to resolve entries. */
    private final Object resolveLock = new Object();

    /* -- Classes -- */

    /**
     * Defines a StreamTokenizer that resets sval, nval, and lineno when the
     * pushBack method is called.
     */
    private static class PushbackStreamTokenizer extends StreamTokenizer {
	private boolean gotToken;
	private boolean pushedBack;
	private double savedNval;
	private String savedSval;
	private int savedTtype;
	private int savedLineno;

	PushbackStreamTokenizer(Reader reader) {
	    super(reader);
	}

	public int nextToken() throws IOException {
	    if (pushedBack) {
		/* Use values saved by pushBack */
		nval = savedNval;
		sval = savedSval;
		ttype = savedTtype;
		pushedBack = false;
	    } else {
		/* Save values for pushBack */
		savedNval = nval;
		savedSval = sval;
		savedTtype = ttype;
		savedLineno = lineno();
		super.nextToken();
		gotToken = true;
	    }
	    return ttype;
	}

	public void pushBack() {
	    if (gotToken && !pushedBack) {
		/*
		 * Save values for next nextToken call and reestablish previous
		 * values.
		 */
		double tempNval = savedNval;
		savedNval = nval;
		nval = tempNval;
		String tempSval = savedSval;
		savedSval = sval;
		sval = tempSval;
		int tempTtype = savedTtype;
		savedTtype = ttype;
		ttype = tempTtype;
		pushedBack = true;
	    }
	}

	public int lineno() {
	    /* Use the previous value if pushed back */
	    return pushedBack ? savedLineno : super.lineno();
	}
    }

    /** Base class to represent parse results. */
    private abstract class ParseNode {

	/* The line number of the text parsed -- for error reporting */
	final int lineno;

	/*
	 * The override of the text parsed, or zero if not an override -- for
	 * error reporting.
	 */
	final int override;

	ParseNode(int lineno) {
	    this.lineno = lineno;
	    this.override = ConfigurationFile.this.override;
	}

	/**
	 * Calculates and returns the declared type of the parse node, as
	 * referred to by the specified entry.
	 */
	abstract Class resolve(Entry inEntry) throws ConfigurationException;

	/** Returns true if the value is a constant. */
	abstract boolean isConstant() throws ConfigurationException;

	/**
	 * Returns the result of evaluating the parse node with the specified
	 * data provided by the call to getEntry.
	 */
	abstract Object eval(Object data) throws ConfigurationException;

	/**
	 * Throws a ConfigurationException for the an error described by the
	 * what argument, using the line number and override for this parse
	 * node.
	 */
	void oops(String what) throws ConfigurationException {
	    ConfigurationFile.this.oops(what, lineno, override, null);
	}

	/**
	 * Throws a ConfigurationException for the an error described by the
	 * what argument, at the specified line number, with the override for
	 * this parse node.
	 */
	void oops(String what, int lineno) throws ConfigurationException {
	    ConfigurationFile.this.oops(what, lineno, override, null);
	}

	/**
	 * Throws a ConfigurationException for the an error described by the
	 * what argument, using the line number and override for this parse
	 * node, and caused by the specified exception, which may be null.
	 */
	void oops(String what, Throwable t) throws ConfigurationException {
	    ConfigurationFile.this.oops(what, lineno, override, t);
	}
    }	

    /** Represents an entry. */
    private class Entry extends ParseNode {
	final String component;
	final String fullName;
	private final ParseNode node;
	final boolean isPrivate;
	final boolean isStatic;
	final boolean isOverride;
	private Class type;
	private boolean resolved;	/* resolve done */
	private boolean resolving;	/* resolve in progress */
	private boolean isConstant;
	private boolean refersToData;	/* computed in parse and resolve */
	private boolean evaluated;	/* static eval done */
	private boolean evaluating;	/* static eval in progress */
	private Object value;

	Entry(String component,
	      String fullName,
	      boolean isPrivate,
	      boolean isStatic,
	      boolean isOverride,
	      int lineno,
	      Parser parser)
	    throws ConfigurationException, IOException
	{
	    super(lineno);
	    this.component = component;
	    this.fullName = fullName;
	    this.isPrivate = isPrivate;
	    this.isStatic = isStatic;
	    this.isOverride = isOverride;
	    node = parser.parseExpr(this);
	}

	boolean getRefersToData() {
	    return refersToData;
	}

	void setRefersToData() {
	    refersToData = true;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    /*
	     * Grab a single lock when resolving any entry.  Until we know that
	     * the entry contains no textually circular references, which is
	     * determined during this resolve step, there could be a deadlock
	     * if two threads were to lock different entries at the same time
	     * that refer to each other.  -tjb[5.Sep.2002]
	     */
	    synchronized (resolveLock) {
		if (!resolved) {
		    if (resolving) {
			oops("entry with circular reference: " + fullName);
		    }
		    resolving = true;
		    try {
			type = node.resolve(this);
			isConstant = node.isConstant();
		    } finally {
			resolving = false;
		    }
		    resolved = true;
		}
	    }
	    return type;
	}

	boolean isConstant() throws ConfigurationException {
	    resolve(this);
	    return isConstant;
	}

	Object eval(Object data) throws ConfigurationException {
	    resolve(this);
	    if (!isStatic) {
		return node.eval(data);
	    }
	    /*
	     * Grab a separate lock when evaluating each static entry.  Using
	     * separate locks permits entries to be evaluated simultaneously if
	     * they don't refer to each other, which is important if the entry
	     * expressions perform network or other long running operations.
	     * Note that there is still the possibility of a deadlock if there
	     * is a circular dependency between static entries via recursive
	     * calls to getEntry.  -tjb[5.Sep.2002]
	     */
	    synchronized (this) {
		if (!evaluated) {
		    if (evaluating) {
			oops("entry with circular reference: " + fullName);
		    }
		    evaluating = true;
		    try {
			value = node.eval(NO_DATA);
		    } finally {
			evaluating = false;
		    }
		    evaluated = true;
		}
	    }
	    return value;
	}
    }

    /**
     * Represents a reference to an entry, data specified by getEntry, or a
     * field.
     */
    private class NameRef extends ParseNode {
	private final String name;
	private final String fullName; /* The fully qualified entry name */
	private Entry entry;
	private Field field;

	NameRef(String component, String name, int lineno) {
	    super(lineno);
	    this.name = name;
	    fullName = name.indexOf('.') < 0 ? component + '.' + name : name;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    entry = (Entry) entries.get(fullName);
	    if (entry != null) {
		Class result = entry.resolve(inEntry);
		if (entry.getRefersToData()) {
		    if (inEntry.isStatic) {
			oops("static entry '" + inEntry.fullName +
			     "' cannot refer to entry '" + fullName +
			     "', which uses '$data'");
		    }
		    inEntry.setRefersToData();
		}
		return result;
	    } else if (name.equals("$data")) {
		return Object.class;
	    } else if (name.equals("$loader")) {
		return ClassLoader.class;
	    } else if (name.startsWith("$") && name.indexOf('.') == -1) {
		try {
		    return getSpecialEntryType(name);
		} catch (NoSuchEntryException e) {
		    oops("entry not found: " + name, lineno);
		} catch (ConfigurationException e) {
		    oops("problem referring to entry '" + name + "'", e);
		} catch (RuntimeException e) {
		    oops("problem referring to entry '" + name + "'", e);
		}
		return null;	/* Not reached */
	    } else {
		field = findField(name, lineno, override);
		return field.getType();
	    }
	}

	boolean isConstant() throws ConfigurationException {
	    /*
	     * There's no way to tell that a field is a compile-time constant,
	     * so only return true for entries.
	     */
	    return entry != null && entry.isConstant();
	}

	Object eval(Object data) throws ConfigurationException {
	    if (entry != null) {
		return entry.eval(data);
	    } else if (field != null) {
		try {
		    return field.get(null);
		} catch (IllegalAccessException e) {
		    oops("problem accessing field '" + name + "'", e);
		    return null; /* Not reached */
		}
	    } else if (name.equals("$data")) {
		if (data == NO_DATA) {
		    oops("no data specified for '$data'");
		}
		return data;
	    } else if (name.equals("$loader")) {
		if (!nonNullLoaderSupplied) {
		    SecurityManager sm = System.getSecurityManager();
		    if (sm != null) {
			sm.checkPermission(getClassLoaderPermission);
		    }
		}
		return cl;
	    } else {
		try {
		    return getSpecialEntry(name);
		} catch (ConfigurationException e) {
		    oops("problem referring to entry '" + name + "'", e);
		} catch (RuntimeException e) {
		    oops("problem referring to entry '" + name + "'", e);
		}
		return null;	/* Not reached */
	    }
	}
    }

    /** Represents a simple literal value. */
    private class Literal extends ParseNode {
	private final Class type;
	private final Object value;

	Literal(Class type, Object value, int lineno) {
	    super(lineno);
	    this.type = type;
	    this.value = value;
	}

	Class resolve(Entry inEntry) {
	    return type;
	}

	boolean isConstant() {
	    return true;
	}

	Object eval(Object data) {
	    return value;
	}
    }

    /** Represents a reference to this ConfigurationFile. */
    private class ThisRef extends Literal {
	ThisRef(int lineno) {
	    super(ConfigurationFile.class, ConfigurationFile.this, lineno);
	}
    }

    /** Represents a String literal. */
    private class StringLiteral extends ParseNode {
	private final String value;

	StringLiteral(String value, int lineno) {
	    super(lineno);
	    this.value = value;
	}

	Class resolve(Entry inEntry) {
	    return String.class;
	}

	boolean isConstant() {
	    return value.indexOf("${") < 0;
	}

	Object eval(Object data) throws ConfigurationException {
	    return expandStringProperties(value, lineno);
	}
    }

    /** Represents a Class literal. */
    private class ClassLiteral extends ParseNode {
	private final String className;
	private final boolean isArray;
	private Class value;

	ClassLiteral(String className, boolean isArray, int lineno) {
	    super(lineno);
	    this.className = className;
	    this.isArray = isArray;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    Class c = findClass(className, lineno, override);
	    if (isArray) {
		c = Array.newInstance(c, 0).getClass();
	    }
	    value = c;
	    return Class.class;
	}

	boolean isConstant() {
	    return true;
	}

	Object eval(Object data) {
	    return value;
	}
    }

    /** Represents a cast. */
    private class Cast extends ParseNode {
	private final String typeName;
	private final boolean isArray;
	private final ParseNode arg;
	private Class type;

	Cast(String typeName, boolean isArray, ParseNode arg, int lineno) {
	    super(lineno);
	    this.typeName = typeName;
	    this.isArray = isArray;
	    this.arg = arg;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    type = findClass(typeName, lineno, override);
	    if (isArray) {
		type = Array.newInstance(type, 0).getClass();
	    }
	    Class argType = arg.resolve(inEntry);
	    if (type == argType) {
		return type;
	    } else if (type.isPrimitive()) {
		if (type != Boolean.TYPE &&
		    argType != Boolean.TYPE &&
		    argType != null &&
		    argType.isPrimitive())
		{
		    /* Cast primitive to primitive, not boolean */
		    return type;
		}
	    } else if (argType == null ||
		       type.isAssignableFrom(argType) ||
		       argType.isAssignableFrom(type))
	    {
		/* Null source, or source and target are related */
		return type;
	    } else if (type.isInterface() && argType.isInterface()) {
		if (compatibleMethods(type, argType)) {
		    /* Both interfaces, compatible methods */
		    return type;
		}
	    } else if ((type.isInterface() &&
			!argType.isArray() &&
			!Modifier.isFinal(argType.getModifiers())) ||
		       (argType.isInterface() &&
			!type.isArray() &&
			!Modifier.isFinal(type.getModifiers())))
	    {
		/* Non-final class and interface */
		return type;
	    }
	    oops("cannot convert '" + Utilities.typeString(argType) +
		 "' to '" + Utilities.typeString(type) + "'");
	    return null;	/* Not reached */
	}

	boolean isConstant() throws ConfigurationException {
	    return arg.isConstant();
	}

	Object eval(Object data) throws ConfigurationException {
	    Object val = arg.eval(data);
	    if (type.isPrimitive()) {
		if (Utilities.getPrimitiveType(val.getClass()) != type) {
		    val = convertPrimitive(val, type);
		}
	    } else if (val != null && !type.isInstance(val)) {
		oops("cannot convert '" +
		     Utilities.typeString(val.getClass()) + "' to '" +
		     Utilities.typeString(type) + "'");
	    }
	    return val;
	}
    }    

    /** Describes a call to a constructor, method, or array constructor. */
    private abstract class Call extends ParseNode {
	final ParseNode[] args;

	Call(ParseNode[] args, int lineno) {
	    super(lineno);
	    this.args = args;
	}

	boolean isConstant() {
	    return false;
	}

	/** Returns the declared types of the argument expressions. */
	Class[] resolveArgs(Entry inEntry) throws ConfigurationException {
	    Class[] types = new Class[args.length];
	    for (int i = 0; i < args.length; i++) {
		types[i] = args[i].resolve(inEntry);
	    }
	    return types;
	}

	/** Evaluates the arguments. */
	Object[] evalArgs(Object data) throws ConfigurationException {
	    Object[] result = new Object[args.length];
	    for (int i = 0; i < args.length; i++) {
		result[i] = args[i].eval(data);
	    }
	    return result;
	}
    }

    /** Describes a Constructor call */
    private class ConstructorCall extends Call {

	/** The name of the class to construct */
	private final String typeName;

	private Constructor constructor;

	ConstructorCall(String typeName, ParseNode[] args, int lineno) {
	    super(args, lineno);
	    this.typeName = typeName;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    constructor = findConstructor(
		typeName, resolveArgs(inEntry), lineno, override);
	    return constructor.getDeclaringClass();
	}

	Object eval(Object data) throws ConfigurationException {
	    Object[] evaluatedArgs = evalArgs(data);
	    Throwable except;
	    try {
		return constructor.newInstance(evaluatedArgs);
	    } catch (InvocationTargetException e) {
		except = e.getTargetException();
		if (except instanceof Error) {
		    throw (Error) except;
		}
	    } catch (Exception e) {
		except = e;
	    }
	    oops("problem invoking constructor for " + typeName, except);
	    return null;	/* Not reached */
	}
    }

    /** Describes a method call. */
    private class MethodCall extends Call {

	/** The full name of the method */
	final String fullName;

	private Method method;

	MethodCall(String fullName, ParseNode[] args, int lineno) {
	    super(args, lineno);
	    this.fullName = fullName;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    method = findMethod(
		fullName, resolveArgs(inEntry), lineno, override);
	    Class c = method.getReturnType();
	    if (c == Void.TYPE) {
		oops("method has void return type: " + fullName);
	    }
	    return c;
	}

	Object eval(Object data) throws ConfigurationException {
	    Object[] evaluatedArgs = evalArgs(data);
	    Throwable except;
	    try {
		return method.invoke(null, evaluatedArgs);
	    } catch (InvocationTargetException e) {
		except = e.getTargetException();
		if (except instanceof Error) {
		    throw (Error) except;
		}
	    } catch (Exception e) {
		except = e;
	    }
	    oops("problem invoking method " + fullName, except);
	    return null;	/* Not reached */
	}
    }

    /** Represents constructing and initializing a single dimensional array. */
    private class ArrayConstructor extends Call {

	/* The name of the element type. */
	private final String typeName;

	/* The element type, when resolved. */
	private Class type;

	ArrayConstructor(String typeName, ParseNode[] args, int lineno) {
	    super(args, lineno);
	    this.typeName = typeName;
	}

	Class resolve(Entry inEntry) throws ConfigurationException {
	    type = findClass(typeName, lineno, override);
	    Class[] types = resolveArgs(inEntry);
	    for (int i = 0; i < types.length; i++) {
		if (!assignable(types[i], type)) {
		    if (args[i] instanceof Literal) {
			/* Handle primitive narrowing conversion */
			Object narrow =
			    narrowingAssignable(args[i].eval(NO_DATA), type);
			if (narrow != null) {
			    args[i] = new Literal(
				Utilities.getPrimitiveType(narrow.getClass()),
				narrow, args[i].lineno);
			    continue;
			}
		    }
		    oops("array element " + i + " has incorrect type",
			 args[i].lineno);
		}
	    }
	    return Array.newInstance(type, 0).getClass();
	}

	Object eval(Object data) throws ConfigurationException {
	    Object[] contents = evalArgs(data);
	    Object result = Array.newInstance(type, args.length);
	    for (int i = 0; i < contents.length; i++) {
		Object value = contents[i];
		Class valueType = value == null ? null : value.getClass();
		if (!type.isPrimitive() && !assignable(valueType, type)) {
		    /* Runtime array assignment check for Object arrays. */
		    oops("array element " + i + " has incorrect type",
			 args[i].lineno);
		}
		Array.set(result, i, value);
	    }
	    return result;
	}
    }   

    /**
     * Parses the input from the specified Reader and options, storing
     * information about imports and entries by side effect.
     */
    private class Parser {

	/** The tokenizer to use for parsing */
	private StreamTokenizer st;

	/**
	 * Adds the imports and entries parsed from the specified input stream,
	 * as well as overrides parsed from the specified options, starting
	 * with the second element.
	 */
	Parser(Reader reader, String[] options) throws ConfigurationException {
	    try {
		onDemandImports.add("java.lang");
		if (reader != null) {
		    if (!(reader instanceof BufferedReader)) {
			reader = new BufferedReader(reader);
		    }
		    createTokenizer(reader);
		    parseSource();
		}
		for (int i = 1; i < options.length; i++) {
		    override = i;
		    createTokenizer(new StringReader(options[i]));
		    parseOverride();
		}
	    } catch (IOException e) {
		throw new ConfigurationException(
		    (location == null ? "" : location + ": ") +
		    "problem reading configuration file",
		    e);
	    } finally {
		override = 0;
	    }
	}

	/** Creates and sets the tokenizer using the specified reader. */
	private void createTokenizer(Reader reader) {
	    reader = new UnicodeEscapesDecodingReader(reader);
	    st = new PushbackStreamTokenizer(reader);
	    st.ordinaryChar('.');
	    st.wordChars('.', '.');
	    st.ordinaryChars('0', '9');
	    st.wordChars('0', '9');
	    st.ordinaryChar('-');
	    st.wordChars('-', '-');
	    st.wordChars('_', '_');
	    st.wordChars('$', '$');
	    st.ordinaryChar('/');
	    st.slashSlashComments(true);
	    st.slashStarComments(true);
	}

	/**
	 * Parses imports and components from the source, and stores the
	 * results.
	 */
	private void parseSource() throws ConfigurationException, IOException {
	    while (true) {
		int t = st.nextToken();
		if (t == StreamTokenizer.TT_EOF) {
		    break;
		}
		st.pushBack();
		String val = token(
		    "keyword 'import', 'static' or 'private', or component");
		if ("import".equals(val)) {
		    parseImport();
		} else {
		    parseComponent(val);
		}
	    }
	}

	/** Parses an Import, updating classImports and onDemandImports. */
	private void parseImport() throws ConfigurationException, IOException {
	    if (!entries.isEmpty()) {
		oops("import follows entry");
	    }
	    /* Include asterisks */
	    st.wordChars('*', '*');
	    String value = token("import package or type");
	    st.ordinaryChar('*');
	    boolean onDemand = value.endsWith(".*");
	    if (onDemand) {
		value = value.substring(0, value.length() - 2);
	    }
	    if (!validQualifiedIdentifier(value)) {
		syntax("import package or type");
	    }
	    token(';');
	    if (onDemand) {
		if (!onDemandImports.contains(value)) {
		    Class c = findClassNoImports(value, st.lineno(), override);
		    if (c != null && c.getName().indexOf('.') < 0) {
			oops("import from unnamed package");		    
		    }
		    onDemandImports.add(value);
		}
	    } else {
		int dot = value.lastIndexOf('.');
		if (dot < 0) {
		    oops("import from unnamed package");
		}
		Class c = findClassNoImports(value, st.lineno(), override);
		if (c == null) {
		    oops("class not found: " + value);
		} else if (c.getName().indexOf('.') < 0) {
		    oops("import from unnamed package");
		}
		String simple = value.substring(dot + 1);
		if (classImports.containsKey(simple) &&
		    !classImports.get(simple).equals(value))
		{
		    oops("conflicting imports: " + classImports.get(simple) +
			 " and " + value);
		}
		classImports.put(simple, value);
	    }
	}

	/** Parses a Component. */
	private void parseComponent(String component)
	    throws ConfigurationException, IOException
	{
	    if (!validQualifiedIdentifier(component)) {
		oops("invalid component: '" + component + "'");
	    }
	    token('{');
	    while (true) {
		int t = st.nextToken();
		if (t == StreamTokenizer.TT_WORD) {
		    parseEntry(component, st.sval);
		} else if (t == '}') {
		    break;
		} else {
		    syntax("'}', keyword 'static' or 'private', " +
			   "or entry name");
		}
	    }
	}

	/** Parses an Entry. */
	private void parseEntry(String component, String name)
	    throws ConfigurationException, IOException
	{
	    boolean isPrivate = false;
	    boolean isStatic = false;
	    while (true) {
		if ("private".equals(name)) {
		    if (isPrivate) {
			oops("duplicate 'private'");
		    }
		    isPrivate = true;
		} else if ("static".equals(name)) {
		    if (isStatic) {
			oops("duplicate 'static'");
		    }
		    isStatic = true;
		} else {
		    break;
		}
		name = token("keyword 'static' or 'private', or entry name");
	    }
	    if (!validIdentifier(name)) {
		oops("illegal entry name: " + name);
	    }
	    String fullName = component + '.' + name;
	    if (entries.containsKey(fullName)) {
		oops("duplicate entry name: " + name);
	    }
	    token('=');
	    Entry entry = new Entry(component, fullName, isPrivate, isStatic,
				    false /* isOverride */, st.lineno(), this);
	    token(';');
	    entries.put(fullName, entry);
	}

	/** Parses an Override . */
	private void parseOverride()
	    throws ConfigurationException, IOException
	{
	    boolean isPrivate = false;
	    boolean isStatic = false;
	    String name;
	    while (true) {
		name = token("keyword 'static' or 'private', " +
			     "or fully qualified entry name");
		if ("private".equals(name)) {
		    if (isPrivate) {
			oops("duplicate 'private'");
		    }
		    isPrivate = true;
		} else if ("static".equals(name)) {
		    if (isStatic) {
			oops("duplicate 'static'");
		    }
		    isStatic = true;
		} else {
		    break;
		}
	    }
	    int dot = name.lastIndexOf('.');
	    if (!validQualifiedIdentifier(name) || dot < 0) {
		syntax("fully qualified entry name");
	    }
	    Entry entry = (Entry) entries.get(name);
	    if (entry != null && entry.isOverride) {
		oops("duplicate override: " + name);
	    }
	    token('=');
	    String component = name.substring(0, dot);
	    entry = new Entry(component, name, isPrivate, isStatic,
			      true /* isOverride */, st.lineno(), this);
	    int t = st.nextToken();
	    if (t != StreamTokenizer.TT_EOF) {
		syntax("no more characters");
	    }
	    entries.put(name, entry);
	}

	/** Parses an ExprList and the trailing token specified by close. */
	private ParseNode[] parseArgs(Entry inEntry, char close)
	    throws ConfigurationException, IOException
	{
	    List args = new ArrayList(1);
	    int t = st.nextToken();
	    if (t != close) {
		st.pushBack();
		while (true) {
		    args.add(parseExpr(inEntry));
		    t = st.nextToken();
		    if (t == close) {
			break;
		    } else if (t != ',') {
			syntax("',' or '" + close + "'");
		    }
		}
	    }
	    return (ParseNode[]) args.toArray(new ParseNode[args.size()]);
	}

	/** Parses an Expr. */
	ParseNode parseExpr(Entry inEntry)
	    throws ConfigurationException, IOException
	{
	    int t = st.nextToken();
	    String val = st.sval;
	    if (t == '\'') {
		if (val.length() != 1) {
		    oops("invalid character: '" + val + "'");
		}
		return new Literal(Character.TYPE,
				   new Character(val.charAt(0)), st.lineno());
	    } else if (t == '"') {
		return new StringLiteral(val, st.lineno());
	    } else if (t == '(') {
		int lineno = st.lineno();
		String type = token("type");
		t = st.nextToken();
		boolean isArray = false;
		if (t == '[') {
		    isArray = true;
		    token(']');
		    t = st.nextToken();
		}
		if (t != ')') {
		    syntax("')'");
		}
		return new Cast(type, isArray, parseExpr(inEntry), lineno);
	    }
	    st.pushBack();
	    val = token("expression");
	    if (Character.isDigit(val.charAt(0)) || val.charAt(0) == '-') {
		return getNumber(val);
	    } else if ("true".equals(val)) {
		return new Literal(Boolean.TYPE, Boolean.TRUE, st.lineno());
	    } else if ("false".equals(val)) {
		return new Literal(Boolean.TYPE, Boolean.FALSE, st.lineno());
	    } else if ("null".equals(val)) {
		return new Literal(null, null, st.lineno());
	    } else if ("new".equals(val)) {
		return parseNewInstance(inEntry);
	    } else if ("this".equals(val)) {
		return new ThisRef(st.lineno());
	    } else if (val.endsWith(".class")) {
		val = val.substring(0, val.length() - 6);
		if (findPrimitiveClass(val) == null &&
		    !validQualifiedIdentifier(val))
		{
		    oops("illegal class name: " + val);
		}
		return new ClassLiteral(val, false, st.lineno());
	    }
	    t = st.nextToken();
	    if (t == '[') {
		int lineno = st.lineno();
		token(']');
		if (!".class".equals(token("'.class'"))) {
		    syntax("'.class'");
		} else if (findPrimitiveClass(val) == null &&
			   !validQualifiedIdentifier(val))
		{
		    oops("illegal class name: " + val);
		}
		return new ClassLiteral(val, true, lineno);
	    }
	    st.pushBack();
	    if (!validQualifiedIdentifier(val)) {
		if (t == '(') {
		    oops("illegal method name: " + val);
		} else {
		    oops("illegal field or entry name: " + val);
		}
	    }
	    if (t == '(') {
		return parseMethodCall(inEntry, val);
	    } else {
		if ("$data".equals(val)) {
		    if (inEntry.isStatic) {
			oops("cannot use '$data' in a static entry");
		    }
		    inEntry.setRefersToData();
		}
		return new NameRef(inEntry.component, val, st.lineno());
	    }
	}

	/**
	 * Parses a numeric literal and returns the value as a Literal.
	 */
	private Literal getNumber(String val)
	    throws ConfigurationException, IOException
	{
	    char c = val.charAt(val.length() - 1);
	    try {
		if (c == 'l' || c == 'L') {
		    return new Literal(
			Long.TYPE,
			Long.decode(val.substring(0, val.length() - 1)),
			st.lineno());
		} else if (c == 'f' || c == 'F') {
		    return new Literal(
			Float.TYPE,
			Float.valueOf(val.substring(0, val.length() - 1)),
			st.lineno());
		} else if (c == 'd' || c == 'D') {
		    return new Literal(
			Double.TYPE,
			Double.valueOf(val.substring(0, val.length() - 1)),
			st.lineno());
		} else if (val.indexOf('e') > 0 ||
			   val.indexOf('E') > 0 ||
			   val.indexOf('.') > 0)
		{
		    return new Literal(
			Double.TYPE, Double.valueOf(val), st.lineno());
		} else {
		    return new Literal(
			Integer.TYPE, Integer.decode(val), st.lineno());
		}
	    } catch (NumberFormatException e) {
		oops("bad numeric literal: " + val);
		return null;	// Not reached
	    }
	}

	/**
	 * Parses a NewExpr except for the leading "new", and returns the
	 * constructed object.
	 */
	private ParseNode parseNewInstance(Entry inEntry)
	    throws ConfigurationException, IOException
	{
	    int lineno = st.lineno();
	    String typeName = token("type name");
	    int t = st.nextToken();
	    if (t == '[') {
		token(']');
		token('{');
		return new ArrayConstructor(
		    typeName, parseArgs(inEntry, '}'), lineno);
	    } else if (t != '(') {
		syntax("'(' or '['");
	    }
	    return new ConstructorCall(
		typeName, parseArgs(inEntry, ')'), lineno);
	}

	/**
	 * Resolves a static method call and returns a MethodCall instance that
	 * describes the method and arguments for the call.
	 */
	private MethodCall parseMethodCall(Entry inEntry, String name)
	    throws ConfigurationException, IOException
	{
	    if (!validQualifiedIdentifier(name) || name.indexOf('.') < 0) {
		syntax("method name");
	    }
	    int lineno = st.lineno();
	    token('(');
	    return new MethodCall(name, parseArgs(inEntry, ')'), lineno);
	}
    
	/**
	 * Parses the next token from the stream, and generates a syntax error
	 * if the token does not equal the specified character.  Expands
	 * references to system properties if the token is a String.
	 */
	private void token(char c) throws ConfigurationException, IOException {
	    int t = st.nextToken();
	    if (t != c) {
		if (c == '"') {
		    syntax("a String");
		} else {
		    syntax(new String(new char[]{'\'', c, + '\''}));
		}
	    }
	}

	/**
	 * Parses the next token from the stream, and generates a syntax error
	 * if the token is not a TT_WORD.  The what argument describes what
	 * kind of token is expected.  Combines multiple TT_WORD tokens if they
	 * are separated by '.' at the boundaries and are not reserved words.
	 */
	private String token(String what)
	    throws ConfigurationException, IOException
	{
	    int t = st.nextToken();
	    if (t != StreamTokenizer.TT_WORD) {
		syntax(what);
	    }
	    if (st.sval.indexOf('.') >= 0 ||
		validIdentifier(st.sval) ||
		findPrimitiveClass(st.sval) != null)
	    {
		String result = st.sval;
		while (true) {
		    int p = st.nextToken();
		    if (p != StreamTokenizer.TT_WORD ||
			(!result.endsWith(".") && !st.sval.startsWith(".")))
		    {
			st.pushBack();
			break;
		    }
		    result += st.sval;
		}
		st.sval = result;
	    }
	    return st.sval;
	}

	/**
	 * Throws a ConfigurationException for a syntax error described by the
	 * what argument.
	 */
	private void syntax(String what) throws ConfigurationException {
	    oops("expected " + what + ", found " + describeCurrentToken());
	}

	/** Returns a String that describes the current token. */
	private String describeCurrentToken() {
	    switch (st.ttype) {
	    case StreamTokenizer.TT_EOF:
		return "end of file";
	    case StreamTokenizer.TT_EOL:
		return "end of line";
	    case StreamTokenizer.TT_WORD:
		return "'" + st.sval + "'";
	    case StreamTokenizer.TT_NUMBER:
		return "'" + st.nval + "'";
	    case '"':
		return "\"" + st.sval + "\"";
	    case '\'':
		return "'" + ((char) st.sval.charAt(0)) + "'";
	    default:
		return "'" + ((char) st.ttype) + "'";
	    }
	}

	/**
	 * Throws a ConfigurationException for an error described by the what
	 * argument.
	 */
	private void oops(String what) throws ConfigurationException {
	    ConfigurationFile.this.oops(what, st.lineno(), override, null);
	}
    }

    /* -- Constructors -- */

    /**
     * Creates an instance containing the entries specified by the options,
     * using the calling thread's context class loader for interpreting class
     * names. The first option is a file or URL specifying the location of the
     * configuration source; no source is specified if <code>options</code> is
     * <code>null</code>, empty, or has <code>"-"</code> as the first
     * element. The remaining options specify values for individual entries,
     * overriding any matching entries supplied in the configuration source.
     *
     * @param options an array whose first element is the location of the
     * configuration source and remaining elements specify override values for
     * entries
     * @throws ConfigurationNotFoundException if the specified source location
     * cannot be found
     * @throws ConfigurationException if <code>options</code> is not
     * <code>null</code> and any of its elements is <code>null</code>; or if
     * there is a syntax error in the contents of the source or the overrides;
     * or if there is an I/O exception reading from the source; or if the
     * calling thread does not have permission to read the specified source
     * file, connect to the specified URL, or read the system properties
     * referred to in the source or overrides. Any <code>Error</code> thrown
     * while creating the instance is propagated to the caller; it is not
     * wrapped in a <code>ConfigurationException</code>.
     */
    public ConfigurationFile(String[] options) throws ConfigurationException {
	this(options, null);
    }

    /**
     * Creates an instance containing the entries specified by the options,
     * using the specified class loader for interpreting class names. The first
     * option is a file or URL specifying the location of the configuration
     * source; no source is specified if <code>options</code> is
     * <code>null</code>, empty, or has <code>"-"</code> as the first
     * element. The remaining options specify values for individual entries,
     * overriding any matching entries supplied in the configuration source.
     * Specifying <code>null</code> for the class loader uses the current
     * thread's context class loader.
     *
     * @param options an array whose first element is the location of the
     * configuration source and remaining elements specify override values for
     * entries
     * @param cl the class loader to use for interpreting class names. If
     * <code>null</code>, uses the context class loader.
     * @throws ConfigurationNotFoundException if the specified source location
     * cannot be found
     * @throws ConfigurationException if <code>options</code> is not
     * <code>null</code> and any of its elements is <code>null</code>; or if
     * there is a syntax error in the contents of the source or the overrides;
     * or if there is an I/O exception reading from the source; or if the
     * calling thread does not have permission to read the specified source
     * file, connect to the specified URL, or read the system properties
     * referred to in the source or overrides. Any <code>Error</code> thrown
     * while creating the instance is propagated to the caller; it is not
     * wrapped in a <code>ConfigurationException</code>.
     */
    public ConfigurationFile(String[] options, ClassLoader cl)
	throws ConfigurationException
    {
	options = checkOptions(options);
	nonNullLoaderSupplied = cl != null;
	this.cl = nonNullLoaderSupplied ? cl
	    : (ClassLoader) Security.doPrivileged(contextClassLoader);
	if (location == null) {
	    new Parser(null, options);
	} else {
	    InputStream in = null;
	    try {
		try {
		    URL url = new URL(location);
		    in = url.openStream();
		} catch (MalformedURLException e) {
		    in = new FileInputStream(location);
		}
		new Parser(new InputStreamReader(in), options);
	    } catch (FileNotFoundException e) {
		throw new ConfigurationNotFoundException(
		    "configuration file not found: " + location, e);
	    } catch (IOException e) {
		throw new ConfigurationException(
		    "problem reading configuration file " + location, e);
	    } catch (RuntimeException e) {
		throw new ConfigurationException(
		    "problem reading configuration file " + location, e);
	    } finally {
		if (in != null) {
		    try {
			in.close();
		    } catch (IOException e) {
		    }
		}
	    }
	}
	logger.log(Level.FINER, "created {0}", this);
    }

    /**
     * Checks that options contains no nulls, returning a non-null value.  If
     * the argument is not null, not empty, and its first element is not "-",
     * sets location to the first element.
     */
    private String[] checkOptions(String[] options)
	throws ConfigurationException
    {
	if (options == null) {
	    return new String[0];
	} else if (options.length > 0) {
	    for (int i = 0; i < options.length; i++) {
		if (options[i] == null) {
		    throw new ConfigurationException("option is null");
		}
	    }
	    if (!"-".equals(options[0])) {
		location = options[0];
	    }
	}
	return options;
    }

    /**
     * Creates an instance containing the entries parsed from the specified
     * character stream and options, using the calling thread's context class
     * loader for interpreting class names. The first option is used for the
     * location of the configuration source when reporting errors; no location
     * is specified if <code>options</code> is <code>null</code>, empty, or has
     * <code>"-"</code> as the first element. The remaining options specify
     * values for individual entries, overriding any matching entries supplied
     * in the configuration source.
     *
     * @param reader the character input stream
     * @param options an array whose first element is the location of the
     * configuration source and remaining elements specify override values for
     * entries
     * @throws ConfigurationException if <code>options</code> is not
     * <code>null</code> and any of its elements is <code>null</code>, or if
     * there is a syntax error in the contents of the character input stream or
     * the overrides, or if there is an I/O exception reading from the input
     * stream, or if the calling thread does not have permission to read the
     * system properties referred to in the input stream or overrides. Any
     * <code>Error</code> thrown while creating the instance is propagated to
     * the caller; it is not wrapped in a <code>ConfigurationException</code>.
     * @throws NullPointerException if <code>reader</code> is <code>null</code>
     */
    public ConfigurationFile(Reader reader, String[] options)
	throws ConfigurationException
    {
	this(reader, options, null);
    }

    /**
     * Creates an instance containing the entries parsed from the specified
     * character stream and options, using the specified class loader for
     * interpreting class names. The first option is used for the location of
     * the configuration source when reporting errors; no location is specified
     * if <code>options</code> is <code>null</code>, empty, or has
     * <code>"-"</code> as the first element. The remaining options specify
     * values for individual entries, overriding any matching entries supplied
     * in the configuration source. Specifying <code>null</code> for the class
     * loader uses the current thread's context class loader.
     *
     * @param reader the character input stream
     * @param options an array whose first element is the location of the
     * configuration source and remaining elements specify override values for
     * entries
     * @param cl the class loader to use for interpreting class names. If
     * <code>null</code>, uses the context class loader.
     * @throws ConfigurationException if <code>options</code> is not
     * <code>null</code> and any of its elements is <code>null</code>, or if
     * there is a syntax error in the contents of the character input stream or
     * the overrides, or if there is an I/O exception reading from the input
     * stream, or if the calling thread does not have permission to read the
     * system properties referred to in the input stream or overrides. Any
     * <code>Error</code> thrown while creating the instance is propagated to
     * the caller; it is not wrapped in a <code>ConfigurationException</code>.
     * @throws NullPointerException if <code>reader</code> is <code>null</code>
     */
    public ConfigurationFile(Reader reader, String[] options, ClassLoader cl)
	throws ConfigurationException
    {
	if (reader == null) {
	    throw new NullPointerException("reader is null");
	}
	options = checkOptions(options);
	nonNullLoaderSupplied = cl != null;
	this.cl = nonNullLoaderSupplied ? cl
	    : (ClassLoader) Security.doPrivileged(contextClassLoader);
	new Parser(reader, options);
	logger.log(Level.FINER, "created {0}", this);
    }

    /* -- Accessor methods -- */

    /**
     * Returns an object created using the information in the entry matching
     * the specified component and name, and the specified data, for the
     * requested type. If the entry value is a primitive, then the object
     * returned is an instance of {@link AbstractConfiguration.Primitive}. This
     * implementation uses <code>type</code> to perform conversions on
     * primitive values. Repeated calls with the same arguments may or may not
     * return the identical object.
     *
     * @throws NoSuchEntryException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    protected Object getEntryInternal(String component,
				      String name,
				      Class type,
				      Object data)
	throws ConfigurationException
    {
	if (component == null || name == null || type == null) {
	    throw new NullPointerException(
		"component, name and type cannot be null");
	}
	Entry entry = (Entry) entries.get(component + '.' + name);
	if (entry == null || entry.isPrivate) {
	    throw new NoSuchEntryException(
		"entry not found for component " + component +
		", name " + name);
	}
	Class entryType = entry.resolve(null);
	if (!assignable(entryType, type)) { 
	    if (entryType != null &&
		entryType.isPrimitive() &&
		entry.isConstant())
	    {
		/* Handle primitive narrowing conversion */
		Object narrow = narrowingAssignable(entry.eval(NO_DATA), type);
		if (narrow != null) {
		    return new Primitive(narrow);
		}
	    }
	    throw new ConfigurationException(
		"entry of wrong type for component " + component +
		", name " + name +
		": expected " + Utilities.typeString(type) +
		", found " + Utilities.typeString(entryType));
	}
	Object result = entry.eval(data);
	if (entryType != null && entryType.isPrimitive()) {
	    if (entryType != type) {
		result = convertPrimitive(result, type);
	    }
	    return new Primitive(result);
	} else {
	    return result;
	}
    }

    /**
     * Returns a set containing the fully qualified names of all non-private
     * entries defined for this instance.
     *
     * @return a set containing the fully qualified names of all non-private
     * entries defined for this instance
     */
    public Set getEntryNames() {
	Set result = new HashSet(entries.size());
	for (Iterator iter = entries.values().iterator(); iter.hasNext(); ) {
	    Entry entry = (Entry) iter.next();
	    if (!entry.isPrivate) {
		result.add(entry.fullName);
	    }
	}
	return result;
    }

    /**
     * Returns the static type of the expression specified for the entry with
     * the specified component and name.
     *
     * @param component the component
     * @param name the name of the entry for the component
     * @return the static type of the matching entry, or <code>null</code> if
     * the value of the entry is the <code>null</code> literal
     * @throws NoSuchEntryException if no matching entry is found
     * @throws ConfigurationException if a matching entry is found but a
     * problem occurs determining the type of the entry. Any <code>Error</code>
     * thrown while processing the entry is propagated to the caller; it is not
     * wrapped in a <code>ConfigurationException</code>.
     * @throws IllegalArgumentException if <code>component</code> is not
     * <code>null</code> and is not a valid <i>QualifiedIdentifier</i>, or if
     * <code>name</code> is not <code>null</code> and is not a valid
     * <i>Identifier</i>
     * @throws NullPointerException if either argument is <code>null</code>
     */
    public Class getEntryType(String component, String name)
	throws ConfigurationException
    {
	if (component == null) {
	    throw new NullPointerException("component cannot be null");
	} else if (!validQualifiedIdentifier(component)) {
	    throw new IllegalArgumentException(
		"component must be a valid qualified identifier");
	} else if (name == null) {
	    throw new NullPointerException("name cannot be null");
	} else if (!validIdentifier(name)) {
	    throw new IllegalArgumentException(
		"name must be a valid identifier");
	}
	Entry entry = (Entry) entries.get(component + '.' + name);
	if (entry == null || entry.isPrivate) {
	    if (logger.isLoggable(Levels.FAILED)) {
		logger.log(
		    Levels.FAILED,
		    "entry for component {0}, name {1} not found in {2}",
		    new Object[] { component, name, this });
	    }
	    throw new NoSuchEntryException(
		"entry not found for component " + component +
		", name " + name);
	}
	ConfigurationException configEx;
	try {
	    Class result = entry.resolve(null);
	    if (logger.isLoggable(Level.FINER)) {
		logger.log(Level.FINER,
			   "{0}, component {1}, name {2}: returns {3}",
			   new Object[] { this, component, name, result });
	    }
	    return result;
	} catch (ConfigurationException e) {
	    configEx = e;
	} catch (RuntimeException e) {
	    configEx = new ConfigurationException(
		"problem getting type of entry for component " + component +
		", name " + name,
		e);
	}
	if (logger.isLoggable(Levels.FAILED)) {
	    logThrow("getEntryType",
		     "{0}, component {1}, name {2}: throws",
		     new Object[] { this, component, name }, configEx);
	}
	throw configEx;
    }

    /**
     * Returns the type of the special entry with the specified name. Special
     * entry names may be referred to from within other entries, but will not
     * be considered by calls to <code>getEntry</code>.  Implementations can
     * assume that the argument to this method is a valid special entry name,
     * which is an <i>Identifier</i> that starts with <code>'$'</code>. This
     * method will be called when attempting to determine the type of a name
     * expression which is a valid special entry name, does not refer to an
     * existing entry, and is not a special entry expression supported by
     * <code>ConfigurationFile</code>. The object returned by a call to {@link
     * #getSpecialEntry getSpecialEntry} with the same argument must be an
     * instance of the class returned by this method. <p>
     *
     * The default implementation always throws
     * <code>NoSuchEntryException</code>.
     *
     * @param name the name of the special entry
     * @return the type of the special entry
     * @throws NoSuchEntryException if the special entry is not found
     * @throws ConfigurationException if there is a problem determining the
     * type of the special entry
     */
    protected Class getSpecialEntryType(String name)
	throws ConfigurationException 
    {
	throw new NoSuchEntryException("entry not found: " + name);
    }

    /**
     * Returns the value of the special entry with the specified name. Special
     * entry names may be referred to from within other entries, but will not
     * be considered by calls to <code>getEntry</code>. Implementations can
     * assume that the argument to this method is a valid special entry name,
     * which is an <i>Identifier</i> that starts with <code>'$'</code>. This
     * method will be called when attempting to determine the value of a name
     * expression which is a valid special entry name, does not refer to an
     * existing entry, and is not a special entry expression supported by
     * <code>ConfigurationFile</code>. The object returned by this method must
     * be an instance of the class returned by a call to {@link
     * #getSpecialEntryType getSpecialEntryType} with the same argument. <p>
     *
     * The default implementation always throws
     * <code>NoSuchEntryException</code>.
     *
     * @param name the name of the special entry
     * @return the value of the special entry
     * @throws NoSuchEntryException if the special entry is not found
     * @throws ConfigurationException if there is a problem evaluating the
     * special entry
     */
    protected Object getSpecialEntry(String name)
	throws ConfigurationException 
    {
	throw new NoSuchEntryException("entry not found: " + name);
    }

    /* -- Utilities -- */

    /**
     * Resolves a type name to a Class.  Primitive type names are supported.
     */
    Class findClass(String name, int lineno, int override)
	throws ConfigurationException
    {
	return findClass(name, lineno, override, false /* returnIfNotFound */);
    }

    /**
     * Resolves a type name to a Class.  Primitive type names are supported.
     * If returnIfNotFound is true, then returns null rather than throwing an
     * exception if the class is not found.
     */
    Class findClass(String name,
		    int lineno,
		    int override,
		    boolean returnIfNotFound)
	throws ConfigurationException
    {
	Class primitiveClass = findPrimitiveClass(name);
	if (primitiveClass != null) {
	    return primitiveClass;
	}
	/*
	 * Try class imports.  If the name refers to an inner class, then find
	 * the topmost class in the name specified.
	 */
	int dot = name.indexOf('.');
	String n = dot < 0 ? name : name.substring(0, dot);
	String classImport = (String) classImports.get(n);
	if (classImport != null) {
	    if (dot >= 0) {
		classImport += name.substring(dot).replace('.', '$');
	    }
	    Class c = findClassNoImports(classImport, lineno, override);
	    if (c != null) {
		return c;
	    }
	}
	/* Try class name as is */
	Class c = findClassNoImports(name, lineno, override);
	if (c != null) {
	    return c;
	}
	/*
	 * Try on demand imports.  If the name refers to an inner class, then
	 * replace all dots with dollars.
	 */
	String inner = name.replace('.', '$');
	Class found = null;
	for (int i = onDemandImports.size(); --i >= 0; ) {
	    String pkgOrClass = (String) onDemandImports.get(i);
	    for (int j = 0; j < 2; j++) {
		char sep = j == 0 ? '.' : '$';
		Class next = findClassExact(
		    pkgOrClass + sep + inner, lineno, override);
		if (next != null) {
		    if (found != null) {
			oops("ambiguous class: " + name, lineno, override);
		    }
		    found = next;
		}
	    }
	}
	if (found == null && !returnIfNotFound) {
	    oops("class not found: " + name, lineno, override);
	}
	return found;
    }

    /** Returns the class if it names a primitive, otherwise null. */
    static Class findPrimitiveClass(String name) {
	if (name.indexOf('.') < 0) {
	    for (int i = primitives.length; --i >= 0; ) {
		if (name.equals(primitives[i].getName())) {
		    return primitives[i];
		}
	    }
	}
	return null;
    }

    /**
     * Returns the class with the specified name, or null if not found,
     * ignoring imports, but looking for nested classes.  Throws a
     * ConfigurationException if the class is found but is not public, or has a
     * non-public declaring class.
     */
    Class findClassNoImports(String name, int lineno, int override)
	throws ConfigurationException
    {
	Class c = findClassExact(name, lineno, override);
	if (c != null) {
	    return c;
	}
	int dot;
	while ((dot = name.lastIndexOf('.')) >= 0) {
	    name = name.substring(0, dot) + '$' + name.substring(dot + 1);
	    c = findClassExact(name, lineno, override);
	    if (c != null) {
		return c;
	    }
	}
	return null;
    }

    /**
     * Returns the class with this precise name, or null if not found, ignoring
     * imports and not substituting '$' for nested classes.  Throws a
     * ConfigurationException if the class is found but is not public, or has
     * a non-public declaring class.
     */
    Class findClassExact(String name, int lineno, int override)
	throws ConfigurationException
    {
	try {
	    Class result = Class.forName(name, false, cl);
	    Class c = result;
	    do {
		if (!Modifier.isPublic(c.getModifiers())) {
		    name = name.replace('$', '.');
		    if (c == result) {
			oops("class not public: " + name, lineno, override);
		    } else {
			oops("class " + name + " is not accessible: " +
			     "declaring class " +
			     c.getName().replace('$', '.') + " is not public",
			     lineno, override);
		    }
		}
		c = c.getDeclaringClass();
	    } while (c != null);
	    return result;
	} catch (ClassNotFoundException e) {
	    return null;
	}
    }

    /** Resolves a field name to a field. */
    Field findField(String name, int lineno, int override)
	throws ConfigurationException
    {
	int dot = name.lastIndexOf('.');
	if (dot < 0) {
	    oops("entry not found: " + name, lineno, override);
	}
	String className = name.substring(0, dot);
	Class c = findClass(
	    className, lineno, override, true /* returnIfNotFound */);
	if (c == null) {
	    oops("entry or field not found: " + name, lineno, override);
	}
	String fieldName = name.substring(dot + 1);
	checkPackageAccess(c);
	try {
	    Field field = c.getField(fieldName);
	    if (!Modifier.isStatic(field.getModifiers())) {
		oops(fieldName + " is not a static field of class " +
		     className, lineno, override);
	    }
	    return field;
	} catch (NoSuchFieldException e) {
	    oops(fieldName + " is not a public field of class " + className,
		 lineno, override);
	    return null;	/* Not reached */
	}
    }

    /**
     * Check for permission to access the package of the specified class, if
     * any.  Call this method prior to using reflection to access class members
     * to insure that the access check is not skipped because of caller-based
     * checks using ConfigurationFile instead of the caller of
     * ConfigurationFile.
     */
    private static void checkPackageAccess(Class type) {
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    String name = type.getName();
	    int i = name.lastIndexOf('.');
	    if (i != -1) {
		sm.checkPackageAccess(name.substring(0, i));
	    }
	}
    }

    /** Resolves a method name to a method */
    Method findMethod(String fullName,
		      Class[] argumentTypes,
		      int lineno,
		      int override)
	throws ConfigurationException
    {
	int dot = fullName.lastIndexOf('.');
	String typeName = fullName.substring(0, dot);
	String methodName = fullName.substring(dot + 1);
	Class type = findClass(typeName, lineno, override, true);
	if (type == null) {
	    oops("method not found: " + fullName, lineno, override);
	}
	Method method = null;
	checkPackageAccess(type);
	Method[] meths = type.getMethods();
	boolean check = false;
	for (int i = meths.length; --i >= 0; ) {
	    if (!methodName.equals(meths[i].getName()) ||
		!Modifier.isStatic(meths[i].getModifiers()) ||
		!compatible(meths[i].getParameterTypes(), argumentTypes))
	    {
		continue;
	    } else if (method == null) {
		method = meths[i];
	    } else if (assignable(meths[i].getParameterTypes(),
				  method.getParameterTypes()) &&
		       assignable(meths[i].getDeclaringClass(),
				  method.getDeclaringClass()))
	    {
		method = meths[i];
	    } else {
		check = true;
	    }
	}
	if (method == null) {
	    oops("no applicable public static method found: " +
		 type.getName() + '.' + methodName +
		 typesString(argumentTypes),
		 lineno, override);
	} else if (prohibitedMethods.contains(
		       method.getDeclaringClass().getName() + '.' +
		       methodName))
	{
	    oops("call to method prohibited: " + fullName, lineno, override);
	} else if (check) {
	    for (int i = meths.length; --i >= 0; ) {
		if (meths[i] == method ||
		    !methodName.equals(meths[i].getName()) ||
		    !Modifier.isStatic(meths[i].getModifiers()) ||
		    !compatible(meths[i].getParameterTypes(), argumentTypes))
		{
		    continue;
		} else if (!assignable(method.getParameterTypes(),
				       meths[i].getParameterTypes()) ||
			   !assignable(method.getDeclaringClass(),
				       meths[i].getDeclaringClass()))
		{
		    oops("ambiguous method invocation: " + type.getName() +
			 '.' + methodName + typesString(argumentTypes),
			 lineno, override);
		}
	    }
	}
	return method;
    }

    /** Resolve a type name to a constructor */
    Constructor findConstructor(String typeName,
				Class[] argumentTypes,
				int lineno,
				int override)
	throws ConfigurationException
    {
	Class type = findClass(typeName, lineno, override);
	if (Modifier.isInterface(type.getModifiers())) {
	    oops("calling constructor for interface class: " + type.getName(),
		 lineno, override);
	} else if (type.isPrimitive()) {
	    oops("calling constructor for primitive class: " + type.getName(),
		 lineno, override);
	} else if (Modifier.isAbstract(type.getModifiers())) {
	    oops("calling constructor for abstract class: " + type.getName(),
		 lineno, override);
	}
	checkPackageAccess(type);
	Constructor[] cons = type.getConstructors();
	Constructor constructor = null;
	boolean check = false;
	for (int i = cons.length; --i >= 0; ) {
	    if (!compatible(cons[i].getParameterTypes(), argumentTypes)) {
		continue;
	    } else if (constructor == null ||
		       assignable(cons[i].getParameterTypes(),
				  constructor.getParameterTypes()))
	    {
		constructor = cons[i];
	    } else {
		check = true;
	    }
	}
	if (constructor == null) {
	    oops("no public constructor found: " + type.getName() +
		 typesString(argumentTypes),
		 lineno, override);
	} else if (check) {
	    for (int i = cons.length; --i >= 0; ) {
		if (cons[i] != constructor &&
		    compatible(cons[i].getParameterTypes(), argumentTypes) &&
		    !assignable(constructor.getParameterTypes(),
				cons[i].getParameterTypes()))
		{
		    oops("ambiguous constructor invocation: " +
			 type.getName() + typesString(argumentTypes),
			 lineno, override);
		}
	    }
	}
	return constructor;
    }

    /**
     * Returns true if methods or constructors with the specified parameter
     * types can accept arguments of the specified types.
     */
    private static boolean compatible(Class[] parameterTypes,
				      Class[] argumentTypes)
    {
	return parameterTypes.length == argumentTypes.length &&
	    assignable(argumentTypes, parameterTypes);
    }

    /**
     * Returns true if values of the types specified in "from" can be passed
     * to parameters of the types specified in "to". The two arrays are
     * assumed to be of the same length. A null type represents a null value.
     */
    private static boolean assignable(Class[] from, Class[] to) {
	for (int i = from.length; --i >= 0; ) {
	    if (!assignable(from[i], to[i])) {
		return false;
	    }
	}
	return true;
    }

    /**
     * Returns true if values of type "from" can be passed to a parameter of
     * type "to". A null "from" type represents a null value. For primitive
     * types, widening conversions are, with one exception, from types other
     * than boolean and byte (the first legal type is char) and are only to
     * the types int, long, float, or double. The one exception is the byte
     * to short conversion, which is checked for separately.
     */
    static boolean assignable(Class from, Class to) {
	if (from == null) {
	    return !to.isPrimitive();
	} else if (to.isAssignableFrom(from)) {
	    return true;
	} else if (to.isPrimitive() && from.isPrimitive()) {
	    if (from == Byte.TYPE && to == Short.TYPE) {
		return true;
	    }
	    for (int j = primitives.length; --j >= INT_INDEX; ) {
		if (to == primitives[j]) {
		    while (--j >= BYTE_INDEX) {
			if (from == primitives[j]) {
			    return true;
			}
		    }
		    break;
		}
	    }
	}
	return false;
    }

    /**
     * Returns the value to assign if the primitive value represented by the
     * wrapper object in "from" can be assigned to parameters of type "to"
     * using a narrowing primitive conversion, else null.
     */
    Object narrowingAssignable(Object from, Class to) {
	if (from instanceof Byte ||
	    from instanceof Character ||
	    from instanceof Short ||
	    from instanceof Integer)
	{
	    int val = (from instanceof Character) ?
		((Character) from).charValue() : ((Number) from).intValue();
	    if (to == Byte.TYPE) {
		if (val >= Byte.MIN_VALUE && val <= Byte.MAX_VALUE) {
		    return new Byte((byte) val);
		}
	    } else if (to == Character.TYPE) {
		if (val >= Character.MIN_VALUE &&
		    val <= Character.MAX_VALUE)
		{
		    return new Character((char) val);
		}
	    } else if (to == Short.TYPE) {
		if (val >= Short.MIN_VALUE && val <= Short.MAX_VALUE) {
		    return new Short((short) val);
		}
	    }
	}
	return null;
    }

    /**
     * Converts a wrapper instance to an instance of the wrapper class for the
     * specified primitive type.
     */
    static Object convertPrimitive(Object obj, Class primitiveType) {
	Number n = obj instanceof Character
	    ? new Integer(((Character) obj).charValue())
	    : (Number) obj;
	if (primitiveType == Byte.TYPE) {
	    return new Byte(n.byteValue());
	} else if (primitiveType == Character.TYPE) {
	    return new Character((char) n.intValue());
	} else if (primitiveType == Short.TYPE) {
	    return new Short(n.shortValue());
	} else if (primitiveType == Integer.TYPE) {
	    return new Integer(n.intValue());
	} else if (primitiveType == Long.TYPE) {
	    return new Long(n.longValue());
	} else if (primitiveType == Float.TYPE) {
	    return new Float(n.floatValue());
	} else if (primitiveType == Double.TYPE) {
	    return new Double(n.doubleValue());
	} else {
	    return null;
	}
    }

    /**
     * Returns true if the two classes have no public methods with the same
     * name and parameter types but different return types.
     */
    static boolean compatibleMethods(Class c1, Class c2) {
	Method[] meths = c1.getMethods();
	for (int i = meths.length; --i >= 0; ) {
	    Method m1 = meths[i];
	    try {
		Method m2 = c2.getMethod(m1.getName(), m1.getParameterTypes());
		if (m2 != null && m1.getReturnType() != m2.getReturnType()) {
		    return false;
		}
	    } catch (NoSuchMethodException e) {
	    }
	}
	return true;
    }

    /** Returns a String describing the types in an argument list. */
    private static String typesString(Class[] types) {
	StringBuffer sb = new StringBuffer();
	sb.append('(');
	for (int i = 0; i < types.length; i++) {
	    if (i != 0) {
		sb.append(", ");
	    }
	    sb.append(Utilities.typeString(types[i]));
	}
	sb.append(')');
	return sb.toString();
    }

    /**
     * Expands properties embedded in a string with ${some.property.name}. Also
     * treats ${/} as ${file.separator}.
     */
    String expandStringProperties(String value, int lineno)
	throws ConfigurationException
    {
	int p = value.indexOf("${", 0);
	if (p == -1) {
	    return value;
	}
	int max = value.length();
	StringBuffer sb = new StringBuffer(max);
	int i = 0;		/* Index of last character we copied */
	while (true) {
	    if (p > i) {
		/* Copy in anything before the special stuff */
		sb.append(value.substring(i, p));
		i = p;
	    }
	    int pe = value.indexOf('}', p + 2);
	    if (pe == -1) {
		/* No matching '}' found, just add in as normal text */
		sb.append(value.substring(p, max));
		break;
	    }
	    String prop = value.substring(p + 2, pe);
	    if (prop.equals("/")) {
		sb.append(File.separatorChar);
	    } else {
		try {
		    String val = prop.length() == 0 ? null
			: System.getProperty(prop);
		    if (val != null) {
			sb.append(val);
		    } else {
			oops("problem expanding system property '" + prop + "'",
			     lineno, override);
		    }
		} catch (SecurityException e) {
		    oops("problem expanding system property '" + prop + "'",
			 lineno, override, e);
		}
	    }
	    i = pe + 1;
	    p = value.indexOf("${", i);
	    if (p == -1) {
		/* No more to expand -- copy in any extra. */
		if (i < max) {
		    sb.append(value.substring(i, max));
		} 
		/* Break out of loop */
		break;
	    }
	}
	return sb.toString();
    }

    /**
     * Throws a ConfigurationException for an error described by the what
     * argument, and for the specified line number and override.
     */
    void oops(String what, int lineno, int override)
	throws ConfigurationException
    {
	oops(what, lineno, override, null);
    }

    /**
     * Throws a ConfigurationException for an error described by the what
     * argument, for the specified line number and override, and caused by the
     * specified evaluation exception, which may be null.
     */
    void oops(String what, int lineno, int override, Throwable t)
	throws ConfigurationException
    {
	throw new ConfigurationException(
	    ((override != 0) ?
	     "Override " + override + ": Line " :
	     (location == null) ?
	     "Line " :
	     location + ":") +
	    lineno + ": " + what,
	    t);
    }

    /** Returns a string representation of this object. */
    public String toString() {
	return "ConfigurationFile@" +
	    Integer.toHexString(hashCode()) +
	    (location == null ? "" : "{" + location + "}");
    }
}
