ExecutionDataStore.java

    1/*******************************************************************************
    2 * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
    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 * $Id: $
   12 *******************************************************************************/
   13package org.jacoco.core.data;
   14
   15import static java.lang.String.format;
   16
   17import java.util.Arrays;
   18import java.util.HashMap;
   19import java.util.Map;
   20
   21/**
   22 * In-memory data store for execution data. The data can be added through its
   23 * {@link IExecutionDataVisitor} interface. If execution data is provided
   24 * multiple times for the same class the data is merged, i.e. a block is marked
   25 * as executed if it is reported as executed at least once. This allows to merge
   26 * coverage date from multiple runs. A instance of this class is not thread
   27 * safe.
   28 * 
   29 * @author Marc R. Hoffmann
   30 * @version $Revision: $
   31 */
   32public class ExecutionDataStore implements IExecutionDataVisitor {
   33
   34    private final Map<Long, Entry> entries = new HashMap<Long, Entry>();
   35
   36    /**
   37     * Adds the given block data structure into the store. If there is already a
   38     * data structure for this class ID, this structure is merged with the given
   39     * one. In this case a {@link IllegalStateException} is thrown, if both
   40     * executions data structure do have different sizes or the name is
   41     * different.
   42     * 
   43     * @param classid
   44     *            unique class identifier
   45     * @param name
   46     *            VM name of the class
   47     * @param data
   48     *            execution data
   49     */
   50    public void put(final Long classid, final String name, final boolean[] data) {
   51        final Entry entry = entries.get(classid);
   52        if (entry == null) {
   53            entries.put(classid, new Entry(name, data));
   54        } else {
   55            entry.merge(classid, name, data);
   56        }
   57    }
   58
   59    /**
   60     * Adds the given block data structure into the store. If there is already a
   61     * data structure for this class ID, this structure is merged with the given
   62     * one. In this case a {@link IllegalStateException} is thrown, if both
   63     * executions data structure do have different sizes or the name is
   64     * different.
   65     * 
   66     * @param classid
   67     *            unique class identifier
   68     * @param name
   69     *            VM name of the class
   70     * @param data
   71     *            execution data
   72     */
   73    public void put(final long classid, final String name, final boolean[] data) {
   74        put(Long.valueOf(classid), name, data);
   75    }
   76
   77    /**
   78     * Returns the coverage data for the class with the given identifier if
   79     * available.
   80     * 
   81     * @param classid
   82     *            class identifier
   83     * @return coverage data or <code>null</code>
   84     */
   85    public boolean[] getData(final Long classid) {
   86        final Entry entry = entries.get(classid);
   87        return entry == null ? null : entry.data;
   88    }
   89
   90    /**
   91     * Returns the coverage data for the class with the given identifier if
   92     * available.
   93     * 
   94     * @param classid
   95     *            class identifier
   96     * @return coverage data or <code>null</code>
   97     */
   98    public boolean[] getData(final long classid) {
   99        return getData(Long.valueOf(classid));
  100    }
  101
  102    /**
  103     * Returns the vm name of the class with the given id.
  104     * 
  105     * @param classid
  106     *            class identifier
  107     * @return vm name or <code>null</code>
  108     */
  109    public String getName(final Long classid) {
  110        final Entry entry = entries.get(classid);
  111        return entry == null ? null : entry.name;
  112    }
  113
  114    /**
  115     * Returns the vm name of the class with the given id.
  116     * 
  117     * @param classid
  118     *            class identifier
  119     * @return vm name or <code>null</code>
  120     */
  121    public String getName(final long classid) {
  122        return getName(Long.valueOf(classid));
  123    }
  124
  125    /**
  126     * Resets all execution data structures, i.e. marks them as not executed.
  127     * The data structures itself are not deleted.
  128     */
  129    public void reset() {
  130        for (final Entry executionData : this.entries.values()) {
  131            Arrays.fill(executionData.data, false);
  132        }
  133    }
  134
  135    /**
  136     * Writes the content of the store to the given visitor interface.
  137     * 
  138     * @param visitor
  139     *            interface to write content to
  140     */
  141    public void accept(final IExecutionDataVisitor visitor) {
  142        for (final Map.Entry<Long, Entry> i : entries.entrySet()) {
  143            final long id = i.getKey().longValue();
  144            final Entry entry = i.getValue();
  145            visitor.visitClassExecution(id, entry.name, entry.data);
  146        }
  147    }
  148
  149    // === IExecutionDataVisitor ===
  150
  151    public void visitClassExecution(final long classid, final String name,
  152            final boolean[] data) {
  153        put(classid, name, data);
  154    }
  155
  156    private static class Entry {
  157
  158        final String name;
  159        final boolean[] data;
  160
  161        Entry(final String name, final boolean[] data) {
  162            this.name = name;
  163            this.data = data;
  164        }
  165
  166        void merge(final Long classid, final String newName,
  167                final boolean[] newData) {
  168            if (!newName.equals(name)) {
  169                throw new IllegalArgumentException(format(
  170                        "Duplicate id %x for classes %s and %s.", classid,
  171                        name, newName));
  172            }
  173            if (data.length != newData.length) {
  174                throw new IllegalStateException(format(
  175                        "Incompatible execution data for class %s (id %s).",
  176                        name, classid));
  177            }
  178            for (int i = 0; i < data.length; i++) {
  179                if (!data[i]) {
  180                    data[i] = newData[i];
  181                }
  182            }
  183        }
  184    }
  185
  186}