Bug 413862 - Restore "forced exports" mechanism
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/.classpath b/plugins/org.eclipse.objectteams.otequinox.turbo/.classpath
new file mode 100644
index 0000000..098194c
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/.project b/plugins/org.eclipse.objectteams.otequinox.turbo/.project
new file mode 100644
index 0000000..6a04c29
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.objectteams.otequinox.turbo</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.objectteams.otequinox.turbo/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..c2a6c9e
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,96 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
+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.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.7
+org.eclipse.objectteams.otdt.compiler.option.pure_java=enabled
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/META-INF/MANIFEST.MF b/plugins/org.eclipse.objectteams.otequinox.turbo/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..72d34a2
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: OT/Equinox Turbo
+Bundle-SymbolicName: org.eclipse.objectteams.otequinox.turbo
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: Eclipse.org - Object Teams
+Fragment-Host: org.eclipse.osgi;bundle-version="[3.10.1,4.0.0)"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Export-Package: org.eclipse.objectteams.otequinox.turbo
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/build.properties b/plugins/org.eclipse.objectteams.otequinox.turbo/build.properties
new file mode 100644
index 0000000..40a1314
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               hookconfigurators.properties
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/hookconfigurators.properties b/plugins/org.eclipse.objectteams.otequinox.turbo/hookconfigurators.properties
new file mode 100644
index 0000000..20cc368
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/hookconfigurators.properties
@@ -0,0 +1 @@
+hook.configurators=org.eclipse.objectteams.otequinox.turbo.OTStorageHook
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/AspectPermission.java b/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/AspectPermission.java
new file mode 100644
index 0000000..cab247b
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/AspectPermission.java
@@ -0,0 +1,28 @@
+/**********************************************************************
+ * This file is part of "Object Teams Development Tooling"-Software
+ * 
+ * Copyright 2009, 2014 Technical University Berlin, Germany.
+ * 
+ * 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.eclipse.org/objectteams for updates and contact.
+ * 
+ * Contributors:
+ * Technical University Berlin - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.otequinox.turbo;
+
+/**
+ * Copy of corresponding type in org.eclipse.objectteams.otequinox (inaccessible to this fragment).
+ */
+public enum AspectPermission {
+	/** Not influencing negotiation between other parties. */
+	UNDEFINED,
+	/** A permission is granted unless someone else denies it. */
+	GRANT, 
+	/** A permission is explicitly denied. Cannot be overridden. */
+	DENY;
+}
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/ForcedExportsRegistry.java b/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/ForcedExportsRegistry.java
new file mode 100644
index 0000000..eb67a16
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/ForcedExportsRegistry.java
@@ -0,0 +1,309 @@
+/**********************************************************************
+ * This file is part of "Object Teams Development Tooling"-Software
+ * 
+ * Copyright 2014 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.eclipse.org/objectteams for updates and contact.
+ * 
+ * Contributors:
+ * 	Stephan Herrmann - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.otequinox.turbo;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+
+/**
+ * Reads forced exports from various locations (granted/denied).
+ * 
+ * All members are static to facilitate reflexive access from org.eclipse.objectteams.otequinox.
+ */
+public class ForcedExportsRegistry {
+	// property names:
+	private static final String FORCED_EXPORTS_DENIED  = "otequinox.forced.exports.denied"; // //$NON-NLS-1$
+	private static final String FORCED_EXPORTS_GRANTED = "otequinox.forced.exports.granted"; // //$NON-NLS-1$
+	
+	// terminal tokens:
+	private static final String XFRIENDS = "x-friends:="; //$NON-NLS-1$
+
+	private static HashMap<String, String> grantedForcedExports = null;
+	private static HashMap<String, String> deniedForcedExports = null;
+
+	static FrameworkLog fwLog;
+
+	public static void install(FrameworkLog log) {
+		fwLog = log;
+		readForcedExports();
+	}
+
+	static void logError(String message, Throwable e) {
+		fwLog.log(new FrameworkLogEntry(ForcedExportsRegistry.class.getName(),
+									    message,
+									    FrameworkLogEntry.ERROR,
+									    e, null));
+	}
+
+	static void logError(String message) {
+		fwLog.log(new FrameworkLogEntry(ForcedExportsRegistry.class.getName(),
+									    message,
+									    FrameworkLogEntry.ERROR,
+									    null, null));
+	}
+
+	private static void readForcedExports() {
+		// read forced exports from config.ini (system properties), or file(s)
+		deniedForcedExports= new HashMap<String, String>();
+		grantedForcedExports= new HashMap<String, String>();
+		readForcedExportsFromProperty(FORCED_EXPORTS_DENIED, AspectPermission.DENY);
+		readForcedExportsFromProperty(FORCED_EXPORTS_GRANTED, AspectPermission.GRANT);
+	}
+
+	private static void readForcedExportsFromProperty(String propKey, AspectPermission perm) {
+		String value= System.getProperty(propKey);
+		if (value != null) {
+			if (value.length() > 0 && value.charAt(0) == '@') {
+				// follows: comma-separated list for filenames to read from:
+				int pos = 1;
+				while (pos < value.length()) {
+					int comma = value.indexOf(',', pos);
+					String fileName;
+					if (comma > -1) {
+						fileName = value.substring(pos, comma);
+						pos = comma+1;
+					} else {
+						fileName = value.substring(pos);
+						pos = value.length();
+					}
+					parseForcedExportsFile(new File(fileName), perm);
+				}
+			} else {
+				parseForcedExports(value, perm);
+			}
+		}
+	}
+	/**
+	 * API:
+	 * Adds the definitions from a given file to the set of granted forced exports.
+	 * @param file file to read from
+	 * @param perm either ALLOW or DENY (via their ordinal()),
+	 * 	 determines how the found forced export decls are interpreted.
+	 */
+	public static void parseForcedExportsFile(File file, int perm) {
+		parseForcedExportsFile(file, AspectPermission.values()[perm]);
+	}
+	private static synchronized void parseForcedExportsFile(File file, AspectPermission perm) {
+		StringBuffer newVal = new StringBuffer();
+		try {
+			// read content of one file:
+			FileInputStream fis = new FileInputStream(file);
+			try (BufferedReader br = new BufferedReader(new InputStreamReader(fis, "UTF8"))) { //$NON-NLS-1$
+				String line;
+				boolean afterClosingBrace = false;
+				while ((line = br.readLine()) != null) {
+					line = line.trim();
+					if (line.length() > 0) {
+						if (line.charAt(0) == '#') continue;
+						// may need to replace linebreak after ']' with ',' - to match one-line syntax in config.ini
+						char lastReadChar = line.charAt(line.length()-1);
+						if (afterClosingBrace && lastReadChar != ',')
+							newVal.append(',');
+						afterClosingBrace = lastReadChar == ']';
+							
+						newVal.append(line);
+					}
+				}
+				if (newVal.length() > 0)
+					parseForcedExports(newVal.toString(), perm);
+			}
+		} catch (IOException e) {
+			fwLog.log(new FrameworkLogEntry(OTStorageHook.class.getName(),
+						    "failed to read forcedExports file "+file.getName(), //$NON-NLS-1$
+						    FrameworkLogEntry.ERROR, e, null));
+		}							
+	}
+	/* Adds the given definitions to the set of granted forced exports. */
+	private static void parseForcedExports(String value, AspectPermission perm) {
+		HashMap<String, String> map = getForcedExportsMap(perm);
+		if (map == null) return; // DONT_CARE
+		int pos = 0;
+		String[] values = new String[2]; // { BaseBundleID , PackageExports }
+		while (true) {
+			pos = getForcedExportForOneBase(value, pos, values);
+			String plugin= values[0];
+			String pack= values[1];
+			if (map.containsKey(plugin)) {
+				String oldPack = map.get(plugin);
+				pack = oldPack+','+pack;  // append to existing definition
+			}
+			map.put(plugin, pack);
+			if (pos >= value.length())
+				break; // eot: done without errors
+			if (value.charAt(pos) != ',')
+				throwSyntaxError(value, pos, "missing ','"); //$NON-NLS-1$
+			pos++; // skip the ','
+		}
+	}
+	/**
+	 * fetch one chunk of forced exports specification from `spec`:
+	 * <ul>
+	 * <li>my.base.plugin[<em>specification-of-force-exported-packages</em>]<em>tail</em></li>
+	 * </ul>
+	 * is split into:
+	 * <ul>
+	 * <li>my.base.plugin</li>
+	 * <li><em>specification-of-force-exported-packages</em></li>
+	 * </ul>
+	 * and return value points to <em>tail</em>
+	 * @param spec where to read from
+	 * @param start where to start reading (within spec)
+	 * @param value store result chunks in this array: [0] = base plugin, [1] = packages specification
+	 * @return position after what has been interpreted so far
+	 */
+	private static int getForcedExportForOneBase(String spec, int start, String[] values) {
+		int open = spec.indexOf('[', start);
+		if (open == -1)
+			throwSyntaxError(spec, start, "missing '['"); //$NON-NLS-1$
+		int close = spec.indexOf(']', start);
+		if (close == -1)
+			throwSyntaxError(spec, open, "missing ']'"); //$NON-NLS-1$
+		values[0] = spec.substring(start, open);
+		values[1] = spec.substring(open+1, close);
+		return close+1;
+	}
+
+	private static void throwSyntaxError(String spec, int pos, String string) {
+		throw new RuntimeException("Illegal syntax in 'forcedExports' directive at position "+pos+" (not counting whitespace): "+string+"\n value is:\n"+spec); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$		
+	}
+
+	private static HashMap<String, String> getForcedExportsMap(AspectPermission perm) {
+		switch (perm) {
+			case GRANT:     return grantedForcedExports;
+			case DENY:      return deniedForcedExports;
+			case UNDEFINED: return null; 
+//			default: // not implementing a default case, want to be warned when new enum-constants are added
+		}
+		// for binary compatibility; see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=265744
+		throw new IncompatibleClassChangeError("enum "+AspectPermission.class.getName()+" has changed unexpectedly."); //$NON-NLS-1$ //$NON-NLS-2$
+	}
+
+	/** 
+	 * API for {@link OTStorageHook.StorageHookImpl}.
+	 * Query whether any forced-exports are declared for the given base bundle.
+	 * @param baseBundleId
+	 * @param the permission we are asking for
+	 * @return comma-separated package names
+	 */ 
+	public static String getGrantedForcedExportsByBase(String baseBundleId) {
+
+		// can be queried before we had a chance to initialize our data structures
+		synchronized (OTStorageHook.class) {
+			if (grantedForcedExports == null)
+				readForcedExports();
+		}
+
+		String exports = grantedForcedExports.get(baseBundleId);
+		if (exports != null) {
+			// yes, we need to add forced exports:
+			if (!exports.contains(XFRIENDS)) { // FIXME: check once for each package?
+				// invalid directive:
+				grantedForcedExports.remove(baseBundleId); 
+				logError("config.ini: missing x-friends directive in forced export of "+exports); //$NON-NLS-1$
+				return null; // don't install illegal forced exports
+			}
+		}
+		return exports;
+	}
+
+	/**
+	 * API for org.eclipse.objectteams.otequinox (as a service and using reflection).
+	 * Query whether any forced-exports are declared in config.ini (or other location) 
+	 * for the given aspect bundle.
+	 * @param aspectBundleId
+	 * @param the permission we are asking for
+	 * @return list of pairs (baseBundleId x packageName)
+	 */ 
+	public static /*@NonNull*/ List<String[]> getForcedExportsByAspect(String aspectBundleId, int perm) 
+	{
+		// can be queried before we had a chance to initialize our data structures
+		synchronized (OTStorageHook.class) {
+			if (grantedForcedExports == null)
+				readForcedExports();
+		}
+
+		ArrayList<String[]> result= new ArrayList<String[]>(5);
+		
+		HashMap<String, String> map = getForcedExportsMap(AspectPermission.values()[perm]);
+		if (map == null) 
+			return result; // DONT_CARE: useless query.
+		
+		for (Map.Entry<String,String> entry: map.entrySet()) {
+			String export= entry.getValue();
+			int start = 0;
+			while (start >= 0 && start < export.length()) {
+				if (start > 0) {
+					// skip separator after previous entry
+					if (export.charAt(start) == ',')
+						start++;
+					else
+						logError("Error parsing forced exports: "+export+", comma expected at position "+start); //$NON-NLS-1$ //$NON-NLS-2$
+				}
+				int pos= export.indexOf(';'+XFRIENDS, start);
+				if (pos == -1)
+					break;
+				String packageName = export.substring(start, pos);
+				List<String> aspectBundles = new ArrayList<String>(); 
+				start = scanAspectBundles(export, pos+XFRIENDS.length()+1, aspectBundles);
+				for (String aspect : aspectBundles) {
+					if (aspect.equals(aspectBundleId)) {
+						result.add(new String[]{entry.getKey(), packageName});
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	private static int scanAspectBundles(String export, int pos, List<String> result) {
+		String termChars = ",]"; //$NON-NLS-1$
+		if (export.charAt(pos) == '"') {
+			pos++;
+			termChars = "\""; //$NON-NLS-1$
+		}
+		int start = pos;
+		while (pos < export.length()) {
+			char c = export.charAt(pos);
+			switch (c) {
+			case ',':
+			case ']':
+			case '"':
+				String next = export.substring(start, pos);
+				result.add(next);
+				start = pos+1;
+				if (termChars.indexOf(c) != -1)
+					return start;
+				break;
+			}
+			pos++;
+		}
+		logError("Unterminated forced exports: "+export); //$NON-NLS-1$
+		if (pos > start)
+			result.add(export.substring(start));
+		return export.length();
+	}
+
+}
diff --git a/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/OTStorageHook.java b/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/OTStorageHook.java
new file mode 100644
index 0000000..8cb54af
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox.turbo/src/org/eclipse/objectteams/otequinox/turbo/OTStorageHook.java
@@ -0,0 +1,122 @@
+/**********************************************************************
+ * This file is part of "Object Teams Development Tooling"-Software
+ * 
+ * Copyright 2014 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.eclipse.org/objectteams for updates and contact.
+ * 
+ * Contributors:
+ * 	Stephan Herrmann - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.otequinox.turbo;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Dictionary;
+
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.Headers;
+import org.eclipse.osgi.internal.hookregistry.HookConfigurator;
+import org.eclipse.osgi.internal.hookregistry.HookRegistry;
+import org.eclipse.osgi.internal.hookregistry.StorageHookFactory;
+import org.eclipse.osgi.storage.BundleInfo.Generation;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+
+/** This class intercepts each bundle manifest and checks if forced exports must be injected. */
+public class OTStorageHook extends StorageHookFactory<Object, Object, OTStorageHook.StorageHookImpl> implements HookConfigurator {
+	// HINTS FOR DEBUGGING:
+	// - This fragment must be colocated with org.eclipse.osgi - perhaps a symbolic link helps to establish this.
+	// - This class must be accessible by its qualified name with no leading "src", again a symbolic link my help:
+	//   $ ln -s bin/org org
+
+	private FrameworkLog fwLog;
+
+	class StorageHookImpl extends StorageHookFactory.StorageHook<Object,Object> {
+
+		public StorageHookImpl(Generation generation) {
+			super(generation, OTStorageHook.class);
+		}
+
+		@Override
+		public void initialize(Dictionary<String, String> manifest) throws BundleException {
+			// when initializing, intercept the manifest and conditionally inject forced exports
+			String[] id = manifest.get(Constants.BUNDLE_SYMBOLICNAME).split(";");
+			if (id.length > 0) {
+				String packages = ForcedExportsRegistry.getGrantedForcedExportsByBase(id[0]);
+				if (packages != null && !packages.isEmpty()) {
+					String exportedPackages = manifest.get(Constants.EXPORT_PACKAGE);
+					if (exportedPackages != null && !exportedPackages.isEmpty())
+						exportedPackages = exportedPackages+','+packages;
+					else
+						exportedPackages = packages;
+					putHeader(id[0], Constants.EXPORT_PACKAGE, exportedPackages, packages);
+				}
+			}
+		}
+		/** Reflexively perform our (unusual) work. */
+		void putHeader(String id, String header, String value, String added) {
+			try {
+				Generation gen = getGeneration();
+				Method getRawHeaders = gen.getClass().getDeclaredMethod("getRawHeaders", new Class<?>[0]);
+				getRawHeaders.setAccessible(true);
+				@SuppressWarnings("unchecked")
+				Headers<String,String> headers = (Headers<String, String>) getRawHeaders.invoke(gen, new Object[0]);
+				Field readOnly = headers.getClass().getDeclaredField("readOnly");
+				readOnly.setAccessible(true);
+				readOnly.set(headers, false);
+				// pay-load:
+				headers.put(header, value);
+				// restore:
+				readOnly.setAccessible(false);
+				getRawHeaders.setAccessible(false);
+				fwLog.log(new FrameworkLogEntry(OTStorageHook.class.getName(), "OT/Equinox Turbo: added forced export into base bundle "+id+":\n\t"+added, FrameworkLogEntry.INFO, null, null));
+			} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchFieldException e) {
+				fwLog.log(new FrameworkLogEntry(OTStorageHook.class.getName(), "Unable to inject forced exports", FrameworkLogEntry.ERROR, e, null));
+			}
+		}
+
+		@Override
+		public void load(Object loadContext, DataInputStream is) throws IOException {
+			// nop
+		}
+
+		@Override
+		public void save(Object saveContext, DataOutputStream os) throws IOException {
+			// nop
+		}
+	}
+
+	@Override
+	public void addHooks(HookRegistry hookRegistry) {
+		hookRegistry.addStorageHookFactory(this);
+		fwLog = hookRegistry.getContainer().getLogServices().getFrameworkLog();
+		ForcedExportsRegistry.install(fwLog);
+	}
+	
+	@Override
+	protected StorageHookImpl createStorageHook(Generation generation) {
+		return new StorageHookImpl(generation);
+	}
+
+	@Override
+	public int getStorageVersion() {
+		return 0;
+	}
+
+	@Override
+	public boolean isCompatibleWith(int version) {
+		return false; // FIXME: forcing to re-init every time, is this OK?
+	}
+
+}
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 5bf298e..fbc0730 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
@@ -130,6 +130,7 @@
 		 */
 		public void addImportTo(WovenClass baseClass, int direction) {
 			importsAdded = true;
+			if (AspectBinding.this.hasBeenDenied) return;
 			
 			String packageOfTeam = "";
 			int dot = teamName.lastIndexOf('.'); // TODO: can we detect if thats really the package (vs. Outer.Inner)?
@@ -207,6 +208,11 @@
 			// sub teams?
 			return null;
 		}
+
+		public boolean hasBeenDenied() {
+			return this.checkedPermission == AspectPermission.DENY
+						|| AspectBinding.this.hasBeenDenied;
+		}
 	}
 	
 	/**
@@ -233,6 +239,8 @@
 	public Set<String> allBaseClassNames = new HashSet<>();
 
 	public boolean hasScannedTeams;
+	public AspectPermission forcedExportsPermission = AspectPermission.UNDEFINED;
+	public boolean hasBeenDenied = false;
 	
 	Set<TeamBinding> teamsInProgress = new HashSet<>(); // TODO cleanup teams that are done
 	
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectPermissionManager.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectPermissionManager.java
index 22e9332..36cddb7 100644
--- a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectPermissionManager.java
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/AspectPermissionManager.java
@@ -102,8 +102,8 @@
 	
 	// collect all forced exports (denied/granted), granted should balance to an empty structure.
 	// structure is: aspect-id -> (base bundle x base package)*
-	private HashMap<String, ArrayList<String[]>> deniedForcedExportsByAspect= new HashMap<String, ArrayList<String[]>>();
-	private HashMap<String, ArrayList<String[]>> grantedForcedExportsByAspect= new HashMap<String, ArrayList<String[]>>();
+	private HashMap<String, List<String[]>> deniedForcedExportsByAspect= new HashMap<String, List<String[]>>();
+	private HashMap<String, List<String[]>> grantedForcedExportsByAspect= new HashMap<String, List<String[]>>();
 	
 	// key is aspectId+"->"+baseId, value is array of team names
 	private HashMap<String, Set<String>> deniedTeamsByAspectBinding = new HashMap<String, Set<String>>();
@@ -117,11 +117,14 @@
 	@SuppressWarnings("deprecation")
 	@Nullable private org.osgi.service.packageadmin.PackageAdmin packageAdmin;
 	
+	private ForcedExportsDelegate forcedExportsDelegate;
+	
 	public AspectPermissionManager(Bundle bundle, 
 			@SuppressWarnings("deprecation") @Nullable org.osgi.service.packageadmin.PackageAdmin packageAdmin)
 	{
 		this.transformerBundle = bundle;
 		this.packageAdmin = packageAdmin;
+		this.forcedExportsDelegate = new ForcedExportsDelegate();
 	}
 
 	/* local cache for isReady(): */
@@ -167,7 +170,6 @@
 		}
 	}
 
-
 	/** Load extensions for EP org.eclipse.objectteams.otequinox.aspectBindingNegotiators. */
 	public void loadAspectBindingNegotiators(IExtensionRegistry extensionRegistry) {
 		IConfigurationElement[] aspectBindingNegotiatorsConfigs = extensionRegistry.getConfigurationElementsFor(
@@ -202,13 +204,25 @@
      * @param forcedExports any forced exports requested in this aspect binding.
 	 * @return whether all requests (if any) have been granted
 	 */
-	public boolean checkForcedExports(String aspectId, String baseBundleId, @Nullable IConfigurationElement[] forcedExports) 
-	{
+	public boolean checkForcedExports(AspectBinding aspectBinding) {
+		switch (aspectBinding.forcedExportsPermission) {
+			case GRANT: return true;
+			case DENY: return false;
+			case UNDEFINED: 
+				aspectBinding.forcedExportsPermission = internalCheckForcedExports(aspectBinding);
+				return aspectBinding.forcedExportsPermission == GRANT;
+		}
+		return true;
+	}
+	private AspectPermission internalCheckForcedExports(AspectBinding aspectBinding) {
+		IConfigurationElement[] forcedExports = aspectBinding.forcedExports;
 		if (forcedExports == null || forcedExports.length == 0)
-			return true;
+			return GRANT;
 		
-		ArrayList<String[]> deniedForcedExports = getConfiguredForcedExports(aspectId, DENY,  deniedForcedExportsByAspect);
-		ArrayList<String[]> grantedForcedExports= getConfiguredForcedExports(aspectId, GRANT, grantedForcedExportsByAspect);
+		String aspectId = aspectBinding.aspectPlugin;
+		String baseBundleId = aspectBinding.basePluginName; 
+		List<String[]> deniedForcedExports = getConfiguredForcedExports(aspectId, DENY,  deniedForcedExportsByAspect);
+		List<String[]> grantedForcedExports= getConfiguredForcedExports(aspectId, GRANT, grantedForcedExportsByAspect);
 
 		// iterate all requested forcedExports to search for a matching permission:
 		for (IConfigurationElement forcedExport : forcedExports) { // [0..1] (as defined in the schema)
@@ -228,7 +242,7 @@
 					log(IStatus.ERROR, "Default denial of forced export regarding package "+singleForcedExportRequest+
 									   " from bundle "+baseBundleId+" as requested by bundle "+aspectId+"; bundle not activated");
 					this.deniedAspects.add(aspectId); // keep for answering the TransformerHook.
-					return false; // NOPE!					
+					return DENY; // NOPE!					
 				}
 				
 				// DENY from configuration?
@@ -237,7 +251,7 @@
 					log(IStatus.ERROR, "Explicit denial of forced export regarding package "+singleForcedExportRequest+
 									   " from bundle "+baseBundleId+" as requested by bundle "+aspectId+"; bundle not activated");
 					this.deniedAspects.add(aspectId); // keep for answering the TransformerHook.
-					return false; // NOPE!
+					return DENY; // NOPE!
 				}
 
 				// GRANT from configuration?
@@ -283,13 +297,13 @@
 									   ": "+singleForcedExportRequest+" (from bundle "+baseBundleId+")"+
 									   ". Aspect is not activated.");
 					this.deniedAspects.add(aspectId); // keep for answering the TransformerHook.
-					return false; // don't install illegal aspect
+					return DENY; // don't install illegal aspect
 				}
 			}
 		}
 		if (!grantedForcedExports.isEmpty())
 			reportUnmatchForcedExports(aspectId, grantedForcedExports);
-		return true;
+		return GRANT;
 	}
 
 	/**
@@ -301,22 +315,20 @@
 	 * @param map		in/out param for storing results from OTStorageHook
 	 * @return		 	list of pairs (base bundle x base package)
 	 */
-	private ArrayList<String[]> getConfiguredForcedExports(String                               aspectId, 
-														   AspectPermission 				    perm, 
-														   HashMap<String, ArrayList<String[]>> map) 
+	private List<String[]> getConfiguredForcedExports( String                          aspectId, 
+														AspectPermission 				perm, 
+														HashMap<String, List<String[]>> map)
     {
-		ArrayList<String[]> forcedExports= map.get(aspectId);
+		List<String[]> forcedExports= map.get(aspectId);
 		if (forcedExports == null) {
 			// fetch declarations from config.ini or other locations.
-// FIXME
-//			forcedExports= HookConfigurator.getForcedExportsByAspect(aspectId, perm);
-//			map.put(aspectId, forcedExports);
-			forcedExports = new ArrayList<String[]>();
+			forcedExports= forcedExportsDelegate.getForcedExportsByAspect(aspectId, perm);
+			map.put(aspectId, forcedExports);
 		}
 		return forcedExports;
 	}
 
-	private @Nullable String[] findRequestInList(String baseBundleId, String basePackage, @Nullable ArrayList<String[]> list) {
+	private @Nullable String[] findRequestInList(String baseBundleId, String basePackage, @Nullable List<String[]> list) {
 		if (list != null)
 			for (String[] singleExport : list)
 				if (   singleExport[0].equals(baseBundleId)
@@ -331,7 +343,7 @@
 	 * If the structure of grantedForcedExports is not empty we have mismatches between forced-export declarations.
 	 * Report these mismatches as warnings.
 	 */
-	void reportUnmatchForcedExports(String aspectId, ArrayList<String[]> unmatchedForcedExports) 
+	void reportUnmatchForcedExports(String aspectId, List<String[]> unmatchedForcedExports) 
 	{
 		for (String[] export: unmatchedForcedExports) {
 			String baseId = export[0];
@@ -383,24 +395,36 @@
 		for (TeamBinding teamForBase : teamsForBase) {
 			AspectBinding aspectBinding = teamForBase.getAspectBinding();
 			String aspectBundleName = aspectBinding.aspectPlugin;
-			if (!checkTeamBinding(aspectBundleName, aspectBinding.basePluginName, teamForBase)) {
+			if (aspectBinding.hasBeenDenied) {
 				deniedTeams.add(teamForBase);
-				try {
-					Bundle aspectBundle = aspectBinding.aspectBundle;
-					if (aspectBundle != null) {
-						aspectBundle.stop();
-						log(IStatus.ERROR, "Stopped bundle "+aspectBundleName+" which requests unconfirmed aspect binding(s).");
-					} else {
-						log(IStatus.ERROR, "Cannot stop aspect bundle "+aspectBundleName);
-					}
-				} catch (Throwable t) { // don't let the aspect bundle get by by throwing an unexpected exception!
-					log(t, "Failed to stop bundle "+aspectBundleName+" which requests unconfirmed aspect binding(s).");
+			} else {
+				if (!checkForcedExports(aspectBinding)) {
+					deniedTeams.add(teamForBase);
+					stopAspectBundle(aspectBinding, aspectBundleName, "requests unconfirmed forced export(s).");
+				} else if (!checkTeamBinding(aspectBundleName, aspectBinding.basePluginName, teamForBase)) {
+					deniedTeams.add(teamForBase);
+					stopAspectBundle(aspectBinding, aspectBundleName, "requests unconfirmed aspect binding(s).");
 				}
-			}	
+			}
 		}
 		return deniedTeams;
 	}
 
+	void stopAspectBundle(AspectBinding aspectBinding, String aspectBundleName, String reason) {
+		try {
+			aspectBinding.hasBeenDenied = true;
+			Bundle aspectBundle = aspectBinding.aspectBundle;
+			if (aspectBundle != null) {
+				aspectBundle.stop();
+				log(IStatus.ERROR, "Stopped bundle "+aspectBundleName+" which "+reason);
+			} else {
+				log(IStatus.ERROR, "Cannot stop aspect bundle "+aspectBundleName);
+			}
+		} catch (Throwable t) { // don't let the aspect bundle get by by throwing an unexpected exception!
+			log(t, "Failed to stop bundle "+aspectBundleName+" which "+reason);
+		}
+	}
+
 	/**
 	 * Check permission for the aspect binding of one specific team.
 	 * 
@@ -535,21 +559,6 @@
 		});
 	}
 
-	public void addForcedExportsObligations(final List<AspectBinding> aspects, final Bundle baseBundle) {
-		final String baseBundleName = baseBundle.getSymbolicName();
-		if (baseBundleName == null) {
-			log(IStatus.ERROR, "Cannot handle unnamed base bundle "+baseBundle);
-		} else {
-			schedule(new Runnable () {
-				public void run() {
-					for (AspectBinding aspectBinding : aspects)
-						if (!checkForcedExports(aspectBinding.aspectPlugin, baseBundleName, aspectBinding.forcedExports))
-							stopIllegalBundle(aspectBinding.aspectPlugin);
-				}
-			});
-		}
-	}
-
 	void schedule(Runnable job) {
 		if (isReady()) // became ready since last query?
 			job.run();
@@ -634,17 +643,16 @@
 		if (configFile.exists())
 			parseTeamPermissionFile(deniedTeamsByAspectBinding, configFile);
 
-//		// configured grant / denied for forced exports:
-//
-//		configFilePath = this.otequinoxState.append(DENIED_FORCED_EXPORTS_FILE);
-//		configFile = new File(configFilePath.toOSString());
-//		if (configFile.exists())
-//			HookConfigurator.parseForcedExportsFile(configFile, DENY);
-//		
-//		configFilePath = this.otequinoxState.append(GRANTED_FORCED_EXPORTS_FILE);
-//		configFile = new File(configFilePath.toOSString());
-//		if (configFile.exists())
-//			HookConfigurator.parseForcedExportsFile(configFile, GRANT);
+		// configured grant / denied for forced exports:
+		configFilePath = state.append(DENIED_FORCED_EXPORTS_FILE);
+		configFile = new File(configFilePath.toOSString());
+		if (configFile.exists())
+			forcedExportsDelegate.parseForcedExportsFile(configFile, DENY);
+		
+		configFilePath = state.append(GRANTED_FORCED_EXPORTS_FILE);
+		configFile = new File(configFilePath.toOSString());
+		if (configFile.exists())
+			forcedExportsDelegate.parseForcedExportsFile(configFile, GRANT);
 	}
 
 	private void writeNegotiationDefaults(File configFile)
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 a76fc38..27ba96a 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
@@ -149,4 +149,11 @@
 				return false;
 		return true;
 	}
+
+	public boolean areAllAspectsDenied() {
+		for (AspectBinding binding : aspectBindings)
+			if (!binding.hasBeenDenied)
+				return false;
+		return true;
+	}
 }
diff --git a/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ForcedExportsDelegate.java b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ForcedExportsDelegate.java
new file mode 100644
index 0000000..bb0438a
--- /dev/null
+++ b/plugins/org.eclipse.objectteams.otequinox/src/org/eclipse/objectteams/internal/osgi/weaving/ForcedExportsDelegate.java
@@ -0,0 +1,74 @@
+/**********************************************************************
+ * This file is part of "Object Teams Development Tooling"-Software
+ * 
+ * Copyright 2014 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.eclipse.org/objectteams for updates and contact.
+ * 
+ * Contributors:
+ * 	Stephan Herrmann - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.objectteams.internal.osgi.weaving;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.objectteams.otequinox.AspectPermission;
+import org.eclipse.objectteams.otequinox.TransformerPlugin;
+
+/** Reflexive gateway to a class from fragment org.eclipse.objectteams.otequinox.turbo, if present. */
+public class ForcedExportsDelegate {
+
+	Class<?> registryClass;
+	Method getForcedExportsByAspect;
+	Method parseForcedExportsFile;
+	
+	public ForcedExportsDelegate() {
+		try {
+			registryClass = getClass().getClassLoader().loadClass("org.eclipse.objectteams.otequinox.turbo.ForcedExportsRegistry");
+			getForcedExportsByAspect = registryClass.getMethod("getForcedExportsByAspect", new Class<?>[] { String.class, int.class } );
+			parseForcedExportsFile = registryClass.getMethod("parseForcedExportsFile", new Class<?>[] { File.class, int.class } );
+		} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
+			// ignore, turbo fragment may just be absent.
+		}
+	}
+	
+	public boolean isAvailable() {
+		return registryClass != null;
+	}
+
+	@SuppressWarnings({ "unchecked", "null" }) // neither reflection nor Collections.emptyList() knows about nullness
+	public @NonNull List<String[]> getForcedExportsByAspect(String aspectBundleId, AspectPermission perm) {
+		if (getForcedExportsByAspect != null) {
+			try {
+				return (List<String[]>) getForcedExportsByAspect.invoke(null, new Object[] {aspectBundleId, perm.ordinal()});
+			} catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+				TransformerPlugin.log(e, "Failed to access forced exports");
+			}
+		}
+		return Collections.emptyList();
+	}
+
+	/**
+	 * Called when we know the workspace location to read workspace-specific permissions.
+	 * New information is integrated with existing information inside the ForcedExportsRegistry.
+	 */
+	public void parseForcedExportsFile(File configFile, AspectPermission perm) {
+		if (parseForcedExportsFile != null) {
+			try {
+				parseForcedExportsFile.invoke(null, new Object[] {configFile, perm.ordinal()});
+			} catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+				TransformerPlugin.log(e, "Failed to access forced exports file");
+			}
+		}
+	}
+}
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 0df8a95..83bfa92 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
@@ -8,7 +8,7 @@
  * 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.
+ * Please visit http://www.eclipse.org/objectteams for updates and contact.
  * 
  * Contributors:
  * 	Stephan Herrmann - Initial API and implementation
@@ -186,14 +186,19 @@
 			baseTripWires.put(baseBundleId, new BaseBundleLoadTrigger(baseBundleId, baseBundle, 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) {
+	/**
+	 * Check if the given base bundle / base class mandate any loading/instantiation/activation of teams.
+	 * @return true if all involved aspect bindings have been denied (permissions).
+	 */
+	boolean triggerBaseTripWires(@Nullable String bundleName, @NonNull WovenClass baseClass) {
 		BaseBundleLoadTrigger activation = baseTripWires.get(bundleName);
 		if (activation != null) {
 			activation.fire(baseClass, beingDefined, this);
 			if (activation.isDone())
 				baseTripWires.remove(bundleName);
+			return activation.areAllAspectsDenied();
 		}
+		return false;
 	}
 
 	// ====== Main Weaving Entry: ======
@@ -220,11 +225,13 @@
 			WeavingReason reason = requiresWeaving(bundleWiring, className, bytes);
 			if (reason != WeavingReason.None) {
 				// do whatever is needed *before* loading this class:
-				triggerBaseTripWires(bundleName, wovenClass);
-				if (reason == WeavingReason.Thread) {
+				boolean allAspectsAreDenied = triggerBaseTripWires(bundleName, wovenClass);
+				if (reason == WeavingReason.Base && allAspectsAreDenied) {
+					return; // don't weave for denied bindings
+				} else if (reason == WeavingReason.Thread) {
 					BaseBundle baseBundle = this.aspectBindingRegistry.getBaseBundle(bundleName);
 					BaseBundleLoadTrigger.addOTREImport(baseBundle, bundleName, wovenClass, this.useDynamicWeaver);
-				}
+				} 
 
 				long time = 0;
 
@@ -266,7 +273,7 @@
 		if (aspectBindings != null && !aspectBindings.isEmpty()) {
 			// potential base class: look deeper:
 			for (AspectBinding aspectBinding : aspectBindings) {
-				if (!aspectBinding.hasScannedTeams)
+				if (!aspectBinding.hasScannedTeams && !aspectBinding.hasBeenDenied)
 					return WeavingReason.Base; // we may be first, go ahead and trigger the trip wire
 			}
 			if (isAdaptedBaseClass(aspectBindings, className, bytes, bundleWiring.getClassLoader()))
@@ -295,7 +302,7 @@
 
 		try {
 			for (AspectBinding aspectBinding : aspectBindings) {
-				if (aspectBinding.allBaseClassNames.contains(className))
+				if (aspectBinding.allBaseClassNames.contains(className) && !aspectBinding.hasBeenDenied)
 					return true;					
 			}
 			// attempt recursion to superclass (not superInterfaces atm):
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 93f70b8..404c252 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
@@ -31,7 +31,6 @@
 import org.eclipse.objectteams.internal.osgi.weaving.AspectBinding.TeamBinding;
 import org.eclipse.objectteams.internal.osgi.weaving.Util.ProfileKind;
 import org.eclipse.objectteams.otequinox.ActivationKind;
-import org.eclipse.objectteams.otequinox.AspectPermission;
 import org.eclipse.objectteams.otequinox.TransformerPlugin;
 import org.eclipse.objectteams.otredyn.runtime.TeamManager;
 import org.objectteams.Team;
@@ -93,7 +92,11 @@
 		
 		List<Team> teamInstances = new ArrayList<>();
 		for (TeamBinding teamForBase : teamsForBase) {
-			if (teamForBase.isActivated || teamForBase.checkedPermission == AspectPermission.DENY) continue;
+			if (teamForBase.isActivated) continue;
+			if (teamForBase.hasBeenDenied()) {
+				log(IStatus.WARNING, "Not activating team "+teamForBase.teamName+" due to denied permissions.");
+				continue;
+			}
 			// Load:
 			Class<? extends Team> teamClass;
 			teamClass = teamForBase.loadTeamClass();