Agent.java revision 283abfa148b749678924b5e75eabd35a2d58f9f8
1/*******************************************************************************
2 * Copyright (c) 2009, 2014 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.agent.rt.internal;
13
14import java.io.ByteArrayOutputStream;
15import java.io.IOException;
16import java.net.InetAddress;
17import java.util.concurrent.Callable;
18
19import org.jacoco.agent.rt.IAgent;
20import org.jacoco.agent.rt.internal.output.FileOutput;
21import org.jacoco.agent.rt.internal.output.IAgentOutput;
22import org.jacoco.agent.rt.internal.output.NoneOutput;
23import org.jacoco.agent.rt.internal.output.TcpClientOutput;
24import org.jacoco.agent.rt.internal.output.TcpServerOutput;
25import org.jacoco.core.JaCoCo;
26import org.jacoco.core.data.ExecutionDataWriter;
27import org.jacoco.core.runtime.AbstractRuntime;
28import org.jacoco.core.runtime.AgentOptions;
29import org.jacoco.core.runtime.AgentOptions.OutputMode;
30import org.jacoco.core.runtime.RuntimeData;
31
32/**
33 * The agent manages the life cycle of JaCoCo runtime.
34 */
35public class Agent implements IAgent {
36
37	private static Agent singleton;
38
39	/**
40	 * Returns a global instance which is already started. If the method is
41	 * called the first time the instance is created with the given options.
42	 *
43	 * @param options
44	 *            options to configure the instance
45	 * @return global instance
46	 */
47	public static synchronized Agent getInstance(final AgentOptions options) {
48		if (singleton == null) {
49			final Agent agent = new Agent(options, IExceptionLogger.SYSTEM_ERR);
50			agent.startup();
51			Runtime.getRuntime().addShutdownHook(new Thread() {
52				@Override
53				public void run() {
54					agent.shutdown();
55				}
56			});
57			singleton = agent;
58		}
59		return singleton;
60	}
61
62	/**
63	 * Returns a global instance which is already started. If a agent has not
64	 * been initialized before this method will fail.
65	 *
66	 * @return global instance
67	 * @throws IllegalStateException
68	 *             if no Agent has been started yet
69	 */
70	public static synchronized Agent getInstance() throws IllegalStateException {
71		if (singleton == null) {
72			throw new IllegalStateException("JaCoCo agent not started.");
73		}
74		return singleton;
75	}
76
77	private final AgentOptions options;
78
79	private final IExceptionLogger logger;
80
81	private final RuntimeData data;
82
83	private IAgentOutput output;
84
85	private Callable<Void> jmxRegistration;
86
87	/**
88	 * Creates a new agent with the given agent options.
89	 *
90	 * @param options
91	 *            agent options
92	 * @param logger
93	 *            logger used by this agent
94	 */
95	Agent(final AgentOptions options, final IExceptionLogger logger) {
96		this.options = options;
97		this.logger = logger;
98		this.data = new RuntimeData();
99	}
100
101	/**
102	 * Returns the runtime data object created by this agent
103	 *
104	 * @return runtime data for this agent instance
105	 */
106	public RuntimeData getData() {
107		return data;
108	}
109
110	/**
111	 * Initializes this agent.
112	 *
113	 */
114	public void startup() {
115		try {
116			String sessionId = options.getSessionId();
117			if (sessionId == null) {
118				sessionId = createSessionId();
119			}
120			data.setSessionId(sessionId);
121			output = createAgentOutput();
122			output.startup(options, data);
123			if (options.getJmx()) {
124				jmxRegistration = new JmxRegistration(this);
125			}
126		} catch (final Exception e) {
127			logger.logExeption(e);
128		}
129	}
130
131	/**
132	 * Shutdown the agent again.
133	 */
134	public void shutdown() {
135		try {
136			if (options.getDumpOnExit()) {
137				output.writeExecutionData(false);
138			}
139			output.shutdown();
140			if (jmxRegistration != null) {
141				jmxRegistration.call();
142			}
143		} catch (final Exception e) {
144			logger.logExeption(e);
145		}
146	}
147
148	/**
149	 * Create output implementation as given by the agent options.
150	 *
151	 * @return configured controller implementation
152	 */
153	IAgentOutput createAgentOutput() {
154		final OutputMode controllerType = options.getOutput();
155		switch (controllerType) {
156		case file:
157			return new FileOutput();
158		case tcpserver:
159			return new TcpServerOutput(logger);
160		case tcpclient:
161			return new TcpClientOutput(logger);
162		case none:
163			return new NoneOutput();
164		default:
165			throw new AssertionError(controllerType);
166		}
167	}
168
169	private String createSessionId() {
170		String host;
171		try {
172			host = InetAddress.getLocalHost().getHostName();
173		} catch (final Exception e) {
174			// Also catch platform specific exceptions (like on Android) to
175			// avoid bailing out here
176			host = "unknownhost";
177		}
178		return host + "-" + AbstractRuntime.createRandomId();
179	}
180
181	// === IAgent Implementation ===
182
183	public String getVersion() {
184		return JaCoCo.VERSION;
185	}
186
187	public String getSessionId() {
188		return data.getSessionId();
189	}
190
191	public void setSessionId(final String id) {
192		data.setSessionId(id);
193	}
194
195	public void reset() {
196		data.reset();
197	}
198
199	public byte[] getExecutionData(final boolean reset) {
200		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
201		try {
202			final ExecutionDataWriter writer = new ExecutionDataWriter(buffer);
203			data.collect(writer, writer, reset);
204		} catch (final IOException e) {
205			// Must not happen with ByteArrayOutputStream
206			throw new AssertionError(e);
207		}
208		return buffer.toByteArray();
209	}
210
211	public void dump(final boolean reset) throws IOException {
212		output.writeExecutionData(reset);
213	}
214
215}
216