/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.aop;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.aop.advice.AspectDefinition;
import org.jboss.aop.joinpoint.Joinpoint;
import org.jboss.aop.metadata.SimpleMetaData;

/**
 * Initialisation and getting of instance and joinpoint aspects needed by the various kinds of
 * InstanceAdvisor implementations
 *  
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 * @version $Revision: 73431 $
 */
public class InstanceAdvisorDelegate implements Serializable
{
   private static final long serialVersionUID = -5421366346785427537L;
   /** Indicates that a call to the factory has been made, but the factory returned null */
   private static final Object NULL_ASPECT = new Object();
   protected transient WeakReference<Advisor> classAdvisor;
   InstanceAdvisor instanceAdvisor;
   protected transient WeakHashMap<AspectDefinition, Object> aspects;
   protected transient WeakHashMap<AspectDefinition, ConcurrentHashMap<Joinpoint, Object>> joinpointAspects;
   protected SimpleMetaData metadata;


   public InstanceAdvisorDelegate(Advisor classAdvisor, InstanceAdvisor instanceAdvisor)
   {
      this.instanceAdvisor = instanceAdvisor;
      this.classAdvisor = new WeakReference<Advisor>(classAdvisor);
   }

   public Advisor getAdvisor()
   {
      if (classAdvisor != null)
      {
         return classAdvisor.get();
      }
      return null;
   }
   
   public void initialize()
   {
      initializeAspects();
      initializeJoinpointAspects();
   }
   
   private Advisor getClassAdvisor()
   {
      return getAdvisor();
   }
   
   private synchronized void initializeAspects()
   {
      if (getClassAdvisor() == null) return;
      if (aspects != null) return; // doublecheck I know, but I don't want to do synchronization if not needed
      //ClassAdvisor cadvisor = (ClassAdvisor) classAdvisor;
      if (instanceAdvisor instanceof Advisor)
      {
         Advisor ia = (Advisor)instanceAdvisor;
         Set<AspectDefinition> instanceDefs = ia.getPerInstanceAspectDefinitions();
         if (instanceDefs.size() > 0)
         {
            aspects = new WeakHashMap<AspectDefinition, Object>();
            for (AspectDefinition def : instanceDefs)
            {
               ia.addPerInstanceAspect(def);
               Object aspect = def.getFactory().createPerInstance(getClassAdvisor(), instanceAdvisor);
               aspects.put(def, aspect);
            }
         }
      }
      Set<AspectDefinition> defs = getClassAdvisor().getPerInstanceAspectDefinitions();
      if (defs.size() > 0)
      {
         if (aspects == null)
         {
            aspects = new WeakHashMap<AspectDefinition, Object>();
         }
         for (AspectDefinition def : defs)
         {
            Object aspect = def.getFactory().createPerInstance(getClassAdvisor(), instanceAdvisor);
            aspects.put(def, aspect);
         }
      }
   }

   private synchronized void initializeJoinpointAspects()
   {
      if (getClassAdvisor() == null) return;
      if (joinpointAspects != null) return; // doublecheck I know, but I don't want to do synchronization if not needed
      if (instanceAdvisor instanceof Advisor)
      {
         Advisor ia = (Advisor)instanceAdvisor;
         Map<AspectDefinition, Set<Joinpoint>> instanceJpAspects = ia.getPerInstanceJoinpointAspectDefinitions();
         
         if (instanceJpAspects.size() > 0)
         {
            joinpointAspects = new WeakHashMap<AspectDefinition, ConcurrentHashMap<Joinpoint, Object>>();
            for (AspectDefinition def : instanceJpAspects.keySet())
            {
               initJoinpointAspect(def, instanceJpAspects);
               Set<Joinpoint> joinpoints = instanceJpAspects.get(def);
               ia.addPerInstanceJoinpointAspect(joinpoints, def);
            }
         }
      }
      
      Map<AspectDefinition, Set<Joinpoint>> jpAspects = getClassAdvisor().getPerInstanceJoinpointAspectDefinitions();
      if (jpAspects.size() > 0)
      {
         joinpointAspects = new WeakHashMap<AspectDefinition, ConcurrentHashMap<Joinpoint, Object>>();
         for (AspectDefinition def : jpAspects.keySet())
         {
            initJoinpointAspect(def, jpAspects);
         }
      }
   }
   
   private void initJoinpointAspect(AspectDefinition def, Map<AspectDefinition, Set<Joinpoint>> jpAspects)
   {
      ConcurrentHashMap<Joinpoint, Object> joins = new ConcurrentHashMap<Joinpoint, Object>();
      joinpointAspects.put(def, joins);
      Set<Joinpoint> joinpoints = jpAspects.get(def);
      for (Joinpoint joinpoint : joinpoints)
      {
         Object aspect = def.getFactory().createPerJoinpoint(getClassAdvisor(),
               instanceAdvisor, joinpoint);
         if (aspect == null)
         {
            joins.put(joinpoint, NULL_ASPECT);
         }
         else
         {
            joins.put(joinpoint, aspect);
         }
      }
   }
   
   public Object getPerInstanceAspect(String def)
   {
      for (AspectDefinition d : aspects.keySet())
      {
         if (d.getName().equals(def)) return aspects.get(d);
      }
      return null;
   }

   public Object getPerInstanceAspect(AspectDefinition def)
   {
      // aspects is a weak hash map of AspectDefinitions so that perinstance advices can be undeployed/redeployed
      if (aspects == null)
      {
         initializeAspects();
         if (aspects != null)
         {
            return aspects.get(def);
         }
      }
      if (!aspects.containsKey(def))
      {
         synchronized (this) // doublecheck, but I don't want to synchronize everywhere and dynamic aspects are rare
         {
            if (aspects.containsKey(def)) return aspects.get(def);
            if (classAdvisor != null && getClassAdvisor() instanceof ClassAdvisor)
            {
               ClassAdvisor cadvisor = (ClassAdvisor) getClassAdvisor();
               cadvisor.getPerInstanceAspectDefinitions().add(def);
               Object aspect = def.getFactory().createPerInstance(null, null);
               WeakHashMap<AspectDefinition, Object> copy = new WeakHashMap<AspectDefinition, Object>(aspects);
               copy.put(def, aspect);
               aspects = copy;
               return aspect;
            }
         }
      }
      return aspects.get(def);
   }

   public Object getPerInstanceJoinpointAspect(Joinpoint joinpoint, AspectDefinition def)
   {
      // aspects is a weak hash map of AspectDefinitions so that perinstance advices can be undeployed/redeployed
      if (joinpointAspects == null)
      {
         initializeJoinpointAspects();
         return getJoinpointAspect(def, joinpoint);
      }
      Object aspect = getJoinpointAspect(def, joinpoint);
      if (aspect == null)
      {
         synchronized (this) // doublecheck, but I don't want to synchronize everywhere and dynamic aspects are rare
         {
            aspect = getJoinpointAspect(def, joinpoint);
            if (aspect != null)
            {
               if (aspect == NULL_ASPECT)
               {
                  return null;
               }
               return aspect;
            }
            if (classAdvisor != null && getClassAdvisor() instanceof ClassAdvisor)
            {
               ClassAdvisor cadvisor = (ClassAdvisor) getClassAdvisor();
               cadvisor.addPerInstanceJoinpointAspect(joinpoint, def);
               aspect = def.getFactory().createPerJoinpoint(getClassAdvisor(), instanceAdvisor, joinpoint);
               WeakHashMap<AspectDefinition, ConcurrentHashMap<Joinpoint, Object>> copy = new WeakHashMap<AspectDefinition, ConcurrentHashMap<Joinpoint, Object>>(joinpointAspects);
               Map<Joinpoint, Object> map = copy.get(def);
               if (map == null)
               {
                  map = new ConcurrentHashMap<Joinpoint, Object>();
               }
               if (aspect == null)
               {
                  map.put(joinpoint, NULL_ASPECT);
               }
               else
               {
                  map.put(joinpoint, aspect);
               }
               joinpointAspects = copy;
            }
         }
      }
      if (aspect == NULL_ASPECT)
      {
         return null;
      }
      return aspect;
   }

   private Object getJoinpointAspect(AspectDefinition def, Joinpoint joinpoint)
   {
      if (joinpointAspects == null) return null;
      Map<Joinpoint, Object> map = joinpointAspects.get(def);
      Object aspect = map.get(joinpoint);
      return aspect;
   }
   
   public SimpleMetaData getMetaData()
   {
      if (metadata == null)
      {
         synchronized (this) // doublecheck :(
         {
            if (metadata == null)
            {
               metadata = new SimpleMetaData();
            }
         }
      }
      return metadata;
   }

}
