• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *    Marc R. Hoffmann - initial API and implementation
10  *
11  *******************************************************************************/
12 package org.jacoco.core.runtime;
13 
14 import static java.lang.String.format;
15 
16 import java.io.File;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Properties;
24 import java.util.regex.Pattern;
25 
26 /**
27  * Utility to create and parse options for the runtime agent. Options are
28  * represented as a string in the following format:
29  *
30  * <pre>
31  *   key1=value1,key2=value2,key3=value3
32  * </pre>
33  */
34 public final class AgentOptions {
35 
36 	/**
37 	 * Specifies the output file for execution data. Default is
38 	 * <code>jacoco.exec</code> in the working directory.
39 	 */
40 	public static final String DESTFILE = "destfile";
41 
42 	/**
43 	 * Default value for the "destfile" agent option.
44 	 */
45 	public static final String DEFAULT_DESTFILE = "jacoco.exec";
46 
47 	/**
48 	 * Specifies whether execution data should be appended to the output file.
49 	 * Default is <code>true</code>.
50 	 */
51 	public static final String APPEND = "append";
52 
53 	/**
54 	 * Wildcard expression for class names that should be included for code
55 	 * coverage. Default is <code>*</code> (all classes included).
56 	 *
57 	 * @see WildcardMatcher
58 	 */
59 	public static final String INCLUDES = "includes";
60 
61 	/**
62 	 * Wildcard expression for class names that should be excluded from code
63 	 * coverage. Default is the empty string (no exclusions).
64 	 *
65 	 * @see WildcardMatcher
66 	 */
67 	public static final String EXCLUDES = "excludes";
68 
69 	/**
70 	 * Wildcard expression for class loaders names for classes that should be
71 	 * excluded from code coverage. This means all classes loaded by a class
72 	 * loader which full qualified name matches this expression will be ignored
73 	 * for code coverage regardless of all other filtering settings. Default is
74 	 * <code>sun.reflect.DelegatingClassLoader</code>.
75 	 *
76 	 * @see WildcardMatcher
77 	 */
78 	public static final String EXCLCLASSLOADER = "exclclassloader";
79 
80 	/**
81 	 * Specifies whether also classes from the bootstrap classloader should be
82 	 * instrumented. Use this feature with caution, it needs heavy
83 	 * includes/excludes tuning. Default is <code>false</code>.
84 	 */
85 	public static final String INCLBOOTSTRAPCLASSES = "inclbootstrapclasses";
86 
87 	/**
88 	 * Specifies whether also classes without a source location should be
89 	 * instrumented. Normally such classes are generated at runtime e.g. by
90 	 * mocking frameworks and are therefore excluded by default. Default is
91 	 * <code>false</code>.
92 	 */
93 	public static final String INCLNOLOCATIONCLASSES = "inclnolocationclasses";
94 
95 	/**
96 	 * Specifies a session identifier that is written with the execution data.
97 	 * Without this parameter a random identifier is created by the agent.
98 	 */
99 	public static final String SESSIONID = "sessionid";
100 
101 	/**
102 	 * Specifies whether the agent will automatically dump coverage data on VM
103 	 * exit. Default is <code>true</code>.
104 	 */
105 	public static final String DUMPONEXIT = "dumponexit";
106 
107 	/**
108 	 * Specifies the output mode. Default is {@link OutputMode#file}.
109 	 *
110 	 * @see OutputMode#file
111 	 * @see OutputMode#tcpserver
112 	 * @see OutputMode#tcpclient
113 	 * @see OutputMode#none
114 	 */
115 	public static final String OUTPUT = "output";
116 
117 	private static final Pattern OPTION_SPLIT = Pattern
118 			.compile(",(?=[a-zA-Z0-9_\\-]+=)");
119 
120 	/**
121 	 * Possible values for {@link AgentOptions#OUTPUT}.
122 	 */
123 	public static enum OutputMode {
124 
125 		/**
126 		 * Value for the {@link AgentOptions#OUTPUT} parameter: At VM
127 		 * termination execution data is written to the file specified by
128 		 * {@link AgentOptions#DESTFILE}.
129 		 */
130 		file,
131 
132 		/**
133 		 * Value for the {@link AgentOptions#OUTPUT} parameter: The agent
134 		 * listens for incoming connections on a TCP port specified by
135 		 * {@link AgentOptions#ADDRESS} and {@link AgentOptions#PORT}.
136 		 */
137 		tcpserver,
138 
139 		/**
140 		 * Value for the {@link AgentOptions#OUTPUT} parameter: At startup the
141 		 * agent connects to a TCP port specified by the
142 		 * {@link AgentOptions#ADDRESS} and {@link AgentOptions#PORT} attribute.
143 		 */
144 		tcpclient,
145 
146 		/**
147 		 * Value for the {@link AgentOptions#OUTPUT} parameter: Do not produce
148 		 * any output.
149 		 */
150 		none
151 
152 	}
153 
154 	/**
155 	 * The IP address or DNS name the tcpserver binds to or the tcpclient
156 	 * connects to. Default is defined by {@link #DEFAULT_ADDRESS}.
157 	 */
158 	public static final String ADDRESS = "address";
159 
160 	/**
161 	 * Default value for the "address" agent option.
162 	 */
163 	public static final String DEFAULT_ADDRESS = null;
164 
165 	/**
166 	 * The port the tcpserver binds to or the tcpclient connects to. In
167 	 * tcpserver mode the port must be available, which means that if multiple
168 	 * JaCoCo agents should run on the same machine, different ports have to be
169 	 * specified. Default is defined by {@link #DEFAULT_PORT}.
170 	 */
171 	public static final String PORT = "port";
172 
173 	/**
174 	 * Default value for the "port" agent option.
175 	 */
176 	public static final int DEFAULT_PORT = 6300;
177 
178 	/**
179 	 * Specifies where the agent dumps all class files it encounters. The
180 	 * location is specified as a relative path to the working directory.
181 	 * Default is <code>null</code> (no dumps).
182 	 */
183 	public static final String CLASSDUMPDIR = "classdumpdir";
184 
185 	/**
186 	 * Specifies whether the agent should expose functionality via JMX under the
187 	 * name "org.jacoco:type=Runtime". Default is <code>false</code>.
188 	 */
189 	public static final String JMX = "jmx";
190 
191 	private static final Collection<String> VALID_OPTIONS = Arrays.asList(
192 			DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER,
193 			INCLBOOTSTRAPCLASSES, INCLNOLOCATIONCLASSES, SESSIONID, DUMPONEXIT,
194 			OUTPUT, ADDRESS, PORT, CLASSDUMPDIR, JMX);
195 
196 	private final Map<String, String> options;
197 
198 	/**
199 	 * New instance with all values set to default.
200 	 */
AgentOptions()201 	public AgentOptions() {
202 		this.options = new HashMap<String, String>();
203 	}
204 
205 	/**
206 	 * New instance parsed from the given option string.
207 	 *
208 	 * @param optionstr
209 	 *            string to parse or <code>null</code>
210 	 */
AgentOptions(final String optionstr)211 	public AgentOptions(final String optionstr) {
212 		this();
213 		if (optionstr != null && optionstr.length() > 0) {
214 			for (final String entry : OPTION_SPLIT.split(optionstr)) {
215 				final int pos = entry.indexOf('=');
216 				if (pos == -1) {
217 					throw new IllegalArgumentException(format(
218 							"Invalid agent option syntax \"%s\".", optionstr));
219 				}
220 				final String key = entry.substring(0, pos);
221 				if (!VALID_OPTIONS.contains(key)) {
222 					throw new IllegalArgumentException(format(
223 							"Unknown agent option \"%s\".", key));
224 				}
225 
226 				final String value = entry.substring(pos + 1);
227 				setOption(key, value);
228 			}
229 
230 			validateAll();
231 		}
232 	}
233 
234 	/**
235 	 * New instance read from the given {@link Properties} object.
236 	 *
237 	 * @param properties
238 	 *            {@link Properties} object to read configuration options from
239 	 */
AgentOptions(final Properties properties)240 	public AgentOptions(final Properties properties) {
241 		this();
242 		for (final String key : VALID_OPTIONS) {
243 			final String value = properties.getProperty(key);
244 			if (value != null) {
245 				setOption(key, value);
246 			}
247 		}
248 	}
249 
validateAll()250 	private void validateAll() {
251 		validatePort(getPort());
252 		getOutput();
253 	}
254 
validatePort(final int port)255 	private void validatePort(final int port) {
256 		if (port < 0) {
257 			throw new IllegalArgumentException("port must be positive");
258 		}
259 	}
260 
261 	/**
262 	 * Returns the output file location.
263 	 *
264 	 * @return output file location
265 	 */
getDestfile()266 	public String getDestfile() {
267 		return getOption(DESTFILE, DEFAULT_DESTFILE);
268 	}
269 
270 	/**
271 	 * Sets the output file location.
272 	 *
273 	 * @param destfile
274 	 *            output file location
275 	 */
setDestfile(final String destfile)276 	public void setDestfile(final String destfile) {
277 		setOption(DESTFILE, destfile);
278 	}
279 
280 	/**
281 	 * Returns whether the output should be appended to an existing file.
282 	 *
283 	 * @return <code>true</code>, when the output should be appended
284 	 */
getAppend()285 	public boolean getAppend() {
286 		return getOption(APPEND, true);
287 	}
288 
289 	/**
290 	 * Sets whether the output should be appended to an existing file.
291 	 *
292 	 * @param append
293 	 *            <code>true</code>, when the output should be appended
294 	 */
setAppend(final boolean append)295 	public void setAppend(final boolean append) {
296 		setOption(APPEND, append);
297 	}
298 
299 	/**
300 	 * Returns the wildcard expression for classes to include.
301 	 *
302 	 * @return wildcard expression for classes to include
303 	 * @see WildcardMatcher
304 	 */
getIncludes()305 	public String getIncludes() {
306 		return getOption(INCLUDES, "*");
307 	}
308 
309 	/**
310 	 * Sets the wildcard expression for classes to include.
311 	 *
312 	 * @param includes
313 	 *            wildcard expression for classes to include
314 	 * @see WildcardMatcher
315 	 */
setIncludes(final String includes)316 	public void setIncludes(final String includes) {
317 		setOption(INCLUDES, includes);
318 	}
319 
320 	/**
321 	 * Returns the wildcard expression for classes to exclude.
322 	 *
323 	 * @return wildcard expression for classes to exclude
324 	 * @see WildcardMatcher
325 	 */
getExcludes()326 	public String getExcludes() {
327 		return getOption(EXCLUDES, "");
328 	}
329 
330 	/**
331 	 * Sets the wildcard expression for classes to exclude.
332 	 *
333 	 * @param excludes
334 	 *            wildcard expression for classes to exclude
335 	 * @see WildcardMatcher
336 	 */
setExcludes(final String excludes)337 	public void setExcludes(final String excludes) {
338 		setOption(EXCLUDES, excludes);
339 	}
340 
341 	/**
342 	 * Returns the wildcard expression for excluded class loaders.
343 	 *
344 	 * @return expression for excluded class loaders
345 	 * @see WildcardMatcher
346 	 */
getExclClassloader()347 	public String getExclClassloader() {
348 		return getOption(EXCLCLASSLOADER, "sun.reflect.DelegatingClassLoader");
349 	}
350 
351 	/**
352 	 * Sets the wildcard expression for excluded class loaders.
353 	 *
354 	 * @param expression
355 	 *            expression for excluded class loaders
356 	 * @see WildcardMatcher
357 	 */
setExclClassloader(final String expression)358 	public void setExclClassloader(final String expression) {
359 		setOption(EXCLCLASSLOADER, expression);
360 	}
361 
362 	/**
363 	 * Returns whether classes from the bootstrap classloader should be
364 	 * instrumented.
365 	 *
366 	 * @return <code>true</code> if classes from the bootstrap classloader
367 	 *         should be instrumented
368 	 */
getInclBootstrapClasses()369 	public boolean getInclBootstrapClasses() {
370 		return getOption(INCLBOOTSTRAPCLASSES, false);
371 	}
372 
373 	/**
374 	 * Sets whether classes from the bootstrap classloader should be
375 	 * instrumented.
376 	 *
377 	 * @param include
378 	 *            <code>true</code> if bootstrap classes should be instrumented
379 	 */
setInclBootstrapClasses(final boolean include)380 	public void setInclBootstrapClasses(final boolean include) {
381 		setOption(INCLBOOTSTRAPCLASSES, include);
382 	}
383 
384 	/**
385 	 * Returns whether classes without source location should be instrumented.
386 	 *
387 	 * @return <code>true</code> if classes without source location should be
388 	 *         instrumented
389 	 */
getInclNoLocationClasses()390 	public boolean getInclNoLocationClasses() {
391 		return getOption(INCLNOLOCATIONCLASSES, false);
392 	}
393 
394 	/**
395 	 * Sets whether classes without source location should be instrumented.
396 	 *
397 	 * @param include
398 	 *            <code>true</code> if classes without source location should be
399 	 *            instrumented
400 	 */
setInclNoLocationClasses(final boolean include)401 	public void setInclNoLocationClasses(final boolean include) {
402 		setOption(INCLNOLOCATIONCLASSES, include);
403 	}
404 
405 	/**
406 	 * Returns the session identifier.
407 	 *
408 	 * @return session identifier
409 	 */
getSessionId()410 	public String getSessionId() {
411 		return getOption(SESSIONID, null);
412 	}
413 
414 	/**
415 	 * Sets the session identifier.
416 	 *
417 	 * @param id
418 	 *            session identifier
419 	 */
setSessionId(final String id)420 	public void setSessionId(final String id) {
421 		setOption(SESSIONID, id);
422 	}
423 
424 	/**
425 	 * Returns whether coverage data should be dumped on exit.
426 	 *
427 	 * @return <code>true</code> if coverage data will be written on VM exit
428 	 */
getDumpOnExit()429 	public boolean getDumpOnExit() {
430 		return getOption(DUMPONEXIT, true);
431 	}
432 
433 	/**
434 	 * Sets whether coverage data should be dumped on exit.
435 	 *
436 	 * @param dumpOnExit
437 	 *            <code>true</code> if coverage data should be written on VM
438 	 *            exit
439 	 */
setDumpOnExit(final boolean dumpOnExit)440 	public void setDumpOnExit(final boolean dumpOnExit) {
441 		setOption(DUMPONEXIT, dumpOnExit);
442 	}
443 
444 	/**
445 	 * Returns the port on which to listen to when the output is
446 	 * <code>tcpserver</code> or the port to connect to when output is
447 	 * <code>tcpclient</code>.
448 	 *
449 	 * @return port to listen on or connect to
450 	 */
getPort()451 	public int getPort() {
452 		return getOption(PORT, DEFAULT_PORT);
453 	}
454 
455 	/**
456 	 * Sets the port on which to listen to when output is <code>tcpserver</code>
457 	 * or the port to connect to when output is <code>tcpclient</code>
458 	 *
459 	 * @param port
460 	 *            port to listen on or connect to
461 	 */
setPort(final int port)462 	public void setPort(final int port) {
463 		validatePort(port);
464 		setOption(PORT, port);
465 	}
466 
467 	/**
468 	 * Gets the hostname or IP address to listen to when output is
469 	 * <code>tcpserver</code> or connect to when output is
470 	 * <code>tcpclient</code>
471 	 *
472 	 * @return Hostname or IP address
473 	 */
getAddress()474 	public String getAddress() {
475 		return getOption(ADDRESS, DEFAULT_ADDRESS);
476 	}
477 
478 	/**
479 	 * Sets the hostname or IP address to listen to when output is
480 	 * <code>tcpserver</code> or connect to when output is
481 	 * <code>tcpclient</code>
482 	 *
483 	 * @param address
484 	 *            Hostname or IP address
485 	 */
setAddress(final String address)486 	public void setAddress(final String address) {
487 		setOption(ADDRESS, address);
488 	}
489 
490 	/**
491 	 * Returns the output mode
492 	 *
493 	 * @return current output mode
494 	 */
getOutput()495 	public OutputMode getOutput() {
496 		final String value = options.get(OUTPUT);
497 // BEGIN android-change
498 //		return value == null ? OutputMode.file : OutputMode.valueOf(value);
499 		return value == null ? OutputMode.none : OutputMode.valueOf(value);
500 // END android-change
501 	}
502 
503 	/**
504 	 * Sets the output mode
505 	 *
506 	 * @param output
507 	 *            Output mode
508 	 */
setOutput(final String output)509 	public void setOutput(final String output) {
510 		setOutput(OutputMode.valueOf(output));
511 	}
512 
513 	/**
514 	 * Sets the output mode
515 	 *
516 	 * @param output
517 	 *            Output mode
518 	 */
setOutput(final OutputMode output)519 	public void setOutput(final OutputMode output) {
520 		setOption(OUTPUT, output.name());
521 	}
522 
523 	/**
524 	 * Returns the location of the directory where class files should be dumped
525 	 * to.
526 	 *
527 	 * @return dump location or <code>null</code> (no dumps)
528 	 */
getClassDumpDir()529 	public String getClassDumpDir() {
530 		return getOption(CLASSDUMPDIR, null);
531 	}
532 
533 	/**
534 	 * Sets the directory where class files should be dumped to.
535 	 *
536 	 * @param location
537 	 *            dump location or <code>null</code> (no dumps)
538 	 */
setClassDumpDir(final String location)539 	public void setClassDumpDir(final String location) {
540 		setOption(CLASSDUMPDIR, location);
541 	}
542 
543 	/**
544 	 * Returns whether the agent exposes functionality via JMX.
545 	 *
546 	 * @return <code>true</code>, when JMX is enabled
547 	 */
getJmx()548 	public boolean getJmx() {
549 		return getOption(JMX, false);
550 	}
551 
552 	/**
553 	 * Sets whether the agent should expose functionality via JMX.
554 	 *
555 	 * @param jmx
556 	 *            <code>true</code> if JMX should be enabled
557 	 */
setJmx(final boolean jmx)558 	public void setJmx(final boolean jmx) {
559 		setOption(JMX, jmx);
560 	}
561 
setOption(final String key, final int value)562 	private void setOption(final String key, final int value) {
563 		setOption(key, Integer.toString(value));
564 	}
565 
setOption(final String key, final boolean value)566 	private void setOption(final String key, final boolean value) {
567 		setOption(key, Boolean.toString(value));
568 	}
569 
setOption(final String key, final String value)570 	private void setOption(final String key, final String value) {
571 		options.put(key, value);
572 	}
573 
getOption(final String key, final String defaultValue)574 	private String getOption(final String key, final String defaultValue) {
575 		final String value = options.get(key);
576 		return value == null ? defaultValue : value;
577 	}
578 
getOption(final String key, final boolean defaultValue)579 	private boolean getOption(final String key, final boolean defaultValue) {
580 		final String value = options.get(key);
581 		return value == null ? defaultValue : Boolean.parseBoolean(value);
582 	}
583 
getOption(final String key, final int defaultValue)584 	private int getOption(final String key, final int defaultValue) {
585 		final String value = options.get(key);
586 		return value == null ? defaultValue : Integer.parseInt(value);
587 	}
588 
589 	/**
590 	 * Generate required JVM argument based on current configuration and
591 	 * supplied agent jar location.
592 	 *
593 	 * @param agentJarFile
594 	 *            location of the JaCoCo Agent Jar
595 	 * @return Argument to pass to create new VM with coverage enabled
596 	 */
getVMArgument(final File agentJarFile)597 	public String getVMArgument(final File agentJarFile) {
598 		return format("-javaagent:%s=%s", agentJarFile, this);
599 	}
600 
601 	/**
602 	 * Generate required quoted JVM argument based on current configuration and
603 	 * supplied agent jar location.
604 	 *
605 	 * @param agentJarFile
606 	 *            location of the JaCoCo Agent Jar
607 	 * @return Quoted argument to pass to create new VM with coverage enabled
608 	 */
getQuotedVMArgument(final File agentJarFile)609 	public String getQuotedVMArgument(final File agentJarFile) {
610 		return CommandLineSupport.quote(getVMArgument(agentJarFile));
611 	}
612 
613 	/**
614 	 * Generate required quotes JVM argument based on current configuration and
615 	 * prepends it to the given argument command line. If a agent with the same
616 	 * JAR file is already specified this parameter is removed from the existing
617 	 * command line.
618 	 *
619 	 * @param arguments
620 	 *            existing command line arguments or <code>null</code>
621 	 * @param agentJarFile
622 	 *            location of the JaCoCo Agent Jar
623 	 * @return VM command line arguments prepended with configured JaCoCo agent
624 	 */
prependVMArguments(final String arguments, final File agentJarFile)625 	public String prependVMArguments(final String arguments,
626 			final File agentJarFile) {
627 		final List<String> args = CommandLineSupport.split(arguments);
628 		final String plainAgent = format("-javaagent:%s", agentJarFile);
629 		for (final Iterator<String> i = args.iterator(); i.hasNext();) {
630 			if (i.next().startsWith(plainAgent)) {
631 				i.remove();
632 			}
633 		}
634 		args.add(0, getVMArgument(agentJarFile));
635 		return CommandLineSupport.quote(args);
636 	}
637 
638 	/**
639 	 * Creates a string representation that can be passed to the agent via the
640 	 * command line. Might be the empty string, if no options are set.
641 	 */
642 	@Override
toString()643 	public String toString() {
644 		final StringBuilder sb = new StringBuilder();
645 		for (final String key : VALID_OPTIONS) {
646 			final String value = options.get(key);
647 			if (value != null) {
648 				if (sb.length() > 0) {
649 					sb.append(',');
650 				}
651 				sb.append(key).append('=').append(value);
652 			}
653 		}
654 		return sb.toString();
655 	}
656 
657 }
658