Bug 406518 - migrate OT/Equinox to the standard OSGi WeavingHook
- replace strings with better abstractions ResolvedTeam & BaseBundle
- connect those during AspectBindingRegistry.loadAspectBindings()
- maintain an equivalenceSet of TeamBindings (from diff. aspectBindings)
- let objects keep track of what work has already been done
- more complete adding of imports (consider declared team inheritance)
diff --git a/plugins/org.eclipse.objectteams.otequinox/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.objectteams.otequinox/.settings/org.eclipse.jdt.core.prefs
index 718115f..11f0a13 100644
--- a/plugins/org.eclipse.objectteams.otequinox/.settings/org.eclipse.jdt.core.prefs
+++ b/plugins/org.eclipse.objectteams.otequinox/.settings/org.eclipse.jdt.core.prefs
@@ -60,7 +60,7 @@
 org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
 org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
 org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
 org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
 org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
 org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
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 f0fa490..06a243d 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
@@ -19,11 +19,12 @@
 import static org.eclipse.objectteams.otequinox.TransformerPlugin.log;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 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;
@@ -32,131 +33,304 @@
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.objectteams.otequinox.ActivationKind;
+import org.objectteams.Team;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.hooks.weaving.WovenClass;
 
 /** 
- * A simple record representing the information read from an extension to org.eclipse.objectteams.otequinox.aspectBindings.
+ * Each instance of this class represents the information read from one extension to org.eclipse.objectteams.otequinox.aspectBindings.
+ * Already during {@link AspectBindingRegistry#loadAspectBindings()} the string based information is resolved into instances
+ * of {@link TeamBinding} and {@link BaseBundle}.
  * @author stephan
  * @since 1.3.0 (was a nested class before that) 
  */
 @NonNullByDefault
 public class AspectBinding {
-	enum State { Initial, TeamsScanned, TeamsActivated };
+
+	class TeamBinding {
+
+		String teamName;
+		@Nullable Class<? extends Team> teamClass;
+
+		@Nullable String superTeamName;
+		@Nullable TeamBinding superTeam;
+		final List<TeamBinding> subTeams = new ArrayList<>();
+		Set<TeamBinding> equivalenceSet = new HashSet<>();
+
+		ActivationKind activation;
+		boolean isActivated;
+
+		boolean importsAdded;
+		boolean importsAddedToSuper;
+		boolean importsAddedToSub;
+
+		final List<String> baseClassNames = new ArrayList<>();
+
+		public TeamBinding(String teamName, ActivationKind activationKind, @Nullable String superTeamName) {
+			this.teamName = teamName;
+			this.activation = activationKind;
+			this.superTeamName = superTeamName;
+			this.equivalenceSet.add(this);
+		}
+
+		/** After scanning class file attributes: add the names of all bound base classes. */
+		public void addBaseClassNames(Collection<String> baseClassNames) {
+			this.baseClassNames.addAll(baseClassNames);
+			for (String baseClassName : baseClassNames) {
+				Set<TeamBinding> teams = baseBundle.teamsPerBase.get(baseClassName);
+				if (teams == null)
+					baseBundle.teamsPerBase.put(baseClassName, teams = new HashSet<>());
+				teams.add(this);
+			}
+		}
+
+		@SuppressWarnings("unchecked")
+		public @Nullable Class<? extends Team> loadTeamClass(Bundle fallbackBundle) {
+			if (teamClass != null) return teamClass;
+			for (String candidate : TeamLoader.possibleTeamNames(teamName)) {
+				try {
+					Bundle aspectBundle = AspectBinding.this.aspectBundle;
+					// FIXME: no aspectBundle if no PackageAdmin was found, is using the fallbackBundle OK?
+					if (aspectBundle == null)
+						aspectBundle = fallbackBundle;
+					Class<?> result = aspectBundle.loadClass(candidate);
+					if (result != null)
+						return this.teamClass = (Class<? extends Team>) result;
+				} catch (NoClassDefFoundError|ClassNotFoundException e) {
+					e.printStackTrace();
+					// keep looking
+				}
+			}
+			return null;
+		}
+
+		/**
+		 * Add imports to this team's package into the bundle of the given base class.
+		 * @param baseClass
+		 * @param direction 1 means traveling to supers, -1 means traveling to subs, 0 comprises both
+		 */
+		public void addImportTo(WovenClass baseClass, int direction) {
+			importsAdded = true;
+			List<String> imports = baseClass.getDynamicImports();
+			String packageOfTeam = "";
+			int dot = teamName.lastIndexOf('.'); // TODO: can we detect if thats really the package (vs. Outer.Inner)?
+			if (dot != -1)
+				packageOfTeam = teamName.substring(0, dot);
+			imports.add(packageOfTeam);
+			log(IStatus.INFO, "Added dependency from base "+baseClass.getClassName()+" to package '"+packageOfTeam+"'");
+			if (direction != -1) {
+				importsAddedToSuper = true;
+				final TeamBinding superTeam2 = superTeam;
+				if (superTeam2 != null)
+					superTeam2.addImportTo(baseClass, 1);
+			}
+			if (direction != 1) {
+				importsAddedToSub = true;
+				for (TeamBinding subTeam : subTeams)
+					subTeam.addImportTo(baseClass, -1);
+			}
+		}
+
+		/** Has all work for this team been done? */
+		public boolean isDone() { // TODO travel up/down?
+			if (activation != ActivationKind.NONE && !isActivated)
+				return false;
+			if (!(importsAdded && importsAddedToSub && importsAddedToSuper))
+				return false;
+			return true;
+		}
+
+		@Override
+		public String toString() {
+			return "team "+teamName+"("+(this.activation)+") super "+superTeamName;
+		}
+	}
+	
+	/**
+	 * Represents a base bundle against which one or more aspectBindings are declared.
+	 * Used to find all teams affecting any given base class in this base bundle.
+	 */
+	static class BaseBundle {
+		String bundleName;
+		/** Team classes indexed by base classes that should trigger activating the team. */
+		private HashMap<String, Set<TeamBinding>> teamsPerBase = new HashMap<>();		
+
+		public BaseBundle(String bundleName) {
+			this.bundleName = bundleName;
+		}
+	}
 	
 	public String aspectPlugin;
-	public String basePlugin;
-	public @Nullable IConfigurationElement[] forcedExports;
-	// the following three are filled in lock-step:
-	public String[]         teamClasses;
-	public ActivationKind[] activations; 
-	public List<String>[]   subTeamClasses;
-	public boolean[]		 isActivated;
+	public @Nullable Bundle aspectBundle; // null if we don't have a PackageAdmin for bundle lookup
+	public String basePluginName;
+	public BaseBundle baseBundle;
+	public @Nullable IConfigurationElement[] forcedExports; // not yet evaluated
+	public TeamBinding[]   teams;
 
-	public State 			 state = State.Initial;
+	public boolean hasScannedTeams;
 	
-	/** Dispenser for team classes indexed by base classes that should trigger activating the team. */
-	private HashMap<String, Set<String>> teamsPerBase = new HashMap<>();
+	Set<TeamBinding> teamsInProgress = new HashSet<>(); // TODO cleanup teams that are done
 	
-	/** Lookup to find base classes affected by a team (need to be available before instantiate/activate). */
-	HashMap<String, Collection<String>> basesPerTeam = new HashMap<>();
-	
-	Set<String> teamsInProgress = new HashSet<>(); // TODO cleanup teams that are done
-	
-	@SuppressWarnings("unchecked")
-	public AspectBinding(String aspectId, String baseId, @Nullable IConfigurationElement[] forcedExportsConfs, int count) {
+	public AspectBinding(String aspectId, @Nullable Bundle aspectBundle, BaseBundle baseBundle, @Nullable IConfigurationElement[] forcedExportsConfs, int count) 
+	{
 		this.aspectPlugin= aspectId;
-		this.basePlugin= baseId;
+		this.aspectBundle= aspectBundle;
+		this.baseBundle=     baseBundle;
+		this.basePluginName= baseBundle.bundleName;
 		this.forcedExports= forcedExportsConfs;
-		this.teamClasses    = new String[count];
-		this.subTeamClasses = new List[count]; // new List<String>[count] is illegal!
-		this.activations    = new ActivationKind[count];
-		this.isActivated    = new boolean[count]; 
-	}
-	
-	public void setActivation(int i, @Nullable String specifier) {
-		if (specifier == null)
-			this.activations[i] = ActivationKind.NONE;
-		else
-			this.activations[i] = ActivationKind.valueOf(specifier);
+		
+		this.teams = new TeamBinding[count];
 	}
 
-	public String toString() {
-		String result = "\tbase plugin "+basePlugin+"\n\tadapted by aspect pluging "+aspectPlugin;
-		for (String teamClass : teamClasses) {
-			result += "\n\t\t + team "+teamClass;
+	/** Create an initial (unconnected) resolved team binding. */
+	public TeamBinding createResolvedTeam(int count, String teamName, @Nullable String activationSpecifier, @Nullable String superTeamName) {
+		ActivationKind kind = ActivationKind.NONE;
+		try {
+			if (activationSpecifier != null)
+				kind = ActivationKind.valueOf(activationSpecifier);
+		} catch (IllegalArgumentException iae) {	
+			log(iae, "Invalid activation kind "+activationSpecifier+" for team "+teamName);
 		}
-		return result;
+		return this.teams[count] = new TeamBinding(teamName, kind, superTeamName);
 	}
 
-	public List<String> getAllTeams() {
-		List<String> all = Arrays.asList(this.teamClasses);
-		if (subTeamClasses != null) {
-			all = new ArrayList<>(all);
-			for (int i = 0; i < subTeamClasses.length; i++)
-				if (subTeamClasses[i] != null)
-					all.addAll(subTeamClasses[i]);
+	/** Connect all resolvable info in all contained TeamBindings using the given lookup table. */
+	public void connect(Map<String, Set<TeamBinding>> teamLookup) {
+		for (int i = 0; i < teams.length; i++) {
+			TeamBinding team = teams[i];
+
+			// do we have TeamBindings representing the same team class (from different aspect bindings)?
+			Set<TeamBinding> equivalenceSet = teamLookup.get(team.teamName);
+			if (equivalenceSet != null)
+				team.equivalenceSet.addAll(equivalenceSet);
+			if (team.equivalenceSet.size() > 1)
+				log(IStatus.INFO, "team "+team.teamName+" participates in "+team.equivalenceSet.size()+" aspect bindings.");
+
+			if (team.superTeamName != null) {
+				Set<TeamBinding> superTeams = teamLookup.get(team.superTeamName);
+				if (superTeams != null) {
+					for (TeamBinding superTeam : superTeams) {
+						team.superTeam = superTeam;
+						superTeam.subTeams.add(team);
+					}
+				} else {
+					Exception e = new Exception("No such aspect binding");
+					log(e, "Class "+team.superTeamName+" not registered (declared to be superclass of team "+team.teamName);
+				}
+			}
+		}		
+	}
+
+	/** Answer the names of teams to load for a given base class. */
+	public synchronized @Nullable Collection<TeamBinding> getTeamsForBase(String baseClassName) {
+		Set<TeamBinding> teams = baseBundle.teamsPerBase.get(baseClassName);
+		// in case any team cannot immediately be instantiated/activated
+		// it will be added to the next queue: OTWeavingHook.deferredTeams.
+		if (teams != null) {
+			teams =  new HashSet<>(teams);
+			teams.removeAll(teamsInProgress); // no double-triggering
+			teamsInProgress.addAll(teams);
+		}
+		return teams;		
+	}
+
+	/** If a given team requires no activation, check if its super team should be activated instead. */
+	public @Nullable TeamBinding getOtherTeamToActivate(TeamBinding team) {
+		TeamBinding superTeam = team.superTeam;
+		if (superTeam != null && superTeam.activation != ActivationKind.NONE) {
+			return superTeam;
+		}
+		// sub teams?
+		return null;
+	}
+
+	/**
+	 * Read OT attributes of all teams in this aspectBinding 
+	 * and collect affected base classes into the teamBindings.
+	 */
+	public synchronized void scanTeamClasses(Bundle bundle) {
+		ClassScanner scanner = new ClassScanner();
+		for (@SuppressWarnings("null")@NonNull TeamBinding team : getAllTeamBindings()) {
+			try {
+				String teamName = scanner.readOTAttributes(bundle, team.teamName);
+				Collection<String> baseClassNames = scanner.getCollectedBaseClassNames();
+				if (team.baseClassNames.isEmpty())
+					addBaseClassNames(teamName, baseClassNames);
+				log(IStatus.INFO, "Scanned team class "+teamName+", found "+baseClassNames.size()+" base classes");
+			} catch (Exception e) {
+				log(e, "Failed to scan team class "+team.teamName);
+			}
+		}
+		this.hasScannedTeams = true;
+	}
+
+	private List<TeamBinding> getAllTeamBindings() {
+		List<TeamBinding> all = new ArrayList<>();
+		for (TeamBinding team : teams) all.add(team);
+		for (int i = 0; i < teams.length; i++) {
+			if (teams[i].superTeam != null)
+				all.add(teams[i].superTeam);
+			all.addAll(teams[i].subTeams);
 		}
 		return all;
 	}
 
-	public void addBaseClassNames(String teamName, Collection<String> baseClassNames) {
-		basesPerTeam.put(teamName, baseClassNames);
-		for (String baseClassName : baseClassNames) {
-			Set<String> teams = teamsPerBase.get(baseClassName);
-			if (teams == null)
-				teamsPerBase.put(baseClassName, teams = new HashSet<>());
-			teams.add(teamName);
-		}
-	}
-
-	/** Destructively read the names of teams to load for a given base class. */
-	public synchronized @Nullable Collection<String> getTeamsForBase(String baseClassName) {
-		Set<String> teamNames = teamsPerBase.remove(baseClassName);
-		if (teamNames != null) {
-			teamNames.removeAll(teamsInProgress);
-			teamsInProgress.addAll(teamNames);
-		}
-		return teamNames;		
-	}
-
-	public ActivationKind getActivation(String teamClassName) {
-		for (int i=0; i<teamClasses.length; i++) {
-			if (teamClasses[i].equals(teamClassName))
-				return activations[i]; // cannot declare array elements as nonnull
-		}
-		return ActivationKind.NONE;
-	}
-
-	/** Read OT attributes of all teams in aspectBinding and collect affected base classes. */
-	public synchronized void scanTeamClasses(Bundle bundle) {
-		ClassScanner scanner = new ClassScanner();
-		for (@SuppressWarnings("null")@NonNull String teamName : getAllTeams()) {
-			try {
-				teamName = scanner.readOTAttributes(bundle, teamName);
-				Collection<String> baseClassNames = scanner.getCollectedBaseClassNames();
-				if (!basesPerTeam.containsKey(teamName))
-					addBaseClassNames(teamName, baseClassNames);
-				log(IStatus.INFO, "Scanned team class "+teamName+", found "+baseClassNames.size()+" base classes");
-			} catch (Exception e) {
-				log(e, "Failed to scan team class "+teamName);
+	private void addBaseClassNames(String teamName, Collection<String> baseClassNames) {
+		for (int i = 0; i < teams.length; i++) {
+			TeamBinding team = teams[i];
+			if (team.teamName.equals(teamName)) {
+				for (TeamBinding equivalent : team.equivalenceSet)
+					equivalent.addBaseClassNames(baseClassNames);
+				return;
 			}
 		}
-		this.state = State.TeamsScanned;
-	}
-
-	public void markAsActivated(String teamName) {
-		for (int i = 0; i < this.teamClasses.length; i++) {
-			if (this.teamClasses[i].equals(teamName)) {
-				this.isActivated[i] = true;
+		// try super:
+		for (int i = 0; i < teams.length; i++) {
+			TeamBinding team = teams[i];
+			TeamBinding superTeam = team.superTeam;
+			if (superTeam != null && superTeam.teamName.equals(teamName)) {
+				for (TeamBinding equivalentSuper : superTeam.equivalenceSet)
+					equivalentSuper.addBaseClassNames(baseClassNames);
 				return;
 			}
 		}
 	}
-	
-	public boolean isActivated(String teamName) {
-		for (int i = 0; i < this.teamClasses.length; i++)
-			if (this.teamClasses[i].equals(teamName))
-				return this.isActivated[i];
-		return false;
+
+	public List<String> getBasesPerTeam(String teamName) {
+		for (int i = 0; i < this.teams.length; i++) {
+			TeamBinding team = this.teams[i];
+			if (team.teamName.equals(teamName))
+				return team.baseClassNames;
+		}
+		@SuppressWarnings("null")@NonNull // well-known library function
+		List<String> emptyList = Collections.emptyList();
+		return emptyList;		
+	}
+
+	/** Add all require imports to match the hidden reverse dependency created by any team binding to this base class. */
+	public void addImports(WovenClass baseClass) {
+		String baseClassName = baseClass.getClassName();
+		Set<TeamBinding> teams = baseBundle.teamsPerBase.get(baseClassName);
+		if (teams != null)
+			for (TeamBinding resolvedTeam : teams)
+				resolvedTeam.addImportTo(baseClass, 0); // 0 = travel both directions (sub/super)
+	}
+
+	public void cleanUp(String baseClass) {
+		baseBundle.teamsPerBase.remove(baseClass);
+	}
+
+	public boolean isDone() {
+		for (int i = 0; i < teams.length; i++)
+			if (!teams[i].isDone())
+				return false;
+		return true;
+	}
+
+	public String toString() {
+		String result = "\tbase plugin "+basePluginName+"\n\tadapted by aspect pluging "+aspectPlugin;
+		for (TeamBinding team : teams)
+			result += "\n\t\t "+team.toString();
+		return result;
 	}
 }
\ No newline at end of file
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 b276b73..b127d8d 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
@@ -32,6 +32,7 @@
 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;
@@ -39,6 +40,8 @@
 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.otequinox.Constants;
 import org.eclipse.objectteams.otequinox.hook.ILogger;
 import org.osgi.framework.Bundle;
@@ -79,6 +82,9 @@
 	{
 		IConfigurationElement[] aspectBindingConfigs = RegistryFactory.getRegistry().getConfigurationElementsFor(
 				TRANSFORMER_PLUGIN_ID, ASPECT_BINDING_EXTPOINT_ID);
+		Map<String, Set<TeamBinding>> teamLookup = new HashMap<>();
+		Map<String, BaseBundle> baseBundleLookup = new HashMap<>();
+		AspectBinding[] bindings = new AspectBinding[aspectBindingConfigs.length];
 		
 		for (int i = 0; i < aspectBindingConfigs.length; i++) {
 			IConfigurationElement currentBindingConfig = aspectBindingConfigs[i];
@@ -86,6 +92,7 @@
 			//aspect:
 			@SuppressWarnings("null")@NonNull 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);
@@ -93,6 +100,7 @@
 					log(ILogger.ERROR, "aspect bundle "+aspectBundleId+" is not resolved - not loading aspectBindings.");
 					continue;
 				}
+				aspectBundle = aspectBundles[0];
 			}
 			
 			//base:
@@ -106,6 +114,9 @@
 				log(ILogger.ERROR, "aspectBinding of "+aspectBundleId+" must specify the id of a basePlugin");
 				continue;
 			}
+			BaseBundle baseBundle = baseBundleLookup.get(baseBundleId); 
+			if (baseBundle == null)		
+				baseBundleLookup.put(baseBundleId, baseBundle = new BaseBundle(baseBundleId));
 				
  			//base fragments?
 			IConfigurationElement[] fragments = basePlugins[0].getChildren(REQUIRED_FRAGMENT);
@@ -117,9 +128,11 @@
 			int teamCount = teams.length;
 			for (int j = 0; j < teams.length; j++) if (teams[j].getAttribute(CLASS) == null) teamCount --;
 			AspectBinding binding = new AspectBinding(aspectBundleId,
-														baseBundleId,
+														aspectBundle,
+														baseBundle,
 														basePlugins[0].getChildren(Constants.FORCED_EXPORTS_ELEMENT), 
 														teamCount);
+			bindings[i] = binding;
 			// TODO(SH): maybe enforce that every bundle id is given only once?
 
 			//teams:
@@ -127,9 +140,11 @@
 				for (int j = 0, count = 0; count < teamCount; j++) {
 					String teamClass = teams[j].getAttribute(CLASS);
 					if (teamClass == null) continue;
-					binding.teamClasses[count] = teamClass;
-					String activation = teams[j].getAttribute(ACTIVATION);
-					binding.setActivation(count++, activation);
+					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);
 				}
 				
 				@NonNull String realBaseBundleId = baseBundleId.toUpperCase().equals(SELF) ? aspectBundleId : baseBundleId;
@@ -137,22 +152,15 @@
 				addBindingForAspectBundle(aspectBundleId, binding);
 				hook.setBaseTripWire(packageAdmin, realBaseBundleId);
 
-				
-				// now that binding.teamClasses is filled connect to super team, if requested:
-				for (int j = 0; j < teamCount; j++) {
-					if (teams[j].getAttribute(CLASS) == null) continue;
-					String superTeamName = teams[j].getAttribute(SUPERCLASS);
-					if (superTeamName != null) {
-						String teamName = binding.teamClasses[j];
-						assert teamName != null : "array should not contain nulls";
-						addSubTeam(aspectBundleId, teamName, superTeamName);
-					}
-				}
 				log(ILogger.INFO, "registered:\n"+binding);
 			} catch (Throwable t) {
 				log(t, "Invalid aspectBinding extension");
 			}
 		}
+		// second round to connect sub/super teams to aspect bindings:
+		for (int i = 0; i < bindings.length; i++) {
+			bindings[i].connect(teamLookup);
+		}
 	}
 	
 	@SuppressWarnings("deprecation") // multiple uses of deprecated but still recommended class PackageAdmin
@@ -221,36 +229,10 @@
 			aspectBindingsByAspectPlugin.put(aspectBundleId, bindingList);
 		}
 		bindingList.add(binding);
-		if (binding.basePlugin.toUpperCase().equals(SELF))
+		if (binding.basePluginName.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
@@ -261,7 +243,7 @@
 		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;
+			basePlugins[i] = bindings.get(i).basePluginName;
 		}
 		return basePlugins;
 	}
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 a3b8ff0..29623a5 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,10 +22,8 @@
 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;
 
@@ -42,6 +40,7 @@
 
 	private String baseBundleName;	
 	private boolean otreAdded = false;
+	private List<AspectBinding> aspectBindings = new ArrayList<>();
 
 	public BaseBundleLoadTrigger(String bundleSymbolicName, AspectBindingRegistry aspectBindingRegistry, 
 			@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin admin) 
@@ -55,10 +54,11 @@
 	 * Signal that the given class is being loaded and trigger any necessary steps:
 	 * (1) add import to OTRE (now)
 	 * (2) scan team (now)
-	 * (3) load & instantiate & activate (now or later).
+	 * (3) add imports base->aspect (now)
+	 * (4) load & instantiate & activate (now or later).
 	 */
 	@SuppressWarnings("deprecation") // uses deprecated PackageAdmin
-	public boolean fire(WovenClass baseClass, Set<String> beingDefined, OTWeavingHook hook) {
+	public void fire(WovenClass baseClass, Set<String> beingDefined, OTWeavingHook hook) {
 
 		// (1) OTRE import added once per base bundle:
 		synchronized(this) {
@@ -71,43 +71,58 @@
 		}
 		
 		// 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;
-				final org.osgi.service.packageadmin.PackageAdmin admin2 = admin;
-				if (admin2 == null) {
-					log(IStatus.ERROR, "Cannot find aspect bundle "+aspectBinding.aspectPlugin);
-					continue;					
-				} else {
+		if (aspectBindings.isEmpty())
+			aspectBindings.addAll(aspectBindingRegistry.getAdaptingAspectBindings(baseBundleName));
+		List<WaitingTeamRecord> deferredTeamClasses = new ArrayList<>();
+		for (AspectBinding aspectBinding : aspectBindings) {
+			if (!aspectBinding.hasScannedTeams)
+				log(IStatus.INFO, "Preparing aspect binding for base bundle "+baseBundleName);
+			final org.osgi.service.packageadmin.PackageAdmin admin2 = admin;
+			if (admin2 == null) {
+				log(IStatus.ERROR, "Cannot find aspect bundle "+aspectBinding.aspectPlugin);
+				continue;					
+			} else {
+				Bundle aspectBundle = aspectBinding.aspectBundle;
+				if (aspectBundle == null) {
 					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;
+					aspectBundle = aspectBundles[0];
+					assert aspectBundle != null : "Package admin should not return a null array element";
 				}
-			}
-			// if some had to be deferred collect them now:
-			if (!deferredTeamClasses.isEmpty()) {
-				hook.addDeferredTeamClasses(deferredTeamClasses);
-				return false;
+				// (2) scan all teams in affecting aspect bindings:
+				if (!aspectBinding.hasScannedTeams)
+					aspectBinding.scanTeamClasses(aspectBundle);
+				
+				// (3) add dependencies to the base bundle:
+				aspectBinding.addImports(baseClass);
+
+				// (4) try optional steps:
+				TeamLoader loading = new TeamLoader(deferredTeamClasses, beingDefined);
+				loading.loadTeamsForBase(aspectBundle, aspectBinding, baseClass);
 			}
 		}
-		return allDone;
+
+		// if some had to be deferred collect them now:
+		if (!deferredTeamClasses.isEmpty()) {
+			hook.addDeferredTeamClasses(deferredTeamClasses);
+		}
+
+		// mark done for this base class 
+		// (do outside the look in case multiple aspect bindings point to the same baseBundle)
+		for (AspectBinding aspectBinding : aspectBindings) {
+			String baseClassName = baseClass.getClassName();
+			assert baseClassName != null : "WovenClass.getClassName() should not answer null";
+			aspectBinding.cleanUp(baseClassName);
+		}
+	}
+
+	public boolean isDone() {
+		for (AspectBinding binding : aspectBindings)
+			if (!binding.isDone())
+				return false;
+		return true;
 	}
 }
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 e61a52f..05cc17e 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
@@ -109,7 +109,8 @@
 	void triggerBaseTripWires(@Nullable String bundleName, @NonNull WovenClass baseClass) {
 		BaseBundleLoadTrigger activation = baseTripWires.get(bundleName);
 		if (activation != null) {
-			if (activation.fire(baseClass, beingDefined, this))
+			activation.fire(baseClass, beingDefined, this);
+			if (activation.isDone())
 				baseTripWires.remove(bundleName);
 		}
 	}
@@ -188,13 +189,16 @@
 		}
 		if (scheduledTeams == null) return;
 		for(WaitingTeamRecord record : scheduledTeams) {
-			if (record.aspectBinding.isActivated(record.getTeamName()))
+			if (record.team.isActivated)
 				continue;
-			log(IStatus.INFO, "Consider for instantiation/activation: team "+record.getTeamName());
+			String teamName = record.team.teamName;
+			log(IStatus.INFO, "Consider for instantiation/activation: team "+teamName);
 			try {
-				new TeamLoader(deferredTeams, beingDefined).instantiateWaitingTeam(record); // may re-insert to deferredTeams
+				TeamLoader loader = new TeamLoader(deferredTeams, beingDefined);
+				// Instantiate (we only get here if activationKind != NONE)
+				loader.instantiateAndActivate(record.aspectBinding, record.team, record.activationKind); // may re-insert to deferredTeams
 			} catch (Exception e) {
-				log(e, "Failed to instantiate team "+record.getTeamName());
+				log(e, "Failed to instantiate team "+teamName);
 				continue;
 			}
 		}
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 32a86f3..7024f8a 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
@@ -27,6 +27,7 @@
 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.TeamBinding;
 import org.eclipse.objectteams.otequinox.ActivationKind;
 import org.eclipse.objectteams.otequinox.TransformerPlugin;
 import org.eclipse.objectteams.otequinox.hook.ILogger;
@@ -37,13 +38,15 @@
 /**
  * This class triggers the actual loading/instantiation/activation of teams.
  * <p>
- * It implements a strategy of deferring those teams where instantiation/activation
- * failed (NoClassDefFoundError), which assumably happens, if one required class
- * cannot be loaded, because its loading is already in progress further down in
- * our call stack.
+ * It implements a strategy of deferring those teams that are not ready for
+ * instantiating / activating, which we check by comparing the sets of
+ * bound base classes of the team vs. the set of classes currently being
+ * processed by class loading & weaving.
+ * This reflects that fact that classes for which loading has already been
+ * started would otherwise trigger an irrecoverable NoClassDefFoundError. 
  * </p><p>
- * Which teams participate in deferred instantiation is communicated via the list
- * {@link #deferredTeams}.
+ * Which teams participate in deferred instantiation is communicated via the 
+ * shared list {@link #deferredTeams}.
  * </p>
  */
 @NonNullByDefault
@@ -52,7 +55,7 @@
 	/** Collect here any teams that cannot yet be handled and should be scheduled later. */
 	private List<WaitingTeamRecord> deferredTeams;
 
-	private Set<String> beingDefined; 
+	private Set<String> beingDefined;
 	
 	public TeamLoader(List<WaitingTeamRecord> deferredTeams, Set<String> beingDefined) {
 		this.deferredTeams = deferredTeams;
@@ -61,49 +64,42 @@
 
 	/**
 	 * Team loading, 1st attempt before the base class is even loaded
-	 * Trying to do these phases: load/instantiate/activate (if ready),
-	 * and also adds a reverse import to the base (always).
+	 * Trying to do these phases: load (now) instantiate/activate (if ready),
 	 */
-	public boolean loadTeamsForBase(Bundle aspectBundle, AspectBinding aspectBinding, WovenClass baseClass) {
+	public void loadTeamsForBase(Bundle aspectBundle, AspectBinding aspectBinding, WovenClass baseClass) {
 		@SuppressWarnings("null")@NonNull String className = baseClass.getClassName();
-		Collection<String> teamsForBase = aspectBinding.getTeamsForBase(className);
+		Collection<TeamBinding> teamsForBase = aspectBinding.getTeamsForBase(className);
 		if (teamsForBase == null) 
-			return false; // not done
-		List<String> imports = baseClass.getDynamicImports();
-		for (String teamForBase : teamsForBase) {
-			// Add dependency:
-			String packageOfTeam = "";
-			int dot = teamForBase.lastIndexOf('.');
-			if (dot != -1)
-				packageOfTeam = teamForBase.substring(0, dot);
-			imports.add(packageOfTeam);
-			log(IStatus.INFO, "Added dependency from base "+baseClass.getClassName()+" to package '"+packageOfTeam+"'");
+			return; // not done
+		for (TeamBinding teamForBase : teamsForBase) {
+			if (teamForBase.isActivated) continue;
 			// Load:
 			Class<? extends Team> teamClass;
-			teamClass = findTeamClass(teamForBase, aspectBundle);
+			teamClass = teamForBase.loadTeamClass(aspectBundle);
 			if (teamClass == null) {
 				log(new ClassNotFoundException("Not found: "+teamForBase), "Failed to load team "+teamForBase);
 				continue;
 			}
 			// Try to instantiate & activate, failures are recorded in deferredTeams
-			ActivationKind activationKind = aspectBinding.getActivation(teamForBase);
-			if (activationKind == ActivationKind.NONE)
+			ActivationKind activationKind = teamForBase.activation;
+			if (activationKind == ActivationKind.NONE) {
+				teamForBase = aspectBinding.getOtherTeamToActivate(teamForBase);
+				if (teamForBase != null) {
+					if (teamForBase.isActivated) continue;
+					activationKind = teamForBase.activation;
+					teamClass = teamForBase.loadTeamClass(aspectBundle);
+					if (teamClass == null) {
+						log(new ClassNotFoundException("Not found: "+teamForBase.teamName+" in bundle "+aspectBundle.getSymbolicName()), "Failed to load team "+teamForBase);
+						continue;						
+					}
+				} else {
+					continue;
+				}
+			}
+			if (activationKind == ActivationKind.NONE) 
 				continue;
-			Team teamInstance = instantiateAndActivate(aspectBinding, teamClass, activationKind);
-			if (teamInstance == null)
-				continue;
+			instantiateAndActivate(aspectBinding, teamForBase, activationKind);
 		}
-		return true; // all activatable teams have been activated or added to deferredTeams
-	}
-
-	/** Team loading, subsequent attempts. */
-	public void instantiateWaitingTeam(WaitingTeamRecord record)
-			throws InstantiationException, IllegalAccessException 
-	{
-		// Instantiate (we only get here if activationKind != NONE)
-		Class<? extends Team> teamClass = record.teamClass;
-		assert teamClass != null : "cannot be null if teamInstance is null";
-		instantiateAndActivate(record.aspectBinding, teamClass, record.activationKind);
 	}
 
 	public static @Nullable Pair<URL,String> findTeamClassResource(String className, Bundle bundle) {
@@ -115,20 +111,6 @@
 		return null;
 	}
 
-	@SuppressWarnings("unchecked")
-	public static @Nullable Class<? extends Team> findTeamClass(String className, Bundle bundle) {
-		for (String candidate : possibleTeamNames(className)) {
-			try {
-				Class<?> result = bundle.loadClass(candidate);
-				if (result != null)
-					return (Class<? extends Team>) result;
-			} catch (NoClassDefFoundError|ClassNotFoundException e) {
-				// keep looking
-			}
-		}
-		return null;
-	}
-
 	/** 
 	 * Starting from currentName compute a list of potential binary names of (nested) teams
 	 * using "$__OT__" as the separator, to find class parts of nested teams.  
@@ -164,18 +146,21 @@
 		return result;
 	}
 
-	@Nullable Team instantiateAndActivate(AspectBinding aspectBinding, Class<? extends Team> teamClass, ActivationKind activationKind) 
+	/**
+	 * Check if the given team is ready. If so instantiate it and if activationKind requires also activate it.
+	 */
+	void instantiateAndActivate(AspectBinding aspectBinding, TeamBinding team, ActivationKind activationKind)
 	{
-		@SuppressWarnings("null")@NonNull String teamName = teamClass.getName();
+		String teamName = team.teamName;
 		// don't try to instantiate before all base classes successfully loaded.
 		synchronized(aspectBinding) {
-			if (!isReadyToLoad(aspectBinding, teamClass, teamName, activationKind))
-				return null;
-			aspectBinding.markAsActivated(teamName);
+			if (!isReadyToLoad(aspectBinding, team, teamName, activationKind))
+				return;
+			team.isActivated = true;
 		}
 
 		try {
-			@SuppressWarnings("null")@NonNull Team instance = teamClass.newInstance();
+			@SuppressWarnings("null")@NonNull Team instance = team.teamClass.newInstance();
 			TransformerPlugin.registerTeamInstance(instance);
 			log(ILogger.INFO, "Instantiated team "+teamName);
 			
@@ -197,23 +182,20 @@
 				// 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);
 		}
-		return null;
 	}
 
-	boolean isReadyToLoad(AspectBinding aspectBinding,
-			Class<? extends Team> teamClass, String teamName,
-			ActivationKind activationKind)
+	private boolean isReadyToLoad(AspectBinding aspectBinding,
+									TeamBinding team, String teamName,
+									ActivationKind activationKind)
 	{
-		for (@SuppressWarnings("null")@NonNull String baseclass : aspectBinding.basesPerTeam.get(teamName)) {
+		for (@SuppressWarnings("null")@NonNull String baseclass : aspectBinding.getBasesPerTeam(teamName)) {
 			if (this.beingDefined.contains(baseclass)) {
 				synchronized (deferredTeams) {
-					WaitingTeamRecord record = new WaitingTeamRecord(teamClass, aspectBinding, activationKind, baseclass);
+					WaitingTeamRecord record = new WaitingTeamRecord(team, aspectBinding, activationKind, baseclass);
 					deferredTeams.add(record); // TODO(SH): synchronization, deadlock? performed while holding lock an aspectBinding
 				}
 				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
index 5438241..b954565 100644
--- 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
@@ -16,26 +16,21 @@
 package org.eclipse.objectteams.internal.osgi.weaving;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.objectteams.internal.osgi.weaving.AspectBinding.TeamBinding;
 import org.eclipse.objectteams.otequinox.ActivationKind;
-import org.objectteams.Team;
 
 /** Record for one team waiting for instantiation & activation. */
 @NonNullByDefault
 class WaitingTeamRecord {
-	Class<? extends Team> teamClass;
+	TeamBinding team;
 	AspectBinding aspectBinding;
 	ActivationKind activationKind;
 	String notFoundClass;
 	
-	public WaitingTeamRecord(Class<? extends Team> teamClass, AspectBinding aspectBinding, ActivationKind activationKind, String notFoundClass) {
-		this.teamClass = teamClass;
+	public WaitingTeamRecord(TeamBinding team, AspectBinding aspectBinding, ActivationKind activationKind, String notFoundClass) {
+		this.team = team;
 		this.aspectBinding = aspectBinding;
 		this.notFoundClass = notFoundClass;
 		this.activationKind = activationKind;
-	}
-
-	@SuppressWarnings("null") // calling well-known library function
-	public String getTeamName() {
-		return teamClass.getName();
-	}		
+	}	
 }
\ No newline at end of file