1515import java .util .ArrayList ;
1616import java .util .Collection ;
1717import java .util .Collections ;
18- import java .util .HashMap ;
1918import java .util .List ;
2019import java .util .Map ;
2120import java .util .Map .Entry ;
2221import java .util .concurrent .CopyOnWriteArrayList ;
2322import java .util .concurrent .atomic .AtomicInteger ;
23+ import java .util .stream .Collectors ;
2424
2525import org .eclipse .core .runtime .IProgressMonitor ;
2626import org .eclipse .jdt .annotation .NonNull ;
3636import org .eclipse .tracecompass .tmf .core .trace .ITmfTrace ;
3737
3838import com .google .common .collect .HashBasedTable ;
39- import com .google .common .collect .Lists ;
4039import com .google .common .collect .Table ;
4140
4241/**
4342 * Represents a base implementation of {@link ITmfTreeDataProvider} that
4443 * supports experiments. Clients of this data provider must provide a list of
4544 * {@link ITmfTreeDataProvider} for each trace in the experiment which supports
4645 * the provider. From the list of sub data provider, this data provider will
47- * merge all responses into one.
46+ * merge all responses into one and default merges {@link TmfTreeModel} .
4847 *
4948 * @param <M>
5049 * The type of {@link ITmfTreeDataModel} that this composite's tree
@@ -101,63 +100,105 @@ public TmfTreeCompositeDataProvider(List<P> providers, String id) {
101100 fId = id ;
102101 }
103102
103+ /**
104+ * Helper class to merge entries from multiple ITmfTreeModel instances into
105+ * a unified ordered list, preserving parent-child relationships and
106+ * avoiding duplicate nodes across scopes.
107+ *
108+ * @param <TEntry>
109+ * The type of tree entry model
110+ * @since 10.2
111+ */
112+ public class TreeMergeHelper <TEntry extends ITmfTreeDataModel > {
113+
114+ private final List <Entry <TEntry , Object >> entries = new ArrayList <>();
115+ private final Table <Object , Long , @ NonNull TEntry > scopedEntries = HashBasedTable .create ();
116+
117+ /**
118+ * Merges the entries from a given tree model into the current list of
119+ * merged entries.
120+ *
121+ * @param model
122+ * The tree model containing entries to merge.
123+ * @param scope
124+ * The logical scope or owner of the model (e.g., a data
125+ * provider).
126+ */
127+ public void mergeTree (ITmfTreeModel <TEntry > model , Object scope ) {
128+ Map <Long , AtomicInteger > indexMap = new java .util .HashMap <>();
129+ Map <Long , @ NonNull TEntry > row = scopedEntries .row (scope );
130+
131+ for (TEntry entry : model .getEntries ()) {
132+ TEntry previous = row .putIfAbsent (entry .getId (), entry );
133+ // Ignore duplicate entries from different data providers
134+ if (previous == null ) {
135+ if (entry .getParentId () == -1 ) {
136+ entries .add (new SimpleEntry <>(entry , scope ));
137+ } else {
138+ /*
139+ * Insert new entries from subsequent data providers
140+ * at the correct position in the entries list. New
141+ * entries are inserted before sibling entries from
142+ * previous data providers.
143+ */
144+ int index = indexMap .computeIfAbsent (entry .getParentId (), l -> new AtomicInteger ()).getAndIncrement ();
145+ int pos = 0 ;
146+ while (pos < entries .size ()) {
147+ Entry <TEntry , Object > added = entries .get (pos );
148+ if (added .getValue ().equals (scope ) && added .getKey ().getParentId () == entry .getParentId ()) {
149+ if (index == 0 ) {
150+ break ;
151+ }
152+ index --;
153+ }
154+ pos ++;
155+ }
156+ if (pos < entries .size ()) {
157+ entries .add (pos , new SimpleEntry <>(entry , scope ));
158+ } else {
159+ entries .add (new SimpleEntry <>(entry , scope ));
160+ }
161+ }
162+ } else {
163+ indexMap .computeIfAbsent (entry .getParentId (), l -> new AtomicInteger ()).getAndIncrement ();
164+ }
165+ }
166+ }
167+
168+ /**
169+ * Retrieves the final list of merged entries.
170+ *
171+ * @return The list of merged model entries, preserving insertion order.
172+ */
173+ public List <TEntry > getMergedEntries () {
174+ return entries .stream ().map (Entry ::getKey ).collect (Collectors .toList ());
175+ }
176+ }
177+
104178 @ Override
105179 public TmfModelResponse <TmfTreeModel <M >> fetchTree (Map <String , Object > fetchParameters , @ Nullable IProgressMonitor monitor ) {
106180 boolean isComplete = true ;
107- List <Entry <M , Object >> entries = new ArrayList <>();
108181 List <ITableColumnDescriptor > columnDescriptor = null ;
109182 int autoExpandLevel = TmfTreeModel .ALL_LEVELS ;
110183
111- Table <Object , Long , @ NonNull M > scopedEntries = HashBasedTable .create ();
184+ TreeMergeHelper <M > mergeHelper = new TreeMergeHelper <>();
185+
112186 for (P dataProvider : fProviders ) {
113- Map <Long , AtomicInteger > indexMap = new HashMap <>();
114- TmfModelResponse <TmfTreeModel <M >> response = dataProvider .fetchTree (fetchParameters , monitor );
187+ TmfModelResponse <? extends ITmfTreeModel <M >> response = dataProvider .fetchTree (fetchParameters , monitor );
115188 isComplete &= response .getStatus () == ITmfResponse .Status .COMPLETED ;
116- TmfTreeModel <M > model = response .getModel ();
189+ ITmfTreeModel <M > model = response .getModel ();
190+
117191 if (model != null ) {
118192 Object scope = (model .getScope () == null ) ? dataProvider : model .getScope ();
119- Map <Long , @ NonNull M > row = scopedEntries .row (scope );
120- for (M entry : model .getEntries ()) {
121- M previous = row .putIfAbsent (entry .getId (), entry );
122- // Ignore duplicate entries from different data providers
123- if (previous == null ) {
124- if (entry .getParentId () == -1 ) {
125- entries .add (new SimpleEntry (entry , scope ));
126- } else {
127- /*
128- * Insert new entries from subsequent data providers
129- * at the correct position in the entries list. New
130- * entries are inserted before sibling entries from
131- * previous data providers.
132- */
133- int index = indexMap .computeIfAbsent (entry .getParentId (), l -> new AtomicInteger ()).getAndIncrement ();
134- int pos = 0 ;
135- while (pos < entries .size ()) {
136- Entry <M , Object > added = entries .get (pos );
137- if (added .getValue ().equals (scope ) && added .getKey ().getParentId () == entry .getParentId ()) {
138- if (index == 0 ) {
139- break ;
140- }
141- index --;
142- }
143- pos ++;
144- }
145- if (pos < entries .size ()) {
146- entries .add (pos , new SimpleEntry (entry , scope ));
147- } else {
148- entries .add (new SimpleEntry (entry , scope ));
149- }
150- }
151- } else {
152- indexMap .computeIfAbsent (entry .getParentId (), l -> new AtomicInteger ()).getAndIncrement ();
193+ mergeHelper .mergeTree (model , scope );
194+
195+ if (model instanceof TmfTreeModel ) {
196+ autoExpandLevel = ((TmfTreeModel <M >) model ).getAutoExpandLevel ();
197+ // Use the column descriptor of the first model. All descriptors are supposed to be the same
198+ if (columnDescriptor == null ) {
199+ columnDescriptor = ((TmfTreeModel <M >) model ).getColumnDescriptors ();
153200 }
154201 }
155- // Use the column descriptor of the first model. All descriptors are supposed to be the same
156- if (columnDescriptor == null ) {
157- columnDescriptor = model .getColumnDescriptors ();
158- }
159- // All autoExpandLevel are supposed to be the same
160- autoExpandLevel = model .getAutoExpandLevel ();
161202 }
162203 if (monitor != null && monitor .isCanceled ()) {
163204 return new TmfModelResponse <>(null , ITmfResponse .Status .CANCELLED , CommonStatusMessage .TASK_CANCELLED );
@@ -169,7 +210,7 @@ public TmfModelResponse<TmfTreeModel<M>> fetchTree(Map<String, Object> fetchPara
169210 columnDescriptor = Collections .emptyList ();
170211 }
171212 treeModelBuilder .setColumnDescriptors (columnDescriptor )
172- .setEntries (Lists . transform ( entries , e -> e . getKey () ))
213+ .setEntries (mergeHelper . getMergedEntries ( ))
173214 .setAutoExpandLevel (autoExpandLevel );
174215
175216 if (isComplete ) {
0 commit comments