/*
 * 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}.
 *
 * 
 * @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;
    }
}
