| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2008, 2013 Technical University Berlin, Germany 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 |
| * |
| * 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.osgi.weaving.Activator.log; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.ACTIVATION; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.ASPECT_BINDING_EXTPOINT_ID; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.BASE_PLUGIN; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.CLASS; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.ID; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.REQUIRED_FRAGMENT; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.SELF; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.SUPERCLASS; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.TEAM; |
| import static org.eclipse.objectteams.osgi.weaving.Constants.TRANSFORMER_PLUGIN_ID; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.RegistryFactory; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.NonNullByDefault; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.objectteams.osgi.weaving.Constants; |
| import org.eclipse.objectteams.otequinox.hook.ILogger; |
| import org.objectteams.ITeam; |
| import org.osgi.framework.Bundle; |
| |
| /** |
| * An instance of this class holds the information loaded from extensions |
| * to the <code>aspectBindings</code> extension point. |
| * <p> |
| * Additionally it maintains dynamic data during the process of loading, |
| * instantiating and activating teams. |
| * </p> |
| */ |
| // 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 HashMap<String, ArrayList<AspectBinding>> aspectBindingsByBasePlugin = |
| new HashMap<String, ArrayList<AspectBinding>>(); |
| private static HashMap<String, ArrayList<AspectBinding>> aspectBindingsByAspectPlugin = |
| new HashMap<String, ArrayList<AspectBinding>>(); |
| private Set<String> selfAdaptingAspects= new HashSet<String>(); // TODO, never read / evaluated |
| |
| private HashMap<String, BaseBundleLoadTrigger> baseTripWires = new HashMap<>(); |
| |
| /** Record for one team waiting for instantiation/activation. */ |
| static class WaitingTeamRecord { |
| @Nullable Class<? extends ITeam> teamClass; // ... either this is set |
| @Nullable ITeam teamInstance; // ... or this |
| AspectBinding aspectBinding; |
| String notFoundClass; |
| |
| public WaitingTeamRecord(Class<? extends ITeam> teamClass, AspectBinding aspectBinding, String notFoundClass) { |
| this.teamClass = teamClass; |
| this.aspectBinding = aspectBinding; |
| this.notFoundClass = notFoundClass; |
| } |
| public WaitingTeamRecord(ITeam teamInstance, AspectBinding aspectBinding, String notFoundClass) { |
| this.teamInstance = teamInstance; |
| this.aspectBinding = aspectBinding; |
| this.notFoundClass = notFoundClass; |
| } |
| public WaitingTeamRecord(WaitingTeamRecord record, String notFoundClass) { |
| this.teamClass = record.teamClass; |
| this.teamInstance = record.teamInstance; |
| this.aspectBinding = record.aspectBinding; |
| this.notFoundClass = notFoundClass; |
| } |
| public @Nullable String getTeamName() { |
| final Class<? extends ITeam> clazz = teamClass; |
| if (clazz != null) { |
| return clazz.getName(); |
| } else { |
| final ITeam instance = teamInstance; |
| if (instance != null) |
| return instance.getClass().getName(); |
| } |
| return "<unknown team>"; |
| } |
| } |
| // records of teams that have been deferred due to unresolved class dependencies: |
| private List<WaitingTeamRecord> deferredTeams = new ArrayList<>(); |
| |
| 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(@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin packageAdmin) { |
| IConfigurationElement[] aspectBindingConfigs = RegistryFactory.getRegistry().getConfigurationElementsFor( |
| TRANSFORMER_PLUGIN_ID, ASPECT_BINDING_EXTPOINT_ID); |
| |
| for (int i = 0; i < aspectBindingConfigs.length; i++) { |
| IConfigurationElement currentBindingConfig = aspectBindingConfigs[i]; |
| |
| //aspect: |
| @SuppressWarnings("null")@NonNull String aspectBundleId= currentBindingConfig.getContributor().getName(); |
| IS_OTDT |= KNOWN_OTDT_ASPECTS.contains(aspectBundleId); |
| if (packageAdmin != null) { |
| @SuppressWarnings("deprecation") |
| Bundle[] aspectBundles = packageAdmin.getBundles(aspectBundleId, null); |
| if (aspectBundles == null || aspectBundles.length == 0 || (aspectBundles[0].getState() < Bundle.RESOLVED)) { |
| log(ILogger.ERROR, "aspect bundle "+aspectBundleId+" is not resolved - not loading aspectBindings."); |
| continue; |
| } |
| } |
| |
| //base: |
| IConfigurationElement[] basePlugins = currentBindingConfig.getChildren(BASE_PLUGIN); |
| if (basePlugins.length != 1) { |
| log(ILogger.ERROR, "aspectBinding of "+aspectBundleId+" must declare exactly one basePlugin"); |
| continue; |
| } |
| String baseBundleId = basePlugins[0].getAttribute(ID); |
| if (baseBundleId == null) { |
| log(ILogger.ERROR, "aspectBinding of "+aspectBundleId+" must specify the id of a basePlugin"); |
| continue; |
| } |
| |
| //base fragments? |
| IConfigurationElement[] fragments = basePlugins[0].getChildren(REQUIRED_FRAGMENT); |
| if (fragments != null |
| && !checkRequiredFragments(aspectBundleId, baseBundleId, fragments, packageAdmin)) // reported inside |
| continue; |
| |
| AspectBinding binding = new AspectBinding(aspectBundleId, baseBundleId, basePlugins[0].getChildren(Constants.FORCED_EXPORTS_ELEMENT)); |
| // TODO(SH): maybe enforce that every bundle id is given only once? |
| |
| //teams: |
| IConfigurationElement[] teams = currentBindingConfig.getChildren(TEAM); |
| binding.initTeams(teams.length); |
| try { |
| for (int j = 0; j < teams.length; j++) { |
| String teamClass = teams[j].getAttribute(CLASS); |
| binding.teamClasses[j] = teamClass; |
| String activation = teams[j].getAttribute(ACTIVATION); |
| binding.setActivation(j, activation); |
| } |
| |
| @NonNull String realBaseBundleId = baseBundleId.toUpperCase().equals(SELF) ? aspectBundleId : baseBundleId; |
| addBindingForBaseBundle(realBaseBundleId, binding); |
| addBindingForAspectBundle(aspectBundleId, binding); |
| if (!baseTripWires.containsKey(realBaseBundleId)) |
| baseTripWires.put(realBaseBundleId, new BaseBundleLoadTrigger(realBaseBundleId, this, packageAdmin)); |
| |
| |
| // now that binding.teamClasses is filled connect to super team, if requested: |
| for (int j = 0; j < teams.length; j++) { |
| String superTeamName = teams[j].getAttribute(SUPERCLASS); |
| if (superTeamName != null) |
| addSubTeam(aspectBundleId, binding.teamClasses[j], superTeamName); |
| } |
| log(ILogger.INFO, "registered:\n"+binding); |
| } catch (Throwable t) { |
| log(t, "Invalid aspectBinding extension"); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") // multiple uses of deprecated but still recommended class PackageAdmin |
| private boolean checkRequiredFragments(String aspectBundleId, String baseBundleId, IConfigurationElement[] fragments, |
| @Nullable org.osgi.service.packageadmin.PackageAdmin packageAdmin) |
| { |
| // checking only, no real action needed. |
| boolean hasError = false; |
| for (IConfigurationElement fragment : fragments) { |
| String fragId = fragment.getAttribute(ID); |
| if (fragId == null) { |
| log(ILogger.ERROR, "Mandatory attribute \"id\" missing from element \"requiredFragment\" of aspect binding in "+aspectBundleId); |
| return false; |
| } |
| if (packageAdmin == null) { |
| log(ILogger.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(ILogger.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(ILogger.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(ILogger.ERROR, "Required fragment " + fragId + " is not resolved" + aspectBindingHint); |
| hasError = true; |
| continue; |
| } |
| log(ILogger.ERROR, "Required fragment "+fragId+" has no host bundle"+aspectBindingHint); |
| hasError = true; |
| continue; |
| } |
| Bundle host = hosts[0]; |
| if (!host.getSymbolicName().equals(baseBundleId)) { |
| log(ILogger.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.basePlugin.toUpperCase().equals(SELF)) |
| selfAdaptingAspects.add(aspectBundleId); |
| } |
| |
| /** |
| * Record a sub-class relationship of two teams within the same aspect bundle. |
| * |
| * @param aspectBundleId |
| * @param subTeamName (nullable only until we have JSR 308) |
| * @param teamName |
| */ |
| private void addSubTeam(String aspectBundleId, @Nullable String subTeamName, String teamName) { |
| ArrayList<AspectBinding> bindingList = aspectBindingsByAspectPlugin.get(aspectBundleId); |
| if (bindingList == null) { |
| Exception e = new Exception("No such aspect binding"); |
| log(e, "Class "+teamName+" not registered (declared to be superclass of team "+subTeamName); |
| } else { |
| for (AspectBinding binding : bindingList) |
| if (binding.teamClasses != null) |
| for (int i=0; i < binding.teamClasses.length; i++) |
| if (binding.teamClasses[i].equals(teamName)) { |
| if (binding.subTeamClasses[i] == null) |
| binding.subTeamClasses[i] = new ArrayList<String>(); |
| binding.subTeamClasses[i].add(subTeamName); |
| return; |
| } |
| Exception e = new Exception("No such aspect binding"); |
| log(e, "Class "+teamName+" not registered(2) (declared to be superclass of team "+subTeamName); |
| } |
| } |
| |
| /** |
| * Given a potential aspect bundle, answer the symbolic names of all base bundles |
| * adapted by the aspect bundle. |
| */ |
| public @Nullable String[] getAdaptedBasePlugins(Bundle aspectBundle) { |
| ArrayList<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).basePlugin; |
| } |
| 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(); |
| } |
| |
| /** |
| * Get the list of aspect bindings affecting the given base plugin. |
| */ |
| public @Nullable List<AspectBinding> getAdaptingAspectBindings(@Nullable String basePluginName) { |
| return aspectBindingsByBasePlugin.get(basePluginName); |
| } |
| |
| /** Check if the given base bundle / base class mandate any loading/instantiation/activation of teams. */ |
| public void triggerLoadingHooks(@Nullable String bundleName, @Nullable String className) { |
| BaseBundleLoadTrigger activation = baseTripWires.get(bundleName); |
| if (activation != null) |
| activation.fire(className); |
| } |
| |
| /** Record the given team classes as waiting for instantiation/activation. */ |
| public void addDeferredTeamClasses(List<WaitingTeamRecord> teamClasses) { |
| synchronized (deferredTeams) { |
| deferredTeams.addAll(teamClasses); |
| } |
| } |
| |
| /** |
| * Try to instantiate/activate any deferred teams that may be unblocked |
| * by the definition of the given trigger class. |
| */ |
| public void instantiateScheduledTeams(String triggerClassName) { |
| List<WaitingTeamRecord> scheduledTeams = null; |
| synchronized(deferredTeams) { |
| for (WaitingTeamRecord record : new ArrayList<>(deferredTeams)) { |
| if (record.notFoundClass.equals(triggerClassName)) { |
| if (scheduledTeams == null) |
| scheduledTeams = new ArrayList<>(); |
| if (deferredTeams.remove(record)) |
| scheduledTeams.add(record); |
| } |
| } |
| } |
| if (scheduledTeams == null) return; |
| for(WaitingTeamRecord record : scheduledTeams) { |
| try { |
| new TeamLoader(deferredTeams).instantiateWaitingTeam(record); // may re-insert to deferredTeams |
| } catch (Exception e) { |
| log(e, "Failed to instantiate team "+record.getTeamName()); |
| continue; |
| } |
| } |
| } |
| } |