1/*******************************************************************************
2 * Copyright (c) 2009, 2018 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.tools;
13
14import java.io.IOException;
15import java.io.InterruptedIOException;
16import java.net.InetAddress;
17import java.net.Socket;
18
19import org.jacoco.core.runtime.RemoteControlReader;
20import org.jacoco.core.runtime.RemoteControlWriter;
21
22/**
23 * A client for remote execution data dumps.
24 */
25public class ExecDumpClient {
26
27	private boolean dump;
28	private boolean reset;
29	private int retryCount;
30	private long retryDelay;
31
32	/**
33	 * New instance with the defaults <code>dump==true</code>,
34	 * <code>reset==false</code>, <code>retryCount==0</code> and
35	 * <code>retryDelay=1000</code>.
36	 */
37	public ExecDumpClient() {
38		this.dump = true;
39		this.reset = false;
40		this.retryCount = 0;
41		this.setRetryDelay(1000);
42	}
43
44	/**
45	 * Specifies whether a dump should be requested
46	 *
47	 * @param dump
48	 *            <code>true</code> if a dump should be requested
49	 */
50	public void setDump(final boolean dump) {
51		this.dump = dump;
52	}
53
54	/**
55	 * Specifies whether execution data should be reset.
56	 *
57	 * @param reset
58	 *            <code>true</code> if execution data should be reset
59	 */
60	public void setReset(final boolean reset) {
61		this.reset = reset;
62	}
63
64	/**
65	 * Sets the number of retry attempts to connect to the target socket. This
66	 * allows to wait for a certain time until the target agent has initialized.
67	 *
68	 * @param retryCount
69	 *            number of retries
70	 */
71	public void setRetryCount(final int retryCount) {
72		this.retryCount = retryCount;
73	}
74
75	/**
76	 * Sets the delay time before between connection attempts.
77	 *
78	 * @param retryDelay
79	 *            delay in milliseconds
80	 */
81	public void setRetryDelay(final long retryDelay) {
82		this.retryDelay = retryDelay;
83	}
84
85	/**
86	 * Requests a dump from the given end-point.
87	 *
88	 * @param address
89	 *            IP-Address to connect to
90	 * @param port
91	 *            port to connect to
92	 * @return container for the dumped data
93	 * @throws IOException
94	 *             in case the dump can not be requested
95	 */
96	public ExecFileLoader dump(final String address, final int port)
97			throws IOException {
98		return dump(InetAddress.getByName(address), port);
99	}
100
101	/**
102	 * Requests a dump from the given end-point.
103	 *
104	 * @param address
105	 *            host name or IP-Address to connect to
106	 * @param port
107	 *            port to connect to
108	 * @return container for the dumped data
109	 * @throws IOException
110	 *             in case the dump can not be requested
111	 */
112	public ExecFileLoader dump(final InetAddress address, final int port)
113			throws IOException {
114		final ExecFileLoader loader = new ExecFileLoader();
115		final Socket socket = tryConnect(address, port);
116		try {
117			final RemoteControlWriter remoteWriter = new RemoteControlWriter(
118					socket.getOutputStream());
119			final RemoteControlReader remoteReader = new RemoteControlReader(
120					socket.getInputStream());
121			remoteReader.setSessionInfoVisitor(loader.getSessionInfoStore());
122			remoteReader
123					.setExecutionDataVisitor(loader.getExecutionDataStore());
124
125			remoteWriter.visitDumpCommand(dump, reset);
126
127			if (!remoteReader.read()) {
128				throw new IOException("Socket closed unexpectedly.");
129			}
130
131		} finally {
132			socket.close();
133		}
134		return loader;
135	}
136
137	private Socket tryConnect(final InetAddress address, final int port)
138			throws IOException {
139		int count = 0;
140		while (true) {
141			try {
142				onConnecting(address, port);
143				return new Socket(address, port);
144			} catch (final IOException e) {
145				if (++count > retryCount) {
146					throw e;
147				}
148				onConnectionFailure(e);
149				sleep();
150			}
151		}
152	}
153
154	private void sleep() throws InterruptedIOException {
155		try {
156			Thread.sleep(retryDelay);
157		} catch (final InterruptedException e) {
158			throw new InterruptedIOException();
159		}
160	}
161
162	/**
163	 * This method can be overwritten to get an event just before a connection
164	 * is made.
165	 *
166	 * @param address
167	 *            target address
168	 * @param port
169	 *            target port
170	 */
171	protected void onConnecting(
172			@SuppressWarnings("unused") final InetAddress address,
173			@SuppressWarnings("unused") final int port) {
174	}
175
176	/**
177	 * This method can be overwritten to get an event for connection failures
178	 * when another retry will be attempted.
179	 *
180	 * @param exception
181	 *            connection error
182	 */
183	protected void onConnectionFailure(
184			@SuppressWarnings("unused") final IOException exception) {
185	}
186
187}
188