package org.apache.velocity.tools;

/*
 * 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.
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;

/**
 * {@link Context} implementation that keeps a list of {@link Toolbox}es
 * and returns them as requested, using its internal context Map as the
 * dynamic properties passed to the requested tools when they are first
 * created.
 *
 * @author Nathan Bubna
 * @version $Id: ToolContext.java 511959 2007-02-26 19:24:39Z nbubna $
 */
public class ToolContext implements Context
{
    public static final String PATH_KEY = "requestPath";
    public static final String CONTEXT_KEY = "velocityContext";
    public static final String ENGINE_KEY = "velocityEngine";
    public static final String LOCALE_KEY = "locale";
    public static final String LOG_KEY = "log";
    public static final String CATCH_EXCEPTIONS_KEY = "catchExceptions";

    private List<Toolbox> toolboxes;
    // this is meant solely for tool setup,
    // values in here are not part of the Context
    private Map<String,Object> toolProps;
    // this is only for values added during use of this context
    private Map<String,Object> localContext;

    public ToolContext(VelocityEngine engine)
    {
        this(null, null);

        // add the engine and log as common tool properties
        putToolProperty(ENGINE_KEY, engine);
        putToolProperty(LOG_KEY, engine.getLog());

        // tell interested tools not to catch exceptions whenever there's a
        // method exception event handler configured for the engine
        Object ehme =
            engine.getProperty(VelocityEngine.EVENTHANDLER_METHODEXCEPTION);
        if (ehme != null)
        {
            putToolProperty(CATCH_EXCEPTIONS_KEY, Boolean.FALSE);
        }
    }

    public ToolContext(Map<String,Object> toolProps)
    {
        this(toolProps, null);
    }

    public ToolContext(Toolbox toolbox)
    {
        this(null, toolbox);
    }

    public ToolContext(Map<String,Object> toolProps, Toolbox toolbox)
    {
        if (toolProps != null)
        {
            this.toolProps = toolProps;
        }
        else
        {
            this.toolProps = new HashMap<String,Object>(8);
        }
        // add this as a common tool property
        putToolProperty(CONTEXT_KEY, this);

        toolboxes = new ArrayList<Toolbox>();
        if (toolbox != null)
        {
            toolboxes.add(toolbox);
        }

        this.localContext = new HashMap<String,Object>();
    }

    public void addToolbox(Toolbox toolbox)
    {
        toolboxes.add(toolbox);
    }

    /**
     * Returns a {@link Map} of all tools available to this
     * context. NOTE: this is not a cheap operation as it will
     * request and initialize an instance of every available tool.
     */
    public Map<String,Object> getToolbox()
    {
        Map<String,Object> aggregate = new HashMap<String,Object>();
        Map<String,Object> toolProps = getToolProperties();
        for (Toolbox toolbox : getToolboxes())
        {
            aggregate.putAll(toolbox.getAll(toolProps));
        }
        return aggregate;
    }

    /**
     * Gets a map of keys to classes for all available tools.
     * This does not include any data nor any local context values.
     */
    public Map<String,Class> getToolClassMap()
    {
        Map<String,Class> toolClasses = new HashMap<String,Class>();
        // go thru toolboxes backwards so final map matches
        // what would be found in lookups
        int n = toolboxes.size();
        for (int i = toolboxes.size() - 1; i >= 0; i--)
        {
            Toolbox toolbox = toolboxes.get(i);
            toolClasses.putAll(toolbox.getToolClassMap());
        }
        return toolClasses;
    }

    protected List<Toolbox> getToolboxes()
    {
        return this.toolboxes;
    }

    protected Map<String,Object> getToolProperties()
    {
        return this.toolProps;
    }

    public Object putToolProperty(String key, Object value)
    {
        return toolProps.put(key, value);
    }

    public Object put(String key, Object value)
    {
        return localContext.put(key, value);
    }

    public Object get(String key)
    {
        Object value = findTool(key);
        if (value == null)
        {
            return internalGet(key);
        }
        return value;
    }

    protected Object internalGet(String key)
    {
        return localContext.get(key);
    }

    protected Object findTool(String key)
    {
        String path = (String)toolProps.get(PATH_KEY);
        for (Toolbox toolbox : getToolboxes())
        {
            Object tool = toolbox.get(key, path, toolProps);
            if (tool != null)
            {
                return tool;
            }
        }
        return null;
    }
        

    public Set<String> keySet()
    {
        Set<String> keys = new HashSet<String>();
        for (Toolbox toolbox : toolboxes)
        {
            keys.addAll(toolbox.getKeys());
        }
        keys.addAll(localContext.keySet());
        return keys;
    }

    public boolean containsKey(Object key)
    {
        return keySet().contains(key);
    }

    public Object[] getKeys()
    {
        return keySet().toArray();
    }

    public Object remove(Object key)
    {
        //tools and their props cannot be removed
        return localContext.remove(key);
    }
}

@@ -6302,11 +6302,98 @@
 );%0A    %7D
+%0A%0A    public void putAll(Map context)%0A    %7B%0A        localContext.putAll(context);%0A    %7D
 %0A%7D%0A

make it easier to fill ToolContexts from a Map