/*
 * 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 java.util.Vector;

import org.apache.log4j.spi.LoggingEvent;

/**
 * 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();
	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();
	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();
	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();

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

	/**
	 * {@inheritDoc}
	 */
	public void append(final LoggingEvent event) {
	    synchronized (monitor) {
		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;
	}

    }

    /**
     * Test that a mutable message object is evaluated before being placed in the
     * async queue. See bug 43559.
     */
    public void testMutableMessage() {
	BlockableVectorAppender blockableAppender = new BlockableVectorAppender();
	AsyncAppender async = new AsyncAppender();
	async.addAppender(blockableAppender);
	async.setBufferSize(5);
	async.setLocationInfo(false);
	async.activateOptions();
	Logger rootLogger = Logger.getRootLogger();
	rootLogger.addAppender(async);
	StringBuffer buf = new StringBuffer("Hello");
	synchronized (blockableAppender.getMonitor()) {
	    rootLogger.info(buf);
	    buf.append(", World.");
	}
	async.close();
	Vector events = blockableAppender.getVector();
	LoggingEvent event = (LoggingEvent) events.get(0);
	PatternLayout layout = new PatternLayout();
	layout.setConversionPattern("%m");
	layout.activateOptions();
	String msg = layout.format(event);
	assertEquals("Hello", msg);
    }

}
