/* * 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.util.Arrays; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; /** * A skeletal implementation of the <code>Configuration</code> interface, used * to simplify writing implementations. This class checks the validity of * arguments to the <code>getEntry</code> methods, checks that the result * matches the requested type, and wraps exceptions other than {@link Error} or * {@link ConfigurationException} in a * <code>ConfigurationException</code>. Subclasses need to implement the {@link * #getEntryInternal(String,String,Class,Object) getEntryInternal} method, * which supplies entry values, throws {@link NoSuchEntryException} if no * matching entry is found, and performs any desired primitive conversions. The * <code>getEntryInternal</code> method should return primitive values as * instances of {@link Primitive}. * * @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 AbstractConfiguration 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 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> getting existing entries * * </table> */ public abstract class AbstractConfiguration implements Configuration { /** * A sorted array of names that cannot be used for identifiers. This list * includes the names of all Java programming language keywords, plus * 'null', 'true', and 'false', which are not keywords, but are not * permitted as identifiers. */ private static final String[] reservedNames = { "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while" }; /** Config logger. */ static final Logger logger = Logger.getLogger("net.jini.config"); /** * Represents the value of an entry with a primitive type. Subclasses of * {@link AbstractConfiguration} that contain primitive entries should * return instances of this class from their {@link #getEntryInternal * getEntryInternal} methods. * * @since 2.0 */ public static final class Primitive { /** The value, as a wrapper instance. */ private final Object value; /** The primitive type. */ private final Class type; /** * Creates an object that represents a primitive value of the type * associated with the specified primitive wrapper object. * * @param value the primitive wrapper object * @throws IllegalArgumentException if <code>value</code> is not an * instance of a primitive wrapper class */ public Primitive(Object value) { this.value = value; type = (value != null) ? Utilities.getPrimitiveType(value.getClass()) : null; if (type == null) { throw new IllegalArgumentException( "value is not a primitive: " + value); } } /** * Returns the primitive value associated with this object, represented * as a primitive wrapper instance. * * @return the value of this object, as a primitive wrapper instance */ public Object getValue() { return value; } /** * Returns the primitive type of the value associated with this object. * * @return the primitive type of the value associated with this object */ public Class getType() { return type; } /** Returns a string representation of this object. */ public String toString() { return "Primitive[(" + type + ") " + value + "]"; } /** * Returns <code>true</code> if the argument is a * <code>Primitive</code> for which the result of calling * <code>getValue</code> is the same as the value for this instance, * otherwise <code>false</code>. */ public boolean equals(Object obj) { return obj instanceof Primitive && value.equals(((Primitive) obj).value); } /** Returns a hash code value for this object. */ public int hashCode() { return value.hashCode(); } } /** Creates an instance of this class. */ protected AbstractConfiguration() { } /** * Returns an object of the specified type created using the information in * the entry matching the specified component and name, which must be * found, and supplying no data. If <code>type</code> is a primitive type, * then the result is returned as an instance of the associated wrapper * class. Repeated calls with the same arguments may or may not return the * identical object. <p> * * The default implementation checks that <code>component</code>, * <code>name</code>, and <code>type</code> are not <code>null</code>; that * <code>component</code> is a valid qualified identifier; and that * <code>name</code> is a valid identifier. It returns the result of * calling {@link #getEntryInternal(String,String,Class,Object) * getEntryInternal} with the specified arguments, as well as {@link * #NO_DEFAULT} and {@link #NO_DATA}, converting results of type {@link * Primitive} into the associated wrapper type. If the call throws an * exception other than an {@link Error} or a {@link * ConfigurationException}, it throws a <code>ConfigurationException</code> * with the original exception as the cause. * * @throws NoSuchEntryException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Object getEntry(String component, String name, Class type) throws ConfigurationException { return getEntryInternal(component, name, type, NO_DEFAULT, NO_DATA); } /** * Returns an object of the specified type created using the information in * the entry matching the specified component and name, and supplying no * data, returning the default value if no matching entry is found and the * default value is not {@link #NO_DEFAULT}. If <code>type</code> is a * primitive type, then the result is returned as an instance of the * associated wrapper class. Repeated calls with the same arguments may or * may not return the identical object. <p> * * The default implementation checks that <code>component</code>, * <code>name</code>, and <code>type</code> are not <code>null</code>; that * <code>component</code> is a valid qualified identifier; that * <code>name</code> is a valid identifier; and that * <code>defaultValue</code> is of the right type. It returns the result of * calling {@link #getEntryInternal(String,String,Class,Object) * getEntryInternal} with the specified arguments, as well as {@link * #NO_DATA}, converting results of type {@link Primitive} into the * associated wrapper type. If the call throws an exception other than an * {@link Error} or a {@link ConfigurationException}, it throws a * <code>ConfigurationException</code> with the original exception as the * cause. * * @throws NoSuchEntryException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Object getEntry(String component, String name, Class type, Object defaultValue) throws ConfigurationException { return getEntryInternal(component, name, type, defaultValue, NO_DATA); } /** * Returns an object of the specified type created using the information in * the entry matching the specified component and name, and using the * specified data (unless it is {@link #NO_DATA}), returning the default * value if no matching entry is found and the default value is not {@link * #NO_DEFAULT}. If <code>type</code> is a primitive type, then the result * is returned as an instance of the associated wrapper class. Repeated * calls with the same arguments may or may not return the identical * object. <p> * * The default implementation checks that <code>component</code>, * <code>name</code>, and <code>type</code> are not <code>null</code>; that * <code>component</code> is a valid qualified identifier; that * <code>name</code> is a valid identifier; and that * <code>defaultValue</code> is of the right type. It returns the result of * calling {@link #getEntryInternal(String,String,Class,Object) * getEntryInternal} with the specified arguments, converting results of * type {@link Primitive} into the associated wrapper type. If the call * throws an exception other than an {@link Error} or a {@link * ConfigurationException}, it throws a <code>ConfigurationException</code> * with the original exception as the cause. * * @throws NoSuchEntryException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Object getEntry(String component, String name, Class type, Object defaultValue, Object data) throws ConfigurationException { return getEntryInternal(component, name, type, defaultValue, data); } /** * 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 should be an instance of {@link Primitive}. Implementations may * use <code>type</code> to perform conversions on primitive values, if * desired, but are not required to check if the object is of the requested * type. Repeated calls with the same arguments may or may not return the * identical object. <p> * * The default implementations of the <code>getEntry</code> methods * delegate to this method; implementations can rely on the fact that calls * made to this method by those methods will have arguments that are not * <code>null</code> and that have the correct syntax. * * @param component the component being configured * @param name the name of the entry for the component * @param type the type of object requested * @param data an object to use when computing the value of the entry, or * {@link #NO_DATA} to specify no data * @return an object created using the information in the entry matching * <code>component</code> and <code>name</code>, and using the value of * <code>data</code> (unless it is <code>NO_DATA</code>) * @throws NoSuchEntryException if no matching entry is found * @throws ConfigurationException if a matching entry is found but a * problem occurs creating the object for the entry * @throws NullPointerException if <code>component</code>, * <code>name</code>, or <code>type</code> is <code>null</code> * @see Configuration#getEntry(String,String,Class) Configuration.getEntry */ protected abstract Object getEntryInternal(String component, String name, Class type, Object data) throws ConfigurationException; /** * Helper method, used to implement the public overloadings of getEntry, * which checks for null or illegal arguments, and logs and wraps * exceptions. */ private Object getEntryInternal(String component, String name, Class type, Object defaultValue, Object data) 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"); } else if (type == null) { throw new NullPointerException("type cannot be null"); } else if (defaultValue != NO_DEFAULT) { if (type.isPrimitive() ? (defaultValue == null || Utilities.getPrimitiveType(defaultValue.getClass()) != type) : (defaultValue != null && !type.isAssignableFrom(defaultValue.getClass()))) { throw new IllegalArgumentException( "defaultValue is of wrong type"); } } ConfigurationException configEx; try { Object result = getEntryInternal(component, name, type, data); Class resultType; if (result instanceof Primitive) { resultType = ((Primitive) result).getType(); result = ((Primitive) result).getValue(); } else if (result != null) { resultType = result.getClass(); } else { resultType = null; } if ((resultType == null) ? type.isPrimitive() : !type.isAssignableFrom(resultType)) { throw new ConfigurationException( "entry for component " + component + ", name " + name + " is of wrong type: " + Utilities.typeString(resultType)); } if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "{0}, component {1}, name {2}" + "{3,choice,0#|1#, data {4}}: returns {5}", new Object[] { this, component, name, new Double(data == NO_DATA ? 0 : 1), data, result }); } return result; } catch (NoSuchEntryException e) { if (defaultValue == NO_DEFAULT) { if (logger.isLoggable(Levels.FAILED)) { logger.log(Levels.FAILED, "{0}, component {1}, name {2}: entry not found", new Object[] { this, component, name }); } throw e; } else { if (logger.isLoggable(Level.FINE)) { logger.log( Level.FINE, "{0}, component {1}, name {2}: returns default {3}", new Object[] { this, component, name, defaultValue }); } return defaultValue; } } catch (ConfigurationException e) { configEx = e; } catch (RuntimeException e) { configEx = new ConfigurationException( "problem getting entry for component " + component + ", name " + name, e); } if (logger.isLoggable(Levels.FAILED)) { logThrow("getEntry", "{0}, component {1}, name {2}" + "{3,choice,0#|1#, data {4}}: throws", new Object[] { this, component, name, new Double(data == NO_DATA ? 0 : 1), data }, configEx); } throw configEx; } /** Logs a throw */ void logThrow(String method, String msg, Object[] msgParams, Throwable t) { LogRecord r = new LogRecord(Levels.FAILED, msg); r.setLoggerName(logger.getName()); r.setSourceClassName(this.getClass().getName()); r.setSourceMethodName(method); r.setParameters(msgParams); r.setThrown(t); logger.log(r); } /** * Checks if the argument is a valid <i>Identifier</i>, as defined in the * <i>Java(TM) Language Specification</i>. * * @param name the name to check * @return <code>true</code> if <code>name</code> is a valid * <i>Identifier</i>, else <code>false</code> */ protected static boolean validIdentifier(String name) { if (name == null || name.length() == 0 || !Character.isJavaIdentifierStart(name.charAt(0))) { return false; } for (int i = name.length(); --i > 0; ) { if (!Character.isJavaIdentifierPart(name.charAt(i))) { return false; } } return Arrays.binarySearch(reservedNames, name) < 0; } /** * Checks if the argument is a valid <i>QualifiedIdentifier</i>, as defined * in the <i>Java Language Specification</i>. * * @param name the name to check * @return <code>true</code> if <code>name</code> is a valid * <i>QualifiedIdentifier</i>, else <code>false</code> */ protected static boolean validQualifiedIdentifier(String name) { if (name == null) { return false; } int offset = 0; int dot; do { dot = name.indexOf('.', offset); String id = name.substring( offset, dot < 0 ? name.length() : dot); if (!validIdentifier(id)) { return false; } offset = dot + 1; } while (dot >= 0); return true; } }