/*
  * 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 gnu.trove.TLongObjectHashMap;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.aop.advice.AdviceBinding;
import org.jboss.aop.advice.AspectDefinition;
import org.jboss.aop.advice.GeneratedAdvisorInterceptor;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.advice.InterceptorFactory;
import org.jboss.aop.advice.PrecedenceSorter;
import org.jboss.aop.instrument.ConByConJoinPointGenerator;
import org.jboss.aop.instrument.ConByMethodJoinPointGenerator;
import org.jboss.aop.instrument.ConstructionJoinPointGenerator;
import org.jboss.aop.instrument.ConstructorJoinPointGenerator;
import org.jboss.aop.instrument.FieldJoinPointGenerator;
import org.jboss.aop.instrument.JoinPointGenerator;
import org.jboss.aop.instrument.MethodByConJoinPointGenerator;
import org.jboss.aop.instrument.MethodByMethodJoinPointGenerator;
import org.jboss.aop.instrument.MethodJoinPointGenerator;
import org.jboss.aop.joinpoint.FieldJoinpoint;
import org.jboss.aop.joinpoint.Joinpoint;
import org.jboss.aop.joinpoint.MethodJoinpoint;
import org.jboss.aop.pointcut.PointcutMethodMatch;
import org.jboss.aop.util.UnmodifiableEmptyCollections;
import org.jboss.aop.util.logging.AOPLogger;

/**
 * Comment
 *
 * @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
 * @version $Revision$
 */
public class GeneratedClassAdvisor extends ClassAdvisor
{
   public static final String ADD_METHOD_INFO = "addMethodInfo";
   public static final String ADD_CONSTRUCTOR_INFO = "addConstructorInfo";
   public static final String ADD_CONSTRUCTION_INFO = "addConstructionInfo";
   public static final String ADD_FIELD_READ_INFO = "addFieldReadInfo";
   public static final String ADD_FIELD_WRITE_INFO = "addFieldWriteInfo";
   public static final String GET_PARENT_ADVISOR = "getParentAdvisor";

   private static final AOPLogger logger = AOPLogger.getLogger(GeneratedClassAdvisor.class);
   
// TODO Flavia   
//   ArrayList<ConstructorInfo> constructorInfos = new ArrayList();
//   ArrayList<ConstructionInfo> constructionInfos = new ArrayList();
//   ArrayList<FieldInfo> fieldReadInfos = new ArrayList();
//   ArrayList<FieldInfo> fieldWriteInfos = new ArrayList();
   /** Super class methods that have been overrridden - these need special handling in this weaving mode */
   ArrayList<MethodInfo> overriddenMethods = new ArrayList<MethodInfo>(); 

   //TODO These are only needed for the class advisor really
   //All joinpoint generators apart from field reads, constructions and MethodCalledByXXX go in here
   private volatile ConcurrentHashMap<Joinpoint, JoinPointGenerator> joinPointGenerators = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;
   //Needs its own map to avoid crashing with the field write generators
   private volatile ConcurrentHashMap<Joinpoint, FieldJoinPointGenerator> fieldReadJoinPoinGenerators = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;
   //Needs its own map to avoid crashing with the constructor generators
   private volatile ConcurrentHashMap<Joinpoint, ConstructorJoinPointGenerator> constructionJoinPointGenerators = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;
   //An extra level of indirection is needed for MethodCalledByCon and MethodCalledByMethod as compared to the main joinPointGenerators map
   private volatile ConcurrentHashMap<Joinpoint, ConcurrentHashMap<Class<?>, JoinPointGenerator>> methodCalledByXXXJoinPointGenerators = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;
   
   ConcurrentHashMap<Joinpoint, Interceptor[]> oldInfos = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;
   ConcurrentHashMap<Joinpoint, Interceptor[]> oldFieldReadInfos = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;
   ConcurrentHashMap<Joinpoint, Interceptor[]> oldConstructionInfos = UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP;

   boolean initialisedSuperClasses; 

   private int version;
   
   /**
    * Different behaviour depending on if we are a class advisor or instance advisor
    */
   AdvisorStrategy advisorStrategy;

   protected GeneratedClassAdvisor(String classname)
   {
      //Generated advisors will not pass in an aspectmanager
      //This will be passed in via the initialise() method
      super(classname, null);
      advisorStrategy = new ClassAdvisorStrategy(); 
   }
   
   protected GeneratedClassAdvisor(String classname, GeneratedClassAdvisor parent)
   {
      super(classname, null);
      advisorStrategy = new InstanceAdvisorStrategy(parent);
   }

   @Override
   public void cleanup()
   {
      super.cleanup();
      methodInfos = null;
      advisorStrategy = null;
      
      Map<Domain, Object> subscribedSubDomains = getManager().getSubscribedSubDomains();
      synchronized (subscribedSubDomains)
      {
         for (Iterator<Domain> it = subscribedSubDomains.keySet().iterator() ; it.hasNext() ; )
         {
            //TODO Not really sure what was attempted here
            //GeneratedAdvisorDomain manager = (GeneratedAdvisorDomain)it.next();
            //Map advisors = manager.getAdvisors();
            it.remove();
         }
      }
   }

   protected void initialise(Class<?> clazz, AspectManager manager)
   {
      advisorStrategy.initialise(clazz, manager);
   }

   /**
    * Generated class advisor sub class will override
    */
   protected void initialiseCallers()
   {
   }

   /**
    * Generated instance advisor sub class will override
    */
   protected void initialiseInfosForInstance()
   {
      

   }
   
   /**
    * To be called by initialiseInfosForInstance() in the generated instance advisors
    */
   protected MethodInfo copyInfoFromClassAdvisor(MethodInfo info)
   {
      MethodInfo copy = (MethodInfo)info.copy();
      copy.setAdvisor(this);
      addMethodInfo(copy);
      return copy;
   }
   
   /**
    * To be called by initialiseInfosForInstance() in the generated instance advisors
    */
   protected FieldInfo copyInfoFromClassAdvisor(FieldInfo info)
   {
      FieldInfo copy = (FieldInfo)info.copy();
      copy.setAdvisor(this);
// TODO Flavia
//      if (copy.isRead())
//      {
//         addFieldReadInfo(copy);
//      }
//      else
//      {
//         addFieldWriteInfo(copy);
//      }
      return copy;
   }

   /**
    * To be called by initialiseInfosForInstance() in the generated instance advisors
    */
   protected ConByConInfo copyInfoFromClassAdvisor(ConByConInfo info)
   {
      ConByConInfo copy = (ConByConInfo)info.copy();
      copy.setAdvisor(this);
      return copy;
   }

   /**
    * To be called by initialiseInfosForInstance() in the generated instance advisors
    */
   protected MethodByConInfo copyInfoFromClassAdvisor(MethodByConInfo info)
   {
      MethodByConInfo copy = (MethodByConInfo)info.copy();
      copy.setAdvisor(this);
      return copy;
   }

   /**
    * To be called by initialiseInfosForInstance() in the generated instance advisors
    */
   protected ConByMethodInfo copyInfoFromClassAdvisor(ConByMethodInfo info)
   {
      ConByMethodInfo copy = (ConByMethodInfo)info.copy();
      copy.setAdvisor(this);
      return copy;
   }
   
   /**
    * To be called by initialiseInfosForInstance() in the generated instance advisors
    */
   protected MethodByMethodInfo copyInfoFromClassAdvisor(MethodByMethodInfo info)
   {
      MethodByMethodInfo copy = (MethodByMethodInfo)info.copy();
      copy.setAdvisor(this);
      return copy;
   }
   
   
   @Override
   protected void rebuildInterceptors()
   {
      version++;
      advisorStrategy.rebuildInterceptors();
   }
   
   @Override
   protected void rebuildInterceptorsForAddedBinding(AdviceBinding binding)
   {
      version++;
      advisorStrategy.rebuildInterceptorsForAddedBinding(binding);
   }
   
   @Override
   protected void rebuildInterceptorsForRemovedBinding(AdviceBinding binding)
   {
      version++;
      advisorStrategy.rebuildInterceptorsForRemovedBinding(binding);
   }
   
   /**
    * Callback for instance advisors to rebuild their interceptors when their
    * version number is out of sync
    */
   protected synchronized void internalRebuildInterceptors()
   {
      super.rebuildInterceptors();
   }

   /**
    * Callback for generated instance advisors to check if the version has been updated 
    */
   protected void checkVersion()
   {
      advisorStrategy.checkVersion();
   }

   /**
    * Will be overridden by generated instanceadvisor classes and perform a rebuild
    */
   protected void doRebuildForInstance()
   {
      
   }

   protected void handleOverriddenMethods(AdviceBinding binding)
   {
      if (overriddenMethods != null && overriddenMethods.size() > 0)
      {
         for(MethodInfo info : overriddenMethods)
         {
            Method method = info.getMethod();
            PointcutMethodMatch match = binding.getPointcut().matchesExecution(this, method);
            
            if (match != null && match.isMatch())
            {
               adviceBindings.add(binding);
               if (AspectManager.verbose)
               {
                  logger.error("method matched binding " + binding.getPointcut().getExpr() + " " + method.toString());     
               }
               binding.addAdvisor(this);
               pointcutResolved(info, binding, new MethodJoinpoint(method));
            }
         }
      }      
   }

   @Override
   protected void resolveMethodPointcut(AdviceBinding binding)
   {
      GeneratedClassAdvisor classAdvisor = getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      if (classAdvisor == null)
      {
         //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
         super.resolveMethodPointcut(binding);
         handleOverriddenMethods(binding);
      }
   }

   @Override
   protected void resolveFieldPointcut(FieldInfo[] newFieldInfos, Interceptor[][] fieldInterceptors, AdviceBinding binding, boolean write)
   {
      GeneratedClassAdvisor classAdvisor = getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      if (classAdvisor == null)
      {
         //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
         super.resolveFieldPointcut(newFieldInfos, fieldInterceptors, binding, write);
      }
   }

   @Override
   protected void resolveConstructorPointcut( AdviceBinding binding)
   {
      advisorStrategy.resolveConstructorPointcut(binding);
   }

   @Override
   protected void resolveConstructionPointcut(AdviceBinding binding)
   {
      advisorStrategy.resolveConstructionPointcut(binding);
   }
   
   /**
    * Generated class advisor sub class will override
    */
   protected void initialiseMethods()
   {
   }
   
   /**
    * Called by initialiseMethods() in generated advisor sub classes
    */
   protected void addMethodInfo(MethodInfo mi)
   {
      MethodInfo old = methodInfos.getMethodInfo(mi.getHash());
      if (old != null)
      {
         overriddenMethods.add(old);
      }
      methodInfos.put(mi.getHash(), mi);
      advisorStrategy.makeAccessibleMethod(mi);
   }

   @Override
   protected void initializeMethodChain()
   {
      //We have all the advised methods here, need to get all the others here too

      long[] keys = advisedMethods.keys();
      for (int i = 0; i < keys.length; i++)
      {
         MethodMatchInfo matchInfo = methodInfos.getMatchInfo(keys[i]);

         if (super.initialized && matchInfo != null)
         {
            matchInfo.clear();
         }

         if (matchInfo == null)
         {
            MethodInfo info = new MethodInfo();
            Method amethod = (Method) advisedMethods.get(keys[i]);
            info.setAdvisedMethod(amethod);
            info.setUnadvisedMethod(amethod);
            info.setHash(keys[i]);
            info.setAdvisor(this);
            methodInfos.put(keys[i], info);
         }
      }
   }


   /**
    * Generated class advisor sub class will override
    */
   protected void initialiseConstructors(Collection<ConstructorInfo> constructorInfos)
   {
   }

   /**
    * Called by initialiseConstructors() in generated advisor sub classes
    */
   protected void addConstructorInfo(ConstructorInfo ci, Collection<ConstructorInfo> constructorInfos)
   {
      constructorInfos.add(ci);
      //If we do dynamic invokes the constructor will need to be accessible via reflection
      SecurityActions.setAccessible(ci.getConstructor());
   }

   @Override
   protected void createInterceptorChains() throws Exception
   {
      advisorStrategy.createInterceptorChains();
   }
   
// TODO Flavia
//   @Override
//   protected ArrayList initializeConstructorChain()
//   {
//      if (super.initialized)
//      {
//         for (Iterator it = constructorInfos.iterator() ; it.hasNext() ; )
//         {
//            ((ConstructorInfo)it.next()).clear();
//         }
//      }
//      return constructorInfos;
//   }

   /**
    * Generated class advisor sub class will override
    */
   protected void initialiseConstructions(Collection<ConstructionInfo> constructorInfos)
   {
   }

   /**
    * Called by initialiseConstructions() in generated advisor sub classes
    */
   protected void addConstructionInfo(ConstructionInfo ci, Collection<ConstructionInfo> constructionInfos)
   {
      constructionInfos.add(ci);
   }

// TODO Flavia
//   @Override
//   protected ArrayList initializeConstructionChain()
//   {
//      if (super.initialized)
//      {
//         for (Iterator it = constructionInfos.iterator() ; it.hasNext() ; )
//         {
//            ((ConstructionInfo)it.next()).clear();
//         }
//      }
//      return constructionInfos;
//   }

   /**
    * Generated class advisor sub class will override
    */
   protected void initialiseFieldReads(Collection<FieldInfo> fieldReadInfos)
   {
   }

   /**
    * Called by initialiseFieldReads() in generated advisor sub classes
    */
   protected void addFieldReadInfo(FieldInfo fi, Collection<FieldInfo> fieldReadInfos)
   {
      fieldReadInfos.add(fi);
      //If we do dynamic invokes the field will need to be accessible via reflection
      advisorStrategy.makeAccessibleField(fi);
   }

   // TODO Flavia remove this once the process is complete
   @Override
   protected void initializeConstructorChain()
   {
      // TODO remove this
      if (this.constructorInfos == null)
      {
         super.initializeConstructorChain();
      }
   }
   
   // TODO remove this once the process is complete
   @Override
   protected void initializeConstructionChain()
   {
      // TODO remove this
      if (this.constructionInfos == null)
      {
         super.initializeConstructionChain();
      }
   }
   
   @Override
   protected void initializeFieldReadChain()
   {
      this.fieldReadInfos = mergeFieldInfos(fieldReadInfos, true);
   }

   /**
    * Generated class advisor sub class will override
    */
   protected void initialiseFieldWrites(Collection<FieldInfo> fieldWriteInfos)
   {
   }

   /**
    * Called by initialiseFieldWrites() in generated advisor sub classes
    */
   protected void addFieldWriteInfo(FieldInfo fi, Collection<FieldInfo> fieldWriteInfos)
   {
      fieldWriteInfos.add(fi);
      //If we do dynamic invokes the field will need to be accessible via reflection
      advisorStrategy.makeAccessibleField(fi);
   }

   @Override
   protected void initializeFieldWriteChain()
   {
      this.fieldWriteInfos = mergeFieldInfos(fieldWriteInfos, false);
   }

   /* Creates a full list of field infos for all fields, using the ones added by
    * generated advisor for advised fields.
    */
   private FieldInfo[] mergeFieldInfos(FieldInfo[] advisedInfos, boolean read)
   {
      FieldInfo[] newInfos = new FieldInfo[advisedFields.length];

      int nextIndex = 0;
      int advisedInfosLength = advisedInfos == null? 0: advisedInfos.length;

      for (int i = 0 ; i < advisedFields.length ; i++)
      {
         if (nextIndex < advisedInfosLength && advisedInfos[nextIndex].getIndex() == i)
         {
            if (super.initialized)
            {
               advisedInfos[nextIndex].clear();
            }

            newInfos[i] = advisedInfos[nextIndex];
            nextIndex++;
         }
         else
         {
            FieldInfo info = new FieldInfo(this, read);
            info.setAdvisedField(advisedFields[i]);
            info.setIndex(i);
            newInfos[i] = info;
         }
      }

      return newInfos;
   }

   @Override
   protected void finalizeChains()
   {
      ClassAdvisor classAdvisor = getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      if (classAdvisor != null)
      {
         //We are an instance advisor who has not resolved their own pointcuts, make sure that we set the bindings that are referenced
         //so that they can be removed properly
         //Make sure that all the adviceBindings for the class advisor are referenced from us
         synchronized(this.adviceBindings)
         {
            this.adviceBindings.addAll(classAdvisor.adviceBindings);
            if (adviceBindings.size() > 0)
            {
               for (AdviceBinding binding : this.adviceBindings)
               {
                  binding.addAdvisor(this);
               }
            }
         }
      }
      
      finalizeMethodChain();
      finalizeFieldReadChain();
      finalizeFieldWriteChain();
      advisorStrategy.finalizeConstructorChain(constructorInfos);
      advisorStrategy.finalizeConstructionChain(constructionInfos);
   }

   @Override
   protected void finalizeMethodChain()
   {
      ClassAdvisor classAdvisor = getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      if (classAdvisor != null)
      {
         //We are an instance advisor with no own data influencing the chains, copy these from the parent advisor
         easyFinalizeMethodChainForInstance(classAdvisor, methodInfos);
      }
      else
      {
         //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
         fullWorkFinalizeMethodChain(methodInfos);
      }
   }

   private void easyFinalizeMethodChainForInstance(ClassAdvisor classAdvisor, MethodInterceptors newMethodInterceptors)
   {
      long[] keys = newMethodInterceptors.keys();
      for (int i = 0; i < keys.length; i++)
      {
         MethodInfo classMethodInfo = classAdvisor.getMethodInfo(keys[i]);
         MethodMatchInfo matchInfo = newMethodInterceptors.getMatchInfo(keys[i]);
         MethodInfo myMethodInfo = matchInfo.getInfo();
         myMethodInfo.cloneChains(classMethodInfo);
         
         if (updateOldInfo(oldInfos, myMethodInfo, OldInfoMaps.INFOS))
         {
            MethodJoinPointGenerator generator = getJoinPointGenerator(myMethodInfo);
            generator.rebindJoinpoint(myMethodInfo);
         }
      }
   }
   
   private void fullWorkFinalizeMethodChain(MethodInterceptors newMethodInterceptors)
   {
      //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
      TLongObjectHashMap newMethodInfos = new TLongObjectHashMap();

      long[] keys = newMethodInterceptors.keys();
      for (int i = 0; i < keys.length; i++)
      {
         MethodMatchInfo matchInfo = newMethodInterceptors.getMatchInfo(keys[i]);
         matchInfo.populateBindings();

         MethodInfo info = matchInfo.getInfo();
         newMethodInfos.put(keys[i], info);

         MethodJoinPointGenerator generator = getJoinPointGenerator(info);
         finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
      }
      if (AspectManager.maintainAdvisorMethodInterceptors)
      {
         methodInterceptors = newMethodInfos;
      }
      
      //Handle the overridden methods
      if (overriddenMethods != null && overriddenMethods.size() > 0)
      {
         for (MethodInfo info : overriddenMethods)
         {
            MethodJoinPointGenerator generator = getJoinPointGenerator(info);
            finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
         }
      }      
   }
   
   private void finalizeFieldReadChain()
   {
      ClassAdvisor classAdvisor = getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      if (classAdvisor != null)
      {
         //We are an instance advisor with no own data influencing the chains, copy these from the parent advisor
         easyFinalizeFieldChainForInstance(oldFieldReadInfos, classAdvisor.getFieldReadInfos(), fieldReadInfos, OldInfoMaps.FIELD_READ_INFOS);
      }
      else
      {
         //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
         fullWorkFinalizeFieldChain(oldFieldReadInfos, fieldReadInfos, OldInfoMaps.FIELD_READ_INFOS);
      }
   }

   protected void finalizeFieldWriteChain()
   {
      ClassAdvisor classAdvisor = getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      if (classAdvisor != null)
      {
         //We are an instance advisor with no own data influencing the chains, copy these from the parent advisor
         easyFinalizeFieldChainForInstance(oldInfos, classAdvisor.getFieldWriteInfos(), fieldWriteInfos, OldInfoMaps.INFOS);
      }
      else
      {
         //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
         fullWorkFinalizeFieldChain(oldInfos, fieldWriteInfos, OldInfoMaps.INFOS);
      }
   }

   private void easyFinalizeFieldChainForInstance(Map<Joinpoint, Interceptor[]> oldFieldInfos, FieldInfo[] classFieldInfos, FieldInfo[] newFieldInfos, OldInfoMaps oldInfoMapInstance)
   {
      //We are an instance advisor with no own data influencing the chains, copy these from the parent advisor
      for (int i = 0; i < newFieldInfos.length; i++)
      {
         FieldInfo myInfo = newFieldInfos[i];
         myInfo.cloneChains(classFieldInfos[i]);

         if (updateOldInfo(oldFieldInfos, myInfo, oldInfoMapInstance))
         {
            FieldJoinPointGenerator generator = getJoinPointGenerator(myInfo);
            generator.rebindJoinpoint(myInfo);
         }
      }
   }
   
   private void fullWorkFinalizeFieldChain(Map<Joinpoint, Interceptor[]> oldFieldInfos, FieldInfo[] newFieldInfos, OldInfoMaps oldInfoMapInstance)
   {
      //We are either the class advisor or an instanceadvisor with own data so we need to do all the work
      for (int i = 0; i < newFieldInfos.length; i++)
      {
         FieldInfo info = newFieldInfos[i];
         FieldJoinPointGenerator generator = getJoinPointGenerator(info);
         finalizeChainAndRebindJoinPoint(oldFieldInfos, info, generator, oldInfoMapInstance);
      }
   }

   @Override
   protected void finalizeMethodCalledByMethodInterceptorChain(MethodByMethodInfo info)
   {
      MethodByMethodJoinPointGenerator generator = getJoinPointGenerator(info);
      finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
   }

   @Override
   protected void finalizeConCalledByMethodInterceptorChain(ConByMethodInfo info)
   {
      ConByMethodJoinPointGenerator generator = getJoinPointGenerator(info);
      finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
   }

   @Override
   protected void finalizeConCalledByConInterceptorChain(ConByConInfo info)
   {
      ConByConJoinPointGenerator generator = getJoinPointGenerator(info);
      finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
   }

   @Override
   protected void finalizeMethodCalledByConInterceptorChain(MethodByConInfo info)
   {
      MethodByConJoinPointGenerator generator = getJoinPointGenerator(info);
      finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
   }

   private JoinPointGenerator getJoinPointGenerator(JoinPointInfo info)
   {
      if (info instanceof MethodInfo)
      {
         return getJoinPointGenerator((MethodInfo)info);
      }
      else if (info instanceof FieldInfo)
      {
         return getJoinPointGenerator((FieldInfo)info);
      }
      else if (info instanceof ConstructionInfo)
      {
         return getJoinPointGenerator((ConstructionInfo)info);
      }
      else if (info instanceof ConstructorInfo)
      {
         return getJoinPointGenerator((ConstructorInfo)info);
      }
      else if (info instanceof ConByConInfo)
      {
         return getJoinPointGenerator((ConByConInfo)info);
      }
      else if (info instanceof ConByMethodInfo)
      {
         return getJoinPointGenerator((ConByMethodInfo)info);
      }
      else if (info instanceof MethodByMethodInfo)
      {
         return getJoinPointGenerator((MethodByMethodInfo)info);
      }
      else if (info instanceof MethodByConInfo)
      {
         return getJoinPointGenerator((MethodByConInfo)info);
      }
      else
      {
         throw new RuntimeException("Invalid JoinPointInfo passed in: " + info.getClass().getName());
      }
   }

   protected MethodJoinPointGenerator getJoinPointGenerator(MethodInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   protected FieldJoinPointGenerator getJoinPointGenerator(FieldInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }
   
   protected ConstructorJoinPointGenerator getJoinPointGenerator(ConstructorInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   protected ConstructionJoinPointGenerator getJoinPointGenerator(ConstructionInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   protected MethodByMethodJoinPointGenerator getJoinPointGenerator(MethodByMethodInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   protected ConByMethodJoinPointGenerator getJoinPointGenerator(ConByMethodInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   protected ConByConJoinPointGenerator getJoinPointGenerator(ConByConInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   protected MethodByConJoinPointGenerator getJoinPointGenerator(MethodByConInfo info)
   {
      return advisorStrategy.getJoinPointGenerator(info);
   }

   /**
    * Override default behaviour of when a pointcut is matched, populate the factories since this
    * is what is needed for generating the optimized invocation method
    */
   @Override
   protected void pointcutResolved(JoinPointInfo info, AdviceBinding binding, Joinpoint joinpoint)
   {
      ArrayList<Interceptor> curr = info.getInterceptorChain();
      if (binding.getCFlow() != null)
      {
         InterceptorFactory[] factories = binding.getInterceptorFactories();
         for (int i = 0 ; i < factories.length ; i++)
         {
            curr.add(new GeneratedAdvisorInterceptor(factories[i], this, joinpoint, binding.getCFlowString(), binding.getCFlow()));
         }
      }
      else
      {
         InterceptorFactory[] factories = binding.getInterceptorFactories();
         for (int i = 0 ; i < factories.length ; i++)
         {
            curr.add(new GeneratedAdvisorInterceptor(factories[i], this, joinpoint));
         }
      }
   }

   private void finalizeChainAndRebindJoinPoint(Map<Joinpoint, Interceptor[]> oldInfos, JoinPointInfo info, JoinPointGenerator generator, OldInfoMaps oldInfoMapInstance)
   {
      adjustInfoForAddedBinding(info);
      List<Interceptor> list = info.getInterceptorChain();
      GeneratedAdvisorInterceptor[] factories = null;
      if (list.size() > 0)
      {
         factories = applyPrecedence(list.toArray(new GeneratedAdvisorInterceptor[list.size()]));
      }
      info.setInterceptors(factories);

      if (updateOldInfo(oldInfos, info, oldInfoMapInstance))
      {
         generator.rebindJoinpoint(info);
      }
   }

   @Override
   public String toString()
   {
      Class<?> clazz = this.getClass();
      StringBuffer sb = new StringBuffer("CLASS: " + clazz.getName());

      Field[] fields = clazz.getFields();
      for (int i = 0 ; i < fields.length ; i++)
      {
         sb.append("\n\t" + fields[i]);
      }
      return sb.toString();
   }

   GeneratedAdvisorInterceptor[] applyPrecedence(GeneratedAdvisorInterceptor[] interceptors)
   {
      return PrecedenceSorter.applyPrecedence(interceptors, manager);
   }

   /**
    * If this is an instance advisor, will check with parent class advisor if the aspect
    * is already registered. If so, we should use the one from the parent advisor
    */
   @Override
   public Object getPerClassAspect(AspectDefinition def)
   {
      return advisorStrategy.getPerClassAspect(def);
   }

   /**
    * Generated ClassAdvisors and InstanceAdvisors will be different instances,
    * so keep track of what per_class_joinpoint aspects have been added where
    */
   ConcurrentHashMap<AspectDefinition, Map<Joinpoint, Object>> perClassJoinpointAspectDefinitions =
         new ConcurrentHashMap<AspectDefinition, Map<Joinpoint, Object>>();


   public Object getPerClassJoinpointAspect(AspectDefinition def, Joinpoint joinpoint)
   {
      return advisorStrategy.getPerClassJoinpointAspect(def, joinpoint);
   }

   public synchronized void addPerClassJoinpointAspect(AspectDefinition def, Joinpoint joinpoint)
   {
      Map<Joinpoint, Object> joinpoints = perClassJoinpointAspectDefinitions.get(def);
      if (joinpoints == null)
      {
         joinpoints = new ConcurrentHashMap<Joinpoint, Object>();
         perClassJoinpointAspectDefinitions.put(def, joinpoints);
      }

      if (!joinpoints.containsKey(joinpoint))
      {
         Object aspect = def.getFactory().createPerJoinpoint(this, joinpoint);
         if (aspect == null)
         {
            joinpoints.put(joinpoint, NULL_ASPECT);
         }
         else
         {
            joinpoints.put(joinpoint, aspect);
         }
      }
      def.registerAdvisor(this);
   }

   public synchronized void removePerClassJoinpointAspect(AspectDefinition def)
   {
      perClassJoinpointAspectDefinitions.remove(def);
   }
   
   /**
    * @see Advisor#chainOverridingForInheritedMethods()
    */
   @Override
   public boolean chainOverridingForInheritedMethods()
   {
      return true;
   }

   @Override
   public Object getFieldAspect(FieldJoinpoint joinpoint, AspectDefinition def)
   {
      Object instance = getPerClassJoinpointAspect(def, joinpoint);
      if (instance == null)
      {
         addPerClassJoinpointAspect(def, joinpoint);
         instance = getPerClassJoinpointAspect(def, joinpoint);
      }
      return instance;
   }

   private GeneratedClassAdvisor getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices()
   {
      return advisorStrategy.getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
   }
   
   /**
    * Optimization so that when we create instance advisors we don't have to create the method tables again,
    * they were already created for the class advisor 
    */
   @Override
   protected void createMethodTables() throws Exception
   {
      advisorStrategy.createMethodTables();
   }
   
   /**
    * Optimization so that when we create instance advisors we don't have to create the field tables again,
    * they were already created for the class advisor 
    */
   @Override
   protected void createFieldTable() throws Exception
   {
      advisorStrategy.createFieldTable();
   }
   
   /**
    * Optimization so that when we create instance advisors we don't have to create the constructor tables again,
    * they were already created for the class advisor 
    */
   @Override
   protected void createConstructorTables() throws Exception
   {
      advisorStrategy.createConstructorTables();
   }

   @Override
   public Set<AspectDefinition> getPerInstanceAspectDefinitions()
   {
      return advisorStrategy.getPerInstanceAspectDefinitions();
   }

   @Override
   public Map<AspectDefinition, Set<Joinpoint>> getPerInstanceJoinpointAspectDefinitions()
   {
      return advisorStrategy.getPerInstanceJoinpointAspectDefinitions();
   }

   /**
    * Caches the old info and checks if the chains have been updated
    */
   private boolean updateOldInfo(Map<Joinpoint, Interceptor[]> oldInfos, JoinPointInfo newInfo, OldInfoMaps oldInfoMapInstance)
   {
      Interceptor[] oldChain = oldInfos.get(newInfo.getJoinpoint());
      if (oldChain != null)
      {
         //We are not changing any of the bindings
         if (newInfo.equalChains(oldChain))
         {
            return false;
         }
      }
      Interceptor[] currentOldChain = newInfo.getInterceptors();
      
      if (oldInfoMapInstance == OldInfoMaps.INFOS) 
      {
         oldInfos = initOldInfosMap();
      }
      else if (oldInfoMapInstance == OldInfoMaps.FIELD_READ_INFOS)
      {
         oldInfos = initOldFieldReadInfosMap();
      }
      else if (oldInfoMapInstance == OldInfoMaps.CONSTRUCTION_INFOS)
      {
         oldInfos = initOldConstructionInfosMap();
      }
      else
      {
         throw new RuntimeException("Unrecognised map");
      }
      if (currentOldChain != null)
      {
         oldInfos.put(newInfo.getJoinpoint(), currentOldChain);
      }
      else if (oldChain != null)
      {
         oldInfos.remove(newInfo.getJoinpoint());
      }
      return true;
   }

   protected void generateJoinPointClass(MethodInfo info)
   {
      MethodJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(FieldInfo info)
   {
      FieldJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(ConstructorInfo info)
   {
      ConstructorJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(ConstructionInfo info)
   {
      ConstructionJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(MethodByMethodInfo info)
   {
      MethodByMethodJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(ConByMethodInfo info)
   {
      ConByMethodJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(ConByConInfo info)
   {
      ConByConJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }

   protected void generateJoinPointClass(MethodByConInfo info)
   {
      MethodByConJoinPointGenerator generator = getJoinPointGenerator(info);
      generator.generateJoinPointClass(getClassLoader(), info);
   }
   
   protected Object rebindJoinPointWithInstanceInformation(JoinPointInfo info)
   {
      info.getInterceptorChainReadWriteLock().readLock().lock();
      try
      {
         JoinPointGenerator generator = getJoinPointGenerator(info);
         generator.rebindJoinpoint(info);
         return generator.generateJoinPointClass(getClassLoader(), info);
      }
      finally
      {
         info.getInterceptorChainReadWriteLock().readLock().unlock();
      }
   }
   
   /**
    * Called back from generated code
    */
   public Object createAndRebindJoinPointForInstance(JoinPointInfo info)
   {
      JoinPointInfo newinfo = info.copy();
      newinfo.setAdvisor(this);
      return rebindJoinPointWithInstanceInformation(newinfo);
   }
   
   
   protected void initJoinPointGeneratorsMap()
   {
      if (joinPointGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (joinPointGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               joinPointGenerators = new ConcurrentHashMap<Joinpoint, JoinPointGenerator>();
            }
         }
      }
   }
   
   protected void initFieldReadJoinPointGeneratorsMap()
   {
      if (fieldReadJoinPoinGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (fieldReadJoinPoinGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               fieldReadJoinPoinGenerators = new ConcurrentHashMap<Joinpoint, FieldJoinPointGenerator>();
            }
         }
      }
   }
   
   protected void initConstructionJoinPointGeneratorsMap()
   {
      if (constructionJoinPointGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (constructionJoinPointGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               constructionJoinPointGenerators = new ConcurrentHashMap<Joinpoint, ConstructorJoinPointGenerator>();
            }
         }
      }
   }
   
   protected void initMethodCalledByConJoinPointGeneratorsMap()
   {
      if (methodCalledByXXXJoinPointGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (methodCalledByXXXJoinPointGenerators == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               methodCalledByXXXJoinPointGenerators = new ConcurrentHashMap<Joinpoint, ConcurrentHashMap<Class<?>, JoinPointGenerator>>();
            }
         }
      }
   }
   
   protected ConcurrentHashMap<Joinpoint, Interceptor[]> initOldInfosMap()
   {
      if (oldInfos == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (oldInfos == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               oldInfos = new ConcurrentHashMap<Joinpoint, Interceptor[]>();
            }
         }
      }
      return oldInfos;
   }
   
   protected ConcurrentHashMap<Joinpoint, Interceptor[]> initOldFieldReadInfosMap()
   {
      if (oldFieldReadInfos == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (oldFieldReadInfos == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               oldFieldReadInfos = new ConcurrentHashMap<Joinpoint, Interceptor[]>();
            }
         }
      }
      return oldFieldReadInfos;
   }
   
   protected ConcurrentHashMap<Joinpoint, Interceptor[]> initOldConstructionInfosMap()
   {
      if (oldConstructionInfos == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
      {
         synchronized(lazyCollectionLock)
         {
            if (oldConstructionInfos == UnmodifiableEmptyCollections.EMPTY_CONCURRENT_HASHMAP)
            {
               oldConstructionInfos = new ConcurrentHashMap<Joinpoint, Interceptor[]>();
            }
         }
      }
      return oldConstructionInfos;
   }
   
   /**
    * Encapsulates different behaviours depending on if this is an instance or class advisor
    */
   private interface AdvisorStrategy
   {
      void initialise(Class<?> clazz, AspectManager manager);
      void checkVersion();
      void createInterceptorChains() throws Exception;
      MethodJoinPointGenerator getJoinPointGenerator(MethodInfo info);
      FieldJoinPointGenerator getJoinPointGenerator(FieldInfo info);
      ConstructorJoinPointGenerator getJoinPointGenerator(ConstructorInfo info);
      ConstructionJoinPointGenerator getJoinPointGenerator(ConstructionInfo info);
      MethodByMethodJoinPointGenerator getJoinPointGenerator(MethodByMethodInfo info);
      ConByMethodJoinPointGenerator getJoinPointGenerator(ConByMethodInfo info);
      ConByConJoinPointGenerator getJoinPointGenerator(ConByConInfo info);
      MethodByConJoinPointGenerator getJoinPointGenerator(MethodByConInfo info);
      Object getPerClassAspect(AspectDefinition def);
      Object getPerClassJoinpointAspect(AspectDefinition def, Joinpoint joinpoint);
      GeneratedClassAdvisor getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices();
      void createMethodTables() throws Exception;
      void createFieldTable() throws Exception;
      void createConstructorTables() throws Exception;
      Set<AspectDefinition> getPerInstanceAspectDefinitions();
      Map<AspectDefinition, Set<Joinpoint>> getPerInstanceJoinpointAspectDefinitions();
      void rebuildInterceptors();
      void rebuildInterceptorsForAddedBinding(AdviceBinding binding);
      void rebuildInterceptorsForRemovedBinding(AdviceBinding binding);
      void resolveConstructorPointcut(AdviceBinding binding);
      void resolveConstructionPointcut(AdviceBinding binding);
      void finalizeConstructorChain(ConstructorInfo[] newConstructorInfos);
      void finalizeConstructionChain(ConstructionInfo[] newConstructionInfos);
      void makeAccessibleField(FieldInfo fi);
      void makeAccessibleMethod(MethodInfo mi);
   }
   
   private class ClassAdvisorStrategy implements AdvisorStrategy
   {
      GeneratedClassAdvisor parent;

      public void initialise(Class<?> clazz, AspectManager manager)
      {
         methodInfos = new MethodInterceptors(GeneratedClassAdvisor.this);
         initialiseMethods();
         // initialise constructor info array
         Collection<ConstructorInfo> constructorInfoCol = new ArrayList<ConstructorInfo>();
         initialiseConstructors(constructorInfoCol);
         constructorInfos = constructorInfoCol.toArray(new ConstructorInfo[constructorInfoCol.size()]);
         // initialise construction info array
         Collection<ConstructionInfo> constructionInfoCol = new ArrayList<ConstructionInfo>();
         initialiseConstructions(constructionInfoCol);
         constructionInfos = constructionInfoCol.toArray(new ConstructionInfo[constructionInfoCol.size()]);
         // initalise field read info array
         Collection<FieldInfo> fieldReadInfoCol = new ArrayList<FieldInfo>();
         initialiseFieldReads(fieldReadInfoCol);
         fieldReadInfos = fieldReadInfoCol.toArray(new FieldInfo[fieldReadInfoCol.size()]);
         // initalise field write info array
         Collection<FieldInfo> fieldWriteInfoCol = new ArrayList<FieldInfo>();
         initialiseFieldWrites(fieldWriteInfoCol);
         fieldWriteInfos = fieldWriteInfoCol.toArray(new FieldInfo[fieldWriteInfoCol.size()]);
         
         GeneratedClassAdvisor.super.setManager(manager);

         //Make sure that we copy across per class and per joinpoint aspects from the old advisor if it exists
         //Generated advisors get created when the class is first accessed (not loaded), meaning that there could be an exisiting advisor
         //used for mathcing already when setting up the microcontainer.
         Advisor existing = AspectManager.instance().getAnyAdvisorIfAdvised(clazz);
         if (existing != null)
         {
            GeneratedClassAdvisor.this.aspects = existing.aspects;
            if (existing instanceof GeneratedClassAdvisor)
            {
               GeneratedClassAdvisor.this.perClassJoinpointAspectDefinitions = ((GeneratedClassAdvisor)existing).perClassJoinpointAspectDefinitions;
            }
         }
         
         manager.initialiseClassAdvisor(clazz, GeneratedClassAdvisor.this);
         
         initialiseCallers();
      }

      public void checkVersion()
      {
         //The version is only has any significance for instance advisors
      }

      public void createInterceptorChains() throws Exception
      {
         GeneratedClassAdvisor.super.createInterceptorChains();
      }
      
      public MethodJoinPointGenerator getJoinPointGenerator(MethodInfo info)
      {
         MethodJoinPointGenerator generator = (MethodJoinPointGenerator)joinPointGenerators.get(info.getJoinpoint());
         if (generator == null)
         {
            generator = new MethodJoinPointGenerator(GeneratedClassAdvisor.this, info);
            initJoinPointGeneratorsMap();
            MethodJoinPointGenerator existing = (MethodJoinPointGenerator)joinPointGenerators.putIfAbsent(info.getJoinpoint(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public FieldJoinPointGenerator getJoinPointGenerator(FieldInfo info)
      {
         if (info.isRead())
         {
            FieldJoinPointGenerator generator = fieldReadJoinPoinGenerators.get(info.getJoinpoint());
            if (generator == null)
            {
               generator = new FieldJoinPointGenerator(GeneratedClassAdvisor.this, info);
               initFieldReadJoinPointGeneratorsMap();
               FieldJoinPointGenerator existing = fieldReadJoinPoinGenerators.putIfAbsent(info.getJoinpoint(), generator);
               if (existing != null)
               {
                  generator = existing;
               }
            }
            return generator;
         }
         else
         {
            FieldJoinPointGenerator generator = (FieldJoinPointGenerator)joinPointGenerators.get(info.getJoinpoint());
            if (generator == null)
            {
               generator = new FieldJoinPointGenerator(GeneratedClassAdvisor.this, info);
               initJoinPointGeneratorsMap();
               FieldJoinPointGenerator existing = (FieldJoinPointGenerator)joinPointGenerators.putIfAbsent(info.getJoinpoint(), generator);
               if (existing != null)
               {
                  generator = existing;
               }
            }
            return generator;
         }
      }
      
      public ConstructorJoinPointGenerator getJoinPointGenerator(ConstructorInfo info)
      {
         //We are the class advisor
         ConstructorJoinPointGenerator generator = constructionJoinPointGenerators.get(info.getJoinpoint());
         if (generator == null)
         {
            generator = new ConstructorJoinPointGenerator(GeneratedClassAdvisor.this, info);
            initConstructionJoinPointGeneratorsMap();
            ConstructorJoinPointGenerator existing = constructionJoinPointGenerators.putIfAbsent(info.getJoinpoint(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public ConstructionJoinPointGenerator getJoinPointGenerator(ConstructionInfo info)
      {
         ConstructionJoinPointGenerator generator = (ConstructionJoinPointGenerator)joinPointGenerators.get(info.getJoinpoint());
         if (generator == null)
         {
            generator = new ConstructionJoinPointGenerator(GeneratedClassAdvisor.this, info);
            initJoinPointGeneratorsMap();
            ConstructionJoinPointGenerator existing = (ConstructionJoinPointGenerator)joinPointGenerators.putIfAbsent(info.getJoinpoint(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public MethodByMethodJoinPointGenerator getJoinPointGenerator(MethodByMethodInfo info)
      {
         //An extra level of indirection since we distinguish between callers of method depending on
         //where the called method is defined (sub/super interfaces)
         ConcurrentHashMap<Class<?>, JoinPointGenerator> map = methodCalledByXXXJoinPointGenerators.get(info.getJoinpoint());
         if (map == null)
         {
            map = new ConcurrentHashMap<Class<?>, JoinPointGenerator>();
            initMethodCalledByConJoinPointGeneratorsMap();
            ConcurrentHashMap<Class<?>, JoinPointGenerator> existing = methodCalledByXXXJoinPointGenerators.putIfAbsent(info.getJoinpoint(), map);
            if (existing != null)
            {
               map = existing;
            }
         }

         MethodByMethodJoinPointGenerator generator = (MethodByMethodJoinPointGenerator)map.get(info.getCalledClass());
         if (generator == null)
         {
            generator = new MethodByMethodJoinPointGenerator(GeneratedClassAdvisor.this, info);
            MethodByMethodJoinPointGenerator existing = (MethodByMethodJoinPointGenerator)map.putIfAbsent(info.getCalledClass(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public ConByMethodJoinPointGenerator getJoinPointGenerator(ConByMethodInfo info)
      {
         ConByMethodJoinPointGenerator generator = (ConByMethodJoinPointGenerator)joinPointGenerators.get(info.getJoinpoint());
         if (generator == null)
         {
            generator = new ConByMethodJoinPointGenerator(GeneratedClassAdvisor.this, info);
            initJoinPointGeneratorsMap();
            ConByMethodJoinPointGenerator existing = (ConByMethodJoinPointGenerator)joinPointGenerators.putIfAbsent(info.getJoinpoint(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public ConByConJoinPointGenerator getJoinPointGenerator(ConByConInfo info)
      {
         //We are the class advisor
         ConByConJoinPointGenerator generator = (ConByConJoinPointGenerator)joinPointGenerators.get(info.getJoinpoint());
         if (generator == null)
         {
            generator = new ConByConJoinPointGenerator(GeneratedClassAdvisor.this, info);
            initJoinPointGeneratorsMap();
            ConByConJoinPointGenerator existing = (ConByConJoinPointGenerator)joinPointGenerators.putIfAbsent(info.getJoinpoint(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public MethodByConJoinPointGenerator getJoinPointGenerator(MethodByConInfo info)
      {
         //An extra level of indirection since we distinguish between callers of method depending on
         //where the called method is defined (sub/super interfaces)
         ConcurrentHashMap<Class<?>, JoinPointGenerator> map = methodCalledByXXXJoinPointGenerators.get(info.getJoinpoint());
         if (map == null)
         {
            map = new ConcurrentHashMap<Class<?>, JoinPointGenerator>();
            initMethodCalledByConJoinPointGeneratorsMap();
            ConcurrentHashMap<Class<?>, JoinPointGenerator> exisiting = methodCalledByXXXJoinPointGenerators.putIfAbsent(info.getJoinpoint(), map);
            if (exisiting != null)
            {
               map = exisiting;
            }
         }

         MethodByConJoinPointGenerator generator = (MethodByConJoinPointGenerator)map.get(info.getCalledClass());
         if (generator == null)
         {
            generator = new MethodByConJoinPointGenerator(GeneratedClassAdvisor.this, info);
            MethodByConJoinPointGenerator existing = (MethodByConJoinPointGenerator)map.putIfAbsent(info.getCalledClass(), generator);
            if (existing != null)
            {
               generator = existing;
            }
         }
         return generator;
      }

      public Object getPerClassAspect(AspectDefinition def)
      {
         return GeneratedClassAdvisor.super.getPerClassAspect(def);
      }

      public Object getPerClassJoinpointAspect(AspectDefinition def, Joinpoint joinpoint)
      {
         Map<Joinpoint, Object> joinpoints = perClassJoinpointAspectDefinitions.get(def);
         if (joinpoints != null)
         {
            Object aspect = joinpoints.get(joinpoint);
            if (aspect != NULL_ASPECT)
            {
               return aspect;
            }
         }
         return null;
      }
      
      public GeneratedClassAdvisor getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices()
      {
         return null;
      }
      
      public void createMethodTables() throws Exception
      {
         GeneratedClassAdvisor.super.createMethodTables();
      }
      
      public void createFieldTable() throws Exception
      {
         GeneratedClassAdvisor.super.createFieldTable();
      }

      public void createConstructorTables() throws Exception
      {
         GeneratedClassAdvisor.super.createConstructorTables();
      }
      
      public Set<AspectDefinition> getPerInstanceAspectDefinitions()
      {
         return GeneratedClassAdvisor.super.getPerInstanceAspectDefinitions();
      }

      public Map<AspectDefinition, Set<Joinpoint>> getPerInstanceJoinpointAspectDefinitions()
      {
         return GeneratedClassAdvisor.super.getPerInstanceJoinpointAspectDefinitions();
      }
      
      public void rebuildInterceptors()
      {
         version++;
         GeneratedClassAdvisor.super.rebuildInterceptors();
      }
      
      public void rebuildInterceptorsForAddedBinding(AdviceBinding binding)
      {
         version++;
         GeneratedClassAdvisor.super.rebuildInterceptorsForAddedBinding(binding);
      }
      
      public void rebuildInterceptorsForRemovedBinding(AdviceBinding binding)
      {
         version++;
         GeneratedClassAdvisor.super.rebuildInterceptorsForRemovedBinding(binding);
      }

      public void resolveConstructorPointcut(AdviceBinding binding)
      {
         GeneratedClassAdvisor.super.resolveConstructorPointcut(binding);
      }

      public void resolveConstructionPointcut(AdviceBinding binding)
      {
         GeneratedClassAdvisor.super.resolveConstructionPointcut(binding);
      }

      public void finalizeConstructorChain(ConstructorInfo[] newConstructorInfos)
      {
         for (int i = 0; i < newConstructorInfos.length; i++)
         {
            ConstructorInfo info = newConstructorInfos[i];
            ConstructorJoinPointGenerator generator = getJoinPointGenerator(info);
            Class<?> clazz = info.getClazz();
            if (clazz != null)
            finalizeChainAndRebindJoinPoint(oldInfos, info, generator, OldInfoMaps.INFOS);
         }
      }

      public void finalizeConstructionChain(ConstructionInfo[] newConstructionInfos)
      {
         for (int i = 0; i < newConstructionInfos.length; i++)
         {
            ConstructionInfo info = newConstructionInfos[i];
            ConstructionJoinPointGenerator generator = getJoinPointGenerator(info);
            finalizeChainAndRebindJoinPoint(oldConstructionInfos, info, generator, OldInfoMaps.CONSTRUCTION_INFOS);
         }
      }

      public void makeAccessibleField(FieldInfo fi)
      {
         //If we do dynamic invokes the field will need to be accessible via reflection
         SecurityActions.setAccessible(fi.getField());
      }
      
      public void makeAccessibleMethod(MethodInfo mi)
      {
         //If we do dynamic invokes the method will need to be accessible via reflection if private/protected
         SecurityActions.setAccessible(mi.getMethod());
      }
   }
   
   private class InstanceAdvisorStrategy implements AdvisorStrategy 
   {
      GeneratedClassAdvisor parent;
      boolean needsRebuild = true;
      
      public InstanceAdvisorStrategy(GeneratedClassAdvisor parent)
      {
         this.parent = parent;
         GeneratedClassAdvisor.this.version = parent.version;         
      }
      
      public void initialise(Class<?> clazz, AspectManager manager)
      {
         initialiseInfosForInstance();
         
         GeneratedClassAdvisor.super.setManager(manager);

         manager.initialiseClassAdvisor(clazz, GeneratedClassAdvisor.this);
      }

      public void checkVersion()
      {
         if (needsRebuild || parent.version != GeneratedClassAdvisor.this.version)
         {
            doRebuildForInstance();
            needsRebuild = false;
         }
      }
      
      public void createInterceptorChains() throws Exception
      {
         if (GeneratedClassAdvisor.super.initialized)
         {
            GeneratedClassAdvisor.super.createInterceptorChains();
         }
         else
         {
            //Instance advisor copies the chains from the class advisor during its initialise stage 
            GeneratedClassAdvisor.super.initialized = true;
         }
      }

      public MethodJoinPointGenerator getJoinPointGenerator(MethodInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      public FieldJoinPointGenerator getJoinPointGenerator(FieldInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }
      
      public ConstructorJoinPointGenerator getJoinPointGenerator(ConstructorInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      public ConstructionJoinPointGenerator getJoinPointGenerator(ConstructionInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      public MethodByMethodJoinPointGenerator getJoinPointGenerator(MethodByMethodInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      public ConByMethodJoinPointGenerator getJoinPointGenerator(ConByMethodInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      public ConByConJoinPointGenerator getJoinPointGenerator(ConByConInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      public MethodByConJoinPointGenerator getJoinPointGenerator(MethodByConInfo info)
      {
         //We are an instance advisor, get the generator from the class advisor
         return parent.getJoinPointGenerator(info);
      }

      /**
       * This is an instance advisor, so we will check with parent class advisor if the aspect
       * is already registered. If so, we should use the one from the parent advisor
       */
      public Object getPerClassAspect(AspectDefinition def)
      {
         Object aspect = parent.getPerClassAspect(def);
         if (aspect != null) return aspect;
         return GeneratedClassAdvisor.super.getPerClassAspect(def);
      }

      /**
       * This is an instance advisor, so we will check with parent class advisor if the aspect
       * is already registered. If so, we should use the one from the parent advisor
       */
      public Object getPerClassJoinpointAspect(AspectDefinition def, Joinpoint joinpoint)
      {
         Object aspect = parent.getPerClassJoinpointAspect(def, joinpoint);
         if (aspect != null)return aspect;
         return parent.getPerClassJoinpointAspect(def, joinpoint);
      }

      public GeneratedClassAdvisor getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices()
      {
         if (((Domain)getManager()).hasOwnDataWithEffectOnAdvices())
         {
            return null;
         }
         return parent;
      }
      
      public void createMethodTables() throws Exception
      {
         GeneratedClassAdvisor.this.unadvisedMethods = parent.unadvisedMethods;
         GeneratedClassAdvisor.this.advisedMethods = parent.advisedMethods;
      }

      public void createFieldTable() throws Exception
      {
         GeneratedClassAdvisor.this.advisedFields = parent.advisedFields;
      }

      public void createConstructorTables() throws Exception
      {
         GeneratedClassAdvisor.this.constructors = parent.constructors;
         
         methodCalledByConBindings = new HashMap[constructors.length];
         methodCalledByConInterceptors = new HashMap[constructors.length];

         conCalledByConBindings = new HashMap[constructors.length];
         conCalledByConInterceptors = new HashMap[constructors.length];
      }

      public Set<AspectDefinition> getPerInstanceAspectDefinitions()
      {
         return parent.getPerInstanceAspectDefinitions();
      }

      public Map<AspectDefinition, Set<Joinpoint>> getPerInstanceJoinpointAspectDefinitions()
      {
         return parent.getPerInstanceJoinpointAspectDefinitions();
      }

      
      public synchronized void rebuildInterceptors()
      {
         if (getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices() != null && GeneratedClassAdvisor.this.version != parent.version)
         {
            adviceBindings.clear();
            needsRebuild = true;
         }
         else
         {
            // check if it is initialized
            if (GeneratedClassAdvisor.this.fieldReadInfos == null)
            {
               try
               {
                  GeneratedClassAdvisor.this.createInterceptorChains();
               }
               catch (Exception ex)
               {
                  if (ex instanceof RuntimeException)
                  {
                     throw (RuntimeException) ex;
                  }
                  throw new RuntimeException(ex);
               }
            }
            else
            {
               GeneratedClassAdvisor.super.rebuildInterceptors();
            }
         }
      }

      public void rebuildInterceptorsForAddedBinding(AdviceBinding binding)
      {
         if (getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices() != null && GeneratedClassAdvisor.this.version != parent.version)
         {
            adviceBindings.clear();
            needsRebuild = true;
         }
         else
         {
            // check if it is initialized
            if (!GeneratedClassAdvisor.this.initialized)
            {
               try
               {
                  GeneratedClassAdvisor.this.createInterceptorChains();
               }
               catch (Exception ex)
               {
                  if (ex instanceof RuntimeException)
                  {
                     throw (RuntimeException) ex;
                  }
                  throw new RuntimeException(ex);
               }
            }
            else
            {
               GeneratedClassAdvisor.super.rebuildInterceptorsForAddedBinding(binding);
            }
         }
      }
      
      public void rebuildInterceptorsForRemovedBinding(AdviceBinding binding)
      {
         if (getClassAdvisorIfInstanceAdvisorWithNoOwnDataWithEffectOnAdvices() != null && GeneratedClassAdvisor.this.version != parent.version)
         {
            adviceBindings.clear();
            needsRebuild = true;
         }
         else
         {
            // check if it is initialized
            if (!GeneratedClassAdvisor.this.initialized)
            {
               try
               {
                  GeneratedClassAdvisor.this.createInterceptorChains();
               }
               catch (Exception ex)
               {
                  if (ex instanceof RuntimeException)
                  {
                     throw (RuntimeException) ex;
                  }
                  throw new RuntimeException(ex);
               }
            }
            else
            {
               GeneratedClassAdvisor.super.rebuildInterceptorsForRemovedBinding(binding);
            }
         }
      }


      public void resolveConstructorPointcut(AdviceBinding binding)
      {
         //Since the instance already exists it makes no sense to have bindings for constructors
      }

      public void resolveConstructionPointcut(AdviceBinding binding)
      {
         //Since the instance already exists it makes no sense to have bindings for constructors         
      }

      public void finalizeConstructorChain(ConstructorInfo[] newConstructorInfos)
      {
         //Since the instance already exists it makes no sense to have bindings for constructors
      }

      public void finalizeConstructionChain(ConstructionInfo[] newConstructionInfos)
      {
         //Since the instance already exists it makes no sense to have bindings for constructors
      }

      public void makeAccessibleField(FieldInfo fi)
      {
         //Do nothing, field was already made accessible in class advisor
      }

      public void makeAccessibleMethod(MethodInfo mi)
      {
         //Do nothing, field was already made accessible in class advisor
      }
   }
   
   enum OldInfoMaps{
      INFOS, FIELD_READ_INFOS, CONSTRUCTION_INFOS;
   }

}
