/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (C) 2000-2002 The Apache Software Foundation.  All rights
 * reserved.
 * Copyright (C) 2010 John Lewis
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Ant" and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package net.sourceforge.cobertura.test;

import groovy.util.AntBuilder;
import groovy.util.Node;
import net.sourceforge.cobertura.ant.ReportTask;
import net.sourceforge.cobertura.test.util.TestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;
import org.junit.Test;

import java.io.File;
import java.util.HashMap;

import static org.junit.Assert.assertTrue;

public class IgnoreTrivialFunctionalTest extends AbstractCoberturaTestCase {
	AntBuilder ant = TestUtils.getCoberturaAntBuilder(TestUtils
			.getCoberturaClassDir());
	TestUtils testUtil = new TestUtils();
	Node dom;
	IgnoreUtil ignoreUtil;

	@Test
	public void ignoreTrivialTest() throws Exception {
		/*
		 * Use a temporary directory and create a Main.java source file that
		 * has trivial methods such as .
		 */
		File tempDir = TestUtils.getTempDir();
		File srcDir = new File(tempDir, "src");
		File reportDir = new File(tempDir, "report");
		File instrumentDir = new File(tempDir, "instrument");
		instrumentDir.mkdirs();

		File mainSourceFile = new File(srcDir, "mypackage/Main.java");
		File interfaceSourceFile = new File(srcDir,
				"mypackage/MyInterface.java");
		File datafile = new File(srcDir, "cobertura.ser");
		mainSourceFile.getParentFile().mkdirs();

		FileUtils.write(interfaceSourceFile, "\n package mypackage;" + "\n "
				+ "\n public interface MyInterface {"
				+ "\n 	public void myInterfaceMethod();" + "\n }");

		FileUtils
				.write(
						mainSourceFile,
						"\n package mypackage;"
								+ "\n "
								+ "\n public class Main extends Thread {"
								+ "\n 			"
								+ "\n 	public static class MyObject implements MyInterface"
								+ "\n 	{"
								+ "\n 		public void myInterfaceMethod()"
								+ "\n 		{"
								+ "\n 		}"
								+ "\n 	}"
								+ "\n "
								+ "\n 	public static void main(String[] args) {"
								+ "\n 		Main main = new Main();"
								+ "\n 			"
								+ "\n 		/*"
								+ "\n 		 * Call all methods so they will be considered \"covered\" unless"
								+ "\n 		 * they are ignored as trivial."
								+ "\n 		 *"
								+ "\n 		 * These are in no particular order."
								+ "\n 		 */"
								+ "\n 		main.getterTrivial();"
								+ "\n 		main.empty();"
								+ "\n 		main.getVoid();"
								+ "\n 		main.getIntWithIntParm(0);"
								+ "\n 		main.isBool();"
								+ "\n 		main.hasBool();"
								+ "\n 		main.set();"
								+ "\n 		main.setInt(1);"
								+ "\n 		main.setIntWithTwoParms(1, 2);"
								+ "\n 		main.getMultiDimArray();"
								+ "\n 		main.setIncrement(1);"
								+ "\n 		main.setConst(\"\");"
								+ "\n 		main.getArray();"
								+ "\n 		main.getObject();"
								+ "\n 		main.getStatic();"
								+ "\n 		main.setStatic(1);"
								+ "\n 		main.setISTORE(1);"
								+ "\n 		main.setLSTORE(1);"
								+ "\n 		main.setFSTORE((float)1.0);"
								+ "\n 		main.setDSTORE(1.0);"
								+ "\n 		main.setASTORE(null);"
								+ "\n 		main.getINVOKEVIRTUAL();"
								+ "\n 		main.getINVOKESPECIAL();"
								+ "\n 		main.getINVOKESTATIC();"
								+ "\n 		main.setINVOKEINTERFACE(new MyObject());"
								+ "\n 			"
								+ "\n 		// call constructors in no particular order"
								+ "\n 		new Main(1);"
								+ "\n 		new Main(true);"
								+ "\n 		new Main(\"str\");"
								+ "\n 		new Main(\"\", \"\");"
								+ "\n 		new Main(\"\", 0);"
								+ "\n 		new Main(\"\", true);"
								+ "\n 		new Main((Thread) null, \"string\");"
								+ "\n 		new Main((Thread) null, 0);"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// Be careful when initializing members.  If you instantiate an"
								+ "\n 	// object, then trivial constructors will become non-trivial."
								+ "\n 	// Ex. Integer myInteger = new Integer(1); will cause Main() to be non-trivial."
								+ "\n 	int myint;"
								+ "\n 	boolean mybool;"
								+ "\n 	private static int mystatic;"
								+ "\n 			"
								+ "\n 	// trivial constructor"
								+ "\n 	public Main() {"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	// constructors that just call super() are trivial"
								+ "\n 	public Main(Thread t, String str)"
								+ "\n 	{"
								+ "\n 		super(str);"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	// constructors that just call super() are usually trivial, but"
								+ "\n 	// this constructor uses a constant, so it is considered non-trivial."
								+ "\n 	public Main(Thread t, int i)"
								+ "\n 	{"
								+ "\n 		super(\"string\");"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public Main(boolean bool) {"
								+ "\n 		// non-trivial conditional"
								+ "\n 		myint = bool ? 0 : 1;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public Main(int num) {"
								+ "\n 		// non-trivial switch"
								+ "\n 		switch(num) {"
								+ "\n 			default:"
								+ "\n 		}"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public Main(String str) {"
								+ "\n 		// setting of statics is non-trivial"
								+ "\n 		mystatic = 2;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public Main(String str1, String str2)"
								+ "\n 	{"
								+ "\n 		// non-trivial method call"
								+ "\n 		privateMethod();"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	public Main(String str1, int i)"
								+ "\n 	{"
								+ "\n 		// non-trivial object construction"
								+ "\n 		new StringBuffer();"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public Main(String str1, boolean bool)"
								+ "\n 	{"
								+ "\n 		// non-trivial this() call"
								+ "\n 		this(str1, 0);"
								+ "\n 	}"
								+ "\n 	"
								+ "\n "
								+ "\n 	// trivial getter"
								+ "\n 	public int getterTrivial() {"
								+ "\n 		return myint;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	// trivial getter"
								+ "\n 	public boolean isBool() {"
								+ "\n 		return mybool;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	// trivial getter"
								+ "\n 	public boolean hasBool() {"
								+ "\n 		return mybool;"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	// trivial setter"
								+ "\n 	public void setInt(int i) {"
								+ "\n 		myint = i;"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	// this would be trivial, but it is a getter that with no return value"
								+ "\n 	public void getVoid() {"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	// \"empty\" does not start with \"get\", \"is\", \"has\", or \"set\", so"
								+ "\n 	// it is considered non-trivial."
								+ "\n 	private int empty() {"
								+ "\n 		return 0;"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	// this is a getter that takes a parameter, so it is non-trivial."
								+ "\n 	public int getIntWithIntParm(int i) {"
								+ "\n 		return 0;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	// this would be a trivial setter, but it does not have a parameter."
								+ "\n 	public void set() {"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	// this would be a trivial setter, but it has more than one parameter."
								+ "\n 	public void setIntWithTwoParms(int i, int j) {"
								+ "\n 		myint = i;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	public int[][] getMultiDimArray() {"
								+ "\n 		// non-trivial construction of a multi-dimensional array"
								+ "\n 		return new int[1][1];"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public void setIncrement(int i) {"
								+ "\n 		// non-trivial increment of local variable"
								+ "\n 		i++;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public void setConst(String str) {"
								+ "\n 		/*"
								+ "\n 		 * cause visitLdcInsn to be called because \"str\" is in the"
								+ "\n 		 * runtime constant pool.  An LDC operation is performed"
								+ "\n 		 * which is considered non-trivial."
								+ "\n 		 */"
								+ "\n 		System.out.println(\"str\");"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public int[] getArray() {"
								+ "\n 		// causes visitIntInsn to be called.  Creating an array is a \"single int operand\"."
								+ "\n 		// non-trivial."
								+ "\n 		return new int[1];"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public Object getObject() {"
								+ "\n 		// causes visitTypeInsn to be called.  Creating an object is a type instruction."
								+ "\n 		// non-trivial."
								+ "\n 		return new Object();"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public int getStatic() {"
								+ "\n 		// getting a static is non-trivial."
								+ "\n 		return mystatic;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	public void setStatic(int i) {"
								+ "\n 		// setting a static is non-trivial."
								+ "\n 		mystatic = i;"
								+ "\n 	}"
								+ "\n 		"
								+ "\n 	// non-trivial local variable instruction (causes visitVarInsn(ISTORE)) (int store to local var)"
								+ "\n 	public void setISTORE(int i) {"
								+ "\n 		i = 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial local variable instruction (causes visitVarInsn(LSTORE)) (long store to local var)"
								+ "\n 	public void setLSTORE(long l) {"
								+ "\n 		l = 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial local variable instruction (causes visitVarInsn(FSTORE)) (floating store to local var)"
								+ "\n 	public void setFSTORE(float f) {"
								+ "\n 		f = 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial local variable instruction (causes visitVarInsn(DSTORE)) (double store to local var)"
								+ "\n 	public void setDSTORE(double d) {"
								+ "\n 		d = 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial local variable instruction (causes visitVarInsn(ASTORE)) (object store to local var)"
								+ "\n 	public void setASTORE(Object obj) {"
								+ "\n 		obj = null;"
								+ "\n 	}"
								+ "\n 			"
								+ "\n 	public void publicMethod() {"
								+ "\n 	}"
								+ "\n 	private void privateMethod() {"
								+ "\n 	}"
								+ "\n 	public static void staticMethod() {"
								+ "\n 	}"
								+ "\n 	"
								+ "\n 	// non-trivial public method call (causes visitMethodInsn(INVOKEVIRTUAL))"
								+ "\n 	public int getINVOKEVIRTUAL() {"
								+ "\n 		publicMethod();"
								+ "\n 		return 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial private method call (causes visitMethodInsn(INVOKESPECIAL)) "
								+ "\n 	public int getINVOKESPECIAL() {"
								+ "\n 		privateMethod();"
								+ "\n 		return 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial static method call (causes visitMethodInsn(INVOKESTATIC)) "
								+ "\n 	public int getINVOKESTATIC() {"
								+ "\n 		staticMethod();"
								+ "\n 		return 0;"
								+ "\n 	}"
								+ "\n "
								+ "\n 	// non-trivial interface method call (causes visitMethodInsn(INVOKEINTERFACE))"
								+ "\n 	public void setINVOKEINTERFACE(MyInterface obj) {"
								+ "\n 		obj.myInterfaceMethod();" + "\n 	}"
								+ "\n }");

		TestUtils.compileSource(ant, srcDir);

		TestUtils.instrumentClasses(ant, srcDir, datafile, instrumentDir,
				new HashMap() {
					{
						put("ignoretrivial", true);
					}
				});

		/*
		 * Kick off the Main (instrumented) class.
		 */
		Path classpath = new Path(TestUtils.project);
		DirSet dirSetInstrumentDir = new DirSet();
		DirSet dirSetSrcDir = new DirSet();
		dirSetInstrumentDir.setDir(instrumentDir);
		dirSetSrcDir.setDir(srcDir);
		classpath.addDirset(dirSetInstrumentDir);
		classpath.addDirset(dirSetSrcDir);
		classpath.addDirset(TestUtils.getCoberturaClassDirSet());
		// This is needed to fix issue #213
		classpath.add(TestUtils.createDependencyPath("org.slf4j", "slf4j-api", "1.7.5"));

		Java java = new Java();
		java.setProject(TestUtils.project);
		java.setClassname("mypackage.Main");
		java.setDir(srcDir);
		java.setFork(true);
		java.setFailonerror(true);
		java.setClasspath(classpath);
		java.execute();

		/*
		 * Now create a cobertura xml file and make sure the correct counts are in it.
		 */
		ReportTask reportTask = new ReportTask();
		reportTask.setProject(TestUtils.project);
		reportTask.setDataFile(datafile.getAbsolutePath());
		reportTask.setFormat("xml");
		reportTask.setDestDir(srcDir);
		reportTask.execute();

		dom = TestUtils.getXMLReportDOM(srcDir.getAbsolutePath()
				+ "/coverage.xml");

		ignoreUtil = new IgnoreUtil("mypackage.Main", dom);

		// trivial empty constructor
		assertIgnored("<init>", "()V");

		// trivial constructor Main(Thread, String) that just calls super()
		assertIgnored("<init>", "(Ljava/lang/Thread;Ljava/lang/String;)V");

		// trivial getter
		assertIgnored("getterTrivial");

		// isBool is trivial
		assertIgnored("isBool");

		// hasBool is trivial
		assertIgnored("hasBool");

		// setInt is trivial
		assertIgnored("setInt");

		// Main(int) has non-trivial switch
		assertNotIgnored("<init>", "(I)V");

		// Main(boolean) has non-trivial conditional
		assertNotIgnored("<init>", "(Z)V");

		// "empty" does not start with "get", "is", "has", or "set".
		assertNotIgnored("empty");

		// gets with no return are considered non-trivial
		assertNotIgnored("getVoid");

		// gets that have parameters are considered non-trivial
		assertNotIgnored("getIntWithIntParm");

		// sets that have no parameters are considered non-trivial
		assertNotIgnored("set");

		// sets that have more than one parameters are considered non-trivial
		assertNotIgnored("setIntWithTwoParms");

		// don't ignore methods with multi-dimensional array creates
		assertNotIgnored("getMultiDimArray");

		// don't ignore methods with increment instructions for local variables
		assertNotIgnored("setIncrement");

		// don't ignore methods with LDC instructions (that use constants from the runtime pool)
		assertNotIgnored("setConst");
		assertNotIgnored("<init>", "(Ljava/lang/Thread;I)V"); // Main(Thread, int)

		// don't ignore methods with a single int operand (like creating an array).
		assertNotIgnored("getArray");

		// don't ignore methods with type instructions (like creating an object).
		assertNotIgnored("getObject");

		// don't ignore methods that use statics.
		assertNotIgnored("getStatic");
		assertNotIgnored("setStatic");
		assertNotIgnored("<init>", "(Ljava/lang/String;)V");

		// non-trivial local variable instructions (causes visitVarInsn call)
		assertNotIgnored("setISTORE");
		assertNotIgnored("setLSTORE");
		assertNotIgnored("setFSTORE");
		assertNotIgnored("setDSTORE");
		assertNotIgnored("setASTORE");

		// non-trivial method calls
		assertNotIgnored("getINVOKEVIRTUAL");
		assertNotIgnored("getINVOKESPECIAL");
		assertNotIgnored("getINVOKESTATIC");
		assertNotIgnored("setINVOKEINTERFACE");
		assertNotIgnored("<init>", "(Ljava/lang/String;Ljava/lang/String;)V"); // Main(String, String)
		assertNotIgnored("<init>", "(Ljava/lang/String;I)V"); // Main(String, int)
		assertNotIgnored("<init>", "(Ljava/lang/String;Z)V"); // Main(String, boolean)

		/*
		 * Now create a cobertura html report and make sure the files are created.
		 */
		reportTask = new ReportTask();
		reportTask.setProject(TestUtils.project);
		reportTask.setDataFile(datafile.getAbsolutePath());
		reportTask.setFormat("html");
		reportTask.setDestDir(reportDir);
		reportTask.setSrcDir(srcDir.getAbsolutePath());
		reportTask.execute();

		assertTrue(new File(reportDir, "index.html").exists());
		assertTrue(new File(reportDir, "mypackage.Main.html").exists());
		assertTrue(new File(reportDir, "mypackage.MyInterface.html").exists());

		File frameSummaryFile = new File(reportDir, "frame-summary.html");
		assertTrue(frameSummaryFile.exists());

		TestUtils.checkFrameSummaryHtmlFile(frameSummaryFile);
	}

	public void assertIgnored(String methodName, String signature) {
		ignoreUtil.assertIgnored(methodName, signature);
	}

	public void assertIgnored(String methodName) {
		assertIgnored(methodName, null);
	}

	public void assertNotIgnored(String methodName, String signature) {
		ignoreUtil.assertNotIgnored(methodName, signature);
	}

	public void assertNotIgnored(String methodName) {
		assertNotIgnored(methodName, null);
	}
}
