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}