/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2006 - Javolution (http://javolution.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.context;

import java.lang.ThreadLocal;
import javolution.util.FastMap;
import javolution.util.FastTable;

/**
 * <p> This class represents the default allocator context. Allocations are 
 *     performed using the <code>new</code> keyword and explicit object 
 *     {@link ObjectFactory#recycle(Object) recycling} is supported:[code]
 *         char[] buffer = ArrayFactory.CHARS_FACTORY.array(4098); // Possibly recycled.
 *         while (reader.read(buffer) > 0) { ... }
 *         ArrayFactory.CHARS_FACTORY.recycle(buffer); // Explicit recycling.
 *     [/code]</p>
 *
 * <p> It should be noted that object recycling is performed on a thread basis
 *     (for performance reasons) and should only be performed if the object
 *     has been factory produced by the same thread doing the recycling.
 *     It is usually not a problem because recycling is done for temporary
 *     objects within the same method. For example:[code]
 *     public String toString() {
 *         TextBuilder tmp = TextBuilder.newInstance(); // Calls ObjectFactory.object()
 *         try {
 *             tmp.append(...);
 *             ...
 *             return tmp.toString();
 *         } finally {
 *             TextBuilder.recycle(tmp); // Calls ObjectFactory.recycle(...)
 *         }
 *     }[/code]
 *     If allocation/recycling is performed by different threads then
 *     {@link PoolContext} should be employed.</p>
 *
 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 5.3, March 19, 2009
 */
public class HeapContext extends AllocatorContext {

    /**
     * Holds the factory to allocator mapping (per thread).
     */
    private static final ThreadLocal FACTORY_TO_ALLOCATOR = new ThreadLocal() {

        protected Object initialValue() {
            return new FastMap();
        }
    };
    
    /**
     * Holds the allocators which have been activated (per thread).
     */
    private static final ThreadLocal ACTIVE_ALLOCATORS = new ThreadLocal() {

        protected Object initialValue() {
            return new FastTable();
        }
    };

    /**
     * Enters a heap context.
     */
    public static void enter() {
         Context.enter(HeapContext.class);
    }

    /**
     * Exits the current heap context.
     * 
     * @throws ClassCastException if the context is not a heap context.
     */
    public static void exit() {
          Context.exit(HeapContext.class);
    }

    /**
     * Default constructor.
     */
    public HeapContext() {
    }

    // Overrides.
    protected void deactivate() {
        FastTable allocators = (FastTable) ACTIVE_ALLOCATORS.get();
        for (int i = 0, n = allocators.size(); i < n;) {
            ((Allocator) allocators.get(i++)).user = null;
        }
        allocators.clear();
    }

    // Overrides.
    protected Allocator getAllocator(ObjectFactory factory) {
        final FastMap factoryToAllocator = (FastMap) FACTORY_TO_ALLOCATOR.get();
        HeapAllocator allocator = (HeapAllocator) factoryToAllocator.get(factory);
        if (allocator == null) {
            allocator = new HeapAllocator(factory);
            factoryToAllocator.put(factory, allocator);
        }
        if (allocator.user == null) { // Activate.
            allocator.user = Thread.currentThread();
            FastTable activeAllocators = (FastTable) ACTIVE_ALLOCATORS.get();
            activeAllocators.add(allocator);
        }
        return allocator;
    }

    // Overrides.
    protected void enterAction() {
        getOuter().getAllocatorContext().deactivate();
    }

    // Overrides.
    protected void exitAction() {
        this.deactivate();
    }

    // Holds heap allocator implementation.
    private static final class HeapAllocator extends Allocator {

        private final ObjectFactory _factory;
        private final FastTable _recycled = new FastTable();

        public HeapAllocator(ObjectFactory factory) {
            _factory = factory;
        }

        protected Object allocate() {
            return _recycled.isEmpty() ? _factory.create() :
                _recycled.removeLast();
        }

        protected void recycle(Object object) {
            if (_factory.doCleanup())
                _factory.cleanup(object);
            _recycled.addLast(object);
        }

        public String toString() {
            return "Heap allocator for " + _factory.getClass();
        }

		@Override
		protected int getRecycledSize()
		{
			return _recycled.size();
		}
    }
}