package org.codehaus.plexus.context;

/*
 * Copyright 2001-2006 Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Default implementation of Context.
 *
 * This implementation is a static hierarchial store. It has the normal <code>get()</code>
 * and <code>put</code> methods. The <code>hide</code> method will hide a property. When
 * a property has been hidden the containerContext will not search in the parent containerContext for the value.
 *
 * @author <a href="mailto:dev@avalon.codehaus.org">Avalon Development Team</a>
 * @version $Id: DefaultContext.java 8061 2009-01-19 23:33:12Z dain $
 */
public class DefaultContext
    implements Context
{
    /**
     * Context data.
     */
    private final ConcurrentMap<Object, Object> contextData = new ConcurrentHashMap<Object, Object>();

    /**
     * Is the containerContext read only.
     */
    private final AtomicBoolean readOnly = new AtomicBoolean(false);

    /**
     * Create an empty Context.
     */
    public DefaultContext()
    {
    }

    /**
     * Create a Context with specified data.  The specified data is copied into the context so any subsequent updates
     * to supplied map are not reflected in this context.  Additionally, changes to this context are not reflected in
     * the specified map.
     *
     * @param contextData the containerContext data
     */
    public DefaultContext( Map<Object, Object> contextData )
    {
        if ( contextData == null )
        {
            throw new NullPointerException( "contextData is null" );
        }

        // check for nulls in key and value
        for ( Entry<Object, Object> entry : contextData.entrySet() )
        {
            Object key = entry.getKey();
            Object value = entry.getValue();
            if ( key == null )
            {
                throw new IllegalArgumentException( "Key is null" );
            }
            if ( value != null )
            {
                this.contextData.put( key, value );
            }
        }
    }

    public boolean contains( Object key )
    {
        Object data = contextData.get( key );

        return data != null;
    }

    public Object get( Object key )
        throws ContextException
    {
        Object data = contextData.get( key );

        if ( data == null )
        {
            // There is no data for the key
            throw new ContextException( "Unable to resolve context key: " + key );
        }
        return data;
    }

    public void put( Object key, Object value )
        throws IllegalStateException
    {
        checkWriteable();

        // check for a null key
        if (key == null)
        {
            throw new IllegalArgumentException("Key is null");
        }

        if ( value == null )
        {
            contextData.remove( key );
        }
        else
        {
            contextData.put( key, value );
        }
    }

    public void hide( Object key )
        throws IllegalStateException
    {
        checkWriteable();
        
        contextData.remove( key );
    }

    /**
     * Utility method to retrieve containerContext data
     *
     * @return the containerContext data
     */
    public Map getContextData()
    {
        return Collections.unmodifiableMap( contextData );

    }

    /**
     * Make the containerContext read-only.
     * Any attempt to write to the containerContext via put()
     * will result in an IllegalStateException.
     */
    public void makeReadOnly()
    {
        readOnly.set( true );
    }

    /**
     * Utility method to check if containerContext is writeable and if not throw exception.
     *
     * @throws java.lang.IllegalStateException if containerContext is read only
     */
    protected void checkWriteable()
        throws IllegalStateException
    {
        if ( readOnly.get() )
        {
            throw new IllegalStateException( "Context is read only and can not be modified" );
        }
    }
    
    public String toString()
    {
        return contextData.toString();
    }
}
