/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2009, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.test.metadata.loader.reflection.test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;

import org.jboss.metadata.plugins.loader.reflection.AnnotatedElementMetaDataLoader;
import org.jboss.metadata.spi.MetaData;
import org.jboss.metadata.spi.retrieval.MetaDataRetrieval;
import org.jboss.metadata.spi.retrieval.MetaDataRetrievalToMetaDataBridge;
import org.jboss.metadata.spi.signature.DeclaredMethodSignature;
import org.jboss.metadata.spi.signature.MethodSignature;
import org.jboss.metadata.spi.signature.Signature;
import org.jboss.test.metadata.AbstractMetaDataTest;
import org.jboss.test.metadata.loader.reflection.support.BridgeMethodBean;
import org.jboss.test.metadata.loader.reflection.support.BridgeMethodChildBean;
import org.jboss.test.metadata.loader.reflection.support.Child;
import org.jboss.test.metadata.loader.reflection.support.Event;
import org.jboss.test.metadata.loader.reflection.support.MethodBean;
import org.jboss.test.metadata.loader.reflection.support.NoAnnotationBean;

/**
 * AnnotatedElementMetadataLoaderTestCase
 *
 * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
 * the presence of annotations on beans
 * 
 * @author Jaikiran Pai
 * @version $Revision: $
 */
public class AnnotatedElementMetadataLoaderTestCase extends AbstractMetaDataTest
{

   /**
    * Constructor.
    *
    * @param name the test name
    */
   public AnnotatedElementMetadataLoaderTestCase(String name)
   {
      super(name);

   }
   
   /**
    * Test that a class having no annotations is successfully processed by the
    * {@link AnnotatedElementMetaDataLoader}. The {@link AnnotatedElementMetaDataLoader} is
    * expected to find *no* annotations on that class
    *  
    * @throws Exception for any error
    */
   public void testNoAnnotationPresent() throws Exception
   {
      // The NoAnnotationBean does not have any annotations
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(NoAnnotationBean.class);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(annotatedElementLoader);
      // should be empty array since the bean has no annotations
      Annotation[] annotations = metadata.getAnnotations();
      assertEmpty(annotations);
   }
   
   /**
    * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
    * the presence of annotations on methods when a {@link DeclaredMethodSignature}
    * is used to create the {@link AnnotatedElementMetaDataLoader}
    * 
    * @throws Exception for any error
    */
   public void testMethodLevelAnnotationsWithDeclaredMethodSignature() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(MethodBean.class);
      // Create a DeclaredMethodSignature for the method with 1 annotation 
      Method methodWithOneAnnotation = MethodBean.class.getMethod("testAnnotation", new Class<?>[] {String.class});
      DeclaredMethodSignature declaredMethodSignature = new DeclaredMethodSignature(methodWithOneAnnotation);
      // create a retrieval out of it
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(declaredMethodSignature);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(retrieval);
      // should be empty array since the bean has no annotations
      Annotation[] annotations = metadata.getAnnotations();
      assertTrue("Expected one annotation on " + methodWithOneAnnotation, annotations.length == 1);
      
      // Now try the same on a method which has 2 annotations
      Method methodWithTwoAnnotations = MethodBean.class.getMethod("testAnnotation12", new Class<?>[] {String.class, Class.class});
      DeclaredMethodSignature anotherDeclaredMethodSignature = new DeclaredMethodSignature(methodWithTwoAnnotations);
      // create a retrieval out of it
      MetaDataRetrieval anotherRetrieval = annotatedElementLoader.getComponentMetaDataRetrieval(anotherDeclaredMethodSignature);
      MetaData anotherMetadata = new MetaDataRetrievalToMetaDataBridge(anotherRetrieval);
      // should be empty array since the bean has no annotations
      Annotation[] annotationsOnMethodWithTwoAnnotations = anotherMetadata.getAnnotations();
      assertTrue("Expected two annotations on " + anotherDeclaredMethodSignature, annotationsOnMethodWithTwoAnnotations.length == 2);
   }

   /**
    * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
    * the presence of annotations on methods when a {@link MethodSignature}
    * is used to create the {@link AnnotatedElementMetaDataLoader}
    * 
    * @throws Exception for any error
    */
   public void testMethodLevelAnnotationsWithMethodSignature() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(MethodBean.class);
      // Create a MethodSignature for the method with 1 annotation 
      MethodSignature methodSignature = new MethodSignature("testAnnotation", String.class);
      // create a retrieval out of it
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(retrieval);
      // should be empty array since the bean has no annotations
      Annotation[] annotations = metadata.getAnnotations();
      assertTrue("Expected one annotation on testAnnotation method of " + MethodBean.class, annotations.length == 1);
      
      // Now try the same on a method which has 2 annotations
      MethodSignature anotherMethodSignature = new MethodSignature("testAnnotation12", String.class, Class.class);
      // create a retrieval out of it
      MetaDataRetrieval anotherRetrieval = annotatedElementLoader.getComponentMetaDataRetrieval(anotherMethodSignature);
      MetaData anotherMetadata = new MetaDataRetrievalToMetaDataBridge(anotherRetrieval);
      // should be empty array since the bean has no annotations
      Annotation[] annotationsOnMethodWithTwoAnnotations = anotherMetadata.getAnnotations();
      assertTrue("Expected two annotations on testAnnotation12 method of " + MethodBean.class, annotationsOnMethodWithTwoAnnotations.length == 2);
   }

   /**
    * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
    * bridge methods.
    */
   public void testMethodLevelAnnotationOnBridgeMethod() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(BridgeMethodBean.class);
      
      Method method = BridgeMethodBean.class.getMethod("unambiguous", Object.class);
      assertTrue(method.isBridge());
      MethodSignature methodSignature = new MethodSignature(method);
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature);
      assertNotNull("Expected a MetaDataRetrieval for method " + method, retrieval);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(retrieval);
      Annotation[] annotations = metadata.getAnnotations();
      assertTrue("Expected one annotation on unambiguous method of " + BridgeMethodBean.class, annotations.length == 1);
      
      method = BridgeMethodBean.class.getMethod("ambiguous", Object.class);
      assertTrue(method.isBridge());
      methodSignature = new MethodSignature(method);
      assertNull(annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature));
   }

   /**
    * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
    * bridge methods.
    */
   public void testMethodLevelAnnotationOnDeclaredBridgeMethod() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(BridgeMethodBean.class);

      Method method = BridgeMethodBean.class.getMethod("unambiguous", Object.class);
      assertTrue(method.isBridge());
      Signature methodSignature = new DeclaredMethodSignature(method);
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature);
      assertNotNull("Expected a MetaDataRetrieval for method " + method, retrieval);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(retrieval);
      Annotation[] annotations = metadata.getAnnotations();
      assertTrue("Expected one annotation on unambiguous method of " + BridgeMethodBean.class, annotations.length == 1);

      method = BridgeMethodBean.class.getMethod("ambiguous", Object.class);
      assertTrue(method.isBridge());
      methodSignature = new DeclaredMethodSignature(method);
      assertNull(annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature));
   }

   /**
    * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
    * bridge methods.
    */
   public void testMethodLevelAnnotationOnBridgeMethodFromParent() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(BridgeMethodChildBean.class);
      
      Method method = BridgeMethodChildBean.class.getMethod("unambiguous", Object.class);
      assertTrue(method.isBridge());
      MethodSignature methodSignature = new MethodSignature(method);
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature);
      assertNotNull("Expected a MetaDataRetrieval for method " + method, retrieval);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(retrieval);
      Annotation[] annotations = metadata.getAnnotations();
      assertTrue("Expected one annotation on unambiguous method of " + BridgeMethodChildBean.class, annotations.length == 1);
      
      method = BridgeMethodChildBean.class.getMethod("ambiguous", Object.class);
      assertTrue(method.isBridge());
      methodSignature = new MethodSignature(method);
      assertNull(annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature));
   }

   /**
    * Tests that the {@link AnnotatedElementMetaDataLoader} correctly identifies
    * bridge methods.
    */
   public void testMethodLevelAnnotationOnDeclaredBridgeMethodFromParent() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(BridgeMethodChildBean.class);

      Method method = BridgeMethodChildBean.class.getMethod("unambiguous", Object.class);
      assertTrue(method.isBridge());
      Signature methodSignature = new DeclaredMethodSignature(method);
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature);
      assertNotNull("Expected a MetaDataRetrieval for method " + method, retrieval);
      MetaData metadata = new MetaDataRetrievalToMetaDataBridge(retrieval);
      Annotation[] annotations = metadata.getAnnotations();
      assertTrue("Expected one annotation on unambiguous method of " + BridgeMethodChildBean.class, annotations.length == 1);

      method = BridgeMethodChildBean.class.getMethod("ambiguous", Object.class);
      assertTrue(method.isBridge());
      methodSignature = new DeclaredMethodSignature(method);
      assertNull(annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature));
   }

   /**
    * Test the bug fix for JBMDR-73
    * <p/>
    *
    * Tests that overlaoded methods don't cause "No original methods found for..." exception
    * while looking for real method corresponding to a bridged method, through {@link AnnotatedElementMetaDataLoader}
    */
   public void testBridgedMethodWithOverloadedMethods() throws Exception
   {
      AnnotatedElementMetaDataLoader annotatedElementLoader = new AnnotatedElementMetaDataLoader(Child.class);
      // get the bridged method
      Method method = Child.class.getMethod("loadByExample", Event.class, Map.class);
      // Ensure that the bridged method was picked up
      assertTrue("Method " + method + " was expected to be a bridged method", method.isBridge());

      // now just get the ComponentMetaDataRetrieval (which internally triggers the call to search for the
      // non-bridged method).
      Signature methodSignature = new DeclaredMethodSignature(method);
      MetaDataRetrieval retrieval = annotatedElementLoader.getComponentMetaDataRetrieval(methodSignature);
      assertNotNull("Expected a MetaDataRetrieval for method " + method, retrieval);
   }
}
