/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.io.StringWriter;
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.Collections;
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>StringConcatenation</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> ,<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
*
* <i>StringConcatenation</i>:
* <i>Expr</i> + <i>Expr</i>
* </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 use of the <code>+</code> operator in a configuration source is also
* allowed, but it may be used only for string concatenation, as defined by the
* JLS. Using the <code>+</code> operator in an arithmetic expression results
* in a <code>ConfigurationException</code> being thrown.<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, array constructor,
* or string concatenation operation.
*/
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;
}
}
private class StringConcatenation extends Call {
StringConcatenation(ParseNode[] args, int lineno) {
super(args,lineno);
}
Class resolve(Entry inEntry) throws ConfigurationException {
Class[] types = resolveArgs(inEntry);
if (!(types[0] == String.class || types[1] == String.class)) {
oops("The static type of the first or second operand in a"
+ " string concatenation expression must be"
+ " java.lang.String. The static types of the first"
+ " and second operands in the string concatenation"
+ " expression are " + types[0] + " and " + types[1]
+ ", respectively");
}
return String.class;
}
boolean isConstant() {
return false;
}
Object eval(Object data) throws ConfigurationException {
StringBuffer value = new StringBuffer();
Object[] evaledArgs = evalArgs(data);
for (int i = 0; i < evaledArgs.length; i++) {
Object o1 = evaledArgs[i];
value.append(o1);
}
return value.toString();
}
}
/** 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) {
ConfigurationFile.this.oops(
"problem reading configuration file", 0, 0, 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();
boolean arrayArgs = close == '}';
if (t != close) {
if (t == ',' && arrayArgs) {
t = st.nextToken();
if ( t != close) {
syntax("'" + close + "'");
}
} else {
st.pushBack();
while (true) {
args.add(parseExpr(inEntry));
t = st.nextToken();
if (t == close) {
break;
} else if (t != ',') {
syntax("',' or '" + close + "'");
} else if (arrayArgs) {
t = st.nextToken();
if (t == close) {
break;
} else {
st.pushBack();
}
}
}
}
}
return (ParseNode[]) args.toArray(new ParseNode[args.size()]);
}
/** Parses a string concatenation */
ParseNode parseExpr(Entry inEntry)
throws ConfigurationException, IOException
{
List nodes = new ArrayList();
for (;;) {
nodes.add(parseSubExpr(inEntry));
if (st.nextToken() != '+') {
st.pushBack();
break;
}
}
if (nodes.size() == 1) {
return (ParseNode) nodes.get(0);
} else {
return new StringConcatenation(
(ParseNode[]) nodes.toArray(new ParseNode[nodes.size()]),
st.lineno());
}
}
/** Parses an Expr. */
ParseNode parseSubExpr(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) {
ErrorDescriptor ed = new ErrorDescriptor(0, 0,
"configuration file not found", location, e);
throwConfigurationException(
new ConfigurationNotFoundException(ed.toString(), e),
Collections.singletonList(ed));
throw new AssertionError("throwConfigurationException must"
+ " throw an exception");
} catch (IOException e) {
oops("problem reading configuration file", 0, 0, e);
} catch (RuntimeException e) {
oops("problem reading configuration file", 0, 0, 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) {
oops("option is null", 0, i);
}
}
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 constructor completes reading
* from the character input stream before returning, but does not close the
* stream. It is the responsibility of the caller to ensure that the
* character input stream is closed. 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 constructor completes reading
* from the character input stream before returning, but does not close the
* stream. It is the responsibility of the caller to ensure that the
* character input stream is closed. 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);
}
/**
* Allows a subclass of <code>ConfigurationFile</code> to
* control the <code>ConfigurationException</code> that is thrown. It
* must be called any time the <code>ConfigurationFile</code> implementation
* encounters a situation that would trigger a
* <code>ConfigurationException</code>. Such situations occur when
* attempting to parse a configuration source or when attempting to
* return an entry or the type of an entry and are fully documented in the
* specification for each <code>ConfigurationFile</code> method that throws
* a <code>ConfigurationException</code>.
* <p>
* The default <code>ConfigurationFile</code> implementation throws
* <code>defaultException</code> if <code>defaultException</code> is not
* <code>null</code>. If <code>defaultException</code> is
* <code>null</code>, the default implementation throws a
* <code>ConfigurationException</code> constructed from the error
* descriptors provided in <code>errors</code>. The default implementation
* throws <code>java.lang.NullPointerException</code>
* if <code>errors</code> is null,
* <code>java.lang.IllegalArgumentException</code> if <code>errors</code>
* is empty, and <code>java.lang.ClassCastException</code> if the contents
* of <code>errors</code> is not assignable to
* {@link ConfigurationFile.ErrorDescriptor ErrorDescriptor}.
*
* @param defaultException the exception the <code>ConfigurationFile</code>
* implementation would normally throw.
* @param errors list of errors encountered in parsing the configuration
* source or when attempting to return an entry or the type of an entry.
* This parameter may not be null, it must contain
* at least one element, and the elements of the list must be assignable to
* {@link ConfigurationFile.ErrorDescriptor ErrorDescriptor}. The order in
* which the errors appear in the list reflects the order in which they
* were encountered by the <code>ConfigurationFile</code> implementation.
* @throws ConfigurationException the subclass-specific
* <code>ConfigurationException</code> or the default exception passed in
* by the <code>ConfigurationFile</code> implementation.
* @since 2.1
*/
protected void throwConfigurationException(
ConfigurationException defaultException, List errors)
throws ConfigurationException
{
if (errors == null)
throw new NullPointerException("errors parameter cannot be null");
if (errors.isEmpty())
throw new IllegalArgumentException(
"error list must contain at least one error");
ConfigurationException exception = null;
if (errors.size() > 1) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
for (Iterator it = errors.iterator(); it.hasNext(); ) {
ErrorDescriptor error = (ErrorDescriptor)it.next();
pw.print("\n" + error.toString());
Throwable cause = error.getCause();
if (cause != null) {
pw.print("\ncaused by: ");
cause.printStackTrace(pw);
}
}
pw.flush();
exception = new ConfigurationException(sw.toString());
} else {
ErrorDescriptor error= (ErrorDescriptor)errors.iterator().next();
exception = new ConfigurationException(error.toString(),
error.getCause());
}
if (defaultException != null)
throw defaultException;
throw exception;
}
/* -- 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.<p>
*
*
* @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) {
oopsNoSuchEntry("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);
}
}
oops("entry of wrong type for component " + component +
", name " + name +
": expected " + Utilities.typeString(type) +
", found " + Utilities.typeString(entryType), entry.lineno,
entry.override);
}
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 });
}
oopsNoSuchEntry("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) {
String description = "problem getting type of entry for component "
+ component + ", name " + name;
configEx = null;
try {
oops(description, entry.lineno, entry.override, e);
} catch (ConfigurationException ce) {
configEx = ce;
}
}
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 that 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
{
oopsNoSuchEntry("entry not found: " + name);
return null;
}
/**
* 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
{
oopsNoSuchEntry("entry not found: " + name);
return null;
}
/* -- 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("declaring class: " + typeName + ", for method: "
+ fullName + " was not found" , 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.
*/
private 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.
*/
private void oops(String what, int lineno, int override, Throwable t)
throws ConfigurationException
{
ErrorDescriptor error = new ErrorDescriptor(lineno, override,
what, location, t);
throwConfigurationException(
new ConfigurationException(error.toString(), t),
Collections.singletonList(error));
throw new AssertionError("throwConfigurationException must throw"
+ " an exception");
}
/**
* Calls throwConfigurationException with a default NoSuchEntryException.
*/
private void oopsNoSuchEntry(String what) throws ConfigurationException {
ErrorDescriptor error = new ErrorDescriptor(0, 0, what, location);
throwConfigurationException(
new NoSuchEntryException(error.toString()),
Collections.singletonList(error));
throw new AssertionError("throwConfigurationException must throw"
+ " an exception");
}
/** Returns a string representation of this object. */
public String toString() {
return "ConfigurationFile@" +
Integer.toHexString(hashCode()) +
(location == null ? "" : "{" + location + "}");
}
/**
* Class used to represent a syntax error encountered when parsing a
* configuration source or a problem encountered when attempting to return
* an existing entry or the type of an existing entry.
*/
public static class ErrorDescriptor implements Serializable {
private static final long serialVersionUID = 1L;
/**
* line number where this syntax error occurred
* @serial
*/
private final int lineno;
/**
* override where this syntax error occurred
* @serial
*/
private final int override;
/**
* textual description of the problem encountered
* @serial
*/
private final String description;
/**
* configuration source location name
* @serial
*/
private final String locationName;
/**
* exception associated with this error
* @serial
*/
private final Throwable t;
/**
* Creates a new error descriptor.
*
* @param lineno line number of the configuration source where this
* problem was found or <code>0</code> if this problem is not
* associated with a line number
* @param override the override sequence number or <code>0</code>
* if the problem was not found in an override
* @param description a description of the problem; this parameter
* cannot be <code>null</code>
* @param locationName the name of the configuration source
* location or <code>null</code> if location information is not
* available
* @param t exception associated with this error or <code>null</code>
* if there is no exception related to the error; <code>t</code>
* cannot be an instance of <code>java.lang.Error</code>
* @throws IllegalArgumentException if <code>lineno</code> <
* <code>0</code>, <code>override</code> < <code>0</code>,
* <code>description</code> is <code>null</code>, or <code>t</code>
* is an instance of <code>java.lang.Error</code>
*/
public ErrorDescriptor(int lineno,
int override,
String description,
String locationName,
Throwable t)
{
if (lineno < 0)
throw new IllegalArgumentException("line number must be"
+ " greater than 0");
if (override < 0)
throw new IllegalArgumentException("override argument must be"
+ " greater than 0");
if (description == null)
throw new IllegalArgumentException("description of the error"
+ " cannot be null");
if (t instanceof Error)
throw new IllegalArgumentException("t cannot be an instance of"
+ " java.lang.Error");
this.lineno = lineno;
this.override = override;
this.description = description;
this.locationName = locationName;
this.t = t;
}
/**
* Creates a new error descriptor.
*
* @param lineno line number of the configuration source where this
* problem was found or <code>0</code> if this problem is not
* associated with a line number
* @param override the override sequence number or <code>0</code>
* if the problem was not found in an override
* @param description a description of the problem; this parameter
* cannot be <code>null</code>
* @param locationName the name of the configuration source
* location or <code>null</code> if location information is not
* available
* @throws IllegalArgumentException if <code>lineno</code> <
* <code>0</code>, <code>override</code> < <code>0</code>, or
* <code>description</code> is <code>null</code>
*/
public ErrorDescriptor(int lineno,
int override,
String description,
String locationName)
{
this(lineno, override, description, locationName, null);
}
/**
* Creates a new error descriptor.
*
* @param lineno line number of the configuration source where this
* problem was found or <code>0</code> if this problem is not
* associated with a line number
* @param override the override sequence number or <code>0</code>
* if the problem was not found in an override
* @param description a description of the problem; this parameter
* cannot be <code>null</code>
* @throws IllegalArgumentException if <code>lineno</code> <
* <code>0</code>, <code>override</code> < <code>0</code>, or
* <code>description</code> is <code>null</code>
*/
public ErrorDescriptor(int lineno, int override, String description) {
this(lineno, override, description, null);
}
/**
* Returns the line number in the configuration source where the
* entry with an error can be found or <code>0</code> if this error
* descriptor is not associated with a line number.
*
* @return the line number in the configuration source where the
* entry with an error can be found or <code>0</code> if this error
* descriptor is not associated with a line number
*/
public int getLineNumber() {
return lineno;
}
/**
* Returns the override sequence number where this error occurred
* or <code>0</code> if the error did not occur in an override.
* For example, if the error occurred in the second override specified,
* the method would return <code>2</code>.
*
* @return the override where this error occurred or <code>0</code>
* if this error did not occur in an override
*/
public int getOverride() {
return override;
}
/**
* Returns a textual description of the error encountered.
*
* @return a description of the problem
*/
public String getDescription() {
return description;
}
/**
* Returns the name of the configuration source location or <code>
* null</code> if location information is not available.
*
* @return the name of the configuration source location where this
* error occurred or <code>null</code> if location information is
* not available.
*/
public String getLocationName() {
return locationName;
}
/**
* Returns the exception associated with this error or <code>null
* </code> if there is no exception associated with this error.
*
* @return the exception associated with this error or null if
* there is no exception associated with this
* error.
*/
public Throwable getCause() {
return t;
}
/**
* Returns a string representation of this error.
*
* @return the string representation of this error
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
if (override > 0) {
buffer.append("Override " + override + ": ");
if (lineno > 0)
buffer.append("Line " + lineno + ": ");
} else {
if (locationName == null && lineno > 0) {
buffer.append("Line " + lineno + ": ");
} else if (locationName != null) {
buffer.append(locationName + ":");
if (lineno > 0)
buffer.append(lineno + ": ");
}
}
buffer.append(description);
return buffer.toString();
}
/**
* Verifies that the deserialized field values for this error
* descriptor are valid.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
if (lineno < 0)
throw new InvalidObjectException("line number must be"
+ " greater than 0");
if (override < 0)
throw new InvalidObjectException("override argument must be"
+ " greater than 0");
if (description == null)
throw new InvalidObjectException("description of the error"
+ " cannot be null");
if (t instanceof Error)
throw new InvalidObjectException("t cannot be an instance of"
+ " java.lang.Error");
}
/**
* Throws InvalidObjectException, since a descriptor is always
* required.
*/
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("no data");
}
}
}