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?
+ }
+
+}