| /******************************************************************************* |
| * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Marc R. Hoffmann - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.jacoco.agent.rt.internal; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.util.concurrent.Callable; |
| |
| import org.jacoco.agent.rt.IAgent; |
| import org.jacoco.agent.rt.internal.output.FileOutput; |
| import org.jacoco.agent.rt.internal.output.IAgentOutput; |
| import org.jacoco.agent.rt.internal.output.NoneOutput; |
| import org.jacoco.agent.rt.internal.output.TcpClientOutput; |
| import org.jacoco.agent.rt.internal.output.TcpServerOutput; |
| import org.jacoco.core.JaCoCo; |
| import org.jacoco.core.data.ExecutionDataWriter; |
| import org.jacoco.core.runtime.AbstractRuntime; |
| import org.jacoco.core.runtime.AgentOptions; |
| import org.jacoco.core.runtime.AgentOptions.OutputMode; |
| import org.jacoco.core.runtime.RuntimeData; |
| |
| /** |
| * The agent manages the life cycle of JaCoCo runtime. |
| */ |
| public class Agent implements IAgent { |
| |
| private static Agent singleton; |
| |
| /** |
| * Returns a global instance which is already started. If the method is |
| * called the first time the instance is created with the given options. |
| * |
| * @param options |
| * options to configure the instance |
| * @return global instance |
| */ |
| public static synchronized Agent getInstance(final AgentOptions options) { |
| if (singleton == null) { |
| final Agent agent = new Agent(options, IExceptionLogger.SYSTEM_ERR); |
| agent.startup(); |
| Runtime.getRuntime().addShutdownHook(new Thread() { |
| @Override |
| public void run() { |
| agent.shutdown(); |
| } |
| }); |
| singleton = agent; |
| } |
| return singleton; |
| } |
| |
| /** |
| * Returns a global instance which is already started. If a agent has not |
| * been initialized before this method will fail. |
| * |
| * @return global instance |
| * @throws IllegalStateException |
| * if no Agent has been started yet |
| */ |
| public static synchronized Agent getInstance() throws IllegalStateException { |
| if (singleton == null) { |
| throw new IllegalStateException("JaCoCo agent not started."); |
| } |
| return singleton; |
| } |
| |
| private final AgentOptions options; |
| |
| private final IExceptionLogger logger; |
| |
| private final RuntimeData data; |
| |
| private IAgentOutput output; |
| |
| private Callable<Void> jmxRegistration; |
| |
| /** |
| * Creates a new agent with the given agent options. |
| * |
| * @param options |
| * agent options |
| * @param logger |
| * logger used by this agent |
| */ |
| Agent(final AgentOptions options, final IExceptionLogger logger) { |
| this.options = options; |
| this.logger = logger; |
| this.data = new RuntimeData(); |
| } |
| |
| /** |
| * Returns the runtime data object created by this agent |
| * |
| * @return runtime data for this agent instance |
| */ |
| public RuntimeData getData() { |
| return data; |
| } |
| |
| /** |
| * Initializes this agent. |
| * |
| */ |
| public void startup() { |
| try { |
| String sessionId = options.getSessionId(); |
| if (sessionId == null) { |
| sessionId = createSessionId(); |
| } |
| data.setSessionId(sessionId); |
| output = createAgentOutput(); |
| output.startup(options, data); |
| if (options.getJmx()) { |
| // BEGIN android-change |
| // jmxRegistration = new JmxRegistration(this); |
| // END android-change |
| } |
| } catch (final Exception e) { |
| logger.logExeption(e); |
| } |
| } |
| |
| /** |
| * Shutdown the agent again. |
| */ |
| public void shutdown() { |
| try { |
| if (options.getDumpOnExit()) { |
| output.writeExecutionData(false); |
| } |
| output.shutdown(); |
| if (jmxRegistration != null) { |
| jmxRegistration.call(); |
| } |
| } catch (final Exception e) { |
| logger.logExeption(e); |
| } |
| } |
| |
| /** |
| * Create output implementation as given by the agent options. |
| * |
| * @return configured controller implementation |
| */ |
| IAgentOutput createAgentOutput() { |
| final OutputMode controllerType = options.getOutput(); |
| switch (controllerType) { |
| case file: |
| return new FileOutput(); |
| case tcpserver: |
| return new TcpServerOutput(logger); |
| case tcpclient: |
| return new TcpClientOutput(logger); |
| case none: |
| return new NoneOutput(); |
| default: |
| throw new AssertionError(controllerType); |
| } |
| } |
| |
| private String createSessionId() { |
| String host; |
| try { |
| host = InetAddress.getLocalHost().getHostName(); |
| } catch (final Exception e) { |
| // Also catch platform specific exceptions (like on Android) to |
| // avoid bailing out here |
| host = "unknownhost"; |
| } |
| return host + "-" + AbstractRuntime.createRandomId(); |
| } |
| |
| // === IAgent Implementation === |
| |
| public String getVersion() { |
| return JaCoCo.VERSION; |
| } |
| |
| public String getSessionId() { |
| return data.getSessionId(); |
| } |
| |
| public void setSessionId(final String id) { |
| data.setSessionId(id); |
| } |
| |
| public void reset() { |
| data.reset(); |
| } |
| |
| public byte[] getExecutionData(final boolean reset) { |
| final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| try { |
| final ExecutionDataWriter writer = new ExecutionDataWriter(buffer); |
| data.collect(writer, writer, reset); |
| } catch (final IOException e) { |
| // Must not happen with ByteArrayOutputStream |
| throw new AssertionError(e); |
| } |
| return buffer.toByteArray(); |
| } |
| |
| public void dump(final boolean reset) throws IOException { |
| output.writeExecutionData(reset); |
| } |
| |
| } |