/*
* JBoss, Home of Professional Open Source
* Copyright 2006, 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.test.classinfo.test;

import java.lang.reflect.AccessibleObject;
import java.security.AccessControlException;

import org.jboss.config.plugins.BasicConfiguration;
import org.jboss.config.spi.Configuration;
import org.jboss.reflect.spi.AnnotatedInfo;
import org.jboss.reflect.spi.ClassInfo;
import org.jboss.test.AbstractTestCaseWithSetup;
import org.jboss.test.AbstractTestDelegate;
import org.jboss.test.classinfo.support.ErrorHolderThread;

/**
 * Access restriction test.
 *
 * @param <T> exact tester type
 * @param <U> exact annotated info
 * @param <V> exact accessible object
 * @author <a href="ales.justin@jboss.com">Ales Justin</a>
 */
public abstract class AccessRestrictionTest<T, U extends AnnotatedInfo, V extends AccessibleObject> extends AbstractTestCaseWithSetup
{
   /** The bean info factory */
   protected Configuration configuration = new BasicConfiguration();

   /**
    * Create a new AccessRestrictionTest.
    *
    * @param name the test name
    */
   protected AccessRestrictionTest(String name)
   {
      super(name);
   }

   /**
    * Default setup with security manager enabled
    *
    * @param clazz the class
    * @return the delegate
    * @throws Exception for any error
    */
   public static AbstractTestDelegate getDelegate(Class<?> clazz) throws Exception
   {
      AbstractTestDelegate delegate = new AbstractTestDelegate(clazz);
      delegate.enableSecurity = true;
      return delegate;
   }

   protected ClassInfo getClassInfo(Class<?> clazz)
   {
      SecurityManager sm = suspendSecurity();
      try
      {
         return configuration.getClassInfo(clazz);
      }
      finally
      {
         resumeSecurity(sm);
      }
   }

   protected abstract T getInstance();
   protected abstract Class<T> getInstanceClass();
   protected abstract U getInfo();
   protected abstract U getSetAnnotatedInfo(ClassInfo info, String member);
   protected abstract U getGetAnnotatedInfo(ClassInfo info, String member);
   protected abstract V getAccessibleObject(String member) throws Exception;
   protected abstract void set(U annotatedInfo, T instance, String string) throws Throwable;
   protected abstract Object get(U annotatedInfo, T instance) throws Throwable;
   protected abstract void set(V accessibleObject, T instance, String string) throws Exception;
   protected abstract void set(U info, V accessibleObject);
   protected abstract String getPrivateString(T instance);
   protected abstract V getAccessibleObject(U info);

   public void testFieldAcessFromMain() throws Throwable
   {
      final T tester = getInstance();
      final U impl = getInfo();

      // I can't do setAccesible
      V accessibleObject = getAccessibleObject("privString");
      // let's try accessible
      try
      {
         accessibleObject.setAccessible(true);
         fail("Should not be here.");
      }
      catch (Throwable t)
      {
         assertInstanceOf(t, AccessControlException.class);
      }
      // ok, setAccessible not set, so set should also fail
      try
      {
         set(accessibleObject, tester, "foobar");
         fail("Should not be here.");
      }
      catch (Throwable t)
      {
         assertInstanceOf(t, IllegalAccessException.class);
      }

      try
      {
         set(impl, accessibleObject);
         fail("Should not be here");
      }
      catch (Throwable t)
      {
         checkThrowable(AccessControlException.class, t);
      }
      try
      {
         set(accessibleObject, tester, "foobar");
         fail("Should not be here");
      }
      catch (Throwable t)
      {
         checkThrowable(IllegalAccessException.class, t);
      }
      assertNull(getPrivateString(tester));

      Runnable runnable = new Runnable()
      {
         public void run()
         {
            try
            {
               V ao = getAccessibleObject(impl); // This should have an access check
               set(ao, tester, "something"); // this should check for caller
            }
            catch (Throwable t)
            {
               throw new RuntimeException(t);
            }
         }
      };
      ErrorHolderThread other = new ErrorHolderThread(runnable);
      other.start();
      other.join();
      // we should get an error here
      assertNotNull("Should get access restriction exception.", other.getError());
      RuntimeException re = assertInstanceOf(other.getError(), RuntimeException.class);
      Throwable cause = re.getCause();
      assertNotNull(cause);
      assertInstanceOf(cause, AccessControlException.class, false);
   }

   public void testFieldAccessFromOther() throws Throwable
   {
      final T tester = getInstance();
      ClassInfo classInfo = configuration.getClassInfo(getInstanceClass());
      // we should not fail
      U pub = getGetAnnotatedInfo(classInfo, "pubString");
      assertNotNull(pub);
      final U priSet = getSetAnnotatedInfo(classInfo, "privString");
      final U priGet = getGetAnnotatedInfo(classInfo, "privString");

      // Shouldn't be able to set the private field
      try
      {
         set(priSet, tester, "foobar");
         fail("Should not be here.");
      }
      catch (Throwable t)
      {
         assertInstanceOf(t, AccessControlException.class);
      }
      // Shouldn't be able to get the private field
      try
      {
         get(priGet, tester);
         fail("Should not be here.");
      }
      catch (Throwable t)
      {
         assertInstanceOf(t, AccessControlException.class);
      }
      // Shouldn't be able to steal the private field which has setAccessible(true)
      try
      {
         getAccessibleObject(priGet);
         fail("Should not be here.");
      }
      catch (Throwable t)
      {
         assertInstanceOf(t, AccessControlException.class);
      }
      ErrorHolderThread other = new ErrorHolderThread(new Runnable()
      {
         public void run()
         {
            SecurityManager sm = suspendSecurity();
            try
            {
               set(priSet, tester, "foobar");
            }
            catch(Throwable t)
            {
               throw new RuntimeException(t);
            }
            finally
            {
               resumeSecurity(sm);
            }
         }
      });
      assertNull(getPrivateString(tester));
      other.start();
      other.join();
      assertNull(other.getError());
      assertEquals("foobar", getPrivateString(tester));
   }
}
