AgentOptions.java
1/*******************************************************************************
2 * Copyright (c) 2009, 2010 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 *******************************************************************************/
12package org.jacoco.core.runtime;
13
14import static java.lang.String.format;
15
16import java.io.File;
17import java.util.Arrays;
18import java.util.Collection;
19import java.util.HashMap;
20import java.util.Map;
21
22/**
23 * Utility to create and parse options for the runtime agent. Options are
24 * represented as a string in the following format:
25 *
26 * <pre>
27 * key1=value1,key2=value2,key3=value3
28 * </pre>
29 *
30 * @author Marc R. Hoffmann
31 * @version 0.4.1.20101007204400
32 */
33public class AgentOptions {
34
35 /**
36 * Specifies the output file for execution data. Default is
37 * <code>jacoco.exec</code> in the working directory.
38 */
39 public static final String DESTFILE = "destfile";
40
41 /**
42 * Specifies whether execution data should be appended to the output file.
43 * Default is <code>true</code>.
44 */
45 public static final String APPEND = "append";
46
47 /**
48 * Wildcard expression for class names that should be included for code
49 * coverage. Default is <code>*</code> (all classes included).
50 *
51 * @see WildcardMatcher
52 */
53 public static final String INCLUDES = "includes";
54
55 /**
56 * Wildcard expression for class names that should be excluded from code
57 * coverage. Default is the empty string (no exclusions).
58 *
59 * @see WildcardMatcher
60 */
61 public static final String EXCLUDES = "excludes";
62
63 /**
64 * Wildcard expression for class loaders names for classes that should be
65 * excluded from code coverage. This means all classes loaded by a class
66 * loader which full qualified name matches this expression will be ignored
67 * for code coverage regardless of all other filtering settings. Default is
68 * <code>sun.reflect.DelegatingClassLoader</code>.
69 *
70 * @see WildcardMatcher
71 */
72 public static final String EXCLCLASSLOADER = "exclclassloader";
73
74 /**
75 * Specifies a session identifier that is written with the execution data.
76 * Without this parameter a random identifier is created by the agent.
77 */
78 public static final String SESSIONID = "sessionid";
79
80 /**
81 * Specifies whether the agent will automatically dump coverage data on VM
82 * exit. Default is <code>true</code>
83 */
84 public static final String DUMPONEXIT = "dumponexit";
85
86 /**
87 * Specifies the output mode. Default is
88 * <code>{@link OutputMode#file}</code>.
89 *
90 * @see OutputMode#file
91 * @see OutputMode#tcpserver
92 * @see OutputMode#tcpclient
93 */
94 public static final String OUTPUT = "output";
95
96 /**
97 * Possible values for {@link AgentOptions#OUTPUT}.
98 */
99 public static enum OutputMode {
100
101 /**
102 * Value for the {@link AgentOptions#OUTPUT} parameter: At VM
103 * termination execution data is written to the file specified by
104 * {@link AgentOptions#DESTFILE}.
105 */
106 file,
107
108 /**
109 * Value for the {@link AgentOptions#OUTPUT} parameter: The agent
110 * listens for incoming connections on a TCP port specified by
111 * {@link AgentOptions#ADDRESS} and {@link AgentOptions#PORT} .
112 */
113 tcpserver,
114
115 /**
116 * Value for the {@link AgentOptions#OUTPUT} parameter: At startup the
117 * agent connects to a TCP port specified by the
118 * {@link AgentOptions#ADDRESS} and {@link AgentOptions#PORT} attribute.
119 */
120 tcpclient
121
122 }
123
124 /**
125 * The IP address or DNS name the tcpserver binds to or the tcpclient
126 * connects to. Default is defined by {@link #DEFAULT_ADDRESS}.
127 */
128 public static final String ADDRESS = "address";
129
130 /**
131 * Default value for the "address" agent option
132 */
133 public static final String DEFAULT_ADDRESS = null;
134
135 /**
136 * The port the tcpserver binds to or the tcpclient connects to. In
137 * tcpserver mode the port must be available, which means that if multiple
138 * JaCoCo agents should run on the same machine, different ports have to be
139 * specified. Default is defined by {@link #DEFAULT_PORT}.
140 */
141 public static final String PORT = "port";
142
143 /**
144 * Default value for the "port" agent option
145 */
146 public static final int DEFAULT_PORT = 6300;
147
148 private static final Collection<String> VALID_OPTIONS = Arrays.asList(
149 DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER, SESSIONID,
150 DUMPONEXIT, OUTPUT, ADDRESS, PORT);
151
152 private final Map<String, String> options;
153
154 /**
155 * New instance with all values set to default.
156 */
157 public AgentOptions() {
158 this.options = new HashMap<String, String>();
159 }
160
161 /**
162 * New instance parsed from the given option string.
163 *
164 * @param optionstr
165 * string to parse or <code>null</code>
166 */
167 public AgentOptions(final String optionstr) {
168 this();
169 if (optionstr != null && optionstr.length() > 0) {
170 for (final String entry : optionstr.split(",")) {
171 final int pos = entry.indexOf('=');
172 if (pos == -1) {
173 throw new IllegalArgumentException(format(
174 "Invalid agent option syntax \"%s\".", optionstr));
175 }
176 final String key = entry.substring(0, pos);
177 if (!VALID_OPTIONS.contains(key)) {
178 throw new IllegalArgumentException(format(
179 "Unknown agent option \"%s\".", key));
180 }
181
182 final String value = entry.substring(pos + 1);
183 setOption(key, value);
184 }
185
186 validateAll();
187 }
188 }
189
190 private void validateAll() {
191 validatePort(getPort());
192 getOutput();
193 }
194
195 private void validatePort(final int port) {
196 if (port < 0) {
197 throw new IllegalArgumentException("port must be positive");
198 }
199 }
200
201 /**
202 * Returns the output file location.
203 *
204 * @return output file location
205 */
206 public String getDestfile() {
207 return getOption(DESTFILE, "jacoco.exec");
208 }
209
210 /**
211 * Sets the output file location.
212 *
213 * @param destfile
214 * output file location
215 */
216 public void setDestfile(final String destfile) {
217 setOption(DESTFILE, destfile);
218 }
219
220 /**
221 * Returns whether the output should be appended to an existing file.
222 *
223 * @return <code>true</code>, when the output should be appended
224 */
225 public boolean getAppend() {
226 return getOption(APPEND, true);
227 }
228
229 /**
230 * Sets whether the output should be appended to an existing file.
231 *
232 * @param append
233 * <code>true</code>, when the output should be appended
234 */
235 public void setAppend(final boolean append) {
236 setOption(APPEND, append);
237 }
238
239 /**
240 * Returns the wildcard expression for classes to include.
241 *
242 * @return wildcard expression for classes to include
243 * @see WildcardMatcher
244 */
245 public String getIncludes() {
246 return getOption(INCLUDES, "*");
247 }
248
249 /**
250 * Sets the wildcard expression for classes to include.
251 *
252 * @param includes
253 * wildcard expression for classes to include
254 * @see WildcardMatcher
255 */
256 public void setIncludes(final String includes) {
257 setOption(INCLUDES, includes);
258 }
259
260 /**
261 * Returns the wildcard expression for classes to exclude.
262 *
263 * @return wildcard expression for classes to exclude
264 * @see WildcardMatcher
265 */
266 public String getExcludes() {
267 return getOption(EXCLUDES, "");
268 }
269
270 /**
271 * Sets the wildcard expression for classes to exclude.
272 *
273 * @param excludes
274 * wildcard expression for classes to exclude
275 * @see WildcardMatcher
276 */
277 public void setExcludes(final String excludes) {
278 setOption(EXCLUDES, excludes);
279 }
280
281 /**
282 * Returns the wildcard expression for excluded class loaders.
283 *
284 * @return expression for excluded class loaders
285 * @see WildcardMatcher
286 */
287 public String getExclClassloader() {
288 return getOption(EXCLCLASSLOADER, "sun.reflect.DelegatingClassLoader");
289 }
290
291 /**
292 * Sets the wildcard expression for excluded class loaders.
293 *
294 * @param expression
295 * expression for excluded class loaders
296 * @see WildcardMatcher
297 */
298 public void setExclClassloader(final String expression) {
299 setOption(EXCLCLASSLOADER, expression);
300 }
301
302 /**
303 * Returns the session identifier.
304 *
305 * @return session identifier
306 */
307 public String getSessionId() {
308 return getOption(SESSIONID, null);
309 }
310
311 /**
312 * Sets the session identifier.
313 *
314 * @param id
315 * session identifier
316 */
317 public void setSessionId(final String id) {
318 setOption(SESSIONID, id);
319 }
320
321 /**
322 * Returns whether coverage data should be dumped on exit
323 *
324 * @return <code>true</code> if coverage data will be written on VM exit
325 */
326 public boolean getDumpOnExit() {
327 return getOption(DUMPONEXIT, true);
328 }
329
330 /**
331 * Sets whether coverage data should be dumped on exit
332 *
333 * @param dumpOnExit
334 * <code>true</code> if coverage data should be written on VM
335 * exit
336 */
337 public void setDumpOnExit(final boolean dumpOnExit) {
338 setOption(DUMPONEXIT, dumpOnExit);
339 }
340
341 /**
342 * Returns the port on which to listen to when the output is
343 * <code>tcpserver</code> or the port to connect to when output is
344 * <code>tcpclient</code>.
345 *
346 * @return port to listen on or connect to
347 */
348 public int getPort() {
349 return getOption(PORT, DEFAULT_PORT);
350 }
351
352 /**
353 * Sets the port on which to listen to when output is <code>tcpserver</code>
354 * or the port to connect to when output is <code>tcpclient</code>
355 *
356 * @param port
357 */
358 public void setPort(final int port) {
359 validatePort(port);
360 setOption(PORT, port);
361 }
362
363 /**
364 * Gets the hostname or IP address to listen to when output is
365 * <code>tcpserver</code> or connect to when output is
366 * <code>tcpclient</code>
367 *
368 * @return Hostname or IP address
369 */
370 public String getAddress() {
371 return getOption(ADDRESS, DEFAULT_ADDRESS);
372 }
373
374 /**
375 * Sets the hostname or IP address to listen to when output is
376 * <code>tcpserver</cose> or connect to when output is <code>tcpclient</code>
377 *
378 * @param address
379 * Hostname or IP address
380 */
381 public void setAddress(final String address) {
382 setOption(ADDRESS, address);
383 }
384
385 /**
386 * Returns the output mode
387 *
388 * @return current output mode
389 */
390 public OutputMode getOutput() {
391 final String value = options.get(OUTPUT);
392 return value == null ? OutputMode.file : OutputMode.valueOf(value);
393 }
394
395 /**
396 * Sets the output mode
397 *
398 * @param output
399 * Output mode
400 */
401 public void setOutput(final String output) {
402 setOutput(OutputMode.valueOf(output));
403 }
404
405 /**
406 * Sets the output mode
407 *
408 * @param output
409 * Output mode
410 */
411 public void setOutput(final OutputMode output) {
412 setOption(OUTPUT, output.name());
413 }
414
415 private void setOption(final String key, final int value) {
416 setOption(key, Integer.toString(value));
417 }
418
419 private void setOption(final String key, final boolean value) {
420 setOption(key, Boolean.toString(value));
421 }
422
423 private void setOption(final String key, final String value) {
424 if (value.contains(",")) {
425 throw new IllegalArgumentException(format(
426 "Invalid character in option argument \"%s\"", value));
427 }
428 options.put(key, value);
429 }
430
431 private String getOption(final String key, final String defaultValue) {
432 final String value = options.get(key);
433 return value == null ? defaultValue : value;
434 }
435
436 private boolean getOption(final String key, final boolean defaultValue) {
437 final String value = options.get(key);
438 return value == null ? defaultValue : Boolean.parseBoolean(value);
439 }
440
441 private int getOption(final String key, final int defaultValue) {
442 final String value = options.get(key);
443 return value == null ? defaultValue : Integer.parseInt(value);
444 }
445
446 /**
447 * Generate required JVM argument string based on current configuration and
448 * supplied agent jar location
449 *
450 * @param agentJarFile
451 * location of the JaCoCo Agent Jar
452 * @return Argument to pass to create new VM with coverage enabled
453 */
454 public String getVMArgument(final File agentJarFile) {
455 return format("-javaagent:%s=%s", agentJarFile, this);
456 }
457
458 /**
459 * Creates a string representation that can be passed to the agent via the
460 * command line. Might be the empty string, if no options are set.
461 */
462 @Override
463 public String toString() {
464 final StringBuilder sb = new StringBuilder();
465 for (final String key : VALID_OPTIONS) {
466 final String value = options.get(key);
467 if (value != null) {
468 if (sb.length() > 0) {
469 sb.append(',');
470 }
471 sb.append(key).append('=').append(value);
472 }
473 }
474 return sb.toString();
475 }
476
477}