/*
 *  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 org.apache.ivy.core.settings;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.NormalRelativeUrlResolver;
import org.apache.ivy.core.RelativeUrlResolver;
import org.apache.ivy.core.cache.CacheUtil;
import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
import org.apache.ivy.core.cache.DefaultResolutionCacheManager;
import org.apache.ivy.core.cache.RepositoryCacheManager;
import org.apache.ivy.core.cache.ResolutionCacheManager;
import org.apache.ivy.core.check.CheckEngineSettings;
import org.apache.ivy.core.deliver.DeliverEngineSettings;
import org.apache.ivy.core.install.InstallEngineSettings;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.module.id.ModuleRules;
import org.apache.ivy.core.module.status.StatusManager;
import org.apache.ivy.core.publish.PublishEngineSettings;
import org.apache.ivy.core.repository.RepositoryManagementEngineSettings;
import org.apache.ivy.core.resolve.ResolveEngineSettings;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.retrieve.RetrieveEngineSettings;
import org.apache.ivy.core.sort.SortEngineSettings;
import org.apache.ivy.osgi.core.OsgiLatestStrategy;
import org.apache.ivy.plugins.IvySettingsAware;
import org.apache.ivy.plugins.circular.CircularDependencyStrategy;
import org.apache.ivy.plugins.circular.ErrorCircularDependencyStrategy;
import org.apache.ivy.plugins.circular.IgnoreCircularDependencyStrategy;
import org.apache.ivy.plugins.circular.WarnCircularDependencyStrategy;
import org.apache.ivy.plugins.conflict.ConflictManager;
import org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager;
import org.apache.ivy.plugins.conflict.LatestConflictManager;
import org.apache.ivy.plugins.conflict.NoConflictManager;
import org.apache.ivy.plugins.conflict.StrictConflictManager;
import org.apache.ivy.plugins.latest.LatestLexicographicStrategy;
import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
import org.apache.ivy.plugins.latest.LatestStrategy;
import org.apache.ivy.plugins.latest.LatestTimeStrategy;
import org.apache.ivy.plugins.lock.ArtifactLockStrategy;
import org.apache.ivy.plugins.lock.LockStrategy;
import org.apache.ivy.plugins.lock.NoLockStrategy;
import org.apache.ivy.plugins.matcher.ExactOrRegexpPatternMatcher;
import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
import org.apache.ivy.plugins.matcher.MapMatcher;
import org.apache.ivy.plugins.matcher.PatternMatcher;
import org.apache.ivy.plugins.matcher.RegexpPatternMatcher;
import org.apache.ivy.plugins.namespace.Namespace;
import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
import org.apache.ivy.plugins.parser.ParserSettings;
import org.apache.ivy.plugins.report.LogReportOutputter;
import org.apache.ivy.plugins.report.ReportOutputter;
import org.apache.ivy.plugins.report.XmlReportOutputter;
import org.apache.ivy.plugins.resolver.ChainResolver;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.plugins.resolver.DualResolver;
import org.apache.ivy.plugins.resolver.ResolverSettings;
import org.apache.ivy.plugins.signer.SignatureGenerator;
import org.apache.ivy.plugins.trigger.Trigger;
import org.apache.ivy.plugins.version.ChainVersionMatcher;
import org.apache.ivy.plugins.version.ExactVersionMatcher;
import org.apache.ivy.plugins.version.LatestVersionMatcher;
import org.apache.ivy.plugins.version.SubVersionMatcher;
import org.apache.ivy.plugins.version.VersionMatcher;
import org.apache.ivy.plugins.version.VersionRangeMatcher;
import org.apache.ivy.util.Checks;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.filter.Filter;
import org.apache.ivy.util.url.URLHandlerRegistry;

public class IvySettings implements SortEngineSettings, PublishEngineSettings, ParserSettings,
        DeliverEngineSettings, CheckEngineSettings, InstallEngineSettings, 
        ResolverSettings, ResolveEngineSettings, RetrieveEngineSettings, 
        RepositoryManagementEngineSettings {
    private static final long INTERUPT_TIMEOUT = 2000;

    private Map typeDefs = new HashMap();

    private Map resolversMap = new HashMap();

    private DependencyResolver defaultResolver;

    private DependencyResolver dictatorResolver = null;

    private String defaultResolverName;

    private File defaultCache;

    private String defaultBranch = null;

    private boolean checkUpToDate = true;

    private ModuleRules moduleSettings = new ModuleRules(); 

    // Map (String conflictManagerName -> ConflictManager)
    private Map conflictsManager = new HashMap(); 

    // Map (String latestStrategyName -> LatestStrategy)
    private Map latestStrategies = new HashMap(); 
    
    // Map (String name -> LockStrategy)
    private Map lockStrategies = new HashMap(); 

    // Map (String namespaceName -> Namespace)
    private Map namespaces = new HashMap(); 

    // Map (String matcherName -> Matcher)
    private Map matchers = new HashMap(); 

    // Map (String outputterName -> ReportOutputter)
    private Map reportOutputters = new HashMap(); 

    // Map (String matcherName -> VersionMatcher)
    private Map versionMatchers = new HashMap(); 

    // Map (String name -> CircularDependencyStrategy)
    private Map circularDependencyStrategies = new HashMap(); 

    // Map (String name -> RepositoryCacheManager)
    private Map repositoryCacheManagers = new HashMap(); 
    
    // Map (String name -> SignatureGenerator)
    private Map signatureGenerators = new HashMap();

    // List (Trigger)
    private List triggers = new ArrayList(); 

    private IvyVariableContainer variableContainer = new IvyVariableContainerImpl();

    private boolean validate = true;

    private LatestStrategy defaultLatestStrategy = null;

    private LockStrategy defaultLockStrategy = null;

    private ConflictManager defaultConflictManager = null;

    private CircularDependencyStrategy circularDependencyStrategy = null;

    private RepositoryCacheManager defaultRepositoryCacheManager = null;

    private ResolutionCacheManager resolutionCacheManager = null;

    private List listingIgnore = new ArrayList();

    private boolean repositoriesConfigured;

    private boolean useRemoteConfig = false;

    private File defaultUserDir;
    
    private File baseDir = new File(".").getAbsoluteFile();

    private List classpathURLs = new ArrayList();

    private ClassLoader classloader;

    private Boolean debugConflictResolution;

    private boolean logNotConvertedExclusionRule;

    private VersionMatcher versionMatcher;

    private StatusManager statusManager;

    private Boolean debugLocking;

    private Boolean dumpMemoryUsage;

    private String defaultCacheIvyPattern;

    private String defaultCacheArtifactPattern;

    private boolean defaultUseOrigin;

    private String defaultResolveMode = ResolveOptions.RESOLVEMODE_DEFAULT;

    public IvySettings() {
        this(new IvyVariableContainerImpl());
    }

    public IvySettings(IvyVariableContainer variableContainer) {
        setVariableContainer(variableContainer);
        setVariable("ivy.default.settings.dir", getDefaultSettingsDir(), true);
        setVariable("ivy.basedir", getBaseDir().getAbsolutePath());
        setDeprecatedVariable("ivy.default.conf.dir", "ivy.default.settings.dir");

        String ivyTypeDefs = System.getProperty("ivy.typedef.files");
        if (ivyTypeDefs != null) {
            String[] files = ivyTypeDefs.split("\\,");
            for (int i = 0; i < files.length; i++) {
                try {
                    typeDefs(new FileInputStream(
                        Checks.checkAbsolute(files[i].trim(), "ivy.typedef.files")), true);
                } catch (FileNotFoundException e) {
                    Message.warn("typedefs file not found: " + files[i].trim());
                } catch (IOException e) {
                    Message.warn("problem with typedef file: " + files[i].trim() + ": "
                            + e.getMessage());
                }
            }
        } else {
            try {
                typeDefs(getSettingsURL("typedef.properties").openStream(), true);
            } catch (IOException e) {
                Message.warn("impossible to load default type defs");
            }
        }
        LatestLexicographicStrategy latestLexicographicStrategy = new LatestLexicographicStrategy();
        LatestRevisionStrategy latestRevisionStrategy = new LatestRevisionStrategy();
        LatestTimeStrategy latestTimeStrategy = new LatestTimeStrategy();
        OsgiLatestStrategy osgiLatestStrategy = new OsgiLatestStrategy();

        addLatestStrategy("latest-revision", latestRevisionStrategy);
        addLatestStrategy("latest-lexico", latestLexicographicStrategy);
        addLatestStrategy("latest-time", latestTimeStrategy);
        addLatestStrategy("latest-osgi", osgiLatestStrategy);

        addLockStrategy("no-lock", new NoLockStrategy());
        addLockStrategy("artifact-lock", new ArtifactLockStrategy(debugLocking()));

        addConflictManager("latest-revision", new LatestConflictManager("latest-revision",
            latestRevisionStrategy));
        addConflictManager("latest-compatible", 
            new LatestCompatibleConflictManager("latest-compatible", latestRevisionStrategy));
        addConflictManager("latest-time", new LatestConflictManager("latest-time",
                latestTimeStrategy));
        addConflictManager("all", new NoConflictManager());
        addConflictManager("strict", new StrictConflictManager());

        addMatcher(ExactPatternMatcher.INSTANCE);
        addMatcher(RegexpPatternMatcher.INSTANCE);
        addMatcher(ExactOrRegexpPatternMatcher.INSTANCE);

        try {
            // GlobPatternMatcher is optional. Only add it when available.
            Class globClazz = IvySettings.class.getClassLoader().loadClass(
                "org.apache.ivy.plugins.matcher.GlobPatternMatcher");
            Field instanceField = globClazz.getField("INSTANCE");
            addMatcher((PatternMatcher) instanceField.get(null));
        } catch (Exception e) {
            // ignore: the matcher isn't on the classpath
            Message.info("impossible to define glob matcher: " 
                + "org.apache.ivy.plugins.matcher.GlobPatternMatcher was not found.");
        }

        addReportOutputter(new LogReportOutputter());
        addReportOutputter(new XmlReportOutputter());

        configureDefaultCircularDependencyStrategies();

        listingIgnore.add(".cvsignore");
        listingIgnore.add("CVS");
        listingIgnore.add(".svn");
        listingIgnore.add("maven-metadata.xml");
        listingIgnore.add("maven-metadata.xml.md5");
        listingIgnore.add("maven-metadata.xml.sha1");

        addSystemProperties();
    }

    private void addSystemProperties() {
        try {
            addAllVariables((Map) System.getProperties().clone());
        } catch (AccessControlException ex) {
            Message.verbose(
                "access denied to getting all system properties: they won't be available as Ivy variables."
                + "\nset " + ex.getPermission() + " permission if you want to access them");
        }
    }

    /**
     * Call this method to ask ivy to configure some variables using either a remote or a local
     * properties file
     */
    public void configureRepositories(boolean remote) {
        if (!repositoriesConfigured) {
            Properties props = new Properties();
            boolean configured = false;
            if (useRemoteConfig && remote) {
                try {
                    URL url = new URL("http://ant.apache.org/ivy/repository.properties");
                    Message.verbose("configuring repositories with " + url);
                    props.load(URLHandlerRegistry.getDefault().openStream(url));
                    configured = true;
                } catch (Exception ex) {
                    Message.verbose("unable to use remote repository configuration: "
                            + ex.getMessage());
                    props = new Properties();
                }
            }
            if (!configured) {
                InputStream repositoryPropsStream = null;
                try {
                    repositoryPropsStream = getSettingsURL("repository.properties").openStream();
                    props.load(repositoryPropsStream);
                } catch (IOException e) {
                    Message.error("unable to use internal repository configuration: "
                            + e.getMessage());
                    if (repositoryPropsStream != null) {
                        try {
                            repositoryPropsStream.close();
                        } catch (Exception ex) {
                            //nothing to do
                        }
                    }
                }
            }
            addAllVariables(props, false);
            repositoriesConfigured = true;
        }
    }

    public void typeDefs(InputStream stream) throws IOException {
        typeDefs(stream, false);
    }

    public void typeDefs(InputStream stream, boolean silentFail) throws IOException {
        try {
            Properties p = new Properties();
            p.load(stream);
            typeDefs(p, silentFail);
        } finally {
            stream.close();
        }
    }

    public void typeDefs(Properties p) {
        typeDefs(p, false);
    }

    public void typeDefs(Properties p, boolean silentFail) {
        for (Iterator iter = p.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            typeDef(name, p.getProperty(name), silentFail);
        }
    }

    public void load(File settingsFile) throws ParseException, IOException {
        Message.info(":: loading settings :: file = " + settingsFile);
        long start = System.currentTimeMillis();
        setSettingsVariables(settingsFile);
        if (getVariable("ivy.default.ivy.user.dir") != null) {
            setDefaultIvyUserDir(Checks.checkAbsolute(
                getVariable("ivy.default.ivy.user.dir"), "ivy.default.ivy.user.dir"));
        } else {
            getDefaultIvyUserDir();
        }

        loadDefaultProperties();
        try {
            new XmlSettingsParser(this).parse(settingsFile.toURI().toURL());
        } catch (MalformedURLException e) {
            IllegalArgumentException iae = new IllegalArgumentException(
                    "given file cannot be transformed to url: " + settingsFile);
            iae.initCause(e);
            throw iae;
        }
        setVariable("ivy.default.ivy.user.dir", getDefaultIvyUserDir().getAbsolutePath(), false);
        Message.verbose("settings loaded (" + (System.currentTimeMillis() - start) + "ms)");
        dumpSettings();
    }

    public void load(URL settingsURL) throws ParseException, IOException {
        Message.info(":: loading settings :: url = " + settingsURL);
        long start = System.currentTimeMillis();
        setSettingsVariables(settingsURL);
        if (getVariable("ivy.default.ivy.user.dir") != null) {
            setDefaultIvyUserDir(Checks.checkAbsolute(
                getVariable("ivy.default.ivy.user.dir"), "ivy.default.ivy.user.dir"));
        } else {
            getDefaultIvyUserDir();
        }

        loadDefaultProperties();
        new XmlSettingsParser(this).parse(settingsURL);
        setVariable("ivy.default.ivy.user.dir", getDefaultIvyUserDir().getAbsolutePath(), false);
        Message.verbose("settings loaded (" + (System.currentTimeMillis() - start) + "ms)");
        dumpSettings();
    }
    
    /**
     * Default initialization of settings, useful when you don't want to load your settings from a
     * settings file or URL, but prefer to set them manually. By calling this method you will still
     * have the basic initialization done when loading settings.
     * 
     * @throws IOException
     */
    public void defaultInit() throws IOException {
        if (getVariable("ivy.default.ivy.user.dir") != null) {
            setDefaultIvyUserDir(Checks.checkAbsolute(
                    getVariable("ivy.default.ivy.user.dir"), "ivy.default.ivy.user.dir"));
        } else {
            getDefaultIvyUserDir();
        }
        getDefaultCache();

        loadDefaultProperties();
        setVariable("ivy.default.ivy.user.dir", getDefaultIvyUserDir().getAbsolutePath(), false);
        dumpSettings();
    }

    public void loadDefault() throws ParseException, IOException {
        load(getDefaultSettingsURL());
    }

    public void loadDefault14() throws ParseException, IOException {
        load(getDefault14SettingsURL());
    }

    private void loadDefaultProperties() throws IOException {
        loadProperties(getDefaultPropertiesURL(), false);
    }

    public static URL getDefaultPropertiesURL() {
        return getSettingsURL("ivy.properties");
    }

    public static URL getDefaultSettingsURL() {
        return getSettingsURL("ivysettings.xml");
    }

    public static URL getDefault14SettingsURL() {
        return getSettingsURL("ivysettings-1.4.xml");
    }

    private String getDefaultSettingsDir() {
        String ivysettingsLocation = getDefaultSettingsURL().toExternalForm();
        return ivysettingsLocation.substring(0, ivysettingsLocation.length()
                - "ivysettings.xml".length() - 1);
    }

    private static URL getSettingsURL(String file) {
        return XmlSettingsParser.class.getResource(file);
    }

    public void setSettingsVariables(File settingsFile) {
        try {
            setVariable("ivy.settings.dir", new File(settingsFile.getAbsolutePath()).getParent());
            setDeprecatedVariable("ivy.conf.dir", "ivy.settings.dir");
            setVariable("ivy.settings.file", settingsFile.getAbsolutePath());
            setDeprecatedVariable("ivy.conf.file", "ivy.settings.file");
            setVariable("ivy.settings.url", settingsFile.toURI().toURL().toExternalForm());
            setDeprecatedVariable("ivy.conf.url", "ivy.settings.url");
        } catch (MalformedURLException e) {
            IllegalArgumentException iae = new IllegalArgumentException(
                    "given file cannot be transformed to url: " + settingsFile);
            iae.initCause(e);
            throw iae;
        }
    }

    /**
     * Sets a deprecated variable with the value of the new variable
     * 
     * @param deprecatedKey
     *            the deprecated variable name
     * @param newKey
     *            the new variable name
     */
    private void setDeprecatedVariable(String deprecatedKey, String newKey) {
        setVariable(deprecatedKey, getVariable(newKey));
    }

    public void setSettingsVariables(URL settingsURL) {
        String settingsURLStr = settingsURL.toExternalForm();
        setVariable("ivy.settings.url", settingsURLStr);
        setDeprecatedVariable("ivy.conf.url", "ivy.settings.url");
        int slashIndex = settingsURLStr.lastIndexOf('/');
        if (slashIndex != -1) {
            setVariable("ivy.settings.dir", settingsURLStr.substring(0, slashIndex));
            setDeprecatedVariable("ivy.conf.dir", "ivy.settings.dir");
        } else {
            Message.warn("settings url does not contain any slash (/): " 
                + "ivy.settings.dir variable not set");
        }
    }

    private void dumpSettings() {
        Message.verbose("\tdefault cache: " + getDefaultCache());
        Message.verbose("\tdefault resolver: " + getDefaultResolver());
        Message.debug("\tdefault latest strategy: " + getDefaultLatestStrategy());
        Message.debug("\tdefault conflict manager: " + getDefaultConflictManager());
        Message.debug("\tcircular dependency strategy: " + getCircularDependencyStrategy());
        Message.debug("\tvalidate: " + doValidate());
        Message.debug("\tcheck up2date: " + isCheckUpToDate());

        if (!classpathURLs.isEmpty()) {
            Message.verbose("\t-- " + classpathURLs.size() + " custom classpath urls:");
            for (Iterator iter = classpathURLs.iterator(); iter.hasNext();) {
                Message.debug("\t\t" + iter.next());
            }
        }
        Message.verbose("\t-- " + resolversMap.size() + " resolvers:");
        for (Iterator iter = resolversMap.values().iterator(); iter.hasNext();) {
            DependencyResolver resolver = (DependencyResolver) iter.next();
            resolver.dumpSettings();
        }
        Message.debug("\tmodule settings:");
        moduleSettings.dump("\t\t");
    }

    public void loadProperties(URL url) throws IOException {
        loadProperties(url, true);
    }

    public void loadProperties(URL url, boolean overwrite) throws IOException {
        loadProperties(url.openStream(), overwrite);
    }

    public void loadProperties(File file) throws IOException {
        loadProperties(file, true);
    }

    public void loadProperties(File file, boolean overwrite) throws IOException {
        loadProperties(new FileInputStream(file), overwrite);
    }

    private void loadProperties(InputStream stream, boolean overwrite) throws IOException {
        try {
            Properties properties = new Properties();
            properties.load(stream);
            addAllVariables(properties, overwrite);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    //nothing
                }
            }
        }
    }

    public void setVariable(String varName, String value) {
        setVariable(varName, value, true);
    }

    public void setVariable(String varName, String value, boolean overwrite) {
        variableContainer.setVariable(varName, value, overwrite);
    }

    public void addAllVariables(Map variables) {
        addAllVariables(variables, true);
    }

    public void addAllVariables(Map variables, boolean overwrite) {
        for (Iterator iter = variables.keySet().iterator(); iter.hasNext();) {
            String key = (String) iter.next();
            String val = (String) variables.get(key);
            setVariable(key, val, overwrite);
        }
    }

    /**
     * Substitute variables in the given string by their value found in the current set of variables
     * 
     * @param str
     *            the string in which substitution should be made
     * @return the string where all current ivy variables have been substituted by their value
     *         If the input str doesn't use any variable, the same object is returned
     */
    public String substitute(String str) {
        return IvyPatternHelper.substituteVariables(str, variableContainer);
    }

    /**
     * Substitute variables in the given map values by their value found in the current set of
     * variables
     * 
     * @param strings
     *            the map of strings in which substitution should be made
     * @return a new map of strings in which all current ivy variables in values have been
     *         substituted by their value
     */
    public Map/*<String, String>*/ substitute(Map/*<String, String>*/ strings) {
        Map substituted = new LinkedHashMap();
        for (Iterator it = strings.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            substituted.put(entry.getKey(), substitute((String) entry.getValue()));
        }
        return substituted;
    }

    /**
     * Returns the variables loaded in configuration file. Those variables may better be seen as ant
     * properties
     * 
     * @return
     */
    public IvyVariableContainer getVariables() {
        return variableContainer;
    }

    public Class typeDef(String name, String className) {
        return typeDef(name, className, false);
    }

    public Class typeDef(String name, String className, boolean silentFail) {
        Class clazz = classForName(className, silentFail);
        if (clazz != null) {
            typeDefs.put(name, clazz);
        }
        return clazz;
    }

    private Class classForName(String className, boolean silentFail) {
        try {
            return getClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            if (silentFail) {
                Message.info("impossible to define new type: class not found: " + className
                        + " in " + classpathURLs + " nor Ivy classloader");
                return null;
            } else {
                throw new RuntimeException("impossible to define new type: class not found: "
                        + className + " in " + classpathURLs + " nor Ivy classloader");
            }
        }
    }

    private ClassLoader getClassLoader() {
        if (classloader == null) {
            if (classpathURLs.isEmpty()) {
                classloader = Ivy.class.getClassLoader();
            } else {
                classloader = new URLClassLoader((URL[]) classpathURLs
                        .toArray(new URL[classpathURLs.size()]), Ivy.class.getClassLoader());
            }
        }
        return classloader;
    }

    public void addClasspathURL(URL url) {
        classpathURLs.add(url);
        classloader = null;
    }

    public Map getTypeDefs() {
        return typeDefs;
    }

    public Class getTypeDef(String name) {
        return (Class) typeDefs.get(name);
    }

    // methods which match ivy conf method signature specs
    public void addConfigured(DependencyResolver resolver) {
        addResolver(resolver);
    }

    public void addConfigured(ModuleDescriptorParser parser) {
        ModuleDescriptorParserRegistry.getInstance().addParser(parser);
    }
    
    public void addConfigured(SignatureGenerator generator) {
        addSignatureGenerator(generator);
    }
    
    public void addSignatureGenerator(SignatureGenerator generator) {
        init(generator);
        signatureGenerators.put(generator.getName(), generator);
    }
    
    public SignatureGenerator getSignatureGenerator(String name) {
        return (SignatureGenerator) signatureGenerators.get(name);
    }

    public void addResolver(DependencyResolver resolver) {
        if (resolver == null) {
            throw new NullPointerException("null resolver");
        }
        init(resolver);
        resolversMap.put(resolver.getName(), resolver);
        if (resolver instanceof ChainResolver) {
            List subresolvers = ((ChainResolver) resolver).getResolvers();
            for (Iterator iter = subresolvers.iterator(); iter.hasNext();) {
                DependencyResolver dr = (DependencyResolver) iter.next();
                addResolver(dr);
            }
        } else if (resolver instanceof DualResolver) {
            DependencyResolver ivyResolver = ((DualResolver) resolver).getIvyResolver();
            if (ivyResolver != null) {
                addResolver(ivyResolver);
            }
            DependencyResolver artifactResolver = ((DualResolver) resolver).getArtifactResolver();
            if (artifactResolver != null) {
                addResolver(artifactResolver);
            }
        }
    }

    public void setDefaultCache(File cacheDirectory) {
        setVariable("ivy.cache.dir", cacheDirectory.getAbsolutePath(), false);
        defaultCache = cacheDirectory;
        if (defaultRepositoryCacheManager != null) {
            if ("default-cache".equals(defaultRepositoryCacheManager.getName())
                    && defaultRepositoryCacheManager instanceof DefaultRepositoryCacheManager) {
                ((DefaultRepositoryCacheManager) defaultRepositoryCacheManager)
                    .setBasedir(defaultCache);
            }
        }
    }

    public void setDefaultResolver(String resolverName) {
        checkResolverName(resolverName);
        if (resolverName != null && !resolverName.equals(defaultResolverName)) {
            defaultResolver = null;
        }
        defaultResolverName = resolverName;
    }

    private void checkResolverName(String resolverName) {
        if (resolverName != null && !resolversMap.containsKey(resolverName)) {
            throw new IllegalArgumentException("no resolver found called " + resolverName
                    + ": check your settings");
        }
    }

    /**
     * regular expressions as explained in Pattern class may be used in attributes
     */
    public void addModuleConfiguration(Map attributes, PatternMatcher matcher, 
            String resolverName, String branch, String conflictManager, String resolveMode) {
        checkResolverName(resolverName);
        moduleSettings.defineRule(
            new MapMatcher(attributes, matcher), 
            new ModuleSettings(resolverName, branch, conflictManager, resolveMode));
    }
    
    /**
     * Return the canonical form of a filename.
     * <p>
     * If the specified file name is relative it is resolved
     * with respect to the settings's base directory.
     *
     * @param fileName The name of the file to resolve.
     *                 Must not be <code>null</code>.
     *
     * @return the resolved File.
     *
     */
    public File resolveFile(String fileName) {
        return FileUtil.resolveFile(baseDir, fileName);
    }
    
    public void setBaseDir(File baseDir) {
        this.baseDir = baseDir.getAbsoluteFile();
        setVariable("ivy.basedir", this.baseDir.getAbsolutePath());
        setVariable("basedir", this.baseDir.getAbsolutePath(), false);
    }
    
    public File getBaseDir() {
        return baseDir;
    }

    public File getDefaultIvyUserDir() {
        if (defaultUserDir == null) {
            if (getVariable("ivy.home") != null) {
                setDefaultIvyUserDir(Checks.checkAbsolute(getVariable("ivy.home"), "ivy.home"));
                Message.verbose("using ivy.default.ivy.user.dir variable for default ivy user dir: "
                                + defaultUserDir);
            } else {
                setDefaultIvyUserDir(new File(System.getProperty("user.home"), ".ivy2"));
                Message.verbose("no default ivy user dir defined: set to " + defaultUserDir);
            }
        }
        return defaultUserDir;
    }

    public void setDefaultIvyUserDir(File defaultUserDir) {
        this.defaultUserDir = defaultUserDir;
        setVariable("ivy.default.ivy.user.dir", this.defaultUserDir.getAbsolutePath());
        setVariable("ivy.home", this.defaultUserDir.getAbsolutePath());
    }

    public File getDefaultCache() {
        if (defaultCache == null) {
            String cache = getVariable("ivy.cache.dir");
            if (cache != null) {
                defaultCache = Checks.checkAbsolute(cache, "ivy.cache.dir");
            } else {
                setDefaultCache(new File(getDefaultIvyUserDir(), "cache"));
                Message.verbose("no default cache defined: set to " + defaultCache);
            }
        }
        return defaultCache;
    }
    
    public void setDefaultRepositoryCacheBasedir(String repositoryCacheRoot) {
        setVariable("ivy.cache.repository", repositoryCacheRoot, true);
        if (defaultRepositoryCacheManager != null
                && "default-cache".equals(defaultRepositoryCacheManager.getName())
                && defaultRepositoryCacheManager instanceof DefaultRepositoryCacheManager) {
            ((DefaultRepositoryCacheManager) defaultRepositoryCacheManager)
                .setBasedir(getDefaultRepositoryCacheBasedir());
        }
    }
    
    public void setDefaultResolutionCacheBasedir(String resolutionCacheRoot) {
        setVariable("ivy.cache.resolution", resolutionCacheRoot, true);
        if (resolutionCacheManager != null
                && resolutionCacheManager instanceof DefaultResolutionCacheManager) {
            ((DefaultResolutionCacheManager) resolutionCacheManager)
                .setBasedir(getDefaultResolutionCacheBasedir());
        }
    }
    
    public File getDefaultRepositoryCacheBasedir() {
        String repositoryCacheRoot = getVariable("ivy.cache.repository");
        if (repositoryCacheRoot != null) {
            return Checks.checkAbsolute(repositoryCacheRoot, "ivy.cache.repository");
        } else {
            return getDefaultCache();
        }
    }

    public File getDefaultResolutionCacheBasedir() {
        String resolutionCacheRoot = getVariable("ivy.cache.resolution");
        if (resolutionCacheRoot != null) {
            return Checks.checkAbsolute(resolutionCacheRoot, "ivy.cache.resolution");
        } else {
            return getDefaultCache();
        }
    }

    public void setDictatorResolver(DependencyResolver resolver) {
        dictatorResolver = resolver;
    }

    public DependencyResolver getResolver(ModuleRevisionId mrid) {
        if (dictatorResolver != null) {
            return dictatorResolver;
        }
        String resolverName = getResolverName(mrid);
        return getResolver(resolverName);
    }

    public DependencyResolver getResolver(String resolverName) {
        if (dictatorResolver != null) {
            return dictatorResolver;
        }
        DependencyResolver resolver = (DependencyResolver) resolversMap.get(resolverName);
        if (resolver == null) {
            Message.error("unknown resolver " + resolverName);
        }
        return resolver;
    }

    public DependencyResolver getDefaultResolver() {
        if (dictatorResolver != null) {
            return dictatorResolver;
        }
        if (defaultResolver == null) {
            defaultResolver = (DependencyResolver) resolversMap.get(defaultResolverName);
        }
        return defaultResolver;
    }

    public String getResolverName(ModuleRevisionId mrid) {
        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(mrid, new Filter() {
            public boolean accept(Object o) {
                return ((ModuleSettings) o).getResolverName() != null;
            }
        });
        return ms == null ? defaultResolverName : ms.getResolverName();
    }

    public String getDefaultBranch(ModuleId moduleId) {
        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(moduleId, new Filter() {
            public boolean accept(Object o) {
                return ((ModuleSettings) o).getBranch() != null;
            }
        });
        return ms == null ? getDefaultBranch() : ms.getBranch();
    }

    public String getDefaultBranch() {
        return defaultBranch;
    }

    public void setDefaultBranch(String defaultBranch) {
        this.defaultBranch = defaultBranch;
    }

    public ConflictManager getConflictManager(ModuleId moduleId) {
        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(moduleId, new Filter() {
            public boolean accept(Object o) {
                return ((ModuleSettings) o).getConflictManager() != null;
            }
        });
        if (ms == null) {
            return getDefaultConflictManager();
        } else {
            ConflictManager cm = getConflictManager(ms.getConflictManager());
            if (cm == null) {
                throw new IllegalStateException(
                        "ivy badly configured: unknown conflict manager "
                                + ms.getConflictManager());
            }
            return cm;
        }
    }

    public String getResolveMode(ModuleId moduleId) {
        ModuleSettings ms = (ModuleSettings) moduleSettings.getRule(moduleId, new Filter() {
            public boolean accept(Object o) {
                return ((ModuleSettings) o).getResolveMode() != null;
            }
        });
        return ms == null ? getDefaultResolveMode() : ms.getResolveMode();
    }

    public String getDefaultResolveMode() {
        return defaultResolveMode;
    }
    
    public void setDefaultResolveMode(String defaultResolveMode) {
        this.defaultResolveMode = defaultResolveMode;
    }

    public void addConfigured(ConflictManager cm) {
        addConflictManager(cm.getName(), cm);
    }

    public ConflictManager getConflictManager(String name) {
        if ("default".equals(name)) {
            return getDefaultConflictManager();
        }
        return (ConflictManager) conflictsManager.get(name);
    }

    public void addConflictManager(String name, ConflictManager cm) {
        init(cm);
        conflictsManager.put(name, cm);
    }

    public void addConfigured(LatestStrategy latest) {
        addLatestStrategy(latest.getName(), latest);
    }

    public LatestStrategy getLatestStrategy(String name) {
        if ("default".equals(name)) {
            return getDefaultLatestStrategy();
        }
        return (LatestStrategy) latestStrategies.get(name);
    }

    public void addLatestStrategy(String name, LatestStrategy latest) {
        init(latest);
        latestStrategies.put(name, latest);
    }

    public void addConfigured(LockStrategy lockStrategy) {
        addLockStrategy(lockStrategy.getName(), lockStrategy);
    }

    public LockStrategy getLockStrategy(String name) {
        if ("default".equals(name)) {
            return getDefaultLockStrategy();
        }
        return (LockStrategy) lockStrategies.get(name);
    }

    public void addLockStrategy(String name, LockStrategy lockStrategy) {
        init(lockStrategy);
        lockStrategies.put(name, lockStrategy);
    }

    public void addConfigured(Namespace ns) {
        addNamespace(ns);
    }

    public Namespace getNamespace(String name) {
        if ("system".equals(name)) {
            return getSystemNamespace();
        }
        return (Namespace) namespaces.get(name);
    }

    public Namespace getSystemNamespace() {
        return Namespace.SYSTEM_NAMESPACE;
    }

    public void addNamespace(Namespace ns) {
        init(ns);
        namespaces.put(ns.getName(), ns);
    }

    public void addConfigured(PatternMatcher m) {
        addMatcher(m);
    }

    public PatternMatcher getMatcher(String name) {
        return (PatternMatcher) matchers.get(name);
    }

    public void addMatcher(PatternMatcher m) {
        init(m);
        matchers.put(m.getName(), m);
    }

    public void addConfigured(RepositoryCacheManager c) {
        addRepositoryCacheManager(c);
    }

    public RepositoryCacheManager getRepositoryCacheManager(String name) {
        return (RepositoryCacheManager) repositoryCacheManagers.get(name);
    }

    public void addRepositoryCacheManager(RepositoryCacheManager c) {
        init(c);
        repositoryCacheManagers.put(c.getName(), c);
    }

    public RepositoryCacheManager[] getRepositoryCacheManagers() {
        return (RepositoryCacheManager[]) repositoryCacheManagers.values().toArray(
            new RepositoryCacheManager[repositoryCacheManagers.size()]);
    }

    public void addConfigured(ReportOutputter outputter) {
        addReportOutputter(outputter);
    }

    public ReportOutputter getReportOutputter(String name) {
        return (ReportOutputter) reportOutputters.get(name);
    }

    public void addReportOutputter(ReportOutputter outputter) {
        init(outputter);
        reportOutputters.put(outputter.getName(), outputter);
    }

    public ReportOutputter[] getReportOutputters() {
        return (ReportOutputter[]) reportOutputters.values().toArray(
            new ReportOutputter[reportOutputters.size()]);
    }

    public void addConfigured(VersionMatcher vmatcher) {
        addVersionMatcher(vmatcher);
    }

    public VersionMatcher getVersionMatcher(String name) {
        return (VersionMatcher) versionMatchers.get(name);
    }

    public void addVersionMatcher(VersionMatcher vmatcher) {
        init(vmatcher);
        versionMatchers.put(vmatcher.getName(), vmatcher);

        if (versionMatcher == null) {
            versionMatcher = new ChainVersionMatcher();
            addVersionMatcher(new ExactVersionMatcher());
        }
        if (versionMatcher instanceof ChainVersionMatcher) {
            ChainVersionMatcher chain = (ChainVersionMatcher) versionMatcher;
            chain.add(vmatcher);
        }
    }

    public VersionMatcher[] getVersionMatchers() {
        return (VersionMatcher[]) versionMatchers.values().toArray(
            new VersionMatcher[versionMatchers.size()]);
    }

    public VersionMatcher getVersionMatcher() {
        if (versionMatcher == null) {
            configureDefaultVersionMatcher();
        }
        return versionMatcher;
    }

    public void configureDefaultVersionMatcher() {
        addVersionMatcher(new LatestVersionMatcher());
        addVersionMatcher(new SubVersionMatcher());
        addVersionMatcher(new VersionRangeMatcher());
    }

    public CircularDependencyStrategy getCircularDependencyStrategy() {
        if (circularDependencyStrategy == null) {
            circularDependencyStrategy = getCircularDependencyStrategy("default");
        }
        return circularDependencyStrategy;
    }

    public CircularDependencyStrategy getCircularDependencyStrategy(String name) {
        if ("default".equals(name)) {
            name = "warn";
        }
        return (CircularDependencyStrategy) circularDependencyStrategies.get(name);
    }

    public void setCircularDependencyStrategy(CircularDependencyStrategy strategy) {
        circularDependencyStrategy = strategy;
    }

    public void addConfigured(CircularDependencyStrategy strategy) {
        addCircularDependencyStrategy(strategy);
    }

    private void addCircularDependencyStrategy(CircularDependencyStrategy strategy) {
        circularDependencyStrategies.put(strategy.getName(), strategy);
    }

    private void configureDefaultCircularDependencyStrategies() {
        addCircularDependencyStrategy(WarnCircularDependencyStrategy.getInstance());
        addCircularDependencyStrategy(ErrorCircularDependencyStrategy.getInstance());
        addCircularDependencyStrategy(IgnoreCircularDependencyStrategy.getInstance());
    }

    public StatusManager getStatusManager() {
        if (statusManager == null) {
            statusManager = StatusManager.newDefaultInstance();
        }
        return statusManager;
    }

    public void setStatusManager(StatusManager statusManager) {
        this.statusManager = statusManager;
    }

    /**
     * Returns the file names of the files that should be ignored when 
     * creating a file listing.
     */
    public String[] getIgnorableFilenames() {
        return (String[]) listingIgnore.toArray(new String[listingIgnore.size()]);
    }
    
    /**
     * Filters the names list by removing all names that should be ignored as defined by the listing
     * ignore list
     * 
     * @param names
     */
    public void filterIgnore(Collection names) {
        names.removeAll(listingIgnore);
    }
    
    public boolean isCheckUpToDate() {
        return checkUpToDate;
    }

    public void setCheckUpToDate(boolean checkUpToDate) {
        this.checkUpToDate = checkUpToDate;
    }
    
    public boolean doValidate() {
        return validate;
    }

    public void setValidate(boolean validate) {
        this.validate = validate;
    }

    public String getVariable(String name) {
        return variableContainer.getVariable(name);
    }

    public ConflictManager getDefaultConflictManager() {
        if (defaultConflictManager == null) {
            defaultConflictManager = new LatestConflictManager(getDefaultLatestStrategy());
            ((LatestConflictManager) defaultConflictManager).setSettings(this);
        }
        return defaultConflictManager;
    }

    public void setDefaultConflictManager(ConflictManager defaultConflictManager) {
        this.defaultConflictManager = defaultConflictManager;
    }

    public LatestStrategy getDefaultLatestStrategy() {
        if (defaultLatestStrategy == null) {
            defaultLatestStrategy = new LatestRevisionStrategy();
        }
        return defaultLatestStrategy;
    }

    public void setDefaultLatestStrategy(LatestStrategy defaultLatestStrategy) {
        this.defaultLatestStrategy = defaultLatestStrategy;
    }

    public LockStrategy getDefaultLockStrategy() {
        if (defaultLockStrategy == null) {
            defaultLockStrategy = new NoLockStrategy();
        }
        return defaultLockStrategy;
    }

    public void setDefaultLockStrategy(LockStrategy defaultLockStrategy) {
        this.defaultLockStrategy = defaultLockStrategy;
    }

    public RepositoryCacheManager getDefaultRepositoryCacheManager() {
        if (defaultRepositoryCacheManager == null) {
            defaultRepositoryCacheManager 
                = new DefaultRepositoryCacheManager(
                    "default-cache", this, getDefaultRepositoryCacheBasedir());
            addRepositoryCacheManager(defaultRepositoryCacheManager);
        }
        return defaultRepositoryCacheManager;
    }

    public void setDefaultRepositoryCacheManager(RepositoryCacheManager cache) {
        this.defaultRepositoryCacheManager = cache;
    }

    public ResolutionCacheManager getResolutionCacheManager() {
        if (resolutionCacheManager == null) {
            resolutionCacheManager 
                = new DefaultResolutionCacheManager(getDefaultResolutionCacheBasedir());
        }
        return resolutionCacheManager;
    }
    
    public void setResolutionCacheManager(ResolutionCacheManager resolutionCacheManager) {
        this.resolutionCacheManager = resolutionCacheManager;
    }

    public void addTrigger(Trigger trigger) {
        init(trigger);
        triggers.add(trigger);
    }

    public List getTriggers() {
        return triggers;
    }

    public void addConfigured(Trigger trigger) {
        addTrigger(trigger);
    }

    public boolean isUseRemoteConfig() {
        return useRemoteConfig;
    }

    public void setUseRemoteConfig(boolean useRemoteConfig) {
        this.useRemoteConfig = useRemoteConfig;
    }

    public boolean logModulesInUse() {
        String var = getVariable("ivy.log.modules.in.use");
        return var == null || Boolean.valueOf(var).booleanValue();
    }

    public boolean logModuleWhenFound() {
        String var = getVariable("ivy.log.module.when.found");
        return var == null || Boolean.valueOf(var).booleanValue();
    }

    public boolean logResolvedRevision() {
        String var = getVariable("ivy.log.resolved.revision");
        return var == null || Boolean.valueOf(var).booleanValue();
    }

    public boolean debugConflictResolution() {
        if (debugConflictResolution == null) {
            String var = getVariable("ivy.log.conflict.resolution");
            debugConflictResolution = Boolean.valueOf(var != null
                    && Boolean.valueOf(var).booleanValue());
        }
        return debugConflictResolution.booleanValue();
    }

    public final boolean debugLocking() {
        if (debugLocking == null) {
            String var = getVariable("ivy.log.locking");
            debugLocking = Boolean.valueOf(var != null
                    && Boolean.valueOf(var).booleanValue());
        }
        return debugLocking.booleanValue();
    }

    public boolean dumpMemoryUsage() {
        if (dumpMemoryUsage == null) {
            String var = getVariable("ivy.log.memory");
            dumpMemoryUsage = Boolean.valueOf(var != null
                    && Boolean.valueOf(var).booleanValue());
        }
        return dumpMemoryUsage.booleanValue();
    }


    public boolean logNotConvertedExclusionRule() {
        return logNotConvertedExclusionRule;
    }

    public void setLogNotConvertedExclusionRule(boolean logNotConvertedExclusionRule) {
        this.logNotConvertedExclusionRule = logNotConvertedExclusionRule;
    }

    private void init(Object obj) {
        if (obj instanceof IvySettingsAware) {
            ((IvySettingsAware) obj).setSettings(this);
        } else if (obj instanceof DependencyResolver) {
            ((DependencyResolver) obj).setSettings(this);
        }
    }

    private static class ModuleSettings {
        private String resolverName;

        private String branch;

        private String conflictManager;

        private String resolveMode;

        public ModuleSettings(
                String resolver, String branchName, String conflictMgr, String resolveMode) {
            this.resolverName = resolver;
            this.branch = branchName;
            this.conflictManager = conflictMgr;
            this.resolveMode = resolveMode;
        }

        public String toString() {
            return (resolverName != null ? "resolver: " + resolverName : "")
                + (branch != null ? "branch: " + branch : "")
                + (conflictManager != null ? "conflictManager: " + conflictManager : "")
                + (resolveMode != null ? "resolveMode: " + resolveMode : "");
        }

        public String getBranch() {
            return branch;
        }

        public String getResolverName() {
            return resolverName;
        }

        public String getConflictManager() {
            return conflictManager;
        }

        public String getResolveMode() {
            return resolveMode;
        }
    }

    public long getInterruptTimeout() {
        return INTERUPT_TIMEOUT;
    }

    public Collection getResolvers() {
        return resolversMap.values();
    }

    public Collection getResolverNames() {
        return resolversMap.keySet();
    }

    public Collection getMatcherNames() {
        return matchers.keySet();
    }

    public IvyVariableContainer getVariableContainer() {
        return variableContainer;
    }

    /**
     * Use a different variable container.
     * 
     * @param variables
     */
    public void setVariableContainer(IvyVariableContainer variables) {
        variableContainer = variables;
    }

    
    public RelativeUrlResolver getRelativeUrlResolver() {
        return new NormalRelativeUrlResolver();
    }

    public void setDefaultCacheIvyPattern(String defaultCacheIvyPattern) {
        CacheUtil.checkCachePattern(defaultCacheIvyPattern);
        this.defaultCacheIvyPattern = defaultCacheIvyPattern;
    }
    
    public String getDefaultCacheIvyPattern() {
        return defaultCacheIvyPattern;
    }

    public void setDefaultCacheArtifactPattern(String defaultCacheArtifactPattern) {
        CacheUtil.checkCachePattern(defaultCacheArtifactPattern);
        this.defaultCacheArtifactPattern = defaultCacheArtifactPattern;
    }
    
    public String getDefaultCacheArtifactPattern() {
        return defaultCacheArtifactPattern;
    }

    public void setDefaultUseOrigin(boolean useOrigin) {
        defaultUseOrigin = useOrigin;
    }
    
    public boolean isDefaultUseOrigin() {
        return defaultUseOrigin;
    }
    
    public void useDeprecatedUseOrigin() {
        Message.deprecated("useOrigin option is deprecated when calling resolve, use useOrigin"
            + " setting on the cache implementation instead");
        setDefaultUseOrigin(true);
        
    }
    
    /**
     * Validates the settings, throwing an {@link IllegalStateException} if the current state is not
     * valid.
     * 
     * @throws IllegalStateException
     *             if the settings is not valid.
     */
    public void validate() {
        validateAll(resolversMap.values());
        validateAll(conflictsManager.values());
        validateAll(latestStrategies.values());
        validateAll(lockStrategies.values());
        validateAll(repositoryCacheManagers.values());
        validateAll(reportOutputters.values());
        validateAll(circularDependencyStrategies.values());
        validateAll(versionMatchers.values());
        validateAll(namespaces.values());
    }

    /**
     * Validates all {@link Validatable} objects in the collection.
     * 
     * @param objects
     *            the collection of objects to validate.
     * @throws IllegalStateException
     *             if any of the objects is not valid.
     */
    private void validateAll(Collection values) {
        for (Iterator iterator = values.iterator(); iterator.hasNext();) {
            Object object = (Object) iterator.next();
            if (object instanceof Validatable) {
                ((Validatable) object).validate();
            }
        }
    }

    public Namespace getContextNamespace() {
        return Namespace.SYSTEM_NAMESPACE;
    }

}
