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