com.cyberway.issue.crawler.settings.ComplexType.java Source code

Java tutorial

Introduction

Here is the source code for com.cyberway.issue.crawler.settings.ComplexType.java

Source

/* ComplexType
 *
 * $Id: ComplexType.java 5028 2007-03-29 23:21:48Z gojomo $
 *
 * Created on Dec 17, 2003
 *
 * Copyright (C) 2004 Internet Archive.
 *
 * This file is part of the Heritrix web crawler (crawler.archive.org).
 *
 * Heritrix is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * any later version.
 *
 * Heritrix is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with Heritrix; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.cyberway.issue.crawler.settings;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.ReflectionException;

import org.apache.commons.httpclient.URIException;
import com.cyberway.issue.crawler.datamodel.CandidateURI;
import com.cyberway.issue.crawler.datamodel.CrawlOrder;
import com.cyberway.issue.crawler.datamodel.CrawlURI;
import com.cyberway.issue.crawler.settings.Constraint.FailedCheck;
import com.cyberway.issue.net.UURI;

/** Superclass of all configurable modules.
 *
 * This class is in many ways the heart of the settings framework. All modules
 * that should be configurable extends this class or one of its subclasses.
 *
 * All subclasses of this class will automatically conform to the
 * JMX DynamicMBean. You could then use the {@link #getMBeanInfo()} method to
 * investigate which attributes this module supports and then use the
 * {@link #getAttribute(String)} and {@link #setAttribute(Attribute)} methods to
 * alter the attributes values.
 *
 * Because the settings framework supports per domain/host settings there is
 * also available context sensitive versions of the DynamicMBean methods.
 * If you use the non context sensitive methods, it is the global settings
 * that will be altered.
 *
 * @author John Erik Halse
 */
public abstract class ComplexType extends Type implements DynamicMBean {
    private static Logger logger = Logger.getLogger("com.cyberway.issue.crawler.settings.ComplexType");

    private transient SettingsHandler settingsHandler;
    private transient ComplexType parent;
    private String description;
    private String absoluteName;
    protected final List<Type> definition = new ArrayList<Type>();
    protected final Map<String, Type> definitionMap = new HashMap<String, Type>();
    private boolean initialized = false;
    private String[] preservedFields = new String[0];

    /**
     * Private constructor to make sure that no one
     * instantiates this class with the empty constructor.
     */
    private ComplexType() {
        super(null, null);
    }

    /** Creates a new instance of ComplexType.
     *
     * @param name the name of the element.
     * @param description the description of the element.
     */
    public ComplexType(String name, String description) {
        super(name, null);
        this.description = description.intern();
    }

    protected void setAsOrder(SettingsHandler settingsHandler) throws InvalidAttributeValueException {
        this.settingsHandler = settingsHandler;
        this.absoluteName = "";
        globalSettings().addTopLevelModule((CrawlOrder) this);
        addComplexType(settingsHandler.getSettingsObject(null), this);
        this.parent = null;
    }

    /** Get the global settings object (aka order).
     *
     * @return the global settings object.
     */
    public CrawlerSettings globalSettings() {
        if (settingsHandler == null) {
            return null;
        }
        return settingsHandler.getSettingsObject(null);
    }

    public Type addElement(CrawlerSettings settings, Type type) throws InvalidAttributeValueException {
        getOrCreateDataContainer(settings).addElementType(type);
        if (type instanceof ComplexType) {
            addComplexType(settings, (ComplexType) type);
        }
        return type;
    }

    private ComplexType addComplexType(CrawlerSettings settings, ComplexType object)
            throws InvalidAttributeValueException {

        if (this.settingsHandler == null) {
            throw new IllegalStateException("Can't add ComplexType to 'free' ComplexType");
        }
        setupVariables(object);
        settings.addComplexType(object);
        if (!object.initialized) {
            Iterator it = object.definition.iterator();
            while (it.hasNext()) {
                Type t = (Type) it.next();
                object.addElement(settings, t);
            }
            object.earlyInitialize(settings);
        }
        object.initialized = true;

        return object;
    }

    private ComplexType replaceComplexType(CrawlerSettings settings, ComplexType object)
            throws InvalidAttributeValueException, AttributeNotFoundException {
        if (this.settingsHandler == null) {
            throw new IllegalStateException("Can't add ComplexType to 'free' ComplexType");
        }
        String[] preservedFields = object.getPreservedFields();

        setupVariables(object);

        DataContainer oldData = settings.getData(object);
        settings.addComplexType(object);
        DataContainer newData = settings.getData(object);

        if (!object.initialized) {
            Iterator it = object.definition.iterator();
            while (it.hasNext()) {
                Type t = (Type) it.next();

                // Check if attribute should be copied from old object.
                boolean found = false;
                if (preservedFields.length > 0) {
                    for (int i = 0; i < preservedFields.length; i++) {
                        if (preservedFields[i].equals(t.getName())) {
                            found = true;
                            break;
                        }
                    }
                }
                if (found && oldData.copyAttribute(t.getName(), newData)) {
                    if (t instanceof ComplexType) {
                        object.setupVariables((ComplexType) t);
                    }
                } else {
                    object.addElement(settings, t);
                }
            }
            object.earlyInitialize(settings);
        }
        object.initialized = true;

        return object;
    }

    /** Set a list of attribute names that the complex type should attempt to
     * preserve if the module is exchanged with an other one.
     *
     * @param preservedFields array of attributenames to preserve.
     */
    protected void setPreservedFields(String[] preservedFields) {
        this.preservedFields = preservedFields;
    }

    /** Get a list of attribute names that the complex type should attempt to
     * preserve if the module is exchanged with an other one.
     *
     * @return an array of attributenames to preserve.
     */
    protected String[] getPreservedFields() {
        return this.preservedFields;
    }

    /** Get the active data container for this ComplexType for a specific
     * settings object.
     *
     * If no value has been overridden on the settings object for this
     * ComplexType, then it traverses up until it find a DataContainer with
     * values for this ComplexType.
     *
     * This method should probably not be called from user code. It is a helper
     * method for the settings framework.
     *
     * @param context Context from which we get settings.
     * @return the active DataContainer.
     */
    protected DataContainer getDataContainerRecursive(Context context) {
        if (context.settings == null) {
            return null;
        }
        DataContainer data = context.settings.getData(this);
        if (data == null && context.settings.getParent(context.uri) != null) {
            context.settings = context.settings.getParent(context.uri);
            data = getDataContainerRecursive(context);
        }
        return data;
    }

    /** Get the active data container for this ComplexType for a specific
     * settings object.
     *
     * If the key has not been overridden on the settings object for this
     * ComplexType, then it traverses up until it find a DataContainer with
     * the key for this ComplexType.
     *
     * This method should probably not be called from user code. It is a helper
     * method for the settings framework.
     *
     * @param context the settings object for which the {@link DataContainer}
     *                 is active.
     * @param key the key to look for.
     * @return the active DataContainer.
     * @throws AttributeNotFoundException
     */
    protected DataContainer getDataContainerRecursive(Context context, String key)
            throws AttributeNotFoundException {
        Context c = new Context(context.settings, context.uri);
        DataContainer data = getDataContainerRecursive(c);
        while (data != null) {
            if (data.containsKey(key)) {
                return data;
            }
            c.settings = data.getSettings().getParent(c.uri);
            data = getDataContainerRecursive(c);
        }
        throw new AttributeNotFoundException(key);
    }

    /** Sets up some variables for a new complex type.
     *
     * The complex type is set up to be an attribute of
     * this complex type.
     *
     * @param object to be set up.
     */
    private void setupVariables(ComplexType object) {
        object.parent = this;
        object.settingsHandler = getSettingsHandler();
        object.absoluteName = (getAbsoluteName() + '/' + object.getName()).intern();
    }

    public SettingsHandler getSettingsHandler() {
        return settingsHandler;
    }

    /** Get the absolute name of this ComplexType.
     *
     * The absolute name is like a file path with the name of the element
     * prepended by all the parents names separated by slashes.
     * @return Absolute name.
     */
    public String getAbsoluteName() {
        return absoluteName;
    }

    /**
     * Get settings object valid for a URI.
     * <p/>
     * This method takes an object,
     * try to convert it into a {@link CrawlURI} and then tries to get the
     * settings object from it. If this fails, then the global settings object
     * is returned.
     * <p/>
     * If the requested attribute is not set on this settings
     * object it tries its parent until it gets a settings object where this
     * attribute is set is found. If nothing is found, global settings is
     * returned.
     *
     * @param o possible {@link CrawlURI}.
     * @param attributeName the attribute that should have a value set on the
     *            returned settings object.
     * @return the settings object valid for the URI.
     */
    Context getSettingsFromObject(Object o, String attributeName) {
        Context context;
        if (o == null) {
            context = null;
        } else if (o instanceof Context) {
            context = (Context) o;
        } else if (o instanceof CrawlerSettings) {
            context = new Context((CrawlerSettings) o, null);
        } else if (o instanceof UURI || o instanceof CandidateURI) {
            // Try to get settings for URI that has no references to a
            // CrawlServer [SIC - CrawlURI may have CrawlServer -gjm]
            context = new Context();
            context.uri = (o instanceof CandidateURI) ? ((CandidateURI) o).getUURI() : (UURI) o;
            try {
                context.settings = getSettingsHandler().getSettings(context.uri.getReferencedHost(), context.uri);
            } catch (URIException e1) {
                logger.severe("Failed to get host");
            }

            if (attributeName != null) {
                try {
                    context.settings = getDataContainerRecursive(context, attributeName).getSettings();
                } catch (AttributeNotFoundException e) {
                    // Nothing found, globals will be used
                }
            }
        } else {
            logger.warning("Unknown object type: " + o.getClass().getName());
            context = null;
        }

        // if settings could not be resolved use globals.
        if (context == null) {
            context = new Context(globalSettings(), null);
        }
        return context;
    }

    /** Get settings object valid for a URI.
    *
    * This method takes an object, try to convert it into a {@link CrawlURI}
    * and then tries to get the settings object from it. If this fails, then
    * the global settings object is returned.
    *
    * @param o possible {@link CrawlURI}.
    * @return the settings object valid for the URI.
    */
    Context getSettingsFromObject(Object o) {
        return getSettingsFromObject(o, null);
    }

    /** Returns true if an element is overridden for this settings object.
     *
     * @param settings the settings object to investigate.
     * @param name the name of the element to check.
     * @return true if element is overridden for this settings object, false
     *              if not set here or is first defined here.
     * @throws AttributeNotFoundException if element doesn't exist.
     */
    public boolean isOverridden(CrawlerSettings settings, String name) throws AttributeNotFoundException {
        settings = settings == null ? globalSettings() : settings;
        DataContainer data = settings.getData(this);
        if (data == null || !data.containsKey(name)) {
            return false;
        }

        // Try to find attribute, will throw an exception if not found.
        Context context = new Context(settings.getParent(), null);
        getDataContainerRecursive(context, name);
        return true;
    }

    /** Obtain the value of a specific attribute from the crawl order.
     *
     * If the attribute doesn't exist in the crawl order, the default
     * value will be returned.
     *
     * @param name the name of the attribute to be retrieved.
     * @return The value of the attribute retrieved.
     * @throws AttributeNotFoundException
     * @throws MBeanException
     * @throws ReflectionException
     */
    public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException {
        return getAttribute(null, name);
    }

    /** Obtain the value of a specific attribute that is valid for a
     * specific CrawlURI.
     *
     * This method will try to get the attribute from the host settings
     * valid for the CrawlURI. If it is not found it will traverse the
     * settings up to the order and as a last resort deliver the default
     * value. This is also the case if the CrawlURI is null or if the CrawlURI
     * hasn't been assigned a CrawlServer.
     *
     * @param name the name of the attribute to be retrieved.
     * @param uri the CrawlURI that this attribute should be valid for.
     * @return The value of the attribute retrieved.
     * @see #getAttribute(Object settings, String name)
     * @throws AttributeNotFoundException
     */
    public Object getAttribute(String name, CrawlURI uri) throws AttributeNotFoundException {
        return getAttribute(uri, name);
    }

    /**
     * Obtain the value of a specific attribute that is valid for a specific
     * CrawlerSettings object.<p>
     * 
     * This method will first try to get a settings object from the supplied
     * context, then try to look up the attribute from this settings object. If
     * it is not found it will traverse the settings up to the order and as a
     * last resort deliver the default value.
     * 
     * @param context the object to get the settings from.
     * @param name the name of the attribute to be retrieved.
     * @return The value of the attribute retrieved.
     * @see CrawlerSettings
     * @throws AttributeNotFoundException
     */
    public Object getAttribute(Object context, String name) throws AttributeNotFoundException {
        Context ctxt = getSettingsFromObject(context);

        // If settings is not set, return the default value
        if (ctxt.settings == null) {
            try {
                return ((Type) definitionMap.get(name)).getDefaultValue();
            } catch (NullPointerException e) {
                throw new AttributeNotFoundException("Could not find attribute: " + name);
            }
        }

        return getDataContainerRecursive(ctxt, name).get(name);
    }

    /**
     * Obtain the value of a specific attribute that is valid for a specific
     * CrawlerSettings object.
     * <p>
     * 
     * This method will first try to get a settings object from the supplied
     * context, then try to look up the attribute from this settings object. If
     * it is not found it will traverse the settings up to the order and as a
     * last resort deliver the default value.
     * <p>
     * 
     * The only difference from the {@link #getAttribute(Object, String)}is
     * that this method doesn't throw any checked exceptions. If an undefined
     * attribute is requested from a ComplexType, it is concidered a bug and a
     * runtime exception is thrown instead.
     * 
     * @param context the object to get the settings from.
     * @param name the name of the attribute to be retrieved.
     * @return The value of the attribute retrieved.
     * @see #getAttribute(Object, String)
     * @see CrawlerSettings
     * @throws IllegalArgumentException
     */
    public Object getUncheckedAttribute(Object context, String name) {
        try {
            return getAttribute(context, name);
        } catch (AttributeNotFoundException e) {
            throw new IllegalArgumentException("Was passed '" + name + "' and got this exception: " + e);
        }
    }

    /** Obtain the value of a specific attribute that is valid for a
     * specific CrawlerSettings object.
     *
     * This method will try to get the attribute from the supplied host
     * settings object. If it is not found it will return <code>null</code>
     * and not try to investigate the hierarchy of settings.
     *
     * @param settings the CrawlerSettings object to search for this attribute.
     * @param name the name of the attribute to be retrieved.
     * @return The value of the attribute retrieved or null if its not set.
     * @see CrawlerSettings
     * @throws AttributeNotFoundException is thrown if the attribute doesn't
     *         exist.
     */
    public Object getLocalAttribute(CrawlerSettings settings, String name) throws AttributeNotFoundException {

        settings = settings == null ? globalSettings() : settings;

        DataContainer data = settings.getData(this);
        if (data != null && data.containsKey(name)) {
            // Attribute was found return it.
            return data.get(name);
        }
        // Try to find the attribute, will throw an exception if not found.
        Context context = new Context(settings, null);
        getDataContainerRecursive(context, name);
        return null;
    }

    /** Set the value of a specific attribute of the ComplexType.
     *
     * This method sets the specific attribute for the order file.
     *
     * @param attribute The identification of the attribute to be set and the
     *                  value it is to be set to.
     * @throws AttributeNotFoundException is thrown if there is no attribute
     *         with this name.
     * @throws InvalidAttributeValueException is thrown if the attribute is of
     *         wrong type and cannot be converted to the right type.
     * @throws MBeanException this is to conform to the MBean specification, but
     *         this exception is never thrown, though this might change in the
     *         future.
     * @throws ReflectionException this is to conform to the MBean specification, but
     *         this exception is never thrown, though this might change in the
     *         future.
     * @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute)
     */
    public synchronized final void setAttribute(Attribute attribute)
            throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
        setAttribute(settingsHandler.getSettingsObject(null), attribute);
    }

    /** Set the value of a specific attribute of the ComplexType.
     *
     * This method is an extension to the Dynamic MBean specification so that
     * it is possible to set the value for a CrawlerSettings object other than
     * the settings object representing the order.
     *
     * @param settings the settings object for which this attributes value is valid
     * @param attribute The identification of the attribute to be set and the
     *                  value it is to be set to.
     * @throws AttributeNotFoundException is thrown if there is no attribute
     *         with this name.
     * @throws InvalidAttributeValueException is thrown if the attribute is of
     *         wrong type and cannot be converted to the right type.
     * @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute)
     */
    public synchronized final void setAttribute(CrawlerSettings settings, Attribute attribute)
            throws InvalidAttributeValueException, AttributeNotFoundException {

        if (settings == null) {
            settings = globalSettings();
        }

        DataContainer data = getOrCreateDataContainer(settings);
        Object value = attribute.getValue();

        ModuleAttributeInfo attrInfo = (ModuleAttributeInfo) getAttributeInfo(settings.getParent(),
                attribute.getName());

        ModuleAttributeInfo localAttrInfo = (ModuleAttributeInfo) data.getAttributeInfo(attribute.getName());

        // Check if attribute exists
        if (attrInfo == null && localAttrInfo == null) {
            throw new AttributeNotFoundException(attribute.getName());
        }

        // Check if we are overriding and if that is allowed for this attribute
        if (localAttrInfo == null) {
            if (!attrInfo.isOverrideable()) {
                throw new InvalidAttributeValueException("Attribute not overrideable: " + attribute.getName());
            }
            localAttrInfo = new ModuleAttributeInfo(attrInfo);
        }

        // Check if value is of correct type. If not, see if it is
        // a string and try to turn it into right type
        Class typeClass = getDefinition(attribute.getName()).getLegalValueType();
        if (!(typeClass.isInstance(value)) && value instanceof String) {
            try {
                value = SettingsHandler.StringToType((String) value,
                        SettingsHandler.getTypeName(typeClass.getName()));
            } catch (ClassCastException e) {
                throw new InvalidAttributeValueException(
                        "Unable to decode string '" + value + "' into type '" + typeClass.getName() + "'");
            }
        }

        // Check if the attribute value is legal
        FailedCheck error = checkValue(settings, attribute.getName(), value);
        if (error != null) {
            if (error.getLevel() == Level.SEVERE) {
                throw new InvalidAttributeValueException(error.getMessage());
            } else if (error.getLevel() == Level.WARNING) {
                if (!getSettingsHandler().fireValueErrorHandlers(error)) {
                    throw new InvalidAttributeValueException(error.getMessage());
                }
            } else {
                getSettingsHandler().fireValueErrorHandlers(error);
            }
        }

        // Everything ok, set it
        localAttrInfo.setType(value);
        Object oldValue = data.put(attribute.getName(), localAttrInfo, value);

        // If the attribute is a complex type other than the old value,
        // make sure that all sub attributes are correctly set
        if (value instanceof ComplexType && value != oldValue) {
            ComplexType complex = (ComplexType) value;
            replaceComplexType(settings, complex);
        }
    }

    /**
     * Get the content type definition for an attribute.
     *
     * @param attributeName the name of the attribute to get definition for.
     * @return the content type definition for the attribute.
     */
    Type getDefinition(String attributeName) {
        return (Type) definitionMap.get(attributeName);
    }

    /**
     * Check an attribute to see if it fulfills all the constraints set on the
     * definition of this attribute.
     *
     * @param settings the CrawlerSettings object for which this check was
     *            executed.
     * @param attributeName the name of the attribute to check.
     * @param value the value to check.
     * @return null if everything is ok, otherwise it returns a FailedCheck
     *         object with detailed information of what went wrong.
     */
    public FailedCheck checkValue(CrawlerSettings settings, String attributeName, Object value) {
        return checkValue(settings, attributeName, getDefinition(attributeName), value);
    }

    FailedCheck checkValue(CrawlerSettings settings, String attributeName, Type definition, Object value) {
        FailedCheck res = null;

        // Check if value fulfills any constraints
        List constraints = definition.getConstraints();
        if (constraints != null) {
            for (Iterator it = constraints.iterator(); it.hasNext() && res == null;) {
                res = ((Constraint) it.next()).check(settings, this, definition, value);
            }
        }

        return res;
    }

    /** Unset an attribute on a per host level.
     *
     * This methods removes an override on a per host or per domain level.
     *
     * @param settings the settings object for which the attribute should be
     *        unset.
     * @param name the name of the attribute.
     * @return The removed attribute or null if nothing was removed.
     * @throws AttributeNotFoundException is thrown if the attribute name
     *         doesn't exist.
     */
    public Object unsetAttribute(CrawlerSettings settings, String name) throws AttributeNotFoundException {

        if (settings == globalSettings()) {
            throw new IllegalArgumentException("Not allowed to unset attributes in Crawl Order.");
        }

        DataContainer data = settings.getData(this);
        if (data != null && data.containsKey(name)) {
            // Remove value
            return data.removeElement(name);
        }

        // Value not found. Check if we should return null or throw an exception
        // This method throws an exception if not found.
        Context context = new Context(settings, null);
        getDataContainerRecursive(context, name);
        return null;
    }

    private DataContainer getOrCreateDataContainer(CrawlerSettings settings) throws InvalidAttributeValueException {

        // Get this ComplexType's data container for the submitted settings
        DataContainer data = settings.getData(this);

        // If there isn't a container, create one
        if (data == null) {
            ComplexType parent = getParent();
            if (parent == null) {
                settings.addTopLevelModule((ModuleType) this);
            } else {
                DataContainer parentData = settings.getData(parent);
                if (parentData == null) {
                    if (this instanceof ModuleType) {
                        settings.addTopLevelModule((ModuleType) this);
                    } else {
                        settings.addTopLevelModule((ModuleType) parent);
                        try {
                            parent.setAttribute(settings, this);
                        } catch (AttributeNotFoundException e) {
                            logger.severe(e.getMessage());
                        }
                    }
                } else {
                    globalSettings().getData(parent).copyAttributeInfo(getName(), parentData);
                }
            }

            // Create fresh DataContainer
            data = settings.addComplexType(this);
        }

        // Make sure that the DataContainer references right type
        if (data.getComplexType() != this) {
            if (this instanceof ModuleType) {
                data = settings.addComplexType(this);
            }
        }
        return data;
    }

    /* (non-Javadoc)
     * @see javax.management.DynamicMBean#getAttributes(java.lang.String[])
     */
    public AttributeList getAttributes(String[] name) {
        return null;
    }

    /* (non-Javadoc)
     * @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList)
     */
    public AttributeList setAttributes(AttributeList attributes) {
        return null;
    }

    /* (non-Javadoc)
     * @see javax.management.DynamicMBean#invoke(java.lang.String, java.lang.Object[], java.lang.String[])
     */
    public Object invoke(String arg0, Object[] arg1, String[] arg2) throws MBeanException, ReflectionException {
        throw new ReflectionException(new NoSuchMethodException("No methods to invoke."));
    }

    /* (non-Javadoc)
     * @see javax.management.DynamicMBean#getMBeanInfo()
     */
    public MBeanInfo getMBeanInfo() {
        return getMBeanInfo(globalSettings());
    }

    public MBeanInfo getMBeanInfo(Object context) {
        MBeanAttributeInfoIterator it = getAttributeInfoIterator(context);
        MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[it.size()];
        int index = 0;
        while (it.hasNext()) {
            attributes[index++] = (MBeanAttributeInfo) it.next();
        }

        MBeanInfo info = new MBeanInfo(getClass().getName(), getDescription(), attributes, null, null, null);
        return info;
    }

    /** Get the effective Attribute info for an element of this type from
     * a settings object.
     *
     * @param settings the settings object for which the Attribute info is
     *        effective.
     * @param name the name of the element to get the attribute for.
     * @return the attribute info
     */
    public MBeanAttributeInfo getAttributeInfo(CrawlerSettings settings, String name) {

        MBeanAttributeInfo info = null;

        Context context = new Context(settings, null);
        DataContainer data = getDataContainerRecursive(context);
        while (data != null && info == null) {
            info = data.getAttributeInfo(name);
            if (info == null) {
                context.settings = data.getSettings().getParent();
                data = getDataContainerRecursive(context);
            }
        }

        return info;
    }

    /** Get the Attribute info for an element of this type from the global
     * settings.
     *
     * @param name the name of the element to get the attribute for.
     * @return the attribute info
     */
    public MBeanAttributeInfo getAttributeInfo(String name) {
        return getAttributeInfo(globalSettings(), name);
    }

    /** Get the description of this type
     *
     * The description should be suitable for showing in a user interface.
     *
     * @return this type's description
     */
    public String getDescription() {
        return description;
    }

    /** Get the parent of this ComplexType.
     *
     * @return the parent of this ComplexType.
     */
    public ComplexType getParent() {
        return parent;
    }

    /** Set the description of this ComplexType
     *
     * The description should be suitable for showing in a user interface.
     *
     * @param string the description to set for this type.
     */
    public void setDescription(String string) {
        description = string;
    }

    /* (non-Javadoc)
     * @see com.cyberway.issue.crawler.settings.Type#getDefaultValue()
     */
    public Object getDefaultValue() {
        return this;
    }

    /** Add a new attribute to the definition of this ComplexType.
     *
     * This method can only be called before the ComplexType has been
     * initialized. This usally means that this method is available for
     * constructors of subclasses of this class.
     *
     * @param type the type to add.
     * @return the newly added type.
     */
    public Type addElementToDefinition(Type type) {
        if (isInitialized()) {
            throw new IllegalStateException("Elements should only be added to definition in the " + "constructor.");
        }
        if (definitionMap.containsKey(type.getName())) {
            definition.remove(definitionMap.remove(type.getName()));
        }

        definition.add(type);
        definitionMap.put(type.getName(), type);
        return type;
    }

    /** Get an element definition from this complex type.
     *
     * This method can only be called before the ComplexType has been
     * initialized. This usally means that this method is available for
     * constructors of subclasses of this class.
     *
     * @param name name of element to get.
     * @return the requested element or null if non existent.
     */
    public Type getElementFromDefinition(String name) {
        if (isInitialized()) {
            throw new IllegalStateException("Elements definition can only be accessed in the " + "constructor.");
        }
        return (Type) definitionMap.get(name);
    }

    /**
     * This method can only be called before the ComplexType has been
     * initialized. This usually means that this method is available for
     * constructors of subclasses of this class.
     * @param name Name of element to remove.
     * @return Element removed.
     */
    protected Type removeElementFromDefinition(final String name) {
        if (isInitialized()) {
            throw new IllegalStateException("Elements definition can only be removed in constructor.");
        }
        Object removedObj = this.definitionMap.remove(name);
        if (removedObj != null) {
            this.definition.remove(removedObj);
        }
        return (Type) removedObj;
    }

    /** This method can be overridden in subclasses to do local
     * initialisation.
     *
     * This method is run before the class has been updated with
     * information from settings files. That implies that if you
     * call getAttribute inside this method you will only get the
     * default values.
     *
     * @param settings the CrawlerSettings object for which this
     *        complex type is defined.
     */
    public void earlyInitialize(CrawlerSettings settings) {
    }

    /** Returns true if this ComplexType is initialized.
     *
     * @return true if this ComplexType is initialized.
     */
    public boolean isInitialized() {
        return initialized;
    }

    public Object[] getLegalValues() {
        return null;
    }

    /** Returns this object.
     *
     * This method is implemented to be able to treat the ComplexType as an
     * subclass of {@link javax.management.Attribute}.
     *
     * @return this object.
     * @see javax.management.Attribute#getValue()
     */
    public Object getValue() {
        return this;
    }

    class Context {
        CrawlerSettings settings;
        UURI uri;

        Context() {
            settings = null;
            uri = null;
        }

        Context(CrawlerSettings settings, UURI uri) {
            this.settings = settings;
            this.uri = uri;
        }
    }

    /** Get an Iterator over all the attributes in this ComplexType.
    *
    * @param context the context for which this set of attributes are valid.
    * @return an iterator over all the attributes in this map.
    */
    public Iterator iterator(Object context) {
        return new AttributeIterator(context);
    }

    /** Get an Iterator over all the MBeanAttributeInfo in this ComplexType.
    *
    * @param context the context for which this set of MBeanAttributeInfo are valid.
    * @return an iterator over all the MBeanAttributeInfo in this map.
    */
    public MBeanAttributeInfoIterator getAttributeInfoIterator(Object context) {
        return new MBeanAttributeInfoIterator(context);
    }

    /**
     * Iterator over all attributes in a ComplexType.
     *
     * @author John Erik Halse
     */
    private class AttributeIterator implements Iterator {
        private Context context;
        private Stack<Iterator<MBeanAttributeInfo>> attributeStack = new Stack<Iterator<MBeanAttributeInfo>>();
        private Iterator currentIterator;

        public AttributeIterator(Object ctxt) {
            this.context = getSettingsFromObject(ctxt);
            Context c = new Context(context.settings, context.uri);
            DataContainer data = getDataContainerRecursive(c);
            while (data != null) {
                this.attributeStack.push(data.getLocalAttributeInfoList().iterator());
                c.settings = data.getSettings().getParent();
                data = getDataContainerRecursive(c);
            }

            this.currentIterator = (Iterator) this.attributeStack.pop();
        }

        public boolean hasNext() {
            if (this.currentIterator.hasNext()) {
                return true;
            }
            if (this.attributeStack.isEmpty()) {
                return false;
            }
            this.currentIterator = (Iterator) this.attributeStack.pop();
            return this.currentIterator.hasNext();
        }

        public Object next() {
            hasNext();
            try {
                MBeanAttributeInfo attInfo = (MBeanAttributeInfo) this.currentIterator.next();
                Object attr = getAttribute(this.context, attInfo.getName());
                if (!(attr instanceof Attribute)) {
                    attr = new Attribute(attInfo.getName(), attr);
                }
                return attr;
            } catch (AttributeNotFoundException e) {
                // This should never happen
                e.printStackTrace();
                return null;
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Iterator over all MBeanAttributeInfo for this ComplexType
     *
     * @author John Erik Halse
     */
    public class MBeanAttributeInfoIterator implements Iterator {
        private Context context;
        private Stack<Iterator<MBeanAttributeInfo>> attributeStack = new Stack<Iterator<MBeanAttributeInfo>>();
        private Iterator currentIterator;
        private int attributeCount = 0;

        public MBeanAttributeInfoIterator(Object ctxt) {
            this.context = getSettingsFromObject(ctxt);
            //Stack attributeStack = new Stack();
            //
            DataContainer data = getDataContainerRecursive(context);
            while (data != null) {
                attributeStack.push(data.getLocalAttributeInfoList().iterator());
                attributeCount += data.getLocalAttributeInfoList().size();
                context.settings = data.getSettings().getParent();
                data = getDataContainerRecursive(context);
            }

            this.currentIterator = (Iterator) this.attributeStack.pop();
        }

        public boolean hasNext() {
            if (this.currentIterator.hasNext()) {
                return true;
            }
            if (this.attributeStack.isEmpty()) {
                return false;
            }
            this.currentIterator = (Iterator) this.attributeStack.pop();
            return this.currentIterator.hasNext();
        }

        public Object next() {
            hasNext();
            MBeanAttributeInfo attInfo = (MBeanAttributeInfo) this.currentIterator.next();
            return attInfo;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public int size() {
            return attributeCount;
        }
    }

    @Override
    public String toString() {
        // In 1.6, toString goes into infinite loop.  Default implementation is
        // return getName() + '=' + getValue() but this class returns itself
        // for a value on which we do a toString... and around we go.  Short
        // circuit it here.
        return getName() + ": " + getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
}