1/*******************************************************************************
2 * Copyright (c) 2009, 2017 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// BEGIN android-change
125//				jmxRegistration = new JmxRegistration(this);
126// END android-change
127			}
128		} catch (final Exception e) {
129			logger.logExeption(e);
130		}
131	}
132
133	/**
134	 * Shutdown the agent again.
135	 */
136	public void shutdown() {
137		try {
138			if (options.getDumpOnExit()) {
139				output.writeExecutionData(false);
140			}
141			output.shutdown();
142			if (jmxRegistration != null) {
143				jmxRegistration.call();
144			}
145		} catch (final Exception e) {
146			logger.logExeption(e);
147		}
148	}
149
150	/**
151	 * Create output implementation as given by the agent options.
152	 *
153	 * @return configured controller implementation
154	 */
155	IAgentOutput createAgentOutput() {
156		final OutputMode controllerType = options.getOutput();
157		switch (controllerType) {
158		case file:
159			return new FileOutput();
160		case tcpserver:
161			return new TcpServerOutput(logger);
162		case tcpclient:
163			return new TcpClientOutput(logger);
164		case none:
165			return new NoneOutput();
166		default:
167			throw new AssertionError(controllerType);
168		}
169	}
170
171	private String createSessionId() {
172		String host;
173		try {
174			host = InetAddress.getLocalHost().getHostName();
175		} catch (final Exception e) {
176			// Also catch platform specific exceptions (like on Android) to
177			// avoid bailing out here
178			host = "unknownhost";
179		}
180		return host + "-" + AbstractRuntime.createRandomId();
181	}
182
183	// === IAgent Implementation ===
184
185	public String getVersion() {
186		return JaCoCo.VERSION;
187	}
188
189	public String getSessionId() {
190		return data.getSessionId();
191	}
192
193	public void setSessionId(final String id) {
194		data.setSessionId(id);
195	}
196
197	public void reset() {
198		data.reset();
199	}
200
201	public byte[] getExecutionData(final boolean reset) {
202		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
203		try {
204			final ExecutionDataWriter writer = new ExecutionDataWriter(buffer);
205			data.collect(writer, writer, reset);
206		} catch (final IOException e) {
207			// Must not happen with ByteArrayOutputStream
208			throw new AssertionError(e);
209		}
210		return buffer.toByteArray();
211	}
212
213	public void dump(final boolean reset) throws IOException {
214		output.writeExecutionData(reset);
215	}
216
217}
218