/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.config.inject;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.decorator.Delegate;
import javax.ejb.Stateful;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.AmbiguousResolutionException;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.IllegalProductException;
import javax.enterprise.inject.InjectionException;
import javax.enterprise.inject.UnsatisfiedResolutionException;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
import javax.inject.Qualifier;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import com.caucho.config.ConfigException;
import com.caucho.config.SerializeHandle;
import com.caucho.config.annotation.NoAspect;
import com.caucho.config.bytecode.SerializationAdapter;
import com.caucho.config.gen.CandiBeanGenerator;
import com.caucho.config.inject.InjectManager.ReferenceFactory;
import com.caucho.config.j2ee.PostConstructProgram;
import com.caucho.config.j2ee.PreDestroyInject;
import com.caucho.config.program.Arg;
import com.caucho.config.program.BeanArg;
import com.caucho.config.program.ConfigProgram;
import com.caucho.config.program.ResourceProgramManager;
import com.caucho.config.reflect.AnnotatedConstructorImpl;
import com.caucho.config.reflect.BaseType;
import com.caucho.config.reflect.BaseTypeAnnotated;
import com.caucho.config.reflect.ReflectionAnnotated;
import com.caucho.config.reflect.ReflectionAnnotatedFactory;
import com.caucho.config.reflect.ReflectionAnnotatedType;
import com.caucho.inject.Module;
import com.caucho.util.L10N;

/**
 * SimpleBean represents a POJO Java bean registered as a WebBean.
 */
@Module
public class InjectionTargetBuilder<X> implements InjectionTarget<X>
{
  private static final L10N L = new L10N(InjectionTargetBuilder.class);
  private static final Logger log
    = Logger.getLogger(InjectionTargetBuilder.class.getName());

  private InjectManager _cdiManager;

  private Class<X> _instanceClass;
  
  private Bean<X> _bean;

  private final AnnotatedType<X> _annotatedType;
  
  private Class<X> _rawClass;

  private AnnotatedConstructor<X> _beanCtor;
  
  private CandiProducer<X> _producer;
  
  private Constructor<X> _javaCtor;
  
  private boolean _isGenerateInterception = true;

  private ConfigProgram []_newArgs;

  private Set<InjectionPoint> _injectionPointSet;
  
  private boolean _isBound;
  
  public InjectionTargetBuilder(InjectManager cdiManager,
                                AnnotatedType<X> beanType,
                                Bean<X> bean)
  {
    _cdiManager = cdiManager;

    _annotatedType = beanType;
    _bean = bean;
    
    Type type = _annotatedType.getBaseType();
    
    // ioc/2601
    BaseType baseType = getBeanManager().createSourceBaseType(type);
    
    _rawClass= (Class<X>) baseType.getRawClass();
    
    
    introspectInjectClass(_annotatedType);
  }
  
  public InjectionTargetBuilder(InjectManager cdiManager,
                                AnnotatedType<X> beanType)
  {
    this(cdiManager, beanType, null);
  }

  protected InjectManager getBeanManager()
  {
    return _cdiManager;
  }
  
  public AnnotatedType<X> getAnnotatedType()
  {
    return _annotatedType;
  }
  
  void setBean(Bean<X> bean)
  {
    _bean = bean;
  }
  
  Bean<X> getBean()
  {
    return _bean;
  }

  /**
   * Returns the injection points.
   */
  @Override
  public Set<InjectionPoint> getInjectionPoints()
  {
    if (_producer == null) {
      if (_cdiManager.isClosed()) {
        return new HashSet();
      }
      _producer = build();
      
      // bind();
      // validate(getBean());
    }
    
    return _producer.getInjectionPoints();
  }
  
  public void validate()
  {
    bind();
    validate(getBean());
  }
  
  public void bind()
  {
    try {
      if (! _isBound) {
        if (_producer == null)
          _producer = build();

        _producer.bind();
      }
    } finally {
      _isBound = true;
    }
  }

  public void setGenerateInterception(boolean isEnable)
  {
    _isGenerateInterception = isEnable;
  }

  @Override
  public X produce(CreationalContext<X> env)
  {
    if (_producer == null) {
      getInjectionPoints();
      bind();
    }
    
    return _producer.produce(env);
  }

  @Override
  public void inject(X instance, CreationalContext<X> env)
  {
    if (! _isBound)
      bind();
    
    _producer.inject(instance, env);
  }

  @Override
  public void postConstruct(X instance)
  {
    if (! _isBound)
      bind();
    
    _producer.postConstruct(instance);
  }
  
  public ConfigProgram []getPostConstructProgram()
  {
    if (_producer == null)
      getInjectionPoints();
    
    return _producer.getPostConstructProgram();
  }
  
  public void setPostConstructProgram(ConfigProgram []program)
  {
    _producer.setPostConstructProgram(program);
  }

  /**
   * Call pre-destroy
   */
  @Override
  public void preDestroy(X instance)
  {
    if (_producer == null)
      getInjectionPoints();
    
    _producer.preDestroy(instance);
  }

  @Override
  public void dispose(X instance)
  {
    if (_producer == null)
      getInjectionPoints();

    _producer.dispose(instance);
  }

  protected Object getHandle()
  {
    return new SingletonHandle(null);
  }
  
  public String getPassivationId()
  {
    return null;
  }
  /**
   * Binds parameters
   */
  private CandiProducer<X> build()
  {
    Thread thread = Thread.currentThread();
    ClassLoader oldLoader = thread.getContextClassLoader();
      
    try {
      thread.setContextClassLoader(getBeanManager().getClassLoader());
      
      introspect();
      
      Class<X> cl = _rawClass;
      

      if (_beanCtor == null) {
        // XXX:
        AnnotatedType<X> beanType = _annotatedType;
          
        if (beanType == null)
          beanType = ReflectionAnnotatedFactory.introspectType(cl);

        introspectConstructor(beanType);
      }

      Class<X> instanceClass = null;
      
      boolean isNoAspect = false;
      
      BaseTypeAnnotated baseAnnType = null;
      if (_annotatedType instanceof BaseTypeAnnotated) {
        baseAnnType = (BaseTypeAnnotated) _annotatedType;
        
        if (baseAnnType.getAnalysisAnnotation(NoAspect.class) != null) {
          isNoAspect = true;
        }
      }
        

      if (_isGenerateInterception && ! isNoAspect) {
        if (! _annotatedType.isAnnotationPresent(javax.interceptor.Interceptor.class)
            && ! _annotatedType.isAnnotationPresent(javax.decorator.Decorator.class)) {
          CandiBeanGenerator<X> bean = new CandiBeanGenerator<X>(getBeanManager(), _annotatedType);
          bean.introspect();
          
          instanceClass = (Class<X>) bean.generateClass();
          
          if (instanceClass == cl) {
            if (_annotatedType instanceof BaseTypeAnnotated) {
              baseAnnType = (BaseTypeAnnotated) _annotatedType;
              
              baseAnnType.addAnalysisAnnotation(NoAspectLiteral.ANN);
            }
          }
        }

        if (instanceClass == cl && isSerializeHandle()) {
            instanceClass = SerializationAdapter.gen(instanceClass);
        }
      }

      if (instanceClass != null && instanceClass != _instanceClass) {
        try {
          if (_javaCtor != null) {
            _javaCtor = (Constructor<X>) getConstructor(instanceClass, _javaCtor.getParameterTypes());
            _javaCtor.setAccessible(true);
          }
        } catch (Exception e) {
          // server/2423
          log.log(Level.FINE, e.toString(), e);
          // throw ConfigException.create(e);
        }
      }

      ConfigProgram []injectProgram = introspectInject();
      ConfigProgram []initProgram = introspectPostConstruct(_annotatedType);

      ArrayList<ConfigProgram> destroyList = new ArrayList<ConfigProgram>();
      introspectDestroy(destroyList, _annotatedType);
      ConfigProgram []destroyProgram = new ConfigProgram[destroyList.size()];
      destroyList.toArray(destroyProgram);
      
      Arg []args = null;
      
      if (_beanCtor != null)
        args = introspectArguments(_beanCtor, _beanCtor.getParameters());
      
      _cdiManager.bindGlobals();

      CandiProducer<X> producer
        = new CandiProducer<X>(_bean,
                               instanceClass,
                               _javaCtor,
                               args,
                               injectProgram,
                               initProgram,
                               destroyProgram,
                               _injectionPointSet);
      
      return producer;
    } finally {
      thread.setContextClassLoader(oldLoader);
    }
  }
  
  private static Constructor<?> getConstructor(Class<?> cl, Class<?> []paramTypes)
  {
    for (Constructor<?> ctor : cl.getDeclaredConstructors()) {
      if (isMatch(ctor.getParameterTypes(), paramTypes))
        return ctor;
    }
    
    throw new IllegalStateException("No matching constructor found for " + cl);
  }
  
  private static boolean isMatch(Class<?> []paramTypesA, Class<?> []paramTypesB)
  {
    if (paramTypesA.length != paramTypesB.length)
      return false;
    
    for (int i = paramTypesA.length - 1; i >= 0; i--) {
      if (! paramTypesA[i].equals(paramTypesB[i]))
        return false;
    }
    
    return true;
  }
  
  private ConfigProgram []introspectPostConstruct(AnnotatedType<X> annType)
  {
    if (annType.isAnnotationPresent(Interceptor.class)) {
      return new ConfigProgram[0];
    }
    
    ArrayList<ConfigProgram> initList = new ArrayList<ConfigProgram>();
    introspectInit(initList, annType);
    ConfigProgram []initProgram = new ConfigProgram[initList.size()];
    initList.toArray(initProgram);
    
    Arrays.sort(initProgram);
    
    return initProgram;
  }

  public static void
    introspectInit(ArrayList<ConfigProgram> initList,
                   AnnotatedType<?> type)
    throws ConfigException
  {
    for (AnnotatedMethod<?> annMethod : type.getMethods()) {
      Method method = annMethod.getJavaMember();
      
      if (! annMethod.isAnnotationPresent(PostConstruct.class)) {
        // && ! isAnnotationPresent(annList, Inject.class)) {
        continue;
      }

      if (method.getParameterTypes().length == 1
          && InvocationContext.class.equals(method.getParameterTypes()[0]))
        continue;

      if (method.isAnnotationPresent(PostConstruct.class)
          && method.getParameterTypes().length != 0) {
          throw new ConfigException(location(method)
                                    + L.l("{0}: @PostConstruct is requires zero arguments"));
      }

      PostConstructProgram initProgram
        = new PostConstructProgram(annMethod, method);

      if (! initList.contains(initProgram))
        initList.add(initProgram);
    }
  }

  public static void
    introspectInit(ArrayList<ConfigProgram> initList,
                   Class<?> type)
    throws ConfigException
  {
    if (type == null)
      return;
    
    introspectInit(initList, type.getSuperclass());
    
    for (Method method: type.getDeclaredMethods()) {
      if (! method.isAnnotationPresent(PostConstruct.class)) {
        // && ! isAnnotationPresent(annList, Inject.class)) {
        continue;
      }

      if (method.getParameterTypes().length == 1
          && InvocationContext.class.equals(method.getParameterTypes()[0]))
        continue;

      if (method.isAnnotationPresent(PostConstruct.class)
          && method.getParameterTypes().length != 0) {
          throw new ConfigException(location(method)
                                    + L.l("{0}: @PostConstruct is requires zero arguments"));
      }

      PostConstructProgram initProgram
        = new PostConstructProgram(null, method);

      if (! initList.contains(initProgram))
        initList.add(initProgram);
    }
  }

  private void
    introspectDestroy(ArrayList<ConfigProgram> destroyList, 
                      AnnotatedType<?> type)
    throws ConfigException
  {
    if (type == null || type.equals(Object.class))
      return;
    
    if (type.isAnnotationPresent(Interceptor.class)) {
      return;
    }

    for (AnnotatedMethod<?> method : type.getMethods()) {
      if (method.isAnnotationPresent(PreDestroy.class)) {
        Method javaMethod = method.getJavaMember();
        
        Class<?> []types = javaMethod.getParameterTypes();

        if (types.length == 0) {
        }
        else if (types.length == 1 && types[0].equals(InvocationContext.class)) {
          // XXX:
          continue;
        }
        else
          throw new ConfigException(location(javaMethod)
                                    + L.l("@PreDestroy is requires zero arguments"));

        PreDestroyInject destroyProgram
          = new PreDestroyInject(javaMethod);

        if (! destroyList.contains(destroyProgram))
          destroyList.add(destroyProgram);
      }
    }
  }

  //
  // introspection
  //

  private void introspect()
  {
    introspect(_annotatedType);
  }

  /**
   * Called for implicit introspection.
   */
  private void introspect(AnnotatedType<X> beanType)
  {
    introspectConstructor(beanType);
  }

  /**
   * Introspects the constructor
   */
  private void introspectConstructor(AnnotatedType<X> beanType)
  {
    if (_beanCtor != null)
      return;

    // XXX: may need to modify BeanFactory
    if (beanType.getJavaClass().isInterface())
      return;

    try {
      /*
      Class cl = getInstanceClass();

      if (cl == null)
        cl = getTargetClass();
      */

      AnnotatedConstructor<X> best = null;
      AnnotatedConstructor<X> second = null;

      for (AnnotatedConstructor<X> ctor : beanType.getConstructors()) {
        if (_newArgs != null
            && ctor.getParameters().size() != _newArgs.length) {
          continue;
        }
        else if (best == null) {
          best = ctor;
        }
        else if (ctor.isAnnotationPresent(Inject.class)) {
          if (best != null && best.isAnnotationPresent(Inject.class))
            throw new ConfigException(L.l("'{0}' can't have two constructors marked by @Inject or by a @Qualifier, because the Java Injection BeanManager can't tell which one to use.",
                                          beanType.getJavaClass().getName()));
          best = ctor;
          second = null;
        }
        else if (best.isAnnotationPresent(Inject.class)) {
        }
        else if (ctor.getParameters().size() == 0) {
          best = ctor;
        }
        else if (best.getParameters().size() == 0) {
        }
        else if (ctor.getParameters().size() == 1
                 && ctor.getParameters().get(0).equals(String.class)) {
          second = best;
          best = ctor;
        }
      }

      if (best == null) {
        // ioc/0q00
        best = new AnnotatedConstructorImpl(beanType, beanType.getJavaClass().getConstructor(new Class[0]));
      }

      if (best == null) {
        throw new ConfigException(L.l("{0}: no constructor found while introspecting bean for Java Injection",
                                      beanType.getJavaClass().getName()));
      }

      if (second == null) {
      }
      else if (beanType.getJavaClass().getName().startsWith("java.lang")
               && best.getParameters().size() == 1
               && best.getParameters().get(0).equals(String.class)) {
        log.fine(L.l("{0}: WebBean does not have a unique constructor, choosing String-arg constructor",
                     beanType.getJavaClass().getName()));
      }
      else
        throw new ConfigException(L.l("{0}: Bean does not have a unique constructor.  One constructor must be marked with @Inject or have a qualifier annotation.",
                                      beanType.getJavaClass().getName()));

      _beanCtor = best;
      _javaCtor = _beanCtor.getJavaMember();
      _javaCtor.setAccessible(true);

    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw ConfigException.create(e);
    }
  }

  @SuppressWarnings("unchecked")
  private Arg<X> []introspectArguments(Annotated ann, List<AnnotatedParameter<X>> params)
  {
    Arg<X> []args = new Arg[params.size()];

    for (int i = 0; i < args.length; i++) {
      AnnotatedParameter<X> param = params.get(i);
      
      args[i] = introspectArg(ann, param);
    }

    return args;
  }
  
  private Arg<X> introspectArg(Annotated ann, AnnotatedParameter<X> param)
  {
    Annotation []qualifiers = getQualifiers(param);
 
    InjectionPoint ip = new InjectionPointImpl<X>(getBeanManager(),
                                                  this,
                                                  param);
    
    if (ann.isAnnotationPresent(Inject.class)) {
      // ioc/022k
      _injectionPointSet.add(ip);
    }
    
    if (param.isAnnotationPresent(Disposes.class)) {
      throw new ConfigException(L.l("{0} is an invalid managed bean because its constructor has a @Disposes parameter",
                                    getAnnotatedType().getJavaClass().getName()));
    }
    
    if (param.isAnnotationPresent(Observes.class)) {
      throw new ConfigException(L.l("{0} is an invalid managed bean because its constructor has an @Observes parameter",
                                    getAnnotatedType().getJavaClass().getName()));
    }

    return new BeanArg<X>(getBeanManager(),
                          param.getBaseType(), 
                          qualifiers,
                          ip);
  }

  private Annotation []getQualifiers(Annotated annotated)
  {
    ArrayList<Annotation> qualifierList = new ArrayList<Annotation>();

    for (Annotation ann : annotated.getAnnotations()) {
      if (ann.annotationType().isAnnotationPresent(Qualifier.class)) {
        qualifierList.add(ann);
      }
    }

    if (qualifierList.size() == 0)
      qualifierList.add(CurrentLiteral.CURRENT);

    Annotation []qualifiers = new Annotation[qualifierList.size()];
    qualifierList.toArray(qualifiers);

    return qualifiers;
  }

  private ConfigProgram []introspectInject()
  {
    ArrayList<ConfigProgram> injectProgramList = new ArrayList<ConfigProgram>();
    
    _injectionPointSet = new HashSet<InjectionPoint>();
    
    introspectInject(injectProgramList);
    
    ConfigProgram []injectProgram
      = new ConfigProgram[injectProgramList.size()];
    injectProgramList.toArray(injectProgram);
    
    Arrays.sort(injectProgram);
    
    return injectProgram;
  }
  
  private void introspectInject(ArrayList<ConfigProgram> injectProgramList)
  {
    AnnotatedType<X> type = _annotatedType;
    
    Class<?> rawType = _rawClass;

    if (rawType == null || Object.class.equals(rawType))
      return;
    
    // Class<?> parentClass = rawType.getSuperclass();
    
    // configureClassResources(injectList, type);

    introspectInjectField(type, injectProgramList);
    introspectInjectMethod(type, injectProgramList);
    // introspectInject(type, injectProgramList, rawType);
    
    ResourceProgramManager resourceManager = _cdiManager.getResourceManager();
    
    resourceManager.buildInject(rawType, injectProgramList);
  }
  
  /*
  private void introspectInject(AnnotatedType<X> type,
                                ArrayList<ConfigProgram> injectProgramList,
                                Class<?> rawType)
  {
    if (rawType == null || Object.class.equals(rawType))
      return;
    
    introspectInject(type, injectProgramList, rawType.getSuperclass());
    
  }
  */
  
  private void introspectInjectClass(AnnotatedType<X> type)
  {
    InjectManager cdiManager = getBeanManager();
    
    for (Annotation ann : type.getAnnotations()) {
      Class<? extends Annotation> annType = ann.annotationType();
      
      InjectionPointHandler handler 
        = cdiManager.getInjectionPointHandler(annType);
      
      if (handler != null) {
        cdiManager.addGlobalProgram(new ClassHandlerProgram(ann, handler));
      }
    }
    
    // ioc/123i
    for (Class<?> parentClass = type.getJavaClass().getSuperclass();
         parentClass != null;
         parentClass = parentClass.getSuperclass()) {
      for (Annotation ann : parentClass.getAnnotations()) {
        Class<? extends Annotation> annType = ann.annotationType();
      
        InjectionPointHandler handler 
          = cdiManager.getInjectionPointHandler(annType);
      
        if (handler != null) {
          cdiManager.addGlobalProgram(new ClassHandlerProgram(ann, handler));
        }
      }
    }
  }
  
  private void introspectInjectField(AnnotatedType<X> type,
                                     ArrayList<ConfigProgram> injectProgramList)
  {
    for (AnnotatedField<?> field : type.getFields()) {
      if (field.getAnnotations().size() == 0)
        continue;
      
      /*
      if (! field.getDeclaringType().getJavaClass().equals(cl))
        continue;
        */

      if (field.isAnnotationPresent(Inject.class)) {
        // boolean isOptional = isQualifierOptional(field);

        InjectionPoint ij = new InjectionPointImpl(getBeanManager(), this, field);

        _injectionPointSet.add(ij);

        if (field.isAnnotationPresent(Delegate.class)) {
          // ioc/0i60
          /*
        if (! type.isAnnotationPresent(javax.decorator.Decorator.class)) {
          throw new IllegalStateException(L.l("'{0}' may not inject with @Delegate because it is not a @Decorator",
                                              type.getJavaClass()));
        }
           */
        }
        else {
          injectProgramList.add(new FieldInjectProgram(field.getJavaMember(), ij));
        }
      }
      else {
        InjectionPointHandler handler
          = getBeanManager().getInjectionPointHandler(field);

        if (handler != null) {
          ConfigProgram program = new FieldHandlerProgram(field, handler);

          injectProgramList.add(program);
        }
      }
    }
  }
  
  private void introspectInjectMethod(AnnotatedType<X> type,
                                      ArrayList<ConfigProgram> injectProgramList)
  {
    for (AnnotatedMethod method : type.getMethods()) {

      if (method.getAnnotations().size() == 0)
        continue;

      /*
      if (! method.getDeclaringType().getJavaClass().equals(cl))
        continue;
        */

      if (method.isAnnotationPresent(Inject.class)) {
        // boolean isOptional = isQualifierOptional(field);

        List<AnnotatedParameter<?>> params = method.getParameters();

        InjectionPoint []args = new InjectionPoint[params.size()];

        for (int i = 0; i < args.length; i++) {
          InjectionPoint ij
            = new InjectionPointImpl(getBeanManager(), this, params.get(i));

          _injectionPointSet.add(ij);

          args[i] = ij;
        }

        injectProgramList.add(new MethodInjectProgram(method.getJavaMember(),
                                                      args));
      }
      else {
        InjectionPointHandler handler
          = getBeanManager().getInjectionPointHandler(method);
        
        if (handler != null) {
          ConfigProgram program = new MethodHandlerProgram(method, handler);
          
          injectProgramList.add(program);
        }
      }
    }
  }
  
  void validate(Bean<?> bean)
  {
    if (bean == null)
      return;
    
    Class<? extends Annotation> scopeType = bean.getScope();
    
    if (getBeanManager().isPassivatingScope(scopeType)) {
      //validateNormal(bean);
      validatePassivating(bean);
    }
    else if (getBeanManager().isNormalScope(scopeType)) {
      //validateNormal(bean);
    }
  }
  
  private void validatePassivating(Bean<?> bean)
  {
    Type baseType = _annotatedType.getBaseType();
    
    Class<?> cl = getBeanManager().createTargetBaseType(baseType).getRawClass();
    boolean isStateful = _annotatedType.isAnnotationPresent(Stateful.class);
    
    if (! Serializable.class.isAssignableFrom(cl) && ! isStateful) {
      throw new ConfigException(L.l("'{0}' is an invalid @{1} bean because it's not serializable for {2}.",
                                    cl.getSimpleName(), bean.getScope().getSimpleName(),
                                    bean));
    }
    
    for (InjectionPoint ip : bean.getInjectionPoints()) {
      if (ip.isTransient())
        continue;
      
      Type type = ip.getType();
      
      if (ip.getBean() instanceof CdiStatefulBean)
        continue;
      
      if (type instanceof Class<?>) {
        Class<?> ipClass = (Class<?>) type;

        if (! ipClass.isInterface()
            && ! Serializable.class.isAssignableFrom(ipClass)
            && ! getBeanManager().isNormalScope(ip.getBean().getScope())) {
          throw new ConfigException(L.l("'{0}' is an invalid @{1} bean because '{2}' value {3} is not serializable for {4}.",
                                        cl.getSimpleName(), bean.getScope().getSimpleName(),
                                        ip.getType(),
                                        ip.getMember().getName(),
                                        bean));
        }
      }
    }
  }

  /**
   * Checks for validity for classpath scanning.
   */
  public static boolean isValid(Class<?> type)
  {
    if (type.isInterface())
      return false;

    if (type.getTypeParameters() != null
        && type.getTypeParameters().length > 0) {
      return false;
    }

    if (! isValidConstructor(type))
      return false;

    return true;
  }

  public static boolean isValidConstructor(Class<?> type)
  {
    for (Constructor<?> ctor : type.getDeclaredConstructors()) {
      if (ctor.getParameterTypes().length == 0)
        return true;

      if (ctor.isAnnotationPresent(Inject.class))
        return true;
    }

    return false;
  }

  private static String location(Method method)
  {
    String className = method.getDeclaringClass().getName();

    return className + "." + method.getName() + ": ";
  }

  private boolean isSerializeHandle()
  {
    return getAnnotatedType().isAnnotationPresent(SerializeHandle.class);
  }

  private static boolean hasQualifierAnnotation(AnnotatedConstructor<?> ctor)
  {
    return ctor.isAnnotationPresent(Inject.class);
  }
  
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _annotatedType + "]";
  }

  class FieldInjectProgram extends ConfigProgram {
    private final Field _field;
    private final InjectionPoint _ip;
    private InjectManager.ReferenceFactory<?> _fieldFactory;
    
    private AtomicBoolean _isStaticSet;

    FieldInjectProgram(Field field, InjectionPoint ip)
    {
      _field = field;
      _field.setAccessible(true);
      _ip = ip;

      if (Modifier.isStatic(field.getModifiers()))
        _isStaticSet = _cdiManager.getStaticMemberBoolean(field);
    }
    
    @Override
    public void bind()
    {
      InjectManager beanManager = getBeanManager();

      try {
        _fieldFactory = beanManager.getReferenceFactory(_ip);
      } catch (AmbiguousResolutionException e) {
        String loc = getLocation(_field);
        
        throw new AmbiguousResolutionException(loc + e.getMessage(), e);
      } catch (UnsatisfiedResolutionException e) {
        String loc = getLocation(_field);
        
        throw new UnsatisfiedResolutionException(loc + e.getMessage(), e);
      } catch (IllegalProductException e) {
        String loc = getLocation(_field);
        
        throw new IllegalProductException(loc + e.getMessage(), e);
      } catch (InjectionException e) {
        String loc = getLocation(_field);
      
        throw new InjectionException(loc + e.getMessage(), e);
      }
    }
    
    @Override
    public Class<?> getDeclaringClass()
    {
      return _field.getDeclaringClass();
    }
    
    @Override
    public String getName()
    {
      return _field.getName();
    }

    /**
     * Sorting priority: fields are second
     */
    @Override
    public int getPriority()
    {
      if (_isStaticSet != null)
        return -2;
      else
        return 0;
    }
    
    private String getLocation(Field field)
    {
      return _field.getDeclaringClass().getName() + "." + _field.getName() + ": ";
      
    }

    @Override
    public <T> void inject(T instance, CreationalContext<T> cxt)
    {
      try {
        if (_isStaticSet != null && _isStaticSet.getAndSet(true))
          return;
        
        CreationalContextImpl<?> env;
        
        if (cxt instanceof CreationalContextImpl<?>)
          env = (CreationalContextImpl<?>) cxt;
        else
          env = null;
        
        // server/30i1 vs ioc/0155
        Object value = _fieldFactory.create(null, env, _ip);
        
        _field.set(instance, value);
      } catch (AmbiguousResolutionException e) {
        throw new AmbiguousResolutionException(getFieldName(_field) + e.getMessage(), e);
      } catch (IllegalProductException e) {
        throw new IllegalProductException(getFieldName(_field) + e.getMessage(), e);
      } catch (InjectionException e) {
        throw new InjectionException(getFieldName(_field) + e.getMessage(), e);
      } catch (Exception e) {
        throw ConfigException.create(_field, e);
      }
    }
    
    private String getFieldName(Field field)
    {
      return field.getDeclaringClass().getSimpleName() + "." + field.getName() + ": ";
    }
    
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _field + "]";
    }
  }

  class MethodInjectProgram extends ConfigProgram {
    private final Method _method;
    private final InjectionPoint []_args;
    private ReferenceFactory<?> []_factoryArgs;

    private AtomicBoolean _isStaticSet;
    
    MethodInjectProgram(Method method, 
                        InjectionPoint []args)
    {
      _method = method;
      _method.setAccessible(true);
      _args = args;

      if (Modifier.isStatic(method.getModifiers()))
        _isStaticSet = _cdiManager.getStaticMemberBoolean(method);
            
      _factoryArgs = new ReferenceFactory[args.length];
    }

    /**
     * Sorting priority: fields are second
     */
    @Override
    public int getPriority()
    {
      if (_isStaticSet != null)
        return -1;
      else
        return 1;
    }
    
    @Override
    public Class<?> getDeclaringClass()
    {
      return _method.getDeclaringClass();
    }
    
    @Override
    public String getName()
    {
      return _method.getName();
    }
    
    @Override
    public <T> void inject(T instance, CreationalContext<T> cxt)
    {
      try {
        if (_isStaticSet != null && _isStaticSet.getAndSet(true))
          return;
        
        CreationalContextImpl<T> env;
        
        if (cxt instanceof CreationalContextImpl<?>)
          env = (CreationalContextImpl<T>) cxt;
        else
          env = null;
        
        Object []args = new Object[_args.length];

        for (int i = 0; i < _args.length; i++) {
          if (_factoryArgs[i] == null)
            _factoryArgs[i] = getBeanManager().getReferenceFactory(_args[i]);

          args[i] = _factoryArgs[i].create(null, env, _args[i]);
        }

        _method.invoke(instance, args);
      } catch (Exception e) {
        throw ConfigException.create(_method, e);
      }
    }
    
    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _method + "]";
    }
  }
  
  class FieldHandlerProgram extends ConfigProgram {
    private final AnnotatedField<?> _field;
    private final InjectionPointHandler _handler;
    private ConfigProgram _boundProgram;
    
    FieldHandlerProgram(AnnotatedField<?> field, InjectionPointHandler handler)
    {
      _field = field;
      _handler = handler;
    }

    @Override
    public <T> void inject(T instance, CreationalContext<T> env)
    {
      if (_boundProgram == null)
        bind();
      
      _boundProgram.inject(instance, env);
    }
    
    @Override
    public void bind()
    {
      _boundProgram = _handler.introspectField(_field);
    }
    
    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _field + "]";
    }
  }
  
  class ClassHandlerProgram extends ConfigProgram {
    private final InjectionPointHandler _handler;
    private ConfigProgram _boundProgram;
    
    ClassHandlerProgram(Annotation ann, InjectionPointHandler handler)
    {
      _handler = handler;
    }

    @Override
    public <T> void inject(T instance, CreationalContext<T> env)
    {
      if (_boundProgram == null)
        bind();
      
      _boundProgram.inject(instance, env);
    }
    
    @Override
    public void bind()
    {
      _boundProgram = _handler.introspectType(_annotatedType);
    }
    
    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _annotatedType + "]";
    }
  }
  
  class MethodHandlerProgram extends ConfigProgram {
    private final AnnotatedMethod<?> _method;
    private final InjectionPointHandler _handler;
    private ConfigProgram _boundProgram;
    
    MethodHandlerProgram(AnnotatedMethod<?> method,
                         InjectionPointHandler handler)
    {
      _method = method;
      _handler = handler;
    }

    @Override
    public int getPriority()
    {
      return 1;
    }
    
    @Override
    public <T> void inject(T instance, CreationalContext<T> env)
    {
      if (_boundProgram == null)
        bind();
      
      _boundProgram.inject(instance, env);
    }
    
    @Override
    public void bind()
    {
      _boundProgram = _handler.introspectMethod(_method);
    }
    
    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _method + "]";
    }
  }
}
