/*******************************************************************************
 * Copyright (c) 2008, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Simon Scholz <simon.scholz@vogella.com> - Bug 462056
 *     Dirk Fauth <dirk.fauth@googlemail.com> - Bug 457939
 *     Alexander Baranov <achilles-86@mail.ru> - Bug 458460
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 483842
 *     Patrik Suzzi <psuzzi@gmail.com> - Bug 487621
 *******************************************************************************/
package org.eclipse.e4.ui.internal.workbench.swt;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.InjectionException;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.di.extensions.EventTopic;
import org.eclipse.e4.core.services.contributions.IContributionFactory;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.core.services.log.Logger;
import org.eclipse.e4.core.services.statusreporter.StatusReporter;
import org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher;
import org.eclipse.e4.ui.css.core.util.impl.resources.OSGiResourceLocator;
import org.eclipse.e4.ui.css.swt.dom.WidgetElement;
import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
import org.eclipse.e4.ui.css.swt.helpers.EclipsePreferencesHelper;
import org.eclipse.e4.ui.css.swt.theme.IThemeEngine;
import org.eclipse.e4.ui.css.swt.theme.IThemeManager;
import org.eclipse.e4.ui.di.Focus;
import org.eclipse.e4.ui.di.PersistState;
import org.eclipse.e4.ui.internal.workbench.Activator;
import org.eclipse.e4.ui.internal.workbench.E4Workbench;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.application.MContribution;
import org.eclipse.e4.ui.model.application.ui.MContext;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MGenericStack;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
import org.eclipse.e4.ui.model.application.ui.advanced.MPlaceholder;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;
import org.eclipse.e4.ui.services.IStylingEngine;
import org.eclipse.e4.ui.workbench.IPresentationEngine;
import org.eclipse.e4.ui.workbench.IResourceUtilities;
import org.eclipse.e4.ui.workbench.IWorkbench;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.swt.factories.IRendererFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.bindings.keys.formatting.KeyFormatterFactory;
import org.eclipse.jface.databinding.swt.DisplayRealm;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.testing.TestableObject;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;
import org.w3c.dom.Element;
import org.w3c.dom.css.CSSStyleDeclaration;

public class PartRenderingEngine implements IPresentationEngine {
	private static final String TAG_EDITOR = "Editor";

	public static final String EARLY_STARTUP_HOOK = "runEarlyStartup";

	public static final String engineURI = "bundleclass://org.eclipse.e4.ui.workbench.swt/"
			+ "org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine";

	private static final String defaultFactoryUrl = "bundleclass://org.eclipse.e4.ui.workbench.renderers.swt/"
			+ "org.eclipse.e4.ui.workbench.renderers.swt.WorkbenchRendererFactory";

	public static final String ENABLED_THEME_KEY = "themeEnabled";

	private String factoryUrl;

	IRendererFactory curFactory = null;

	private Map<String, AbstractPartRenderer> customRendererMap = new HashMap<>();

	org.eclipse.swt.widgets.Listener keyListener;

	@Inject
	@Optional
	private void subscribeTopicToBeRendered(@EventTopic(UIEvents.UIElement.TOPIC_TOBERENDERED) Event event) {

		MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
		MUIElement parent = changedElement.getParent();

		// Handle Detached Windows
		if (parent == null) {
			parent = (MUIElement) ((EObject) changedElement).eContainer();
		}

		// menus are not handled here... ??
		if (parent instanceof MMenu) {
			return;
		}

		// If the parent isn't visible we don't care (The application is
		// never rendered)
		boolean okToRender = parent instanceof MApplication || parent.getWidget() != null;

		if (changedElement.isToBeRendered() && okToRender) {
			if (Policy.DEBUG_RENDERER) {
				WorkbenchSWTActivator.trace(Policy.DEBUG_RENDERER_FLAG, "visible -> true", null); //$NON-NLS-1$
			}

			// Note that the 'createGui' protocol calls 'childAdded'
			Object w = createGui(changedElement);
			if (w instanceof Control && !(w instanceof Shell)) {
				fixZOrder(changedElement);
			}
		} else {
			if (Policy.DEBUG_RENDERER) {
				WorkbenchSWTActivator.trace(Policy.DEBUG_RENDERER_FLAG, "visible -> false", null); //$NON-NLS-1$
			}

			// Ensure that the element about to be removed is not the
			// selected element
			if (parent instanceof MElementContainer<?>) {
				@SuppressWarnings("unchecked")
				MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) parent;
				if (container.getSelectedElement() == changedElement) {
					container.setSelectedElement(null);
				}
			}

			if (okToRender) {
				// Un-maximize the element before tearing it down
				if (changedElement.getTags().contains(MAXIMIZED)) {
					changedElement.getTags().remove(MAXIMIZED);
				}

				// Note that the 'removeGui' protocol calls 'childRemoved'
				removeGui(changedElement);
			}
		}
	}

	@Inject
	@Optional
	private void subscribeVisibilityHandler(@EventTopic(UIEvents.UIElement.TOPIC_VISIBLE) Event event) {

		MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
		MUIElement parent = changedElement.getParent();
		if (parent == null) {
			parent = (MUIElement) ((EObject) changedElement).eContainer();
			if (parent == null) {
				return;
			}
		}

		AbstractPartRenderer renderer = (AbstractPartRenderer) parent.getRenderer();
		if (renderer == null || parent instanceof MToolBar) {
			return;
		}

		// Re-parent the control based on the visible state
		if (changedElement.isVisible()) {
			if (changedElement.isToBeRendered()) {
				if (changedElement.getWidget() instanceof Control) {
					// Ensure that the control is under its 'real' parent if
					// it's visible
					Composite realComp = (Composite) renderer.getUIContainer(changedElement);
					Control ctrl = (Control) changedElement.getWidget();
					ctrl.setParent(realComp);
					fixZOrder(changedElement);
				}

				if (parent instanceof MElementContainer<?>) {
					@SuppressWarnings("unchecked")
					MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) parent;
					renderer.childRendered(container, changedElement);
				}
			}
		} else {
			// Put the control under the 'limbo' shell
			if (changedElement.getWidget() instanceof Control) {
				Control ctrl = (Control) changedElement.getWidget();
				ctrl.requestLayout();
				ctrl.setParent(getLimboShell());
			}

			if (parent instanceof MElementContainer<?>) {
				@SuppressWarnings("unchecked")
				MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) parent;
				renderer.hideChild(container, changedElement);
			}
		}
	}

	@Inject
	@Optional
	private void subscribeTrimHandler(@EventTopic(UIEvents.TrimmedWindow.TOPIC_TRIMBARS) Event event) {

		Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
		if (!(changedObj instanceof MTrimmedWindow)) {
			return;
		}

		MTrimmedWindow window = (MTrimmedWindow) changedObj;
		if (window.getWidget() == null) {
			return;
		}

		if (UIEvents.isADD(event)) {
			for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) {
				MUIElement added = (MUIElement) o;
				if (added.isToBeRendered()) {
					createGui(added, window.getWidget(), window.getContext());
				}
			}
		} else if (UIEvents.isREMOVE(event)) {
			for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) {
				MUIElement removed = (MUIElement) o;
				if (removed.getRenderer() != null) {
					removeGui(removed);
				}
			}
		}
	}

	@Inject
	@Optional
	private void subscribeChildrenHandler(@EventTopic(UIEvents.ElementContainer.TOPIC_CHILDREN) Event event) {

		Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT);
		if (!(changedObj instanceof MElementContainer<?>)) {
			return;
		}

		@SuppressWarnings("unchecked")
		MElementContainer<MUIElement> changedElement = (MElementContainer<MUIElement>) changedObj;
		boolean isApplication = changedObj instanceof MApplication;

		boolean menuChild = changedObj instanceof MMenu;
		// If the parent isn't in the UI then who cares?
		AbstractPartRenderer renderer = getRendererFor(changedElement);
		if ((!isApplication && renderer == null) || menuChild) {
			return;
		}

		if (UIEvents.isADD(event)) {
			if (Policy.DEBUG_RENDERER) {
				WorkbenchSWTActivator.trace(Policy.DEBUG_RENDERER_FLAG, "Child Added", null); //$NON-NLS-1$
			}
			for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) {
				MUIElement added = (MUIElement) o;

				// OK, we have a new -visible- part we either have to create
				// it or host it under the correct parent. Note that we
				// explicitly do *not* render non-selected elements in
				// stacks (to support lazy loading).
				boolean isStack = changedObj instanceof MGenericStack<?>;
				boolean hasWidget = added.getWidget() != null;
				boolean isSelected = added == changedElement.getSelectedElement();
				boolean renderIt = !isStack || hasWidget || isSelected;
				if (renderIt) {
					// NOTE: createGui will call 'childAdded' if successful
					Object w = createGui(added);
					if (w instanceof Control && !(w instanceof Shell)) {
						final Control ctrl = (Control) w;
						fixZOrder(added);
						if (!ctrl.isDisposed()) {
							ctrl.requestLayout();
						}
					}
				} else {
					if (renderer != null && added.isToBeRendered()) {
						renderer.childRendered(changedElement, added);
					}
				}

				// If the element being added is a placeholder, check to see
				// if
				// it's 'globally visible' and, if so, remove all other
				// 'local' placeholders referencing the same element.
				int newLocation = modelService.getElementLocation(added);
				if (newLocation == EModelService.IN_SHARED_AREA || newLocation == EModelService.OUTSIDE_PERSPECTIVE) {
					MWindow topWin = modelService.getTopLevelWindowFor(added);
					modelService.hideLocalPlaceholders(topWin, null);
				}
			}
		} else if (UIEvents.isREMOVE(event)) {
			if (Policy.DEBUG_RENDERER) {
				WorkbenchSWTActivator.trace(Policy.DEBUG_RENDERER_FLAG, "Child Removed", null); //$NON-NLS-1$
			}
			for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.OLD_VALUE)) {
				MUIElement removed = (MUIElement) o;
				// Removing invisible elements is a NO-OP as far as the
				// renderer is concerned
				if (!removed.isToBeRendered()) {
					continue;
				}

				if (removed.getWidget() instanceof Control) {
					Control ctrl = (Control) removed.getWidget();
					ctrl.setLayoutData(null);
					// bug 487621
					ctrl.getParent().layout(new Control[] { ctrl }, SWT.CHANGED | SWT.DEFER);
				}

				// Ensure that the element about to be removed is not the
				// selected element
				if (changedElement.getSelectedElement() == removed) {
					changedElement.setSelectedElement(null);
				}

				if (renderer != null) {
					renderer.hideChild(changedElement, removed);
				}
			}
		}
	}

	@Inject
	@Optional
	private void subscribeWindowsHandler(@EventTopic(UIEvents.Window.TOPIC_WINDOWS) Event event) {

		subscribeChildrenHandler(event);
	}

	@Inject
	@Optional
	private void subscribePerspectiveWindowsHandler(@EventTopic(UIEvents.Perspective.TOPIC_WINDOWS) Event event) {
		subscribeChildrenHandler(event);
	}

	@Inject
	@Optional
	private void subscribeCssThemeChanged(@EventTopic(IThemeEngine.Events.THEME_CHANGED) Event event) {
		cssThemeChangedHandler.handleEvent(event);
	}

	private IEclipseContext appContext;

	protected Shell testShell;

	protected MApplication theApp;

	@Inject
	EModelService modelService;

	@Inject
	protected Logger logger;

	private Shell limbo;

	private MUIElement removeRoot = null;

	@Inject
	@Optional
	IEventBroker eventBroker;

	private StylingPreferencesHandler cssThemeChangedHandler;

	@Inject
	public PartRenderingEngine(
			@Named(E4Workbench.RENDERER_FACTORY_URI) @Optional String factoryUrl) {
		if (factoryUrl == null) {
			factoryUrl = defaultFactoryUrl;
		}
		this.factoryUrl = factoryUrl;
	}

	protected void fixZOrder(MUIElement element) {
		MElementContainer<MUIElement> parent = element.getParent();
		if (parent == null) {
			Object econtainer = ((EObject) element).eContainer();
			if (econtainer instanceof MElementContainer<?>) {
				@SuppressWarnings("unchecked")
				MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) econtainer;
				parent = container;
			}
		}
		if (parent == null || !(element.getWidget() instanceof Control)) {
			return;
		}

		Control elementCtrl = (Control) element.getWidget();
		Control prevCtrl = null;
		for (MUIElement kid : parent.getChildren()) {
			if (kid == element) {
				if (prevCtrl != null) {
					elementCtrl.moveBelow(prevCtrl);
				} else {
					elementCtrl.moveAbove(null);
				}
				break;
			} else if (kid.getWidget() instanceof Control && kid.isVisible()) {
				prevCtrl = (Control) kid.getWidget();
			}
		}

		Object widget = parent.getWidget();
		if (widget instanceof Composite) {
			Composite composite = (Composite) widget;
			if (composite.getShell() == elementCtrl.getShell()) {
				Composite temp = elementCtrl.getParent();
				while (temp != composite) {
					if (temp == null) {
						return;
					}
					temp = temp.getParent();
				}
				composite.layout(true, true);
			}
		}
	}

	/**
	 * Initialize a part renderer from the extension point.
	 *
	 * @param context
	 *            the context for the part factories
	 */
	@PostConstruct
	void initialize(IEclipseContext context) {
		this.appContext = context;

		// initialize the correct key-binding display formatter
		KeyFormatterFactory.setDefault(SWTKeySupport.getKeyFormatterForPlatform());

		// Add the renderer to the context
		context.set(IPresentationEngine.class, this);

		IRendererFactory factory = null;
		IContributionFactory contribFactory = context.get(IContributionFactory.class);
		try {
			factory = (IRendererFactory) contribFactory.create(factoryUrl, context);
		} catch (Exception e) {
			logger.warn(e, "Could not create rendering factory");
		}

		// Try to load the default one
		if (factory == null) {
			try {
				factory = (IRendererFactory) contribFactory.create(defaultFactoryUrl, context);
			} catch (Exception e) {
				logger.error(e, "Could not create default rendering factory");
			}
		}

		if (factory == null) {
			throw new IllegalStateException("Could not create any rendering factory. Aborting ...");
		}

		curFactory = factory;
		context.set(IRendererFactory.class, curFactory);

		cssThemeChangedHandler = new StylingPreferencesHandler(context.get(Display.class));
	}

	private static void populateModelInterfaces(MContext contextModel,
			IEclipseContext context, Class<?>[] interfaces) {
		for (Class<?> intf : interfaces) {
			if (Policy.DEBUG_CONTEXTS) {
				WorkbenchSWTActivator.trace(Policy.DEBUG_CONTEXTS_FLAG,
						"Adding " + intf.getName() + " for " //$NON-NLS-1$ //$NON-NLS-2$
								+ contextModel.getClass().getName(), null);
			}
			context.set(intf.getName(), contextModel);

			populateModelInterfaces(contextModel, context, intf.getInterfaces());
		}
	}

	private String getContextName(MUIElement element) {
		StringBuilder builder = new StringBuilder(element.getClass()
				.getSimpleName());
		String elementId = element.getElementId();
		if (elementId != null && elementId.length() != 0) {
			builder.append(" (").append(elementId).append(") ");
		}
		List<String> tags = element.getTags();
		if (element instanceof MPart && tags.contains(TAG_EDITOR)) {
			tags.stream().filter(x -> !x.equals(TAG_EDITOR)).forEach(x -> builder.append(" ").append(x));
			String label = ((MPart) element).getLabel();
			if (label != null) {
				builder.append(" (").append(label).append(") ");
			}
		}
		builder.append("Context");
		return builder.toString();
	}

	@Override
	public Object createGui(final MUIElement element,
			final Object parentWidget, final IEclipseContext parentContext) {
		final Object[] gui = { null };
		// wrap the handling in a SafeRunner so that exceptions do not prevent
		// the renderer from processing other elements
		SafeRunner.run(new ISafeRunnable() {
			@Override
			public void handleException(Throwable e) {
				if (e instanceof Error) {
					// errors are deadly, we shouldn't ignore these
					throw (Error) e;
				}
				// log exceptions otherwise
				if (logger != null) {
					String message = "Exception occurred while rendering: {0}"; //$NON-NLS-1$
					logger.error(e, NLS.bind(message, element));
				}
			}

			@Override
			public void run() throws Exception {
				gui[0] = safeCreateGui(element, parentWidget, parentContext);
			}
		});
		return gui[0];
	}

	public Object safeCreateGui(MUIElement element, Object parentWidget,
			IEclipseContext parentContext) {
		if (!element.isToBeRendered())
			return null;

		// no creates while processing a remove
		if (removeRoot != null) {
			return null;
		}

		Object currentWidget = element.getWidget();
		if (currentWidget != null) {
			if (currentWidget instanceof Control) {
				Control control = (Control) currentWidget;
				// make sure the control is visible
				MUIElement elementParent = element.getParent();
				if (!(element instanceof MPlaceholder)
						|| !(elementParent instanceof MPartStack))
					control.setVisible(true);

				if (parentWidget instanceof Composite) {
					Composite currentParent = control.getParent();
					if (currentParent != parentWidget) {
						// check if the original parent was a tab folder
						if (currentParent instanceof CTabFolder) {
							CTabFolder folder = (CTabFolder) currentParent;
							// if we used to be the tab folder's top right
							// control, unset it
							if (folder.getTopRight() == control) {
								folder.setTopRight(null);
							}
						}

						// the parents are different so we should reparent it
						control.setParent((Composite) parentWidget);
					}
				}
			}

			// Reparent the context (or the kid's context)
			if (element instanceof MContext) {
				IEclipseContext ctxt = ((MContext) element).getContext();
				if (ctxt != null)
					ctxt.setParent(parentContext);
			} else {
				List<MContext> childContexts = modelService.findElements(
						element, null, MContext.class, null);
				for (MContext c : childContexts) {
					// Ensure that we only reset the context of our direct
					// children
					MUIElement kid = (MUIElement) c;
					MUIElement parent = kid.getParent();
					if (parent == null && kid.getCurSharedRef() != null)
						parent = kid.getCurSharedRef().getParent();
					if (!(element instanceof MPlaceholder) && parent != element)
						continue;

					if (c.getContext() != null
							&& c.getContext().getParent() != parentContext) {
						c.getContext().setParent(parentContext);
					}
				}
			}

			// Now that we have a widget let the parent (if any) know
			MElementContainer<MUIElement> parentElement = element.getParent();
			if (parentElement != null) {
				AbstractPartRenderer parentRenderer = getRendererFor(parentElement);
				if (parentRenderer != null) {
					parentRenderer.childRendered(parentElement, element);
				}
			}
			return element.getWidget();
		}

		if (element instanceof MContext) {
			MContext ctxt = (MContext) element;
			// Assert.isTrue(ctxt.getContext() == null,
			// "Before rendering Context should be null");
			if (ctxt.getContext() == null) {
				IEclipseContext lclContext = parentContext
						.createChild(getContextName(element));
				populateModelInterfaces(ctxt, lclContext, element.getClass()
						.getInterfaces());
				ctxt.setContext(lclContext);

				// System.out.println("New Context: " + lclContext.toString()
				// + " parent: " + parentContext.toString());

				// make sure the context knows about these variables that have
				// been defined in the model
				for (String variable : ctxt.getVariables()) {
					lclContext.declareModifiable(variable);
				}

				Map<String, String> props = ctxt.getProperties();
				for (Entry<String, String> entry : props.entrySet()) {
					lclContext.set(entry.getKey(), entry.getValue());
				}
			}
		}

		// We check the widget again since it could be created by some UI event.
		// See Bug 417399
		if (element.getWidget() != null) {
			return safeCreateGui(element, parentWidget, parentContext);
		}

		// Create a control appropriate to the part
		Object newWidget = createWidget(element, parentWidget);

		// Remember that we've created the control
		if (newWidget != null) {
			AbstractPartRenderer renderer = getRendererFor(element);

			// Have the renderer hook up any widget specific listeners
			renderer.hookControllerLogic(element);

			// Process its internal structure through the renderer that created
			// it
			if (element instanceof MElementContainer) {
				@SuppressWarnings("unchecked")
				MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) element;
				renderer.processContents(container);
			}

			// Allow a final chance to set up
			renderer.postProcess(element);

			// Now that we have a widget let the parent (if any) know
			MElementContainer<MUIElement> parentElement = element.getParent();
			if (parentElement != null) {
				AbstractPartRenderer parentRenderer = getRendererFor(parentElement);
				if (parentRenderer != null) {
					parentRenderer.childRendered(parentElement, element);
				}
			}
		} else {
			// failed to create the widget, dispose its context if necessary
			if (element instanceof MContext) {
				MContext ctxt = (MContext) element;
				IEclipseContext lclContext = ctxt.getContext();
				if (lclContext != null) {
					lclContext.dispose();
					ctxt.setContext(null);
				}
			}
		}

		return newWidget;
	}

	private IEclipseContext getContext(MUIElement parent) {
		if (parent instanceof MContext) {
			return ((MContext) parent).getContext();
		}
		return modelService.getContainingContext(parent);
	}

	@Override
	public Object createGui(final MUIElement element) {
		final Object[] gui = { null };
		// wrap the handling in a SafeRunner so that exceptions do not prevent
		// the renderer from processing other elements
		SafeRunner.run(new ISafeRunnable() {
			@Override
			public void handleException(Throwable e) {
				if (e instanceof Error) {
					// errors are deadly, we shouldn't ignore these
					throw (Error) e;
				}
				// log exceptions otherwise
				if (logger != null) {
					String message = "Exception occurred while rendering: {0}"; //$NON-NLS-1$
					logger.error(e, NLS.bind(message, element));
				}
			}

			@Override
			public void run() throws Exception {
				gui[0] = safeCreateGui(element);
			}
		});
		return gui[0];
	}

	private Object safeCreateGui(MUIElement element) {
		// Obtain the necessary parent widget
		Object parent = null;
		MUIElement parentME = element.getParent();
		if (parentME == null)
			parentME = (MUIElement) ((EObject) element).eContainer();
		if (parentME != null) {
			AbstractPartRenderer renderer = getRendererFor(parentME);
			if (renderer != null) {
				if (!element.isVisible()) {
					parent = getLimboShell();
				} else {
					parent = renderer.getUIContainer(element);
				}
			}
		}

		// Obtain the necessary parent context
		IEclipseContext parentContext = null;
		if (element.getCurSharedRef() != null) {
			MPlaceholder ph = element.getCurSharedRef();
			parentContext = getContext(ph.getParent());
		} else if (parentContext == null && element.getParent() != null) {
			parentContext = getContext(element.getParent());
		} else if (parentContext == null && element.getParent() == null) {
			parentContext = getContext((MUIElement) ((EObject) element)
					.eContainer());
		}

		return safeCreateGui(element, parent, parentContext);
	}

	@Override
	public void focusGui(MUIElement element) {
		AbstractPartRenderer renderer = (AbstractPartRenderer) element
				.getRenderer();
		if (renderer == null || element.getWidget() == null) {
			if (Policy.DEBUG_FOCUS) {
				WorkbenchSWTActivator.trace(Policy.DEBUG_FOCUS_FLAG,
						"Trying to focus GUI on element without renderer or widget: " + element, new Exception()); //$NON-NLS-1$
			}
			return;
		}

		Object implementation = element instanceof MContribution ? ((MContribution) element)
				.getObject() : null;

		// If there is no class to call @Focus on then revert to the default
		if (implementation == null) {
			renderer.forceFocus(element);
			return;
		}

		try {
			IEclipseContext context = getContext(element);
			Object defaultValue = new Object();
			Object returnValue = ContextInjectionFactory.invoke(implementation,
					Focus.class, context, defaultValue);
			if (returnValue == defaultValue) {
				// No @Focus method, force the focus
				renderer.forceFocus(element);
			} else {
				if (Policy.DEBUG_FOCUS) {
					WorkbenchSWTActivator.trace(Policy.DEBUG_FOCUS_FLAG, "Focused GUI on element: " + element, null); //$NON-NLS-1$
				}
			}
		} catch (InjectionException e) {
			log("Failed to grant focus to element", "Failed to grant focus to element ({0})", //$NON-NLS-1$ //$NON-NLS-2$
					element.getElementId(), e);
		} catch (RuntimeException e) {
			log("Failed to grant focus to element via DI", //$NON-NLS-1$
					"Failed to grant focus via DI to element ({0})", element.getElementId(), e); //$NON-NLS-1$
		}
	}

	private void log(String unidentifiedMessage, String identifiedMessage,
			String id, Exception e) {
		if (id == null || id.length() == 0) {
			logger.error(e, unidentifiedMessage);
		} else {
			logger.error(e, NLS.bind(identifiedMessage, id));
		}
	}

	private Shell getLimboShell() {
		if (limbo == null) {
			limbo = new Shell(Display.getCurrent(), SWT.NONE);
			limbo.setText("PartRenderingEngine's limbo"); //$NON-NLS-1$ // just for debugging, not shown anywhere

			// Place the limbo shell 'off screen'
			limbo.setLocation(0, 10000);

			limbo.setBackgroundMode(SWT.INHERIT_DEFAULT);
			limbo.setData(ShellActivationListener.DIALOG_IGNORE_KEY,
					Boolean.TRUE);
			limbo.addShellListener(new ShellAdapter() {
				@Override
				public void shellClosed(ShellEvent e) {
					// please don't close the limbo shell
					e.doit = false;
				}
			});
		}
		return limbo;
	}

	/**
	 * @param element
	 */
	@Override
	public void removeGui(final MUIElement element) {
		// wrap the handling in a SafeRunner so that exceptions do not prevent
		// the menu from being shown
		SafeRunner.run(new ISafeRunnable() {
			@Override
			public void handleException(Throwable e) {
				if (e instanceof Error) {
					// errors are deadly, we shouldn't ignore these
					throw (Error) e;
				}
				// log exceptions otherwise
				if (logger != null) {
					String message = "Exception occurred while unrendering: {0}"; //$NON-NLS-1$
					logger.error(e, NLS.bind(message, element));
				}
			}

			@Override
			public void run() throws Exception {
				safeRemoveGui(element);
			}
		});
	}

	private void safeRemoveGui(MUIElement element) {
		if (removeRoot == null)
			removeRoot = element;

		// We call 'hideChild' *before* checking if the actual element
		// has been rendered in order to pick up cases of 'lazy loading'
		MUIElement parent = element.getParent();
		AbstractPartRenderer parentRenderer = parent != null ? getRendererFor(parent)
				: null;
		if (parentRenderer != null) {
			parentRenderer.hideChild(element.getParent(), element);
		}

		AbstractPartRenderer renderer = getRendererFor(element);

		// If the element hasn't been rendered then this is a NO-OP
		if (renderer != null) {

			if (element instanceof MElementContainer<?>) {
				@SuppressWarnings("unchecked")
				MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) element;
				MUIElement selectedElement = container.getSelectedElement();
				List<MUIElement> children = container.getChildren();
				// Bug 458460: Operate on a copy in case child nulls out parent
				for (MUIElement child : new ArrayList<>(children)) {
					// remove stuff in the "back" first
					if (child != selectedElement) {
						removeGui(child);
					}
				}

				if (selectedElement != null
						&& children.contains(selectedElement)) {
					// now remove the selected element
					removeGui(selectedElement);
				}
			}

			if (element instanceof MPerspective) {
				MPerspective perspective = (MPerspective) element;
				for (MWindow subWindow : perspective.getWindows()) {
					removeGui(subWindow);
				}
			} else if (element instanceof MWindow) {
				MWindow window = (MWindow) element;
				for (MWindow subWindow : window.getWindows()) {
					removeGui(subWindow);
				}

				if (window instanceof MTrimmedWindow) {
					MTrimmedWindow trimmedWindow = (MTrimmedWindow) window;
					for (MUIElement trimBar : trimmedWindow.getTrimBars()) {
						removeGui(trimBar);
					}
				}
			}

			if (element instanceof MContribution) {
				MContribution contribution = (MContribution) element;
				Object client = contribution.getObject();
				IEclipseContext parentContext = renderer.getContext(element);
				if (parentContext != null && client != null) {
					try {
						ContextInjectionFactory.invoke(client, PersistState.class, parentContext, null);
					} catch (Exception e) {
						if (logger != null) {
							logger.error(e);
						}
					}
				}
			}

			renderer.disposeWidget(element);

			// unset the client object
			if (element instanceof MContribution) {
				MContribution contribution = (MContribution) element;
				Object client = contribution.getObject();
				IEclipseContext parentContext = renderer.getContext(element);
				if (parentContext != null && client != null) {
					try {
						ContextInjectionFactory.uninject(client, parentContext);
					} catch (Exception e) {
						if (logger != null) {
							logger.error(e);
						}
					}
				}
				contribution.setObject(null);
			}

			// dispose the context
			if (element instanceof MContext) {
				clearContext((MContext) element);
			}
		}

		if (element instanceof MPlaceholder) {
			MPlaceholder ph = (MPlaceholder) element;
			if (ph.getRef() != null && ph.getRef().getCurSharedRef() == ph) {
				ph.getRef().setCurSharedRef(null);
			}
		}

		if (removeRoot == element)
			removeRoot = null;
	}

	private void clearContext(MContext contextME) {
		MContext ctxt = contextME;
		IEclipseContext lclContext = ctxt.getContext();
		if (lclContext != null) {
			IEclipseContext parentContext = lclContext.getParent();
			IEclipseContext child = parentContext != null ? parentContext
					.getActiveChild() : null;
			if (child == lclContext) {
				child.deactivate();
			}

			ctxt.setContext(null);
			lclContext.dispose();
		}
	}

	protected Object createWidget(MUIElement element, Object parent) {
		AbstractPartRenderer renderer = getRenderer(element, parent);
		if (renderer != null) {
			// Remember which renderer is responsible for this widget
			element.setRenderer(renderer);
			Object newWidget = renderer.createWidget(element, parent);
			if (newWidget != null) {
				renderer.bindWidget(element, newWidget);
				return newWidget;
			}
		}

		return null;
	}

	private AbstractPartRenderer getRenderer(MUIElement uiElement, Object parent) {
		// Is there a custom renderer defined ?
		String customURI = uiElement.getPersistedState().get(IPresentationEngine.CUSTOM_RENDERER_KEY);
		if (customURI != null) {
			AbstractPartRenderer abstractPartRenderer = customRendererMap.get(customURI);
			if (abstractPartRenderer != null) {
				return abstractPartRenderer;
			}

			IEclipseContext owningContext = modelService.getContainingContext(uiElement);
			IContributionFactory contributionFactory = owningContext.get(IContributionFactory.class);
			Object customRenderer = contributionFactory.create(customURI, owningContext);
			if (customRenderer instanceof AbstractPartRenderer) {
				customRendererMap.put(customURI, (AbstractPartRenderer) customRenderer);
				return (AbstractPartRenderer) customRenderer;
			}
		}

		// If not then use the default renderer
		return curFactory.getRenderer(uiElement, parent);
	}

	protected AbstractPartRenderer getRendererFor(MUIElement element) {
		return (AbstractPartRenderer) element.getRenderer();
	}

	@Override
	@Inject
	@Optional
	public Object run(final MApplicationElement uiRoot, final IEclipseContext runContext) {
		final Display display;
		if (runContext.get(Display.class) != null) {
			display = runContext.get(Display.class);
		} else {
			display = Display.getDefault();
			runContext.set(Display.class, display);
		}
		Realm.runWithDefault(DisplayRealm.getRealm(display), new Runnable() {

			@Override
			public void run() {
				initializeStyling(display, runContext);

				// Register an SWT resource handler
				runContext.set(IResourceUtilities.class, new ResourceUtility());

				// set up the keybinding manager
				KeyBindingDispatcher dispatcher = ContextInjectionFactory.make(KeyBindingDispatcher.class, runContext);
				runContext.set(KeyBindingDispatcher.class, dispatcher);
				keyListener = dispatcher.getKeyDownFilter();
				display.addFilter(SWT.KeyDown, keyListener);
				display.addFilter(SWT.Traverse, keyListener);

				// Show the initial UI

				// Create a 'limbo' shell (used to host controls that shouldn't
				// be in the current layout)
				Shell limbo = getLimboShell();
				runContext.set("limbo", limbo);

				// HACK!! we should loop until the display gets disposed...
				// ...then we listen for the last 'main' window to get disposed
				// and dispose the Display
				testShell = null;
				theApp = null;
				boolean spinOnce = true;
				if (uiRoot instanceof MApplication) {
					ShellActivationListener shellDialogListener = new ShellActivationListener((MApplication) uiRoot);
					display.addFilter(SWT.Activate, shellDialogListener);
					display.addFilter(SWT.Deactivate, shellDialogListener);
					spinOnce = false; // loop until the app closes
					theApp = (MApplication) uiRoot;
					// long startTime = System.currentTimeMillis();
					for (MWindow window : theApp.getChildren()) {
						createGui(window);
					}

					// long endTime = System.currentTimeMillis();
					// System.out.println("Render: " + (endTime - startTime));
					// tell the app context we are starting so the splash is
					// torn down
					IApplicationContext ac = appContext.get(IApplicationContext.class);
					if (ac != null) {
						ac.applicationRunning();
						if (eventBroker != null) {
							eventBroker.post(
									UIEvents.UILifeCycle.APP_STARTUP_COMPLETE,
									theApp);
						}
					}
				} else if (uiRoot instanceof MUIElement) {
					if (uiRoot instanceof MWindow) {
						testShell = (Shell) createGui((MUIElement) uiRoot);
					} else {
						// Special handling for partial models (for testing...)
						testShell = new Shell(display, SWT.SHELL_TRIM);
						createGui((MUIElement) uiRoot, testShell, null);
					}
				}

				// allow any early startup extensions to run
				Runnable earlyStartup = (Runnable) runContext.get(EARLY_STARTUP_HOOK);
				if (earlyStartup != null) {
					earlyStartup.run();
				}

				TestableObject testableObject = runContext.get(TestableObject.class);
				if (testableObject instanceof E4Testable) {
					((E4Testable) testableObject).init(display, runContext.get(IWorkbench.class));
				}

				IEventLoopAdvisor advisor = runContext.getActiveLeaf().get(IEventLoopAdvisor.class);
				if (advisor == null) {
					advisor = new IEventLoopAdvisor() {
						@Override
						public void eventLoopIdle(Display display) {
							display.sleep();
						}

						@Override
						public void eventLoopException(Throwable exception) {
							StatusReporter statusReporter = appContext.get(StatusReporter.class);
							if (statusReporter != null) {
								statusReporter.show(StatusReporter.ERROR, "Internal Error", exception);
							} else {
								if (logger != null) {
									logger.error(exception);
								}
							}
						}
					};
				}
				final IEventLoopAdvisor finalAdvisor = advisor;
				display.setErrorHandler(e -> {
					// If e is one of the exception types that are generally
					// recoverable, hand it to the event loop advisor
					if (e instanceof LinkageError || e instanceof AssertionError) {
						handle(e, finalAdvisor);
					} else {
						// Otherwise, rethrow it
						throw e;
					}
				});
				display.setRuntimeExceptionHandler(e -> handle(e, finalAdvisor));
				// Spin the event loop until someone disposes the display
				while (((testShell != null && !testShell.isDisposed()) || (theApp != null && someAreVisible(theApp
						.getChildren()))) && !display.isDisposed()) {
					try {
						if (!display.readAndDispatch()) {
							runContext.processWaiting();
							if (spinOnce) {
								return;
							}
							advisor.eventLoopIdle(display);
						}
					} catch (ThreadDeath th) {
						throw th;
					} catch (Exception ex) {
						handle(ex, advisor);
					} catch (Error err) {
						handle(err, advisor);
					}
				}
				if (!spinOnce) {
					cleanUp();
				}
			}

			private void handle(Throwable ex, IEventLoopAdvisor advisor) {
				try {
					advisor.eventLoopException(ex);
				} catch (Throwable t) {
					if (t instanceof ThreadDeath) {
						throw (ThreadDeath) t;
					}

					// couldn't handle the exception, print to console
					t.printStackTrace();
				}
			}
		});

		return IApplication.EXIT_OK;
	}

	protected boolean someAreVisible(List<MWindow> windows) {
		// This method is called from the event dispatch loop, so the
		// following optimization is in order...

		// Ideally, we'd just do:
		// for (MWindow win : theApp.getChildren()) {
		// But this creates an iterator (which must be GC'd)
		// at every call. The code below creates no objects.
		final int limit = windows.size();
		for (int i = 0; i < limit; i++) {
			final MWindow win = windows.get(i);
			// Note: Removed isVisible test, as this should have
			// no impact on the whether the event loop
			// terminates - non-visible windows still exists
			// and can receive events.
			// Note: isToBeRendered() == true => win.getWidget() != null
			// but I'm not sure whether there is latency between setting
			// toBeRendered and the creation of the widget. So, keeping
			// both tests seems prudent.
			if (win.isToBeRendered() && win.getWidget() != null) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void stop() {
		// FIXME Without this call the test-suite fails
		cleanUp();
		if (theApp != null) {
			for (MWindow window : theApp.getChildren()) {
				if (window.getWidget() != null) {
					removeGui(window);
				}
			}
		} else if (testShell != null && !testShell.isDisposed()) {
			Object model = testShell.getData(AbstractPartRenderer.OWNING_ME);
			if (model instanceof MUIElement) {
				removeGui((MUIElement) model);
			} else {
				testShell.close();
			}
		}
	}

	/*
	 * There are situations where this is called more than once until we know
	 * why this is needed we should make this safe for multiple calls
	 */
	private void cleanUp() {
		if (keyListener != null) {
			Display display = Display.getDefault();
			if (!display.isDisposed()) {
				display.removeFilter(SWT.KeyDown, keyListener);
				display.removeFilter(SWT.Traverse, keyListener);
				keyListener = null;
			}
		}
	}

	public static void initializeStyling(Display display,
			IEclipseContext appContext) {
		String cssTheme = (String) appContext.get(E4Application.THEME_ID);
		String cssURI = (String) appContext.get(IWorkbench.CSS_URI_ARG);

		IScopeContext[] contexts = new IScopeContext[] { DefaultScope.INSTANCE, InstanceScope.INSTANCE };
		boolean enableThemePreference = Platform.getPreferencesService()
				.getBoolean("org.eclipse.e4.ui.workbench.renderers.swt", ENABLED_THEME_KEY, true, contexts);

		if ("none".equals(cssTheme) || (!enableThemePreference)) {
			appContext.set(IStylingEngine.class, new IStylingEngine() {
				@Override
				public void setClassname(Object widget, String classname) {
					WidgetElement.setCSSClass((Widget) widget, classname);
				}

				@Override
				public void setId(Object widget, String id) {
					WidgetElement.setID((Widget) widget, id);
				}

				@Override
				public void style(Object widget) {
				}

				@Override
				public CSSStyleDeclaration getStyle(Object widget) {
					return null;
				}

				@Override
				public void setClassnameAndId(Object widget, String classname,
						String id) {
					WidgetElement.setCSSClass((Widget) widget, classname);
					WidgetElement.setID((Widget) widget, id);
				}
			});
		} else if (cssTheme != null) {
			final IThemeEngine themeEngine = createThemeEngine(display, appContext);
			String cssResourcesURI = (String) appContext.get(IWorkbench.CSS_RESOURCE_URI_ARG);

			// Create the OSGi resource locator
			if (cssResourcesURI != null) {
				// TODO: Should this be set through an extension as well?
				themeEngine.registerResourceLocator(new OSGiResourceLocator(cssResourcesURI));
			}

			appContext.set(IStylingEngine.class, new IStylingEngine() {
				@Override
				public void setClassname(Object widget, String classname) {
					WidgetElement.setCSSClass((Widget) widget, classname);
					themeEngine.applyStyles(widget, true);
				}

				@Override
				public void setId(Object widget, String id) {
					WidgetElement.setID((Widget) widget, id);
					themeEngine.applyStyles(widget, true);
				}

				@Override
				public void style(Object widget) {
					themeEngine.applyStyles(widget, true);
				}

				@Override
				public CSSStyleDeclaration getStyle(Object widget) {
					return themeEngine.getStyle(widget);
				}

				@Override
				public void setClassnameAndId(Object widget, String classname, String id) {
					WidgetElement.setCSSClass((Widget) widget, classname);
					WidgetElement.setID((Widget) widget, id);
					themeEngine.applyStyles(widget, true);
				}
			});

			setCSSTheme(display, themeEngine, cssTheme);

		} else if (cssURI != null) {
			String cssResourcesURI = (String) appContext.get(IWorkbench.CSS_RESOURCE_URI_ARG);
			final CSSSWTEngineImpl cssEngine = new CSSSWTEngineImpl(display, true);
			WidgetElement.setEngine(display, cssEngine);
			if (cssResourcesURI != null) {
				cssEngine.getResourcesLocatorManager().registerResourceLocator(
						new OSGiResourceLocator(cssResourcesURI.toString()));
			}
			// FIXME: is this needed?
			display.setData("org.eclipse.e4.ui.css.context", appContext); //$NON-NLS-1$
			appContext.set(IStylingEngine.class, new IStylingEngine() {
				@Override
				public void setClassname(Object widget, String classname) {
					WidgetElement.setCSSClass((Widget) widget, classname);
					cssEngine.applyStyles(widget, true);
				}

				@Override
				public void setId(Object widget, String id) {
					WidgetElement.setID((Widget) widget, id);
					cssEngine.applyStyles(widget, true);
				}

				@Override
				public void style(Object widget) {
					cssEngine.applyStyles(widget, true);
				}

				@Override
				public CSSStyleDeclaration getStyle(Object widget) {
					Element e = cssEngine.getCSSElementContext(widget).getElement();
					if (e == null) {
						return null;
					}
					return cssEngine.getViewCSS().getComputedStyle(e, null);
				}

				@Override
				public void setClassnameAndId(Object widget, String classname, String id) {
					WidgetElement.setCSSClass((Widget) widget, classname);
					WidgetElement.setID((Widget) widget, id);
					cssEngine.applyStyles(widget, true);
				}
			});

			URL url;
			try {
				url = FileLocator.resolve(new URL(cssURI));
				try (InputStream stream = url.openStream()) {
					cssEngine.parseStyleSheet(stream);
				}
			} catch (IOException e) {
				Activator.log(LogService.LOG_ERROR, e.getMessage(), e);
			}

			Shell[] shells = display.getShells();
			for (Shell s : shells) {
				try {
					s.setRedraw(false);
					s.reskin(SWT.ALL);
					cssEngine.applyStyles(s, true);
				} catch (Exception e) {
					Activator.log(LogService.LOG_ERROR, e.getMessage(), e);
				} finally {
					s.setRedraw(true);
				}
			}
		}

		CSSRenderingUtils cssUtils = ContextInjectionFactory.make(CSSRenderingUtils.class, appContext);
		appContext.set(CSSRenderingUtils.class, cssUtils);
	}

	private static IThemeEngine createThemeEngine(Display display, IEclipseContext appContext) {
		// Store the app context
		IContributionFactory contribution = appContext.get(IContributionFactory.class);
		IEclipseContext cssContext = EclipseContextFactory.create();
		cssContext.set(IContributionFactory.class, contribution);
		display.setData("org.eclipse.e4.ui.css.context", cssContext); //$NON-NLS-1$

		IThemeManager mgr = appContext.get(IThemeManager.class);
		IThemeEngine themeEngine = mgr.getEngineForDisplay(display);

		appContext.set(IThemeEngine.class, themeEngine);
		return themeEngine;
	}

	private static void setCSSTheme(Display display, IThemeEngine themeEngine, String cssTheme) {
		if (display.getHighContrast()) {
			themeEngine.setTheme(cssTheme, false);
		} else {
			themeEngine.restore(cssTheme);
		}
	}

	public static class StylingPreferencesHandler implements EventHandler {
		private HashSet<IEclipsePreferences> prefs = null;

		public StylingPreferencesHandler(Display display) {
			if (display != null) {
				display.addListener(SWT.Dispose, createOnDisplayDisposedListener());
			}
		}

		protected Listener createOnDisplayDisposedListener() {
			return event -> resetOverriddenPreferences();
		}

		@Override
		public void handleEvent(Event event) {
			resetOverriddenPreferences();
			overridePreferences(getThemeEngine(event));
		}

		protected void resetOverriddenPreferences() {
			for (IEclipsePreferences preferences : getPreferences()) {
				resetOverriddenPreferences(preferences);
			}
		}

		protected void resetOverriddenPreferences(IEclipsePreferences preferences) {
			for (String name : getOverriddenPropertyNames(preferences)) {
				preferences.remove(name);
			}
			removeOverriddenPropertyNames(preferences);
		}

		protected void removeOverriddenPropertyNames(IEclipsePreferences preferences) {
			EclipsePreferencesHelper.removeOverriddenPropertyNames(preferences);
		}

		protected List<String> getOverriddenPropertyNames(IEclipsePreferences preferences) {
			return EclipsePreferencesHelper.getOverriddenPropertyNames(preferences);
		}

		protected Set<IEclipsePreferences> getPreferences() {
			if (prefs == null) {
				prefs = new HashSet<>();
				BundleContext context = WorkbenchSWTActivator.getDefault().getContext();
				for (Bundle bundle : context.getBundles()) {
					if (bundle.getSymbolicName() != null) {
						prefs.add(InstanceScope.INSTANCE.getNode(bundle.getSymbolicName()));
					}
				}
			}
			return prefs;
		}

		private void overridePreferences(IThemeEngine themeEngine) {
			if (themeEngine != null) {
				for (IEclipsePreferences preferences : getPreferences()) {
					themeEngine.applyStyles(preferences, false);
				}
			}
		}

		private IThemeEngine getThemeEngine(Event event) {
			return (IThemeEngine) event.getProperty(IThemeEngine.Events.THEME_ENGINE);
		}
	}
}
