Bug 406518 - migrate OT/Equinox to the standard OSGi WeavingHook
Refactored and added more null annotations
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBinding.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBinding.java
index e9b8ac3..b3988d6 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBinding.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBinding.java
@@ -28,6 +28,8 @@
 
 import org.eclipse.core.runtime.IConfigurationElement;
 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.otequinox.ActivationKind;
 import org.osgi.framework.Bundle;
@@ -37,14 +39,16 @@
  * @author stephan
  * @since 1.3.0 (was a nested class before that) 
  */
+@NonNullByDefault
 public class AspectBinding {
 	enum State { Initial, TeamsScanned, TeamsActivated };
 	
 	public String aspectPlugin;
 	public String basePlugin;
-	public IConfigurationElement[] forcedExports;
-	public ActivationKind[] activations = null; 
+	public @Nullable IConfigurationElement[] forcedExports;
+	// the following three are filled in lock-step:
 	public String[]         teamClasses;
+	public ActivationKind[] activations; 
 	public List<String>[]   subTeamClasses;
 
 	public State 			 state = State.Initial;
@@ -57,25 +61,23 @@
 	
 	Set<String> teamsInProgress = new HashSet<>(); // TODO cleanup teams that are done
 	
-	public AspectBinding(String aspectId, String baseId, IConfigurationElement[] forcedExportsConfs) {
+	@SuppressWarnings("unchecked")
+	public AspectBinding(String aspectId, String baseId, @Nullable IConfigurationElement[] forcedExportsConfs, int count) {
 		this.aspectPlugin= aspectId;
 		this.basePlugin= baseId;
 		this.forcedExports= forcedExportsConfs;
-	}
-	
-	@SuppressWarnings("unchecked")
-	public void initTeams(int count) {
 		this.teamClasses    = new String[count];
 		this.subTeamClasses = new List[count]; // new List<String>[count] is illegal!
 		this.activations    = new ActivationKind[count];
 	}
 	
-	public void setActivation(int i, String specifier) {
+	public void setActivation(int i, @Nullable String specifier) {
 		if (specifier == null)
 			this.activations[i] = ActivationKind.NONE;
 		else
 			this.activations[i] = ActivationKind.valueOf(specifier);
 	}
+
 	public String toString() {
 		String result = "\tbase plugin "+basePlugin+"\n\tadapted by aspect pluging "+aspectPlugin;
 		for (String teamClass : teamClasses) {
@@ -118,7 +120,7 @@
 	public ActivationKind getActivation(String teamClassName) {
 		for (int i=0; i<teamClasses.length; i++) {
 			if (teamClasses[i].equals(teamClassName))
-				return activations[i];
+				return activations[i]; // cannot declare array elements as nonnull
 		}
 		return ActivationKind.NONE;
 	}
@@ -126,7 +128,7 @@
 	/** Read OT attributes of all teams in aspectBinding and collect affected base classes. */
 	public synchronized void scanTeamClasses(Bundle bundle) {
 		ClassScanner scanner = new ClassScanner();
-		for (String teamName : getAllTeams()) {
+		for (@SuppressWarnings("null")@NonNull String teamName : getAllTeams()) {
 			try {
 				teamName = scanner.readOTAttributes(bundle, teamName);
 				Collection<String> baseClassNames = scanner.getCollectedBaseClassNames();
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBindingRegistry.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBindingRegistry.java
index 6b6e40c..5070380 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBindingRegistry.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectBindingRegistry.java
@@ -35,24 +35,17 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.IConfigurationElement;
-import org.eclipse.core.runtime.IStatus;
 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.otequinox.Constants;
 import org.eclipse.objectteams.otequinox.hook.ILogger;
-import org.objectteams.Team;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.hooks.weaving.WovenClass;
 
 /**
  * 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
@@ -73,52 +66,6 @@
 		       new HashMap<String, ArrayList<AspectBinding>>();
 	private Set<String> selfAdaptingAspects= new HashSet<String>(); // TODO, never read / evaluated
 	
-	private HashMap<String, BaseBundleLoadTrigger> baseTripWires = new HashMap<>();
-	
-	Set<String> beingDefined = new HashSet<>(); // shared with OTWeavingHook!
-
-	public AspectBindingRegistry(Set<String> beingDefined) {
-		this.beingDefined = beingDefined;
-	}
-
-	/** Record for one team waiting for instantiation/activation. */
-	static class WaitingTeamRecord {
-		@Nullable Class<? extends Team> teamClass; // ... either this is set
-		@Nullable Team teamInstance;	// ... or this
-		AspectBinding aspectBinding;
-		String notFoundClass;
-		
-		public WaitingTeamRecord(Class<? extends Team> teamClass, AspectBinding aspectBinding, String notFoundClass) {
-			this.teamClass = teamClass;
-			this.aspectBinding = aspectBinding;
-			this.notFoundClass = notFoundClass;
-		}
-		public WaitingTeamRecord(Team 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 Team> clazz = teamClass;
-			if (clazz != null) {
-				return clazz.getName();
-			} else {
-				final Team 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() {
@@ -126,7 +73,10 @@
 	}
 
 	/* Load extensions for org.eclipse.objectteams.otequinox.aspectBindings and check aspect permissions. */
-	public void loadAspectBindings(@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin packageAdmin) {
+	public void loadAspectBindings(
+			@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin packageAdmin,
+			OTWeavingHook hook) 
+	{
 		IConfigurationElement[] aspectBindingConfigs = RegistryFactory.getRegistry().getConfigurationElementsFor(
 				TRANSFORMER_PLUGIN_ID, ASPECT_BINDING_EXTPOINT_ID);
 		
@@ -163,12 +113,14 @@
 					&& !checkRequiredFragments(aspectBundleId, baseBundleId, fragments, packageAdmin)) // reported inside
 				continue;
 			
-			AspectBinding binding = new AspectBinding(aspectBundleId, baseBundleId, basePlugins[0].getChildren(Constants.FORCED_EXPORTS_ELEMENT));
+			IConfigurationElement[] teams = currentBindingConfig.getChildren(TEAM);
+			AspectBinding binding = new AspectBinding(aspectBundleId,
+														baseBundleId,
+														basePlugins[0].getChildren(Constants.FORCED_EXPORTS_ELEMENT), 
+														teams.length);
 			// 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);
@@ -180,8 +132,7 @@
 				@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));
+				hook.setBaseTripWire(packageAdmin, realBaseBundleId);
 
 				
 				// now that binding.teamClasses is filled connect to super team, if requested:
@@ -320,48 +271,4 @@
 	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 WovenClass baseClass) {
-		BaseBundleLoadTrigger activation = baseTripWires.get(bundleName);
-		if (activation != null) {
-			if (activation.fire(baseClass, beingDefined))
-				baseTripWires.remove(bundleName);
-		}
-	}
-
-	/** 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) {
-			log(IStatus.INFO, "Consider for instantiation/activation: team "+record.getTeamName());
-			try {
-				new TeamLoader(deferredTeams, beingDefined).instantiateWaitingTeam(record); // may re-insert to deferredTeams
-			} catch (Exception e) {
-				log(e, "Failed to instantiate team "+record.getTeamName());
-				continue;
-			}
-		}
-	}
 }
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/BaseBundleLoadTrigger.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/BaseBundleLoadTrigger.java
index 958f310..a3b8ff0 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/BaseBundleLoadTrigger.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/BaseBundleLoadTrigger.java
@@ -22,6 +22,9 @@
 import java.util.Set;
 
 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.State;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.hooks.weaving.WovenClass;
@@ -30,16 +33,18 @@
  * Each instance of this class represents the fact that a given base bundle has aspect bindings,
  * which require to load / instantiate / activate one or more teams at a suitable point in time.
  */
+@NonNullByDefault
 public class BaseBundleLoadTrigger {
 
 	private AspectBindingRegistry aspectBindingRegistry;
 	@SuppressWarnings("deprecation")
-	private org.osgi.service.packageadmin.PackageAdmin admin;
+	private @Nullable org.osgi.service.packageadmin.PackageAdmin admin;
 
 	private String baseBundleName;	
+	private boolean otreAdded = false;
 
 	public BaseBundleLoadTrigger(String bundleSymbolicName, AspectBindingRegistry aspectBindingRegistry, 
-			@SuppressWarnings("deprecation") org.osgi.service.packageadmin.PackageAdmin admin) 
+			@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin admin) 
 	{
 		this.baseBundleName = bundleSymbolicName;
 		this.aspectBindingRegistry = aspectBindingRegistry;
@@ -48,34 +53,58 @@
 	
 	/**
 	 * Signal that the given class is being loaded and trigger any necessary steps:
-	 * - scan team & add reverse imports (now)
-	 * - load & instantiate & activate (now or later).
+	 * (1) add import to OTRE (now)
+	 * (2) scan team (now)
+	 * (3) load & instantiate & activate (now or later).
 	 */
-	public boolean fire(WovenClass baseClass, Set<String> beingDefined) {
-		List<AspectBindingRegistry.WaitingTeamRecord> deferredTeamClasses = new ArrayList<>();
-		List<AspectBinding> aspectBindings = aspectBindingRegistry.getAdaptingAspectBindings(baseBundleName);
+	@SuppressWarnings("deprecation") // uses deprecated PackageAdmin
+	public boolean fire(WovenClass baseClass, Set<String> beingDefined, OTWeavingHook hook) {
+
+		// (1) OTRE import added once per base bundle:
+		synchronized(this) {
+			if (!otreAdded) {
+				otreAdded = true;
+				log(IStatus.INFO, "Adding OTRE import to "+baseBundleName);
+				List<String> imports = baseClass.getDynamicImports();
+				imports.add("org.objectteams");
+			}
+		}
+		
+		// for each team in each aspect binding:
 		boolean allDone = true;
+		List<AspectBinding> aspectBindings = aspectBindingRegistry.getAdaptingAspectBindings(baseBundleName);
 		if (aspectBindings != null) {
+			List<WaitingTeamRecord> deferredTeamClasses = new ArrayList<>();
 			for (AspectBinding aspectBinding : aspectBindings) {
 				if (aspectBinding.state == State.Initial)
 					log(IStatus.INFO, "Preparing aspect binding for base bundle "+baseBundleName);
 				if (aspectBinding.state == State.TeamsActivated)
 					continue;
-				@SuppressWarnings("deprecation")
-				Bundle[] aspectBundles = admin.getBundles(aspectBinding.aspectPlugin, null);
-				if (aspectBundles == null || aspectBundles.length == 0) {
+				final org.osgi.service.packageadmin.PackageAdmin admin2 = admin;
+				if (admin2 == null) {
 					log(IStatus.ERROR, "Cannot find aspect bundle "+aspectBinding.aspectPlugin);
-					continue;
+					continue;					
+				} else {
+					Bundle[] aspectBundles = admin2.getBundles(aspectBinding.aspectPlugin, null);
+					if (aspectBundles == null || aspectBundles.length == 0) {
+						log(IStatus.ERROR, "Cannot find aspect bundle "+aspectBinding.aspectPlugin);
+						continue;
+					}
+					// (2) scan all teams in affecting aspect bindings:
+					@SuppressWarnings("null") @NonNull
+					Bundle aspectBundle = aspectBundles[0];
+					if (aspectBinding.state != State.TeamsScanned)
+						aspectBinding.scanTeamClasses(aspectBundle);
+
+					// (3) try optional steps:
+					TeamLoader loading = new TeamLoader(deferredTeamClasses, beingDefined);
+					if (!loading.loadTeamsForBase(aspectBundle, aspectBinding, baseClass))
+						allDone = false;
 				}
-				Bundle aspectBundle = aspectBundles[0];
-				if (aspectBinding.state != State.TeamsScanned)
-					aspectBinding.scanTeamClasses(aspectBundle);
-				TeamLoader loading = new TeamLoader(deferredTeamClasses, beingDefined);
-				if (!loading.loadTeamsForBase(aspectBundle, aspectBinding, baseClass))
-					allDone = false;
 			}
+			// if some had to be deferred collect them now:
 			if (!deferredTeamClasses.isEmpty()) {
-				aspectBindingRegistry.addDeferredTeamClasses(deferredTeamClasses);
+				hook.addDeferredTeamClasses(deferredTeamClasses);
 				return false;
 			}
 		}
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ClassScanner.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ClassScanner.java
index f8ef150..0d48c5e 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ClassScanner.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ClassScanner.java
@@ -26,6 +26,9 @@
 import java.util.HashMap;
 import java.util.List;
 
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.objectteams.otequinox.hook.ILogger;
 import org.eclipse.objectteams.otre.jplis.ObjectTeamsTransformer;
 import org.eclipse.objectteams.otre.util.CallinBindingManager;
@@ -39,6 +42,7 @@
  * @since 1.2.0
  */
 @SuppressWarnings("nls")
+@NonNullByDefault
 public class ClassScanner 
 {
 	// default is "on", leave this switch for trouble shooting and profiling:
@@ -99,7 +103,7 @@
 	 * encountered while reading the byte code attributes.
 	 * (Destructive read). 
 	 */
-	public Collection<String> getCollectedBaseClassNames(String teamName) {
+	public @Nullable Collection<String> getCollectedBaseClassNames(String teamName) {
 		return this.baseClassNamesByTeam.remove(teamName);
 	}
 
@@ -126,13 +130,13 @@
 	/*
 	 * Recurse into member types scanning OT attributes.
 	 */
-	private void readMemberTypeAttributes(Bundle               bundle,
+	private void readMemberTypeAttributes(Bundle			     bundle,
 										  String                 className, 
 										  ObjectTeamsTransformer transformer)
 	{
 		List<String> roles = CallinBindingManager.getRolePerTeam(className);
 		if (roles != null) {
-			for (String roleName: roles) {
+			for (@SuppressWarnings("null")@NonNull String roleName: roles) {
 				log(ILogger.OK, "scanning role "+roleName);
 				try {
 					this.roleClassNames.add(roleName);
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/OTWeavingHook.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/OTWeavingHook.java
index 656530c..88544be 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/OTWeavingHook.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/OTWeavingHook.java
@@ -19,13 +19,16 @@
 
 import java.lang.instrument.IllegalClassFormatException;
 import java.security.ProtectionDomain;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.objectteams.otequinox.TransformerPlugin;
 import org.eclipse.objectteams.otequinox.hook.ILogger;
 import org.eclipse.objectteams.otre.jplis.ObjectTeamsTransformer;
@@ -56,20 +59,30 @@
  */
 public class OTWeavingHook implements WeavingHook, WovenClassListener {
 
-	private AspectBindingRegistry aspectBindingRegistry;
+	/** Interface to data about aspectBinding extensions. */
+	private @NonNull AspectBindingRegistry aspectBindingRegistry = new AspectBindingRegistry();
 	
-	@NonNull Set<String> beingDefined = new HashSet<>(); // shared with AspectBindingRegistry!
-	
+	/** Map of trip wires to be fired when a particular base bundle is loaded. */
+	private @NonNull HashMap<String, BaseBundleLoadTrigger> baseTripWires = new HashMap<>();
+
+	/** Set of classes for which processing has started but which are not yet defined in the class loader. */
+	private @NonNull Set<String> beingDefined = new HashSet<>();
+
+	/** Records of teams that have been deferred due to unresolved class dependencies: */
+	private @NonNull List<WaitingTeamRecord> deferredTeams = new ArrayList<>();
+
 	
 	/** Call-back from DS framework. */
 	public void activate(ComponentContext context) {
-		this.aspectBindingRegistry = loadAspectBindingRegistry(context.getBundleContext());
+		loadAspectBindingRegistry(context.getBundleContext());
 		TransformerPlugin.getDefault().registerAspectBindingRegistry(this.aspectBindingRegistry);
 	}
 
+	// ====== Aspect Binding: ======
+	
 	@SuppressWarnings("deprecation")
-	private AspectBindingRegistry loadAspectBindingRegistry(BundleContext context) {
-		org.osgi.service.packageadmin.PackageAdmin packageAdmin = null;;
+	private void loadAspectBindingRegistry(BundleContext context) {
+		org.osgi.service.packageadmin.PackageAdmin packageAdmin = null;
 		
 		ServiceReference<?> ref= context.getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
 		if (ref!=null)
@@ -77,11 +90,32 @@
 		else
 			log(ILogger.ERROR, "Failed to load PackageAdmin service. Will not be able to handle fragments.");
 
-		AspectBindingRegistry aspectBindingRegistry = new AspectBindingRegistry(this.beingDefined);
-		aspectBindingRegistry.loadAspectBindings(packageAdmin);
-		return aspectBindingRegistry;
+		aspectBindingRegistry.loadAspectBindings(packageAdmin, this);
 	}
 
+	// ====== Base Bundle Trip Wires: ======
+	
+	/**
+	 * Callback during AspectBindingRegistry#loadAspectBindings():
+	 * Set-up a trip wire to fire when the mentioned base bundle is loaded.
+	 */
+	void setBaseTripWire(@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin packageAdmin, @NonNull String baseBundleId) 
+	{
+		if (!baseTripWires.containsKey(baseBundleId))
+			baseTripWires.put(baseBundleId, new BaseBundleLoadTrigger(baseBundleId, aspectBindingRegistry, packageAdmin));
+	}
+
+	/** Check if the given base bundle / base class mandate any loading/instantiation/activation of teams. */
+	void triggerBaseTripWires(@Nullable String bundleName, @NonNull WovenClass baseClass) {
+		BaseBundleLoadTrigger activation = baseTripWires.get(bundleName);
+		if (activation != null) {
+			if (activation.fire(baseClass, beingDefined, this))
+				baseTripWires.remove(bundleName);
+		}
+	}
+
+	// ====== Main Weaving Entry: ======
+
 	@Override
 	public void weave(WovenClass wovenClass) {
 		beingDefined.add(wovenClass.getClassName());
@@ -91,10 +125,11 @@
 			String bundleName = bundleWiring.getBundle().getSymbolicName();
 			String className = wovenClass.getClassName();
 			
-			// do whatever is needed *before* loading this class:
-			aspectBindingRegistry.triggerLoadingHooks(bundleName, wovenClass);
 			
 			if (requiresWeaving(bundleWiring)) {
+				// do whatever is needed *before* loading this class:
+				triggerBaseTripWires(bundleName, wovenClass);
+
 				ObjectTeamsTransformer transformer = new ObjectTeamsTransformer();
 				Class<?> classBeingRedefined = null; // TODO
 				ProtectionDomain protectionDomain = wovenClass.getProtectionDomain();
@@ -106,11 +141,6 @@
 					if (newBytes != bytes && !Arrays.equals(newBytes, bytes)) {
 						log(IStatus.INFO, "Transformation performed on "+className);
 						wovenClass.setBytes(newBytes);
-						if (otreAdded.add(bundleWiring.getBundle())) {
-							log(IStatus.INFO, "Adding OTRE import to "+bundleName);
-							List<String> imports = wovenClass.getDynamicImports();
-							imports.add("org.objectteams");
-						}
 					}
 				} catch (IllegalClassFormatException e) {
 					log(e, "Failed to transform class "+className);
@@ -120,22 +150,60 @@
 			log(cce, "Weaver encountered a circular class dependency");
 		}
 	}
-	
-	Set<Bundle> otreAdded = new HashSet<>();
-	@Override
-	public void modified(WovenClass wovenClass) {
-		if (wovenClass.getState() == WovenClass.DEFINED) {
-			beingDefined.remove(wovenClass.getClassName());
-			@SuppressWarnings("null") @NonNull String className = wovenClass.getClassName();
-			aspectBindingRegistry.instantiateScheduledTeams(className);
-		}
-	}
 
-	private boolean requiresWeaving(BundleWiring bundleWiring) {
+	boolean requiresWeaving(BundleWiring bundleWiring) {
 		@SuppressWarnings("null")@NonNull
 		Bundle bundle = bundleWiring.getBundle();
 		return aspectBindingRegistry.getAdaptedBasePlugins(bundle) != null
 				|| aspectBindingRegistry.isAdaptedBasePlugin(bundle.getSymbolicName());
 	}
 
+	// ===== handling deferred teams: ======
+
+	/**
+	 * Record the given team classes as waiting for instantiation/activation.
+	 * Callback during {@link BaseBundleLoadTrigger#fire()}
+	 */
+	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) {
+			log(IStatus.INFO, "Consider for instantiation/activation: team "+record.getTeamName());
+			try {
+				new TeamLoader(deferredTeams, beingDefined).instantiateWaitingTeam(record); // may re-insert to deferredTeams
+			} catch (Exception e) {
+				log(e, "Failed to instantiate team "+record.getTeamName());
+				continue;
+			}
+		}
+	}
+
+	@Override
+	public void modified(WovenClass wovenClass) {
+		if (wovenClass.getState() == WovenClass.DEFINED) {
+			beingDefined.remove(wovenClass.getClassName());
+			@SuppressWarnings("null") @NonNull String className = wovenClass.getClassName();
+			instantiateScheduledTeams(className);
+		}
+	}
 }
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/Pair.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/Pair.java
index 947c35d..516fa33 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/Pair.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/Pair.java
@@ -15,6 +15,9 @@
  **********************************************************************/
 package org.eclipse.objectteams.internal.osgi.weaving;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+@NonNullByDefault
 class Pair<A, B> {
 	public A first;
 	public B second;
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/TeamLoader.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/TeamLoader.java
index 99ef94a..c518984 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/TeamLoader.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/TeamLoader.java
@@ -24,8 +24,9 @@
 import java.util.Set;
 
 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.AspectBindingRegistry.WaitingTeamRecord;
 import org.eclipse.objectteams.otequinox.ActivationKind;
 import org.eclipse.objectteams.otequinox.TransformerPlugin;
 import org.eclipse.objectteams.otequinox.hook.ILogger;
@@ -45,12 +46,11 @@
  * {@link #deferredTeams}.
  * </p>
  */
+@NonNullByDefault
 public class TeamLoader {
 
+	/** Collect here any teams that cannot yet be handled and should be scheduled later. */
 	private List<WaitingTeamRecord> deferredTeams;
-	
-	/** did we record the fact that a team needs deferring? */
-	boolean needDeferring;
 
 	private Set<String> beingDefined; 
 	
@@ -65,8 +65,10 @@
 	 * and also adds a reverse import to the base.
 	 */
 	public boolean loadTeamsForBase(Bundle aspectBundle, AspectBinding aspectBinding, WovenClass baseClass) {
-		Collection<String> teamsForBase = aspectBinding.getTeamsForBase(baseClass.getClassName());
-		if (teamsForBase == null) return false;
+		@SuppressWarnings("null")@NonNull String className = baseClass.getClassName();
+		Collection<String> teamsForBase = aspectBinding.getTeamsForBase(className);
+		if (teamsForBase == null) 
+			return false; // not done
 		List<String> imports = baseClass.getDynamicImports();
 		for (String teamForBase : teamsForBase) {
 			// Add dependency:
@@ -83,17 +85,15 @@
 				log(new ClassNotFoundException("Not found: "+teamForBase), "Failed to load team "+teamForBase);
 				continue;
 			}
-			// Instantiate?
+			// Try to instantiate & activate, failures are recorded in deferredTeams
 			ActivationKind activationKind = aspectBinding.getActivation(teamForBase);
 			if (activationKind == ActivationKind.NONE)
 				continue;
-			Team teamInstance = instantiateTeam(aspectBinding, teamClass, teamForBase);
+			Team teamInstance = instantiateAndActivate(aspectBinding, teamClass, teamForBase, activationKind);
 			if (teamInstance == null)
 				continue;
-			// Activate?
-			activateTeam(aspectBinding, teamForBase, teamInstance, activationKind);
 		}
-		return true;
+		return true; // all activatable teams have been activated or added to deferredTeams
 	}
 
 	/** Team loading, subsequent attempts. */
@@ -104,16 +104,13 @@
 		String teamName = record.getTeamName();
 		if (teamInstance == null) {
 			// Instantiate (we only get here if activationKind != NONE)
-			teamInstance = instantiateTeam(record.aspectBinding, record.teamClass, teamName);
-			if (teamInstance == null)
-				return;
+			Class<? extends Team> teamClass = record.teamClass;
+			assert teamClass != null : "cannot be null if teamInstance is null";
+			teamInstance = instantiateAndActivate(record.aspectBinding, teamClass, teamName, record.activationKind);
 		}
-		// Activate?
-		ActivationKind activationKind = record.aspectBinding.getActivation(teamName);
-		activateTeam(record.aspectBinding, teamName, teamInstance, activationKind);
 	}
 
-	public static Pair<URL,String> findTeamClassResource(String className, Bundle bundle) {
+	public static @Nullable Pair<URL,String> findTeamClassResource(String className, Bundle bundle) {
 		for (String candidate : possibleTeamNames(className)) {
 			URL result = bundle.getResource(candidate.replace('.', '/')+".class");
 			if (result != null)
@@ -123,7 +120,7 @@
 	}
 
 	@SuppressWarnings("unchecked")
-	public static Class<? extends Team> findTeamClass(String className, Bundle bundle) {
+	public static @Nullable Class<? extends Team> findTeamClass(String className, Bundle bundle) {
 		for (String candidate : possibleTeamNames(className)) {
 			try {
 				Class<?> result = bundle.loadClass(candidate);
@@ -171,21 +168,37 @@
 		return result;
 	}
 
-	private @Nullable Team instantiateTeam(AspectBinding aspectBinding, Class<? extends Team> teamClass, String teamName) {
+	@Nullable Team instantiateAndActivate(AspectBinding aspectBinding, Class<? extends Team> teamClass, String teamName, ActivationKind activationKind) 
+	{
 		// don't try to instantiate before all base classes successfully loaded.
-		if (!isReadyToLoad(aspectBinding, teamClass, null, teamName))
+		if (!isReadyToLoad(aspectBinding, teamClass, null, teamName, activationKind))
 			return null;
 
 		try {
 			Team instance = teamClass.newInstance();
 			TransformerPlugin.registerTeamInstance(instance);
 			log(ILogger.INFO, "Instantiated team "+teamName);
-			return instance;
-		} catch (NoClassDefFoundError ncdfe) {
-			needDeferring = true;
-			synchronized(deferredTeams) {
-				deferredTeams.add(new WaitingTeamRecord(teamClass, aspectBinding, ncdfe.getMessage().replace('/','.')));
+			
+			try {
+				switch (activationKind) {
+				case ALL_THREADS:
+					instance.activate(Team.ALL_THREADS);
+					log(IStatus.INFO, "Activated team "+teamName);
+					break;
+				case THREAD:
+					instance.activate();
+					log(IStatus.INFO, "Activated team "+teamName);
+					break;
+				//$CASES-OMITTED$
+				default:
+					break;
+				}
+			} catch (Throwable t) {
+				// application errors during activation
+				log(t, "Failed to activate team "+teamName);
 			}
+
+			return instance;
 		} catch (Throwable e) {
 			// application error during constructor execution?
 			log(e, "Failed to instantiate team "+teamName);
@@ -193,38 +206,16 @@
 		return null;
 	}
 
-	private void activateTeam(AspectBinding aspectBinding, String teamName, Team teamInstance, ActivationKind activationKind)
+	boolean isReadyToLoad(AspectBinding aspectBinding,
+			Class<? extends Team> teamClass, @Nullable Team teamInstance,
+			String teamName, ActivationKind activationKind)
 	{
-		// don't try to activate before all base classes successfully loaded.
-		if (!isReadyToLoad(aspectBinding, teamInstance.getClass(), teamInstance, teamName))
-			return;
-		// good to go, so go:
-		try {
-			switch (activationKind) {
-			case ALL_THREADS:
-				teamInstance.activate(Team.ALL_THREADS);
-				log(IStatus.INFO, "Activated team "+teamName);
-				break;
-			case THREAD:
-				teamInstance.activate();
-				log(IStatus.INFO, "Activated team "+teamName);
-				break;
-			//$CASES-OMITTED$
-			default:
-				break;
-			}
-		} catch (Throwable t) {
-			// application errors during activation
-			log(t, "Failed to activate team "+teamName);
-		}
-	}
-	boolean isReadyToLoad(AspectBinding aspectBinding, Class<? extends Team> teamClass, Team teamInstance, String teamName) {
-		for (String baseclass : aspectBinding.basesPerTeam.get(teamName)) {
+		for (@SuppressWarnings("null")@NonNull String baseclass : aspectBinding.basesPerTeam.get(teamName)) {
 			if (this.beingDefined.contains(baseclass)) {
 				synchronized (deferredTeams) {
 					WaitingTeamRecord record = teamInstance != null
-							? new WaitingTeamRecord(teamInstance, aspectBinding, baseclass)
-							: new WaitingTeamRecord(teamClass, aspectBinding, baseclass);
+							? new WaitingTeamRecord(teamInstance, aspectBinding, activationKind, baseclass)
+							: new WaitingTeamRecord(teamClass, aspectBinding, activationKind, baseclass);
 					deferredTeams.add(record); // TODO(SH): synchronization, deadlock?
 				}
 				log(IStatus.INFO, "Defer instantation/activation of team "+teamName);
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/WaitingTeamRecord.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/WaitingTeamRecord.java
new file mode 100644
index 0000000..cfc7756
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/WaitingTeamRecord.java
@@ -0,0 +1,63 @@
+/**********************************************************************
+ * This file is part of "Object Teams Development Tooling"-Software
+ * 
+ * Copyright 2013 GK Software AG
+ *  
+ * 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:
+ * 	Stephan Herrmann - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.internal.osgi.weaving;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.objectteams.otequinox.ActivationKind;
+import org.objectteams.Team;
+
+/** Record for one team waiting for instantiation/activation. */
+@NonNullByDefault
+class WaitingTeamRecord {
+	@Nullable Class<? extends Team> teamClass; // ... either this is set
+	@Nullable Team teamInstance;				// ... or this
+	AspectBinding aspectBinding;
+	ActivationKind activationKind;
+	String notFoundClass;
+	
+	public WaitingTeamRecord(Class<? extends Team> teamClass, AspectBinding aspectBinding, ActivationKind activationKind, String notFoundClass) {
+		this.teamClass = teamClass;
+		this.aspectBinding = aspectBinding;
+		this.notFoundClass = notFoundClass;
+		this.activationKind = activationKind;
+	}
+	public WaitingTeamRecord(Team teamInstance, AspectBinding aspectBinding, ActivationKind activationKind, String notFoundClass) {
+		this.teamInstance = teamInstance;
+		this.aspectBinding = aspectBinding;
+		this.notFoundClass = notFoundClass;
+		this.activationKind = activationKind;
+	}
+	public WaitingTeamRecord(WaitingTeamRecord record, String notFoundClass) {
+		this.teamClass = record.teamClass;
+		this.teamInstance = record.teamInstance;
+		this.aspectBinding = record.aspectBinding;
+		this.activationKind = record.activationKind;
+		this.notFoundClass = notFoundClass;
+	}
+	@SuppressWarnings("null") // calling well-known library functions
+	public String getTeamName() {
+		final Class<? extends Team> clazz = teamClass;
+		if (clazz != null) {
+			return clazz.getName();
+		} else {
+			final Team instance = teamInstance;
+			if (instance != null)
+				return instance.getClass().getName();
+		}
+		return "<unknown team>";
+	}		
+}
\ No newline at end of file