/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.log4j;

import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.framework.Test;

import java.util.Vector;

import org.apache.log4j.*;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.varia.NullAppender;

/**
   A superficial but general test of log4j.
 */
public class AsyncAppenderTestCase extends TestCase {

  public AsyncAppenderTestCase(String name) {
    super(name);
  }

  public void setUp() {
  }

  public void tearDown() {
    LogManager.shutdown();
  }

  // this test checks whether it is possible to write to a closed AsyncAppender
  public void closeTest() throws Exception {    
    Logger root = Logger.getRootLogger();
    Layout layout = new SimpleLayout();
    VectorAppender vectorAppender = new VectorAppender();
    AsyncAppender asyncAppender = new AsyncAppender();
    asyncAppender.setName("async-CloseTest");
    asyncAppender.addAppender(vectorAppender);
    root.addAppender(asyncAppender); 

    root.debug("m1");
    asyncAppender.close();
    root.debug("m2");
    
    Vector v = vectorAppender.getVector();
    assertEquals(v.size(), 1);
  }

  // this test checks whether appenders embedded within an AsyncAppender are also 
  // closed 
  public void test2() {
    Logger root = Logger.getRootLogger();
    Layout layout = new SimpleLayout();
    VectorAppender vectorAppender = new VectorAppender();
    AsyncAppender asyncAppender = new AsyncAppender();
    asyncAppender.setName("async-test2");
    asyncAppender.addAppender(vectorAppender);
    root.addAppender(asyncAppender); 

    root.debug("m1");
    asyncAppender.close();
    root.debug("m2");
    
    Vector v = vectorAppender.getVector();
    assertEquals(v.size(), 1);
    assertTrue(vectorAppender.isClosed());
  }

  // this test checks whether appenders embedded within an AsyncAppender are also 
  // closed 
  public void test3() {
    int LEN = 200;
    Logger root = Logger.getRootLogger();
    Layout layout = new SimpleLayout();
    VectorAppender vectorAppender = new VectorAppender();
    AsyncAppender asyncAppender = new AsyncAppender();
    asyncAppender.setName("async-test3");
    asyncAppender.addAppender(vectorAppender);
    root.addAppender(asyncAppender); 

    for(int i = 0; i < LEN; i++) {
      root.debug("message"+i);
    }
    
    System.out.println("Done loop.");
    System.out.flush();
    asyncAppender.close();
    root.debug("m2");
    
    Vector v = vectorAppender.getVector();
    assertEquals(v.size(), LEN);
    assertTrue(vectorAppender.isClosed());
  }

    private static class NullPointerAppender extends AppenderSkeleton {
          public NullPointerAppender() {
          }


          /**
             This method is called by the {@link org.apache.log4j.AppenderSkeleton#doAppend}
             method.

          */
          public void append(org.apache.log4j.spi.LoggingEvent event) {
              throw new NullPointerException();
          }

          public void close() {
          }

          public boolean requiresLayout() {
            return false;
          }
    }


    /**
     * Tests that a bad appender will switch async back to sync.
     * See bug 23021
     * @since 1.2.12
     * @throws Exception thrown if Thread.sleep is interrupted
     */
    public void testBadAppender() throws Exception {
        Appender nullPointerAppender = new NullPointerAppender();
        AsyncAppender asyncAppender = new AsyncAppender();
        asyncAppender.addAppender(nullPointerAppender);
        asyncAppender.setBufferSize(5);
        asyncAppender.activateOptions();
        Logger root = Logger.getRootLogger();
        root.addAppender(nullPointerAppender);
        try {
           root.info("Message");
           Thread.sleep(10);
           root.info("Message");
           fail("Should have thrown exception");
        } catch(NullPointerException ex) {

        }
    }

    /**
     * Tests location processing when buffer is full and locationInfo=true.
     * See bug 41186.
     */
    public void testLocationInfoTrue() {
        BlockableVectorAppender blockableAppender = new BlockableVectorAppender();
        AsyncAppender async = new AsyncAppender();
        async.addAppender(blockableAppender);
        async.setBufferSize(5);
        async.setLocationInfo(true);
        async.setBlocking(false);
        async.activateOptions();
        Logger rootLogger = Logger.getRootLogger();
        rootLogger.addAppender(async);
        Greeter greeter = new Greeter(rootLogger, 100);
        synchronized(blockableAppender.getMonitor()) {
            greeter.run();
            rootLogger.error("That's all folks.");
        }
        async.close();
        Vector events = blockableAppender.getVector();
        LoggingEvent initialEvent = (LoggingEvent) events.get(0);
        LoggingEvent discardEvent = (LoggingEvent) events.get(events.size() - 1);
        PatternLayout layout = new PatternLayout();
        layout.setConversionPattern("%C:%L %m%n");
        layout.activateOptions();
        String initialStr = layout.format(initialEvent);
        assertEquals(AsyncAppenderTestCase.class.getName(),
                initialStr.substring(0, AsyncAppenderTestCase.class.getName().length()));
        String discardStr = layout.format(discardEvent);
        assertEquals("?:? ", discardStr.substring(0, 4));
    }


    /**
     * Tests location processing when buffer is full and locationInfo=false.
     * See bug 41186.
     */
    public void testLocationInfoFalse() {
        BlockableVectorAppender blockableAppender = new BlockableVectorAppender();
        AsyncAppender async = new AsyncAppender();
        async.addAppender(blockableAppender);
        async.setBufferSize(5);
        async.setLocationInfo(false);
        async.setBlocking(false);
        async.activateOptions();
        Logger rootLogger = Logger.getRootLogger();
        rootLogger.addAppender(async);
        Greeter greeter = new Greeter(rootLogger, 100);
        synchronized(blockableAppender.getMonitor()) {
            greeter.run();
            rootLogger.error("That's all folks.");
        }
        async.close();
        Vector events = blockableAppender.getVector();
        LoggingEvent initialEvent = (LoggingEvent) events.get(0);
        LoggingEvent discardEvent = (LoggingEvent) events.get(events.size() - 1);
        PatternLayout layout = new PatternLayout();
        layout.setConversionPattern("%C:%L %m%n");
        layout.activateOptions();
        String initialStr = layout.format(initialEvent);
        assertEquals("?:? ", initialStr.substring(0, 4));
        String discardStr = layout.format(discardEvent);
        assertEquals("?:? ", discardStr.substring(0, 4));
    }

    /**
     *  Logging request runnable.
     */
    private static final class Greeter implements Runnable {
      /**
       * Logger.
       */
      private final Logger logger;

      /**
       * Repetitions.
       */
      private final int repetitions;

      /**
       * Create new instance.
       * @param logger logger, may not be null.
       * @param repetitions repetitions.
       */
      public Greeter(final Logger logger, final int repetitions) {
        if (logger == null) {
          throw new IllegalArgumentException("logger");
        }

        this.logger = logger;
        this.repetitions = repetitions;
      }

      /**
       * {@inheritDoc}
       */
      public void run() {
        try {
          for (int i = 0; i < repetitions; i++) {
            logger.info("Hello, World");
            Thread.sleep(1);
          }
        } catch (InterruptedException ex) {
          Thread.currentThread().interrupt();
        }
      }
    }



    /**
     * Vector appender that can be explicitly blocked.
     */
    private static final class BlockableVectorAppender extends VectorAppender {
      /**
       * Monitor object used to block appender.
       */
      private final Object monitor = new Object();

      /**
       * Thread of last call to append.
       */
      private Thread dispatcher;

      /**
       * Create new instance.
       */
      public BlockableVectorAppender() {
        super();
      }

      /**
       * {@inheritDoc}
       */
      public void append(final LoggingEvent event) {
        synchronized (monitor) {
          dispatcher = Thread.currentThread();
          super.append(event);
            //
            //   if fatal, echo messages for testLoggingInDispatcher
            //
            if (event.getLevel() == Level.FATAL) {
                Logger logger = Logger.getLogger(event.getLoggerName());
                logger.error(event.getMessage().toString());
                logger.warn(event.getMessage().toString());
                logger.info(event.getMessage().toString());
                logger.debug(event.getMessage().toString());
            }
        }
      }

      /**
       * Get monitor object.
       * @return monitor.
       */
      public Object getMonitor() {
        return monitor;
      }

      /**
       * Get thread of previous call to append.
       * @return thread, may be null.
       */
      public Thread getDispatcher() {
        synchronized (monitor) {
          return dispatcher;
        }
      }
    }


}
