/*
 * JBoss, Home of Professional Open Source
 * Copyright 2007, Red Hat Middleware LLC, and individual contributors
 * 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.deployment;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.annotation.Resources;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.annotation.security.RunAs;
import javax.ejb.ApplicationException;
import javax.ejb.EJB;
import javax.ejb.EJBs;
import javax.ejb.Init;
import javax.ejb.Local;
import javax.ejb.LocalHome;
import javax.ejb.MessageDriven;
import javax.ejb.PostActivate;
import javax.ejb.PrePassivate;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionManagement;
import javax.interceptor.AroundInvoke;
import javax.interceptor.ExcludeClassInterceptors;
import javax.interceptor.ExcludeDefaultInterceptors;
import javax.interceptor.Interceptors;
import javax.persistence.Entity;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContexts;
import javax.xml.ws.WebServiceRef;
import javax.xml.ws.WebServiceRefs;

import org.jboss.mcann.AnnotationRepository;
import org.jboss.mcann.Element;
import org.jboss.deployers.vfs.spi.structure.VFSDeploymentUnit;
import org.jboss.virtual.VirtualFile;

/**
 * A POST_CLASSLOADER deployer which generates metadata from annotations.
 * Alternative option to its super class.
 *
 * @author Ales.Justin@jboss.org
 */
public class AltAnnotationMetaDataDeployer extends AnnotationMetaDataDeployer
{
   private Set<Class<? extends Annotation>> annotationOnClass = new HashSet<Class<? extends Annotation>>();
   private Set<Class<? extends Annotation>> annotationOnMethod = new HashSet<Class<? extends Annotation>>();
   private Set<Class<? extends Annotation>> annotationOnField = new HashSet<Class<? extends Annotation>>();

   public AltAnnotationMetaDataDeployer()
   {
      super();
      setInput(AnnotationRepository.class);
      // add annotations AnnotationMetaDataDeployer scans
      addAnnotationClass(Stateful.class);
      addAnnotationClass(Remove.class);
      addAnnotationClass(PostActivate.class);
      addAnnotationClass(PrePassivate.class);
      addAnnotationClass(Local.class);
      addAnnotationClass(LocalHome.class);
      addAnnotationClass(Remote.class);
      addAnnotationClass(RemoteHome.class);
      addAnnotationClass(Init.class);
      addAnnotationClass(Timeout.class);
      addAnnotationClass(AroundInvoke.class);
      addAnnotationClass(TransactionManagement.class);
      addAnnotationClass(TransactionAttribute.class);
      addAnnotationClass(RunAs.class);
      addAnnotationClass(DeclareRoles.class);
      addAnnotationClass(DenyAll.class);
      addAnnotationClass(RolesAllowed.class);
      addAnnotationClass(PermitAll.class);
      addAnnotationClass(Interceptors.class);
      addAnnotationClass(ExcludeClassInterceptors.class);
      addAnnotationClass(ExcludeDefaultInterceptors.class);
      addAnnotationClass(Resource.class);
      addAnnotationClass(Resources.class);
      addAnnotationClass(EJB.class);
      addAnnotationClass(EJBs.class);
      addAnnotationClass(PersistenceContext.class);
      addAnnotationClass(PersistenceContexts.class);
      addAnnotationClass(PostConstruct.class);
      addAnnotationClass(PreDestroy.class);
      addAnnotationClass(WebServiceRef.class);
      addAnnotationClass(WebServiceRefs.class);
      addAnnotationClass(Stateless.class);
      addAnnotationClass(MessageDriven.class);
      addAnnotationClass(Entity.class);
      addAnnotationClass(ApplicationException.class);
   }

   @SuppressWarnings("unchecked")
   protected Collection<Class<?>> getClasses(VFSDeploymentUnit unit, String mainClassName, List<VirtualFile> classpath) throws IOException
   {
      AnnotationRepository env = unit.getAttachment(AnnotationRepository.class);
      if (env == null)
      {
         if (log.isTraceEnabled())
            log.trace("Cannot scan classes, missing AnnotationRepository as attachment: " + unit.getName());
         return Collections.emptySet();
      }

      Set<Class<?>> classes = new HashSet<Class<?>>();
      for(Class<? extends Annotation> annotation : annotationOnClass)
      {
         Class<Annotation> annotationClass = (Class<Annotation>)annotation;
         Set<Element<Annotation, Class<?>>> elements = env.classIsAnnotatedWith(annotationClass);
         for(Element<Annotation, Class<?>> elt : elements)
            classes.add(elt.getOwner());
      }
      for(Class<? extends Annotation> annotation : annotationOnMethod)
      {
         Class<Annotation> annotationClass = (Class<Annotation>)annotation;
         Set<Element<Annotation, Method>> elements = env.classHasMethodAnnotatedWith(annotationClass);
         for(Element<Annotation, Method> elt : elements)
            classes.add(elt.getOwner());
      }
      for(Class<? extends Annotation> annotation : annotationOnField)
      {
         Class<Annotation> annotationClass = (Class<Annotation>)annotation;
         Set<Element<Annotation, Field>> elements = env.classHasFieldAnnotatedWith(annotationClass);
         for(Element<Annotation, Field> elt : elements)
            classes.add(elt.getOwner());
      }

      if(log.isTraceEnabled() && classes.isEmpty() == false)
         log.trace("Annotated classes: " + classes);

      return classes;
   }

   /**
    * Cleanup annotation classes.
    */
   public void stop()
   {
      annotationOnClass.clear();
      annotationOnMethod.clear();
      annotationOnField.clear();
   }

   /**
    * Add annotation to matching set.
    *
    * @param annotation the annotation class
    */
   public void addAnnotationClass(Class<? extends Annotation> annotation)
   {
      Target target = annotation.getAnnotation(Target.class);
      if (target == null)
      {
         log.info("Annotation " + annotation + " has not @Target.");
         return;
      }
      ElementType[] types = target.value();
      if (types == null || types.length == 0)
      {
         log.info("Null or empty types on annotation's @Target: " + annotation);
         return;
      }
      for(ElementType type : types)
      {
         boolean used = false; // no need for duplicates
         if (type == ElementType.TYPE)
         {
            annotationOnClass.add(annotation);
            used = true;
         }
         if (used == false && type == ElementType.METHOD)
         {
            annotationOnMethod.add(annotation);
            used = true;
         }
         if (used == false && type == ElementType.FIELD)
         {
            annotationOnField.add(annotation);
         }
      }
   }
}