changeset 586:1fe2a4f7981f

Added jacoco code coverage support Note - in time of this commit one needs custom build of jacoco containing the xboot.patch form approving discussion: http://mail.openjdk.java.net/pipermail/distro-pkg-dev/attachments/20121127/ba8f6a1e/xboot-0001.patch The tracking of upstraming can be found on jacoco page.
author Jiri Vanek <jvanek@redhat.com>
date Tue, 11 Dec 2012 20:32:26 +0100
parents d8b97eea56e3
children e04380d3306f
files ChangeLog Makefile.am configure.ac plugin/icedteanp/IcedTeaNPPlugin.cc tests/jacoco-operator/org/jacoco/operator/Main.java tests/jacoco-operator/org/jacoco/operator/MergeTask.java tests/jacoco-operator/org/jacoco/operator/ReportGenerator.java
diffstat 7 files changed, 1055 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Dec 11 14:11:39 2012 -0500
+++ b/ChangeLog	Tue Dec 11 20:32:26 2012 +0100
@@ -1,3 +1,35 @@
+2012-12-11  Jiri Vanek <jvanek@redhat.com>
+
+	Added jacoco code coverage support
+	* Makefile.am: (JACOCO_*) bunch of new variables encapsulating jacoco
+	files. (PLUGIN_COVERAGE_BOOTCLASSPATH) classpath to be used in plugin
+	instead of normal one in coverage mode. (COVERABLE_PLUGIN_DIR) for
+	compiling plugin with agent on. (jacoco-operator-source-files.txt) for
+	storing files of reporting tool. All XSLTPROC command were done as non-fatal
+	(stamps/compile-jacoco-operator.stamp) for compiling report operator.
+	(stamps/run-unit-test-code-coverage-jacoco.stam) for cover unittests
+	Set of (COVERABLE_PLUGIN_*) targets to compile plugin with agent on.
+	(stamps/build-fake-plugin.stamp) top level target for fake plugin.
+	(stamps/run-reproducers-test-code-coverage-jacoco.stamp) target for
+	cover reproducers. (run-test-code-coverage-jacoco) for merged coverage
+	(clean-unit-test-code-coverage-jacoco) and
+	(clean-reproducers-test-code-coverage-jacoco) and 
+	(clean-test-code-coverage-jacoco) and 
+	(clean-test-code-coverage-tools-jacoco) cleaning targets.
+	(run-reproducers-test-code-coverage-jacoco) and
+	(run-unit-test-code-coverage-jacoco) as top level aliases.
+	* configure.ac: added check for jacoco library and asm library
+	* plugin/icedteanp/IcedTeaNPPlugin.cc removed duplicate code
+	(plugin_start_appletviewe) removed duplicated code and added handling of
+	java agent if defined.
+	* tests/jacoco-operator/org/jacoco/operator/Main.java : New class,
+	comamndline tool for merging results and for generating reports.
+	* tests/jacoco-operator/org/jacoco/operator/MergeTask.java: New class.
+	Utility class responsible for merging exec results to one exec file.
+	* tests/jacoco-operator/org/jacoco/operator/ReportGenerator: New class.
+	Utility method for gathering sources and builds and outputing xml and
+	html reports.
+
 2012-12-11  Adam Domurad  <adomurad@redhat.com>
 
 	* plugin/icedteanp/java/sun/applet/PluginParameterParser.java: 
--- a/Makefile.am	Tue Dec 11 14:11:39 2012 -0500
+++ b/Makefile.am	Tue Dec 11 20:32:26 2012 +0100
@@ -20,6 +20,8 @@
 export JUNIT_RUNNER_DIR=$(TESTS_DIR)/junit-runner
 export JUNIT_RUNNER_SRCDIR=$(TESTS_SRCDIR)/junit-runner
 
+export JACOCO_OPERATOR_DIR=$(TESTS_DIR)/jacoco-operator
+export JACOCO_OPERATOR_SRCDIR=$(TESTS_SRCDIR)/jacoco-operator
 
 export TEST_EXTENSIONS_SRCDIR=$(TESTS_SRCDIR)/test-extensions
 export TEST_EXTENSIONS_TESTS_SRCDIR=$(TESTS_SRCDIR)/test-extensions-tests
@@ -56,6 +58,22 @@
 export ALL_NONCUSTOM_REPRODUCERS=$(SIMPLE_REPRODUCERS) $(SIGNED_REPRODUCERS)
 export ALL_REPRODUCERS=$(ALL_NONCUSTOM_REPRODUCERS) $(CUSTOM_REPRODUCERS)
 
+export JACOCO_PATH:=$(shell dirname "$(JACOCO_JAR)")
+export JACOCO_AGENT=org.jacoco.agent.jar
+export JACOCO_ANT=org.jacoco.ant.jar
+export JACOCO_REPORT=org.jacoco.report.jar
+export JACOCO_AGENTRT=org.jacoco.agent.rt.jar
+export JACOCO_CORE=org.jacoco.core.jar
+export JACOCO_JAVAWS_RESULTS=$(TEST_EXTENSIONS_DIR)/jacoco_javaws.exec
+export JACOCO_PLUGIN_RESULTS=$(TEST_EXTENSIONS_DIR)/jacoco_plugin.exec
+export JACOCO_CLASSPATH=$(JACOCO_PATH)/$(JACOCO_CORE):$(JACOCO_PATH)/$(JACOCO_AGENT):$(JACOCO_PATH)/$(JACOCO_REPORT):$(JACOCO_PATH)/$(JACOCO_AGENTRT):$(JACOCO_PATH)/$(JACOCO_ANT):$(ASM_JAR)
+export JACOCO_AGENT_SWITCH_BODY=-javaagent:$(JACOCO_PATH)/$(JACOCO_AGENTRT)
+export JACOCO_BASE_EXCLUDE=org.junit.*:junit.*
+export JACOCO_ADVANCED_EXCLUDE=:*jacoco*:java.lang.*:java.reflect.*:java.util.*:sun.reflect.*
+export JACOCO_AGENT_SWITCH="$(JACOCO_AGENT_SWITCH_BODY)=excludes=$(JACOCO_BASE_EXCLUDE)"
+export JACOCO_AGENT_JAVAWS_SWITCH=\"$(JACOCO_AGENT_SWITCH_BODY)=excludes=$(JACOCO_BASE_EXCLUDE)$(JACOCO_ADVANCED_EXCLUDE),xboot=true,destfile=$(JACOCO_JAVAWS_RESULTS)\"
+export JACOCO_AGENT_PLUGIN_SWITCH=\"$(JACOCO_AGENT_SWITCH_BODY)=excludes=$(JACOCO_BASE_EXCLUDE)$(JACOCO_ADVANCED_EXCLUDE),xboot=true,destfile=$(JACOCO_PLUGIN_RESULTS)\"
+export JACOCO_OPERATOR_EXEC=$(BOOT_DIR)/bin/java $(EMMA_JAVA_ARGS) -cp $(JACOCO_OPERATOR_DIR):$(JACOCO_CLASSPATH):. org.jacoco.operator.Main
 
 # linking variables
 export PLUGIN_LINK_NAME=libjavaplugin.so
@@ -103,6 +121,7 @@
 #
 export LAUNCHER_BOOTCLASSPATH="-Xbootclasspath/a:$(datadir)/$(PACKAGE_NAME)/netx.jar$(RHINO_RUNTIME)"
 export PLUGIN_BOOTCLASSPATH='"-Xbootclasspath/a:$(datadir)/$(PACKAGE_NAME)/netx.jar:$(datadir)/$(PACKAGE_NAME)/plugin.jar$(RHINO_RUNTIME)"'
+export PLUGIN_COVERAGE_BOOTCLASSPATH='"-Xbootclasspath/a:$(datadir)/$(PACKAGE_NAME)/netx.jar:$(datadir)/$(PACKAGE_NAME)/plugin.jar$(RHINO_RUNTIME):$(JACOCO_CLASSPATH)"'
 
 # Fake update version to work with the Deployment Toolkit script used by Oracle
 # http://download.oracle.com/javase/tutorial/deployment/deploymentInDepth/depltoolkit_index.html
@@ -128,6 +147,8 @@
 export LIVECONNECT_SRCS = $(PLUGIN_SRCDIR)/java
 export ICEDTEAPLUGIN_TARGET = $(PLUGIN_DIR)/$(BUILT_PLUGIN_LIBRARY) stamps/liveconnect-dist.stamp
 export PLUGIN_PKGS = sun.applet netscape.security netscape.javascript
+#this is for plugin testcoverage
+export COVERABLE_PLUGIN_DIR=$(TESTS_DIR)/icedteanp-build-with-jacoco
 endif
 
 if CP_SUPPORTS_REFLINK
@@ -588,6 +609,9 @@
 junit-runner-source-files.txt:
 	find $(JUNIT_RUNNER_SRCDIR) -name '*.java' | sort > $@
 
+jacoco-operator-source-files.txt:
+	find $(JACOCO_OPERATOR_SRCDIR) -name '*.java' | sort > $@
+
 $(JUNIT_RUNNER_JAR): junit-runner-source-files.txt stamps/test-extensions-compile.stamp
 	mkdir -p $(JUNIT_RUNNER_DIR) && \
 	$(BOOT_DIR)/bin/javac $(IT_JAVACFLAGS) \
@@ -825,8 +849,8 @@
 	  $(BOOT_DIR)/bin/java $(REPRODUCERS_DPARAMETERS) \
 	 -Xbootclasspath:$(RUNTIME) CommandLine $$class_names
 if WITH_XSLTPROC
-	$(XSLTPROC)  --stringparam logs logs_reproducers.html $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/jreport.xsl $(TEST_EXTENSIONS_DIR)/tests-output.xml > $(TESTS_DIR)/index_reproducers.html
-	$(XSLTPROC)  $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/logs.xsl $(TEST_EXTENSIONS_DIR)/ServerAccess-logs.xml > $(TESTS_DIR)/logs_reproducers.html
+	-$(XSLTPROC)  --stringparam logs logs_reproducers.html $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/jreport.xsl $(TEST_EXTENSIONS_DIR)/tests-output.xml > $(TESTS_DIR)/index_reproducers.html
+	-$(XSLTPROC)  $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/logs.xsl $(TEST_EXTENSIONS_DIR)/ServerAccess-logs.xml > $(TESTS_DIR)/logs_reproducers.html
 endif
 	touch $@
 
@@ -1002,8 +1026,8 @@
 	CLASSPATH=$(NETX_DIR)/lib/classes.jar:$(abs_top_builddir)/liveconnect/lib/classes.jar:$(JUNIT_JAR):$(JUNIT_RUNNER_JAR):$(TEST_EXTENSIONS_DIR):. \
 	  $(BOOT_DIR)/bin/java -Xbootclasspath:$(RUNTIME) CommandLine $$class_names 
 if WITH_XSLTPROC
-	$(XSLTPROC) --stringparam logs logs_unit.html $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/jreport.xsl $(NETX_UNIT_TEST_DIR)/tests-output.xml > $(TESTS_DIR)/index_unit.html
-	$(XSLTPROC) $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/logs.xsl $(NETX_UNIT_TEST_DIR)/ServerAccess-logs.xml > $(TESTS_DIR)/logs_unit.html
+	-$(XSLTPROC) --stringparam logs logs_unit.html $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/jreport.xsl $(NETX_UNIT_TEST_DIR)/tests-output.xml > $(TESTS_DIR)/index_unit.html
+	-$(XSLTPROC) $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/logs.xsl $(NETX_UNIT_TEST_DIR)/ServerAccess-logs.xml > $(TESTS_DIR)/logs_unit.html
 endif
 	mkdir -p stamps && \
 	touch $@
@@ -1049,6 +1073,53 @@
 endif 
 	touch $@
 
+stamps/compile-jacoco-operator.stamp: jacoco-operator-source-files.txt
+if WITH_JACOCO	
+	mkdir -p $(JACOCO_OPERATOR_DIR) && \
+	$(BOOT_DIR)/bin/javac $(IT_JAVACFLAGS) \
+	  -d $(JACOCO_OPERATOR_DIR) \
+	  -classpath $(JACOCO_CLASSPATH) \
+	  @jacoco-operator-source-files.txt ;
+else 
+	echo "Sorry, jacoco coverage report generator  cant be compiled without jacoco installed. Try installing jacoco or specify with-jacoco value" ;
+	exit 5
+endif 
+	touch $@
+
+
+#warning,  during this target tests.build/netx/unit/tests-output.xml is backup and rewriten (but not coresponding html file)
+#xml results run with jacoco agent however, can be wrong, co the new tests-output.xml is then renamed and orginal one restored
+stamps/run-unit-test-code-coverage-jacoco.stamp: stamps/netx-unit-tests-compile.stamp $(JUNIT_RUNNER_JAR)  \
+ $(TESTS_DIR)/$(REPORT_STYLES_DIRNAME)  $(UNIT_CLASS_NAMES) stamps/compile-jacoco-operator.stamp
+if WITH_JACOCO
+	filename=" " ; \
+	cd $(NETX_UNIT_TEST_SRCDIR) ; \
+	for file in `find . -type f \! -iname "*.java"`; do\
+		filename=`echo $$file `; \
+		cp --parents $$filename $(NETX_UNIT_TEST_DIR) ; \
+	done ; \
+	cd $(NETX_UNIT_TEST_DIR) ; \
+	for file in $(EMMA_MODIFIED_FILES) ; do \
+	  mv $(NETX_UNIT_TEST_DIR)/$$file  $(NETX_UNIT_TEST_DIR)/"$$file""$(EMMA_BACKUP_SUFFIX)" ; \
+	done ;\
+	class_names=`cat $(UNIT_CLASS_NAMES)` ; \
+	CLASSPATH=$(NETX_DIR)/lib/classes.jar:$(abs_top_builddir)/liveconnect/lib/classes.jar:$(JUNIT_JAR):$(JUNIT_RUNNER_JAR):$(TEST_EXTENSIONS_DIR):$(JACOCO_CLASSPATH):.  \
+	  $(BOOT_DIR)/bin/java $(JACOCO_AGENT_SWITCH) -Xbootclasspath:$(RUNTIME)  CommandLine $$class_names ; \
+	for file in $(EMMA_MODIFIED_FILES) ; do \
+	  mv $(NETX_UNIT_TEST_DIR)/$$file  $(NETX_UNIT_TEST_DIR)/"$$file""$(EMMA_SUFFIX)" ; \
+	  mv $(NETX_UNIT_TEST_DIR)/"$$file""$(EMMA_BACKUP_SUFFIX)"  $(NETX_UNIT_TEST_DIR)/$$file ; \
+	done ; \
+	$(JACOCO_OPERATOR_EXEC) \
+	  report --die-soon --html-output coverage --xml-output coverage.xml --input-file jacoco.exec \
+	  --input-srcs  $(NETX_SRCDIR) $(PLUGIN_SRCDIR)/java $(NETX_UNIT_TEST_SRCDIR) $(JUNIT_RUNNER_SRCDIR) $(TEST_EXTENSIONS_SRCDIR) \
+	  --input-builds $(NETX_DIR)/lib/classes.jar $(abs_top_builddir)/liveconnect/lib/classes.jar $(NETX_UNIT_TEST_DIR) $(JUNIT_RUNNER_JAR)  $(TEST_EXTENSIONS_DIR) \
+	  --title "IcedTea-Web unit-tests codecoverage" ;
+else 
+	echo "Sorry, coverage report cant be run without jacoco installed. Try installing jacoco or specify with-jacoco value" ;
+	exit 5
+endif 
+	touch $@
+
 #warning,  during this target tests.build/netx/jnlp_testsengine/tests-output.xml is backup and rewriten (but not coresponding html file)
 #xml results run from emma sandbox, however, can be wrong, co the new tests-output.xml is then renamed and orginal one restored
 stamps/run-reproducers-test-code-coverage.stamp: stamps/run-netx-dist-tests.stamp $(REPRODUCERS_CLASS_NAMES)
@@ -1139,7 +1210,145 @@
 endif
 	touch $@
 
-run-test-code-coverage: run-unit-test-code-coverage run-reproducers-test-code-coverage
+$(COVERABLE_PLUGIN_DIR):
+	mkdir -p $(COVERABLE_PLUGIN_DIR);
+	
+$(COVERABLE_PLUGIN_DIR)/%.o: $(PLUGIN_SRCDIR)/%.cc
+	cd $(COVERABLE_PLUGIN_DIR) && \
+	$(CXX) $(CXXFLAGS) \
+	   $(DEFS) $(VERSION_DEFS) \
+	  -DJDK_UPDATE_VERSION="\"$(JDK_UPDATE_VERSION)\"" \
+	  -DPLUGIN_NAME="\"IcedTea-Web Plugin with jacoco coverage agent\"" \
+	  -DPLUGIN_VERSION="\"$(PLUGIN_VERSION)\"" \
+	  -DPACKAGE_URL="\"$(PACKAGE_URL)\"" \
+	  -DMOZILLA_VERSION_COLLAPSED="$(MOZILLA_VERSION_COLLAPSED)" \
+	  -DICEDTEA_WEB_JRE="\"$(SYSTEM_JRE_DIR)\"" \
+	  -DPLUGIN_BOOTCLASSPATH=$(PLUGIN_COVERAGE_BOOTCLASSPATH) \
+	  -DCOVERAGE_AGENT=$(JACOCO_AGENT_PLUGIN_SWITCH) \
+	  $(GLIB_CFLAGS) \
+	  $(GTK_CFLAGS) \
+	  $(MOZILLA_CFLAGS) \
+	  -fvisibility=hidden \
+	  -fPIC -o $@ -c $<
+
+$(COVERABLE_PLUGIN_DIR)/$(BUILT_PLUGIN_LIBRARY): $(addprefix $(COVERABLE_PLUGIN_DIR)/,$(PLUGIN_OBJECTS))
+	cd $(COVERABLE_PLUGIN_DIR) && \
+	$(CXX) $(CXXFLAGS) \
+	  $(PLUGIN_OBJECTS) \
+	  $(GLIB_LIBS) \
+	  $(GTK_LIBS) \
+	  $(MOZILLA_LIBS) \
+	  -shared -o $@
+
+stamps/build-fake-plugin.stamp:  $(COVERABLE_PLUGIN_DIR)  $(addprefix $(PLUGIN_SRCDIR)/,$(PLUGIN_SRC)) $(addprefix $(COVERABLE_PLUGIN_DIR)/,$(PLUGIN_OBJECTS)) stamps/liveconnect-dist.stamp  $(COVERABLE_PLUGIN_DIR)/$(BUILT_PLUGIN_LIBRARY)
+	touch $@
+
+#warning,  during this target tests.build/netx/jnlp_testsengine/tests-output.xml is backup and rewriten (but not coresponding html file)
+#xml results run with jacoco agent, however, can be wrong, co the new tests-output.xml is then renamed and orginal one restored
+stamps/run-reproducers-test-code-coverage-jacoco.stamp: stamps/run-netx-dist-tests.stamp $(REPRODUCERS_CLASS_NAMES) \
+stamps/compile-jacoco-operator.stamp stamps/build-fake-plugin.stamp
+if WITH_JACOCO
+	cd $(TESTS_DIR) ; \
+	for file in $(EMMA_MODIFIED_FILES) ; do \
+	  mv $(TEST_EXTENSIONS_DIR)/$$file $(TEST_EXTENSIONS_DIR)/"$$file""$(EMMA_BACKUP_SUFFIX)" ; \
+	done ;\
+	echo "backuping javaws in $(DESTDIR)$(bindir)" ; \
+	javaws_backup=$(DESTDIR)$(bindir)/javaws_backup ; \
+	mv $(DESTDIR)$(bindir)/javaws $$javaws_backup ;  \
+	echo "patching $(javaws)" ; \
+	nw_bootclasspath="$(LAUNCHER_BOOTCLASSPATH):$(JACOCO_CLASSPATH)" ; \
+	cat $$javaws_backup | sed "s|COMMAND.k.=\"..JAVA.\"|COMMAND[k]=\"\\$$\\{JAVA\\}\" ;  k=1 ; COMMAND[k]=$(JACOCO_AGENT_JAVAWS_SWITCH)|" | sed "s,$(LAUNCHER_BOOTCLASSPATH),$$nw_bootclasspath," > $(DESTDIR)$(bindir)/$(javaws) ; \
+	chmod 777 $(DESTDIR)$(bindir)/$(javaws) ; \
+	echo "backuping plugin in $(DESTDIR)/$(libdir)$(BUILT_PLUGIN_LIBRARY)" ; \
+	plugin_backup=$(DESTDIR)$(libdir)/$(BUILT_PLUGIN_LIBRARY)_backup ; \
+	mv $(DESTDIR)$(libdir)/$(BUILT_PLUGIN_LIBRARY) $$plugin_backup ;  \
+	echo "fakeing plugin" ; \
+	cp $(COVERABLE_PLUGIN_DIR)/$(BUILT_PLUGIN_LIBRARY) $(DESTDIR)$(libdir)/$(BUILT_PLUGIN_LIBRARY) ; \
+	testcases_srcs=( ) ; \
+	k=0 ; \
+	types=($(ALL_REPRODUCERS)); \
+	for which in "$${types[@]}" ; do \
+	  . $(abs_top_srcdir)/NEW_LINE_IFS ; \
+	  simpleReproducers=(`cat $(abs_top_builddir)/junit-jnlp-dist-$$which.txt `); \
+	  IFS="$$IFS_BACKUP" ; \
+	  for dir in "$${simpleReproducers[@]}" ; do \
+	    testcases_srcs[k]="$(REPRODUCERS_TESTS_SRCDIR)/$$which/$$dir/testcases/" ; \
+	    k=$$((k+1)) ; \
+	    done ; \
+	  done ; \
+	cd $(TEST_EXTENSIONS_DIR) ; \
+	class_names=`cat $(REPRODUCERS_CLASS_NAMES)` ; \
+	CLASSPATH=$(NETX_DIR)/lib/classes.jar:$(JUNIT_JAR):$(JUNIT_RUNNER_JAR):.:$(TEST_EXTENSIONS_DIR):$(JACOCO_CLASSPATH):$(TEST_EXTENSIONS_TESTS_DIR) \
+	  $(BOOT_DIR)/bin/java $(JACOCO_AGENT_SWITCH) $(REPRODUCERS_DPARAMETERS) \
+	 -Xbootclasspath:$(RUNTIME) CommandLine $$class_names ; \
+	if [ -f $(JACOCO_JAVAWS_RESULTS) ] ; then \
+	  jacoco_javaws_results=$(JACOCO_JAVAWS_RESULTS) ; \
+	  $(JACOCO_OPERATOR_EXEC) \
+	    report --die-soon --html-output coverage-javaws --xml-output coverage-javaws.xml --input-file $(JACOCO_JAVAWS_RESULTS) \
+	    --input-srcs  $(NETX_SRCDIR) \
+	    --input-builds $(NETX_DIR)/lib/classes.jar \
+	    --title "IcedTea-Web javaws reproducers codecoverage" ; \
+	fi; \
+	if [ -f $(JACOCO_PLUGIN_RESULTS) ] ; then \
+	  jacoco_plugin_results=$(JACOCO_PLUGIN_RESULTS) ; \
+	  $(JACOCO_OPERATOR_EXEC) \
+	    report --die-soon --html-output coverage-plugin --xml-output coverage-plugin.xml --input-file $(JACOCO_PLUGIN_RESULTS) \
+	    --input-srcs  $(NETX_SRCDIR) $(PLUGIN_SRCDIR)/java \
+	    --input-builds $(NETX_DIR)/lib/classes.jar $(abs_top_builddir)/liveconnect/lib/classes.jar \
+	    --title "IcedTea-Web plugin reproducers codecoverage" ; \
+	fi; \
+	$(JACOCO_OPERATOR_EXEC) \
+	  merge --die-soon --input-files jacoco.exec $$jacoco_javaws_results $$jacoco_plugin_results  --output-file jacoco-merged-reproducers.exec ; \
+	$(JACOCO_OPERATOR_EXEC) \
+	  report --html-output coverage --xml-output coverage.xml --input-file jacoco-merged-reproducers.exec \
+	  --input-srcs  $(NETX_SRCDIR) $(PLUGIN_SRCDIR)/java  $(JUNIT_RUNNER_SRCDIR) $(TEST_EXTENSIONS_SRCDIR) $(TEST_EXTENSIONS_TESTS_SRCDIR) "$${testcases_srcs[@]}" \
+	  --input-builds $(NETX_DIR)/lib/classes.jar $(abs_top_builddir)/liveconnect/lib/classes.jar  $(JUNIT_RUNNER_JAR)  $(TEST_EXTENSIONS_DIR) $(TEST_EXTENSIONS_TESTS_DIR) \
+	  --title "IcedTea-Web reproducers-tests codecoverage" ; \
+	echo "restoring javaws in $(DESTDIR)$(bindir)" ; \
+	rm -f $(DESTDIR)$(bindir)/$(javaws);  \
+	mv $$javaws_backup $(DESTDIR)$(bindir)/$(javaws);  \
+	echo "restoring  plugin in $(DESTDIR)/$(libdir)$(BUILT_PLUGIN_LIBRARY)" ; \
+	mv $$plugin_backup $(DESTDIR)$(libdir)/$(BUILT_PLUGIN_LIBRARY) ; \
+	for file in $(EMMA_MODIFIED_FILES) ; do \
+	  mv $(TEST_EXTENSIONS_DIR)/$$file  $(TEST_EXTENSIONS_DIR)/"$$file""$(EMMA_SUFFIX)" ; \
+	  mv $(TEST_EXTENSIONS_DIR)/"$$file""$(EMMA_BACKUP_SUFFIX)"  $(TEST_EXTENSIONS_DIR)/$$file ; \
+	done ;
+else
+	echo "Sorry, coverage report cant be run without jacoco installed. Try installing jacoco or specify with-jacoco value" ;
+	exit 5
+endif
+	touch $@
+
+run-test-code-coverage-jacoco: stamps/run-unit-test-code-coverage-jacoco.stamp stamps/run-reproducers-test-code-coverage-jacoco.stamp
+if WITH_JACOCO
+	cd $(TESTS_DIR) ; \
+	k=0 ; \
+	types=($(ALL_REPRODUCERS)); \
+	for which in "$${types[@]}" ; do \
+	  . $(abs_top_srcdir)/NEW_LINE_IFS ; \
+	  simpleReproducers=(`cat $(abs_top_builddir)/junit-jnlp-dist-$$which.txt `); \
+	  IFS="$$IFS_BACKUP" ; \
+	  for dir in "$${simpleReproducers[@]}" ; do \
+	    testcases_srcs[k]="$(REPRODUCERS_TESTS_SRCDIR)/$$which/$$dir/testcases/" ; \
+	    k=$$((k+1)) ; \
+	    done ; \
+	  done ; \
+	class_names=`cat $(REPRODUCERS_CLASS_NAMES)` ; \
+	$(JACOCO_OPERATOR_EXEC) \
+	  merge --die-soon --input-files $(TEST_EXTENSIONS_DIR)/jacoco-merged-reproducers.exec $(NETX_UNIT_TEST_DIR)/jacoco.exec  --output-file  jacoco-merged.exec; \
+	$(JACOCO_OPERATOR_EXEC) \
+	  report --html-output coverage --xml-output coverage.xml --input-file jacoco-merged.exec \
+	  --input-srcs  $(NETX_SRCDIR) $(PLUGIN_SRCDIR)/java  $(JUNIT_RUNNER_SRCDIR) $(TEST_EXTENSIONS_SRCDIR) $(TEST_EXTENSIONS_TESTS_SRCDIR) "$${testcases_srcs[@]}" \
+	  --input-builds $(NETX_DIR)/lib/classes.jar $(abs_top_builddir)/liveconnect/lib/classes.jar  $(JUNIT_RUNNER_JAR)  $(TEST_EXTENSIONS_DIR) $(TEST_EXTENSIONS_TESTS_DIR) \
+	  --input-srcs  $(NETX_UNIT_TEST_SRCDIR)  \
+	  --input-builds $(NETX_UNIT_TEST_DIR)  \
+	  --title "IcedTea-Web complete codecoverage" ; 
+else
+	echo "Sorry, coverage report cant be run without jacoco installed. Try installing jacoco or specify with-jacoco value" ;
+	exit 5
+endif
+
+run-test-code-coverage: stamps/run-unit-test-code-coverage.stamps stamps/run-reproducers-test-code-coverage.stamps
 if WITH_EMMA
 	cd $(TESTS_DIR) ; \
 	k=0 ; \
@@ -1175,7 +1384,7 @@
 	exit 5
 endif
 
-clean-netx-tests: clean-netx-unit-tests clean-junit-runner clean-netx-dist-tests clean-test-code-coverage
+clean-netx-tests: clean-netx-unit-tests clean-junit-runner clean-netx-dist-tests clean-test-code-coverage-jacoco clean-test-code-coverage
 	if [ -e $(TESTS_DIR)/netx ]; then \
 	  rmdir $(TESTS_DIR)/netx ; \
 	fi
@@ -1258,6 +1467,43 @@
 	  rm -f $(TESTS_DIR)/coverage.em ; \
 	fi
 
+clean-unit-test-code-coverage-jacoco:
+	if [ -e stamps/run-unit-test-code-coverage-jacoco.stamp ]; then \
+	  rm -rf $(NETX_UNIT_TEST_DIR)/coverage ; \
+	  rm -f $(NETX_UNIT_TEST_DIR)/coverage.xml ; \
+	  rm -f $(NETX_UNIT_TEST_DIR)/jacoco.exec ; \
+	  rm -f $(NETX_UNIT_TEST_DIR)/tests-output_withEmma.xml ; \
+	  rm -f stamps/run-unit-test-code-coverage-jacoco.stamp ; \
+	fi	
+
+clean-reproducers-test-code-coverage-jacoco:
+	if [ -e stamps/run-reproducers-test-code-coverage-jacoco.stamp ]; then \
+	 rm -rf $(TEST_EXTENSIONS_DIR)/coverage-javaws ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/coverage-javaws.xml ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/jacoco_javaws.exec ; \
+	 rm -rf $(TEST_EXTENSIONS_DIR)/coverage-plugin ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/coverage-plugin.xml ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/jacoco_plugin.exec ; \
+	 rm -rf $(TEST_EXTENSIONS_DIR)/coverage ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/coverage.xml ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/jacoco-merged-reproducers.exec ; \
+	 rm -f $(TEST_EXTENSIONS_DIR)/tests-output_withEmma.xml ; \
+	 rm -f stamps/run-reproducers-test-code-coverage-jacoco.stamp ; \
+	fi
+
+clean-test-code-coverage-jacoco: clean-unit-test-code-coverage-jacoco clean-reproducers-test-code-coverage-jacoco clean-test-code-coverage-tools-jacoco
+	if [ -e $(TESTS_DIR)/coverage.xml ]; then \
+	  rm -rf $(TESTS_DIR)/coverage  ; \
+	  rm -f $(TESTS_DIR)/jacoco-merged.exec; \
+	fi
+
+clean-test-code-coverage-tools-jacoco:
+	rm -rf  $(JACOCO_OPERATOR_DIR)
+	rm -rf  $(COVERABLE_PLUGIN_DIR)
+	rm -f stamps/compile-jacoco-operator.stamp;
+	rm -f jacoco-operator-source-files.txt
+	rm -f stamps/build-fake-plugin.stamp
+
 
 # plugin tests
 
@@ -1377,3 +1623,7 @@
 run-unit-test-code-coverage: stamps/run-unit-test-code-coverage.stamp
 
 run-reproducers-test-code-coverage: stamps/run-reproducers-test-code-coverage.stamp
+
+run-unit-test-code-coverage-jacoco: stamps/run-unit-test-code-coverage-jacoco.stamp
+
+run-reproducers-test-code-coverage-jacoco: stamps/run-reproducers-test-code-coverage-jacoco.stamp
--- a/configure.ac	Tue Dec 11 14:11:39 2012 -0500
+++ b/configure.ac	Tue Dec 11 20:32:26 2012 +0100
@@ -106,6 +106,10 @@
     [/usr/share/java/junit4.jar /usr/share/junit-4/lib/junit.jar])
 IT_FIND_OPTIONAL_JAR([emma], EMMA,
     [/usr/share/java/emma.jar])
+IT_FIND_OPTIONAL_JAR([jacoco], JACOCO,
+    [/usr/share/java/jacoco/org.jacoco.core.jar])
+IT_FIND_OPTIONAL_JAR([asm], ASM,
+    [/usr/share/java/objectweb-asm4/asm-all-4.0.jar /usr/share/java/objectweb-asm/asm-all.jar])
 
 AC_CONFIG_FILES([jrunscript], [chmod u+x jrunscript])
 AC_CONFIG_FILES([build.properties])
--- a/plugin/icedteanp/IcedTeaNPPlugin.cc	Tue Dec 11 14:11:39 2012 -0500
+++ b/plugin/icedteanp/IcedTeaNPPlugin.cc	Tue Dec 11 20:32:26 2012 +0100
@@ -1594,20 +1594,28 @@
   gchar** environment = NULL;
   std::vector<std::string*>* jvm_args = get_jvm_args();
 
+  // Construct command line parameters
+
+  command_line.push_back(appletviewer_executable);
+
+  //Add JVM args to command_line
+  for (int i = 0; i < jvm_args->size(); i++)
+  {
+    command_line.push_back(*jvm_args->at(i));
+  }
+
+  command_line.push_back(PLUGIN_BOOTCLASSPATH);
+  // set the classpath to avoid using the default (cwd).
+  command_line.push_back("-classpath");
+  command_line.push_back(ICEDTEA_WEB_JRE "/lib/rt.jar");
+
+  // Enable coverage agent if we are running instrumented plugin
+#ifdef COVERAGE_AGENT
+  command_line.push_back(COVERAGE_AGENT);
+#endif
+
   if (plugin_debug)
   {
-    command_line.push_back(appletviewer_executable);
-
-    //Add JVM args to command_line
-    for (int i = 0; i < jvm_args->size(); i++)
-    {
-      command_line.push_back(*jvm_args->at(i));
-    }
-
-    command_line.push_back(PLUGIN_BOOTCLASSPATH);
-    // set the classpath to avoid using the default (cwd).
-    command_line.push_back("-classpath");
-    command_line.push_back(ICEDTEA_WEB_JRE "/lib/rt.jar");
     command_line.push_back("-Xdebug");
     command_line.push_back("-Xnoagent");
 
@@ -1615,29 +1623,14 @@
     std::string debug_flags = "-Xrunjdwp:transport=dt_socket,address=8787,server=y,";
     debug_flags += plugin_debug_suspend ? "suspend=y" : "suspend=n";
     command_line.push_back(debug_flags);
-
-    command_line.push_back("sun.applet.PluginMain");
-    command_line.push_back(out_pipe_name);
-    command_line.push_back(in_pipe_name);
-  } else
-  {
-    command_line.push_back(std::string(appletviewer_executable));
-
-    //Add JVM args to command_line
-    for (int i = 0; i < jvm_args->size(); i++)
-    {
-      command_line.push_back(*jvm_args->at(i));
-    }
-
-    command_line.push_back(PLUGIN_BOOTCLASSPATH);
-    command_line.push_back("-classpath");
-    command_line.push_back(ICEDTEA_WEB_JRE "/lib/rt.jar");
-    command_line.push_back("sun.applet.PluginMain");
-    command_line.push_back(out_pipe_name);
-    command_line.push_back(in_pipe_name);
-
   }
 
+  command_line.push_back("sun.applet.PluginMain");
+  command_line.push_back(out_pipe_name);
+  command_line.push_back(in_pipe_name);
+
+  // Finished command line parameters
+
   environment = plugin_filter_environment();
   std::vector<gchar*> vector_gchar = IcedTeaPluginUtilities::vectorStringToVectorGchar(&command_line);
   gchar **command_line_args = &vector_gchar[0];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/jacoco-operator/org/jacoco/operator/Main.java	Tue Dec 11 20:32:26 2012 +0100
@@ -0,0 +1,288 @@
+/*
+Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea 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
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+ */
+
+package org.jacoco.operator;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Commandline launcher
+ */
+public class Main {
+
+    //main switches
+    private static final String MERGE = "merge";
+    private static final String REPORT = "report";
+    //switches
+    private static final String die_on_failure = "--die-soon";
+    //merge
+    private static final String output_file = "--output-file";
+    private static final String input_files = "--input-files";
+    //report
+    private static final String html_output = "--html-output";
+    private static final String xml_output = "--xml-output";
+    private static final String input_srcs = "--input-srcs";
+    private static final String input_builds = "--input-builds";
+    private static final String title = "--title";
+    private static String input_file = "--input-file";
+    /**
+     * *
+     */
+    private static boolean dieOnFailure = false;
+    private static boolean warned = false;
+
+    public static void main(String[] args) throws IOException {
+        if (args.length < 2) {
+            printHelp();
+            System.exit(0);
+        }
+
+        Runnable r = null;
+        if (args[0].equalsIgnoreCase(MERGE)) {
+            r = proceedMerge(cutFirstParam(args));
+        } else if (args[0].equalsIgnoreCase(REPORT)) {
+            r = proceedReport(cutFirstParam(args));
+        } else {
+            System.err.println("Unsuported main switch `" + args[0] + "`, use " + MERGE + " or " + REPORT);
+            printHelp();
+            System.exit(1);
+        }
+        if (dieOnFailure && warned) {
+            System.err.println(die_on_failure + " is specified and warning occured. Exiting");
+            System.exit(2);
+        }
+        r.run();
+
+    }
+
+    private static void printHelp() {
+        System.out.println("Usage: java `classpath` org.jacoco.operator.Main [" + MERGE + "|" + REPORT + "] switches/files");
+        System.out.println("    order of switches does not matter");
+        System.out.println("  Merge usage: java `classpath` org.jacoco.operator.Main " + MERGE + " " + output_file + " file  " + input_files + " file file file ...");
+        System.out.println("  Report usage: java `classpath` org.jacoco.operator.Main " + REPORT + " " + html_output + " file " + xml_output + " file  " + input_srcs + " file file file ... " + input_builds + " file file file " + title + " titleOfReport " + input_file + " file");
+        System.out.println("Where:");
+        System.out.println("  classpath should contain this application, and complete jacoco, and sometimes asm3 (depends on jacoco bundle)");
+        System.out.println("    " + die_on_failure + " - can be set as first parameter (after main switch), each warning then will cause exit of application");
+        System.out.println("  " + MERGE);
+        System.out.println("    " + output_file + " - is file where merged inputs will be saved");
+        System.out.println("    " + input_files + " - is list of files which will be merged into output file");
+        System.out.println("  " + REPORT);
+        System.out.println("    " + html_output + " - name of directory into which report will be generated. Should be empty or not yet exist");
+        System.out.println("    " + xml_output + " - is name of file into which xml report will be written");
+        System.out.println("    " + input_srcs + " - jars, zips or directories with java sources which will be used during report generation");
+        System.out.println("    " + input_builds + " - jars, zips or directories with compiled java classes, debug information must be present");
+        System.out.println("    " + title + " - title of report");
+        System.out.println("    " + input_file + " - input file with recorded coverage-run-session. By default jacoco saves into " + MergeTask.DEFAULT_NAME);
+
+    }
+
+    private static String[] cutFirstParam(String[] args) {
+        String[] arg = new String[args.length - 1];
+        System.arraycopy(args, 1, arg, 0, arg.length);
+        return arg;
+    }
+
+    private static Runnable proceedMerge(String[] a) throws IOException {
+        String doing = null;
+        String outputFile = null;
+        List<String> inputFiles = new ArrayList<String>(2);
+        for (String s : a) {
+            if (s.startsWith("--")) {
+                if (s.equalsIgnoreCase(die_on_failure)) {
+                    doing = null;
+                    dieOnFailure = true;
+                } else if (s.equalsIgnoreCase(output_file)) {
+                    doing = output_file;
+                } else if (s.equalsIgnoreCase(input_files)) {
+                    doing = input_files;
+                } else {
+                    warnOrDie("Unknown Switch for merge " + s);
+                    doing = null;
+                }
+            } else {
+                if (doing == null) {
+                    warnOrDie("Missing switch during processing of " + s);
+                } else {
+                    if (doing.equalsIgnoreCase(output_file)) {
+                        outputFile = s;
+                    } else if (doing.equalsIgnoreCase(input_files)) {
+                        inputFiles.add(s);
+                    } else {
+                        warnOrDie("Unknown processing of switch of" + doing);
+                    }
+
+                }
+            }
+        }
+        throwIfNullOrEmpty(outputFile, "empty output file");
+        File ff = new File(outputFile);
+        if (ff.exists()) {
+            warnOrDie("Warning, output file " + ff.getAbsolutePath() + " exists");
+        }
+        MergeTask m = new MergeTask(ff);
+        for (String string : inputFiles) {
+            if (checkIfNotNullOrEmpty(string)) {
+                File f = new File(string);
+                if (!f.exists()) {
+                    warnOrDie("Warning, input coverage " + f.getAbsolutePath() + " does not exists!");
+                }
+                m.addInputFile(f);
+            }
+        }
+        return m;
+
+    }
+
+    private static Runnable proceedReport(String[] a) throws IOException {
+        String doing = null;
+        String htmlDir = null;
+        String xmlFile = null;
+        List<String> inputSrcs = new ArrayList<String>(1);
+        List<String> inputBuilds = new ArrayList<String>(1);
+        String titleValue = null;
+        String inputFile = null;
+        for (String s : a) {
+            if (s.startsWith("--")) {
+                if (s.equalsIgnoreCase(die_on_failure)) {
+                    doing = null;
+                    dieOnFailure = true;
+                } else if (s.equalsIgnoreCase(html_output)) {
+                    doing = html_output;
+                } else if (s.equalsIgnoreCase(xml_output)) {
+                    doing = xml_output;
+                } else if (s.equalsIgnoreCase(input_srcs)) {
+                    doing = input_srcs;
+                } else if (s.equalsIgnoreCase(input_builds)) {
+                    doing = input_builds;
+                } else if (s.equalsIgnoreCase(title)) {
+                    doing = title;
+                } else if (s.equalsIgnoreCase(input_file)) {
+                    doing = input_file;
+                } else {
+                    warnOrDie("Unknown Switch for report " + s);
+                    doing = null;
+                }
+            } else {
+                if (doing == null) {
+                    warnOrDie("Missing switch during processing of " + s);
+                } else {
+                    if (doing.equalsIgnoreCase(html_output)) {
+                        htmlDir = s;
+                    } else if (doing.equalsIgnoreCase(xml_output)) {
+                        xmlFile = s;
+                    } else if (doing.equalsIgnoreCase(input_srcs)) {
+                        inputSrcs.add(s);
+                    } else if (doing.equalsIgnoreCase(input_builds)) {
+                        inputBuilds.add(s);
+                    } else if (doing.equalsIgnoreCase(title)) {
+                        titleValue = s;
+                    } else if (doing.equalsIgnoreCase(input_file)) {
+                        inputFile = s;
+                    } else {
+                        warnOrDie("Unknown processing of switch of " + doing);
+                    }
+
+                }
+            }
+        }
+        File finalHtmlFile = null;
+        if (checkIfNotNullOrEmpty(htmlDir)) {
+            finalHtmlFile = new File(htmlDir);
+            if (finalHtmlFile.exists()) {
+                warnOrDie("Warning, direcotry for html report exists! " + finalHtmlFile.getAbsolutePath());
+            }
+        }
+        File finalXmlFile = null;
+        if (checkIfNotNullOrEmpty(xmlFile)) {
+            finalXmlFile = new File(xmlFile);
+            if (finalXmlFile.exists()) {
+                warnOrDie("Warning, file for xml report exists! " + finalHtmlFile.getAbsolutePath());
+            }
+        }
+        if (chckIfNUllOrEmpty(titleValue)) {
+            titleValue = "Coverage report";
+        }
+        throwIfNullOrEmpty(inputFile, "No coverage data file specified!");
+        File finalInputFile = new File(inputFile);
+
+        ReportGenerator rg = new ReportGenerator(titleValue, finalInputFile, finalHtmlFile, finalXmlFile);
+
+        for (String string : inputSrcs) {
+            if (checkIfNotNullOrEmpty(string)) {
+                File f = new File(string);
+                if (!f.exists()) {
+                    warnOrDie("Warning, input source " + f.getAbsolutePath() + " does not exists!");
+                }
+                rg.addSource(f);
+            }
+        }
+        for (String string : inputBuilds) {
+            if (checkIfNotNullOrEmpty(string)) {
+                File f = new File(string);
+                if (!f.exists()) {
+                    warnOrDie("Warning, input build " + f.getAbsolutePath() + " does not exists!");
+                }
+                rg.addClasses(f);
+            }
+        }
+        return rg;
+    }
+
+    private static String throwIfNullOrEmpty(String outputFile, String message) throws RuntimeException {
+        if (chckIfNUllOrEmpty(outputFile)) {
+            throw new RuntimeException(message);
+        }
+        return outputFile;
+    }
+
+    private static boolean checkIfNotNullOrEmpty(String string) {
+        return string != null && string.trim().length() != 0;
+    }
+
+    private static boolean chckIfNUllOrEmpty(String outputFile) {
+        return outputFile == null || outputFile.trim().length() == 0;
+    }
+
+    private static void warnOrDie(String string) {
+        System.err.println(string);
+        warned = true;
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/jacoco-operator/org/jacoco/operator/MergeTask.java	Tue Dec 11 20:32:26 2012 +0100
@@ -0,0 +1,157 @@
+/*
+Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea 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
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+ */
+package org.jacoco.operator;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.jacoco.core.data.ExecutionDataReader;
+import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.data.ExecutionDataWriter;
+import org.jacoco.core.data.SessionInfoStore;
+
+/**
+ * Task for merging a set of execution data store files into a single file
+ * 
+ * Inspired by:
+ * https://raw.github.com/jacoco/jacoco/master/org.jacoco.ant/src/org/jacoco/ant/MergeTask.java
+ */
+public class MergeTask implements Runnable {
+
+    public static final String DEFAULT_NAME = "jacoco.exec";
+    private File destfile;
+    private final List<File> files = new ArrayList<File>(1);
+
+    public MergeTask(File destfile) {
+        this.destfile = destfile;
+    }
+
+    public MergeTask(File destfile, List<File> inputs) {
+        this.destfile = destfile;
+        files.addAll(inputs);
+    }
+
+    /**
+     * Sets the location of the merged data store
+     *
+     * @param destfile Destination data store location
+     */
+    public void setDestfile(final File destfile) {
+        this.destfile = destfile;
+    }
+
+    public void addInputFile(final File input) {
+        if (input != null) {
+            files.add(input);
+        }
+    }
+
+    public void addInputFiles(final List<File> input) {
+        files.addAll(input);
+    }
+
+    public void execute() throws IOException {
+        if (destfile == null) {
+            throw new RuntimeException("Destination file must be supplied");
+        }
+
+        final SessionInfoStore infoStore = new SessionInfoStore();
+        final ExecutionDataStore dataStore = new ExecutionDataStore();
+
+        loadSourceFiles(infoStore, dataStore);
+
+        OutputStream outputStream = null;
+        try {
+
+            outputStream = new BufferedOutputStream(new FileOutputStream(
+                    destfile));
+            final ExecutionDataWriter dataWriter = new ExecutionDataWriter(
+                    outputStream);
+            infoStore.accept(dataWriter);
+            dataStore.accept(dataWriter);
+        } finally {
+            if (outputStream != null) {
+                outputStream.close();
+            }
+        }
+
+    }
+
+    private void loadSourceFiles(final SessionInfoStore infoStore, final ExecutionDataStore dataStore) throws IOException {
+        if (files == null || files.isEmpty()) {
+            throw new RuntimeException("No input files");
+        }
+        final Iterator<?> resourceIterator = files.iterator();
+        while (resourceIterator.hasNext()) {
+            final File resource = (File) resourceIterator.next();
+
+            if (resource.isDirectory()) {
+                continue;
+            }
+            InputStream resourceStream = null;
+            try {
+                resourceStream = new FileInputStream(resource);
+                final ExecutionDataReader reader = new ExecutionDataReader(
+                        resourceStream);
+                reader.setSessionInfoVisitor(infoStore);
+                reader.setExecutionDataVisitor(dataStore);
+                reader.read();
+            } finally {
+                if (resourceStream != null) {
+                    resourceStream.close();
+                }
+            }
+        }
+    }
+    
+    @Override
+    public void run() {
+        try {
+            execute();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/jacoco-operator/org/jacoco/operator/ReportGenerator.java	Tue Dec 11 20:32:26 2012 +0100
@@ -0,0 +1,292 @@
+/*
+Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea 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
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+ */
+
+package org.jacoco.operator;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.jacoco.core.analysis.Analyzer;
+import org.jacoco.core.analysis.CoverageBuilder;
+import org.jacoco.core.analysis.IBundleCoverage;
+import org.jacoco.core.data.ExecutionDataReader;
+import org.jacoco.core.data.ExecutionDataStore;
+import org.jacoco.core.data.SessionInfoStore;
+import org.jacoco.report.DirectorySourceFileLocator;
+import org.jacoco.report.FileMultiReportOutput;
+import org.jacoco.report.IReportVisitor;
+import org.jacoco.report.MultiSourceFileLocator;
+import org.jacoco.report.html.HTMLFormatter;
+import org.jacoco.report.xml.XMLFormatter;
+
+/**
+ * This example creates a HTML report for eclipse like projects based on a
+ * single execution data store called jacoco.exec. The report contains no
+ * grouping information.
+ *
+ * The class files under test must be compiled with debug information, otherwise
+ * source highlighting will not work.
+ * 
+ * Originally based on:
+ * http://www.eclemma.org/jacoco/trunk/doc/examples/java/ReportGenerator.java
+ */
+public class ReportGenerator implements Runnable {
+
+    private final String title;
+    private final File executionDataFile;
+    private final List<File> classesDirectories = new ArrayList<File>(1);
+    private final List<File> sourceDirectories = new ArrayList<File>(1);
+    private File reportDirectory;
+    private File xmlOutput;
+    private ExecutionDataStore executionDataStore;
+    private SessionInfoStore sessionInfoStore;
+    private String XML_DEF_NAME = "coverage-summary.xml";
+
+    /**
+     * Create a new generator based for the given project.
+     *
+     * @param projectDirectory
+     */
+    public ReportGenerator(final File projectDirectory) {
+        this.title = projectDirectory.getName();
+        this.executionDataFile = new File(projectDirectory, MergeTask.DEFAULT_NAME);
+        this.classesDirectories.add(new File(projectDirectory, "bin"));
+        this.sourceDirectories.add(new File(projectDirectory, "src"));
+        this.reportDirectory = new File(projectDirectory, "coveragereport");
+        this.xmlOutput = new File(projectDirectory, XML_DEF_NAME);
+    }
+
+    public ReportGenerator(String title, File exec, File classes, File sources, File htmlReport, File xmlReport) {
+        this.title = title;
+        this.executionDataFile = exec;
+        if (classes != null) {
+            this.classesDirectories.add(classes);
+        }
+        if (sources != null) {
+            this.sourceDirectories.add(sources);
+        }
+        this.reportDirectory = htmlReport;
+
+        this.xmlOutput = xmlReport;
+    }
+
+    public ReportGenerator(String title, File exec, List<File> classes, List<File> sources, File htmlReport, File xmlReport) {
+        this.title = title;
+        this.executionDataFile = exec;
+        if (classes != null) {
+            this.classesDirectories.addAll(classes);
+        }
+        if (sources != null) {
+            this.sourceDirectories.addAll(sources);
+        }
+        this.reportDirectory = htmlReport;
+        this.xmlOutput = xmlReport;
+    }
+
+    public ReportGenerator(String title, File exec, List<File> classes, List<File> sources, File report) {
+        this.title = title;
+        this.executionDataFile = exec;
+        if (classes != null) {
+            this.classesDirectories.addAll(classes);
+        }
+        if (sources != null) {
+            this.sourceDirectories.addAll(sources);
+        }
+        this.reportDirectory = report;
+        this.xmlOutput = new File(report, XML_DEF_NAME);
+    }
+
+    public ReportGenerator(String title, File exec, File htmlReport, File xmlReport) {
+        this.title = title;
+        this.executionDataFile = exec;
+        this.reportDirectory = htmlReport;
+        this.xmlOutput = xmlReport;
+    }
+
+    public ReportGenerator(String title, File exec, File report) {
+        this.title = title;
+        this.executionDataFile = exec;
+        this.reportDirectory = report;
+        this.xmlOutput = new File(report, XML_DEF_NAME);
+    }
+
+    public void addSource(File f) {
+        sourceDirectories.add(f);
+
+    }
+
+    public void addClasses(File f) {
+        classesDirectories.add(f);
+
+    }
+
+    /**
+     * Create the report.
+     *
+     * @throws IOException
+     */
+    public void execute() throws IOException {
+
+        // Read the jacoco.exec file. Multiple data stores could be merged
+        // at this point
+        loadExecutionData();
+
+        // Run the structure analyzer on a single class folder to build up
+        // the coverage model. The process would be similar if your classes
+        // were in a jar file. Typically you would create a bundle for each
+        // class folder and each jar you want in your report. If you have
+        // more than one bundle you will need to add a grouping node to your
+        // report
+        final IBundleCoverage bundleCoverage = analyzeStructure();
+
+        if (reportDirectory != null) {
+            createHtmlReport(bundleCoverage);
+        }
+        if (xmlOutput != null) {
+            createXmlReport(bundleCoverage);
+        }
+
+    }
+
+    private void createHtmlReport(final IBundleCoverage bundleCoverage)
+            throws IOException {
+
+        // Create a concrete report visitor based on some supplied
+        // configuration. In this case we use the defaults
+        final HTMLFormatter htmlFormatter = new HTMLFormatter();
+        final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(reportDirectory));
+
+        // Initialize the report with all of the execution and session
+        // information. At this point the report doesn't know about the
+        // structure of the report being created
+        visitor.visitInfo(sessionInfoStore.getInfos(),
+                executionDataStore.getContents());
+
+        // Populate the report structure with the bundle coverage information.
+        // Call visitGroup if you need groups in your report.
+        MultiSourceFileLocator msf = new MultiSourceFileLocator(4);
+        for (File file : sourceDirectories) {
+            msf.add(new DirectorySourceFileLocator(
+                    file, "utf-8", 4));
+        }
+
+        visitor.visitBundle(bundleCoverage, msf);
+
+        // Signal end of structure information to allow report to write all
+        // information out
+        visitor.visitEnd();
+
+    }
+
+    private void createXmlReport(final IBundleCoverage bundleCoverage)
+            throws IOException {
+
+        OutputStream fos = new FileOutputStream(xmlOutput);
+        try {
+            // Create a concrete report visitor based on some supplied
+            // configuration. In this case we use the defaults
+            final XMLFormatter htmlFormatter = new XMLFormatter();
+            final IReportVisitor visitor = htmlFormatter.createVisitor(fos);
+
+            // Initialize the report with all of the execution and session
+            // information. At this point the report doesn't know about the
+            // structure of the report being created
+            visitor.visitInfo(sessionInfoStore.getInfos(),
+                    executionDataStore.getContents());
+
+            // Populate the report structure with the bundle coverage information.
+            // Call visitGroup if you need groups in your report.
+            visitor.visitBundle(bundleCoverage, null);
+
+
+            // Signal end of structure information to allow report to write all
+            // information out
+            visitor.visitEnd();
+        } finally {
+            if (fos != null) {
+                fos.close();
+            }
+        }
+
+    }
+
+    private void loadExecutionData() throws IOException {
+        final FileInputStream fis = new FileInputStream(executionDataFile);
+        try {
+            final ExecutionDataReader executionDataReader = new ExecutionDataReader(
+                    fis);
+            executionDataStore = new ExecutionDataStore();
+            sessionInfoStore = new SessionInfoStore();
+
+            executionDataReader.setExecutionDataVisitor(executionDataStore);
+            executionDataReader.setSessionInfoVisitor(sessionInfoStore);
+
+            while (executionDataReader.read()) {
+            }
+        } finally {
+            if (fis != null) {
+                fis.close();
+            }
+        }
+    }
+
+    private IBundleCoverage analyzeStructure() throws IOException {
+        final CoverageBuilder coverageBuilder = new CoverageBuilder();
+        final Analyzer analyzer = new Analyzer(executionDataStore,
+                coverageBuilder);
+        for (File file : classesDirectories) {
+            analyzer.analyzeAll(file);
+
+        }
+
+        return coverageBuilder.getBundle(title);
+    }
+
+    @Override
+    public void run() {
+        try {
+            execute();
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+}