content: /*******************************************************************************
   * Copyright (c) 2009, 2019 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.core.tools;
  
  import java.io.IOException;
  import java.io.InterruptedIOException;
  import java.net.InetAddress;
  import java.net.Socket;
  
  import org.jacoco.core.runtime.RemoteControlReader;
  import org.jacoco.core.runtime.RemoteControlWriter;
  
  /**
   * A client for remote execution data dumps.
   */
  public class ExecDumpClient {
  
  	private boolean dump;
  	private boolean reset;
  	private int retryCount;
  	private long retryDelay;
  
  	/**
  	 * New instance with the defaults <code>dump==true</code>,
  	 * <code>reset==false</code>, <code>retryCount==0</code> and
  	 * <code>retryDelay=1000</code>.
  	 */
  	public ExecDumpClient() {
  		this.dump = true;
  		this.reset = false;
  		this.retryCount = 0;
  		this.setRetryDelay(1000);
  	}
  
  	/**
  	 * Specifies whether a dump should be requested
  	 * 
  	 * @param dump
  	 *            <code>true</code> if a dump should be requested
  	 */
  	public void setDump(final boolean dump) {
  		this.dump = dump;
  	}
  
  	/**
  	 * Specifies whether execution data should be reset.
  	 * 
  	 * @param reset
  	 *            <code>true</code> if execution data should be reset
  	 */
  	public void setReset(final boolean reset) {
  		this.reset = reset;
  	}
  
  	/**
  	 * Sets the number of retry attempts to connect to the target socket. This
  	 * allows to wait for a certain time until the target agent has initialized.
  	 * 
  	 * @param retryCount
  	 *            number of retries
  	 */
  	public void setRetryCount(final int retryCount) {
  		this.retryCount = retryCount;
  	}
  
  	/**
  	 * Sets the delay time before between connection attempts.
  	 * 
  	 * @param retryDelay
  	 *            delay in milliseconds
  	 */
  	public void setRetryDelay(final long retryDelay) {
  		this.retryDelay = retryDelay;
  	}
  
  	/**
  	 * Requests a dump from the given end-point.
  	 * 
  	 * @param address
  	 *            IP-Address to connect to
  	 * @param port
  	 *            port to connect to
  	 * @return container for the dumped data
  	 * @throws IOException
  	 *             in case the dump can not be requested
  	 */
  	public ExecFileLoader dump(final String address, final int port)
  			throws IOException {
  		return dump(InetAddress.getByName(address), port);
  	}
  
  	/**
  	 * Requests a dump from the given end-point.
  	 * 
  	 * @param address
  	 *            host name or IP-Address to connect to
  	 * @param port
  	 *            port to connect to
  	 * @return container for the dumped data
  	 * @throws IOException
  	 *             in case the dump can not be requested
  	 */
  	public ExecFileLoader dump(final InetAddress address, final int port)
  			throws IOException {
  		final ExecFileLoader loader = new ExecFileLoader();
  		final Socket socket = tryConnect(address, port);
  		try {
  			final RemoteControlWriter remoteWriter = new RemoteControlWriter(
  					socket.getOutputStream());
  			final RemoteControlReader remoteReader = new RemoteControlReader(
  					socket.getInputStream());
  			remoteReader.setSessionInfoVisitor(loader.getSessionInfoStore());
  			remoteReader
  					.setExecutionDataVisitor(loader.getExecutionDataStore());
  
  			remoteWriter.visitDumpCommand(dump, reset);
  
  			if (!remoteReader.read()) {
  				throw new IOException("Socket closed unexpectedly.");
  			}
  
  		} finally {
  			socket.close();
  		}
  		return loader;
  	}
  
  	private Socket tryConnect(final InetAddress address, final int port)
  			throws IOException {
  		int count = 0;
  		while (true) {
  			try {
  				onConnecting(address, port);
  				return new Socket(address, port);
  			} catch (final IOException e) {
  				if (++count > retryCount) {
  					throw e;
  				}
  				onConnectionFailure(e);
  				sleep();
  			}
  		}
  	}
  
  	private void sleep() throws InterruptedIOException {
  		try {
  			Thread.sleep(retryDelay);
  		} catch (final InterruptedException e) {
  			throw new InterruptedIOException();
  		}
  	}
  
  	/**
  	 * This method can be overwritten to get an event just before a connection
  	 * is made.
  	 * 
  	 * @param address
  	 *            target address
  	 * @param port
  	 *            target port
  	 */
  	protected void onConnecting(
  			@SuppressWarnings("unused") final InetAddress address,
  			@SuppressWarnings("unused") final int port) {
  	}
  
  	/**
  	 * This method can be overwritten to get an event for connection failures
  	 * when another retry will be attempted.
  	 * 
  	 * @param exception
  	 *            connection error
  	 */
  	protected void onConnectionFailure(
  			@SuppressWarnings("unused") final IOException exception) {
  	}
  
  }
  