tmf: Fix TmfSignalManager leaking listeners
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / project / model / TmfCommonProjectElement.java
1 /*******************************************************************************
2 * Copyright (c) 2010, 2015 Ericsson, École Polytechnique de Montréal
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Bernd Hufmann - Added supplementary files handling (in class TmfTraceElement)
11 * Geneviève Bastien - Copied supplementary files handling from TmfTracElement
12 * Moved to this class code to copy a model element
13 * Renamed from TmfWithFolderElement to TmfCommonProjectElement
14 * Patrick Tasse - Add support for folder elements
15 *******************************************************************************/
16
17 package org.eclipse.tracecompass.tmf.ui.project.model;
18
19 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.InputStream;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.stream.Collectors;
26
27 import org.eclipse.core.resources.IContainer;
28 import org.eclipse.core.resources.IFile;
29 import org.eclipse.core.resources.IFolder;
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.resources.IWorkspaceRoot;
32 import org.eclipse.core.resources.ResourcesPlugin;
33 import org.eclipse.core.runtime.CoreException;
34 import org.eclipse.core.runtime.IConfigurationElement;
35 import org.eclipse.core.runtime.IPath;
36 import org.eclipse.core.runtime.NullProgressMonitor;
37 import org.eclipse.core.runtime.Path;
38 import org.eclipse.core.runtime.Platform;
39 import org.eclipse.jdt.annotation.NonNull;
40 import org.eclipse.osgi.util.NLS;
41 import org.eclipse.swt.graphics.Image;
42 import org.eclipse.tracecompass.internal.tmf.ui.Activator;
43 import org.eclipse.tracecompass.internal.tmf.ui.editors.ITmfEventsEditorConstants;
44 import org.eclipse.tracecompass.tmf.core.TmfCommonConstants;
45 import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceType;
46 import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceType.TraceElementType;
47 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
48 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
49 import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
50 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
51 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
52 import org.eclipse.ui.IEditorReference;
53 import org.eclipse.ui.IWorkbench;
54 import org.eclipse.ui.IWorkbenchPage;
55 import org.eclipse.ui.IWorkbenchWindow;
56 import org.eclipse.ui.PartInitException;
57 import org.eclipse.ui.PlatformUI;
58 import org.eclipse.ui.part.FileEditorInput;
59 import org.osgi.framework.Bundle;
60
61 /**
62 * Base class for tracing project elements: it implements the common behavior of
63 * all project elements: supplementary files, analysis, types, etc.
64 *
65 * @author Geneviève Bastien
66 */
67 public abstract class TmfCommonProjectElement extends TmfProjectModelElement {
68
69 // ------------------------------------------------------------------------
70 // Attributes
71 // ------------------------------------------------------------------------
72
73 private static final String BOOKMARKS_HIDDEN_FILE = ".bookmarks"; //$NON-NLS-1$
74
75 /* Direct child elements */
76 private TmfViewsElement fViewsElement = null;
77 private TmfOnDemandAnalysesElement fOnDemandAnalysesElement = null;
78 private TmfReportsElement fReportsElement = null;
79
80 /** This trace type ID as defined in plugin.xml */
81 private String fTraceTypeId = null;
82
83 // ------------------------------------------------------------------------
84 // Constructors
85 // ------------------------------------------------------------------------
86
87 /**
88 * Constructor. Creates model element.
89 *
90 * @param name
91 * The name of the element
92 * @param resource
93 * The resource.
94 * @param parent
95 * The parent element
96 */
97 public TmfCommonProjectElement(String name, IResource resource, TmfProjectModelElement parent) {
98 super(name, resource, parent);
99 refreshTraceType();
100 TmfSignalManager.register(this);
101 }
102
103 // ------------------------------------------------------------------------
104 // ITmfProjectModelElement
105 // ------------------------------------------------------------------------
106
107 @Override
108 public void dispose() {
109 super.dispose();
110 TmfSignalManager.deregister(this);
111 }
112
113 /**
114 * @since 2.0
115 */
116 @Override
117 protected void refreshChildren() {
118 /* Get the base path to put the resource to */
119 IPath tracePath = getResource().getFullPath();
120 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
121
122 if (fViewsElement == null) {
123 /* Add the "Views" node */
124 IFolder viewsNodeRes = root.getFolder(tracePath.append(TmfViewsElement.PATH_ELEMENT));
125 fViewsElement = new TmfViewsElement(viewsNodeRes, this);
126 addChild(fViewsElement);
127 }
128 fViewsElement.refreshChildren();
129
130 if (fOnDemandAnalysesElement == null) {
131 /* Add the "On-demand Analyses" node */
132 IFolder analysesNodeRes = root.getFolder(tracePath.append(TmfOnDemandAnalysesElement.PATH_ELEMENT));
133 fOnDemandAnalysesElement = new TmfOnDemandAnalysesElement(analysesNodeRes, this);
134 addChild(fOnDemandAnalysesElement);
135 }
136 fOnDemandAnalysesElement.refreshChildren();
137
138 if (fReportsElement == null) {
139 /* Add the "Reports" node */
140 IFolder reportsNodeRes = root.getFolder(tracePath.append(TmfReportsElement.PATH_ELEMENT));
141 fReportsElement = new TmfReportsElement(reportsNodeRes, this);
142 addChild(fReportsElement);
143 }
144 fReportsElement.refreshChildren();
145 }
146
147 /**
148 * @since 2.0
149 */
150 @Override
151 public Image getIcon() {
152 String traceType = getTraceType();
153 if (traceType == null || TmfTraceType.getTraceType(traceType) == null) {
154 // request the label to the Eclipse platform
155 Image icon = TmfProjectModelIcons.WORKSPACE_LABEL_PROVIDER.getImage(getResource());
156 return (icon == null ? TmfProjectModelIcons.DEFAULT_TRACE_ICON : icon);
157 }
158
159 IConfigurationElement traceUIAttributes = TmfTraceTypeUIUtils.getTraceUIAttributes(traceType,
160 (this instanceof TmfTraceElement) ? TraceElementType.TRACE : TraceElementType.EXPERIMENT);
161 if (traceUIAttributes != null) {
162 String iconAttr = traceUIAttributes.getAttribute(TmfTraceTypeUIUtils.ICON_ATTR);
163 if (iconAttr != null) {
164 String name = traceUIAttributes.getContributor().getName();
165 if (name != null) {
166 Bundle bundle = Platform.getBundle(name);
167 if (bundle != null) {
168 Image image = TmfProjectModelIcons.loadIcon(bundle, iconAttr);
169 if (image != null) {
170 return image;
171 }
172 }
173 }
174 }
175 }
176 /* Let subclasses specify an icon */
177 return null;
178 }
179
180 // ------------------------------------------------------------------------
181 // Operations
182 // ------------------------------------------------------------------------
183
184 /**
185 * Get the child element "Views". There should always be one.
186 *
187 * @return The child element
188 * @since 2.0
189 */
190 protected TmfViewsElement getChildElementViews() {
191 return fViewsElement;
192 }
193
194 /**
195 * Get the child element "Reports".
196 *
197 * @return The Reports child element
198 * @since 2.0
199 */
200 public TmfReportsElement getChildElementReports() {
201 return fReportsElement;
202 }
203
204 /**
205 * Returns the trace type ID.
206 *
207 * @return trace type ID.
208 */
209 public String getTraceType() {
210 return fTraceTypeId;
211 }
212
213 /**
214 * Refreshes the trace type field by reading the trace type persistent
215 * property of the resource.
216 */
217 public void refreshTraceType() {
218 try {
219 fTraceTypeId = TmfTraceType.getTraceTypeId(getResource());
220 } catch (CoreException e) {
221 Activator.getDefault().logError(NLS.bind(Messages.TmfCommonProjectElement_ErrorRefreshingProperty, getName()), e);
222 }
223 }
224
225 /**
226 * Instantiate a <code>ITmfTrace</code> object based on the trace type and
227 * the corresponding extension.
228 *
229 * @return the <code>ITmfTrace</code> or <code>null</code> for an error
230 */
231 public abstract ITmfTrace instantiateTrace();
232
233 /**
234 * Return the supplementary folder path for this element. The returned path
235 * is relative to the project's supplementary folder.
236 *
237 * @return The supplementary folder path for this element
238 */
239 protected String getSupplementaryFolderPath() {
240 return getElementPath() + getSuffix();
241 }
242
243 /**
244 * Return the element path relative to its common element (traces folder,
245 * experiments folder or experiment element).
246 *
247 * @return The element path
248 */
249 public @NonNull String getElementPath() {
250 ITmfProjectModelElement parent = getParent();
251 while (!(parent instanceof TmfTracesFolder || parent instanceof TmfExperimentElement || parent instanceof TmfExperimentFolder)) {
252 parent = parent.getParent();
253 }
254 IPath path = getResource().getFullPath().makeRelativeTo(parent.getPath());
255 return checkNotNull(path.toString());
256 }
257
258 /**
259 * @return The suffix for the supplementary folder
260 */
261 protected String getSuffix() {
262 return ""; //$NON-NLS-1$
263 }
264
265 /**
266 * Returns a list of TmfTraceElements contained in project element.
267 *
268 * @return a list of TmfTraceElements, empty list if none
269 */
270 public List<TmfTraceElement> getTraces() {
271 return new ArrayList<>();
272 }
273
274 /**
275 * Get the instantiated trace associated with this element.
276 *
277 * @return The instantiated trace or null if trace is not (yet) available
278 */
279 public ITmfTrace getTrace() {
280 for (ITmfTrace trace : TmfTraceManager.getInstance().getOpenedTraces()) {
281 if (trace.getResource().equals(getResource())) {
282 return trace;
283 }
284 }
285 return null;
286 }
287
288 /**
289 * Returns the file resource used to store bookmarks after creating it if
290 * necessary. If the trace resource is a file, it is returned directly. If
291 * the trace resource is a folder, a linked file is returned. The file will
292 * be created if it does not exist.
293 *
294 * @return the bookmarks file
295 * @throws CoreException
296 * if the bookmarks file cannot be created
297 */
298 public abstract IFile createBookmarksFile() throws CoreException;
299
300 /**
301 * Actually returns the bookmark file or creates it in the project element's
302 * folder
303 *
304 * @param bookmarksFolder
305 * Folder where to put the bookmark file
306 * @param editorInputType
307 * The editor input type to set (trace or experiment)
308 * @return The bookmark file
309 * @throws CoreException
310 * if the bookmarks file cannot be created
311 */
312 protected IFile createBookmarksFile(IFolder bookmarksFolder, String editorInputType) throws CoreException {
313 IFile file = getBookmarksFile();
314 if (!file.exists()) {
315 final IFile bookmarksFile = bookmarksFolder.getFile(BOOKMARKS_HIDDEN_FILE);
316 if (!bookmarksFile.exists()) {
317 final InputStream source = new ByteArrayInputStream(new byte[0]);
318 bookmarksFile.create(source, IResource.FORCE | IResource.HIDDEN, null);
319 }
320 file.createLink(bookmarksFile.getLocation(), IResource.REPLACE | IResource.HIDDEN, null);
321 file.setPersistentProperty(TmfCommonConstants.TRACETYPE, editorInputType);
322 }
323 return file;
324 }
325
326 /**
327 * Returns the optional editor ID from the trace type extension.
328 *
329 * @return the editor ID or <code>null</code> if not defined.
330 */
331 public abstract String getEditorId();
332
333 /**
334 * Returns the file resource used to store bookmarks. The file may not
335 * exist.
336 *
337 * @return the bookmarks file
338 */
339 public IFile getBookmarksFile() {
340 final IFolder folder = (IFolder) getResource();
341 IFile file = folder.getFile(getName() + '_');
342 return file;
343 }
344
345 /**
346 * Close open editors associated with this experiment.
347 */
348 public void closeEditors() {
349 IFile file = getBookmarksFile();
350 FileEditorInput input = new FileEditorInput(file);
351 IWorkbench wb = PlatformUI.getWorkbench();
352 for (IWorkbenchWindow wbWindow : wb.getWorkbenchWindows()) {
353 for (IWorkbenchPage wbPage : wbWindow.getPages()) {
354 for (IEditorReference editorReference : wbPage.getEditorReferences()) {
355 try {
356 if (editorReference.getEditorInput().equals(input)) {
357 wbPage.closeEditor(editorReference.getEditor(false), false);
358 }
359 } catch (PartInitException e) {
360 Activator.getDefault().logError(NLS.bind(Messages.TmfCommonProjectElement_ErrorClosingEditor, getName()), e);
361 }
362 }
363 }
364 }
365 }
366
367 /**
368 * Get a friendly name for the type of element this common project element
369 * is, to be displayed in UI messages.
370 *
371 * @return A string for the type of project element this object is, for
372 * example "trace" or "experiment"
373 */
374 public abstract String getTypeName();
375
376 /**
377 * Copy this model element
378 *
379 * @param newName
380 * The name of the new element
381 * @param copySuppFiles
382 * Whether to copy supplementary files or not
383 * @return the new Resource object
384 */
385 public IResource copy(final String newName, final boolean copySuppFiles) {
386
387 final IPath newPath = getParent().getResource().getFullPath().addTrailingSeparator().append(newName);
388
389 /* Copy supplementary files first, only if needed */
390 if (copySuppFiles) {
391 String newElementPath = new Path(getElementPath()).removeLastSegments(1).append(newName).toString();
392 copySupplementaryFolder(newElementPath);
393 }
394 /* Copy the trace */
395 try {
396 getResource().copy(newPath, IResource.FORCE | IResource.SHALLOW, null);
397 IResource trace = ((IFolder) getParent().getResource()).findMember(newName);
398
399 /* Delete any bookmarks file found in copied trace folder */
400 if (trace instanceof IFolder) {
401 IFolder folderTrace = (IFolder) trace;
402 for (IResource member : folderTrace.members()) {
403 String traceTypeId = TmfTraceType.getTraceTypeId(member);
404 if (ITmfEventsEditorConstants.TRACE_INPUT_TYPE_CONSTANTS.contains(traceTypeId)) {
405 member.delete(true, null);
406 } else if (ITmfEventsEditorConstants.EXPERIMENT_INPUT_TYPE_CONSTANTS.contains(traceTypeId)) {
407 member.delete(true, null);
408 }
409 }
410 }
411 return trace;
412 } catch (CoreException e) {
413
414 }
415 return null;
416 }
417
418 /**
419 * Get the list of analysis elements
420 *
421 * @return Array of analysis elements
422 */
423 public List<TmfAnalysisElement> getAvailableAnalysis() {
424 return getChildElementViews().getChildren().stream()
425 .map(elem -> (TmfAnalysisElement) elem)
426 .collect(Collectors.toList());
427 }
428
429 // ------------------------------------------------------------------------
430 // Supplementary files operations
431 // ------------------------------------------------------------------------
432
433 /**
434 * Deletes this element specific supplementary folder.
435 */
436 public void deleteSupplementaryFolder() {
437 IFolder supplFolder = getTraceSupplementaryFolder(getSupplementaryFolderPath());
438 try {
439 deleteFolder(supplFolder);
440 } catch (CoreException e) {
441 Activator.getDefault().logError("Error deleting supplementary folder " + supplFolder, e); //$NON-NLS-1$
442 }
443 }
444
445 private static void deleteFolder(IFolder folder) throws CoreException {
446 if (folder.exists()) {
447 folder.delete(true, new NullProgressMonitor());
448 }
449 IContainer parent = folder.getParent();
450 // delete empty folders up to the parent project
451 if (parent instanceof IFolder && (!parent.exists() || parent.members().length == 0)) {
452 deleteFolder((IFolder) parent);
453 }
454 }
455
456 /**
457 * Renames the element specific supplementary folder according to the new
458 * element name or path.
459 *
460 * @param newElementPath
461 * The new element name or path
462 */
463 public void renameSupplementaryFolder(String newElementPath) {
464 IFolder oldSupplFolder = getTraceSupplementaryFolder(getSupplementaryFolderPath());
465
466 // Rename supplementary folder
467 try {
468 if (oldSupplFolder.exists()) {
469 IFolder newSupplFolder = prepareTraceSupplementaryFolder(newElementPath + getSuffix(), false);
470 oldSupplFolder.move(newSupplFolder.getFullPath(), true, new NullProgressMonitor());
471 }
472 deleteFolder(oldSupplFolder);
473 } catch (CoreException e) {
474 Activator.getDefault().logError("Error renaming supplementary folder " + oldSupplFolder, e); //$NON-NLS-1$
475 }
476 }
477
478 /**
479 * Copies the element specific supplementary folder to the new element name
480 * or path.
481 *
482 * @param newElementPath
483 * The new element name or path
484 */
485 public void copySupplementaryFolder(String newElementPath) {
486 IFolder oldSupplFolder = getTraceSupplementaryFolder(getSupplementaryFolderPath());
487
488 // copy supplementary folder
489 if (oldSupplFolder.exists()) {
490 try {
491 IFolder newSupplFolder = prepareTraceSupplementaryFolder(newElementPath + getSuffix(), false);
492 oldSupplFolder.copy(newSupplFolder.getFullPath(), true, new NullProgressMonitor());
493 } catch (CoreException e) {
494 Activator.getDefault().logError("Error renaming supplementary folder " + oldSupplFolder, e); //$NON-NLS-1$
495 }
496 }
497 }
498
499 /**
500 * Copies the element specific supplementary folder a new folder.
501 *
502 * @param destination
503 * The destination folder to copy to.
504 */
505 public void copySupplementaryFolder(IFolder destination) {
506 IFolder oldSupplFolder = getTraceSupplementaryFolder(getSupplementaryFolderPath());
507
508 // copy supplementary folder
509 if (oldSupplFolder.exists()) {
510 try {
511 TraceUtils.createFolder((IFolder) destination.getParent(), new NullProgressMonitor());
512 oldSupplFolder.copy(destination.getFullPath(), true, new NullProgressMonitor());
513 } catch (CoreException e) {
514 Activator.getDefault().logError("Error copying supplementary folder " + oldSupplFolder, e); //$NON-NLS-1$
515 }
516 }
517 }
518
519 /**
520 * Refreshes the element specific supplementary folder information. It
521 * creates the folder if not exists. It sets the persistence property of the
522 * trace resource
523 */
524 public void refreshSupplementaryFolder() {
525 IFolder supplFolder = createSupplementaryFolder();
526 try {
527 supplFolder.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
528 } catch (CoreException e) {
529 Activator.getDefault().logError("Error refreshing supplementary folder " + supplFolder, e); //$NON-NLS-1$
530 }
531 }
532
533 /**
534 * Checks if supplementary resource exist or not.
535 *
536 * @return <code>true</code> if one or more files are under the element
537 * supplementary folder
538 */
539 public boolean hasSupplementaryResources() {
540 IResource[] resources = getSupplementaryResources();
541 return (resources.length > 0);
542 }
543
544 /**
545 * Returns the supplementary resources under the trace supplementary folder.
546 *
547 * @return array of resources under the trace supplementary folder.
548 */
549 public IResource[] getSupplementaryResources() {
550 IFolder supplFolder = getTraceSupplementaryFolder(getSupplementaryFolderPath());
551 if (supplFolder.exists()) {
552 try {
553 return supplFolder.members();
554 } catch (CoreException e) {
555 Activator.getDefault().logError("Error deleting supplementary folder " + supplFolder, e); //$NON-NLS-1$
556 }
557 }
558 return new IResource[0];
559 }
560
561 /**
562 * Deletes the given resources.
563 *
564 * @param resources
565 * array of resources to delete.
566 */
567 public void deleteSupplementaryResources(IResource[] resources) {
568
569 for (int i = 0; i < resources.length; i++) {
570 try {
571 resources[i].delete(true, new NullProgressMonitor());
572 } catch (CoreException e) {
573 Activator.getDefault().logError("Error deleting supplementary resource " + resources[i], e); //$NON-NLS-1$
574 }
575 }
576 }
577
578 /**
579 * Deletes all supplementary resources in the supplementary directory
580 */
581 public void deleteSupplementaryResources() {
582 deleteSupplementaryResources(getSupplementaryResources());
583 }
584
585 private IFolder createSupplementaryFolder() {
586 IFolder supplFolder = prepareTraceSupplementaryFolder(getSupplementaryFolderPath(), true);
587
588 try {
589 getResource().setPersistentProperty(TmfCommonConstants.TRACE_SUPPLEMENTARY_FOLDER, supplFolder.getLocation().toOSString());
590 } catch (CoreException e) {
591 Activator.getDefault().logError("Error setting persistant property " + TmfCommonConstants.TRACE_SUPPLEMENTARY_FOLDER, e); //$NON-NLS-1$
592 }
593 return supplFolder;
594 }
595
596 // -------------------------------------------------------
597 // Signal handlers
598 // -------------------------------------------------------
599
600 /**
601 * Handler for the Trace Opened signal
602 *
603 * @param signal
604 * The incoming signal
605 */
606 @TmfSignalHandler
607 public void traceOpened(TmfTraceOpenedSignal signal) {
608 IResource resource = signal.getTrace().getResource();
609 if ((resource == null) || !resource.equals(getResource())) {
610 return;
611 }
612
613 getParent().refresh();
614 }
615
616 }
This page took 0.044928 seconds and 5 git commands to generate.