package org.apache.velocity.context;

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

/**
 *  This class is the abstract base class for all conventional
 *  Velocity Context  implementations.  Simply extend this class
 *  and implement the abstract routines that access your preferred
 *  storage method.
 *
 *  Takes care of context chaining.
 *
 *  Also handles / enforces policy on null keys and values :
 *
 *  <ul>
 *  <li> Null keys and values are accepted and basically dropped.
 *  <li> If you place an object into the context with a null key, it
 *        will be ignored and logged.
 *  <li> If you try to place a null into the context with any key, it
 *        will be dropped and logged.
 *  </ul>
 *
 *  The default implementation of this for application use is
 *  org.apache.velocity.VelocityContext.
 *
 *  All thanks to Fedor for the chaining idea.
 *
 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 * @author <a href="mailto:fedor.karpelevitch@home.com">Fedor Karpelevitch</a>
 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 * @version $Id$
 */

public abstract class AbstractContext extends InternalContextBase
    implements Context
{
    /**
     *  the chained Context if any
     */
    private Context innerContext = null;

    /**
     *  Implement to return a value from the context storage.
     *  <br><br>
     *  The implementation of this method is required for proper
     *  operation of a Context implementation in general
     *  Velocity use.
     *
     *  @param key key whose associated value is to be returned
     *  @return object stored in the context
     */
    public abstract Object internalGet( String key );

    /**
     *  Implement to put a value into the context storage.
     *  <br><br>
     *  The implementation of this method is required for
     *  proper operation of a Context implementation in
     *  general Velocity use.
     *
     *  @param key key with which to associate the value
     *  @param value value to be associated with the key
     *  @return previously stored value if exists, or null
     */
    public abstract Object internalPut( String key, Object value );

    /**
     *  Implement to determine if a key is in the storage.
     *  <br><br>
     *  Currently, this method is not used internally by
     *  the Velocity engine.
     *
     *   @param key key to test for existence
     *   @return true if found, false if not
     */
    public abstract boolean internalContainsKey(String key);

    /**
     *  Implement to return an object array of key
     *  strings from your storage.
     *  <br><br>
     *  Currently, this method is not used internally by
     *  the Velocity engine.
     *
     *  @return array of keys
     */
    public abstract String[] internalGetKeys();

    /**
     *  Implement to remove an item from your storage.
     *  <br><br>
     *  Currently, this method is not used internally by
     *  the Velocity engine.
     *
     *  @param key key to remove
     *  @return object removed if exists, else null
     */
    public abstract Object internalRemove(String key);

    /**
     *  default CTOR
     */
    public AbstractContext()
    {
    }

    /**
     *  Chaining constructor accepts a Context argument.
     *  It will relay get() operations into this Context
     *  in the even the 'local' get() returns null.
     *
     *  @param inner context to be chained
     */
    public AbstractContext( Context inner )
    {
        innerContext = inner;

        /*
         *  now, do a 'forward pull' of event cartridge so
         *  it's accessible, bringing to the top level.
         */

        if (innerContext instanceof InternalEventContext )
        {
            attachEventCartridge( ( (InternalEventContext) innerContext).getEventCartridge() );
        }
    }

    /**
     * Adds a name/value pair to the context.
     *
     * @param key   The name to key the provided value with.
     * @param value The corresponding value.
     * @return Object that was replaced in the the Context if
     *         applicable or null if not.
     */
    @Override
    public Object put(String key, Object value)
    {
        /*
         * don't even continue if key is null
         */
        if (key == null)
        {
            return null;
        }

        /*
         * We always use string interning here:
         * 1) speed is generally less critical when populating the context than during parsing or rendering
         * 2) a context key is very likely to be used several times, or even a lot of times, in the template (but does it save memory if runtime.string_interning is false?)
         * 3) last but not least: we don't have access to RuntimeServices from here, the reengineering would be painful...
         */
        return internalPut(key.intern(), value);
    }

    /**
     *  Gets the value corresponding to the provided key from the context.
     *
     *  Supports the chaining context mechanism.  If the 'local' context
     *  doesn't have the value, we try to get it from the chained context.
     *
     *  @param key The name of the desired value.
     *  @return    The value corresponding to the provided key or null if
     *             the key param is null.
     */
    @Override
    public Object get(String key)
    {
        /*
         *  punt if key is null
         */

        if (key == null)
        {
            return null;
        }

        /*
         *  get the object for this key.  If null, and we are chaining another Context
         *  call the get() on it.
         */

        Object o = internalGet( key );

        if (o == null && innerContext != null)
        {
            o = innerContext.get( key );
        }

        return o;
    }

    /**
     *  Indicates whether the specified key is in the context.  Provided for
     *  debugging purposes.
     *
     * @param key The key to look for.
     * @return true if the key is in the context, false if not.
     */
    @Override
    public boolean containsKey(String key)
    {
        if (key == null)
        {
            return false;
        }

        boolean exists = internalContainsKey(key);
        if (!exists && innerContext != null)
        {
            exists = innerContext.containsKey(key);
        }

        return exists;
    }

    /**
     *  Get all the keys for the values in the context
     *  @return Object[] of keys in the Context. Does not return
     *          keys in chained context.
     */
    @Override
    public String[] getKeys()
    {
        return internalGetKeys();
    }

    /**
     * Removes the value associated with the specified key from the context.
     *
     * @param key The name of the value to remove.
     * @return    The value that the key was mapped to, or <code>null</code>
     *            if unmapped.
     */
    @Override
    public Object remove(String key)
    {
        if (key == null)
        {
            return null;
        }

        return internalRemove(key);
    }

    /**
     *  returns innerContext if one is chained
     *
     *  @return Context if chained, <code>null</code> if not
     */
    public Context getChainedContext()
    {
        return innerContext;
    }

}
