/******************************************************************************* * Copyright (c) 2017 École Polytechnique de Montréal * * 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 *******************************************************************************/ package org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.overlapping; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.internal.provisional.datastore.core.condition.RangeCondition; import org.eclipse.tracecompass.internal.provisional.datastore.core.exceptions.RangeException; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.HTNode; import org.eclipse.tracecompass.internal.provisional.datastore.core.historytree.IHTNode; import org.eclipse.tracecompass.internal.provisional.datastore.core.interval.IHTInterval; import com.google.common.annotations.VisibleForTesting; /** * History tree node for overlapping history tree. Those nodes keep both the * child node's start and end times * * @author Loic Prieur-Drevon * @author Geneviève Bastien * * @param <E> * The type of objects that will be saved in the tree */ public class OverlappingNode<E extends IHTInterval> extends HTNode<E> { /** * Listeners for when nodes are closed, to update the child node's * information. */ private final Set<NodeClosedListener> fListeners = new HashSet<>(); /** * Listener to node close operations. This can be used by parent nodes to * update their children data when a node is closed. */ @FunctionalInterface protected static interface NodeClosedListener { /** * A node was just closed * * @param node * The node that was just closed * @param endtime * The end time of the node */ void nodeClosed(OverlappingNode<?> node, long endtime); } /** * Adds the data concerning the segment nodes, start/end of children */ protected static class OverlappingExtraData extends CoreNodeData { private final long[] fChildStart; private final long[] fChildEnd; /** * Node data constructor * * @param node * The node containing this extra data. */ public OverlappingExtraData(OverlappingNode<?> node) { super(node); int size = node.getMaxChildren(); /* * * We instantiate the two following arrays at full size right * away, since we want to reserve that space in the node's header. * "this.nbChildren" will tell us how many relevant entries there * are in those tables. */ fChildStart = new long[size]; fChildEnd = new long[size]; for (int i = 0; i < size; i++) { fChildStart[i] = 0; fChildEnd[i] = Long.MAX_VALUE; } } @Override protected OverlappingNode<?> getNode() { /* Type enforced by constructor */ return (OverlappingNode<?>) super.getNode(); } @Override public void readSpecificHeader(@NonNull ByteBuffer buffer) { super.readSpecificHeader(buffer); int size = getNode().getMaxChildren(); for (int i = 0; i < size; i++) { fChildStart[i] = buffer.getLong(); fChildEnd[i] = buffer.getLong(); } } @Override protected void writeSpecificHeader(@NonNull ByteBuffer buffer) { getNode().takeReadLock(); try { super.writeSpecificHeader(buffer); int size = getNode().getMaxChildren(); /* * Write the children array */ for (int i = 0; i < size; i++) { buffer.putLong(fChildStart[i]); buffer.putLong(fChildEnd[i]); } } finally { getNode().releaseReadLock(); } } @Override protected int getSpecificHeaderSize() { int maxChildren = getNode().getMaxChildren(); int specificSize = super.getSpecificHeaderSize(); /* * MAX_NB * child start and child end arrays */ specificSize += 2 * Long.BYTES * maxChildren; return specificSize; } @Override public void linkNewChild(IHTNode<?> childNode) { if (!(childNode instanceof OverlappingNode<?>)) { throw new IllegalArgumentException("Adding a node that is not an overlapping node to an overlapping tree!"); //$NON-NLS-1$ } getNode().takeWriteLock(); try { super.linkNewChild(childNode); final int childIndex = getNbChildren() - 1; // We do not know the end time at this point, add a listener to // update child end when the node is closed ((OverlappingNode<?>) childNode).addListener((node, endtime) -> { // FIXME On who are we calling getNode() and fChildEnd here? getNode().takeWriteLock(); try { fChildEnd[childIndex] = endtime; } finally { getNode().releaseWriteLock(); } }); fChildStart[childIndex] = childNode.getNodeStart(); // The child may already be closed if (childNode.isOnDisk()) { fChildEnd[childIndex] = childNode.getNodeEnd(); } } finally { getNode().releaseWriteLock(); } } @Override protected Collection<Integer> selectNextIndices(RangeCondition<Long> rc) { OverlappingNode<?> node = getNode(); if (rc.min() < node.getNodeStart() || (node.isOnDisk() && rc.max() > node.getNodeEnd())) { throw new RangeException("Requesting children outside the node's range: " + rc.toString()); //$NON-NLS-1$ } node.takeReadLock(); try { List<Integer> childList = new ArrayList<>(); for (int i = 0; i < getNbChildren(); i++) { if (rc.intersects(fChildStart[i], fChildEnd[i])) { childList.add(i); } } return childList; } finally { node.releaseReadLock(); } } /** * Get the start value of a child * * @param index * The child index * @return The start value */ public long getChildStart(int index) { getNode().takeReadLock(); try { if (index >= getNbChildren()) { throw new IndexOutOfBoundsException("The child at index " + index + " does not exist"); //$NON-NLS-1$ //$NON-NLS-2$ } return fChildStart[index]; } finally { getNode().releaseReadLock(); } } /** * Get the start value of a child * * @param index * The child index * @return The start value */ public long getChildEnd(int index) { getNode().takeReadLock(); try { if (index >= getNbChildren()) { throw new IndexOutOfBoundsException("The child at index " + index + " does not exist"); //$NON-NLS-1$ //$NON-NLS-2$ } return fChildEnd[index]; } finally { getNode().releaseReadLock(); } } @Override public int hashCode() { return Objects.hash(super.hashCode(), fChildStart, fChildEnd); } @Override public boolean equals(@Nullable Object obj) { if (!super.equals(obj)) { return false; } /* super.equals already checks for null / same class */ OverlappingExtraData other = (OverlappingExtraData) checkNotNull(obj); return (Arrays.equals(fChildStart, other.fChildStart) && Arrays.equals(fChildEnd, other.fChildEnd)); } } /** * Constructor * * @param type * The type of this node * @param blockSize * The size (in bytes) of a serialized node on disk * @param maxChildren * The maximum allowed number of children per node * @param seqNumber * The (unique) sequence number assigned to this particular node * @param parentSeqNumber * The sequence number of this node's parent node * @param start * The earliest timestamp stored in this node */ public OverlappingNode(NodeType type, int blockSize, int maxChildren, int seqNumber, int parentSeqNumber, long start) { super(type, blockSize, maxChildren, seqNumber, parentSeqNumber, start); } @Override protected @Nullable OverlappingExtraData createNodeExtraData(final NodeType type) { if (type == NodeType.CORE) { return new OverlappingExtraData(this); } return null; } @Override public void add(E newInterval) { super.add(newInterval); } @Override public void closeThisNode(long endtime) { super.closeThisNode(endtime); fListeners.forEach(l -> l.nodeClosed(this, endtime)); } /** * The listener for this node * * @param listener * The update listener */ protected void addListener(NodeClosedListener listener) { fListeners.add(listener); } @Override protected @Nullable OverlappingExtraData getCoreNodeData() { return (OverlappingExtraData) super.getCoreNodeData(); } /** * Get the start value of a child of this node * * @param index * The index of the node to get the child start * @return The child start value */ @VisibleForTesting long getChildStart(int index) { OverlappingExtraData extraData = getCoreNodeData(); if (extraData != null) { return extraData.getChildStart(index); } throw new UnsupportedOperationException("A leaf node does not have children"); //$NON-NLS-1$ } /** * Get the end value of a child of this node * * @param index * The index of the node to get the child end * @return The child end value */ @VisibleForTesting long getChildEnd(int index) { OverlappingExtraData extraData = getCoreNodeData(); if (extraData != null) { return extraData.getChildEnd(index); } throw new UnsupportedOperationException("A leaf node does not have children"); //$NON-NLS-1$ } }