| /********************************************************************** |
| * 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; |
| } |
| } |
| } |