tmf: Bug 567888: TmfTreeCompositeDataProvider doesn't consider scope
Allows the composite data provider to receive the same entryId from
multiple data providers. If they are within the same scope, a single
entry will be returned to the caller fetching the tree and duplicates
will be discarded.
The order of entries from a single data provider is preserved in most
cases but the order of independent entries from different data providers
is undefined.
[Fixed] Bug 567888: TmfTreeCompositeDataProvider doesn't consider scope
which can lead to ID clashes
Change-Id: I5aa416434623cadfaa295a813de8ef9da67c2e5d
Signed-off-by: Patrick Tasse <patrick.tasse@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/tracecompass/org.eclipse.tracecompass/+/178911
Tested-by: Trace Compass Bot <tracecompass-bot@eclipse.org>
Tested-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Reviewed-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
diff --git a/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/model/tree/TmfTreeDataModelTest.java b/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/model/tree/TmfTreeDataModelTest.java
index 1d31e36..f81e379 100644
--- a/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/model/tree/TmfTreeDataModelTest.java
+++ b/tmf/org.eclipse.tracecompass.tmf.core.tests/src/org/eclipse/tracecompass/tmf/core/tests/model/tree/TmfTreeDataModelTest.java
@@ -1,5 +1,5 @@
/**********************************************************************
- * Copyright (c) 2020 Ericsson
+ * Copyright (c) 2020, 2021 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
@@ -59,17 +59,20 @@
private static final long ID0 = 0L;
private static final long PARENT_ID0 = -1L;
private static final OutputElementStyle STYLE0 = null;
+
private static final @NonNull List<@NonNull String> LABELS1 = Arrays.asList("label4, label5, label6", "label7");
private static final long ID1 = 1L;
private static final long PARENT_ID1 = 0L;
private static final boolean HAS_MODEL1 = false;
private static final @NonNull OutputElementStyle STYLE1 = new OutputElementStyle("1");
- private static final long ID2 = 0L;
+ private static final long ID2 = 2L;
private static final long PARENT_ID2 = -1L;
private static final OutputElementStyle STYLE3 = null;
private static final String NAME = "Name";
+ private static final String SCOPE = "scope";
+
private TmfTreeDataModel fModel0 = null;
private TmfTreeDataModel fModel1 = null;
@@ -203,7 +206,7 @@
assertEquals(TO_STRING, "<name=[label1, label2, label3] id=0 parentId=-1 style=null hasRowModel=true>", fModel0.toString());
assertEquals(TO_STRING, "<name=[label4, label5, label6, label7] id=1 parentId=0 style=Style [1, {}] hasRowModel=false>", fModel1.toString());
TmfTreeDataModel model2 = createModel(2);
- assertEquals(TO_STRING, "<name=[Name] id=0 parentId=-1 style=null hasRowModel=true>", model2.toString());
+ assertEquals(TO_STRING, "<name=[Name] id=2 parentId=-1 style=null hasRowModel=true>", model2.toString());
}
/**
@@ -212,7 +215,7 @@
@Test
public void testCompositeTree() {
List<DummyDataProvider> ddps = new ArrayList<>();
- for (int i = 0; i < 2; i++) {
+ for (int i = 0; i < 3; i++) {
ddps.add(new DummyDataProvider(i));
}
TmfTreeCompositeDataProvider<@NonNull TmfTreeDataModel, @NonNull DummyDataProvider> composite = new TmfTreeCompositeDataProvider<>(ddps, "composite-dummy");
@@ -222,26 +225,26 @@
TmfTreeModel<@NonNull TmfTreeDataModel> model = tree.getModel();
assertNotNull(model);
assertEquals(Arrays.asList("header"), model.getHeaders());
- assertEquals(2, model.getEntries().size());
+ assertEquals(3, model.getEntries().size());
// AnnotationCategories
TmfModelResponse<@NonNull AnnotationCategoriesModel> returnVal = composite.fetchAnnotationCategories(Collections.emptyMap(), monitor);
AnnotationCategoriesModel categoryModel = returnVal.getModel();
assertNotNull(categoryModel);
- assertEquals(Arrays.asList("0","1","common"), categoryModel.getAnnotationCategories());
+ assertEquals(Arrays.asList("0", "1", "2", "common"), categoryModel.getAnnotationCategories());
// Annotations
TmfModelResponse<@NonNull AnnotationModel> annotations = composite.fetchAnnotations(Collections.emptyMap(), monitor);
AnnotationModel annotationsModel = annotations.getModel();
assertNotNull(annotationsModel);
Collection<@NonNull Annotation> collection = annotationsModel.getAnnotations().get("test");
assertNotNull(collection);
- assertEquals(4, collection.size());
+ assertEquals(6, collection.size());
}
// ------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------
- private static TmfTreeDataModel createModel(int i) {
+ private static @NonNull TmfTreeDataModel createModel(int i) {
switch (i) {
case 0:
return new TmfTreeDataModel(ID0, PARENT_ID0, LABELS0);
@@ -264,8 +267,16 @@
@Override
public TmfModelResponse<TmfTreeModel<TmfTreeDataModel>> fetchTree(@NonNull Map<@NonNull String, @NonNull Object> fetchParameters, @Nullable IProgressMonitor monitor) {
- TmfTreeDataModel createModel = createModel(fModelNo);
- TmfModelResponse<TmfTreeModel<TmfTreeDataModel>> response = new TmfModelResponse<>(new TmfTreeModel<>(Arrays.asList("header"), Arrays.asList(createModel)), ITmfResponse.Status.COMPLETED, "");
+ List<TmfTreeDataModel> modelList = new ArrayList<>();
+ TmfTreeDataModel model = createModel(fModelNo);
+ modelList.add(model);
+ long parentId = model.getParentId();
+ while (parentId != -1) {
+ TmfTreeDataModel parent = createModel((int) parentId);
+ modelList.add(0, parent);
+ parentId = parent.getParentId();
+ }
+ TmfModelResponse<TmfTreeModel<TmfTreeDataModel>> response = new TmfModelResponse<>(new TmfTreeModel<>(Arrays.asList("header"), modelList, SCOPE), ITmfResponse.Status.COMPLETED, "");
return response;
}
diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/tree/TmfTreeCompositeDataProvider.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/tree/TmfTreeCompositeDataProvider.java
index 4886628..12f519a 100644
--- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/tree/TmfTreeCompositeDataProvider.java
+++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/model/tree/TmfTreeCompositeDataProvider.java
@@ -1,5 +1,5 @@
/**********************************************************************
- * Copyright (c) 2017 Ericsson
+ * Copyright (c) 2017, 2021 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
@@ -11,12 +11,16 @@
package org.eclipse.tracecompass.internal.tmf.core.model.tree;
+import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
@@ -34,7 +38,9 @@
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Table;
/**
* Represents a base implementation of {@link ITmfTreeDataProvider} that
@@ -101,15 +107,53 @@
@Override
public TmfModelResponse<TmfTreeModel<M>> fetchTree(Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) {
boolean isComplete = true;
- ImmutableList.Builder<M> series = ImmutableList.builder();
+ List<Entry<M, Object>> entries = new ArrayList<>();
List<ITableColumnDescriptor> columnDescriptor = null;
+ Table<Object, Long, @NonNull M> scopedEntries = HashBasedTable.create();
for (P dataProvider : fProviders) {
+ Map<Long, AtomicInteger> indexMap = new HashMap<>();
TmfModelResponse<TmfTreeModel<M>> response = dataProvider.fetchTree(fetchParameters, monitor);
isComplete &= response.getStatus() == ITmfResponse.Status.COMPLETED;
TmfTreeModel<M> model = response.getModel();
if (model != null) {
- series.addAll(model.getEntries());
+ Object scope = (model.getScope() == null) ? dataProvider : model.getScope();
+ Map<Long, @NonNull M> row = scopedEntries.row(scope);
+ for (M entry : model.getEntries()) {
+ M previous = row.putIfAbsent(entry.getId(), entry);
+ // Ignore duplicate entries from different data providers
+ if (previous == null) {
+ if (entry.getParentId() == -1) {
+ entries.add(new SimpleEntry(entry, scope));
+ } else {
+ /*
+ * Insert new entries from subsequent data providers
+ * at the correct position in the entries list. New
+ * entries are inserted before sibling entries from
+ * previous data providers.
+ */
+ int index = indexMap.computeIfAbsent(entry.getParentId(), l -> new AtomicInteger()).getAndIncrement();
+ int pos = 0;
+ while (pos < entries.size()) {
+ Entry<M, Object> added = entries.get(pos);
+ if (added.getValue().equals(scope) && added.getKey().getParentId() == entry.getParentId()) {
+ if (index == 0) {
+ break;
+ }
+ index--;
+ }
+ pos++;
+ }
+ if (pos < entries.size()) {
+ entries.add(pos, new SimpleEntry(entry, scope));
+ } else {
+ entries.add(new SimpleEntry(entry, scope));
+ }
+ }
+ } else {
+ indexMap.computeIfAbsent(entry.getParentId(), l -> new AtomicInteger()).getAndIncrement();
+ }
+ }
// Use the column descriptor of the first model. All descriptors are supposed to be the same
if (columnDescriptor == null) {
columnDescriptor = model.getColumnDescriptors();
@@ -125,7 +169,7 @@
columnDescriptor = Collections.emptyList();
}
treeModelBuilder.setColumnDescriptors(columnDescriptor)
- .setEntries(series.build());
+ .setEntries(Lists.transform(entries, e -> e.getKey()));
if (isComplete) {
return new TmfModelResponse<>(treeModelBuilder.build(), ITmfResponse.Status.COMPLETED, CommonStatusMessage.COMPLETED);