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.data;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * In-memory data store for execution data. The data can be added through its
   * {@link IExecutionDataVisitor} interface. If execution data is provided
   * multiple times for the same class the data is merged, i.e. a probe is marked
   * as executed if it is reported as executed at least once. This allows to merge
   * coverage date from multiple runs. A instance of this class is not thread
   * safe.
   */
  public final class ExecutionDataStore implements IExecutionDataVisitor {
  
  	private final Map<Long, ExecutionData> entries = new HashMap<Long, ExecutionData>();
  
  	private final Set<String> names = new HashSet<String>();
  
  	/**
  	 * Adds the given {@link ExecutionData} object into the store. If there is
  	 * already execution data with this same class id, this structure is merged
  	 * with the given one.
  	 * 
  	 * @param data
  	 *            execution data to add or merge
  	 * @throws IllegalStateException
  	 *             if the given {@link ExecutionData} object is not compatible
  	 *             to a corresponding one, that is already contained
  	 * @see ExecutionData#assertCompatibility(long, String, int)
  	 */
  	public void put(final ExecutionData data) throws IllegalStateException {
  		final Long id = Long.valueOf(data.getId());
  		final ExecutionData entry = entries.get(id);
  		if (entry == null) {
  			entries.put(id, data);
  			names.add(data.getName());
  		} else {
  			entry.merge(data);
  		}
  	}
  
  	/**
  	 * Subtracts the probes in the given {@link ExecutionData} object from the
  	 * store. I.e. for all set probes in the given data object the corresponding
  	 * probes in this store will be unset. If there is no execution data with id
  	 * of the given data object this operation will have no effect.
  	 * 
  	 * @param data
  	 *            execution data to subtract
  	 * @throws IllegalStateException
  	 *             if the given {@link ExecutionData} object is not compatible
  	 *             to a corresponding one, that is already contained
  	 * @see ExecutionData#assertCompatibility(long, String, int)
  	 */
  	public void subtract(final ExecutionData data) throws IllegalStateException {
  		final Long id = Long.valueOf(data.getId());
  		final ExecutionData entry = entries.get(id);
  		if (entry != null) {
  			entry.merge(data, false);
  		}
  	}
  
  	/**
  	 * Subtracts all probes in the given execution data store from this store.
  	 * 
  	 * @param store
  	 *            execution data store to subtract
  	 * @see #subtract(ExecutionData)
  	 */
  	public void subtract(final ExecutionDataStore store) {
  		for (final ExecutionData data : store.getContents()) {
  			subtract(data);
  		}
  	}
  
  	/**
  	 * Returns the {@link ExecutionData} entry with the given id if it exists in
  	 * this store.
  	 * 
  	 * @param id
  	 *            class id
  	 * @return execution data or <code>null</code>
  	 */
  	public ExecutionData get(final long id) {
  		return entries.get(Long.valueOf(id));
  	}
  
  	/**
  	 * Checks whether execution data for classes with the given name are
  	 * contained in the store.
  	 * 
  	 * @param name
  	 *            VM name
  	 * @return <code>true</code> if at least one class with the name is
  	 *         contained.
  	 */
  	public boolean contains(final String name) {
  		return names.contains(name);
  	}
  
  	/**
  	 * Returns the coverage data for the class with the given identifier. If
  	 * there is no data available under the given id a new entry is created.
  	 * 
  	 * @param id
  	 *            class identifier
  	 * @param name
  	 *            VM name of the class
  	 * @param probecount
  	 *            probe data length
  	 * @return execution data
  	 */
  	public ExecutionData get(final Long id, final String name,
  			final int probecount) {
  		ExecutionData entry = entries.get(id);
  		if (entry == null) {
  			entry = new ExecutionData(id.longValue(), name, probecount);
  			entries.put(id, entry);
  			names.add(name);
  		} else {
  			entry.assertCompatibility(id.longValue(), name, probecount);
  		}
  		return entry;
  	}
  
  	/**
  	 * Resets all execution data probes, i.e. marks them as not executed. The
  	 * execution data objects itself are not removed.
  	 */
  	public void reset() {
  		for (final ExecutionData executionData : this.entries.values()) {
  			executionData.reset();
  		}
  	}
  
  	/**
  	 * Returns a collection that represents current contents of the store.
  	 * 
  	 * @return current contents
  	 */
  	public Collection<ExecutionData> getContents() {
  		return new ArrayList<ExecutionData>(entries.values());
  	}
  
  	/**
  	 * Writes the content of the store to the given visitor interface.
  	 * 
  	 * @param visitor
  	 *            interface to write content to
  	 */
  	public void accept(final IExecutionDataVisitor visitor) {
  		for (final ExecutionData data : getContents()) {
  			visitor.visitClassExecution(data);
  		}
  	}
  
  	// === IExecutionDataVisitor ===
  
  	public void visitClassExecution(final ExecutionData data) {
  		put(data);
  	}
  }
  