/******************************************************************************* * Copyright (c) 2012-2015 INRIA. * 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 * * Contributors: * Generoso Pagano - initial API and implementation ******************************************************************************/ package fr.inria.soctrace.framesoc.ui.perspective; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewReference; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopic; import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopicList; import fr.inria.soctrace.framesoc.core.bus.IFramesocBusListener; import fr.inria.soctrace.framesoc.ui.model.TraceIntervalDescriptor; import fr.inria.soctrace.framesoc.ui.perspective.FramesocPartContributionManager.PartContributionDescriptor; import fr.inria.soctrace.framesoc.ui.utils.TraceSelection; import fr.inria.soctrace.lib.model.Trace; import fr.inria.soctrace.lib.utils.Configuration; import fr.inria.soctrace.lib.utils.Configuration.SoCTraceProperty; /** * Singleton to access Framesoc parts management functionalities. It is created at UI plugin startup * (@see FramesocUiStartup). * * <p> * This manager provides the following functionalities: * <ul> * <li>create Framesoc analysis views, ensuring correct secondary ID management * <li>manage Framesoc analysis views groups, for a given trace * <li>clean the Framesoc perspective * <li>handle inter-view communication topics * <li>enable disposal of Framesoc analysis views * <li>enable checking of Framesoc analysis views existence * </ul> * * @author "Generoso Pagano <generoso.pagano@inria.fr>" * */ public final class FramesocPartManager implements IFramesocBusListener { public final static int NO_GROUP = -1; public final static int NEW_GROUP = -2; private final int MaxViewInstancesDefault = 5; /** * Logger */ private final static Logger logger = LoggerFactory.getLogger(FramesocPartManager.class); /** * Followed topics */ protected FramesocBusTopicList topics = null; /** * The listener we register with the selection service. * * I have to set highlighting directly here and avoid sending the focused trace event on the * Framesoc bus, in order to avoid recursion: in fact, the trace tree view listens to such event * too, so it would change its selection, and trigger this listener again.... */ private ISelectionListener listener = new ISelectionListener() { @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { logger.debug("Updating titles after selectionChanged in Trace view"); updateTitlesHighlight(TraceSelection.getTraceFromSelection(selection)); } }; /** * View Descriptor */ private class ViewDesc { public int maxInstances; public int instances; public List<FramesocPart> openParts; public Map<Trace, Map<FramesocPart, Integer>> partToGroup; public ViewDesc(int maxInstances) { this.maxInstances = maxInstances; this.instances = 0; this.openParts = new LinkedList<>(); this.partToGroup = new HashMap<>(); } @Override public String toString() { return "ViewDesc [maxInstances=" + maxInstances + ", instances=" + instances + ", openParts=" + openParts + ", partToGroup=" + partToGroup + "]"; } } /** * Single instance of the manager */ private static FramesocPartManager instance = null; /** * Map of view descriptors for Framesoc view types: ID -> descriptor. */ private Map<String, ViewDesc> viewDescMap; /** * Instance getter * * @return the manager instance */ public static FramesocPartManager getInstance() { if (instance == null) instance = new FramesocPartManager(); return instance; } /** * Get an instance for the given Framesoc analysis view ID. * * <p> * If an empty view corresponding to this ID is present, it is used. Otherwise a new one is * created and activated, if the maximum number of instance has not been reached yet. * * @param viewID * view ID corresponding to an existing Framesoc analysis view * @param trace * the trace we want to load, or null if we need an empty view * @param forceNew * forces the creation of another view for the passed ID, even if one is already * existing * @return a view, or null if the passed ID does not correspond to a Framesoc view, if the * maximum number of instances for the view has been reached, if PartInitException is * launched. */ public OpenFramesocPartStatus getPartInstance(String viewID, Trace trace, boolean allowNew) { return getPartInstance(viewID, trace, allowNew, NO_GROUP); } /** * Get an instance for the given Framesoc analysis view ID, specifying the view group. * * <p> * If an empty view corresponding to this ID is present, it is used. Otherwise a new one is * created and activated, if the maximum number of instance has not been reached yet. * * @param viewID * view ID corresponding to an existing Framesoc analysis view * @param trace * the trace we want to load, or null if we need an empty view * * @param forceNew * forces the creation of another view for the passed ID, even if one is already * existing * @param group * group id requested for the new view. It is ignored if <code>forceNew</code> is * false. If it is set to <code>NO_GROUP</code> or <code>NEW_GROUP</code> the value * is automatically assigned, according to the view ID and the trace. * @return a view, or null if the passed ID does not correspond to a Framesoc view, if the * maximum number of instances for the view has been reached, if PartInitException is * launched. */ protected OpenFramesocPartStatus getPartInstance(String viewID, Trace trace, boolean forceNew, int group) { OpenFramesocPartStatus status = new OpenFramesocPartStatus(); status.message = "View loaded."; // check if it is a valid Framesoc part id ViewDesc desc = viewDescMap.get(viewID); if (desc == null) { status.part = null; status.message = "View '" + viewID + "' is not a Framesoc view."; logger.error(status.message); return status; } // see if the trace is already loaded in a view with the same group id if (trace != null && !forceNew) { logger.debug("see if the trace is already loaded"); FramesocPart part = searchAlreadyLoaded(viewID, trace, group); if (part != null) { part.activateView(); status.part = part; return status; } } // try to reuse an empty view, if any, setting the group logger.debug("try to reuse an empty view"); FramesocPart part = searchEmpty(viewID); if (part != null) { part.activateView(); status.part = part; desc.instances = 1; // there can be only one empty view setGroup(part, trace, desc, group); return status; } // create a new view, if possible logger.debug("create a new view if possible"); // check if max instances reached if (desc.maxInstances != Configuration.INFINITE_VIEWS && desc.instances >= desc.maxInstances) { status.part = null; status.message = "Maximum number of instances (" + desc.maxInstances + ") reached for view '" + viewID + "'."; logger.error(status.message); return status; } // new view creation FramesocPart v = createNewView(viewID); if (v == null) { status.part = null; status.message = "Unable to create view '" + viewID + "'."; logger.error(status.message); return status; } desc.instances++; desc.openParts.add(v); setGroup(v, trace, desc, group); status.part = v; v.activateView(); return status; } private void setGroup(FramesocPart part, Trace trace, ViewDesc desc, int group) { if (trace == null) { // do not create object in the part to group map for null traces return; } if (!desc.partToGroup.containsKey(trace)) { desc.partToGroup.put(trace, new HashMap<FramesocPart, Integer>()); } Map<FramesocPart, Integer> p2g = desc.partToGroup.get(trace); if (group == NO_GROUP || group == NEW_GROUP) { group = getNextGroupId(p2g); } p2g.put(part, group); } private int getNextGroupId(Map<FramesocPart, Integer> p2g) { if (p2g.values().isEmpty()) return 0; return Collections.max(p2g.values()) + 1; } /** * Close all the instances (except one) for the open Framesoc views. * * This method must be called only at the beginning. */ public void cleanFramesocParts() { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { logger.debug("Before clean"); printDescriptors(); final IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows(); // clean desc for (ViewDesc desc : viewDescMap.values()) { desc.instances = 0; desc.openParts.clear(); desc.partToGroup.clear(); } logger.debug("After clean"); printDescriptors(); // for all workbench windows for (int w = 0; w < windows.length; w++) { final IWorkbenchPage[] pages = windows[w].getPages(); // for all workbench pages for (int p = 0; p < pages.length; p++) { final IWorkbenchPage page = pages[p]; final IViewReference[] viewRefs = page.getViewReferences(); // for all view references for (int v = 0; v < viewRefs.length; v++) { final IViewReference viewRef = viewRefs[v]; logger.debug("name: {}, sec id: {}", viewRef.getPartName(), viewRef.getSecondaryId()); // count the FramesocPart instances ViewDesc desc = viewDescMap.get(viewRef.getId()); if (desc != null) { logger.debug("found desc name: {}, sec id: {}", viewRef.getPartName(), viewRef.getSecondaryId()); FramesocPart part = (FramesocPart) viewRef.getPart(true); desc.instances++; desc.openParts.add(part); } else { logger.debug("not found desc name: {}, sec id: {}", viewRef.getPartName(), viewRef.getSecondaryId()); } } } } logger.debug("before leave one instance"); printDescriptors(); logger.debug("leave only one instance"); Iterator<Entry<String, ViewDesc>> it = viewDescMap.entrySet().iterator(); while (it.hasNext()) { Entry<String, ViewDesc> e = it.next(); logger.debug("View ID: {}", e.getKey()); ViewDesc desc = e.getValue(); Iterator<FramesocPart> pit = desc.openParts.iterator(); while (pit.hasNext()) { FramesocPart part = pit.next(); if (desc.instances > 1) { logger.debug("Hide view ID: {}", e.getKey()); part.hideView(); desc.instances--; pit.remove(); } } } logger.debug("After leave one instance, reload"); printDescriptors(); } }); } @Override public void handle(FramesocBusTopic topic, Object data) { if (topic.equals(FramesocBusTopic.TOPIC_UI_HISTOGRAM_DISPLAY_TIME_INTERVAL)) { logger.debug("Topic histogram interval"); displayFramesocView(FramesocViews.HISTOGRAM_VIEW_ID, data); } else if (topic.equals(FramesocBusTopic.TOPIC_UI_TABLE_DISPLAY_TIME_INTERVAL) && data != null) { logger.debug("Topic table interval"); displayFramesocView(FramesocViews.EVENT_TABLE_VIEW_ID, data); } else if (topic.equals(FramesocBusTopic.TOPIC_UI_GANTT_DISPLAY_TIME_INTERVAL) && data != null) { logger.debug("Topic gantt interval"); displayFramesocView(FramesocViews.GANTT_CHART_VIEW_ID, data); } else if (topic.equals(FramesocBusTopic.TOPIC_UI_PIE_DISPLAY_TIME_INTERVAL) && data != null) { logger.debug("Topic pie interval"); displayFramesocView(FramesocViews.STATISTICS_PIE_CHART_VIEW_ID, data); } else if (topic .equals(FramesocBusTopic.TOPIC_UI_SYNCHRONIZE_TIME_AND_FILTER) && data != null) { logger.debug("Topic synchronize all the group view"); synchAllGroupView(data); } } /** * Display a Framesoc view using the trace interval descriptor contained in the data. * * @param viewId * view id * @param data * data containing a trace interval descriptor */ private void displayFramesocView(String viewId, Object data) { TraceIntervalDescriptor des = (TraceIntervalDescriptor) data; OpenFramesocPartStatus status = getPartInstance(viewId, des.getTrace(), false, des.getGroup()); if (status.part == null) { MessageDialog.openError(Display.getDefault().getActiveShell(), "Error", status.message); return; } status.part.showTrace(des.getTrace(), des); updateTitlesHighlight((Trace) des.getTrace()); } /** * Update the Framesoc parts view names, highlighting them if the selected trace is the one * shown in the view, unhighlighting otherwise. * * We use the asyncExec to avoid a non-deterministic buggy behavior: for the pie-chart view, the * show view event does not highlight the name otherwise... * * @param trace * selected trace */ public void updateTitlesHighlight(final Trace trace) { if (trace == null) return; Display.getCurrent().asyncExec(new Runnable() { @Override public void run() { logger.debug("------------------------------------------"); for (ViewDesc desc : viewDescMap.values()) { for (FramesocPart fp : desc.openParts) { if (trace.equals(fp.getCurrentShownTrace())) { logger.debug("Highlight " + fp.getPartName()); fp.highlightTitle(true); } else { logger.debug("Unhighlight " + fp.getPartName()); fp.highlightTitle(false); } } } logger.debug("------------------------------------------"); } }); } /** * Dispose the passed FramesocPart * * @param framesocPart * the part to dispose */ public void disposeFramesocPart(FramesocPart framesocPart) { ViewDesc desc = viewDescMap.get(framesocPart.getId()); if (desc != null) { desc.instances = Math.max(0, desc.instances - 1); desc.openParts.remove(framesocPart); Trace t = framesocPart.getCurrentShownTrace(); if (t != null) { Map<FramesocPart, Integer> parts = desc.partToGroup.get(t); if (parts != null) { parts.remove(framesocPart); } // update also titles (possibly remove numbers) updateTitlesHighlight(t); } } } /** * Tell if a FramesocPart exists or not in the current runtime. * * @param id * FramesocPart id * @return true if the FramesocPart exists in the runtime, false otherwise */ public boolean isFramesocPartExisting(String id) { return viewDescMap.containsKey(id); } /* * Private methods */ /** * Private constructor. Prevents instantiation. */ private FramesocPartManager() { loadDescMap(); topics = new FramesocBusTopicList(this); topics.addTopic(FramesocBusTopic.TOPIC_UI_HISTOGRAM_DISPLAY_TIME_INTERVAL); topics.addTopic(FramesocBusTopic.TOPIC_UI_TABLE_DISPLAY_TIME_INTERVAL); topics.addTopic(FramesocBusTopic.TOPIC_UI_GANTT_DISPLAY_TIME_INTERVAL); topics.addTopic(FramesocBusTopic.TOPIC_UI_PIE_DISPLAY_TIME_INTERVAL); topics.addTopic(FramesocBusTopic.TOPIC_UI_SYNCHRONIZE_TIME_AND_FILTER); topics.registerAll(); // register the selection listener Display.getDefault().asyncExec(new Runnable() { @Override public void run() { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService() .addSelectionListener(listener); } }); }; /** * Create and activate a new FramesocPart, assigning a random secondary ID. * * @param id * primary ID of a FramesocPart * @return a view corresponding to the given ID, or null if a PartInitException occurs */ private FramesocPart createNewView(String id) { // get the active page IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); // pick a random secondary ID String secondaryId = UUID.randomUUID().toString(); // these two lines open (create) and focus on the view try { // Check if there is a plugin providing a view with this ID if (!isFramesocPartExisting(id)) return null; page.showView(id, secondaryId, IWorkbenchPage.VIEW_CREATE); FramesocPart view = (FramesocPart) page.showView(id, secondaryId, IWorkbenchPage.VIEW_ACTIVATE); return view; } catch (Exception e) { e.printStackTrace(); } return null; } /** * Load constants in view descriptor map. */ private void loadDescMap() { int max = MaxViewInstancesDefault; try { max = Integer.valueOf(Configuration.getInstance().get( SoCTraceProperty.max_view_instances)); } catch (NumberFormatException e) { logger.error(SoCTraceProperty.max_view_instances.toString() + " is not an integer, using " + max + " instead."); } viewDescMap = new HashMap<String, ViewDesc>(); if (FramesocPerspective.DEBUG) { viewDescMap.put(FramesocViews.DEBUG_VIEW_ID, new ViewDesc(max)); return; } // Add the FramesocPart advertised in the extension point to the descriptor map List<PartContributionDescriptor> parts = FramesocPartContributionManager.getInstance() .getPartContributionDescriptors(); for (PartContributionDescriptor des : parts) { viewDescMap.put(des.id, new ViewDesc(max)); } } /** * Look for a FramesocPart for the given id with the given trace loaded inside. * * @param viewId * view ID * @param group * @return the part, or null if not found */ private FramesocPart searchAlreadyLoaded(String viewId, Trace trace, int group) { if (group == NEW_GROUP) return null; ViewDesc desc = viewDescMap.get(viewId); if (desc != null) { for (FramesocPart part : desc.openParts) { Trace t = part.getCurrentShownTrace(); if (t != null && trace.equals(t)) { if (group == NO_GROUP || desc.partToGroup.get(trace).get(part) == group) { return part; } } } } return null; } /** * Look for a FramesocPart for the given id without a loaded trace inside. * * @param viewId * view ID * @return the part, or null if not found */ private FramesocPart searchEmpty(String viewId) { ViewDesc desc = viewDescMap.get(viewId); if (desc != null) { for (FramesocPart part : desc.openParts) { if (part.getCurrentShownTrace() == null) return part; } } return null; } /** * Check if a view part corresponding to a given view id is already open for a given trace. * * @param viewId * Framesoc part id * @param trace * trace * @return true, if the trace is already loaded in a view corresponding to a given ID. */ public boolean isAlreadyLoaded(String viewId, Trace trace) { ViewDesc desc = viewDescMap.get(viewId); if (desc != null) { for (FramesocPart part : desc.openParts) { Trace t = part.getCurrentShownTrace(); if (t != null) { if (trace.equals(t)) { return true; } } } } return false; } /** * Return the group corresponding to the given view for the given trace. * * Note that a view part can store only a single trace. The parameter is passed only to avoid * iterating over all open traces to find the passed part reference. * * @param trace * the trace * @param part * the view part * @return the group corresponding to the view part, or <code>NO_GROUP</code> if the view part * is not found. */ public int getPartGroup(Trace trace, FramesocPart part) { ViewDesc desc = viewDescMap.get(part.getId()); if (desc == null || !desc.partToGroup.containsKey(trace)) { return NO_GROUP; } if (!desc.partToGroup.get(trace).containsKey(part)) { return NO_GROUP; } return desc.partToGroup.get(trace).get(part); } /** * Check if for the given trace there are only views for a given group. * * @param trace * trace * @param group * group of views * @return true, if for the given trace, there are only views for the given group */ public boolean isUniqueGroup(Trace trace, int group) { for (ViewDesc desc : viewDescMap.values()) { if (desc.partToGroup.containsKey(trace)) { for (Integer g : desc.partToGroup.get(trace).values()) { if (g != group) { return false; } } } } return true; } // debug private void printDescriptors() { Iterator<Entry<String, ViewDesc>> it = viewDescMap.entrySet().iterator(); while (it.hasNext()) { Entry<String, ViewDesc> e = it.next(); logger.debug("View ID: {}", e.getKey()); logger.debug("Descriptor: {}", e.getValue()); } } /** * Change the value of max instance of a view with the current value in * configuration */ public void updateMaxInstances() { Integer max = MaxViewInstancesDefault; try { max = Integer.valueOf(Configuration.getInstance().get( SoCTraceProperty.max_view_instances)); } catch (NumberFormatException e) { logger.error(SoCTraceProperty.max_view_instances.toString() + " is not an integer, using " + max + " instead."); } for (ViewDesc viewDesc : viewDescMap.values()) { viewDesc.maxInstances = max; } } /** * Synchronize all the views of a given group with the current view on time * interval and producer and type filters * * @param data * a TraceIntervalDescriptor containing the synchronization * elements */ private void synchAllGroupView(Object data) { // Get trace TraceIntervalDescriptor desc = (TraceIntervalDescriptor) data; Trace trace = desc.getTrace(); List<FramesocPart> parts = new ArrayList<FramesocPart>(); // Get all parts of the group for (ViewDesc viewDesc : viewDescMap.values()) { if (viewDesc.partToGroup.containsKey(trace)) { for (FramesocPart fPart : viewDesc.partToGroup.get(trace) .keySet()) { if (viewDesc.partToGroup.get(trace).get(fPart) == desc .getGroup() && fPart != desc.getSender()) parts.add(fPart); } } } // Update them all for (FramesocPart fPart : parts) { fPart.showTrace(trace, desc); } } }