/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.test.mt.util;
import com.google.common.base.Supplier;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import org.junit.ComparisonFailure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
import static org.junit.Assert.assertEquals;
public final class TimeMarkerComparison
{
private final List<List<String>> combinedMarks;
public TimeMarkerComparison(Collection<? extends HasTimeMarker> hasTimeMarkers) {
List<TimeMarker> timeMarkers = new ArrayList<>();
for(HasTimeMarker htm : hasTimeMarkers) {
timeMarkers.add(htm.getTimeMarker());
}
combinedMarks = combineTimeMarkers(timeMarkers);
}
/**
* Combine multiple TimeMarkers into a single, ordered list of marks.
*
* <p>
* Final output is a list of messages in the order they occurred. If the
* order is unknown (i.e. overlapping timestamps from multiple markers)
* then multiple messages will be in a single position and sorted by name.
* </p>
*
* <pre>
* Input:
* A: 1 -> [baz,hop], 2 -> [cap], 3 -> [dig]
* B: 1 -> [gap,foo], 3 -> [jog] 4 -> [zap]
* Output:
* [ [baz,gap], [foo,hop], [cap], [dig,jog], [zap] ]
* </pre>
*/
static List<List<String>> combineTimeMarkers(TimeMarker... timeMarkers) {
return combineTimeMarkers(Arrays.asList(timeMarkers));
}
/** See {@link #combineTimeMarkers(TimeMarker...)}. */
static List<List<String>> combineTimeMarkers(Collection<TimeMarker> timeMarkers) {
// Sort by time, list of lists of messages from all markers
ListMultimap<Long,List<String>> timeToMarks = Multimaps.newListMultimap(
new TreeMap<Long, Collection<List<String>>>(),
new Supplier<List<List<String>>>() {
@Override
public List<List<String>> get() {
return new ArrayList<>();
}
}
);
for(TimeMarker tm : timeMarkers) {
for(Entry<Long, List<String>> entry : Multimaps.asMap(tm.getMarks()).entrySet()) {
timeToMarks.put(entry.getKey(), entry.getValue());
}
}
// Concatenate into final ordering
List<List<String>> output = new ArrayList<>();
for(List<List<String>> singleTimeMarks : Multimaps.asMap(timeToMarks).values()) {
// Split a given Marker's messages apart, coalesce overlapping timestamps from multiple Marker's
ListMultimap<Integer,String> split = ArrayListMultimap.create();
for(List<String> marks : singleTimeMarks) {
for(int i = 0; i < marks.size(); ++i) {
split.put(i, marks.get(i));
}
}
// Sort any coalesced and output
for(List<String> marks : Multimaps.asMap(split).values()) {
Collections.sort(marks);
output.add(marks);
}
}
return output;
}
public void verify(String... expectedMessages) {
List<Collection<String>> expected = new ArrayList<>();
for (String expectedMessage : expectedMessages) {
if(expectedMessage != null) {
expected.add(Collections.singletonList(expectedMessage));
}
}
// For pretty print
if(!expected.equals(combinedMarks)) {
throw new ComparisonFailure("TimePoint messages (in order)", expected.toString(), combinedMarks.toString());
}
}
public List<String> getMarkNames() {
List<String> markNames = new ArrayList<>();
for (Collection<String> marksList : combinedMarks) {
assertEquals("individual marks lists must be singletons; size ", 1, marksList.size());
markNames.addAll(marksList);
}
return markNames;
}
public boolean startsWith(String... expectedMessages) {
List<String> expected = Arrays.asList(expectedMessages);
List<String> actual = getMarkNames();
if (actual.size() < expected.size()) {
return false;
}
actual = actual.subList(0, expected.size());
return expected.equals(actual);
}
public boolean matches(String... expectedMessages) {
List<String> expected = Arrays.asList(expectedMessages);
List<String> actual = getMarkNames();
return expected.equals(actual);
}
@Override
public String toString() {
return combinedMarks.toString();
}
}