blob: 5478ab753ee6297a2e59caee72e3f41e49e189b9 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2008, 2015 Technical University Berlin, Germany 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
*
* Please visit http://www.objectteams.org for updates and contact.
*
* Contributors:
* Technical University Berlin - Initial API and implementation
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.internal.osgi.weaving;
import static org.eclipse.objectteams.otequinox.Constants.ACTIVATION;
import static org.eclipse.objectteams.otequinox.Constants.NONE;
import static org.eclipse.objectteams.otequinox.Constants.ASPECT_BINDING_EXTPOINT_ID;
import static org.eclipse.objectteams.otequinox.Constants.BASE_PLUGIN;
import static org.eclipse.objectteams.otequinox.Constants.CLASS;
import static org.eclipse.objectteams.otequinox.Constants.ID;
import static org.eclipse.objectteams.otequinox.Constants.REQUIRED_FRAGMENT;
import static org.eclipse.objectteams.otequinox.Constants.SELF;
import static org.eclipse.objectteams.otequinox.Constants.SUPERCLASS;
import static org.eclipse.objectteams.otequinox.Constants.TEAM;
import static org.eclipse.objectteams.otequinox.Constants.SUPER_BASE;
import static org.eclipse.objectteams.otequinox.Constants.SUPER_BASE_CLASS;
import static org.eclipse.objectteams.otequinox.Constants.SUPER_BASE_PLUGIN;
import static org.eclipse.objectteams.otequinox.Constants.TRANSFORMER_PLUGIN_ID;
import static org.eclipse.objectteams.otequinox.TransformerPlugin.log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.objectteams.internal.osgi.weaving.AspectBinding.BaseBundle;
import org.eclipse.objectteams.internal.osgi.weaving.AspectBinding.TeamBinding;
import org.eclipse.objectteams.internal.osgi.weaving.DelegatingTransformer.OTAgentNotInstalled;
import org.eclipse.objectteams.otequinox.Constants;
import org.osgi.framework.Bundle;
import org.osgi.framework.wiring.BundleWiring;
/**
* An instance of this class holds the information loaded from extensions
* to the <code>aspectBindings</code> extension point.
*/
// parts of this class are moved from org.eclipse.objectteams.otequinox.TransformerPlugin
@NonNullByDefault
public class AspectBindingRegistry {
private static List<String> KNOWN_OTDT_ASPECTS = new ArrayList<String>();
static {
KNOWN_OTDT_ASPECTS.add("org.eclipse.objectteams.otdt.jdt.ui");
KNOWN_OTDT_ASPECTS.add("org.eclipse.objectteams.otdt.compiler.adaptor");
KNOWN_OTDT_ASPECTS.add("org.eclipse.objectteams.otdt.refactoring");
KNOWN_OTDT_ASPECTS.add("org.eclipse.objectteams.otdt.pde.ui");
KNOWN_OTDT_ASPECTS.add("org.eclipse.objectteams.otdt.samples");
}
/** main internal registry of aspect bindings. */
private static Map<String, ArrayList<AspectBinding>> aspectBindingsByBasePlugin =
new HashMap<String, ArrayList<AspectBinding>>();
private static Map<String, ArrayList<AspectBinding>> aspectBindingsByAspectPlugin =
new HashMap<String, ArrayList<AspectBinding>>();
private Map<String, BaseBundle> baseBundleLookup = new HashMap<>();
private Set<String> selfAdaptingAspects= new HashSet<String>(); // TODO, never read / evaluated
private Set<String> allAllBoundBaseClasses = new HashSet<>(1024);
public static boolean IS_OTDT = false;
public boolean isOTDT() {
return IS_OTDT;
}
/* Load extensions for org.eclipse.objectteams.otequinox.aspectBindings and check aspect permissions. */
public void loadAspectBindings(
IExtensionRegistry extensionRegistry,
@SuppressWarnings("deprecation") org.osgi.service.packageadmin.@Nullable PackageAdmin packageAdmin,
OTWeavingHook hook) throws OTAgentNotInstalled
{
IConfigurationElement[] aspectBindingConfigs = extensionRegistry
.getConfigurationElementsFor(TRANSFORMER_PLUGIN_ID, ASPECT_BINDING_EXTPOINT_ID);
Map<String, Set<TeamBinding>> teamLookup = new HashMap<>();
List<AspectBinding> bindings = new ArrayList<>(aspectBindingConfigs.length);
for (int i = 0; i < aspectBindingConfigs.length; i++) {
IConfigurationElement currentBindingConfig = aspectBindingConfigs[i];
//aspect:
String aspectBundleId= currentBindingConfig.getContributor().getName();
IS_OTDT |= KNOWN_OTDT_ASPECTS.contains(aspectBundleId);
Bundle aspectBundle = null;
if (packageAdmin != null) {
@SuppressWarnings("deprecation")
Bundle[] aspectBundles = packageAdmin.getBundles(aspectBundleId, null);
if (aspectBundles == null || aspectBundles.length == 0 || (aspectBundles[0].getState() < Bundle.RESOLVED)) {
log(IStatus.ERROR, "aspect bundle "+aspectBundleId+" is not resolved - not loading aspectBindings.");
continue;
}
aspectBundle = aspectBundles[0];
}
//base:
IConfigurationElement[] basePlugins = currentBindingConfig.getChildren(BASE_PLUGIN);
if (basePlugins.length != 1) {
log(IStatus.ERROR, "aspectBinding of "+aspectBundleId+" must declare exactly one basePlugin");
continue;
}
String baseBundleId = basePlugins[0].getAttribute(ID);
if (baseBundleId == null) {
log(IStatus.ERROR, "aspectBinding of "+aspectBundleId+" must specify the id of a basePlugin");
continue;
}
BaseBundle baseBundle = getBaseBundle(baseBundleId);
//base fragments?
IConfigurationElement[] fragments = basePlugins[0].getChildren(REQUIRED_FRAGMENT);
if (!checkRequiredFragments(aspectBundleId, baseBundleId, fragments, packageAdmin)) // reported inside
continue;
IConfigurationElement[] teams = currentBindingConfig.getChildren(TEAM);
int teamCount = teams.length;
for (int j = 0; j < teams.length; j++) if (teams[j].getAttribute(CLASS) == null) teamCount --;
if (teamCount == 0) {
log(IStatus.WARNING, "aspectbinding of "+aspectBundleId+" for base "+baseBundleId+" defines no team class");
continue;
}
AspectBinding binding = new AspectBinding(aspectBundleId,
aspectBundle,
baseBundle,
basePlugins[0].getChildren(Constants.FORCED_EXPORTS_ELEMENT),
teamCount);
bindings.add(binding);
// TODO(SH): maybe enforce that every bundle id is given only once?
boolean isSelfAdaptation = baseBundleId.toUpperCase().equals(SELF);
//teams:
try {
for (int j = 0, count = 0; count < teamCount; j++) {
String teamClass = teams[j].getAttribute(CLASS);
if (teamClass == null) continue;
TeamBinding team = binding.createResolvedTeam(count++, teamClass, teams[j].getAttribute(ACTIVATION), teams[j].getAttribute(SUPERCLASS));
Set<TeamBinding> teamSet = teamLookup.get(teamClass);
if (teamSet == null)
teamLookup.put(teamClass, teamSet = new HashSet<>());
teamSet.add(team);
for (@NonNull IConfigurationElement superBase : teams[j].getChildren(SUPER_BASE)) {
AspectBinding superBaseBinding = addSuperBase(superBase, aspectBundleId, aspectBundle, baseBundle, team, packageAdmin, hook);
bindings.add(superBaseBinding);
@SuppressWarnings("null") // addSuperBase ensures a valid teams[0]
@NonNull TeamBinding superBaseTeam = superBaseBinding.teams[0];
teamSet.add(superBaseTeam);
}
}
String realBaseBundleId = isSelfAdaptation ? aspectBundleId : baseBundleId;
addBindingForBaseBundle(realBaseBundleId, binding);
addBindingForAspectBundle(aspectBundleId, binding);
hook.setBaseTripWire(packageAdmin, realBaseBundleId, baseBundle);
log(IStatus.INFO, "registered:\n"+binding);
} catch (OTAgentNotInstalled otani) {
throw otani;
} catch (Throwable t) {
log(t, "Invalid aspectBinding extension");
}
if (packageAdmin != null && aspectBundle != null && !binding.hasScannedTeams && !isSelfAdaptation) {
@SuppressWarnings("deprecation")
Bundle[] baseBundles = packageAdmin.getBundles(baseBundleId, null);
if (baseBundles == null || baseBundles.length == 0 || (baseBundles[0].getState() < Bundle.RESOLVED)) {
log(IStatus.ERROR, "base bundle "+baseBundleId+" is not resolved - weaving may be incomplete.");
} else {
@Nullable BundleWiring baseBundleWiring = baseBundles[0].adapt(BundleWiring.class);
assert baseBundleWiring != null : "Bundle should adapt to BundleWiring";
Collection<String> boundBases = binding.scanTeamClasses(aspectBundle, DelegatingTransformer.newTransformer(binding.weavingScheme, hook, baseBundleWiring));
addBoundBaseClasses(boundBases);
}
}
}
// second round to connect sub/super teams to aspect bindings:
for (AspectBinding binding : bindings) {
binding.connect(teamLookup);
}
}
private AspectBinding addSuperBase(IConfigurationElement superBase, String aspectBundleId, @Nullable Bundle aspectBundle,
BaseBundle baseBundle, TeamBinding teamBinding,
@SuppressWarnings("deprecation") org.osgi.service.packageadmin.@Nullable PackageAdmin packageAdmin,
OTWeavingHook hook) throws OTAgentNotInstalled
{
String superBaseClass = superBase.getAttribute(SUPER_BASE_CLASS);
String superBasePlugin = superBase.getAttribute(SUPER_BASE_PLUGIN);
if (superBaseClass == null) throw new IllegalArgumentException("superBase element must define 'class'");
BaseBundle superBaseBundle;
if (superBasePlugin == null) {
superBasePlugin = baseBundle.bundleName;
superBaseBundle = baseBundle;
} else {
superBaseBundle = getBaseBundle(superBasePlugin);
}
AspectBinding superBinding = new AspectBinding(aspectBundleId, aspectBundle, superBaseBundle, new IConfigurationElement[0], 1);
TeamBinding team2 = superBinding.createResolvedTeam(0, teamBinding.teamName, NONE, teamBinding.superTeamName);
superBinding.allBaseClassNames.add(superBaseClass);
team2.baseClassNames.add(superBaseClass);
addBindingForBaseBundle(superBasePlugin, superBinding);
addBindingForAspectBundle(aspectBundleId, superBinding);
hook.setBaseTripWire(packageAdmin, superBasePlugin, superBaseBundle);
teamBinding.superBases.add(superBasePlugin+'/'+superBaseClass);
return superBinding;
}
@SuppressWarnings("deprecation") // multiple uses of deprecated but still recommended class PackageAdmin
private boolean checkRequiredFragments(String aspectBundleId, String baseBundleId, IConfigurationElement[] fragments,
org.osgi.service.packageadmin.@Nullable PackageAdmin packageAdmin)
{
// checking only, no real action needed.
boolean hasError = false;
for (IConfigurationElement fragment : fragments) {
String fragId = fragment.getAttribute(ID);
if (fragId == null) {
log(IStatus.ERROR, "Mandatory attribute \"id\" missing from element \"requiredFragment\" of aspect binding in "+aspectBundleId);
return false;
}
if (packageAdmin == null) {
log(IStatus.ERROR, "Not checking required fragment "+fragId+" in aspect binding of "+aspectBundleId+", package admin service not present");
return false; // report only once.
}
Bundle[] fragmentBundles = packageAdmin.getBundles(fragId, null);
if (fragmentBundles == null || fragmentBundles.length == 0) {
log(IStatus.ERROR, "Required fragment "+fragId+" not found in aspect binding of "+aspectBundleId);
hasError = true;
continue;
}
Bundle fragmentBundle = fragmentBundles[0];
String aspectBindingHint = " (aspect binding of "+aspectBundleId+")";
if (packageAdmin.getBundleType(fragmentBundle) != org.osgi.service.packageadmin.PackageAdmin.BUNDLE_TYPE_FRAGMENT) {
log(IStatus.ERROR, "Required fragment " + fragId + " is not a fragment" + aspectBindingHint);
hasError = true;
continue;
}
Bundle[] hosts = packageAdmin.getHosts(fragmentBundle);
if (hosts == null || hosts.length == 0) {
if (fragmentBundle.getState() < Bundle.RESOLVED) {
log(IStatus.ERROR, "Required fragment " + fragId + " is not resolved" + aspectBindingHint);
hasError = true;
continue;
}
log(IStatus.ERROR, "Required fragment "+fragId+" has no host bundle"+aspectBindingHint);
hasError = true;
continue;
}
Bundle host = hosts[0];
if (!host.getSymbolicName().equals(baseBundleId)) {
log(IStatus.ERROR, "Required fragment "+fragId+" has wrong host "+host.getSymbolicName()+aspectBindingHint);
hasError = true;
}
}
return !hasError;
}
private static void addBindingForBaseBundle(String baseBundleId, AspectBinding binding) {
ArrayList<AspectBinding> bindingList = aspectBindingsByBasePlugin.get(baseBundleId);
if (bindingList == null) {
bindingList = new ArrayList<AspectBinding>();
aspectBindingsByBasePlugin.put(baseBundleId, bindingList);
}
bindingList.add(binding);
}
private void addBindingForAspectBundle(String aspectBundleId, AspectBinding binding) {
ArrayList<AspectBinding> bindingList = aspectBindingsByAspectPlugin.get(aspectBundleId);
if (bindingList == null) {
bindingList = new ArrayList<AspectBinding>();
aspectBindingsByAspectPlugin.put(aspectBundleId, bindingList);
}
bindingList.add(binding);
if (binding.basePluginName.toUpperCase().equals(SELF))
selfAdaptingAspects.add(aspectBundleId);
}
/**
* Given a potential aspect bundle, answer the symbolic names of all base bundles
* adapted by the aspect bundle.
*/
public String @Nullable[] getAdaptedBasePlugins(Bundle aspectBundle) {
List<AspectBinding> bindings = aspectBindingsByAspectPlugin.get(aspectBundle.getSymbolicName());
if (bindings == null) return null;
String[] basePlugins = new String[bindings.size()];
for (int i=0; i<basePlugins.length; i++) {
basePlugins[i] = bindings.get(i).basePluginName;
}
return basePlugins;
}
/** Is `symbolicName' the name of a base plugin for which an adapting team is registered? */
public boolean isAdaptedBasePlugin(@Nullable String symbolicName) {
ArrayList<AspectBinding> list = aspectBindingsByBasePlugin.get(symbolicName);
return list != null && !list.isEmpty();
}
/** Does className denote a class to which a role is bound via any aspectBinding? */
public boolean isBoundBaseClass(String className) {
return this.allAllBoundBaseClasses.contains(className);
}
/** Record the given class names as bound base classes. */
public void addBoundBaseClasses(Collection<String> boundBaseclassNames) {
this.allAllBoundBaseClasses.addAll(boundBaseclassNames);
}
/**
* Get the list of aspect bindings affecting the given base plugin.
*/
public @Nullable List<AspectBinding> getAdaptingAspectBindings(@Nullable String basePluginName) {
return aspectBindingsByBasePlugin.get(basePluginName);
}
public @Nullable List<AspectBinding> getAspectBindings(String aspectBundle) {
return aspectBindingsByAspectPlugin.get(aspectBundle);
}
public BaseBundle getBaseBundle(String bundleName) {
synchronized (baseBundleLookup) {
BaseBundle bundle = baseBundleLookup.get(bundleName);
if (bundle == null) {
bundle = new BaseBundle(bundleName);
baseBundleLookup.put(bundleName, bundle);
}
return bundle;
}
}
}