blob: c35894e6c8a038f1cdb1037868c53412ce98e3a8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 EfficiOS Inc., Alexandre Montplaisir and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Alexandre Montplaisir - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.segmentstore.core.treemap;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.segmentstore.core.BasicSegment;
import org.eclipse.tracecompass.segmentstore.core.ISegment;
import org.eclipse.tracecompass.segmentstore.core.ISegmentStore;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.TreeMultimap;
/**
* Implementation of a {@link ISegmentStore} using an in-memory {@link TreeMultimap}s.
* This relatively simple implementation holds everything in memory, and as such
* cannot contain too much data.
*
* The TreeMapStore itself is Iterable, and its iteration order will be by
* ascending order of start times. For segments with identical start times, the
* secondary comparator will be the end time. If even those are equal, it will
* defer to the segments' natural ordering ({@link ISegment#compareTo}).
*
* The store's tree map will not accept duplicate key-value pairs, which means
* that if you want several segments with the same start and end times, make
* sure their compareTo() differentiates them.
*
* Removal operations are not supported.
*
* @param <E>
* The type of segment held in this store
*
* @author Alexandre Montplaisir
*/
public class TreeMapStore<@NonNull E extends ISegment> implements ISegmentStore<E> {
private final ReadWriteLock fLock = new ReentrantReadWriteLock(false);
private final TreeMultimap<Long, E> fStartTimesIndex;
private volatile int fSize;
private volatile long fStart = Long.MAX_VALUE;
private volatile long fEnd = Long.MIN_VALUE;
private @Nullable transient Iterable<E> fLastSnapshot = null;
/**
* Constructor
*/
public TreeMapStore() {
/*
* For the start times index, the "key comparator" will compare the
* start times as longs directly. This is the primary comparator for its
* tree map.
*
* The secondary "value" comparator will check the end times first, and
* in the event of a tie, defer to the ISegment's Comparable
* implementation, a.k.a. its natural ordering.
*/
fStartTimesIndex = TreeMultimap.create(Comparator.<Long>naturalOrder(),
Comparator.comparingLong(E::getEnd).thenComparing(Function.identity()));
fSize = 0;
}
// ------------------------------------------------------------------------
// Methods from Collection
// ------------------------------------------------------------------------
@Override
public Iterator<E> iterator() {
fLock.readLock().lock();
try {
Iterable<E> lastSnapshot = fLastSnapshot;
if (lastSnapshot == null) {
lastSnapshot = ImmutableList.copyOf(fStartTimesIndex.values());
fLastSnapshot = lastSnapshot;
}
return checkNotNull(lastSnapshot.iterator());
} finally {
fLock.readLock().unlock();
}
}
@Override
public boolean add(@Nullable E val) {
if (val == null) {
throw new IllegalArgumentException();
}
fLock.writeLock().lock();
try {
boolean put = fStartTimesIndex.put(val.getStart(), val);
if (put) {
fSize++;
fStart = Math.min(fStart, val.getStart());
fEnd = Math.max(fEnd, val.getEnd());
fLastSnapshot = null;
}
return put;
} finally {
fLock.writeLock().unlock();
}
}
@Override
public int size() {
return fSize;
}
@Override
public boolean isEmpty() {
return (fSize == 0);
}
@Override
public boolean contains(@Nullable Object o) {
if (o == null || !(o instanceof ISegment)) {
return false;
}
fLock.readLock().lock();
try {
/* Narrow down the search */
ISegment seg = (ISegment) o;
return fStartTimesIndex.get(seg.getStart()).contains(o);
} finally {
fLock.readLock().unlock();
}
}
@Override
public boolean containsAll(@Nullable Collection<?> c) {
fLock.readLock().lock();
try {
return fStartTimesIndex.values().containsAll(c);
} finally {
fLock.readLock().unlock();
}
}
@Override
public Object[] toArray() {
fLock.readLock().lock();
try {
return fStartTimesIndex.values().toArray();
} finally {
fLock.readLock().unlock();
}
}
@Override
public <T> T[] toArray(T[] a) {
fLock.readLock().lock();
try {
return fStartTimesIndex.values().toArray(a);
} finally {
fLock.readLock().unlock();
}
}
@Override
public boolean addAll(@Nullable Collection<? extends E> c) {
if (c == null) {
throw new IllegalArgumentException();
}
fLock.writeLock().lock();
try {
boolean changed = false;
for (E elem : c) {
if (add(elem)) {
changed = true;
}
}
return changed;
} finally {
fLock.writeLock().unlock();
}
}
@Override
public void clear() {
fLock.writeLock().lock();
try {
fSize = 0;
fStart = Long.MAX_VALUE;
fEnd = Long.MIN_VALUE;
fStartTimesIndex.clear();
} finally {
fLock.writeLock().unlock();
}
}
// ------------------------------------------------------------------------
// Methods added by ISegmentStore
// ------------------------------------------------------------------------
@Override
public Iterable<E> getIntersectingElements(long start, long end) {
fLock.readLock().lock();
try {
if (start <= fStart && end >= fEnd) {
if (fLastSnapshot == null) {
fLastSnapshot = ImmutableList.copyOf(fStartTimesIndex.values());
}
return checkNotNull(fLastSnapshot);
}
List<E> iterable = new ArrayList<>();
/**
* fromElement is used to search the navigable sets of the
* TreeMultiMap for Segments that end after start query time.
*/
E fromElement = (E) new BasicSegment(Long.MIN_VALUE, start);
/* Get the sets of segments for startTimes <= end */
for (Collection<E> col : fStartTimesIndex.asMap().headMap(end, true).values()) {
/*
* The collections of segments are NavigableSets for
* TreeMultimap, add elements from the tailSet: which will have
* endTimes >= start.
*/
NavigableSet<E> nav = (NavigableSet<E>) col;
iterable.addAll(nav.tailSet(fromElement, true));
}
return iterable;
} finally {
fLock.readLock().unlock();
}
}
@Override
public void dispose() {
clear();
}
}