/*
* 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.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

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

import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;

/**
 * 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: 1.4 $
 */
public class InstanceAdvisorDelegate implements Serializable
{
   private static final long serialVersionUID = -5421366346785427537L;
   
   protected transient WeakReference classAdvisor;
   InstanceAdvisor instanceAdvisor;
   protected transient WeakHashMap aspects;
   protected transient WeakHashMap joinpointAspects;
   protected SimpleMetaData metadata;


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

   public Advisor getAdvisor()
   {
      if (classAdvisor != null)
      {
         return (Advisor)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;
      Set defs = getClassAdvisor().getPerInstanceAspectDefinitions();
      if (instanceAdvisor instanceof Advisor)
      {
         Advisor ia = (Advisor)instanceAdvisor;
         Set instanceDefs = ia.getPerInstanceAspectDefinitions();
         defs.addAll(instanceDefs);
      }
      if (defs.size() > 0)
      {
         aspects = new WeakHashMap();
         Iterator it = defs.iterator();
         while (it.hasNext())
         {
            AspectDefinition def = (AspectDefinition) it.next();
            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
      Map jpAspects = getClassAdvisor().getPerInstanceJoinpointAspectDefinitions();
      if (instanceAdvisor instanceof Advisor)
      {
         Advisor ia = (Advisor)instanceAdvisor;
         Map instanceJpAspects = ia.getPerInstanceJoinpointAspectDefinitions();
         jpAspects.putAll(instanceJpAspects);
      }
      
      if (jpAspects.size() > 0)
      {
         joinpointAspects = new WeakHashMap();
         Iterator it = jpAspects.keySet().iterator();
         while (it.hasNext())
         {
            AspectDefinition def = (AspectDefinition) it.next();
            ConcurrentReaderHashMap joins = new ConcurrentReaderHashMap();
            joinpointAspects.put(def, joins);
            Set joinpoints = (Set) jpAspects.get(def);
            Iterator jps = joinpoints.iterator();
            while (jps.hasNext())
            {
               Object joinpoint = jps.next();
               joins.put(joinpoint, def.getFactory().createPerJoinpoint(getClassAdvisor(), instanceAdvisor, (Joinpoint) joinpoint));
            }
         }
      }
   }
   
   public Object getPerInstanceAspect(String def)
   {
      Iterator it = aspects.keySet().iterator();
      while (it.hasNext())
      {
         AspectDefinition d = (AspectDefinition) it.next();
         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();
         return aspects.get(def);
      }
      Object aspect = aspects.get(def);
      if (aspect == null)
      {
         synchronized (this) // doublecheck, but I don't want to synchronize everywhere and dynamic aspects are rare
         {
            aspect = aspects.get(def);
            if (aspect != null) return aspect;
            if (classAdvisor != null && getClassAdvisor() instanceof ClassAdvisor)
            {
               ClassAdvisor cadvisor = (ClassAdvisor) getClassAdvisor();
               cadvisor.getPerInstanceAspectDefinitions().add(def);
               aspect = def.getFactory().createPerInstance(null, null);
               WeakHashMap copy = new WeakHashMap(aspects);
               copy.put(def, aspect);
               aspects = copy;
            }
         }
      }
      return aspect;
   }

   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) return aspect;
            if (classAdvisor != null && getClassAdvisor() instanceof ClassAdvisor)
            {
               ClassAdvisor cadvisor = (ClassAdvisor) getClassAdvisor();
               cadvisor.addPerInstanceJoinpointAspect(joinpoint, def);
               aspect = def.getFactory().createPerJoinpoint(getClassAdvisor(), instanceAdvisor, joinpoint);
               WeakHashMap copy = new WeakHashMap(joinpointAspects);
               Map map = (Map) copy.get(def);
               if (map == null)
               {
                  map = new ConcurrentReaderHashMap();
               }
               map.put(joinpoint, aspect);
               joinpointAspects = copy;
            }
         }
      }
      return aspect;
   }

   private Object getJoinpointAspect(AspectDefinition def, Joinpoint joinpoint)
   {
      if (joinpointAspects == null) return null;
      Map map = (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;
   }

}
