/*
 * Copyright 2003-2004 The Apache Software Foundation
 * 
 * Licensed 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 org.apache.commons.attributes;

import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * API for accessing attributes.
 *
 * <h3>General Notes on Errors</h3>
 * 
 * All Methods in this class may throw <code>RepositoryError</code> or subclasses thereof.
 * This Error is thrown if an attribute repository can not be loaded for some Exceptional
 * reason.
 * 
 * <h4>Rationale for Errors instead of Exceptions</h4>
 *
 * The methods in this class throws <code>Error</code>s instead of <code>Exception</code>s.
 * This rationale behind this is that:
 *
 * <ul> 
 * <li>The programmer should not have to wrap all accesses to 
 *     the Attributes API in a try/catch clause.
 * <li>An Exception being thrown here is caused by missing classes
 *     or other "Error-like" conditions.
 * </ul>
 *
 * <h3>Null References</h3>
 *
 * <p>If a parameter to a method may not be null, and a null is passed to the
 * method, a {@link java.lang.NullPointerException} will be thrown, with the
 * parameter name in the message.
 *
 * <p>Rationale for using this instead of {@link java.lang.IllegalArgumentException}
 * is that it is more precise - the reference was null.
 *
 * <h3>Performance Notes</h3>
 * <p>The process of loading attributes for a class is a
 * (relatively) time-consuming process, as it involves some dynamic linking 
 * in the form of inheritable attributes, a lot of reflection and so on. However,
 * once loaded the attributes are cached, so repeated access to them are fast.
 * But despite this the process of finding one attribute of a given type
 * or such operations does involve some iteration of HashSets that <b>runs in linear
 * time in the number of attributes associated with the program element</b>, and you should
 * avoid accessing attributes in your innermost loops if you can avoid it. For 
 * example, instead of:
 *
 * <pre><code>
 * Class myClass = ...;
 * for (int i = 0; i < 10000; i++) {
 *     if (Attributes.hasAttributeType (myClass, MyAttribute.class)) {
 *         doThis(myClass);
 *     } else {
 *         doThat(myClass);
 *     }
 * }
 * </code></pre>
 *
 * do:
 * 
 * <pre><code>
 * Class myClass = ...;
 * boolean shouldDoThis = Attributes.hasAttributeType (myClass, MyAttribute.class);
 * for (int i = 0; i < 10000; i++) {
 *     if (shouldDoThis) {
 *         doThis(myClass);
 *     } else {
 *         doThat(myClass);
 *     }
 * }
 * </code></pre>
 *
 * if the loop should run at maximum speed.
 *
 * @since 2.1
 */
public class Attributes {
    
    /**
     * A cache of attribute repositories. The map used is a WeakHashMap keyed on the
     * Class owning the attribute repository. This works because Class objects use
     * the identity function to compare for equality - i.e. if the two classes
     * have the same name, and are loaded from the same two ClassLoaders, then
     * <code>class1 == class2</code>. Also, <code>(class1.equals(class2)) == (class1 ==
     * class2)</code>. This means that a once a Class reference has been garbage-collected,
     * it can't be re-created. Thus we can treat the cache map as a normal map - the only
     * entries that will ever disappear are those we can't look up anyway because we
     * can't ever create the key for them!
     *
     * <p>Also, this will keep the cache from growing too big in environments where
     * classes are loaded and unloaded all the time (i.e. application servers).
     */
    private final static Map classRepositories = new WeakHashMap ();
    
    /**
     * List used to keep track of the initialization list in getCachedRepository.
     * Since the method is synchronized static, we only need one list.
     */
    private static List initList = new ArrayList ();
    
    private synchronized static CachedRepository getCachedRepository (Class clazz) throws RepositoryError, CircularDependencyError {
        if (initList.contains (clazz)) {
            List dependencyList = new ArrayList ();
            dependencyList.addAll (initList);
            dependencyList.add (clazz);
            throw new CircularDependencyError (clazz.getName (), dependencyList);
        } else if (classRepositories.containsKey (clazz)) {
            CachedRepository cr = (CachedRepository) classRepositories.get (clazz);
            return cr;
        } else {
            // Indicate that we're loading it.
            CachedRepository cached = null;
            
            initList.add (clazz);
            try {
                Class attributeRepo = null;
                AttributeRepositoryClass repo = EmptyAttributeRepositoryClass.INSTANCE;
                try {
                    attributeRepo = Class.forName (clazz.getName () + "$__attributeRepository", true, clazz.getClassLoader ());
                    repo = (AttributeRepositoryClass) attributeRepo.newInstance ();
                } catch (ClassNotFoundException cnfe) {
                    // OK, just means no repo available, so default to empty one.
                    repo = EmptyAttributeRepositoryClass.INSTANCE;
                } catch (InstantiationException ie) {
                    throw new RepositoryError (ie);
                } catch (IllegalAccessException iae) {
                    throw new RepositoryError (iae);
                }
                cached = new DefaultCachedRepository (clazz, repo);
                
                classRepositories.put (clazz, cached);
                
                if (repo != null) {
                    Util.validateRepository (clazz, repo);
                }
            } finally {
                initList.remove (initList.size () - 1);
            }
            
            return cached;
        }
    }
    
    /**
     * Selects from a collection of attributes one attribute with a given class.
     *
     * @param attrs the collection of attribute instances to select from.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     */
    private static Object getAttribute (Collection attrs, Class attributeClass) throws MultipleAttributesError {
        Object candidate = null;
        Iterator iter = attrs.iterator ();
        while (iter.hasNext ()) {
            Object attr = iter.next ();
            if (attr.getClass () == attributeClass) {
                if (candidate == null) {
                    candidate = attr;
                } else {
                    throw new MultipleAttributesError (attributeClass.getName ());
                }
            }
        }
        
        return candidate;
    }
    
    /**
     * Get one attributes of a given type from a class.
     *
     * @param clazz the class. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     *
     * @since 2.1
     */
    public static Object getAttribute (Class clazz, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getAttributes (clazz), attributeClass);
    }
    
    /**
     * Get one attributes of a given type from a field.
     *
     * @param field the field. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     *
     * @since 2.1
     */
    public static Object getAttribute (Field field, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getAttributes (field), attributeClass);
    }
    
    /**
     * Get one attributes of a given type from a constructor.
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     *
     * @since 2.1
     */
    public static Object getAttribute (Constructor constructor, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getAttributes (constructor), attributeClass);
    }
    
    /**
     * Get one attributes of a given type from a method.
     *
     * @param method the method. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     *
     * @since 2.1
     */
    public static Object getAttribute (Method method, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getAttributes (method), attributeClass);
    }
    
    /**
     * Get one attributes of a given type from a parameter.
     *
     * @param method the method. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @param parameter index of the parameter in the method's parameter list.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the method accepts.
     *
     * @since 2.1
     */
    public static Object getParameterAttribute (Method method, int parameter, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getParameterAttributes (method, parameter), attributeClass);
    }
    
    /**
     * Get one attributes of a given type from a constructor's parameter.
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param parameter index of the parameter in the method's parameter list.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the constructor accepts.
     *
     * @since 2.1
     */
    public static Object getParameterAttribute (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getParameterAttributes (constructor, parameter), attributeClass);
    }
    
    /**
     * Get one attributes of a given type from a method's return value.
     *
     * @param method the method. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @return the attribute instance, or <code>null</code> of none could be found.
     * @throws MultipleAttributesError if the collection contains more than one
     *         instance of the specified class.
     *
     * @since 2.1
     */
    public static Object getReturnAttribute (Method method, Class attributeClass) throws RepositoryError, MultipleAttributesError {
        return getAttribute (getReturnAttributes (method), attributeClass);
    }
    
    /**
     * Gets all attributes for a class.
     *
     * @param clazz the class. May not be <code>null</code>.    
     *
     * @since 2.1
     */
    public static Collection getAttributes (Class clazz) throws RepositoryError {
        if (clazz == null) {
            throw new NullPointerException ("clazz");
        }
        
        return getCachedRepository (clazz).getAttributes ();
    }
    
    /**
     * Gets all attributes for a method.
     *
     * @param method the method. May not be <code>null</code>.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Method method) throws RepositoryError {
        if (method == null) {
            throw new NullPointerException ("method");
        }
        
        return getCachedRepository (method.getDeclaringClass()).getAttributes (method);
    }
    
    /**
     * Gets all attributes for a parameter of a method.
     *
     * @param method the method. May not be <code>null</code>.
     * @param parameter the index of the parameter in the method's parameter list.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the method accepts.
     *    
     * @since 2.1
     */
    public static Collection getParameterAttributes (Method method, int parameter) throws RepositoryError {
        if (method == null) {
            throw new NullPointerException ("method");
        }
        
        return getCachedRepository (method.getDeclaringClass()).getParameterAttributes (method, parameter);
    }
    
    /**
     * Gets all attributes for a parameter of a constructor.
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param parameter the index of the parameter in the constructor's parameter list.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the constructor accepts.
     *
     * @since 2.1
     */
    public static Collection getParameterAttributes (Constructor constructor, int parameter) throws RepositoryError {
        if (constructor == null) {
            throw new NullPointerException ("constructor");
        }
        return getCachedRepository (constructor.getDeclaringClass()).getParameterAttributes (constructor, parameter);
    }
    
    /**
     * Gets all attributes for the return value of a method.
     *
     * @param method the method. May not be <code>null</code>.
     *
     * @since 2.1
     */
    public static Collection getReturnAttributes (Method method) throws RepositoryError {
        if (method == null) {
            throw new NullPointerException ("method");
        }
        return getCachedRepository (method.getDeclaringClass()).getReturnAttributes (method);
    }
    
    /**
     * Gets all attributes for a field.
     *
     * @param field the field. May not be <code>null</code>.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Field field) throws RepositoryError {
        if (field == null) {
            throw new NullPointerException ("field");
        }
        return getCachedRepository (field.getDeclaringClass()).getAttributes (field);
    }
    
    /**
     * Gets all attributes for a constructor.
     *
     * @param constructor the constructor. May not be <code>null</code>.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Constructor constructor) throws RepositoryError {
        if (constructor == null) {
            throw new NullPointerException ("constructor");
        }
        return getCachedRepository (constructor.getDeclaringClass()).getAttributes (constructor);
    }
    
    /**
     * Selects from a collection of attributes only those with a given class.
     *
     * @since 2.1
     */
    private static Collection getAttributes (Collection attrs, Class attributeClass) {
        HashSet result = new HashSet ();
        Iterator iter = attrs.iterator ();
        while (iter.hasNext ()) {
            Object attr = iter.next ();
            if (attr.getClass () == attributeClass) {
                result.add (attr);
            }
        }
        
        return Collections.unmodifiableCollection (result);
    }
    
    /**
     * Get all attributes of a given type from a class. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param clazz the class. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Class clazz, Class attributeClass) throws RepositoryError {
        return getAttributes (getAttributes (clazz), attributeClass);
    }
    
    /**
     * Get all attributes of a given type from a field. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param field the field. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Field field, Class attributeClass) throws RepositoryError {
        return getAttributes (getAttributes (field), attributeClass);
    }
    
    /**
     * Get all attributes of a given type from a constructor. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Constructor constructor, Class attributeClass) throws RepositoryError {
        return getAttributes (getAttributes (constructor), attributeClass);
    }
    
    /**
     * Get all attributes of a given type from a method. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param method the method. May not be <code>null</code>.
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static Collection getAttributes (Method method, Class attributeClass) throws RepositoryError {
        return getAttributes (getAttributes (method), attributeClass);
    }
    
    /**
     * Get all attributes of a given type from a method's parameter. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param method the method. May not be <code>null</code>.
     * @param parameter index of the parameter in the method's parameter list
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the method accepts.
     *
     * @since 2.1
     */
    public static Collection getParameterAttributes (Method method, int parameter, Class attributeClass) throws RepositoryError {
        return getAttributes (getParameterAttributes (method, parameter), attributeClass);
    }
    
    /**
     * Get all attributes of a given type from a method's parameter. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param parameter index of the parameter in the constructor's parameter list
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the constructor accepts.
     *
     * @since 2.1
     */
    public static Collection getParameterAttributes (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError {
        return getAttributes (getParameterAttributes (constructor, parameter), attributeClass);
    }
    
    /**
     * Get all attributes of a given type from a method's return value. For all objects o in the returned 
     * collection, <code>o.getClass() == attributeClass</code>.
     *
     * @param method the method
     * @param attributeClass the type of attribute wanted. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static Collection getReturnAttributes (Method method, Class attributeClass) throws RepositoryError {
        return getAttributes (getReturnAttributes (method), attributeClass);
    }
    
    /**
     * Convenience function to test whether a collection of attributes contain
     * an attribute of a given class.
     *
     * @since 2.1
     */
    private static boolean hasAttributeType (Collection attrs, Class attributeClass) {
        Iterator iter = attrs.iterator ();
        while (iter.hasNext ()) {
            Object attr = iter.next ();
            if (attr.getClass () == attributeClass) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Tests if a class has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param clazz the class. May not be <code>null</code>.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static boolean hasAttributeType (Class clazz, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getAttributes (clazz), attributeClass);
    }
    
    /**
     * Tests if a field has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param field the field. May not be <code>null</code>.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static boolean hasAttributeType (Field field, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getAttributes (field), attributeClass);
    }
    
    /**
     * Tests if a constructor has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static boolean hasAttributeType (Constructor constructor, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getAttributes (constructor), attributeClass);
    }
    
    /**
     * Tests if a method has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param method the method. May not be <code>null</code>.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static boolean hasAttributeType (Method method, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getAttributes (method), attributeClass);
    }
    
    /**
     * Tests if a method's parameter has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param method the method. May not be <code>null</code>.
     * @param parameter the index of the parameter in the method's parameter list.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the method accepts.
     *
     * @since 2.1
     */
    public static boolean hasParameterAttributeType (Method method, int parameter, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getParameterAttributes (method, parameter), attributeClass);
    }
    
    /**
     * Tests if a constructor's parameter has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param parameter the index of the parameter in the constructor's parameter list.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the constructor accepts.
     *
     * @since 2.1
     */
    public static boolean hasParameterAttributeType (Constructor constructor, int parameter, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getParameterAttributes (constructor, parameter), attributeClass);
    }
    
    /**
     * Tests if a method's return value has an attribute of a given type. That is, is there any attribute
     * <code>attr</code> such that <code>attr.getClass() == attributeClass</code>?
     *
     * @param method the method. May not be <code>null</code>.
     * @param attributeClass the type of attribute. May be <code>null</code>, but this will not match anything.
     *
     * @since 2.1
     */
    public static boolean hasReturnAttributeType (Method method, Class attributeClass) throws RepositoryError {
        return hasAttributeType (getReturnAttributes (method), attributeClass);
    }
    
    /**
     * Convenience function to test whether a collection of attributes contain
     * an attribute.
     *
     * @since 2.1
     */
    private static boolean hasAttribute (Collection attrs, Object attribute) throws RepositoryError {
        return attrs.contains (attribute);
    }
    
    /**
     * Tests if a class has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *
     * @param clazz the class. May not be <code>null</code>.
     * @param attribute the attribute to compare to.
     *
     * @since 2.1
     */
    public static boolean hasAttribute (Class clazz, Object attribute) throws RepositoryError {
        return hasAttribute (getAttributes (clazz), attribute);
    }
    
    /**
     * Tests if a field has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *
     * @param field the field. May not be <code>null</code>.
     * @param attribute the attribute to compare to.
     *
     * @since 2.1
     */
    public static boolean hasAttribute (Field field, Object attribute) throws RepositoryError {
        return hasAttribute (getAttributes (field), attribute);
    }
    
    /**
     * Tests if a constructor has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param attribute the attribute to compare to.
     *
     * @since 2.1
     */
    public static boolean hasAttribute (Constructor constructor, Object attribute) throws RepositoryError {
        return hasAttribute (getAttributes (constructor), attribute);
    }
    
    /**
     * Tests if a method has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *
     * @param method the method. May not be <code>null</code>.
     * @param attribute the attribute to compare to.
     *
     * @since 2.1
     */
    public static boolean hasAttribute (Method method, Object attribute) throws RepositoryError {
        return hasAttribute (getAttributes (method), attribute);
    }
    
    /**
     * Tests if a method's parameter has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *    
     * @param method the method. May not be <code>null</code>.
     * @param parameter the index of the parameter in the method's parameter list.
     * @param attribute the attribute to compare to.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the method accepts.
     *
     * @since 2.1
     */
    public static boolean hasParameterAttribute (Method method, int parameter, Object attribute) throws RepositoryError {
        return hasAttribute (getParameterAttributes (method, parameter), attribute);
    }
    
    /**
     * Tests if a constructor's parameter has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *
     * @param constructor the constructor. May not be <code>null</code>.
     * @param parameter the index of the parameter in the constructor's parameter list.
     * @param attribute the attribute to compare to.
     * @throws ParameterIndexOutOfBoundsException if the parameter index is < 0 or greater than the number 
     *                                            of parameters the constructor accepts.
     *
     * @since 2.1
     */
    public static boolean hasParameterAttribute (Constructor constructor, int parameter, Object attribute) throws RepositoryError {
        return hasAttribute (getParameterAttributes (constructor, parameter), attribute);
    }
    
    /**
     * Tests if a method's return value has an attribute. That is, is there any attribute
     * <code>attr</code> such that <code>attr.equals(attribute)</code>?
     *
     * @param method the method. May not be <code>null</code>.
     * @param attribute the attribute to compare to.
     *
     * @since 2.1
     */
    public static boolean hasReturnAttribute (Method method, Object attribute) throws RepositoryError {
        return hasAttribute (getReturnAttributes (method), attribute);
    }
    
    /**
     * Set attributes for a given class. The class must not have attributes set for it already
     * (i.e. you can't redefine the attributes of a class at runtime). This because it
     * really messes up the concept of inheritable attributes, and because the attribute set
     * associated with a class is immutable, like the set of methods and fields.
     *
     * @param repo The repository. The repository will be sealed before any attributes are
     *             set. This to guarantee that the repository remains constant
     *             during setting.
     * @throws IllegalStateException if the class whose attributes are defined already have
     *         attributes defined for it (even if it has no attributes).
     * @since 2.1
     */
    public static synchronized void setAttributes (RuntimeAttributeRepository repo) throws IllegalStateException {
        if (repo == null) {
            throw new NullPointerException ("repo");
        }
        
        repo.seal ();
        
        Class clazz = repo.getDefinedClass ();
        if (classRepositories.get (clazz) != null) {
            throw new IllegalStateException (clazz.getName ());
        }
        Util.validateRepository (clazz, repo);
        
        DefaultCachedRepository cached = new DefaultCachedRepository (clazz, repo);
        classRepositories.put (clazz, cached);
    }
}