/*
 * 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.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jboss.mcann.AnnotationRepository;
import org.jboss.mcann.Element;
import org.jboss.deployers.vfs.spi.structure.VFSDeploymentUnit;
import org.jboss.metadata.annotation.creator.AbstractCreator;
import org.jboss.metadata.annotation.creator.AnnotationContext;
import org.jboss.metadata.annotation.creator.client.ApplicationClient5MetaDataCreator;
import org.jboss.metadata.annotation.creator.ejb.jboss.JBoss50Creator;
import org.jboss.metadata.annotation.creator.web.Web25MetaDataCreator;
import org.jboss.metadata.annotation.finder.AnnotationFinder;
import org.jboss.metadata.annotation.finder.DefaultAnnotationFinder;
import org.jboss.metadata.client.spec.ApplicationClientMetaData;
import org.jboss.metadata.ejb.jboss.JBossMetaData;
import org.jboss.metadata.ejb.spec.EjbJarMetaData;
import org.jboss.metadata.web.spec.WebMetaData;
import org.jboss.virtual.VirtualFile;

/**
 * A POST_CLASSLOADER deployer which generates metadata from annotations.
 * Optimized option to its super class.
 * TODO: Will need to support per JAR annotations for Servlet 3.0
 *
 * @author Ales.Justin@jboss.org
 */
public class OptAnnotationMetaDataDeployer extends AnnotationMetaDataDeployer
{
   public OptAnnotationMetaDataDeployer()
   {
      super();
      setInput(AnnotationRepository.class);
   }

   protected void processMetaData(VFSDeploymentUnit unit, WebMetaData webMetaData, ApplicationClientMetaData clientMetaData, List<VirtualFile> classpath) throws Exception
   {
      AnnotationFinder<AnnotatedElement> finder = new DefaultAnnotationFinder<AnnotatedElement>();
      // don't do webMetaData anymore (rev 96033)

      String mainClassName = getMainClassName(unit);
      if(mainClassName != null)
         processJBossClientMetaData(unit, finder, mainClassName);
      else
         processJBossMetaData(unit, finder);
   }

   /**
    * Process jboss web meta data.
    *
    * @param unit the deployment unit
    * @param finder the finder
    */
   protected void processJBossWebMetaData(VFSDeploymentUnit unit, AnnotationFinder<AnnotatedElement> finder)
   {
      Web25MetaDataCreator creator = new Web25MetaDataCreator(finder);
      Collection<Class<?>> classes = getClasses(unit, creator);
      WebMetaData annotationMetaData = creator.create(classes);
      if(annotationMetaData != null)
         unit.addAttachment(WEB_ANNOTATED_ATTACHMENT_NAME, annotationMetaData, WebMetaData.class);
   }

   /**
    * Process jboss app client meta data.
    *
    * @param unit the deployment unit
    * @param finder the finder
    * @param mainClassName the main class name
    * @throws ClassNotFoundException for any error
    */
   protected void processJBossClientMetaData(VFSDeploymentUnit unit, AnnotationFinder<AnnotatedElement> finder, String mainClassName) throws ClassNotFoundException
   {
      ApplicationClient5MetaDataCreator creator = new ApplicationClient5MetaDataCreator(finder, mainClassName);
      Collection<Class<?>> classes = new ArrayList<Class<?>>(1);
      Class<?> mainClass = unit.getClassLoader().loadClass(mainClassName);
      classes.add(mainClass);
      ApplicationClientMetaData annotationMetaData = creator.create(classes);
      if(annotationMetaData != null)
         unit.addAttachment(CLIENT_ANNOTATED_ATTACHMENT_NAME, annotationMetaData, ApplicationClientMetaData.class);
   }

   /**
    * Process jboss meta data.
    *
    * @param unit the deployment unit
    * @param finder the finder
    */
   protected void processJBossMetaData(VFSDeploymentUnit unit, AnnotationFinder<AnnotatedElement> finder)
   {
      // Create the metadata model from the annotations
      EjbJarMetaData ejbJarMetaData = unit.getAttachment(EjbJarMetaData.class);
      JBoss50Creator creator = new JBoss50Creator(ejbJarMetaData, unit.getClassLoader(), finder);
      Collection<Class<?>> classes = getClasses(unit, creator);
      JBossMetaData annotationMetaData = creator.create(classes);
      if(annotationMetaData != null)
         unit.addAttachment(EJB_ANNOTATED_ATTACHMENT_NAME, annotationMetaData, JBossMetaData.class);
   }

   /**
    * Get the classes for creator to process.
    *
    * @param unit the deployment unit
    * @param creator the creator
    * @return classes to process
    */
   @SuppressWarnings("unchecked")
   protected Collection<Class<?>> getClasses(VFSDeploymentUnit unit, AbstractCreator creator)
   {
      boolean trace = log.isTraceEnabled();

      AnnotationRepository env = unit.getAttachment(AnnotationRepository.class);
      if (env == null)
      {
         if (trace)
            log.trace("Cannot scan classes, missing AnnotationRepository as attachment: " + unit.getName());

         return Collections.emptySet();
      }

      String creatorInfo = creator.toString();
      AnnotationContext context = creator.getAnnotationContext();
      Set<Class<?>> classes = new HashSet<Class<?>>();

      Collection<Class<? extends Annotation>> typeAnnotations = context.getTypeAnnotations();
      if (trace)
         log.trace("Creator: " + creatorInfo + ", type annotations: " + typeAnnotations);
      for(Class<? extends Annotation> annotation : typeAnnotations)
      {
         Class<Annotation> annotationClass = (Class<Annotation>)annotation;
         Set<Element<Annotation, Class<?>>> elements = env.classIsAnnotatedWith(annotationClass);
         for(Element<Annotation, Class<?>> elt : elements)
               classes.add(elt.getOwner());
      }

      Collection<Class<? extends Annotation>> methodAnnotations = context.getMethodAnnotations();
      if (trace)
         log.trace("Creator: " + creatorInfo + ", method annotations: " + methodAnnotations);
      for(Class<? extends Annotation> annotation : methodAnnotations)
      {
         Class<Annotation> annotationClass = (Class<Annotation>)annotation;
         Set<Element<Annotation, Method>> elements = env.classHasMethodAnnotatedWith(annotationClass);
         for(Element<Annotation, Method> elt : elements)
            classes.add(elt.getOwner());
      }

      Collection<Class<? extends Annotation>> fieldAnnotations = context.getFieldAnnotations();
      if (trace)
         log.trace("Creator: " + creatorInfo + ", field annotations: " + fieldAnnotations);
      for(Class<? extends Annotation> annotation : fieldAnnotations)
      {
         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 (trace)
         log.trace("Annotated classes [" + unit.getName() + ", " + creatorInfo + "]: " + classes);

      return classes;
   }
}