/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.container;

import org.exoplatform.container.spi.ComponentAdapter;
import org.exoplatform.container.spi.Container;
import org.exoplatform.container.spi.ContainerException;
import org.exoplatform.container.spi.ContainerVisitor;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
public class CachingContainer extends AbstractInterceptor
{

   /**
    * Serial Version UID
    */
   private static final long serialVersionUID = 316388590860241305L;

   private final ConcurrentMap<Class<?>, ComponentAdapter<?>> adapterByType =
      new ConcurrentHashMap<Class<?>, ComponentAdapter<?>>();

   private final ConcurrentMap<Class<?>, Object> instanceByType = new ConcurrentHashMap<Class<?>, Object>();

   private final ConcurrentMap<Object, Object> instanceByKey = new ConcurrentHashMap<Object, Object>();

   private final ConcurrentMap<Class<?>, Object> adaptersByType =
      new ConcurrentHashMap<Class<?>, Object>();

   private final ConcurrentMap<Class<?>, List<?>> instancesByType = new ConcurrentHashMap<Class<?>, List<?>>();

   private final ThreadLocal<Boolean> enabled = new ThreadLocal<Boolean>();

   @SuppressWarnings("unchecked")
   public <T> ComponentAdapter<T> getComponentAdapterOfType(Class<T> componentType, boolean autoRegistration)
   {
      ComponentAdapter<T> adapter = (ComponentAdapter<T>)adapterByType.get(componentType);
      if (adapter == null)
      {
         adapter = super.getComponentAdapterOfType(componentType, autoRegistration);
         if (adapter != null)
         {
            adapterByType.put(componentType, adapter);
         }
      }
      return adapter;
   }

   public <T> List<ComponentAdapter<T>> getComponentAdaptersOfType(Class<T> componentType)
   {
      @SuppressWarnings("unchecked")
      List<ComponentAdapter<T>> adapters = (List<ComponentAdapter<T>>)adaptersByType.get(componentType);
      if (adapters == null)
      {
         adapters = super.getComponentAdaptersOfType(componentType);
         if (adapters != null)
         {
            adaptersByType.put(componentType, adapters);
         }
      }
      return adapters;
   }

   @SuppressWarnings("unchecked")
   public <T> List<T> getComponentInstancesOfType(Class<T> componentType) throws ContainerException
   {
      List<?> instances = instancesByType.get(componentType);
      if (instances == null)
      {
         instances = super.getComponentInstancesOfType(componentType);
         if (instances != null)
         {
            Boolean cacheEnabled = enabled.get();
            try
            {
               if (cacheEnabled == null || cacheEnabled.booleanValue())
               {
                  instancesByType.put(componentType, instances);
               }
            }
            finally
            {
               if (cacheEnabled != null)
                  enabled.remove();
            }
         }
      }
      return (List<T>)instances;
   }

   public <T> T getComponentInstance(Object componentKey, Class<T> bindType, boolean autoRegistration) throws ContainerException
   {
      Object instance = instanceByKey.get(componentKey);
      if (instance == null)
      {
         instance = super.getComponentInstance(componentKey, bindType, autoRegistration);
         if (instance != null)
         {
            Boolean cacheEnabled = enabled.get();
            try
            {
               if (cacheEnabled == null || cacheEnabled.booleanValue())
               {
                  instanceByKey.put(componentKey, instance);
               }
            }
            finally
            {
               if (cacheEnabled != null)
                  enabled.remove();
            }
         }
      }
      return bindType.cast(instance);
   }

   public <T> T getComponentInstanceOfType(Class<T> componentType, boolean autoRegistration)
   {
      Object instance = instanceByType.get(componentType);
      if (instance == null)
      {
         instance = super.getComponentInstanceOfType(componentType, autoRegistration);
         if (instance != null)
         {
            Boolean cacheEnabled = enabled.get();
            try
            {
               if (cacheEnabled == null || cacheEnabled.booleanValue())
               {
                  instanceByType.put(componentType, instance);
               }
            }
            finally
            {
               if (cacheEnabled != null)
                  enabled.remove();
            }
         }
      }
      return componentType.cast(instance);
   }

   private static final ContainerVisitor invalidator = new ContainerVisitor()
   {
      public void visitContainer(Container container)
      {
         do
         {
            if (container instanceof CachingContainer)
            {
               CachingContainer caching = (CachingContainer)container;
               caching.adapterByType.clear();
               caching.adaptersByType.clear();
               caching.instanceByKey.clear();
               caching.instanceByType.clear();
               caching.instancesByType.clear();
               break;
            }
         }
         while ((container = container.getSuccessor()) != null);
      }
   };

   private void invalidate()
   {
      accept(invalidator);
   }

   public ComponentAdapter<?> unregisterComponent(Object componentKey)
   {
      ComponentAdapter<?> adapter = super.unregisterComponent(componentKey);
      invalidate();
      return adapter;
   }

   public <T> ComponentAdapter<T> registerComponentInstance(Object componentKey, T componentInstance)
      throws ContainerException
   {
      ComponentAdapter<T> adapter = super.registerComponentInstance(componentKey, componentInstance);
      invalidate();
      return adapter;
   }

   public <T> ComponentAdapter<T> registerComponentImplementation(Object componentKey, Class<T> componentImplementation)
      throws ContainerException
   {
      ComponentAdapter<T> adapter = super.registerComponentImplementation(componentKey, componentImplementation);
      invalidate();
      return adapter;
   }

   /**
    * {@inheritDoc}
    */
   public String getId()
   {
      return "Cache";
   }

   void disable()
   {
      enabled.set(Boolean.FALSE);
   }
}
