blob: d110bdb75418c10142e8e9ff7f83614d8f07945a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 BestSolution.at and others.
* 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:
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
******************************************************************************/
package org.eclipse.e4.ui.internal.workbench;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.RegistryFactory;
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.annotations.Execute;
import org.eclipse.e4.core.services.contributions.IContributionFactory;
import org.eclipse.e4.core.services.log.Logger;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.fragment.MModelFragment;
import org.eclipse.e4.ui.model.fragment.MModelFragments;
import org.eclipse.e4.ui.model.fragment.impl.FragmentPackageImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EContentsEList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.osgi.framework.Bundle;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.packageadmin.RequiredBundle;
/**
*
*/
public class ModelAssembler {
@Inject
private Logger logger;
@Inject
private MApplication application;
@Inject
private IEclipseContext context;
final private static String extensionPointID = "org.eclipse.e4.workbench.model"; //$NON-NLS-1$
/**
* Process the model
*/
public void processModel() {
IExtensionRegistry registry = RegistryFactory.getRegistry();
IExtensionPoint extPoint = registry.getExtensionPoint(extensionPointID);
IExtension[] extensions = topoSort(extPoint.getExtensions());
List<MApplicationElement> imports = new ArrayList<MApplicationElement>();
List<MApplicationElement> addedElements = new ArrayList<MApplicationElement>();
E4XMIResource applicationResource = (E4XMIResource) ((EObject) application).eResource();
ResourceSet resourceSet = applicationResource.getResourceSet();
for (IExtension extension : extensions) {
IConfigurationElement[] ces = extension.getConfigurationElements();
for (IConfigurationElement ce : ces) {
if (!"processor".equals(ce.getName()) || !Boolean.parseBoolean(ce.getAttribute("beforefragment"))) { //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
runProcessor(ce);
}
}
for (IExtension extension : extensions) {
IConfigurationElement[] ces = extension.getConfigurationElements();
for (IConfigurationElement ce : ces) {
if (!"fragment".equals(ce.getName())) { //$NON-NLS-1$
continue;
}
IContributor contributor = ce.getContributor();
String attrURI = ce.getAttribute("uri"); //$NON-NLS-1$
if (attrURI == null) {
logger.warn("Unable to find location for the model extension \"{0}\"", //$NON-NLS-1$
contributor.getName());
continue;
}
URI uri;
String bundleName = contributor.getName();
String path = bundleName + '/' + attrURI;
try {
uri = URI.createPlatformPluginURI(path, false);
} catch (RuntimeException e) {
logger.warn(e, "Model extension has invalid location"); //$NON-NLS-1$
continue;
}
String contributorURI = URIHelper.constructPlatformURI(contributor);
Resource resource;
try {
resource = resourceSet.getResource(uri, true);
} catch (RuntimeException e) {
logger.warn(e, "Unable to read model extension"); //$NON-NLS-1$
continue;
}
EList<?> contents = resource.getContents();
if (contents.isEmpty()) {
continue;
}
Object extensionRoot = contents.get(0);
if (!(extensionRoot instanceof MModelFragments)) {
logger.warn("Unable to create model extension \"{0}\"", //$NON-NLS-1$
contributor.getName());
continue;
}
MModelFragments fragmentsContainer = (MModelFragments) extensionRoot;
List<MModelFragment> fragments = fragmentsContainer.getFragments();
boolean evalImports = false;
for (MModelFragment fragment : fragments) {
List<MApplicationElement> elements = fragment.getElements();
if (elements.size() == 0) {
continue;
}
for (MApplicationElement el : elements) {
EObject o = (EObject) el;
E4XMIResource r = (E4XMIResource) o.eResource();
applicationResource.setID(o, r.getID(o));
if (contributorURI != null)
el.setContributorURI(contributorURI);
// Remember IDs of subitems
TreeIterator<EObject> treeIt = EcoreUtil.getAllContents(o, true);
while (treeIt.hasNext()) {
EObject eObj = treeIt.next();
r = (E4XMIResource) eObj.eResource();
if (contributorURI != null && (eObj instanceof MApplicationElement))
((MApplicationElement) eObj).setContributorURI(contributorURI);
applicationResource.setID(eObj, r.getInternalId(eObj));
}
}
List<MApplicationElement> merged = fragment.merge(application);
if (merged.size() > 0) {
evalImports = true;
addedElements.addAll(merged);
}
}
if (evalImports) {
List<MApplicationElement> localImports = fragmentsContainer.getImports();
if (localImports != null) {
imports.addAll(localImports);
}
}
}
}
for (IExtension extension : extensions) {
IConfigurationElement[] ces = extension.getConfigurationElements();
for (IConfigurationElement ce : ces) {
if (!"processor".equals(ce.getName()) || Boolean.parseBoolean(ce.getAttribute("beforefragment"))) { //$NON-NLS-1$ //$NON-NLS-2$
continue;
}
runProcessor(ce);
}
}
resolveImports(imports, addedElements);
}
private void runProcessor(IConfigurationElement ce) {
IEclipseContext localContext = EclipseContextFactory.create();
IContributionFactory factory = context.get(IContributionFactory.class);
for (IConfigurationElement ceEl : ce.getChildren("element")) { //$NON-NLS-1$
String id = ceEl.getAttribute("id"); //$NON-NLS-1$
if (id == null) {
logger.warn("No element id given"); //$NON-NLS-1$
continue;
}
String key = ceEl.getAttribute("contextKey"); //$NON-NLS-1$
if (key == null) {
key = id;
}
MApplicationElement el = findElementById(application, id);
if (el == null) {
logger.warn("Could not find element with id '" + id + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
localContext.set(key, el);
}
try {
Object o = factory
.create("bundleclass://" + ce.getContributor().getName() + "/" + ce.getAttribute("class"), //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
context, localContext);
ContextInjectionFactory.invoke(o, Execute.class, context, localContext);
} catch (Exception e) {
logger.warn(e, "Could not run processor"); //$NON-NLS-1$
}
}
private void resolveImports(List<MApplicationElement> imports,
List<MApplicationElement> addedElements) {
if (imports.isEmpty())
return;
// now that we have all components loaded, resolve imports
Map<MApplicationElement, MApplicationElement> importMaps = new HashMap<MApplicationElement, MApplicationElement>();
for (MApplicationElement importedElement : imports) {
MApplicationElement realElement = findElementById(application,
importedElement.getElementId());
if (realElement == null) {
logger.warn("Could not resolve an import element for '" + realElement + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
importMaps.put(importedElement, realElement);
}
TreeIterator<EObject> it = EcoreUtil.getAllContents(addedElements);
List<Runnable> commands = new ArrayList<Runnable>();
// TODO Probably use EcoreUtil.UsageCrossReferencer
while (it.hasNext()) {
EObject o = it.next();
EContentsEList.FeatureIterator<EObject> featureIterator = (EContentsEList.FeatureIterator<EObject>) o
.eCrossReferences().iterator();
while (featureIterator.hasNext()) {
EObject importObject = featureIterator.next();
if (importObject.eContainmentFeature() == FragmentPackageImpl.Literals.MODEL_FRAGMENTS__IMPORTS) {
EStructuralFeature feature = featureIterator.feature();
MApplicationElement el = importMaps.get(importObject);
if (el == null) {
logger.warn("Could not resolve import for " + el); //$NON-NLS-1$
}
final EObject interalTarget = o;
final EStructuralFeature internalFeature = feature;
final MApplicationElement internalElment = el;
final EObject internalImportObject = importObject;
commands.add(new Runnable() {
public void run() {
if (internalFeature.isMany()) {
System.err.println("Replacing"); //$NON-NLS-1$
List<Object> l = (List<Object>) interalTarget.eGet(internalFeature);
int index = l.indexOf(internalImportObject);
if (index >= 0) {
l.set(index, internalElment);
}
} else {
interalTarget.eSet(internalFeature, internalElment);
}
}
});
}
}
}
for (Runnable cmd : commands) {
cmd.run();
}
}
/**
* Sort the provided extensions by the dependencies of their contributors. Note that sorting is
* done in-place.
*
* @param extensions
* the list of extensions to be sorted
* @return the same list of extensions in a topologically-sorted order
*/
private IExtension[] topoSort(IExtension[] extensions) {
if (extensions.length == 0) {
return extensions;
}
PackageAdmin admin = Activator.getDefault().getBundleAdmin();
final Map<String, Collection<IExtension>> mappedExtensions = new HashMap<String, Collection<IExtension>>();
// Captures the bundles that are listed as requirements for a particular bundle.
final Map<String, Collection<String>> requires = new HashMap<String, Collection<String>>();
// Captures the bundles that list a particular bundle as a requirement
final Map<String, Collection<String>> depends = new HashMap<String, Collection<String>>();
// {@code requires} and {@code depends} define a graph where the vertices are
// bundleIds and the edges are the requires-relation. {@code requires} defines
// the out-edges for a vertex, and {@code depends} defines the in-edges for a vertex.
//
// Description of the algorithm:
// (1) build up the graph: we only record the bundles actually being considered
// (i.e., those that are contributors of {@code extensions})
// (2) sort the list of bundles by their out-degree: the bundles with the least
// out-edges are those that are depend on the fewest. If there is no bundles
// with 0 out-edges, then we must have a cycle; oh well, can't win them all.
// (3) take the bundle with lowest out-degree and add its extensions to the list.
// Remove the bundle from the list, and remove it from all of its dependents'
// required lists. This may require that the bundle list be resorted.
//
// Note this implementation assumes direct dependencies: if any of the bundles
// are dependent through a third bundle, then the ordering will fail. To prevent
// this would require recording the entire dependency subgraph for all contributors
// of the {@code extensions}.
// first build up the list of bundles actually being considered
for (IExtension extension : extensions) {
IContributor contributor = extension.getContributor();
Collection<IExtension> exts = mappedExtensions.get(contributor.getName());
if (exts == null) {
mappedExtensions.put(contributor.getName(), exts = new ArrayList<IExtension>());
}
exts.add(extension);
requires.put(contributor.getName(), new HashSet<String>());
depends.put(contributor.getName(), new HashSet<String>());
}
// now populate the dependency graph
for (String bundleId : mappedExtensions.keySet()) {
assert requires.containsKey(bundleId) && depends.containsKey(bundleId);
for (RequiredBundle requiredBundle : admin.getRequiredBundles(bundleId)) {
assert requiredBundle.getSymbolicName().equals(bundleId);
for (Bundle dependentBundle : requiredBundle.getRequiringBundles()) {
if (!mappedExtensions.containsKey(dependentBundle.getSymbolicName())) {
// not a contributor of an extension
continue;
}
String depBundleId = dependentBundle.getSymbolicName();
Collection<String> depBundleReqs = requires.get(depBundleId);
depBundleReqs.add(bundleId);
Collection<String> bundleDeps = depends.get(bundleId);
assert bundleDeps != null;
bundleDeps.add(depBundleId);
}
}
}
int resultIndex = 0;
// sort by out-degree ({@code depends})
// I suppose we could make {@code depends} a SortedMap, but we'd still need
// to explicitly resort anyways
List<String> sortedByOutdegree = new ArrayList<String>(requires.keySet());
Comparator<String> outdegreeSorter = new Comparator<String>() {
public int compare(String o1, String o2) {
assert requires.containsKey(o1) && requires.containsKey(o2);
return requires.get(o1).size() - requires.get(o2).size();
}
};
Collections.sort(sortedByOutdegree, outdegreeSorter);
if (!requires.get(sortedByOutdegree.get(0)).isEmpty()) {
logger.warn("Extensions have a cycle"); //$NON-NLS-1$
}
while (!sortedByOutdegree.isEmpty()) {
// don't sort unnecessarily: the current ordering is fine providing
// item #0 still has no dependencies
if (!requires.get(sortedByOutdegree.get(0)).isEmpty()) {
Collections.sort(sortedByOutdegree, outdegreeSorter);
}
String bundleId = sortedByOutdegree.remove(0);
assert depends.containsKey(bundleId) && requires.containsKey(bundleId);
for (IExtension ext : mappedExtensions.get(bundleId)) {
extensions[resultIndex++] = ext;
}
assert requires.get(bundleId).isEmpty();
requires.remove(bundleId);
for (String depId : depends.get(bundleId)) {
requires.get(depId).remove(bundleId);
}
depends.remove(bundleId);
}
assert resultIndex == extensions.length;
return extensions;
}
// FIXME Should we not reuse ModelUtils???
private static MApplicationElement findElementById(MApplicationElement element, String id) {
if (id == null || id.length() == 0)
return null;
// is it me?
if (id.equals(element.getElementId()))
return element;
// Recurse if this is a container
EList<EObject> elements = ((EObject) element).eContents();
for (EObject childElement : elements) {
if (!(childElement instanceof MApplicationElement))
continue;
MApplicationElement result = findElementById((MApplicationElement) childElement, id);
if (result != null)
return result;
}
return null;
}
}