Merge pull request #65 from apache/frameworkR8

FELIX-6369: Implement R8 core spec
diff --git a/framework.security/src/main/java/org/apache/felix/framework/FakeBundle.java b/framework.security/src/main/java/org/apache/felix/framework/FakeBundle.java
index f7b3d9b..19a0506 100644
--- a/framework.security/src/main/java/org/apache/felix/framework/FakeBundle.java
+++ b/framework.security/src/main/java/org/apache/felix/framework/FakeBundle.java
@@ -185,11 +185,6 @@
         return System.identityHashCode(this);
     }
 
-    public int compareTo(Bundle o) {
-        // TODO Auto-generated method stub
-        return 0;
-    }
-
     public Object adapt(Class arg0) {
         // TODO Auto-generated method stub
         return null;
@@ -199,9 +194,4 @@
         // TODO Auto-generated method stub
         return null;
     }
-
-    public int compareTo(Object t)
-    {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
 }
diff --git a/framework.security/src/main/java/org/apache/felix/framework/security/util/Permissions.java b/framework.security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
index 4df53f2..801182e 100644
--- a/framework.security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
+++ b/framework.security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
@@ -34,12 +34,16 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.PropertyPermission;
 
 import org.apache.felix.framework.util.SecureAction;
 import org.osgi.framework.AdminPermission;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.CapabilityPermission;
 import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.PackagePermission;
+import org.osgi.framework.ServicePermission;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
@@ -107,12 +111,15 @@
     {
         return new PermissionInfo[] {
             IMPLICIT[0],
-            new PermissionInfo(AdminPermission.class.getName(), "(id="
-                + bundle.getBundleId() + ")", AdminPermission.METADATA),
-            new PermissionInfo(AdminPermission.class.getName(), "(id="
-                + bundle.getBundleId() + ")", AdminPermission.RESOURCE),
-            new PermissionInfo(AdminPermission.class.getName(), "(id="
-                + bundle.getBundleId() + ")", AdminPermission.CONTEXT) };
+            new PermissionInfo(PropertyPermission.class.getName(), "org.osgi.framework.*", "read"),
+            new PermissionInfo(
+                AdminPermission.class.getName(),
+                "(id=" + bundle.getBundleId() + ")",
+                AdminPermission.CLASS + "," + AdminPermission.METADATA + "," + AdminPermission.RESOURCE + "," + AdminPermission.CONTEXT),
+            new PermissionInfo(CapabilityPermission.class.getName(), "(|(capability.namespace=osgi.ee)(capability.namespace=osgi.native))", CapabilityPermission.REQUIRE),
+            new PermissionInfo(PackagePermission.class.getName(),"(package.name=java.*)",PackagePermission.IMPORT),
+            new PermissionInfo(ServicePermission.class.getName(),"org.osgi.service.condition.Condition", ServicePermission.GET)
+        };
     }
 
     public Permissions getPermissions(PermissionInfo[] permissionInfos)
diff --git a/framework/pom.xml b/framework/pom.xml
index 89ab25c..ed9f6f6 100644
--- a/framework/pom.xml
+++ b/framework/pom.xml
@@ -21,7 +21,7 @@
     <groupId>org.apache.felix</groupId>
     <artifactId>felix-parent</artifactId>
     <version>6</version>
-    <relativePath>../../pom/pom.xml</relativePath>
+    <relativePath>../pom/pom.xml</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <packaging>bundle</packaging>
@@ -30,12 +30,12 @@
   <version>6.1.0-SNAPSHOT</version>
   <properties>
     <dollar>$</dollar>
-    <felix.java.version>6</felix.java.version>
+    <felix.java.version>8</felix.java.version>
   </properties>
   <scm>
-      <connection>scm:git:https://github.com/apache/felix-dev.git</connection>
-      <developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
-      <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
+    <connection>scm:git:https://github.com/apache/felix-dev.git</connection>
+    <developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
+    <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
     <tag>HEAD</tag>
   </scm>
 
@@ -44,13 +44,13 @@
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
-        <version>4.1.0</version>
+        <version>5.1.1</version>
         <extensions>true</extensions>
         <configuration>
           <instructions>
             <Bundle-SymbolicName>org.apache.felix.framework</Bundle-SymbolicName>
             <Bundle-Name>Apache Felix Framework</Bundle-Name>
-            <Bundle-Description>OSGi R7 framework implementation.</Bundle-Description>
+            <Bundle-Description>OSGi R8 framework implementation.</Bundle-Description>
             <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
             <Export-Package>
                 org.osgi.framework.*;-split-package:=first,
@@ -61,7 +61,8 @@
                 org.osgi.service.url;-split-package:=first,
                 org.osgi.service.resolver,
                 org.osgi.util.tracker;-split-package:=first,
-                org.osgi.dto;-split-package:=first
+                org.osgi.dto;-split-package:=first,
+                org.osgi.service.condition;-split-package:=first
             </Export-Package>
             <Private-Package>org.apache.felix.framework.*, org.apache.felix.resolver.*</Private-Package>
             <Import-Package>!*</Import-Package>
@@ -90,7 +91,7 @@
               <excludes>
                   <exclude>src/main/appended-resources/**</exclude>
                   <exclude>src/**/packageinfo</exclude>
-                  <exclude>src/main/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory</exclude>
+                  <exclude>src/main/resources/META-INF/**</exclude>
                   <exclude>src/main/resources/org/apache/felix/framework/Felix.properties</exclude>
               </excludes>
             </configuration>
@@ -119,18 +120,19 @@
       <resource>
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
+        <excludes>
+        <exclude>**/*.bytes</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <includes>
+          <include>**/*.bytes</include>
+        </includes>
       </resource>
     </resources>
   </build>
-  <dependencyManagement>
-      <dependencies>
-          <dependency>
-              <groupId>org.hamcrest</groupId>
-              <artifactId>hamcrest-core</artifactId>
-              <version>1.3</version>
-          </dependency>
-      </dependencies>
-  </dependencyManagement>
   <dependencies>
     <dependency>
        <groupId>org.osgi</groupId>
@@ -153,7 +155,7 @@
     <dependency>
         <groupId>org.ow2.asm</groupId>
         <artifactId>asm-all</artifactId>
-        <version>4.2</version>
+        <version>5.2</version>
         <scope>test</scope>
     </dependency>
     <dependency>
@@ -164,7 +166,7 @@
     </dependency>
     <dependency>
         <groupId>org.mockito</groupId>
-        <artifactId>mockito-core</artifactId>
+        <artifactId>mockito-all</artifactId>
         <version>1.10.19</version>
         <scope>test</scope>
     </dependency>
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
index d3b6724..7dd59d0 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
@@ -67,6 +67,7 @@
 
     private final BundleArchive m_archive;
     private final List<BundleRevisionImpl> m_revisions = new ArrayList<BundleRevisionImpl>(0);
+    private volatile BundleRevisionImpl m_currentRevision = null;
     private volatile int m_state;
     private boolean m_useDeclaredActivationPolicy;
     private BundleActivator m_activator = null;
@@ -188,16 +189,24 @@
         {
             // Get current revision, since we can reuse it.
             BundleRevisionImpl current = adapt(BundleRevisionImpl.class);
-            // Close all existing revisions.
-            closeRevisions();
-            // Clear all revisions.
+
+            if (isRemovalPending()) {
+                closeRevisions();
+                // Purge all old archive revisions, only keeping the newest one.
+                m_archive.purge();
+
+                current.resetContent(m_archive.getCurrentRevision().getContent());
+            }
+            else {
+                // Remove the revision from the resolver state.
+                getFramework().getResolver().removeRevision(current);
+                current.resolve(null);
+                current.disposeContentPath();
+            }
+
             m_revisions.clear();
+            m_currentRevision = null;
 
-            // Purge all old archive revisions, only keeping the newest one.
-            m_archive.purge();
-
-            // Reset the content of the current bundle revision.
-            current.resetContent(m_archive.getCurrentRevision().getContent());
             // Re-add the revision to the bundle.
             addRevision(current);
 
@@ -1091,7 +1100,7 @@
     }
 
     @Override
-    public synchronized <A> A adapt(Class<A> type)
+    public <A> A adapt(Class<A> type)
     {
         checkAdapt(type);
         if (type == BundleContext.class)
@@ -1109,14 +1118,14 @@
             {
                 return null;
             }
-            return (A) m_revisions.get(0);
+            return (A) m_currentRevision;
         }
         // We need some way to get the current revision even if
         // the associated bundle is uninstalled, so we use the
         // impl revision class for this purpose.
         else if (type == BundleRevisionImpl.class)
         {
-            return (A) m_revisions.get(0);
+            return (A) m_currentRevision;
         }
         else if (type == BundleRevisions.class)
         {
@@ -1128,7 +1137,9 @@
             {
                 return null;
             }
-            return (A) m_revisions.get(0).getWiring();
+            BundleRevisionImpl revision = m_currentRevision;
+
+            return (A) (revision != null ? revision.getWiring() : null);
         }
         else if ( type == AccessControlContext.class)
         {
@@ -1229,6 +1240,8 @@
     synchronized boolean rollbackRevise() throws Exception
     {
         BundleRevision br = m_revisions.remove(0);
+        m_currentRevision = !m_revisions.isEmpty() ? m_revisions.get(0) : null;
+
         // Since revising a bundle adds a revision to the global
         // state, we must remove it from the global state on rollback.
         getFramework().getResolver().removeRevision(br);
@@ -1242,7 +1255,9 @@
     // which is the normal case.
     synchronized void addRevision(BundleRevisionImpl revision) throws Exception
     {
+        BundleRevisionImpl previous = m_currentRevision;
         m_revisions.add(0, revision);
+        m_currentRevision = revision;
 
         try
         {
@@ -1251,6 +1266,7 @@
         catch (Exception ex)
         {
             m_revisions.remove(0);
+            m_currentRevision = previous;
             throw ex;
         }
 
@@ -1332,7 +1348,7 @@
                     }
                 }
             }
-            if (!collisionCanditates.isEmpty())
+            if (!collisionCanditates.isEmpty() && m_installingBundle != null)
             {
                 throw new BundleException(
                     "Bundle symbolic name and version are not unique: "
@@ -1347,7 +1363,7 @@
     {
         ProtectionDomain pd = null;
 
-        for (int i = m_revisions.size() - 1; (i >= 0) && (pd == null); i--)
+        for (int i = 0; (i < m_revisions.size()) && (pd == null); i++)
         {
             pd = m_revisions.get(i).getProtectionDomain();
         }
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleProtectionDomain.java b/framework/src/main/java/org/apache/felix/framework/BundleProtectionDomain.java
index ccd4807..9d51085 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleProtectionDomain.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleProtectionDomain.java
@@ -21,13 +21,10 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.ref.WeakReference;
-import java.lang.reflect.InvocationTargetException;
-import java.net.JarURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
@@ -45,16 +42,12 @@
 import java.util.Enumeration;
 import java.util.List;
 import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
 import java.util.jar.JarOutputStream;
 import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.cache.JarContent;
 import org.apache.felix.framework.util.FelixConstants;
-import org.osgi.framework.Bundle;
 import org.osgi.framework.PackagePermission;
 
-import org.osgi.framework.wiring.BundleRevision;
-
 public class BundleProtectionDomain extends ProtectionDomain
 {
     private static final class BundleInputStream extends InputStream
@@ -76,7 +69,7 @@
 
             int count = 0;
             String manifest = null;
-            for (Enumeration e = m_root.getEntries(); e.hasMoreElements();)
+            for (Enumeration e = m_root.getEntries(); e != null && e.hasMoreElements();)
             {
                 String entry = (String) e.nextElement();
                 if (entry.endsWith("/"))
@@ -264,12 +257,12 @@
                 {
                     target = Felix.m_secureAction.createTempFile("jar", null, null);
                     Felix.m_secureAction.deleteFileOnExit(target);
-                    FileOutputStream output = null;
+                    OutputStream output = null;
                     InputStream input = null;
                     IOException rethrow = null;
                     try
                     {
-                        output = new FileOutputStream(target);
+                        output = Felix.m_secureAction.getOutputStream(target);
                         input = new BundleInputStream(content);
                         byte[] buffer = new byte[64 * 1024];
                         for (int i = input.read(buffer);i != -1; i = input.read(buffer))
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
index 036732b..5aa9daa 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleRevisionImpl.java
@@ -319,6 +319,15 @@
         }
     }
 
+    synchronized void disposeContentPath()
+    {
+        for (int i = 0; (m_contentPath != null) && (i < m_contentPath.size()); i++)
+        {
+            m_contentPath.get(i).close();
+        }
+        m_contentPath = null;
+    }
+
     public void setProtectionDomain(ProtectionDomain pd)
     {
         m_protectionDomain = pd;
@@ -616,6 +625,25 @@
         return getContentPath().get(index - 1).getEntryAsStream(urlPath);
     }
 
+
+    public long getContentTime(int index, String urlPath)
+    {
+        if (urlPath.startsWith("/"))
+        {
+            urlPath = urlPath.substring(1);
+        }
+        Content content;
+        if (index == 0)
+        {
+            content = getContent();
+        }
+        else {
+            content = getContentPath().get(index - 1);
+        }
+        long result = content.getContentTime(urlPath);
+        return result > 0 ? result : m_bundle.getLastModified();
+    }
+
     public URL getLocalURL(int index, String urlPath)
     {
         if (urlPath.startsWith("/"))
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
index 437d962..0e970bc 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.framework;
 
+import org.apache.felix.framework.cache.ConnectContentContent;
 import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.capabilityset.SimpleFilter;
 import org.apache.felix.framework.resolver.ResourceNotFoundException;
@@ -112,7 +113,7 @@
 
     private volatile List<BundleRequirement> m_wovenReqs = null;
 
-    private volatile BundleClassLoader m_classLoader;
+    private volatile ClassLoader m_classLoader;
 
     // Bundle-specific class loader for boot delegation.
     private final ClassLoader m_bootClassLoader;
@@ -726,16 +727,24 @@
         // is not disposed.
         if (!m_isDisposed && (m_classLoader == null))
         {
-            m_classLoader = BundleRevisionImpl.getSecureAction().run(
-                new PrivilegedAction<BundleClassLoader>()
-                {
-                    @Override
-                    public BundleClassLoader run()
+            if (m_revision.getContent() instanceof ConnectContentContent)
+            {
+                m_classLoader = ((ConnectContentContent) m_revision.getContent()).getClassLoader();
+            }
+
+            if (m_classLoader == null)
+            {
+                m_classLoader = BundleRevisionImpl.getSecureAction().run(
+                    new PrivilegedAction<BundleClassLoader>()
                     {
-                        return new BundleClassLoader(BundleWiringImpl.this, determineParentClassLoader(), m_logger);
+                        @Override
+                        public BundleClassLoader run()
+                        {
+                            return new BundleClassLoader(BundleWiringImpl.this, determineParentClassLoader(), m_logger);
+                        }
                     }
-                }
-            );
+                );
+            }
         }
         return m_classLoader;
     }
@@ -1490,24 +1499,21 @@
 
                     try
                     {
-                        result = tryImplicitBootDelegation(name, isClass);
-                    }
-                    catch (Exception ex)
-                    {
-                        // Ignore, will throw using CNFE_CLASS_LOADER
-                    }
+                        // Get the appropriate class loader for delegation.
+                        ClassLoader bdcl = getBootDelegationClassLoader();
+                        result = (isClass) ? (Object) bdcl.loadClass(name) : (Object) bdcl.getResource(name);
 
-                    if (result != null)
-                    {
-                        m_accessorLookupCache.put(name, BundleRevisionImpl.getSecureAction()
-                                .getClassLoader(this.getClass()));
-                        return result;
+                        if (result != null)
+                        {
+                            m_accessorLookupCache.put(name, bdcl);
+                            return result;
+                        }
                     }
-                    else
+                    catch (ClassNotFoundException ex)
                     {
-                        m_accessorLookupCache.put(name, CNFE_CLASS_LOADER);
-                        CNFE_CLASS_LOADER.loadClass(name);
                     }
+                    m_accessorLookupCache.put(name, CNFE_CLASS_LOADER);
+                    CNFE_CLASS_LOADER.loadClass(name);
                 }
 
                 // Look in the revision's imports. Note that the search may
@@ -1518,25 +1524,39 @@
                 // If not found, try the revision's own class path.
                 if (result == null)
                 {
-                    if (isClass)
+                    ClassLoader cl = getClassLoaderInternal();
+                    if (cl == null)
                     {
-                        ClassLoader cl = getClassLoaderInternal();
-                        if (cl == null)
+                        if (isClass)
                         {
                             throw new ClassNotFoundException(
-                                    "Unable to load class '"
-                                            + name
-                                            + "' because the bundle wiring for "
-                                            + m_revision.getSymbolicName()
-                                            + " is no longer valid.");
+                                "Unable to load class '"
+                                    + name
+                                    + "' because the bundle wiring for "
+                                    + m_revision.getSymbolicName()
+                                    + " is no longer valid.");
                         }
-                        result = ((BundleClassLoader) cl).findClass(name);
+                        else
+                        {
+                            throw new ResourceNotFoundException("Unable to load resource '"
+                                + name
+                                + "' because the bundle wiring for "
+                                + m_revision.getSymbolicName()
+                                + " is no longer valid.");
+                        }
+                    }
+                    if (cl instanceof BundleClassLoader)
+                    {
+                        result = isClass ? ((BundleClassLoader) cl).findClass(name) :
+                            ((BundleClassLoader) cl).findResource(name);
                     }
                     else
                     {
-                        result = m_revision.getResourceLocal(name);
+                        result = isClass ? cl.loadClass(name) : !name.startsWith("/") ? cl.getResource(name) :
+                        cl.getResource(name.substring(1));
                     }
 
+
                     // If still not found, then try the revision's dynamic imports.
                     if (result == null)
                     {
@@ -2052,7 +2072,7 @@
                         catch (Error e)
                         {
                             // Mark the woven class as incomplete.
-                            wci.complete(null, null, null);
+                            wci.complete();
                             wci.setState(WovenClass.TRANSFORMING_FAILED);
                             callWovenClassListeners(felix, wovenClassListeners, wci);
                             throw e;
diff --git a/framework/src/main/java/org/apache/felix/framework/DTOFactory.java b/framework/src/main/java/org/apache/felix/framework/DTOFactory.java
index 0abcf9c..7d38e40 100644
--- a/framework/src/main/java/org/apache/felix/framework/DTOFactory.java
+++ b/framework/src/main/java/org/apache/felix/framework/DTOFactory.java
@@ -120,6 +120,11 @@
         return null;
     }
 
+    static ServiceReferenceDTO createDTO(ServiceReference ref)
+    {
+        return createServiceReferenceDTO(ref);
+    }
+
     private static BundleDTO createBundleDTO(Bundle bundle)
     {
         BundleDTO dto = new BundleDTO();
diff --git a/framework/src/main/java/org/apache/felix/framework/ExtensionManager.java b/framework/src/main/java/org/apache/felix/framework/ExtensionManager.java
index a5c8664..5ed9955 100644
--- a/framework/src/main/java/org/apache/felix/framework/ExtensionManager.java
+++ b/framework/src/main/java/org/apache/felix/framework/ExtensionManager.java
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.framework;
 
+import org.apache.felix.framework.cache.ConnectContentContent;
 import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.cache.DirectoryContent;
 import org.apache.felix.framework.cache.JarContent;
@@ -53,8 +54,6 @@
 import org.osgi.framework.wiring.BundleWiring;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -245,11 +244,23 @@
 
         String sysprops = felix._getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES);
 
+
+        boolean subst = "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES));
+
+        if (sysprops != null && sysprops.isEmpty())
+        {
+            if (felix.hasConnectFramework())
+            {
+                subst = true;
+                sysprops = "${osgi-exports}";
+                config.put(Constants.FRAMEWORK_SYSTEMPACKAGES, sysprops);
+            }
+        }
+
         final Map<String, Set<String>> exports = Util.initializeJPMS(defaultProperties);
 
         if (exports != null && (sysprops == null || "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES))))
         {
-            java.nio.file.FileSystem fs = java.nio.file.FileSystems.getFileSystem(URI.create("jrt:/"));
             final ClassParser classParser = new ClassParser();
             final Set<String> imports = new HashSet<String>();
             for (Set<String> moduleImport : exports.values())
@@ -271,15 +282,16 @@
                     final SortedMap<String, SortedSet<String>> referred = new TreeMap<String, SortedSet<String>>();
                     if ("true".equalsIgnoreCase(felix._getProperty(FelixConstants.CALCULATE_SYSTEMPACKAGES_USES)))
                     {
+                        java.nio.file.FileSystem fs = java.nio.file.FileSystems.getFileSystem(URI.create("jrt:/"));
                         try
                         {
                             Properties cachedProps = new Properties();
                             File modulesDir = felix.getDataFile(felix, "modules");
-                            modulesDir.mkdirs();
+                            Felix.m_secureAction.mkdirs(modulesDir);
                             File cached = new File(modulesDir, moduleKey + ".properties");
-                            if (cached.isFile())
+                            if (Felix.m_secureAction.isFile(cached))
                             {
-                                FileInputStream input = new FileInputStream(cached);
+                                InputStream input = Felix.m_secureAction.getInputStream(cached);
                                 cachedProps.load(new InputStreamReader(input, "UTF-8"));
                                 input.close();
                                 for (Enumeration<?> keys = cachedProps.propertyNames(); keys.hasMoreElements();)
@@ -301,7 +313,7 @@
                                         cachedProps.setProperty(pkg, String.join(",", uses));
                                     }
                                 }
-                                OutputStream output = new FileOutputStream(cached);
+                                OutputStream output = Felix.m_secureAction.getOutputStream(cached);
                                 cachedProps.store(new OutputStreamWriter(output, "UTF-8"), null);
                                 output.close();
                             }
@@ -344,7 +356,7 @@
             }
         }
 
-        if(sysprops != null && "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES)))
+        if(sysprops != null && subst)
         {
             config.put(Constants.FRAMEWORK_SYSTEMPACKAGES, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMPACKAGES));
         }
@@ -414,23 +426,6 @@
             ((BundleRevisionImpl) bundle.adapt(BundleRevision.class))
                 .getHeaders().get(Constants.FRAGMENT_HOST));
 
-        if (!Constants.EXTENSION_FRAMEWORK.equals(directive))
-        {
-           throw new BundleException("Unsupported Extension Bundle type: " +
-                directive, new UnsupportedOperationException(
-                "Unsupported Extension Bundle type!"));
-        }
-        else if (m_extenderFramework == null)
-        {
-            // We don't support extensions
-            m_logger.log(bundle, Logger.LOG_WARNING,
-                "Unable to add extension bundle - Maybe ClassLoader is not supported " +
-                        "(on java9, try --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED)?");
-
-            throw new UnsupportedOperationException(
-                "Unable to add extension bundle.");
-        }
-
         Content content = bundle.adapt(BundleRevisionImpl.class).getContent();
         final File file;
         if (content instanceof JarContent)
@@ -445,14 +440,31 @@
         {
             file = null;
         }
-        if (file == null)
+        if (file == null && !(content instanceof ConnectContentContent))
         {
             // We don't support revision type for extension
             m_logger.log(bundle, Logger.LOG_WARNING,
-                    "Unable to add extension bundle - wrong revision type?");
+                "Unable to add extension bundle - wrong revision type?");
 
             throw new UnsupportedOperationException(
-                    "Unable to add extension bundle.");
+                "Unable to add extension bundle.");
+        }
+
+        if (!Constants.EXTENSION_FRAMEWORK.equals(directive))
+        {
+           throw new BundleException("Unsupported Extension Bundle type: " +
+                directive, new UnsupportedOperationException(
+                "Unsupported Extension Bundle type!"));
+        }
+        else if (m_extenderFramework == null && file != null)
+        {
+            // We don't support extensions
+            m_logger.log(bundle, Logger.LOG_WARNING,
+                "Unable to add extension bundle - Maybe ClassLoader is not supported " +
+                        "(on java9, try --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED)?");
+
+            throw new UnsupportedOperationException(
+                "Unable to add extension bundle.");
         }
 
         BundleRevisionImpl bri = bundle.adapt(BundleRevisionImpl.class);
@@ -559,25 +571,33 @@
             {
                 f = ((JarContent) revisionContent).getFile();
             }
-            else
+            else if (revisionContent instanceof DirectoryContent)
             {
                 f = ((DirectoryContent) revisionContent).getFile();
             }
-            try
+            else
             {
-                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>()
-                {
-                    @Override
-                    public Void run() throws Exception {
-                        m_extenderFramework.add(f);
-                        return null;
-                    }
-                });
+                f = null;
             }
-            catch (Exception ex)
+            if (f != null)
             {
-                m_logger.log(revision.getBundle(), Logger.LOG_ERROR,
-                    "Error adding extension bundle to framework classloader: " + revision.getBundle(), ex);
+                try
+                {
+                    AccessController.doPrivileged(new PrivilegedExceptionAction<Void>()
+                    {
+                        @Override
+                        public Void run() throws Exception
+                        {
+                            m_extenderFramework.add(f);
+                            return null;
+                        }
+                    });
+                }
+                catch (Exception ex)
+                {
+                    m_logger.log(revision.getBundle(), Logger.LOG_ERROR,
+                        "Error adding extension bundle to framework classloader: " + revision.getBundle(), ex);
+                }
             }
 
             felix.setBundleStateAndNotify(revision.getBundle(), Bundle.RESOLVED);
@@ -878,6 +898,12 @@
         return null;
     }
 
+    @Override
+    public long getContentTime(String name)
+    {
+        return -1L;
+    }
+
     //
     // Utility methods.
     //
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 46792c5..ade90ee 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -58,6 +58,7 @@
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.Version;
+import org.osgi.framework.connect.ModuleConnector;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.namespace.HostNamespace;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
@@ -74,9 +75,7 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -213,6 +212,8 @@
     // Do we need to consult the default java security policy if no security provider is present?
     private volatile boolean m_securityDefaultPolicy;
 
+    private final ModuleConnector m_connectFramework;
+
     /**
      * <p>
      * This constructor creates a framework instance with a specified <tt>Map</tt>
@@ -351,6 +352,11 @@
     **/
     public Felix(Map configMap)
     {
+        this(configMap, null);
+    }
+
+    public Felix(Map configMap, ModuleConnector connectFramework)
+    {
         super();
         // Copy the configuration properties; convert keys to strings.
         m_configMutableMap = new StringMap();
@@ -460,6 +466,8 @@
         m_fwkWiring = new FrameworkWiringImpl(this, m_registry);
         // Create framework start level object.
         m_fwkStartLevel = new FrameworkStartLevelImpl(this, m_registry);
+
+        m_connectFramework = connectFramework;
     }
 
     Logger getLogger()
@@ -634,12 +642,12 @@
         return true;
     }
 
-
     @Override
     public void init() throws BundleException
     {
-        init((FrameworkListener[]) null);
+        init(null);
     }
+
     /**
      * @see org.osgi.framework.launch.Framework#init(org.osgi.framework.FrameworkListener[])
      */
@@ -726,6 +734,10 @@
                             throw new BundleException("Unable to flush bundle cache.", ex);
                         }
                     }
+                    if (m_connectFramework != null)
+                    {
+                        m_connectFramework.initialize(m_cache.getCacheDir(), (Map) m_configMap);
+                    }
                 }
 
                 // Initialize installed bundle data structures.
@@ -774,7 +786,6 @@
                         "Unresolved constraint in System Bundle:"
                         + ex.getUnresolvedRequirements());
                 }
-
                 // Reload the cached bundles before creating and starting the
                 // system bundle, since we want all cached bundles to be reloaded
                 // when we activate the system bundle and any subsequent system
@@ -784,7 +795,7 @@
                 // First get cached bundle identifiers.
                 try
                 {
-                    archives = m_cache.getArchives();
+                    archives = m_cache.getArchives(m_connectFramework);
                 }
                 catch (Exception ex)
                 {
@@ -850,6 +861,12 @@
                     m_extensionManager.startExtensionBundle(this, (BundleImpl) extension);
                 }
 
+
+                if (m_connectFramework != null)
+                {
+                    m_connectFramework.newBundleActivator().ifPresent(m_activatorList::add);
+                }
+
                 // Now that we have loaded all cached bundles and have determined the
                 // max bundle ID of cached bundles, we need to try to load the next
                 // bundle ID from persistent storage. In case of failure, we should
@@ -884,7 +901,6 @@
                 }
                 catch (Throwable ex)
                 {
-                    m_dispatcher.stopDispatching();
                     m_logger.log(Logger.LOG_ERROR, "Unable to start system bundle.", ex);
                     throw new RuntimeException("Unable to start system bundle.");
                 }
@@ -948,6 +964,17 @@
                 }
             }
         }
+        catch (Throwable t)
+        {
+            stopBundle(this, false);
+            if (m_cache != null)
+            {
+                m_cache.release();
+                m_cache = null;
+            }
+            __setState(Bundle.INSTALLED);
+            throw t;
+        }
         finally
         {
             releaseBundleLock(this);
@@ -987,7 +1014,7 @@
             BufferedReader input = null;
             try
             {
-                input = new BufferedReader(new InputStreamReader(new FileInputStream(dataFile), "UTF-8"));
+                input = new BufferedReader(new InputStreamReader(m_secureAction.getInputStream(dataFile), "UTF-8"));
                 lastVersion = Version.parseVersion(input.readLine()).getMajor();
             }
             catch (Exception ignore)
@@ -1020,7 +1047,7 @@
         try
         {
             output = new PrintWriter(new OutputStreamWriter(
-                new FileOutputStream(getDataFile(this, "last.java.version")), "UTF-8"));
+                m_secureAction.getOutputStream(getDataFile(this, "last.java.version")), "UTF-8"));
             output.println(Integer.toString(currentVersion));
             output.flush();
         }
@@ -3224,7 +3251,7 @@
                 try
                 {
                     // Add the bundle to the cache.
-                    ba = m_cache.create(id, getInitialBundleStartLevel(), location, is);
+                    ba = m_cache.create(id, getInitialBundleStartLevel(), location, is, m_connectFramework);
                 }
                 catch (Exception ex)
                 {
@@ -3760,8 +3787,11 @@
                 Class clazz = Util.loadClassUsingClass(svcObj.getClass(), classNames[i], m_secureAction);
                 if (clazz == null)
                 {
-                    throw new IllegalArgumentException(
-                        "Cannot cast service: " + classNames[i]);
+                    if (!Util.checkImplementsWithName(svcObj.getClass(), classNames[i]))
+                    {
+                        throw new IllegalArgumentException(
+                                "Cannot cast service: " + classNames[i]);
+                    }
                 }
                 else if (!clazz.isAssignableFrom(svcObj.getClass()))
                 {
@@ -5025,9 +5055,12 @@
             try
             {
                 File file = m_cache.getSystemBundleDataFile("bundle.id");
-                is = m_secureAction.getFileInputStream(file);
-                br = new BufferedReader(new InputStreamReader(is));
-                return Long.parseLong(br.readLine());
+                if (m_secureAction.isFile(file))
+                {
+                    is = m_secureAction.getInputStream(file);
+                    br = new BufferedReader(new InputStreamReader(is));
+                    return Long.parseLong(br.readLine());
+                }
             }
             catch (FileNotFoundException ex)
             {
@@ -5078,7 +5111,7 @@
             try
             {
                 File file = m_cache.getSystemBundleDataFile("bundle.id");
-                os = m_secureAction.getFileOutputStream(file);
+                os = m_secureAction.getOutputStream(file);
                 bw = new BufferedWriter(new OutputStreamWriter(os));
                 String s = Long.toString(m_nextId);
                 bw.write(s, 0, s.length());
@@ -5110,18 +5143,28 @@
         }
     }
 
+    public boolean hasConnectFramework()
+    {
+        return m_connectFramework != null;
+    }
+
     //
     // Miscellaneous inner classes.
     //
 
     class SystemBundleActivator implements BundleActivator
     {
+        private volatile ServiceRegistration<org.osgi.service.condition.Condition> m_reg;
         @Override
         public void start(BundleContext context) throws Exception
         {
             // Add the bundle activator for the url handler service.
             m_activatorList.add(0, new URLHandlersActivator(m_configMap, Felix.this));
 
+
+            m_reg = context.registerService(org.osgi.service.condition.Condition.class, org.osgi.service.condition.Condition.INSTANCE,
+                FrameworkUtil.asDictionary(Collections.singletonMap(org.osgi.service.condition.Condition.CONDITION_ID, org.osgi.service.condition.Condition.CONDITION_ID_TRUE)));
+
             // Start all activators.
             for (Iterator<BundleActivator> iter = m_activatorList.iterator(); iter.hasNext(); )
             {
@@ -5132,6 +5175,7 @@
                 }
                 catch (Throwable throwable)
                 {
+                    throwable.printStackTrace();
                     iter.remove();
                     fireFrameworkEvent(FrameworkEvent.ERROR, context.getBundle(),
                             new BundleException("Unable to start Bundle", throwable));
@@ -5141,6 +5185,7 @@
                         throwable);
                 }
             }
+
         }
 
         @Override
@@ -5163,6 +5208,8 @@
             // Stop framework start level thread.
             m_fwkStartLevel.stop();
 
+            m_resolver.stop();
+
             // Shutdown event dispatching queue.
             m_dispatcher.stopDispatching();
 
@@ -5234,6 +5281,8 @@
                         throwable);
                 }
             }
+            m_reg.unregister();
+            m_activatorList.clear();
             if (m_securityManager != null)
             {
                 System.setSecurityManager(null);
diff --git a/framework/src/main/java/org/apache/felix/framework/FrameworkFactory.java b/framework/src/main/java/org/apache/felix/framework/FrameworkFactory.java
index ce377ad..938845f 100644
--- a/framework/src/main/java/org/apache/felix/framework/FrameworkFactory.java
+++ b/framework/src/main/java/org/apache/felix/framework/FrameworkFactory.java
@@ -19,12 +19,20 @@
 package org.apache.felix.framework;
 
 import java.util.Map;
+
+import org.osgi.framework.connect.ModuleConnector;
 import org.osgi.framework.launch.Framework;
 
-public class FrameworkFactory implements org.osgi.framework.launch.FrameworkFactory
+public class FrameworkFactory implements org.osgi.framework.launch.FrameworkFactory, org.osgi.framework.connect.ConnectFrameworkFactory
 {
     public Framework newFramework(Map configuration)
     {
         return new Felix(configuration);
     }
+
+    @Override
+    public Framework newFramework(Map<String, String> configuration, ModuleConnector connectFramework)
+    {
+        return new Felix(configuration, connectFramework);
+    }
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/FrameworkWiringImpl.java b/framework/src/main/java/org/apache/felix/framework/FrameworkWiringImpl.java
index 7bc42ce..f051ac0 100644
--- a/framework/src/main/java/org/apache/felix/framework/FrameworkWiringImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/FrameworkWiringImpl.java
@@ -63,9 +63,6 @@
      * thread explicitly is required in the embedded case, where Felix may be
      * stopped without the Java VM being stopped. In this case the
      * FelixFrameworkWiring thread must be stopped explicitly.
-     * <p>
-     * This method is called by the
-     * {@link PackageAdminActivator#stop(BundleContext)} method.
      */
     void stop()
     {
diff --git a/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java b/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
index 9169900..614797a 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
@@ -42,6 +42,7 @@
 import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.dto.ServiceReferenceDTO;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWire;
@@ -363,9 +364,12 @@
                 {
                     if (clazz == null)
                     {
-                        throw new ServiceException(
-                            "Service cannot be cast due to missing class: " + m_classes[i],
-                            ServiceException.FACTORY_ERROR);
+                        if (!Util.checkImplementsWithName(svcObj.getClass(), m_classes[i]))
+                        {
+                            throw new ServiceException(
+                                    "Service cannot be cast due to missing class: " + m_classes[i],
+                                    ServiceException.FACTORY_ERROR);
+                        }
                     }
                     else
                     {
@@ -696,9 +700,20 @@
         }
 
         @Override
-        public Dictionary<String, Object> getProperties() {
+        public Dictionary<String, Object> getProperties()
+        {
             return new Hashtable<String, Object>(ServiceRegistrationImpl.this.m_propMap);
         }
+
+        @Override
+        public Object adapt(Class type)
+        {
+            if (type == ServiceReferenceDTO.class)
+            {
+                return DTOFactory.createDTO(this);
+            }
+            return null;
+        }
     }
 
     private class ServiceReferenceMap implements Map
diff --git a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
index 2d71eae..cca8544 100644
--- a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
+++ b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
@@ -31,7 +31,6 @@
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -45,7 +44,6 @@
 import org.apache.felix.framework.util.FelixConstants;
 import org.apache.felix.framework.util.ShrinkableCollection;
 import org.apache.felix.framework.util.Util;
-import org.apache.felix.framework.util.manifestparser.NativeLibrary;
 import org.apache.felix.framework.wiring.BundleRequirementImpl;
 import org.apache.felix.framework.wiring.BundleWireImpl;
 import org.apache.felix.resolver.ResolverImpl;
@@ -57,6 +55,7 @@
 import org.osgi.framework.Constants;
 import org.osgi.framework.PackagePermission;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.hooks.resolver.ResolverHook;
 import org.osgi.framework.hooks.resolver.ResolverHookFactory;
 import org.osgi.framework.wiring.BundleCapability;
@@ -91,6 +90,7 @@
     private final Map<String, List<BundleRevision>> m_singletons;
     // Selected singleton bundle revisions.
     private final Set<BundleRevision> m_selectedSingletons;
+    private volatile ServiceRegistration<?> m_serviceRegistration;
 
     StatefulResolver(Felix felix, ServiceRegistry registry)
     {
@@ -168,12 +168,22 @@
 
     void start()
     {
-        m_registry.registerService(m_felix,
-                new String[] { Resolver.class.getName() },
+        m_serviceRegistration = m_registry.registerService(m_felix,
+                new String[]{Resolver.class.getName()},
                 new ResolverImpl(m_logger, 1),
                 null);
     }
 
+    void stop()
+    {
+        ServiceRegistration reg = m_serviceRegistration;
+        if (reg != null)
+        {
+            reg.unregister();
+            m_serviceRegistration = null;
+        }
+    }
+
     synchronized void addRevision(BundleRevision br)
     {
         // Always attempt to remove the revision, since
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlers.java b/framework/src/main/java/org/apache/felix/framework/URLHandlers.java
index 79b13ce..88b65e9 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlers.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlers.java
@@ -117,23 +117,34 @@
             ? DEFAULT_STREAM_HANDLER_PACKAGE
             : pkgs + "|" + DEFAULT_STREAM_HANDLER_PACKAGE;
         m_loaded = (null != URLHandlersStreamHandlerProxy.class) &&
-            (null != URLHandlersContentHandlerProxy.class) && (null != URLStreamHandlerService.class);
+            (null != URLHandlersContentHandlerProxy.class) && (null != URLStreamHandlerService.class) && new URLHandlersStreamHandlerProxy(null, null) != null;
     }
 
     private void init(String protocol, URLStreamHandlerFactory factory)
     {
         try
         {
-            URLStreamHandler handler = getBuiltInStreamHandler(protocol, factory);
-            if (handler != null)
-            {
-                URL url = new URL(protocol, null, -1, "", handler);
-                addToCache(m_protocolToURL, protocol, url);
-            }
+            // Try to get it directly from the URL class to if possible
+            Method getURLStreamHandler = m_secureAction.getDeclaredMethod(URL.class,"getURLStreamHandler", new Class[]{String.class});
+            URLStreamHandler handler = (URLStreamHandler) m_secureAction.invoke(getURLStreamHandler, null, new Object[]{protocol});
+            addToCache(m_builtIn, protocol, handler);
         }
         catch (Throwable ex)
         {
-            // Ignore, this is a best effort (maybe log it or something).
+            // Ignore, this is a best effort
+            try
+            {
+                URLStreamHandler handler = getBuiltInStreamHandler(protocol, factory);
+                if (handler != null)
+                {
+                    URL url = new URL(protocol, null, -1, "", handler);
+                    addToCache(m_protocolToURL, protocol, url);
+                }
+            }
+            catch (Throwable ex2)
+            {
+                // Ignore, this is a best effort (maybe log it or something).
+            }
         }
     }
 
@@ -165,14 +176,7 @@
             init("ftp", currentFactory);
             init("http", currentFactory);
             init("https", currentFactory);
-            try
-            {
-                getBuiltInStreamHandler("jar", currentFactory);
-            }
-            catch (Throwable ex)
-            {
-                // Ignore, this is a best effort (maybe log it or something)
-            }
+
 
             // Try to preload the jrt handler as we need it from the jvm on java > 8
             if (getFromCache(m_builtIn, "jrt") == null)
@@ -198,6 +202,30 @@
                 }
             }
 
+            // Try to preload the jrt handler as we need it from the jvm on java > 8
+            if (getFromCache(m_builtIn, "jar") == null)
+            {
+                try
+                {
+                    // Try to get it directly from the URL class to if possible
+                    Method getURLStreamHandler = m_secureAction.getDeclaredMethod(URL.class,"getURLStreamHandler", new Class[]{String.class});
+                    URLStreamHandler handler = (URLStreamHandler) m_secureAction.invoke(getURLStreamHandler, null, new Object[]{"jar"});
+                    addToCache(m_builtIn, "jar", handler);
+                }
+                catch (Throwable ex)
+                {
+                    // Ignore, this is a best effort
+                    try
+                    {
+                        getBuiltInStreamHandler("jar", currentFactory);
+                    }
+                    catch (Throwable ex2)
+                    {
+                        // Ignore, this is a best effort (maybe log it or something)
+                    }
+                }
+            }
+
             if (currentFactory != null)
             {
                 try
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java b/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
index 794af28..bbde321 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlersBundleURLConnection.java
@@ -35,7 +35,7 @@
     private Felix m_framework;
     private BundleRevision m_targetRevision;
     private int m_classPathIdx = -1;
-    private int m_contentLength;
+    private long m_contentLength;
     private long m_contentTime;
     private String m_contentType;
     private InputStream m_is;
@@ -87,7 +87,6 @@
         {
             throw new IOException("No bundle associated with resource: " + url);
         }
-        m_contentTime = bundle.getLastModified();
 
         // Get the bundle's revisions to find the target revision.
         BundleRevisions revisions = bundle.adapt(BundleRevisions.class);
@@ -149,6 +148,7 @@
             m_is = ((BundleRevisionImpl)
                 m_targetRevision).getInputStream(m_classPathIdx, m_path);
             m_contentLength = (m_is == null) ? 0 : m_is.available();
+            m_contentTime = ((BundleRevisionImpl) m_targetRevision).getContentTime(m_classPathIdx, m_path);
             m_contentType = URLConnection.guessContentTypeFromName(m_path);
             connected = true;
         }
@@ -164,6 +164,11 @@
 
     public int getContentLength()
     {
+        return (int) getContentLengthLong();
+    }
+
+    public long getContentLengthLong()
+    {
         try
         {
             connect();
@@ -176,11 +181,6 @@
         return m_contentLength;
     }
 
-    public long getContentLengthLong()
-    {
-        return getContentLength();
-    }
-
     public long getLastModified()
     {
         try
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java b/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
index ef577c6..7ad09da 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
@@ -22,7 +22,6 @@
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -138,7 +137,7 @@
         m_builtInURL = builtInURL;
     }
 
-    private URLHandlersStreamHandlerProxy(Object service, SecureAction action)
+    URLHandlersStreamHandlerProxy(Object service, SecureAction action)
     {
         m_protocol = null;
         m_service = service;
diff --git a/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java b/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java
index 9459f2f..4e3a8c7 100644
--- a/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java
@@ -30,7 +30,6 @@
 import org.osgi.framework.AdminPermission;
 import org.osgi.framework.PackagePermission;
 import org.osgi.framework.hooks.weaving.WovenClass;
-import org.osgi.framework.wiring.BundleRequirement;
 import org.osgi.framework.wiring.BundleWiring;
 
 class WovenClassImpl implements WovenClass, List<String>
@@ -52,12 +51,10 @@
         m_state = TRANSFORMING;
     }
 
-    synchronized void complete(Class definedClass, byte[] bytes,
-            List<String> imports)
+    synchronized void complete()
     {
-        completeDefine(definedClass);
-        m_bytes = (bytes == null) ? m_bytes : bytes;
-        completeImports(imports);
+        completeDefine(null);
+        completeImports(null);
     }
 
     synchronized void completeImports(List<String> imports)
@@ -66,7 +63,7 @@
                 : Util.newImmutableList(imports);
     }
 
-    synchronized void completeDefine(Class definedClass)
+    synchronized void completeDefine(Class<?> definedClass)
     {
         m_definedClass = definedClass;
     }
@@ -191,14 +188,12 @@
         {
             try
             {
-                List<BundleRequirement> reqs = ManifestParser
+                ManifestParser
                         .parseDynamicImportHeader(null, null, s);
             } catch (Exception ex)
             {
-                RuntimeException re = new IllegalArgumentException(
-                        "Unable to parse dynamic import.");
-                re.initCause(ex);
-                throw re;
+                throw new IllegalArgumentException(
+                        "Unable to parse dynamic import.", ex);
             }
             checkImport(s);
             return m_imports.add(s);
@@ -244,14 +239,12 @@
         {
             try
             {
-                List<BundleRequirement> reqs = ManifestParser
+                ManifestParser
                         .parseDynamicImportHeader(null, null, s);
             } catch (Exception ex)
             {
-                RuntimeException re = new IllegalArgumentException(
-                        "Unable to parse dynamic import.");
-                re.initCause(ex);
-                throw re;
+                throw new IllegalArgumentException(
+                        "Unable to parse dynamic import.", ex);
             }
             checkImport(s);
         }
@@ -271,14 +264,12 @@
         {
             try
             {
-                List<BundleRequirement> reqs = ManifestParser
+                ManifestParser
                         .parseDynamicImportHeader(null, null, s);
             } catch (Exception ex)
             {
-                RuntimeException re = new IllegalArgumentException(
-                        "Unable to parse dynamic import.");
-                re.initCause(ex);
-                throw re;
+                throw new IllegalArgumentException(
+                        "Unable to parse dynamic import.", ex);
             }
             checkImport(s);
         }
@@ -333,14 +324,12 @@
         }
         try
         {
-            List<BundleRequirement> reqs = ManifestParser
+            ManifestParser
                     .parseDynamicImportHeader(null, null, s);
         } catch (Exception ex)
         {
-            RuntimeException re = new IllegalArgumentException(
-                    "Unable to parse dynamic import.");
-            re.initCause(ex);
-            throw re;
+            throw new IllegalArgumentException(
+                    "Unable to parse dynamic import.", ex);
         }
         checkImport(s);
         return m_imports.set(i, s);
@@ -356,14 +345,13 @@
         }
         try
         {
-            List<BundleRequirement> reqs = ManifestParser
+            ManifestParser
                     .parseDynamicImportHeader(null, null, s);
-        } catch (Exception ex)
+        }
+        catch (Exception ex)
         {
-            RuntimeException re = new IllegalArgumentException(
-                    "Unable to parse dynamic import.");
-            re.initCause(ex);
-            throw re;
+            throw new IllegalArgumentException(
+                    "Unable to parse dynamic import.", ex);
         }
         checkImport(s);
         m_imports.add(i, s);
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
index fc527bc..50e4d31 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
@@ -27,6 +27,8 @@
 import org.apache.felix.framework.util.WeakZipFileFactory;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
+import org.osgi.framework.connect.ModuleConnector;
+import org.osgi.framework.connect.ConnectModule;
 
 /**
  * <p>
@@ -100,6 +102,8 @@
     **/
     private long m_refreshCount = -1;
 
+    private final ModuleConnector m_connector;
+
     // Maps a Long revision number to a BundleRevision.
     private final SortedMap<Long, BundleArchiveRevision> m_revisions
         = new TreeMap<Long, BundleArchiveRevision>();
@@ -121,7 +125,8 @@
      * @param is input stream from which to read the bundle content.
      * @throws Exception if any error occurs.
     **/
-    public BundleArchive(Logger logger, Map configMap, WeakZipFileFactory zipFactory,
+    public BundleArchive(Logger logger, Map configMap, WeakZipFileFactory zipFactory, ModuleConnector
+        connectFactory,
         File archiveRootDir, long id, int startLevel, String location, InputStream is)
         throws Exception
     {
@@ -141,6 +146,8 @@
         m_lastModified = System.currentTimeMillis();
         m_refreshCount = 0;
 
+        m_connector = connectFactory;
+
         // Save state.
         initialize();
 
@@ -160,7 +167,7 @@
      * @param configMap configMap for BundleArchive
      * @throws Exception if any error occurs.
     **/
-    public BundleArchive(Logger logger, Map configMap, WeakZipFileFactory zipFactory,
+    public BundleArchive(Logger logger, Map configMap, WeakZipFileFactory zipFactory, ModuleConnector connectFactory,
         File archiveRootDir)
         throws Exception
     {
@@ -184,7 +191,7 @@
         for (File child : children)
         {
             if (child.getName().startsWith(REVISION_DIRECTORY)
-                && child.isDirectory())
+                && BundleCache.getSecureAction().isFileDirectory(child))
             {
                 // Determine the revision number and add it to the revision map.
                 int idx = child.getName().lastIndexOf('.');
@@ -211,8 +218,12 @@
         Long currentRevNum = m_revisions.lastKey();
         m_revisions.remove(currentRevNum);
 
+        String location = getRevisionLocation(currentRevNum);
+
+        m_connector = connectFactory;
+
         // Add the revision object for the most recent revision.
-        reviseInternal(true, currentRevNum, getRevisionLocation(currentRevNum), null);
+        reviseInternal(true, currentRevNum, location, null);
     }
 
     /**
@@ -542,7 +553,7 @@
         BufferedReader br = null;
         try
         {
-            is = BundleCache.getSecureAction().getFileInputStream(new File(
+            is = BundleCache.getSecureAction().getInputStream(new File(
                 new File(m_archiveRootDir, REVISION_DIRECTORY +
                 getRefreshCount() + "." + revNum.toString()), REVISION_LOCATION_FILE));
 
@@ -565,7 +576,7 @@
         try
         {
             os = BundleCache.getSecureAction()
-                .getFileOutputStream(new File(
+                .getOutputStream(new File(
                     new File(m_archiveRootDir, REVISION_DIRECTORY +
                     getRefreshCount() + "." + revNum.toString()), REVISION_LOCATION_FILE));
             bw = new BufferedWriter(new OutputStreamWriter(os));
@@ -795,9 +806,19 @@
             }
             else
             {
-                // Anything else is assumed to be a URL to a JAR file.
-                result = new JarRevision(m_logger, m_configMap,
-                    m_zipFactory, revisionRootDir, location, false, null);
+                ConnectModule module = m_connector != null ?
+                    m_connector.connect(location).orElse(null) : null;
+
+                if (module != null)
+                {
+                    result = new ConnectRevision(m_logger, m_configMap, m_zipFactory, revisionRootDir, location, module);
+                }
+                else
+                {
+                    // Anything else is assumed to be a URL to a JAR file.
+                    result = new JarRevision(m_logger, m_configMap,
+                        m_zipFactory, revisionRootDir, location, false, null);
+                }
             }
         }
         catch (Exception ex)
@@ -869,7 +890,7 @@
         try
         {
             is = BundleCache.getSecureAction()
-                .getFileInputStream(infoFile);
+                .getInputStream(infoFile);
             br = new BufferedReader(new InputStreamReader(is));
 
             // Read id.
@@ -900,7 +921,7 @@
         try
         {
             os = BundleCache.getSecureAction()
-                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_INFO_FILE));
+                .getOutputStream(new File(m_archiveRootDir, BUNDLE_INFO_FILE));
             bw = new BufferedWriter(new OutputStreamWriter(os));
 
             // Write id.
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java b/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
index 8e63af9..ef544d0 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
@@ -22,9 +22,9 @@
 import org.apache.felix.framework.util.SecureAction;
 import org.apache.felix.framework.util.WeakZipFileFactory;
 import org.osgi.framework.Constants;
+import org.osgi.framework.connect.ModuleConnector;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -144,25 +144,14 @@
         {
             File lockFile = new File(cacheDir, CACHE_LOCK_NAME);
             FileChannel fc = null;
-            FileOutputStream fos = null;
             try
             {
-                if (!getSecureAction().fileExists(lockFile))
-                {
-                    fos = getSecureAction().getFileOutputStream(lockFile);
-                    fc = fos.getChannel();
-                }
-                else
-                {
-                    fos = getSecureAction().getFileOutputStream(lockFile);
-                    fc = fos.getChannel();
-                }
+                fc = getSecureAction().getFileChannel(lockFile);
             }
             catch (Exception ex)
             {
                 try
                 {
-                    if (fos != null) fos.close();
                     if (fc != null) fc.close();
                 }
                 catch (Exception ex2)
@@ -386,6 +375,11 @@
         }
     }
 
+    public File getCacheDir()
+    {
+        return determineCacheDir(m_configMap);
+    }
+
     /* package */ static SecureAction getSecureAction()
     {
         return m_secureAction;
@@ -398,7 +392,7 @@
         deleteDirectoryTree(cacheDir);
     }
 
-    public BundleArchive[] getArchives()
+    public BundleArchive[] getArchives(ModuleConnector connectFactory)
         throws Exception
     {
         // Get buffer size value.
@@ -431,7 +425,7 @@
                 {
                     archiveList.add(
                         new BundleArchive(
-                            m_logger, m_configMap, m_zipFactory, children[i]));
+                            m_logger, m_configMap, m_zipFactory, connectFactory, children[i]));
                 }
                 catch (Exception ex)
                 {
@@ -447,7 +441,7 @@
             archiveList.toArray(new BundleArchive[archiveList.size()]);
     }
 
-    public BundleArchive create(long id, int startLevel, String location, InputStream is)
+    public BundleArchive create(long id, int startLevel, String location, InputStream is, ModuleConnector connectFactory)
         throws Exception
     {
         File cacheDir = determineCacheDir(m_configMap);
@@ -461,7 +455,7 @@
             // Create the archive and add it to the list of archives.
             BundleArchive ba =
                 new BundleArchive(
-                    m_logger, m_configMap, m_zipFactory, archiveRootDir,
+                    m_logger, m_configMap, m_zipFactory, connectFactory, archiveRootDir,
                     id, startLevel, location, is);
             return ba;
         }
@@ -548,7 +542,7 @@
 
         try
         {
-            os = getSecureAction().getFileOutputStream(outputFile);
+            os = getSecureAction().getOutputStream(outputFile);
             for (int i = is.read(bytes);i != -1; i = is.read(bytes))
             {
                 os.write(bytes, 0, i);
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/ConnectContentContent.java b/framework/src/main/java/org/apache/felix/framework/cache/ConnectContentContent.java
new file mode 100644
index 0000000..88e6dec
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/cache/ConnectContentContent.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.WeakZipFileFactory;
+import org.osgi.framework.connect.ConnectContent;
+
+public class ConnectContentContent implements Content
+{
+    private static final transient String EMBEDDED_DIRECTORY = "-embedded";
+    private static final transient String LIBRARY_DIRECTORY = "-lib";
+
+    private final Logger m_logger;
+    private final WeakZipFileFactory m_zipFactory;
+    private final Map m_configMap;
+    private final String m_name;
+    private final File m_rootDir;
+    private final Object m_revisionLock;
+    private final ConnectContent m_content;
+
+    public ConnectContentContent(Logger logger, WeakZipFileFactory zipFactory, Map configMap, String name, File rootDir, Object revisionLock, ConnectContent content) throws IOException
+    {
+        m_logger = logger;
+        m_zipFactory = zipFactory;
+        m_configMap = configMap;
+        m_name = name;
+        m_rootDir = rootDir;
+        m_revisionLock = revisionLock;
+        m_content = content;
+    }
+
+    @Override
+    public void close()
+    {
+        // TODO: Connect
+    }
+
+    @Override
+    public boolean hasEntry(String name)
+    {
+        return m_content.getEntry(name).isPresent();
+    }
+
+    @Override
+    public Enumeration<String> getEntries()
+    {
+        try
+        {
+            Iterator<String> entries = m_content.getEntries().iterator();
+            return new Enumeration<String>()
+            {
+                @Override
+                public boolean hasMoreElements()
+                {
+                    return entries.hasNext();
+                }
+
+                @Override
+                public String nextElement()
+                {
+                    return entries.next();
+                }
+            };
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    @Override
+    public byte[] getEntryAsBytes(String name)
+    {
+        return m_content.getEntry(name).flatMap(entry ->
+        {
+            try
+            {
+                return Optional.of(entry.getBytes());
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+                return null;
+            }
+        }).orElse(null);
+    }
+
+    @Override
+    public InputStream getEntryAsStream(String name) throws IOException
+    {
+        return m_content.getEntry(name).flatMap(entry ->
+        {
+            try
+            {
+                return Optional.of(entry.getInputStream());
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+                return null;
+            }
+        }).orElse(null);
+    }
+
+    public ClassLoader getClassLoader() {
+        return m_content.getClassLoader().orElse(null);
+    }
+
+    @Override
+    public Content getEntryAsContent(String name)
+    {
+        if (".".equals(name) || "".equals(name))
+        {
+            return this;
+        }
+        String dir = name.endsWith("/") ? name : name + "/";
+
+        if (hasEntry(dir))
+        {
+            return new ContentDirectoryContent(this, name);
+        }
+
+        if (hasEntry(name) && name.endsWith(".jar"))
+        {
+            // Any embedded JAR files will be extracted to the embedded directory.
+            // Since embedded JAR file names may clash when extracting from multiple
+            // embedded JAR files, the embedded directory is per embedded JAR file.
+            File embedDir = new File(m_rootDir, m_name + EMBEDDED_DIRECTORY);
+
+            File extractJar = new File(embedDir, name);
+
+            try
+            {
+                if (!BundleCache.getSecureAction().fileExists(extractJar))
+                {
+                    // Extracting the embedded JAR file impacts all other existing
+                    // contents for this revision, so we have to grab the revision
+                    // lock first before trying to extract the embedded JAR file
+                    // to avoid a race condition.
+                    synchronized (m_revisionLock)
+                    {
+                        if (!BundleCache.getSecureAction().fileExists(extractJar))
+                        {
+                            // Make sure that the embedded JAR's parent directory exists;
+                            // it may be in a sub-directory.
+                            File jarDir = extractJar.getParentFile();
+                            if (!BundleCache.getSecureAction().fileExists(jarDir) && !BundleCache.getSecureAction().mkdirs(jarDir))
+                            {
+                                throw new IOException("Unable to create embedded JAR directory.");
+                            }
+
+                            // Extract embedded JAR into its directory.
+                            BundleCache.copyStreamToFile(m_content.getEntry(name).get().getInputStream(), extractJar);
+                        }
+                    }
+                }
+                return new JarContent(
+                    m_logger, m_configMap, m_zipFactory, m_revisionLock,
+                    extractJar.getParentFile(), extractJar, null);
+            }
+            catch (Exception ex)
+            {
+                m_logger.log(
+                    Logger.LOG_ERROR,
+                    "Unable to extract embedded JAR file.", ex);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getEntryAsNativeLibrary(String entryName)
+    {
+        // TODO: Connect
+        return null;
+    }
+
+    @Override
+    public URL getEntryAsURL(String name)
+    {
+        // TODO: Connect
+        return null;
+    }
+
+    @Override
+    public long getContentTime(String urlPath)
+    {
+        return m_content.getEntry(urlPath).flatMap(entry -> Optional.of(entry.getLastModified())).orElse(-1L);
+    }
+}
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/ConnectRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/ConnectRevision.java
new file mode 100644
index 0000000..94b3284
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/cache/ConnectRevision.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.StringMap;
+import org.apache.felix.framework.util.WeakZipFileFactory;
+import org.osgi.framework.connect.ConnectContent;
+import org.osgi.framework.connect.ConnectModule;
+
+public class ConnectRevision extends BundleArchiveRevision
+{
+    private final WeakZipFileFactory m_zipFactory;
+    private final ConnectContent m_module;
+
+    public ConnectRevision(Logger logger, Map configMap, WeakZipFileFactory zipFactory,
+        File revisionRootDir, String location, ConnectModule module) throws Exception
+    {
+        super(logger, configMap, revisionRootDir, location);
+        m_zipFactory = zipFactory;
+        m_module = module.getContent();
+        m_module.open();
+        // If the revision directory exists, then we don't
+        // need to initialize since it has already been done.
+        if (BundleCache.getSecureAction().fileExists(getRevisionRootDir()))
+        {
+            return;
+        }
+        // Create revision directory, we only need this to store the
+        // revision location, since nothing else needs to be extracted
+        // since we are referencing a read directory already.
+        if (!BundleCache.getSecureAction().mkdir(getRevisionRootDir()))
+        {
+            getLogger().log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to create revision directory.");
+            throw new IOException("Unable to create archive directory.");
+        }
+    }
+
+    @Override
+    public Map<String, Object> getManifestHeader() throws Exception
+    {
+        return (Map) m_module.getHeaders().orElseGet(() -> m_module.getEntry("META-INF/MANIFEST.MF").flatMap(entry -> {
+                try
+                {
+                    byte[] manifest = entry.getBytes();
+
+                    return Optional.of((Map<String, String>) (Map) BundleCache.getMainAttributes(new StringMap(), new java.io.ByteArrayInputStream(manifest), manifest.length));
+                }
+                catch (Exception e)
+                {
+                    throw new IllegalStateException(e);
+                }
+            }).orElse(null));
+    }
+
+    @Override
+    public Content getContent() throws Exception
+    {
+        return new ConnectContentContent(getLogger(), m_zipFactory, getConfig(), "connect", getRevisionRootDir(), this, m_module);
+    }
+
+    @Override
+    protected void close() throws Exception
+    {
+        m_module.close();
+    }
+}
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/Content.java b/framework/src/main/java/org/apache/felix/framework/cache/Content.java
index 09ace44..79580c9 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/Content.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/Content.java
@@ -121,4 +121,6 @@
      *           or null if not possible.
      */
     URL getEntryAsURL(String name);
+
+    long getContentTime(String urlPath);
 }
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/ContentDirectoryContent.java b/framework/src/main/java/org/apache/felix/framework/cache/ContentDirectoryContent.java
index d268f32..e1f04ac 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/ContentDirectoryContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/ContentDirectoryContent.java
@@ -47,10 +47,7 @@
 
     public boolean hasEntry(String name) throws IllegalStateException
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         return m_content.hasEntry(m_rootPath + name);
     }
@@ -63,10 +60,7 @@
 
     public byte[] getEntryAsBytes(String name) throws IllegalStateException
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         return m_content.getEntryAsBytes(m_rootPath + name);
     }
@@ -74,35 +68,42 @@
     public InputStream getEntryAsStream(String name)
         throws IllegalStateException, IOException
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         return m_content.getEntryAsStream(m_rootPath + name);
     }
 
+    private String getName(String name)
+    {
+        if ((name.length() > 0) && (name.charAt(0) == '/')) {
+            name = name.substring(1);
+        }
+        return name;
+    }
+
     public URL getEntryAsURL(String name)
     {
         return m_content.getEntryAsURL(m_rootPath + name);
     }
 
+    @Override
+    public long getContentTime(String name)
+    {
+        name = getName(name);
+
+        return m_content.getContentTime(m_rootPath + name);
+    }
+
     public Content getEntryAsContent(String name)
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         return m_content.getEntryAsContent(m_rootPath + name);
     }
 
     public String getEntryAsNativeLibrary(String name)
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         return m_content.getEntryAsNativeLibrary(m_rootPath + name);
     }
@@ -143,14 +144,17 @@
 
         private String findNextEntry()
         {
-            // Find next entry that is inside the root directory.
-            while (m_enumeration.hasMoreElements())
+            if (m_enumeration != null)
             {
-                String next = (String) m_enumeration.nextElement();
-                if (next.startsWith(m_rootPath) && !next.equals(m_rootPath))
+                // Find next entry that is inside the root directory.
+                while (m_enumeration.hasMoreElements())
                 {
-                    // Strip off the root directory.
-                    return next.substring(m_rootPath.length());
+                    String next = (String) m_enumeration.nextElement();
+                    if (next.startsWith(m_rootPath) && !next.equals(m_rootPath))
+                    {
+                        // Strip off the root directory.
+                        return next.substring(m_rootPath.length());
+                    }
                 }
             }
             return null;
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java
index 50da934..0240933 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryContent.java
@@ -72,10 +72,7 @@
 
     public boolean hasEntry(String name) throws IllegalStateException
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         // Return true if the file associated with the entry exists,
         // unless the entry name ends with "/", in which case only
@@ -97,10 +94,7 @@
 
     public byte[] getEntryAsBytes(String name) throws IllegalStateException
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         // Get the embedded resource.
 
@@ -108,7 +102,7 @@
         try
         {
 
-            return BundleCache.getSecureAction().fileExists(file) ? BundleCache.read(BundleCache.getSecureAction().getFileInputStream(file), file.length()) : null;
+            return BundleCache.getSecureAction().fileExists(file) ? BundleCache.read(BundleCache.getSecureAction().getInputStream(file), file.length()) : null;
         }
         catch (Exception ex)
         {
@@ -122,15 +116,12 @@
     public InputStream getEntryAsStream(String name)
         throws IllegalStateException, IOException
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
-            name = name.substring(1);
-        }
+        name = getName(name);
 
         File file = new File(m_dir, name);
         try
         {
-            return BundleCache.getSecureAction().fileExists(file) ? BundleCache.getSecureAction().getFileInputStream(file) : null;
+            return BundleCache.getSecureAction().fileExists(file) ? BundleCache.getSecureAction().getInputStream(file) : null;
         }
         catch (Exception ex)
         {
@@ -141,12 +132,17 @@
         }
     }
 
-    public URL getEntryAsURL(String name)
+    private String getName(String name)
     {
-        if ((name.length() > 0) && (name.charAt(0) == '/'))
-        {
+        if ((name.length() > 0) && (name.charAt(0) == '/')) {
             name = name.substring(1);
         }
+        return name;
+    }
+
+    public URL getEntryAsURL(String name)
+    {
+        name = getName(name);
 
         if (hasEntry(name))
         {
@@ -165,6 +161,16 @@
         }
     }
 
+    @Override
+    public long getContentTime(String name)
+    {
+        name = getName(name);
+
+        File file = new File(m_dir, name);
+
+        return BundleCache.getSecureAction().getLastModified(file);
+    }
+
     public Content getEntryAsContent(String entryName)
     {
         // If the entry name refers to the content itself, then
@@ -278,7 +284,7 @@
 
                         try
                         {
-                            is = BundleCache.getSecureAction().getFileInputStream(entryFile);
+                            is = BundleCache.getSecureAction().getInputStream(entryFile);
 
                             // Create the file.
                             BundleCache.copyStreamToFile(is, libFile);
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
index a1cf608..f6d01a9 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
@@ -71,7 +71,7 @@
         throws Exception
     {
         File manifest = new File(m_refDir, "META-INF/MANIFEST.MF");
-        return manifest.isFile() ? BundleCache.getMainAttributes(new StringMap(), BundleCache.getSecureAction().getFileInputStream(manifest), manifest.length()) : null;
+        return BundleCache.getSecureAction().isFile(manifest) ? BundleCache.getMainAttributes(new StringMap(), BundleCache.getSecureAction().getInputStream(manifest), manifest.length()) : null;
     }
 
     public Content getContent() throws Exception
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java b/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
index 35afc9c..79c2249 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarContent.java
@@ -112,9 +112,6 @@
         {
             return false;
         }
-        finally
-        {
-        }
     }
 
     public Enumeration<String> getEntries()
@@ -195,6 +192,20 @@
         }
     }
 
+    @Override
+    public long getContentTime(String urlPath)
+    {
+        try
+        {
+            ZipEntry ze = m_zipFile.getEntry(urlPath);
+            return ze.getTime();
+        }
+        catch (Exception ex)
+        {
+            return -1L;
+        }
+    }
+
     public Content getEntryAsContent(String entryName)
     {
         // If the entry name refers to the content itself, then
@@ -398,7 +409,7 @@
         return m_file;
     }
 
-    private static class DevNullRunnable implements Runnable
+    static class DevNullRunnable implements Runnable
     {
         private final InputStream m_in;
 
diff --git a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
index ac5c1e8..4ff4e84 100644
--- a/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
+++ b/framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java
@@ -147,7 +147,7 @@
                             // set request properties such as proxy auth.
                             URL url = BundleCache.getSecureAction().createURL(
                                 null, getLocation(), null);
-                            conn = url.openConnection();
+                            conn = BundleCache.getSecureAction().openURLConnection(url);
 
                             // Support for http proxy authentication.
                             String auth = BundleCache.getSecureAction()
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
index 7116751..e887c3a 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
@@ -577,7 +577,7 @@
     {
         // If the LHS expects a string, then we can just return
         // the RHS since it is a string.
-        if (lhs.getClass() == rhsString.getClass())
+        if (lhs instanceof String)
         {
             return rhsString;
         }
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
index 253c70d..6e56485 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
@@ -18,7 +18,6 @@
  */
 package org.apache.felix.framework.capabilityset;
 
-import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
 
 import java.util.ArrayList;
diff --git a/framework/src/main/java/org/apache/felix/framework/ext/ClassPathExtenderFactory.java b/framework/src/main/java/org/apache/felix/framework/ext/ClassPathExtenderFactory.java
index 26103b4..f83663c 100644
--- a/framework/src/main/java/org/apache/felix/framework/ext/ClassPathExtenderFactory.java
+++ b/framework/src/main/java/org/apache/felix/framework/ext/ClassPathExtenderFactory.java
@@ -23,6 +23,8 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 
+import org.apache.felix.framework.util.SecureAction;
+
 public interface ClassPathExtenderFactory
 {
     interface ClassPathExtender
@@ -50,10 +52,10 @@
                 try
                 {
                     append = app.getClass().getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
-                    append.setAccessible(true);
+                    new SecureAction().setAccesssible(append);
                     break;
                 }
-                catch (Exception e)
+                catch (Throwable e)
                 {
                     append = null;
                     try
@@ -74,9 +76,9 @@
             try
             {
                 addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
-                addURL.setAccessible(true);
+                new SecureAction().setAccesssible(addURL);
             }
-            catch (Exception e)
+            catch (Throwable e)
             {
                 addURL = null;
             }
diff --git a/framework/src/main/java/org/apache/felix/framework/util/MultiReleaseContent.java b/framework/src/main/java/org/apache/felix/framework/util/MultiReleaseContent.java
index 53996dd..29a5e30 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/MultiReleaseContent.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/MultiReleaseContent.java
@@ -18,23 +18,15 @@
  */
 package org.apache.felix.framework.util;
 
+import org.apache.felix.framework.cache.BundleCache;
 import org.apache.felix.framework.cache.Content;
 import org.osgi.framework.Version;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
+import java.util.*;
 
 public class MultiReleaseContent implements Content
 {
@@ -61,14 +53,16 @@
         }
         if (javaVersion > 8)
         {
-            InputStream manifestStream = null;
-
             try
             {
-                manifestStream = content.getEntryAsStream("META-INF/MANIFEST.MF");
-                if (manifestStream != null)
+                byte[] versionManifestInput = content.getEntryAsBytes("META-INF/MANIFEST.MF");
+
+                if (versionManifestInput != null)
                 {
-                    if ("true".equals(new Manifest(manifestStream).getMainAttributes().getValue("Multi-Release")))
+                    Map<String, Object> versionManifest = BundleCache.getMainAttributes(
+                            new StringMap(), new ByteArrayInputStream(versionManifestInput), versionManifestInput.length);
+
+                    if ("true".equals(versionManifest.get("Multi-Release")))
                     {
                         content = new MultiReleaseContent(javaVersion, content);
                     }
@@ -77,17 +71,6 @@
             catch (Exception ex)
             {
             }
-            finally
-            {
-                if (manifestStream != null)
-                {
-                    try
-                    {
-                        manifestStream.close();
-                    }
-                    catch (Exception ex){}
-                }
-            }
         }
         return content;
     }
@@ -175,6 +158,12 @@
         return m_content.getEntryAsURL(findPath(name));
     }
 
+    @Override
+    public long getContentTime(String name)
+    {
+        return m_content.getContentTime(findPath(name));
+    }
+
     private String findPath(String path)
     {
         String internalPath = path;
diff --git a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
index 4a202a7..1e8024e 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
@@ -22,11 +22,15 @@
 import java.lang.reflect.*;
 import java.lang.reflect.Proxy;
 import java.net.*;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
 import java.security.*;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.jar.JarFile;
 import java.util.zip.ZipFile;
 
@@ -56,6 +60,32 @@
 **/
 public class SecureAction
 {
+    private static final byte[] accessor;
+
+    static
+    {
+        byte[] result;
+
+        try (ByteArrayOutputStream output = new ByteArrayOutputStream();
+             InputStream input = SecureAction.class.getResourceAsStream("accessor.bytes"))
+        {
+
+            byte[] buffer = new byte[input.available() > 0 ? input.available() : 1024];
+            for (int i = input.read(buffer); i != -1; i = input.read(buffer))
+            {
+                output.write(buffer, 0, i);
+            }
+            result = output.toByteArray();
+        }
+        catch (Throwable t)
+        {
+            t.printStackTrace();
+            result = new byte[0];
+        }
+        accessor = result;
+        getAccessor(URL.class);
+    }
+
     private static final ThreadLocal m_actions = new ThreadLocal()
     {
         public Object initialValue()
@@ -321,6 +351,28 @@
         }
     }
 
+    public boolean isFile(File file)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                Actions actions = (Actions) m_actions.get();
+                actions.set(Actions.FILE_IS_FILE_ACTION, file);
+                return ((Boolean) AccessController.doPrivileged(actions, m_acc))
+                        .booleanValue();
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw (RuntimeException) ex.getException();
+            }
+        }
+        else
+        {
+            return file.isFile();
+        }
+    }
+
     public boolean isFileDirectory(File file)
     {
         if (System.getSecurityManager() != null)
@@ -430,6 +482,56 @@
         }
     }
 
+    public InputStream getInputStream(File file) throws IOException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                Actions actions = (Actions) m_actions.get();
+                actions.set(Actions.GET_INPUT_ACTION, file);
+                return (InputStream) AccessController.doPrivileged(actions, m_acc);
+            }
+            catch (PrivilegedActionException ex)
+            {
+                if (ex.getException() instanceof IOException)
+                {
+                    throw (IOException) ex.getException();
+                }
+                throw (RuntimeException) ex.getException();
+            }
+        }
+        else
+        {
+            return Files.newInputStream(file.toPath());
+        }
+    }
+
+    public OutputStream getOutputStream(File file) throws IOException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                Actions actions = (Actions) m_actions.get();
+                actions.set(Actions.GET_OUTPUT_ACTION, file);
+                return (OutputStream) AccessController.doPrivileged(actions, m_acc);
+            }
+            catch (PrivilegedActionException ex)
+            {
+                if (ex.getException() instanceof IOException)
+                {
+                    throw (IOException) ex.getException();
+                }
+                throw (RuntimeException) ex.getException();
+            }
+        }
+        else
+        {
+            return Files.newOutputStream(file.toPath());
+        }
+    }
+
     public FileInputStream getFileInputStream(File file) throws IOException
     {
         if (System.getSecurityManager() != null)
@@ -480,6 +582,31 @@
         }
     }
 
+    public FileChannel getFileChannel(File file) throws IOException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                Actions actions = (Actions) m_actions.get();
+                actions.set(Actions.GET_FILE_CHANNEL_ACTION, file);
+                return (FileChannel) AccessController.doPrivileged(actions, m_acc);
+            }
+            catch (PrivilegedActionException ex)
+            {
+                if (ex.getException() instanceof IOException)
+                {
+                    throw (IOException) ex.getException();
+                }
+                throw (RuntimeException) ex.getException();
+            }
+        }
+        else
+        {
+            return FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+        }
+    }
+
     public URI toURI(File file)
     {
         if (System.getSecurityManager() != null)
@@ -762,7 +889,7 @@
             Method addURL =
                 URLClassLoader.class.getDeclaredMethod("addURL",
                 new Class[] {URL.class});
-            addURL.setAccessible(true);
+            getAccessor(URLClassLoader.class).accept(new AccessibleObject[]{addURL});
             addURL.invoke(loader, new Object[]{extension});
         }
     }
@@ -851,7 +978,7 @@
         }
     }
 
-    public void setAccesssible(AccessibleObject ao)
+    public void setAccesssible(Executable ao)
     {
         if (System.getSecurityManager() != null)
         {
@@ -868,7 +995,7 @@
         }
         else
         {
-            ao.setAccessible(true);
+            getAccessor(ao.getDeclaringClass()).accept(new AccessibleObject[]{ao});
         }
     }
 
@@ -889,7 +1016,8 @@
         }
         else
         {
-            method.setAccessible(true);
+            getAccessor(method.getDeclaringClass()).accept(new AccessibleObject[]{method});
+
             return method.invoke(target, params);
         }
     }
@@ -955,8 +1083,7 @@
         else
         {
             Field field = targetClass.getDeclaredField(name);
-            field.setAccessible(true);
-
+            getAccessor(targetClass).accept(new AccessibleObject[]{field});
             return field.get(target);
         }
     }
@@ -985,9 +1112,51 @@
         }
     }
 
+    private static volatile Consumer<AccessibleObject[]> m_accessorCache = null;
+
+    @SuppressWarnings("unchecked")
+    private static Consumer<AccessibleObject[]> getAccessor(Class clazz)
+    {
+        String packageName = clazz.getPackage().getName();
+        if ("java.net".equals(packageName) || "jdk.internal.loader".equals(packageName))
+        {
+            if (m_accessorCache == null)
+            {
+                try
+                {
+                    // Use reflection on Unsafe to avoid having to compile against it
+                    Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); //$NON-NLS-1$
+                    Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); //$NON-NLS-1$
+                    // NOTE: deep reflection is allowed on sun.misc package for java 9.
+                    theUnsafe.setAccessible(true);
+                    Object unsafe = theUnsafe.get(null);
+                    // using defineAnonymousClass here because it seems more simple to get what we need
+                    Method defineAnonymousClass = unsafeClass.getMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class); //$NON-NLS-1$
+                    // The bytes stored in a resource to avoid real loading of it (see accessible.src for source).
+
+                    Class<Consumer<AccessibleObject[]>> result =
+                        (Class<Consumer<AccessibleObject[]>>)
+                            defineAnonymousClass.invoke(unsafe, URL.class, accessor , null);
+                    m_accessorCache = result.getConstructor().newInstance();
+                }
+                catch (Throwable t)
+                {
+                    t.printStackTrace();
+                    m_accessorCache = objects -> AccessibleObject.setAccessible(objects, true);
+                }
+            }
+            return m_accessorCache;
+        }
+        else
+        {
+            return objects -> AccessibleObject.setAccessible(objects, true);
+        }
+    }
+
     private static Object _swapStaticFieldIfNotClass(Class targetClazz,
         Class targetType, Class condition, String lockName) throws Exception
     {
+
         Object lock = null;
         if (lockName != null)
         {
@@ -995,7 +1164,7 @@
             {
                 Field lockField =
                     targetClazz.getDeclaredField(lockName);
-                lockField.setAccessible(true);
+                getAccessor(targetClazz).accept(new AccessibleObject[]{lockField});
                 lock = lockField.get(null);
             }
             catch (NoSuchFieldException ex)
@@ -1010,14 +1179,14 @@
         {
             Field[] fields = targetClazz.getDeclaredFields();
 
+            getAccessor(targetClazz).accept(fields);
+
             Object result = null;
             for (int i = 0; (i < fields.length) && (result == null); i++)
             {
                 if (Modifier.isStatic(fields[i].getModifiers()) &&
                     (fields[i].getType() == targetType))
                 {
-                    fields[i].setAccessible(true);
-
                     result = fields[i].get(null);
 
                     if (result != null)
@@ -1040,7 +1209,6 @@
                         if (Modifier.isStatic(fields[i].getModifiers()) &&
                             (fields[i].getType() == Hashtable.class))
                         {
-                            fields[i].setAccessible(true);
                             Hashtable cache = (Hashtable) fields[i].get(null);
                             if (cache != null)
                             {
@@ -1081,13 +1249,13 @@
         synchronized (lock)
         {
             Field[] fields = targetClazz.getDeclaredFields();
+            getAccessor(targetClazz).accept(fields);
             // reset cache
             for (int i = 0; i < fields.length; i++)
             {
                 if (Modifier.isStatic(fields[i].getModifiers()) &&
                     ((fields[i].getType() == Hashtable.class) || (fields[i].getType() == HashMap.class)))
                 {
-                    fields[i].setAccessible(true);
                     if (fields[i].getType() == Hashtable.class)
                     {
                         Hashtable cache = (Hashtable) fields[i].get(null);
@@ -1548,6 +1716,27 @@
         }
     }
 
+    public long getLastModified(File file)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            Actions actions = (Actions) m_actions.get();
+            actions.set(Actions.LAST_MODIFIED, file);
+            try
+            {
+                return (Long) AccessController.doPrivileged(actions, m_acc);
+            }
+            catch (PrivilegedActionException e)
+            {
+                throw (RuntimeException) e.getException();
+            }
+        }
+        else
+        {
+            return file.lastModified();
+        }
+    }
+
     private static class Actions implements PrivilegedExceptionAction
     {
         public static final int INITIALIZE_CONTEXT_ACTION = 0;
@@ -1609,6 +1798,11 @@
         public static final int INVOKE_WOVEN_CLASS_LISTENER = 56;
         public static final int GET_CANONICAL_PATH = 57;
         public static final int CREATE_PROXY = 58;
+        public static final int LAST_MODIFIED = 59;
+        public static final int FILE_IS_FILE_ACTION = 60;
+        public static final int GET_FILE_CHANNEL_ACTION = 61;
+        private static final int GET_INPUT_ACTION = 62;
+        private static final int GET_OUTPUT_ACTION = 63;
 
         private int m_action = -1;
         private Object m_arg1 = null;
@@ -1708,7 +1902,7 @@
                     Method addURL =
                         URLClassLoader.class.getDeclaredMethod("addURL",
                         new Class[] {URL.class});
-                    addURL.setAccessible(true);
+                    getAccessor(URLClassLoader.class).accept(new AccessibleObject[]{addURL});
                     addURL.invoke(arg2, new Object[]{arg1});
                     return null;
                 case CREATE_TMPFILE_ACTION:
@@ -1740,7 +1934,7 @@
                     return ((Class) arg1).getDeclaredMethod((String) arg2, (Class[]) arg3);
                 case GET_FIELD_ACTION:
                     Field field = ((Class) arg1).getDeclaredField((String) arg2);
-                    field.setAccessible(true);
+                    getAccessor((Class) arg1).accept(new AccessibleObject[]{field});
                     return field.get(arg3);
                 case GET_FILE_INPUT_ACTION:
                     return new FileInputStream((File) arg1);
@@ -1765,7 +1959,7 @@
                 case INVOKE_DIRECTMETHOD_ACTION:
                     return ((Method) arg1).invoke(arg2, (Object[]) arg3);
                 case INVOKE_METHOD_ACTION:
-                    ((Method) arg1).setAccessible(true);
+                    getAccessor(((Method) arg1).getDeclaringClass()).accept(new AccessibleObject[]{(Method) arg1});
                     return ((Method) arg1).invoke(arg2, (Object[]) arg3);
                 case LIST_DIRECTORY_ACTION:
                     return ((File) arg1).listFiles();
@@ -1780,7 +1974,7 @@
                 case RENAME_FILE_ACTION:
                     return ((File) arg1).renameTo((File) arg2) ? Boolean.TRUE : Boolean.FALSE;
                 case SET_ACCESSIBLE_ACTION:
-                    ((AccessibleObject) arg1).setAccessible(true);
+                    getAccessor(((Executable) arg1).getDeclaringClass()).accept(new AccessibleObject[]{(Executable) arg1});
                     return null;
                 case START_ACTIVATOR_ACTION:
                     ((BundleActivator) arg1).start((BundleContext) arg2);
@@ -1872,6 +2066,16 @@
                 case CREATE_PROXY:
                     return Proxy.newProxyInstance((ClassLoader)arg1, (Class<?>[])arg2,
                             (InvocationHandler) arg3);
+                case LAST_MODIFIED:
+                    return ((File) arg1).lastModified();
+                case FILE_IS_FILE_ACTION:
+                    return ((File) arg1).isFile() ? Boolean.TRUE : Boolean.FALSE;
+                case GET_FILE_CHANNEL_ACTION:
+                    return FileChannel.open(((File) arg1).toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+                case GET_INPUT_ACTION:
+                    return Files.newInputStream(((File) arg1).toPath());
+                case GET_OUTPUT_ACTION:
+                    return Files.newOutputStream(((File) arg1).toPath());
             }
 
             return null;
diff --git a/framework/src/main/java/org/apache/felix/framework/util/Util.java b/framework/src/main/java/org/apache/felix/framework/util/Util.java
index f6acd36..b8618bb 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/Util.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/Util.java
@@ -45,8 +45,9 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -60,37 +61,51 @@
 {
     /**
      * The default name used for the default configuration properties file.
-    **/
-    private static final String DEFAULT_PROPERTIES_FILE = "default.properties";
+     **/
+    private static final String DEFAULT_PROPERTIES_FILE = "/default.properties";
+
+    private static final Properties DEFAULTS;
+
+    private static final IOException DEFAULT_EX;
+
+    private static final Map<String, Set<String>> MODULES_MAP;
+
+    static
+    {
+        Properties defaults = null;
+        IOException defaultEX = null;
+        try
+        {
+            defaults = loadDefaultProperties();
+        }
+        catch (IOException ex)
+        {
+            defaultEX = ex;
+        }
+        DEFAULTS = defaults;
+        DEFAULT_EX = defaultEX;
+
+        MODULES_MAP = calculateModulesMap();
+    }
 
     public static Properties loadDefaultProperties(Logger logger)
     {
+        if (DEFAULTS.isEmpty())
+        {
+            logger.log(Logger.LOG_ERROR, "Unable to load any configuration properties.", DEFAULT_EX);
+        }
+        return DEFAULTS;
+    }
+
+    private static Properties loadDefaultProperties() throws IOException
+    {
         Properties defaultProperties = new Properties();
-        URL propURL = Util.class.getClassLoader().getResource(DEFAULT_PROPERTIES_FILE);
+        URL propURL = Util.class.getResource(DEFAULT_PROPERTIES_FILE);
         if (propURL != null)
         {
-            InputStream is = null;
-            try
+            try (InputStream is = propURL.openConnection().getInputStream())
             {
-                // Load properties from URL.
-                is = propURL.openConnection().getInputStream();
                 defaultProperties.load(is);
-                is.close();
-            }
-            catch (Exception ex)
-            {
-                // Try to close input stream if we have one.
-                try
-                {
-                    if (is != null) is.close();
-                }
-                catch (IOException ex2)
-                {
-                    // Nothing we can do.
-                }
-
-                logger.log(
-                    Logger.LOG_ERROR, "Unable to load any configuration properties.", ex);
             }
         }
         return defaultProperties;
@@ -138,13 +153,15 @@
                 properties.put("ee-jpms", ee.toString());
 
                 properties.put("eecap-jpms", eecap.toString());
+
+                properties.put("felix.detect.jpms", "jpms");
             }
 
-            properties.put("felix.detect.java.specification.version", version.getMajor() < 9 ? ("1." + (version.getMinor() > 6 ? version.getMinor() : 6)) : Integer.toString(version.getMajor()));
+            properties.put("felix.detect.java.specification.version", version.getMajor() < 9 ? ("1." + (version.getMinor() > 6 ? version.getMinor() < 8 ? version.getMinor() : 8 : 6)) : Integer.toString(version.getMajor()));
 
             if (version.getMajor() < 9)
             {
-                properties.put("felix.detect.java.version", String.format("0.0.0.JavaSE_001_%03d", version.getMinor() > 6 ? version.getMinor() : 6));
+                properties.put("felix.detect.java.version", String.format("0.0.0.JavaSE_001_%03d", version.getMinor() > 6 ? version.getMinor() < 8 ? version.getMinor() : 8 : 6));
             }
             else
             {
@@ -157,9 +174,9 @@
         }
     }
 
-    public static Map<String, Set<String>> initializeJPMS(Properties properties)
+    private static Map<String, Set<String>> calculateModulesMap()
     {
-        Map<String,Set<String>> exports = null;
+        Map<String, Set<String>> result = new LinkedHashMap<>();
         try
         {
             Class<?> c_ModuleLayer = Felix.class.getClassLoader().loadClass("java.lang.ModuleLayer");
@@ -179,16 +196,13 @@
                 moduleLayer = c_ModuleLayer.getMethod("boot").invoke(null);
             }
 
-            Set<String> modules = new TreeSet<String>();
-            exports = new HashMap<String, Set<String>>();
             for (Object module : ((Iterable) c_ModuleLayer.getMethod("modules").invoke(moduleLayer)))
             {
                 if ((Boolean) m_canRead.invoke(self, module))
                 {
-                    Object name = m_getName.invoke(module);
-                    properties.put("felix.detect.jpms." + name, name);
-                    modules.add("felix.jpms." + name);
-                    Set<String> pkgs = new HashSet<String>();
+                    String name = (String) m_getName.invoke(module);
+
+                    Set<String> pkgs = new LinkedHashSet<>();
 
                     Object descriptor = c_Module.getMethod("getDescriptor").invoke(module);
 
@@ -199,24 +213,45 @@
                             pkgs.add((String) c_Exports.getMethod("source").invoke(export));
                         }
                     }
-                    if (!pkgs.isEmpty())
-                    {
-                        exports.put("felix.jpms." + c_Descriptor.getMethod("toNameAndVersion").invoke(descriptor), pkgs);
-                    }
+                    result.put(name, pkgs);
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            //ex.printStackTrace();
+            // Not much we can do - probably not on java9
+        }
+        return result;
+    }
+
+    public static Map<String, Set<String>> initializeJPMS(Properties properties)
+    {
+        Map<String,Set<String>> exports = null;
+        if (!MODULES_MAP.isEmpty())
+        {
+            Set<String> modules = new TreeSet<String>();
+            exports = new HashMap<String, Set<String>>();
+            for (Map.Entry<String, Set<String>> module : MODULES_MAP.entrySet())
+            {
+                Object name = module.getKey();
+                properties.put("felix.detect.jpms." + name, name);
+                modules.add("felix.jpms." + name);
+                Set<String> pkgs = module.getValue();
+
+                if (!pkgs.isEmpty())
+                {
+                    exports.put("felix.jpms." + name, pkgs);
                 }
             }
 
-            properties.put("felix.detect.jpms", "jpms");
             String modulesString = "";
             for (String module : modules) {
                 modulesString += "${" + module + "}";
             }
             properties.put("jre-jpms", modulesString);
         }
-        catch (Exception ex)
-        {
-            // Not much we can do - probably not on java9
-        }
+
         return exports;
     }
 
@@ -427,6 +462,31 @@
         return null;
     }
 
+    public static boolean checkImplementsWithName(Class<?> clazz, String name)
+    {
+        while (clazz != null)
+        {
+            if (clazz.getName().equals(name))
+            {
+                return true;
+            }
+            // Try to see if
+            // one of the class's implemented interface
+            // is the name.
+            Class[] ifcs = clazz.getInterfaces();
+            for (int i = 0; i < ifcs.length; i++)
+            {
+                if (checkImplementsWithName(ifcs[i], name)) {
+                    return true;
+                }
+            }
+
+            // Try the super class class loader.
+            clazz = clazz.getSuperclass();
+        }
+        return false;
+    }
+
     /**
      * This method determines if the requesting bundle is able to cast
      * the specified service reference based on class visibility rules
@@ -1034,4 +1094,4 @@
     {
         return map == null || map.isEmpty() ? EMPTY_MAP : Collections.unmodifiableMap(map);
     }
-}
\ No newline at end of file
+}
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
index 9158c6a..09e0572 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
@@ -20,6 +20,7 @@
 
 import org.apache.felix.framework.BundleRevisionImpl;
 import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.cache.ConnectContentContent;
 import org.apache.felix.framework.capabilityset.SimpleFilter;
 import org.apache.felix.framework.util.FelixConstants;
 import org.apache.felix.framework.wiring.BundleCapabilityImpl;
@@ -28,6 +29,7 @@
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 import org.osgi.framework.VersionRange;
+import org.osgi.framework.connect.ConnectContent;
 import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
 import org.osgi.framework.namespace.IdentityNamespace;
@@ -41,6 +43,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -109,7 +112,7 @@
         // Parse bundle symbolic name.
         //
 
-        BundleCapabilityImpl bundleCap = parseBundleSymbolicName(owner, m_headerMap);
+        BundleCapabilityImpl bundleCap = parseBundleSymbolicName(logger, owner, m_headerMap);
         if (bundleCap != null)
         {
             m_bundleSymbolicName = (String)
@@ -215,7 +218,7 @@
         List<ParsedHeaderClause> exportClauses =
             parseStandardHeader((String) headerMap.get(Constants.EXPORT_PACKAGE));
         exportClauses = normalizeExportClauses(logger, exportClauses,
-            getManifestVersion(), m_bundleSymbolicName, m_bundleVersion);
+            getManifestVersion(), m_bundleSymbolicName, m_bundleVersion, owner instanceof BundleRevisionImpl && ((BundleRevisionImpl) owner).getContent() instanceof ConnectContentContent);
         List<BundleCapability> exportCaps = convertExports(exportClauses, owner);
 
         //
@@ -696,7 +699,7 @@
         throws BundleException
     {
 
-        if (!mv.equals("2") && !clauses.isEmpty())
+        if (mv != null && !mv.equals("2") && !clauses.isEmpty())
         {
             // Should we error here if we are not an R4 bundle?
         }
@@ -836,7 +839,7 @@
 
     private static List<ParsedHeaderClause> normalizeExportClauses(
         Logger logger, List<ParsedHeaderClause> clauses,
-        String mv, String bsn, Version bv)
+        String mv, String bsn, Version bv, boolean connectModule)
         throws BundleException
     {
         for (ParsedHeaderClause clause : clauses)
@@ -845,7 +848,7 @@
             for (String pkgName : clause.m_paths)
             {
                 // Verify that java.* packages are not exported (except from the system bundle).
-                if (!FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(bsn) && pkgName.startsWith("java."))
+                if ((!FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(bsn) && !connectModule) && pkgName.startsWith("java."))
                 {
                     throw new BundleException(
                         "Exporting java.* packages not allowed: "
@@ -1358,12 +1361,12 @@
         return false;
     }
 
-    private static BundleCapabilityImpl parseBundleSymbolicName(
+    private static BundleCapabilityImpl parseBundleSymbolicName(Logger logger,
         BundleRevision owner, Map<String, Object> headerMap)
         throws BundleException
     {
-        List<ParsedHeaderClause> clauses = parseStandardHeader(
-            (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+        List<ParsedHeaderClause> clauses = normalizeCapabilityClauses(logger, parseStandardHeader(
+            (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME)), getManifestVersion(headerMap));
         if (clauses.size() > 0)
         {
             if (clauses.size() > 1)
@@ -1378,6 +1381,11 @@
                     "Cannot have multiple symbolic names: "
                         + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
             }
+            else if (clauses.get(0).m_attrs.containsKey(Constants.BUNDLE_VERSION))
+            {
+                throw new BundleException(
+                    "Cannot have a bundle version: " + headerMap.get(Constants.BUNDLE_VERSION));
+            }
 
             // Get bundle version.
             Version bundleVersion = Version.emptyVersion;
@@ -1400,6 +1408,48 @@
                 }
             }
 
+            Object tagList = clauses.get(0).m_attrs.get(IdentityNamespace.CAPABILITY_TAGS_ATTRIBUTE);
+            LinkedHashSet<String> tags = new LinkedHashSet<>();
+            if (tagList != null)
+            {
+                if (tagList instanceof List)
+                {
+                    for (Object member : ((List) tagList))
+                    {
+                        if (member instanceof String)
+                        {
+                            tags.add((String) member);
+                        }
+                        else
+                        {
+                            throw new BundleException("Invalid tags list: " + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+                        }
+                    }
+                }
+                else if (tagList instanceof String)
+                {
+                    tags.add((String) tagList);
+                }
+                else
+                {
+                    throw new BundleException("Invalid tags list: " + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+                }
+            }
+
+            if (tags.contains(ConnectContent.TAG_OSGI_CONNECT))
+            {
+                throw new BundleException("Invalid tags list: " + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
+            }
+            if (owner != null && ((BundleRevisionImpl) owner).getContent() instanceof ConnectContentContent)
+            {
+                tags.add(ConnectContent.TAG_OSGI_CONNECT);
+            }
+
+            if (!tags.isEmpty())
+            {
+                clauses.get(0).m_attrs.put(IdentityNamespace.CAPABILITY_TAGS_ATTRIBUTE, new ArrayList<>(tags));
+            }
+
             // Create a require capability and return it.
             String symName = (String) clauses.get(0).m_paths.get(0);
             clauses.get(0).m_attrs.put(BundleRevision.BUNDLE_NAMESPACE, symName);
@@ -1415,9 +1465,9 @@
     }
 
     private static BundleCapabilityImpl addIdentityCapability(BundleRevision owner,
-        Map<String, Object> headerMap, BundleCapabilityImpl bundleCap)
+        Map<String, Object> headerMap, BundleCapabilityImpl bundleCap) throws BundleException
     {
-        Map<String, Object> attrs = new HashMap<String, Object>();
+        Map<String, Object> attrs = new HashMap<String, Object>(bundleCap.getAttributes());
 
         attrs.put(IdentityNamespace.IDENTITY_NAMESPACE,
             bundleCap.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE));
diff --git a/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java b/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
index 9fa3806..f426ad1 100644
--- a/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
@@ -25,7 +25,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
-import org.apache.felix.framework.capabilityset.SimpleFilter;
 import org.apache.felix.framework.util.Util;
 import org.apache.felix.framework.util.manifestparser.ManifestParser;
 import org.osgi.framework.Constants;
@@ -41,8 +40,6 @@
     private final Map<String, String> m_dirs;
     private final Map<String, Object> m_attrs;
     private final List<String> m_uses;
-    private final List<List<String>> m_includeFilter;
-    private final List<List<String>> m_excludeFilter;
     private final Set<String> m_mandatory;
 
     public BundleCapabilityImpl(BundleRevision revision, String namespace,
@@ -55,13 +52,13 @@
 
         // Find all export directives: uses, mandatory, include, and exclude.
 
-        List<String> uses = Collections.EMPTY_LIST;
+        List<String> uses = Collections.emptyList();
         String value = m_dirs.get(Constants.USES_DIRECTIVE);
         if (value != null)
         {
             // Parse these uses directive.
             StringTokenizer tok = new StringTokenizer(value, ",");
-            uses = new ArrayList(tok.countTokens());
+            uses = new ArrayList<>(tok.countTokens());
             while (tok.hasMoreTokens())
             {
                 uses.add(tok.nextToken().trim());
@@ -69,44 +66,12 @@
         }
         m_uses = uses;
 
-        value = m_dirs.get(Constants.INCLUDE_DIRECTIVE);
-        if (value != null)
-        {
-            List<String> filters = ManifestParser.parseDelimitedString(value, ",");
-            m_includeFilter = new ArrayList<List<String>>(filters.size());
-            for (int filterIdx = 0; filterIdx < filters.size(); filterIdx++)
-            {
-                List<String> substrings = SimpleFilter.parseSubstring(filters.get(filterIdx));
-                m_includeFilter.add(substrings);
-            }
-        }
-        else
-        {
-            m_includeFilter = null;
-        }
-
-        value = m_dirs.get(Constants.EXCLUDE_DIRECTIVE);
-        if (value != null)
-        {
-            List<String> filters = ManifestParser.parseDelimitedString(value, ",");
-            m_excludeFilter = new ArrayList<List<String>>(filters.size());
-            for (int filterIdx = 0; filterIdx < filters.size(); filterIdx++)
-            {
-                List<String> substrings = SimpleFilter.parseSubstring(filters.get(filterIdx));
-                m_excludeFilter.add(substrings);
-            }
-        }
-        else
-        {
-            m_excludeFilter = null;
-        }
-
-        Set<String> mandatory = Collections.EMPTY_SET;
+        Set<String> mandatory = Collections.emptySet();
         value = m_dirs.get(Constants.MANDATORY_DIRECTIVE);
         if (value != null)
         {
             List<String> names = ManifestParser.parseDelimitedString(value, ",");
-            mandatory = new HashSet<String>(names.size());
+            mandatory = new HashSet<>(names.size());
             for (String name : names)
             {
                 // If attribute exists, then record it as mandatory.
@@ -160,38 +125,6 @@
         return m_uses;
     }
 
-    public boolean isIncluded(String name)
-    {
-        if ((m_includeFilter == null) && (m_excludeFilter == null))
-        {
-            return true;
-        }
-
-        // Get the class name portion of the target class.
-        String className = Util.getClassName(name);
-
-        // If there are no include filters then all classes are included
-        // by default, otherwise try to find one match.
-        boolean included = (m_includeFilter == null);
-        for (int i = 0;
-            (!included) && (m_includeFilter != null) && (i < m_includeFilter.size());
-            i++)
-        {
-            included = SimpleFilter.compareSubstring(m_includeFilter.get(i), className);
-        }
-
-        // If there are no exclude filters then no classes are excluded
-        // by default, otherwise try to find one match.
-        boolean excluded = false;
-        for (int i = 0;
-            (!excluded) && (m_excludeFilter != null) && (i < m_excludeFilter.size());
-            i++)
-        {
-            excluded = SimpleFilter.compareSubstring(m_excludeFilter.get(i), className);
-        }
-        return included && !excluded;
-    }
-
     @Override
     public String toString()
     {
diff --git a/framework/src/main/java/org/apache/felix/framework/wiring/BundleRequirementImpl.java b/framework/src/main/java/org/apache/felix/framework/wiring/BundleRequirementImpl.java
index 3e3b685..881d366 100644
--- a/framework/src/main/java/org/apache/felix/framework/wiring/BundleRequirementImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/wiring/BundleRequirementImpl.java
@@ -61,7 +61,7 @@
         BundleRevision revision, String namespace,
         Map<String, String> dirs, Map<String, Object> attrs)
     {
-        this(revision, namespace, dirs, Collections.EMPTY_MAP, SimpleFilter.convert(attrs));
+        this(revision, namespace, dirs, Collections.emptyMap(), SimpleFilter.convert(attrs));
     }
 
     public String getNamespace()
@@ -91,7 +91,7 @@
 
     public boolean matches(BundleCapability cap)
     {
-        return CapabilitySet.matches((BundleCapabilityImpl) cap, getFilter());
+        return CapabilitySet.matches(cap, getFilter());
     }
 
     public boolean isOptional()
diff --git a/framework/src/main/java/org/osgi/dto/DTO.java b/framework/src/main/java/org/osgi/dto/DTO.java
index 53074a6..f109341 100644
--- a/framework/src/main/java/org/osgi/dto/DTO.java
+++ b/framework/src/main/java/org/osgi/dto/DTO.java
@@ -35,7 +35,7 @@
  * The object graph from a Data Transfer Object must be a tree to simplify
  * serialization and deserialization.
  * 
- * @author $Id$
+ * @author $Id: DTO.java 1825132 2018-02-23 15:11:00Z pauls $
  * @NotThreadSafe
  */
 public abstract class DTO {
diff --git a/framework/src/main/java/org/osgi/dto/package-info.java b/framework/src/main/java/org/osgi/dto/package-info.java
index d0d0ad1..0a1637e 100644
--- a/framework/src/main/java/org/osgi/dto/package-info.java
+++ b/framework/src/main/java/org/osgi/dto/package-info.java
@@ -30,7 +30,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.dto; version="[1.1,1.2)"}
  *
- * @author $Id$
+ * @author $Id: package-info.java 1825132 2018-02-23 15:11:00Z pauls $
  */
 
 @Version("1.1")
diff --git a/framework/src/main/java/org/osgi/framework/BundlePermission.java b/framework/src/main/java/org/osgi/framework/BundlePermission.java
index 2371599..d4de480 100644
--- a/framework/src/main/java/org/osgi/framework/BundlePermission.java
+++ b/framework/src/main/java/org/osgi/framework/BundlePermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2004, 2016). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2004, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@
  * 
  * @since 1.3
  * @ThreadSafe
- * @author $Id: 7b0816059dc9b3e37f0375039bebbe5f0b18d998 $
+ * @author $Id: 7a45b1cb579ff9cfd1582f82436dd755ff805fba $
  */
 
 public final class BundlePermission extends BasicPermission {
@@ -543,7 +543,7 @@
 			// work our way up the tree...
 			int last;
 			int offset = requestedName.length() - 1;
-			while ((last = requestedName.lastIndexOf(".", offset)) != -1) {
+			while ((last = requestedName.lastIndexOf('.', offset)) != -1) {
 				requestedName = requestedName.substring(0, last + 1) + "*";
 				bp = pc.get(requestedName);
 				if (bp != null) {
diff --git a/framework/src/main/java/org/osgi/framework/CapabilityPermission.java b/framework/src/main/java/org/osgi/framework/CapabilityPermission.java
index 177ff97..60a5202 100644
--- a/framework/src/main/java/org/osgi/framework/CapabilityPermission.java
+++ b/framework/src/main/java/org/osgi/framework/CapabilityPermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2000, 2017). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2000, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@
  * </ul>
  * 
  * @ThreadSafe
- * @author $Id: 8a38df04e56e9dcab7ea413ba69d4c4f05487c25 $
+ * @author $Id: c7bae50cab3a92ba810fcf983081c008f22a6bbd $
  * @since 1.6
  */
 
@@ -714,7 +714,7 @@
 			/* work our way up the tree... */
 			int last;
 			int offset = requestedName.length() - 1;
-			while ((last = requestedName.lastIndexOf(".", offset)) != -1) {
+			while ((last = requestedName.lastIndexOf('.', offset)) != -1) {
 				requestedName = requestedName.substring(0, last + 1) + "*";
 				cp = pc.get(requestedName);
 				if (cp != null) {
diff --git a/framework/src/main/java/org/osgi/framework/Constants.java b/framework/src/main/java/org/osgi/framework/Constants.java
index 68dfb53..19c68b8 100644
--- a/framework/src/main/java/org/osgi/framework/Constants.java
+++ b/framework/src/main/java/org/osgi/framework/Constants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2000, 2018). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2000, 2020). All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
  * otherwise indicated.
  * 
  * @since 1.1
- * @author $Id: 41e648afb56767a610f279a9c7effef47dfcbf2e $
+ * @author $Id: 57af8721d5f7e08f9b8a1d9f109a5ebb94bf9646 $
  */
 @ProviderType
 public interface Constants {
@@ -1025,7 +1025,6 @@
 	/**
 	 * Framework environment property identifying whether the Framework supports
 	 * bootclasspath extension bundles.
-	 * 
 	 * <p>
 	 * If the value of this property is {@code true}, then the Framework
 	 * supports bootclasspath extension bundles. The default value is
@@ -1035,6 +1034,7 @@
 	 * {@code BundleContext.getProperty} method.
 	 * 
 	 * @since 1.3
+	 * @deprecated As of 1.10.
 	 */
 	String	SUPPORTS_BOOTCLASSPATH_EXTENSION		= "org.osgi.supports.bootclasspath.extension";
 
@@ -1574,7 +1574,7 @@
 	String	SERVICE_IMPORTED_CONFIGS				= "service.imported.configs";
 
 	/**
-	 * Service property identifying the intents that this service implement.
+	 * Service property identifying the intents that this service implements.
 	 * This property has a dual purpose:
 	 * <ul>
 	 * <li>A bundle can use this service property to notify the distribution
@@ -1833,8 +1833,8 @@
 	 * Service property identifying the monotonically increasing change count of
 	 * a service.
 	 * <p>
-	 * A service may optional provide this property to indicate there has been a
-	 * change in some data provided by the service. The change count must be
+	 * A service may provide this property to indicate there has been a change
+	 * in some data provided by the service. The change count must be
 	 * incremented with a positive value every time the data provided by the
 	 * service is changed. The service announces the modified change count by
 	 * updating its service properties with the new value for this service
diff --git a/framework/src/main/java/org/osgi/framework/FilterImpl.java b/framework/src/main/java/org/osgi/framework/FilterImpl.java
new file mode 100755
index 0000000..5d88a58
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/FilterImpl.java
@@ -0,0 +1,1385 @@
+/*
+ * Copyright (c) OSGi Alliance (2005, 2020). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.framework;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * RFC 1960-based Filter. Filter objects can be created by calling the
+ * constructor with the desired filter string. A Filter object can be called
+ * numerous times to determine if the match argument matches the filter string
+ * that was used to create the Filter object.
+ * <p>
+ * The syntax of a filter string is the string representation of LDAP search
+ * filters as defined in RFC 1960: <i>A String Representation of LDAP Search
+ * Filters</i> (available at http://www.ietf.org/rfc/rfc1960.txt). It should be
+ * noted that RFC 2254: <i>A String Representation of LDAP Search Filters</i>
+ * (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes RFC 1960 but
+ * only adds extensible matching and is not applicable for this API.
+ * <p>
+ * The string representation of an LDAP search filter is defined by the
+ * following grammar. It uses a prefix format.
+ * 
+ * <pre>
+ *   &lt;filter&gt; ::= '(' &lt;filtercomp&gt; ')'
+ *   &lt;filtercomp&gt; ::= &lt;and&gt; | &lt;or&gt; | &lt;not&gt; | &lt;item&gt;
+ *   &lt;and&gt; ::= '&amp;' &lt;filterlist&gt;
+ *   &lt;or&gt; ::= '|' &lt;filterlist&gt;
+ *   &lt;not&gt; ::= '!' &lt;filter&gt;
+ *   &lt;filterlist&gt; ::= &lt;filter&gt; | &lt;filter&gt; &lt;filterlist&gt;
+ *   &lt;item&gt; ::= &lt;simple&gt; | &lt;present&gt; | &lt;substring&gt;
+ *   &lt;simple&gt; ::= &lt;attr&gt; &lt;filtertype&gt; &lt;value&gt;
+ *   &lt;filtertype&gt; ::= &lt;equal&gt; | &lt;approx&gt; | &lt;greater&gt; | &lt;less&gt;
+ *   &lt;equal&gt; ::= '='
+ *   &lt;approx&gt; ::= '&tilde;='
+ *   &lt;greater&gt; ::= '&gt;='
+ *   &lt;less&gt; ::= '&lt;='
+ *   &lt;present&gt; ::= &lt;attr&gt; '=*'
+ *   &lt;substring&gt; ::= &lt;attr&gt; '=' &lt;initial&gt; &lt;any&gt; &lt;final&gt;
+ *   &lt;initial&gt; ::= NULL | &lt;value&gt;
+ *   &lt;any&gt; ::= '*' &lt;starval&gt;
+ *   &lt;starval&gt; ::= NULL | &lt;value&gt; '*' &lt;starval&gt;
+ *   &lt;final&gt; ::= NULL | &lt;value&gt;
+ * </pre>
+ * 
+ * {@code &lt;attr&gt;} is a string representing an attribute, or key, in the
+ * properties objects of the registered services. Attribute names are not case
+ * sensitive; that is cn and CN both refer to the same attribute.
+ * {@code &lt;value&gt;} is a string representing the value, or part of one, of
+ * a key in the properties objects of the registered services. If a
+ * {@code &lt;value&gt;} must contain one of the characters ' {@code *}' or
+ * '{@code (}' or '{@code )}', these characters should be escaped by preceding
+ * them with the backslash '{@code \}' character. Note that although both the
+ * {@code &lt;substring&gt;} and {@code &lt;present&gt;} productions can produce
+ * the {@code 'attr=*'} construct, this construct is used only to denote a
+ * presence filter.
+ * <p>
+ * Examples of LDAP filters are:
+ * 
+ * <pre>
+ *   &quot;(cn=Babs Jensen)&quot;
+ *   &quot;(!(cn=Tim Howes))&quot;
+ *   &quot;(&amp;(&quot; + Constants.OBJECTCLASS + &quot;=Person)(|(sn=Jensen)(cn=Babs J*)))&quot;
+ *   &quot;(o=univ*of*mich*)&quot;
+ * </pre>
+ * <p>
+ * The approximate match ({@code ~=}) is implementation specific but should at
+ * least ignore case and white space differences. Optional are codes like
+ * soundex or other smart "closeness" comparisons.
+ * <p>
+ * Comparison of values is not straightforward. Strings are compared differently
+ * than numbers and it is possible for a key to have multiple values. Note that
+ * that keys in the match argument must always be strings. The comparison is
+ * defined by the object type of the key's value. The following rules apply for
+ * comparison: <blockquote>
+ * <TABLE BORDER=0>
+ * <TR>
+ * <TD><b>Property Value Type </b></TD>
+ * <TD><b>Comparison Type</b></TD>
+ * </TR>
+ * <TR>
+ * <TD>String</TD>
+ * <TD>String comparison</TD>
+ * </TR>
+ * <TR valign=top>
+ * <TD>Integer, Long, Float, Double, Byte, Short, BigInteger, BigDecimal</TD>
+ * <TD>numerical comparison</TD>
+ * </TR>
+ * <TR>
+ * <TD>Character</TD>
+ * <TD>character comparison</TD>
+ * </TR>
+ * <TR>
+ * <TD>Boolean</TD>
+ * <TD>equality comparisons only</TD>
+ * </TR>
+ * <TR>
+ * <TD>[] (array)</TD>
+ * <TD>recursively applied to values</TD>
+ * </TR>
+ * <TR>
+ * <TD>Collection</TD>
+ * <TD>recursively applied to values</TD>
+ * </TR>
+ * </TABLE>
+ * Note: arrays of primitives are also supported. </blockquote> A filter matches
+ * a key that has multiple values if it matches at least one of those values.
+ * For example,
+ * 
+ * <pre>
+ * Dictionary d = new Hashtable();
+ * d.put(&quot;cn&quot;, new String[] {
+ * 		&quot;a&quot;, &quot;b&quot;, &quot;c&quot;
+ * });
+ * </pre>
+ * 
+ * d will match {@code (cn=a)} and also {@code (cn=b)}
+ * <p>
+ * A filter component that references a key having an unrecognizable data type
+ * will evaluate to {@code false} .
+ */
+abstract class FilterImpl implements Filter {
+	/* normalized filter string for Filter object */
+	private transient String filterString;
+
+	/**
+	 * Creates a {@link FilterImpl} object. This filter object may be used to
+	 * match a {@link ServiceReference} or a Dictionary.
+	 * <p>
+	 * If the filter cannot be parsed, an {@link InvalidSyntaxException} will be
+	 * thrown with a human readable message where the filter became unparsable.
+	 * 
+	 * @param filterString the filter string.
+	 * @throws InvalidSyntaxException If the filter parameter contains an
+	 *             invalid filter string that cannot be parsed.
+	 */
+	static FilterImpl createFilter(String filterString)
+			throws InvalidSyntaxException {
+		return new Parser(filterString).parse();
+	}
+
+	FilterImpl() {
+		// empty constructor for subclasses
+	}
+
+	/**
+	 * Filter using a service's properties.
+	 * <p>
+	 * This {@code Filter} is executed using the keys and values of the
+	 * referenced service's properties. The keys are looked up in a case
+	 * insensitive manner.
+	 * 
+	 * @param reference The reference to the service whose properties are used
+	 *            in the match.
+	 * @return {@code true} if the service's properties match this
+	 *         {@code Filter}; {@code false} otherwise.
+	 */
+	@Override
+	public boolean match(ServiceReference< ? > reference) {
+		return matches0((reference != null) ? new ServiceReferenceMap(reference)
+				: Collections.emptyMap());
+	}
+
+	/**
+	 * Filter using a {@code Dictionary} with case insensitive key lookup. This
+	 * {@code Filter} is executed using the specified {@code Dictionary}'s keys
+	 * and values. The keys are looked up in a case insensitive manner.
+	 * 
+	 * @param dictionary The {@code Dictionary} whose key/value pairs are used
+	 *            in the match.
+	 * @return {@code true} if the {@code Dictionary}'s values match this
+	 *         filter; {@code false} otherwise.
+	 * @throws IllegalArgumentException If {@code dictionary} contains case
+	 *             variants of the same key name.
+	 */
+	@Override
+	public boolean match(Dictionary<String, ? > dictionary) {
+		return matches0(
+				(dictionary != null) ? new CaseInsensitiveMap(dictionary)
+						: Collections.emptyMap());
+	}
+
+	/**
+	 * Filter using a {@code Dictionary}. This {@code Filter} is executed using
+	 * the specified {@code Dictionary}'s keys and values. The keys are looked
+	 * up in a normal manner respecting case.
+	 * 
+	 * @param dictionary The {@code Dictionary} whose key/value pairs are used
+	 *            in the match.
+	 * @return {@code true} if the {@code Dictionary}'s values match this
+	 *         filter; {@code false} otherwise.
+	 * @since 1.3
+	 */
+	@Override
+	public boolean matchCase(Dictionary<String, ? > dictionary) {
+		return matches0((dictionary != null) ? DictionaryMap.asMap(dictionary)
+				: Collections.emptyMap());
+	}
+
+	/**
+	 * Filter using a {@code Map}. This {@code Filter} is executed using the
+	 * specified {@code Map}'s keys and values. The keys are looked up in a
+	 * normal manner respecting case.
+	 * 
+	 * @param map The {@code Map} whose key/value pairs are used in the match.
+	 *            Maps with {@code null} key or values are not supported. A
+	 *            {@code null} value is considered not present to the filter.
+	 * @return {@code true} if the {@code Map}'s values match this filter;
+	 *         {@code false} otherwise.
+	 * @since 1.6
+	 */
+	@Override
+	public boolean matches(Map<String, ? > map) {
+		return matches0((map != null) ? map : Collections.emptyMap());
+	}
+
+	abstract boolean matches0(Map<String, ? > map);
+
+	/**
+	 * Returns this {@code Filter}'s filter string.
+	 * <p>
+	 * The filter string is normalized by removing whitespace which does not
+	 * affect the meaning of the filter.
+	 * 
+	 * @return This {@code Filter}'s filter string.
+	 */
+	@Override
+	public String toString() {
+		String result = filterString;
+		if (result == null) {
+			filterString = result = normalize(new StringBuilder()).toString();
+		}
+		return result;
+	}
+
+	/**
+	 * Returns this {@code Filter}'s normalized filter string.
+	 * <p>
+	 * The filter string is normalized by removing whitespace which does not
+	 * affect the meaning of the filter.
+	 * 
+	 * @return This {@code Filter}'s filter string.
+	 */
+	abstract StringBuilder normalize(StringBuilder sb);
+
+	/**
+	 * Compares this {@code Filter} to another {@code Filter}.
+	 * <p>
+	 * This implementation returns the result of calling
+	 * {@code this.toString().equals(obj.toString()}.
+	 * 
+	 * @param obj The object to compare against this {@code Filter}.
+	 * @return If the other object is a {@code Filter} object, then returns the
+	 *         result of calling {@code this.toString().equals(obj.toString()};
+	 *         {@code false} otherwise.
+	 */
+	@Override
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return true;
+		}
+
+		if (!(obj instanceof Filter)) {
+			return false;
+		}
+
+		return this.toString().equals(obj.toString());
+	}
+
+	/**
+	 * Returns the hashCode for this {@code Filter}.
+	 * <p>
+	 * This implementation returns the result of calling
+	 * {@code this.toString().hashCode()}.
+	 * 
+	 * @return The hashCode of this {@code Filter}.
+	 */
+	@Override
+	public int hashCode() {
+		return this.toString().hashCode();
+	}
+
+	static final class And extends FilterImpl {
+		private final FilterImpl[] operands;
+
+		And(FilterImpl[] operands) {
+			this.operands = operands;
+		}
+
+		@Override
+		boolean matches0(Map<String, ? > map) {
+			for (FilterImpl operand : operands) {
+				if (!operand.matches0(map)) {
+					return false;
+				}
+			}
+			return true;
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append('&');
+			for (FilterImpl operand : operands) {
+				operand.normalize(sb);
+			}
+			return sb.append(')');
+		}
+	}
+
+	static final class Or extends FilterImpl {
+		private final FilterImpl[] operands;
+
+		Or(FilterImpl[] operands) {
+			this.operands = operands;
+		}
+
+		@Override
+		boolean matches0(Map<String, ? > map) {
+			for (FilterImpl operand : operands) {
+				if (operand.matches0(map)) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append('|');
+			for (FilterImpl operand : operands) {
+				operand.normalize(sb);
+			}
+			return sb.append(')');
+		}
+	}
+
+	static final class Not extends FilterImpl {
+		private final FilterImpl operand;
+
+		Not(FilterImpl operand) {
+			this.operand = operand;
+		}
+
+		@Override
+		boolean matches0(Map<String, ? > map) {
+			return !operand.matches0(map);
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append('!');
+			operand.normalize(sb);
+			return sb.append(')');
+		}
+	}
+
+	static abstract class Item extends FilterImpl {
+		final String attr;
+
+		Item(String attr) {
+			this.attr = attr;
+		}
+
+		@Override
+		boolean matches0(Map<String, ? > map) {
+			return compare(map.get(attr));
+		}
+
+		private boolean compare(Object value1) {
+			if (value1 == null) {
+				return false;
+			}
+			if (value1 instanceof String) {
+				return compare_String((String) value1);
+			}
+			if (value1 instanceof Version) {
+				return compare_Version((Version) value1);
+			}
+
+			Class< ? > clazz = value1.getClass();
+			if (clazz.isArray()) {
+				Class< ? > type = clazz.getComponentType();
+				if (type.isPrimitive()) {
+					return compare_PrimitiveArray(type, value1);
+				}
+				return compare_ObjectArray((Object[]) value1);
+			}
+			if (value1 instanceof Collection< ? >) {
+				return compare_Collection((Collection< ? >) value1);
+			}
+			if (value1 instanceof Integer || value1 instanceof Long
+					|| value1 instanceof Byte || value1 instanceof Short) {
+				return compare_Long(((Number) value1).longValue());
+			}
+			if (value1 instanceof Character) {
+				return compare_Character(((Character) value1).charValue());
+			}
+			if (value1 instanceof Float) {
+				return compare_Float(((Float) value1).floatValue());
+			}
+			if (value1 instanceof Double) {
+				return compare_Double(((Double) value1).doubleValue());
+			}
+			if (value1 instanceof Boolean) {
+				return compare_Boolean(((Boolean) value1).booleanValue());
+			}
+			if (value1 instanceof Comparable< ? >) {
+				@SuppressWarnings("unchecked")
+				Comparable<Object> comparable = (Comparable<Object>) value1;
+				return compare_Comparable(comparable);
+			}
+			return compare_Unknown(value1);
+		}
+
+		private boolean compare_Collection(Collection< ? > collection) {
+			for (Object value1 : collection) {
+				if (compare(value1)) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		private boolean compare_ObjectArray(Object[] array) {
+			for (Object value1 : array) {
+				if (compare(value1)) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		private boolean compare_PrimitiveArray(Class< ? > type,
+				Object primarray) {
+			if (Integer.TYPE.isAssignableFrom(type)) {
+				int[] array = (int[]) primarray;
+				for (int value1 : array) {
+					if (compare_Long(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Long.TYPE.isAssignableFrom(type)) {
+				long[] array = (long[]) primarray;
+				for (long value1 : array) {
+					if (compare_Long(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Byte.TYPE.isAssignableFrom(type)) {
+				byte[] array = (byte[]) primarray;
+				for (byte value1 : array) {
+					if (compare_Long(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Short.TYPE.isAssignableFrom(type)) {
+				short[] array = (short[]) primarray;
+				for (short value1 : array) {
+					if (compare_Long(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Character.TYPE.isAssignableFrom(type)) {
+				char[] array = (char[]) primarray;
+				for (char value1 : array) {
+					if (compare_Character(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Float.TYPE.isAssignableFrom(type)) {
+				float[] array = (float[]) primarray;
+				for (float value1 : array) {
+					if (compare_Float(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Double.TYPE.isAssignableFrom(type)) {
+				double[] array = (double[]) primarray;
+				for (double value1 : array) {
+					if (compare_Double(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			if (Boolean.TYPE.isAssignableFrom(type)) {
+				boolean[] array = (boolean[]) primarray;
+				for (boolean value1 : array) {
+					if (compare_Boolean(value1)) {
+						return true;
+					}
+				}
+				return false;
+			}
+			return false;
+		}
+
+		boolean compare_String(String string) {
+			return false;
+		}
+
+		boolean compare_Version(Version value1) {
+			return false;
+		}
+
+		boolean compare_Comparable(Comparable<Object> value1) {
+			return false;
+		}
+
+		boolean compare_Unknown(Object value1) {
+			return false;
+		}
+
+		boolean compare_Boolean(boolean boolval) {
+			return false;
+		}
+
+		boolean compare_Character(char charval) {
+			return false;
+		}
+
+		boolean compare_Double(double doubleval) {
+			return false;
+		}
+
+		boolean compare_Float(float floatval) {
+			return false;
+		}
+
+		boolean compare_Long(long longval) {
+			return false;
+		}
+
+		/**
+		 * Encode the value string such that '(', '*', ')' and '\' are escaped.
+		 * 
+		 * @param value unencoded value string.
+		 */
+		static StringBuilder encodeValue(StringBuilder sb, String value) {
+			for (int i = 0, len = value.length(); i < len; i++) {
+				char c = value.charAt(i);
+				switch (c) {
+					case '(' :
+					case '*' :
+					case ')' :
+					case '\\' :
+						sb.append('\\');
+						// FALL-THROUGH
+					default :
+						sb.append(c);
+						break;
+				}
+			}
+			return sb;
+		}
+	}
+
+	static final class Present extends Item {
+		Present(String attr) {
+			super(attr);
+		}
+
+		@Override
+		boolean matches0(Map<String, ? > map) {
+			return map.get(attr) != null;
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			return sb.append('(')
+					.append(attr)
+					.append('=')
+					.append('*')
+					.append(')');
+		}
+	}
+
+	static final class Substring extends Item {
+		final String[] substrings;
+
+		Substring(String attr, String[] substrings) {
+			super(attr);
+			this.substrings = substrings;
+		}
+
+		@Override
+		boolean compare_String(String string) {
+			int pos = 0;
+			for (int i = 0, size = substrings.length; i < size; i++) {
+				String substr = substrings[i];
+				if (i + 1 < size) /* if this is not that last substr */ {
+					if (substr == null) /* * */ {
+						String substr2 = substrings[i + 1];
+						if (substr2 == null) /* ** */
+							continue; /* ignore first star */
+						/* xxx */
+						int index = string.indexOf(substr2, pos);
+						if (index == -1) {
+							return false;
+						}
+						pos = index + substr2.length();
+						if (i + 2 < size) // if there are more
+							// substrings, increment
+							// over the string we just
+							// matched; otherwise need
+							// to do the last substr
+							// check
+							i++;
+					} else /* xxx */ {
+						int len = substr.length();
+						if (string.regionMatches(pos, substr, 0, len)) {
+							pos += len;
+						} else {
+							return false;
+						}
+					}
+				} else /* last substr */ {
+					if (substr == null) /* * */ {
+						return true;
+					}
+					/* xxx */
+					return string.endsWith(substr);
+				}
+			}
+			return true;
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append(attr).append('=');
+			for (String substr : substrings) {
+				if (substr == null) /* * */ {
+					sb.append('*');
+				} else /* xxx */ {
+					encodeValue(sb, substr);
+				}
+			}
+			return sb.append(')');
+		}
+	}
+
+	static class Equal extends Item {
+		final String value;
+		private Object	cached;
+
+		Equal(String attr, String value) {
+			super(attr);
+			this.value = value;
+		}
+		
+		private <T> T convert(Class<T> type, Function<String, ? extends T> converter) {
+			@SuppressWarnings("unchecked")
+			T converted = (T) cached;
+			if ((converted != null) && type.isInstance(converted)) {
+				return converted;
+			}
+			cached = converted = converter.apply(value.trim());
+			return converted;
+		}
+
+		boolean comparison(int compare) {
+			return compare == 0;
+		}
+
+		@Override
+		boolean compare_String(String string) {
+			return comparison((string == value) ? 0 : string.compareTo(value));
+		}
+
+		@Override
+		boolean compare_Version(Version value1) {
+			try {
+				Version version2 = convert(Version.class, Version::valueOf);
+				return comparison(value1.compareTo(version2));
+			} catch (Exception e) {
+				// if the valueOf or compareTo method throws an exception
+				return false;
+			}
+		}
+
+		@Override
+		boolean compare_Boolean(boolean boolval) {
+			boolean boolval2 = convert(Boolean.class, Boolean::valueOf).booleanValue();
+			return comparison(Boolean.compare(boolval, boolval2));
+		}
+
+		@Override
+		boolean compare_Character(char charval) {
+			char charval2;
+			try {
+				charval2 = value.charAt(0);
+			} catch (IndexOutOfBoundsException e) {
+				return false;
+			}
+			return comparison(Character.compare(charval, charval2));
+		}
+
+		@Override
+		boolean compare_Double(double doubleval) {
+			double doubleval2;
+			try {
+				doubleval2 = convert(Double.class, Double::valueOf).doubleValue();
+			} catch (IllegalArgumentException e) {
+				return false;
+			}
+			return comparison(Double.compare(doubleval, doubleval2));
+		}
+
+		@Override
+		boolean compare_Float(float floatval) {
+			float floatval2;
+			try {
+				floatval2 = convert(Float.class, Float::valueOf).floatValue();
+			} catch (IllegalArgumentException e) {
+				return false;
+			}
+			return comparison(Float.compare(floatval, floatval2));
+		}
+
+		@Override
+		boolean compare_Long(long longval) {
+			long longval2;
+			try {
+				longval2 = convert(Long.class, Long::valueOf).longValue();
+			} catch (IllegalArgumentException e) {
+				return false;
+			}
+			return comparison(Long.compare(longval, longval2));
+		}
+
+		@Override
+		boolean compare_Comparable(Comparable<Object> value1) {
+			Object value2 = valueOf(value1.getClass());
+			if (value2 == null) {
+				return false;
+			}
+			try {
+				return comparison(value1.compareTo(value2));
+			} catch (Exception e) {
+				// if the compareTo method throws an exception; return false
+				return false;
+			}
+		}
+
+		@Override
+		boolean compare_Unknown(Object value1) {
+			Object value2 = valueOf(value1.getClass());
+			if (value2 == null) {
+				return false;
+			}
+			try {
+				return value1.equals(value2);
+			} catch (Exception e) {
+				// if the equals method throws an exception; return false
+				return false;
+			}
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append(attr).append('=');
+			return encodeValue(sb, value).append(')');
+		}
+
+		Object valueOf(Class< ? > target) {
+			do {
+				Method method;
+				try {
+					method = target.getMethod("valueOf", String.class);
+				} catch (NoSuchMethodException e) {
+					break;
+				}
+				if (Modifier.isStatic(method.getModifiers())
+						&& target.isAssignableFrom(method.getReturnType())) {
+					setAccessible(method);
+					try {
+						return method.invoke(null, value.trim());
+					} catch (Error e) {
+						throw e;
+					} catch (Throwable e) {
+						return null;
+					}
+				}
+			} while (false);
+
+			do {
+				Constructor< ? > constructor;
+				try {
+					constructor = target.getConstructor(String.class);
+				} catch (NoSuchMethodException e) {
+					break;
+				}
+				setAccessible(constructor);
+				try {
+					return constructor.newInstance(value.trim());
+				} catch (Error e) {
+					throw e;
+				} catch (Throwable e) {
+					return null;
+				}
+			} while (false);
+
+			return null;
+		}
+
+		private static void setAccessible(AccessibleObject accessible) {
+			if (!accessible.isAccessible()) {
+				AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+					accessible.setAccessible(true);
+					return null;
+				});
+			}
+		}
+	}
+
+	static final class LessEqual extends Equal {
+		LessEqual(String attr, String value) {
+			super(attr, value);
+		}
+
+		@Override
+		boolean comparison(int compare) {
+			return compare <= 0;
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append(attr).append('<').append('=');
+			return encodeValue(sb, value).append(')');
+		}
+	}
+
+	static final class GreaterEqual extends Equal {
+		GreaterEqual(String attr, String value) {
+			super(attr, value);
+		}
+
+		@Override
+		boolean comparison(int compare) {
+			return compare >= 0;
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append(attr).append('>').append('=');
+			return encodeValue(sb, value).append(')');
+		}
+	}
+
+	static final class Approx extends Equal {
+		final String approx;
+
+		Approx(String attr, String value) {
+			super(attr, value);
+			this.approx = approxString(value);
+		}
+
+		@Override
+		boolean compare_String(String string) {
+			string = approxString(string);
+			return string.equalsIgnoreCase(approx);
+		}
+
+		@Override
+		boolean compare_Character(char charval) {
+			char charval2;
+			try {
+				charval2 = approx.charAt(0);
+			} catch (IndexOutOfBoundsException e) {
+				return false;
+			}
+			return (charval == charval2)
+					|| (Character.toUpperCase(charval) == Character
+							.toUpperCase(charval2))
+					|| (Character.toLowerCase(charval) == Character
+							.toLowerCase(charval2));
+		}
+
+		@Override
+		StringBuilder normalize(StringBuilder sb) {
+			sb.append('(').append(attr).append('~').append('=');
+			return encodeValue(sb, approx).append(')');
+		}
+
+		/**
+		 * Map a string for an APPROX (~=) comparison. This implementation
+		 * removes white spaces. This is the minimum implementation allowed by
+		 * the OSGi spec.
+		 * 
+		 * @param input Input string.
+		 * @return String ready for APPROX comparison.
+		 */
+		static String approxString(String input) {
+			boolean changed = false;
+			char[] output = input.toCharArray();
+			int cursor = 0;
+			for (char c : output) {
+				if (Character.isWhitespace(c)) {
+					changed = true;
+					continue;
+				}
+
+				output[cursor] = c;
+				cursor++;
+			}
+
+			return changed ? new String(output, 0, cursor) : input;
+		}
+	}
+
+	/**
+	 * Parser class for OSGi filter strings. This class parses the complete
+	 * filter string and builds a tree of FilterImpl objects rooted at the
+	 * parent.
+	 */
+	static private final class Parser {
+		private final String	filterstring;
+		private final char[]	filterChars;
+		private int				pos;
+
+		Parser(String filterstring) {
+			this.filterstring = filterstring;
+			filterChars = filterstring.toCharArray();
+			pos = 0;
+		}
+
+		FilterImpl parse() throws InvalidSyntaxException {
+			FilterImpl filter;
+			try {
+				filter = parse_filter();
+			} catch (ArrayIndexOutOfBoundsException e) {
+				throw new InvalidSyntaxException("Filter ended abruptly",
+						filterstring, e);
+			}
+
+			if (pos != filterChars.length) {
+				throw new InvalidSyntaxException(
+						"Extraneous trailing characters: "
+								+ filterstring.substring(pos),
+						filterstring);
+			}
+			return filter;
+		}
+
+		private FilterImpl parse_filter() throws InvalidSyntaxException {
+			FilterImpl filter;
+			skipWhiteSpace();
+
+			if (filterChars[pos] != '(') {
+				throw new InvalidSyntaxException(
+						"Missing '(': " + filterstring.substring(pos),
+						filterstring);
+			}
+
+			pos++;
+
+			filter = parse_filtercomp();
+
+			skipWhiteSpace();
+
+			if (filterChars[pos] != ')') {
+				throw new InvalidSyntaxException(
+						"Missing ')': " + filterstring.substring(pos),
+						filterstring);
+			}
+
+			pos++;
+
+			skipWhiteSpace();
+
+			return filter;
+		}
+
+		private FilterImpl parse_filtercomp() throws InvalidSyntaxException {
+			skipWhiteSpace();
+
+			char c = filterChars[pos];
+
+			switch (c) {
+				case '&' : {
+					pos++;
+					return parse_and();
+				}
+				case '|' : {
+					pos++;
+					return parse_or();
+				}
+				case '!' : {
+					pos++;
+					return parse_not();
+				}
+			}
+			return parse_item();
+		}
+
+		private FilterImpl parse_and() throws InvalidSyntaxException {
+			int lookahead = pos;
+			skipWhiteSpace();
+
+			if (filterChars[pos] != '(') {
+				pos = lookahead - 1;
+				return parse_item();
+			}
+
+			List<FilterImpl> operands = new ArrayList<>(10);
+
+			while (filterChars[pos] == '(') {
+				FilterImpl child = parse_filter();
+				operands.add(child);
+			}
+
+			return new FilterImpl.And(operands.toArray(new FilterImpl[0]));
+		}
+
+		private FilterImpl parse_or() throws InvalidSyntaxException {
+			int lookahead = pos;
+			skipWhiteSpace();
+
+			if (filterChars[pos] != '(') {
+				pos = lookahead - 1;
+				return parse_item();
+			}
+
+			List<FilterImpl> operands = new ArrayList<>(10);
+
+			while (filterChars[pos] == '(') {
+				FilterImpl child = parse_filter();
+				operands.add(child);
+			}
+
+			return new FilterImpl.Or(operands.toArray(new FilterImpl[0]));
+		}
+
+		private FilterImpl parse_not() throws InvalidSyntaxException {
+			int lookahead = pos;
+			skipWhiteSpace();
+
+			if (filterChars[pos] != '(') {
+				pos = lookahead - 1;
+				return parse_item();
+			}
+
+			FilterImpl child = parse_filter();
+
+			return new FilterImpl.Not(child);
+		}
+
+		private FilterImpl parse_item() throws InvalidSyntaxException {
+			String attr = parse_attr();
+
+			skipWhiteSpace();
+
+			switch (filterChars[pos]) {
+				case '~' : {
+					if (filterChars[pos + 1] == '=') {
+						pos += 2;
+						return new FilterImpl.Approx(attr, parse_value());
+					}
+					break;
+				}
+				case '>' : {
+					if (filterChars[pos + 1] == '=') {
+						pos += 2;
+						return new FilterImpl.GreaterEqual(attr, parse_value());
+					}
+					break;
+				}
+				case '<' : {
+					if (filterChars[pos + 1] == '=') {
+						pos += 2;
+						return new FilterImpl.LessEqual(attr, parse_value());
+					}
+					break;
+				}
+				case '=' : {
+					if (filterChars[pos + 1] == '*') {
+						int oldpos = pos;
+						pos += 2;
+						skipWhiteSpace();
+						if (filterChars[pos] == ')') {
+							return new FilterImpl.Present(attr);
+						}
+						pos = oldpos;
+					}
+
+					pos++;
+					String[] substrings = parse_substring();
+
+					int length = substrings.length;
+					if (length == 0) {
+						return new FilterImpl.Equal(attr, "");
+					}
+					if (length == 1) {
+						String single = substrings[0];
+						if (single != null) {
+							return new FilterImpl.Equal(attr, single);
+						}
+					}
+					return new FilterImpl.Substring(attr, substrings);
+				}
+			}
+
+			throw new InvalidSyntaxException(
+					"Invalid operator: " + filterstring.substring(pos),
+					filterstring);
+		}
+
+		private String parse_attr() throws InvalidSyntaxException {
+			skipWhiteSpace();
+
+			int begin = pos;
+			int end = pos;
+
+			char c = filterChars[pos];
+
+			while (c != '~' && c != '<' && c != '>' && c != '=' && c != '('
+					&& c != ')') {
+				pos++;
+
+				if (!Character.isWhitespace(c)) {
+					end = pos;
+				}
+
+				c = filterChars[pos];
+			}
+
+			int length = end - begin;
+
+			if (length == 0) {
+				throw new InvalidSyntaxException(
+						"Missing attr: " + filterstring.substring(pos),
+						filterstring);
+			}
+
+			return new String(filterChars, begin, length);
+		}
+
+		private String parse_value() throws InvalidSyntaxException {
+			StringBuilder sb = new StringBuilder(filterChars.length - pos);
+
+			parseloop: while (true) {
+				char c = filterChars[pos];
+
+				switch (c) {
+					case ')' : {
+						break parseloop;
+					}
+
+					case '(' : {
+						throw new InvalidSyntaxException(
+								"Invalid value: " + filterstring.substring(pos),
+								filterstring);
+					}
+
+					case '\\' : {
+						pos++;
+						c = filterChars[pos];
+						/* fall through into default */
+					}
+
+					default : {
+						sb.append(c);
+						pos++;
+						break;
+					}
+				}
+			}
+
+			if (sb.length() == 0) {
+				throw new InvalidSyntaxException(
+						"Missing value: " + filterstring.substring(pos),
+						filterstring);
+			}
+
+			return sb.toString();
+		}
+
+		private String[] parse_substring() throws InvalidSyntaxException {
+			StringBuilder sb = new StringBuilder(filterChars.length - pos);
+
+			List<String> operands = new ArrayList<>(10);
+
+			parseloop: while (true) {
+				char c = filterChars[pos];
+
+				switch (c) {
+					case ')' : {
+						if (sb.length() > 0) {
+							operands.add(sb.toString());
+						}
+
+						break parseloop;
+					}
+
+					case '(' : {
+						throw new InvalidSyntaxException(
+								"Invalid value: " + filterstring.substring(pos),
+								filterstring);
+					}
+
+					case '*' : {
+						if (sb.length() > 0) {
+							operands.add(sb.toString());
+						}
+
+						sb.setLength(0);
+
+						operands.add(null);
+						pos++;
+
+						break;
+					}
+
+					case '\\' : {
+						pos++;
+						c = filterChars[pos];
+						/* fall through into default */
+					}
+
+					default : {
+						sb.append(c);
+						pos++;
+						break;
+					}
+				}
+			}
+
+			return operands.toArray(new String[0]);
+		}
+
+		private void skipWhiteSpace() {
+			for (int length = filterChars.length; (pos < length)
+					&& Character.isWhitespace(filterChars[pos]);) {
+				pos++;
+			}
+		}
+	}
+
+	/**
+	 * This Map is used for key lookup during filter evaluation. This Map
+	 * implementation only supports the get operation using a String key as no
+	 * other operations are used by the Filter implementation.
+	 */
+	private static class DictionaryMap extends AbstractMap<String,Object>
+			implements Map<String,Object> {
+		static Map<String, ? > asMap(Dictionary<String, ? > dictionary) {
+			if (dictionary instanceof Map) {
+				@SuppressWarnings("unchecked")
+				Map<String, ? > coerced = (Map<String, ? >) dictionary;
+				return coerced;
+			}
+			return new DictionaryMap(dictionary);
+		}
+
+		private final Dictionary<String, ? > dictionary;
+
+		DictionaryMap(Dictionary<String, ? > dictionary) {
+			this.dictionary = requireNonNull(dictionary);
+		}
+
+		@Override
+		public Object get(Object key) {
+			return dictionary.get(key);
+		}
+
+		@Override
+		public Set<Entry<String,Object>> entrySet() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	/**
+	 * This Map is used for case-insensitive key lookup during filter
+	 * evaluation. This Map implementation only supports the get operation using
+	 * a String key as no other operations are used by the Filter
+	 * implementation.
+	 */
+	private static final class CaseInsensitiveMap
+			extends DictionaryMap implements Map<String,Object> {
+		private final String[]					keys;
+
+		/**
+		 * Create a case insensitive map from the specified dictionary.
+		 * 
+		 * @param dictionary
+		 * @throws IllegalArgumentException If {@code dictionary} contains case
+		 *             variants of the same key name.
+		 */
+		CaseInsensitiveMap(Dictionary<String, ? > dictionary) {
+			super(dictionary);
+			List<String> keyList = new ArrayList<>(dictionary.size());
+			for (Enumeration< ? > e = dictionary.keys(); e.hasMoreElements();) {
+				Object k = e.nextElement();
+				if (k instanceof String) {
+					String key = (String) k;
+					for (String i : keyList) {
+						if (key.equalsIgnoreCase(i)) {
+							throw new IllegalArgumentException();
+						}
+					}
+					keyList.add(key);
+				}
+			}
+			this.keys = keyList.toArray(new String[0]);
+		}
+
+		@Override
+		public Object get(Object o) {
+			String k = (String) o;
+			for (String key : keys) {
+				if (key.equalsIgnoreCase(k)) {
+					return super.get(key);
+				}
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * This Map is used for key lookup from a ServiceReference during filter
+	 * evaluation. This Map implementation only supports the get operation using
+	 * a String key as no other operations are used by the Filter
+	 * implementation.
+	 */
+	private static final class ServiceReferenceMap
+			extends AbstractMap<String,Object> implements Map<String,Object> {
+		private final ServiceReference< ? > reference;
+
+		ServiceReferenceMap(ServiceReference< ? > reference) {
+			this.reference = requireNonNull(reference);
+		}
+
+		@Override
+		public Object get(Object key) {
+			return reference.getProperty((String) key);
+		}
+
+		@Override
+		public Set<Entry<String,Object>> entrySet() {
+			throw new UnsupportedOperationException();
+		}
+	}
+}
diff --git a/framework/src/main/java/org/osgi/framework/FrameworkEvent.java b/framework/src/main/java/org/osgi/framework/FrameworkEvent.java
index 84c63f1..9156a79 100644
--- a/framework/src/main/java/org/osgi/framework/FrameworkEvent.java
+++ b/framework/src/main/java/org/osgi/framework/FrameworkEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2004, 2016). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2004, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@
  * 
  * @Immutable
  * @see FrameworkListener
- * @author $Id: b3072b2d058e70389a52e342ed5f8647b930b8f1 $
+ * @author $Id: bcff0614c20b454723977355f99486ca01ee89ea $
  */
 
 public class FrameworkEvent extends EventObject {
@@ -148,13 +148,13 @@
 
 	/**
 	 * The Framework has stopped and the boot class path has changed.
-	 * 
 	 * <p>
 	 * This event is fired when the Framework has been stopped because of a stop
 	 * operation on the system bundle and a bootclasspath extension bundle has
 	 * been installed or updated. The source of this event is the System Bundle.
 	 * 
 	 * @since 1.5
+	 * @deprecated As of 1.10.
 	 */
 	public final static int	STOPPED_BOOTCLASSPATH_MODIFIED	= 0x00000100;
 
@@ -173,9 +173,9 @@
 	 * The Framework has stopped and the framework requires a new class loader
 	 * to restart.
 	 * <p>
-	 * This event is fired when the Framework has been stopped because of a stop
-	 * operation on the system bundle and the framework requires a new class
-	 * loader to be used to restart. For example, if a framework extension
+	 * This event is fired when the Framework has been stopped because of a
+	 * refresh operation on the system bundle and the framework requires a new
+	 * class loader to be used to restart. For example, if a framework extension
 	 * bundle has been refreshed. The source of this event is the System Bundle.
 	 * 
 	 * @since 1.9
@@ -244,7 +244,6 @@
 	 * <li>{@link #PACKAGES_REFRESHED}</li>
 	 * <li>{@link #STARTLEVEL_CHANGED}</li>
 	 * <li>{@link #STOPPED}</li>
-	 * <li>{@link #STOPPED_BOOTCLASSPATH_MODIFIED}</li>
 	 * <li>{@link #STOPPED_UPDATE}</li>
 	 * <li>{@link #WAIT_TIMEDOUT}</li>
 	 * </ul>
diff --git a/framework/src/main/java/org/osgi/framework/FrameworkUtil.java b/framework/src/main/java/org/osgi/framework/FrameworkUtil.java
index 9f72092..edc8008 100644
--- a/framework/src/main/java/org/osgi/framework/FrameworkUtil.java
+++ b/framework/src/main/java/org/osgi/framework/FrameworkUtil.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2005, 2016). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2005, 2020). All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,25 +16,28 @@
 
 package org.osgi.framework;
 
-import java.lang.reflect.AccessibleObject;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import static java.util.Objects.requireNonNull;
+
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.AbstractMap;
+import java.util.AbstractSet;
 import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ServiceLoader;
 import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.osgi.framework.connect.FrameworkUtilHelper;
+
 /**
  * Framework Utility class.
  * 
@@ -44,7 +47,7 @@
  * 
  * @since 1.3
  * @ThreadSafe
- * @author $Id: 90d50e4d3f69b659bed23beedab6e54b31b96d76 $
+ * @author $Id: 71423feb17277b685e6d5d7864907e768da8cd0b $
  */
 public class FrameworkUtil {
 	/**
@@ -77,7 +80,7 @@
 	 * @see Filter
 	 */
 	public static Filter createFilter(String filter) throws InvalidSyntaxException {
-		return FilterImpl.newInstance(filter);
+		return FilterImpl.createFilter(filter);
 	}
 
 	/**
@@ -194,1523 +197,68 @@
 	}
 
 	/**
-	 * Return a {@code Bundle} for the specified bundle class. The returned
-	 * {@code Bundle} is the bundle associated with the bundle class loader
-	 * which defined the specified class.
+	 * Return a {@code Bundle} for the specified bundle class loader.
 	 * 
-	 * @param classFromBundle A class defined by a bundle class loader.
+	 * @param bundleClassLoader A bundle class loader.
+	 * @return An Optional containing {@code Bundle} for the specified bundle
+	 *         class loader or an empty Optional if the specified class loader
+	 *         is not associated with a specific bundle.
+	 * @since 1.10
+	 */
+	public static Optional<Bundle> getBundle(ClassLoader bundleClassLoader) {
+		requireNonNull(bundleClassLoader);
+		return Optional
+				.ofNullable((bundleClassLoader instanceof BundleReference)
+						? ((BundleReference) bundleClassLoader).getBundle()
+						: null);
+	}
+
+	/**
+	 * Return a {@code Bundle} for the specified bundle class.
+	 * 
+	 * @param classFromBundle A class defined by a bundle.
 	 * @return A {@code Bundle} for the specified bundle class or {@code null}
-	 *         if the specified class was not defined by a bundle class loader.
+	 *         if the specified class was not defined by a bundle.
 	 * @since 1.5
 	 */
-	public static Bundle getBundle(final Class<?> classFromBundle) {
+	public static Bundle getBundle(Class< ? > classFromBundle) {
 		// We use doPriv since the caller may not have permission
 		// to call getClassLoader.
-		Object cl = AccessController.doPrivileged(new PrivilegedAction<Object>() {
-			@Override
-			public Object run() {
-				return classFromBundle.getClassLoader();
-			}
-		});
+		Optional<ClassLoader> cl = Optional
+				.ofNullable(AccessController.doPrivileged(
+						(PrivilegedAction<ClassLoader>) () -> classFromBundle
+								.getClassLoader()));
 
-		if (cl instanceof BundleReference) {
-			return ((BundleReference) cl).getBundle();
-		}
-		return null;
+		return cl.flatMap(FrameworkUtil::getBundle)
+				.orElseGet(() -> helpers.stream()
+						.map(helper -> helper.getBundle(classFromBundle))
+						.filter(Optional::isPresent)
+						.map(Optional::get)
+						.findFirst()
+						.orElse(null));
 	}
 
-	/**
-	 * RFC 1960-based Filter. Filter objects can be created by calling the
-	 * constructor with the desired filter string. A Filter object can be called
-	 * numerous times to determine if the match argument matches the filter
-	 * string that was used to create the Filter object.
-	 * 
-	 * <p>
-	 * The syntax of a filter string is the string representation of LDAP search
-	 * filters as defined in RFC 1960: <i>A String Representation of LDAP Search
-	 * Filters</i> (available at http://www.ietf.org/rfc/rfc1960.txt). It should
-	 * be noted that RFC 2254: <i>A String Representation of LDAP Search
-	 * Filters</i> (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes
-	 * RFC 1960 but only adds extensible matching and is not applicable for this
-	 * API.
-	 * 
-	 * <p>
-	 * The string representation of an LDAP search filter is defined by the
-	 * following grammar. It uses a prefix format.
-	 * 
-	 * <pre>
-	 *   &lt;filter&gt; ::= '(' &lt;filtercomp&gt; ')'
-	 *   &lt;filtercomp&gt; ::= &lt;and&gt; | &lt;or&gt; | &lt;not&gt; | &lt;item&gt;
-	 *   &lt;and&gt; ::= '&amp;' &lt;filterlist&gt;
-	 *   &lt;or&gt; ::= '|' &lt;filterlist&gt;
-	 *   &lt;not&gt; ::= '!' &lt;filter&gt;
-	 *   &lt;filterlist&gt; ::= &lt;filter&gt; | &lt;filter&gt; &lt;filterlist&gt;
-	 *   &lt;item&gt; ::= &lt;simple&gt; | &lt;present&gt; | &lt;substring&gt;
-	 *   &lt;simple&gt; ::= &lt;attr&gt; &lt;filtertype&gt; &lt;value&gt;
-	 *   &lt;filtertype&gt; ::= &lt;equal&gt; | &lt;approx&gt; | &lt;greater&gt; | &lt;less&gt;
-	 *   &lt;equal&gt; ::= '='
-	 *   &lt;approx&gt; ::= '&tilde;='
-	 *   &lt;greater&gt; ::= '&gt;='
-	 *   &lt;less&gt; ::= '&lt;='
-	 *   &lt;present&gt; ::= &lt;attr&gt; '=*'
-	 *   &lt;substring&gt; ::= &lt;attr&gt; '=' &lt;initial&gt; &lt;any&gt; &lt;final&gt;
-	 *   &lt;initial&gt; ::= NULL | &lt;value&gt;
-	 *   &lt;any&gt; ::= '*' &lt;starval&gt;
-	 *   &lt;starval&gt; ::= NULL | &lt;value&gt; '*' &lt;starval&gt;
-	 *   &lt;final&gt; ::= NULL | &lt;value&gt;
-	 * </pre>
-	 * 
-	 * {@code &lt;attr&gt;} is a string representing an attribute, or key, in
-	 * the properties objects of the registered services. Attribute names are
-	 * not case sensitive; that is cn and CN both refer to the same attribute.
-	 * {@code &lt;value&gt;} is a string representing the value, or part of one,
-	 * of a key in the properties objects of the registered services. If a
-	 * {@code &lt;value&gt;} must contain one of the characters ' {@code *}' or
-	 * '{@code (}' or '{@code )}', these characters should be escaped by
-	 * preceding them with the backslash '{@code \}' character. Note that
-	 * although both the {@code &lt;substring&gt;} and {@code &lt;present&gt;}
-	 * productions can produce the {@code 'attr=*'} construct, this construct is
-	 * used only to denote a presence filter.
-	 * 
-	 * <p>
-	 * Examples of LDAP filters are:
-	 * 
-	 * <pre>
-	 *   &quot;(cn=Babs Jensen)&quot;
-	 *   &quot;(!(cn=Tim Howes))&quot;
-	 *   &quot;(&amp;(&quot; + Constants.OBJECTCLASS + &quot;=Person)(|(sn=Jensen)(cn=Babs J*)))&quot;
-	 *   &quot;(o=univ*of*mich*)&quot;
-	 * </pre>
-	 * 
-	 * <p>
-	 * The approximate match ({@code ~=}) is implementation specific but should
-	 * at least ignore case and white space differences. Optional are codes like
-	 * soundex or other smart "closeness" comparisons.
-	 * 
-	 * <p>
-	 * Comparison of values is not straightforward. Strings are compared
-	 * differently than numbers and it is possible for a key to have multiple
-	 * values. Note that that keys in the match argument must always be strings.
-	 * The comparison is defined by the object type of the key's value. The
-	 * following rules apply for comparison:
-	 * 
-	 * <blockquote>
-	 * <TABLE BORDER=0>
-	 * <TR>
-	 * <TD><b>Property Value Type </b></TD>
-	 * <TD><b>Comparison Type</b></TD>
-	 * </TR>
-	 * <TR>
-	 * <TD>String</TD>
-	 * <TD>String comparison</TD>
-	 * </TR>
-	 * <TR valign=top>
-	 * <TD>Integer, Long, Float, Double, Byte, Short, BigInteger, BigDecimal</TD>
-	 * <TD>numerical comparison</TD>
-	 * </TR>
-	 * <TR>
-	 * <TD>Character</TD>
-	 * <TD>character comparison</TD>
-	 * </TR>
-	 * <TR>
-	 * <TD>Boolean</TD>
-	 * <TD>equality comparisons only</TD>
-	 * </TR>
-	 * <TR>
-	 * <TD>[] (array)</TD>
-	 * <TD>recursively applied to values</TD>
-	 * </TR>
-	 * <TR>
-	 * <TD>Collection</TD>
-	 * <TD>recursively applied to values</TD>
-	 * </TR>
-	 * </TABLE>
-	 * Note: arrays of primitives are also supported. </blockquote>
-	 * 
-	 * A filter matches a key that has multiple values if it matches at least
-	 * one of those values. For example,
-	 * 
-	 * <pre>
-	 * Dictionary d = new Hashtable();
-	 * d.put(&quot;cn&quot;, new String[] {&quot;a&quot;, &quot;b&quot;, &quot;c&quot;});
-	 * </pre>
-	 * 
-	 * d will match {@code (cn=a)} and also {@code (cn=b)}
-	 * 
-	 * <p>
-	 * A filter component that references a key having an unrecognizable data
-	 * type will evaluate to {@code false} .
-	 */
-	static private final class FilterImpl implements Filter {
-		/* filter operators */
-		private static final int	EQUAL		= 1;
-		private static final int	APPROX		= 2;
-		private static final int	GREATER		= 3;
-		private static final int	LESS		= 4;
-		private static final int	PRESENT		= 5;
-		private static final int	SUBSTRING	= 6;
-		private static final int	AND			= 7;
-		private static final int	OR			= 8;
-		private static final int	NOT			= 9;
-
-		/** filter operation */
-		private final int			op;
-		/** filter attribute or null if operation AND, OR or NOT */
-		private final String		attr;
-		/** filter operands */
-		private final Object		value;
-
-		/* normalized filter string for Filter object */
-		private transient String	filterString;
-
-		/**
-		 * Constructs a {@link FilterImpl} object. This filter object may be
-		 * used to match a {@link ServiceReference} or a Dictionary.
-		 * 
-		 * <p>
-		 * If the filter cannot be parsed, an {@link InvalidSyntaxException}
-		 * will be thrown with a human readable message where the filter became
-		 * unparsable.
-		 * 
-		 * @param filterString the filter string.
-		 * @throws InvalidSyntaxException If the filter parameter contains an
-		 *         invalid filter string that cannot be parsed.
-		 */
-		static FilterImpl newInstance(String filterString) throws InvalidSyntaxException {
-			return new Parser(filterString).parse();
-		}
-
-		FilterImpl(int operation, String attr, Object value) {
-			this.op = operation;
-			this.attr = attr;
-			this.value = value;
-			filterString = null;
-		}
-
-		/**
-		 * Filter using a service's properties.
-		 * <p>
-		 * This {@code Filter} is executed using the keys and values of the
-		 * referenced service's properties. The keys are looked up in a case
-		 * insensitive manner.
-		 * 
-		 * @param reference The reference to the service whose properties are
-		 *        used in the match.
-		 * @return {@code true} if the service's properties match this
-		 *         {@code Filter}; {@code false} otherwise.
-		 */
-		@Override
-		public boolean match(ServiceReference<?> reference) {
-			return matches(new ServiceReferenceMap(reference));
-		}
-
-		/**
-		 * Filter using a {@code Dictionary} with case insensitive key lookup.
-		 * This {@code Filter} is executed using the specified
-		 * {@code Dictionary}'s keys and values. The keys are looked up in a
-		 * case insensitive manner.
-		 * 
-		 * @param dictionary The {@code Dictionary} whose key/value pairs are
-		 *        used in the match.
-		 * @return {@code true} if the {@code Dictionary}'s values match this
-		 *         filter; {@code false} otherwise.
-		 * @throws IllegalArgumentException If {@code dictionary} contains case
-		 *         variants of the same key name.
-		 */
-		@Override
-		public boolean match(Dictionary<String, ?> dictionary) {
-			return matches(new CaseInsensitiveMap(dictionary));
-		}
-
-		/**
-		 * Filter using a {@code Dictionary}. This {@code Filter} is executed
-		 * using the specified {@code Dictionary}'s keys and values. The keys
-		 * are looked up in a normal manner respecting case.
-		 * 
-		 * @param dictionary The {@code Dictionary} whose key/value pairs are
-		 *        used in the match.
-		 * @return {@code true} if the {@code Dictionary}'s values match this
-		 *         filter; {@code false} otherwise.
-		 * @since 1.3
-		 */
-		@Override
-		public boolean matchCase(Dictionary<String, ?> dictionary) {
-			switch (op) {
-				case AND : {
-					FilterImpl[] filters = (FilterImpl[]) value;
-					for (FilterImpl f : filters) {
-						if (!f.matchCase(dictionary)) {
-							return false;
-						}
-					}
-					return true;
-				}
-
-				case OR : {
-					FilterImpl[] filters = (FilterImpl[]) value;
-					for (FilterImpl f : filters) {
-						if (f.matchCase(dictionary)) {
-							return true;
-						}
-					}
-					return false;
-				}
-
-				case NOT : {
-					FilterImpl filter = (FilterImpl) value;
-					return !filter.matchCase(dictionary);
-				}
-
-				case SUBSTRING :
-				case EQUAL :
-				case GREATER :
-				case LESS :
-				case APPROX : {
-					Object prop = (dictionary == null) ? null : dictionary.get(attr);
-					return compare(op, prop, value);
-				}
-
-				case PRESENT : {
-					Object prop = (dictionary == null) ? null : dictionary.get(attr);
-					return prop != null;
-				}
-			}
-
-			return false;
-		}
-
-		/**
-		 * Filter using a {@code Map}. This {@code Filter} is executed using the
-		 * specified {@code Map}'s keys and values. The keys are looked up in a
-		 * normal manner respecting case.
-		 * 
-		 * @param map The {@code Map} whose key/value pairs are used in the
-		 *        match. Maps with {@code null} key or values are not supported.
-		 *        A {@code null} value is considered not present to the filter.
-		 * @return {@code true} if the {@code Map}'s values match this filter;
-		 *         {@code false} otherwise.
-		 * @since 1.6
-		 */
-		@Override
-		public boolean matches(Map<String, ?> map) {
-			switch (op) {
-				case AND : {
-					FilterImpl[] filters = (FilterImpl[]) value;
-					for (FilterImpl f : filters) {
-						if (!f.matches(map)) {
-							return false;
-						}
-					}
-					return true;
-				}
-
-				case OR : {
-					FilterImpl[] filters = (FilterImpl[]) value;
-					for (FilterImpl f : filters) {
-						if (f.matches(map)) {
-							return true;
-						}
-					}
-					return false;
-				}
-
-				case NOT : {
-					FilterImpl filter = (FilterImpl) value;
-					return !filter.matches(map);
-				}
-
-				case SUBSTRING :
-				case EQUAL :
-				case GREATER :
-				case LESS :
-				case APPROX : {
-					Object prop = (map == null) ? null : map.get(attr);
-					return compare(op, prop, value);
-				}
-
-				case PRESENT : {
-					Object prop = (map == null) ? null : map.get(attr);
-					return prop != null;
-				}
-			}
-
-			return false;
-		}
-
-		/**
-		 * Returns this {@code Filter}'s filter string.
-		 * <p>
-		 * The filter string is normalized by removing whitespace which does not
-		 * affect the meaning of the filter.
-		 * 
-		 * @return This {@code Filter}'s filter string.
-		 */
-		@Override
-		public String toString() {
-			String result = filterString;
-			if (result == null) {
-				filterString = result = normalize().toString();
-			}
-			return result;
-		}
-
-		/**
-		 * Returns this {@code Filter}'s normalized filter string.
-		 * <p>
-		 * The filter string is normalized by removing whitespace which does not
-		 * affect the meaning of the filter.
-		 * 
-		 * @return This {@code Filter}'s filter string.
-		 */
-		private StringBuilder normalize() {
-			StringBuilder sb = new StringBuilder();
-			sb.append('(');
-
-			switch (op) {
-				case AND : {
-					sb.append('&');
-
-					FilterImpl[] filters = (FilterImpl[]) value;
-					for (FilterImpl f : filters) {
-						sb.append(f.normalize());
-					}
-
-					break;
-				}
-
-				case OR : {
-					sb.append('|');
-
-					FilterImpl[] filters = (FilterImpl[]) value;
-					for (FilterImpl f : filters) {
-						sb.append(f.normalize());
-					}
-
-					break;
-				}
-
-				case NOT : {
-					sb.append('!');
-					FilterImpl filter = (FilterImpl) value;
-					sb.append(filter.normalize());
-
-					break;
-				}
-
-				case SUBSTRING : {
-					sb.append(attr);
-					sb.append('=');
-
-					String[] substrings = (String[]) value;
-
-					for (String substr : substrings) {
-						if (substr == null) /* * */{
-							sb.append('*');
-						} else /* xxx */{
-							sb.append(encodeValue(substr));
-						}
-					}
-
-					break;
-				}
-				case EQUAL : {
-					sb.append(attr);
-					sb.append('=');
-					sb.append(encodeValue((String) value));
-
-					break;
-				}
-				case GREATER : {
-					sb.append(attr);
-					sb.append(">=");
-					sb.append(encodeValue((String) value));
-
-					break;
-				}
-				case LESS : {
-					sb.append(attr);
-					sb.append("<=");
-					sb.append(encodeValue((String) value));
-
-					break;
-				}
-				case APPROX : {
-					sb.append(attr);
-					sb.append("~=");
-					sb.append(encodeValue(approxString((String) value)));
-
-					break;
-				}
-
-				case PRESENT : {
-					sb.append(attr);
-					sb.append("=*");
-
-					break;
-				}
-			}
-
-			sb.append(')');
-
-			return sb;
-		}
-
-		/**
-		 * Compares this {@code Filter} to another {@code Filter}.
-		 * 
-		 * <p>
-		 * This implementation returns the result of calling
-		 * {@code this.toString().equals(obj.toString()}.
-		 * 
-		 * @param obj The object to compare against this {@code Filter}.
-		 * @return If the other object is a {@code Filter} object, then returns
-		 *         the result of calling
-		 *         {@code this.toString().equals(obj.toString()}; {@code false}
-		 *         otherwise.
-		 */
-		@Override
-		public boolean equals(Object obj) {
-			if (obj == this) {
-				return true;
-			}
-
-			if (!(obj instanceof Filter)) {
-				return false;
-			}
-
-			return this.toString().equals(obj.toString());
-		}
-
-		/**
-		 * Returns the hashCode for this {@code Filter}.
-		 * 
-		 * <p>
-		 * This implementation returns the result of calling
-		 * {@code this.toString().hashCode()}.
-		 * 
-		 * @return The hashCode of this {@code Filter}.
-		 */
-		@Override
-		public int hashCode() {
-			return this.toString().hashCode();
-		}
-
-		/**
-		 * Encode the value string such that '(', '*', ')' and '\' are escaped.
-		 * 
-		 * @param value unencoded value string.
-		 * @return encoded value string.
-		 */
-		private static String encodeValue(String value) {
-			boolean encoded = false;
-			int inlen = value.length();
-			int outlen = inlen << 1; /* inlen 2 */
-
-			char[] output = new char[outlen];
-			value.getChars(0, inlen, output, inlen);
-
-			int cursor = 0;
-			for (int i = inlen; i < outlen; i++) {
-				char c = output[i];
-
-				switch (c) {
-					case '(' :
-					case '*' :
-					case ')' :
-					case '\\' : {
-						output[cursor] = '\\';
-						cursor++;
-						encoded = true;
-
-						break;
-					}
-				}
-
-				output[cursor] = c;
-				cursor++;
-			}
-
-			return encoded ? new String(output, 0, cursor) : value;
-		}
-
-		private boolean compare(int operation, Object value1, Object value2) {
-			if (value1 == null) {
-				return false;
-			}
-			if (value1 instanceof String) {
-				return compare_String(operation, (String) value1, value2);
-			}
-			if (value1 instanceof Version) {
-				return compare_Version(operation, (Version) value1, value2);
-			}
-
-			Class<?> clazz = value1.getClass();
-			if (clazz.isArray()) {
-				Class<?> type = clazz.getComponentType();
-				if (type.isPrimitive()) {
-					return compare_PrimitiveArray(operation, type, value1, value2);
-				}
-				return compare_ObjectArray(operation, (Object[]) value1, value2);
-			}
-			if (value1 instanceof Collection<?>) {
-				return compare_Collection(operation, (Collection<?>) value1, value2);
-			}
-			if (value1 instanceof Integer) {
-				return compare_Integer(operation, ((Integer) value1).intValue(), value2);
-			}
-			if (value1 instanceof Long) {
-				return compare_Long(operation, ((Long) value1).longValue(), value2);
-			}
-			if (value1 instanceof Byte) {
-				return compare_Byte(operation, ((Byte) value1).byteValue(), value2);
-			}
-			if (value1 instanceof Short) {
-				return compare_Short(operation, ((Short) value1).shortValue(), value2);
-			}
-			if (value1 instanceof Character) {
-				return compare_Character(operation, ((Character) value1).charValue(), value2);
-			}
-			if (value1 instanceof Float) {
-				return compare_Float(operation, ((Float) value1).floatValue(), value2);
-			}
-			if (value1 instanceof Double) {
-				return compare_Double(operation, ((Double) value1).doubleValue(), value2);
-			}
-			if (value1 instanceof Boolean) {
-				return compare_Boolean(operation, ((Boolean) value1).booleanValue(), value2);
-			}
-			if (value1 instanceof Comparable<?>) {
-				@SuppressWarnings("unchecked")
-				Comparable<Object> comparable = (Comparable<Object>) value1;
-				return compare_Comparable(operation, comparable, value2);
-			}
-			return compare_Unknown(operation, value1, value2);
-		}
-
-		private boolean compare_Collection(int operation, Collection<?> collection, Object value2) {
-			for (Object value1 : collection) {
-				if (compare(operation, value1, value2)) {
-					return true;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_ObjectArray(int operation, Object[] array, Object value2) {
-			for (Object value1 : array) {
-				if (compare(operation, value1, value2)) {
-					return true;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_PrimitiveArray(int operation, Class<?> type, Object primarray, Object value2) {
-			if (Integer.TYPE.isAssignableFrom(type)) {
-				int[] array = (int[]) primarray;
-				for (int value1 : array) {
-					if (compare_Integer(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Long.TYPE.isAssignableFrom(type)) {
-				long[] array = (long[]) primarray;
-				for (long value1 : array) {
-					if (compare_Long(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Byte.TYPE.isAssignableFrom(type)) {
-				byte[] array = (byte[]) primarray;
-				for (byte value1 : array) {
-					if (compare_Byte(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Short.TYPE.isAssignableFrom(type)) {
-				short[] array = (short[]) primarray;
-				for (short value1 : array) {
-					if (compare_Short(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Character.TYPE.isAssignableFrom(type)) {
-				char[] array = (char[]) primarray;
-				for (char value1 : array) {
-					if (compare_Character(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Float.TYPE.isAssignableFrom(type)) {
-				float[] array = (float[]) primarray;
-				for (float value1 : array) {
-					if (compare_Float(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Double.TYPE.isAssignableFrom(type)) {
-				double[] array = (double[]) primarray;
-				for (double value1 : array) {
-					if (compare_Double(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (Boolean.TYPE.isAssignableFrom(type)) {
-				boolean[] array = (boolean[]) primarray;
-				for (boolean value1 : array) {
-					if (compare_Boolean(operation, value1, value2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			return false;
-		}
-
-		private boolean compare_String(int operation, String string, Object value2) {
-			switch (operation) {
-				case SUBSTRING : {
-					String[] substrings = (String[]) value2;
-					int pos = 0;
-					for (int i = 0, size = substrings.length; i < size; i++) {
-						String substr = substrings[i];
-
-						if (i + 1 < size) /* if this is not that last substr */{
-							if (substr == null) /* * */{
-								String substr2 = substrings[i + 1];
-
-								if (substr2 == null) /* ** */
-									continue; /* ignore first star */
-								/* xxx */
-								int index = string.indexOf(substr2, pos);
-								if (index == -1) {
-									return false;
-								}
-
-								pos = index + substr2.length();
-								if (i + 2 < size) // if there are more
-									// substrings, increment
-									// over the string we just
-									// matched; otherwise need
-									// to do the last substr
-									// check
-									i++;
-							} else /* xxx */{
-								int len = substr.length();
-								if (string.regionMatches(pos, substr, 0, len)) {
-									pos += len;
-								} else {
-									return false;
-								}
-							}
-						} else /* last substr */{
-							if (substr == null) /* * */{
-								return true;
-							}
-							/* xxx */
-							return string.endsWith(substr);
-						}
-					}
-
-					return true;
-				}
-				case EQUAL : {
-					return string.equals(value2);
-				}
-				case APPROX : {
-					string = approxString(string);
-					String string2 = approxString((String) value2);
-
-					return string.equalsIgnoreCase(string2);
-				}
-				case GREATER : {
-					return string.compareTo((String) value2) >= 0;
-				}
-				case LESS : {
-					return string.compareTo((String) value2) <= 0;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Integer(int operation, int intval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			int intval2;
+	private final static List<FrameworkUtilHelper> helpers;
+	static {
+		List<FrameworkUtilHelper> l = new ArrayList<>();
+		try {
+			ServiceLoader<FrameworkUtilHelper> helperLoader = AccessController
+					.doPrivileged(
+							(PrivilegedAction<ServiceLoader<FrameworkUtilHelper>>) () -> ServiceLoader
+									.load(FrameworkUtilHelper.class,
+											FrameworkUtilHelper.class
+													.getClassLoader()));
+			helperLoader.forEach(l::add);
+		} catch (Throwable error) {
+			// try hard not to fail static <clinit>
 			try {
-				intval2 = Integer.parseInt(((String) value2).trim());
-			} catch (IllegalArgumentException e) {
-				return false;
-			}
-			switch (operation) {
-				case APPROX :
-				case EQUAL : {
-					return intval == intval2;
-				}
-				case GREATER : {
-					return intval >= intval2;
-				}
-				case LESS : {
-					return intval <= intval2;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Long(int operation, long longval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			long longval2;
-			try {
-				longval2 = Long.parseLong(((String) value2).trim());
-			} catch (IllegalArgumentException e) {
-				return false;
-			}
-
-			switch (operation) {
-				case APPROX :
-				case EQUAL : {
-					return longval == longval2;
-				}
-				case GREATER : {
-					return longval >= longval2;
-				}
-				case LESS : {
-					return longval <= longval2;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Byte(int operation, byte byteval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			byte byteval2;
-			try {
-				byteval2 = Byte.parseByte(((String) value2).trim());
-			} catch (IllegalArgumentException e) {
-				return false;
-			}
-
-			switch (operation) {
-				case APPROX :
-				case EQUAL : {
-					return byteval == byteval2;
-				}
-				case GREATER : {
-					return byteval >= byteval2;
-				}
-				case LESS : {
-					return byteval <= byteval2;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Short(int operation, short shortval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			short shortval2;
-			try {
-				shortval2 = Short.parseShort(((String) value2).trim());
-			} catch (IllegalArgumentException e) {
-				return false;
-			}
-
-			switch (operation) {
-				case APPROX :
-				case EQUAL : {
-					return shortval == shortval2;
-				}
-				case GREATER : {
-					return shortval >= shortval2;
-				}
-				case LESS : {
-					return shortval <= shortval2;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Character(int operation, char charval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			char charval2;
-			try {
-				charval2 = ((String) value2).charAt(0);
-			} catch (IndexOutOfBoundsException e) {
-				return false;
-			}
-
-			switch (operation) {
-				case EQUAL : {
-					return charval == charval2;
-				}
-				case APPROX : {
-					return (charval == charval2) || (Character.toUpperCase(charval) == Character.toUpperCase(charval2)) || (Character.toLowerCase(charval) == Character.toLowerCase(charval2));
-				}
-				case GREATER : {
-					return charval >= charval2;
-				}
-				case LESS : {
-					return charval <= charval2;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Boolean(int operation, boolean boolval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			boolean boolval2 = Boolean.valueOf(((String) value2).trim()).booleanValue();
-			switch (operation) {
-				case APPROX :
-				case EQUAL :
-				case GREATER :
-				case LESS : {
-					return boolval == boolval2;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Float(int operation, float floatval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			float floatval2;
-			try {
-				floatval2 = Float.parseFloat(((String) value2).trim());
-			} catch (IllegalArgumentException e) {
-				return false;
-			}
-
-			switch (operation) {
-				case APPROX :
-				case EQUAL : {
-					return Float.compare(floatval, floatval2) == 0;
-				}
-				case GREATER : {
-					return Float.compare(floatval, floatval2) >= 0;
-				}
-				case LESS : {
-					return Float.compare(floatval, floatval2) <= 0;
-				}
-			}
-			return false;
-		}
-
-		private boolean compare_Double(int operation, double doubleval, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			double doubleval2;
-			try {
-				doubleval2 = Double.parseDouble(((String) value2).trim());
-			} catch (IllegalArgumentException e) {
-				return false;
-			}
-
-			switch (operation) {
-				case APPROX :
-				case EQUAL : {
-					return Double.compare(doubleval, doubleval2) == 0;
-				}
-				case GREATER : {
-					return Double.compare(doubleval, doubleval2) >= 0;
-				}
-				case LESS : {
-					return Double.compare(doubleval, doubleval2) <= 0;
-				}
-			}
-			return false;
-		}
-
-		private static Object valueOf(Class<?> target, String value2) {
-			do {
-				Method method;
-				try {
-					method = target.getMethod("valueOf", String.class);
-				} catch (NoSuchMethodException e) {
-					break;
-				}
-				if (Modifier.isStatic(method.getModifiers()) && target.isAssignableFrom(method.getReturnType())) {
-					setAccessible(method);
-					try {
-						return method.invoke(null, value2.trim());
-					} catch (IllegalAccessException e) {
-						return null;
-					} catch (InvocationTargetException e) {
-						return null;
-					}
-				}
-			} while (false);
-
-			do {
-				Constructor<?> constructor;
-				try {
-					constructor = target.getConstructor(String.class);
-				} catch (NoSuchMethodException e) {
-					break;
-				}
-				setAccessible(constructor);
-				try {
-					return constructor.newInstance(value2.trim());
-				} catch (IllegalAccessException e) {
-					return null;
-				} catch (InvocationTargetException e) {
-					return null;
-				} catch (InstantiationException e) {
-					return null;
-				}
-			} while (false);
-
-			return null;
-		}
-
-		private static void setAccessible(AccessibleObject accessible) {
-			if (!accessible.isAccessible()) {
-				AccessController.doPrivileged(new SetAccessibleAction(accessible));
+				Thread t = Thread.currentThread();
+				t.getUncaughtExceptionHandler().uncaughtException(t, error);
+			} catch (Throwable ignored) {
+				// we ignore this
 			}
 		}
-
-		private boolean compare_Comparable(int operation, Comparable<Object> value1, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			value2 = valueOf(value1.getClass(), (String) value2);
-			if (value2 == null) {
-				return false;
-			}
-			try {
-				switch (operation) {
-					case APPROX :
-					case EQUAL : {
-						return value1.compareTo(value2) == 0;
-					}
-					case GREATER : {
-						return value1.compareTo(value2) >= 0;
-					}
-					case LESS : {
-						return value1.compareTo(value2) <= 0;
-					}
-				}
-			} catch (Exception e) {
-				// if the compareTo method throws an exception; return false
-				return false;
-			}
-			return false;
-		}
-
-		private boolean compare_Version(int operation, Version value1, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			try {
-				Version version2 = Version.valueOf((String) value2);
-				switch (operation) {
-					case APPROX :
-					case EQUAL : {
-						return value1.compareTo(version2) == 0;
-					}
-					case GREATER : {
-						return value1.compareTo(version2) >= 0;
-					}
-					case LESS : {
-						return value1.compareTo(version2) <= 0;
-					}
-				}
-			} catch (Exception e) {
-				// if the valueOf or compareTo method throws an exception
-				return false;
-			}
-			return false;
-		}
-
-		private boolean compare_Unknown(int operation, Object value1, Object value2) {
-			if (operation == SUBSTRING) {
-				return false;
-			}
-			value2 = valueOf(value1.getClass(), (String) value2);
-			if (value2 == null) {
-				return false;
-			}
-			try {
-				switch (operation) {
-					case APPROX :
-					case EQUAL :
-					case GREATER :
-					case LESS : {
-						return value1.equals(value2);
-					}
-				}
-			} catch (Exception e) {
-				// if the equals method throws an exception; return false
-				return false;
-			}
-			return false;
-		}
-
-		/**
-		 * Map a string for an APPROX (~=) comparison.
-		 * 
-		 * This implementation removes white spaces. This is the minimum
-		 * implementation allowed by the OSGi spec.
-		 * 
-		 * @param input Input string.
-		 * @return String ready for APPROX comparison.
-		 */
-		private static String approxString(String input) {
-			boolean changed = false;
-			char[] output = input.toCharArray();
-			int cursor = 0;
-			for (char c : output) {
-				if (Character.isWhitespace(c)) {
-					changed = true;
-					continue;
-				}
-
-				output[cursor] = c;
-				cursor++;
-			}
-
-			return changed ? new String(output, 0, cursor) : input;
-		}
-
-		/**
-		 * Parser class for OSGi filter strings. This class parses the complete
-		 * filter string and builds a tree of Filter objects rooted at the
-		 * parent.
-		 */
-		static private final class Parser {
-			private final String	filterstring;
-			private final char[]	filterChars;
-			private int				pos;
-
-			Parser(String filterstring) {
-				this.filterstring = filterstring;
-				filterChars = filterstring.toCharArray();
-				pos = 0;
-			}
-
-			FilterImpl parse() throws InvalidSyntaxException {
-				FilterImpl filter;
-				try {
-					filter = parse_filter();
-				} catch (ArrayIndexOutOfBoundsException e) {
-					throw new InvalidSyntaxException("Filter ended abruptly", filterstring, e);
-				}
-
-				if (pos != filterChars.length) {
-					throw new InvalidSyntaxException("Extraneous trailing characters: " + filterstring.substring(pos), filterstring);
-				}
-				return filter;
-			}
-
-			private FilterImpl parse_filter() throws InvalidSyntaxException {
-				FilterImpl filter;
-				skipWhiteSpace();
-
-				if (filterChars[pos] != '(') {
-					throw new InvalidSyntaxException("Missing '(': " + filterstring.substring(pos), filterstring);
-				}
-
-				pos++;
-
-				filter = parse_filtercomp();
-
-				skipWhiteSpace();
-
-				if (filterChars[pos] != ')') {
-					throw new InvalidSyntaxException("Missing ')': " + filterstring.substring(pos), filterstring);
-				}
-
-				pos++;
-
-				skipWhiteSpace();
-
-				return filter;
-			}
-
-			private FilterImpl parse_filtercomp() throws InvalidSyntaxException {
-				skipWhiteSpace();
-
-				char c = filterChars[pos];
-
-				switch (c) {
-					case '&' : {
-						pos++;
-						return parse_and();
-					}
-					case '|' : {
-						pos++;
-						return parse_or();
-					}
-					case '!' : {
-						pos++;
-						return parse_not();
-					}
-				}
-				return parse_item();
-			}
-
-			private FilterImpl parse_and() throws InvalidSyntaxException {
-				int lookahead = pos;
-				skipWhiteSpace();
-
-				if (filterChars[pos] != '(') {
-					pos = lookahead - 1;
-					return parse_item();
-				}
-
-				List<FilterImpl> operands = new ArrayList<FilterImpl>(10);
-
-				while (filterChars[pos] == '(') {
-					FilterImpl child = parse_filter();
-					operands.add(child);
-				}
-
-				return new FilterImpl(FilterImpl.AND, null,
-						operands.toArray(new FilterImpl[0]));
-			}
-
-			private FilterImpl parse_or() throws InvalidSyntaxException {
-				int lookahead = pos;
-				skipWhiteSpace();
-
-				if (filterChars[pos] != '(') {
-					pos = lookahead - 1;
-					return parse_item();
-				}
-
-				List<FilterImpl> operands = new ArrayList<FilterImpl>(10);
-
-				while (filterChars[pos] == '(') {
-					FilterImpl child = parse_filter();
-					operands.add(child);
-				}
-
-				return new FilterImpl(FilterImpl.OR, null,
-						operands.toArray(new FilterImpl[0]));
-			}
-
-			private FilterImpl parse_not() throws InvalidSyntaxException {
-				int lookahead = pos;
-				skipWhiteSpace();
-
-				if (filterChars[pos] != '(') {
-					pos = lookahead - 1;
-					return parse_item();
-				}
-
-				FilterImpl child = parse_filter();
-
-				return new FilterImpl(FilterImpl.NOT, null, child);
-			}
-
-			private FilterImpl parse_item() throws InvalidSyntaxException {
-				String attr = parse_attr();
-
-				skipWhiteSpace();
-
-				switch (filterChars[pos]) {
-					case '~' : {
-						if (filterChars[pos + 1] == '=') {
-							pos += 2;
-							return new FilterImpl(FilterImpl.APPROX, attr, parse_value());
-						}
-						break;
-					}
-					case '>' : {
-						if (filterChars[pos + 1] == '=') {
-							pos += 2;
-							return new FilterImpl(FilterImpl.GREATER, attr, parse_value());
-						}
-						break;
-					}
-					case '<' : {
-						if (filterChars[pos + 1] == '=') {
-							pos += 2;
-							return new FilterImpl(FilterImpl.LESS, attr, parse_value());
-						}
-						break;
-					}
-					case '=' : {
-						if (filterChars[pos + 1] == '*') {
-							int oldpos = pos;
-							pos += 2;
-							skipWhiteSpace();
-							if (filterChars[pos] == ')') {
-								return new FilterImpl(FilterImpl.PRESENT, attr, null);
-							}
-							pos = oldpos;
-						}
-
-						pos++;
-						Object string = parse_substring();
-
-						if (string instanceof String) {
-							return new FilterImpl(FilterImpl.EQUAL, attr, string);
-						}
-						return new FilterImpl(FilterImpl.SUBSTRING, attr, string);
-					}
-				}
-
-				throw new InvalidSyntaxException("Invalid operator: " + filterstring.substring(pos), filterstring);
-			}
-
-			private String parse_attr() throws InvalidSyntaxException {
-				skipWhiteSpace();
-
-				int begin = pos;
-				int end = pos;
-
-				char c = filterChars[pos];
-
-				while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') {
-					pos++;
-
-					if (!Character.isWhitespace(c)) {
-						end = pos;
-					}
-
-					c = filterChars[pos];
-				}
-
-				int length = end - begin;
-
-				if (length == 0) {
-					throw new InvalidSyntaxException("Missing attr: " + filterstring.substring(pos), filterstring);
-				}
-
-				return new String(filterChars, begin, length);
-			}
-
-			private String parse_value() throws InvalidSyntaxException {
-				StringBuilder sb = new StringBuilder(filterChars.length - pos);
-
-				parseloop: while (true) {
-					char c = filterChars[pos];
-
-					switch (c) {
-						case ')' : {
-							break parseloop;
-						}
-
-						case '(' : {
-							throw new InvalidSyntaxException("Invalid value: " + filterstring.substring(pos), filterstring);
-						}
-
-						case '\\' : {
-							pos++;
-							c = filterChars[pos];
-							/* fall through into default */
-						}
-
-						default : {
-							sb.append(c);
-							pos++;
-							break;
-						}
-					}
-				}
-
-				if (sb.length() == 0) {
-					throw new InvalidSyntaxException("Missing value: " + filterstring.substring(pos), filterstring);
-				}
-
-				return sb.toString();
-			}
-
-			private Object parse_substring() throws InvalidSyntaxException {
-				StringBuilder sb = new StringBuilder(filterChars.length - pos);
-
-				List<String> operands = new ArrayList<String>(10);
-
-				parseloop: while (true) {
-					char c = filterChars[pos];
-
-					switch (c) {
-						case ')' : {
-							if (sb.length() > 0) {
-								operands.add(sb.toString());
-							}
-
-							break parseloop;
-						}
-
-						case '(' : {
-							throw new InvalidSyntaxException("Invalid value: " + filterstring.substring(pos), filterstring);
-						}
-
-						case '*' : {
-							if (sb.length() > 0) {
-								operands.add(sb.toString());
-							}
-
-							sb.setLength(0);
-
-							operands.add(null);
-							pos++;
-
-							break;
-						}
-
-						case '\\' : {
-							pos++;
-							c = filterChars[pos];
-							/* fall through into default */
-						}
-
-						default : {
-							sb.append(c);
-							pos++;
-							break;
-						}
-					}
-				}
-
-				int size = operands.size();
-
-				if (size == 0) {
-					return "";
-				}
-
-				if (size == 1) {
-					Object single = operands.get(0);
-
-					if (single != null) {
-						return single;
-					}
-				}
-
-				return operands.toArray(new String[0]);
-			}
-
-			private void skipWhiteSpace() {
-				for (int length = filterChars.length; (pos < length) && Character.isWhitespace(filterChars[pos]);) {
-					pos++;
-				}
-			}
-		}
-	}
-
-	/**
-	 * This Map is used for case-insensitive key lookup during filter
-	 * evaluation. This Map implementation only supports the get operation using
-	 * a String key as no other operations are used by the Filter
-	 * implementation.
-	 */
-	static private final class CaseInsensitiveMap extends AbstractMap<String, Object> implements Map<String, Object> {
-		private final Dictionary<String, ?>	dictionary;
-		private final String[]				keys;
-
-		/**
-		 * Create a case insensitive map from the specified dictionary.
-		 * 
-		 * @param dictionary
-		 * @throws IllegalArgumentException If {@code dictionary} contains case
-		 *         variants of the same key name.
-		 */
-		CaseInsensitiveMap(Dictionary<String, ?> dictionary) {
-			if (dictionary == null) {
-				this.dictionary = null;
-				this.keys = new String[0];
-				return;
-			}
-			this.dictionary = dictionary;
-			List<String> keyList = new ArrayList<String>(dictionary.size());
-			for (Enumeration<?> e = dictionary.keys(); e.hasMoreElements();) {
-				Object k = e.nextElement();
-				if (k instanceof String) {
-					String key = (String) k;
-					for (String i : keyList) {
-						if (key.equalsIgnoreCase(i)) {
-							throw new IllegalArgumentException();
-						}
-					}
-					keyList.add(key);
-				}
-			}
-			this.keys = keyList.toArray(new String[0]);
-		}
-
-		@Override
-		public Object get(Object o) {
-			String k = (String) o;
-			for (String key : keys) {
-				if (key.equalsIgnoreCase(k)) {
-					return dictionary.get(key);
-				}
-			}
-			return null;
-		}
-
-		@Override
-		public Set<java.util.Map.Entry<String, Object>> entrySet() {
-			throw new UnsupportedOperationException();
-		}
-	}
-
-	/**
-	 * This Map is used for key lookup from a ServiceReference during filter
-	 * evaluation. This Map implementation only supports the get operation using
-	 * a String key as no other operations are used by the Filter
-	 * implementation.
-	 */
-	static private final class ServiceReferenceMap extends AbstractMap<String, Object> implements Map<String, Object> {
-		private final ServiceReference<?>	reference;
-
-		ServiceReferenceMap(ServiceReference<?> reference) {
-			this.reference = reference;
-		}
-
-		@Override
-		public Object get(Object key) {
-			if (reference == null) {
-				return null;
-			}
-			return reference.getProperty((String) key);
-		}
-
-		@Override
-		public Set<java.util.Map.Entry<String, Object>> entrySet() {
-			throw new UnsupportedOperationException();
-		}
-	}
-
-	static private final class SetAccessibleAction implements PrivilegedAction<Void> {
-		private final AccessibleObject	accessible;
-
-		SetAccessibleAction(AccessibleObject accessible) {
-			this.accessible = accessible;
-		}
-
-		@Override
-		public Void run() {
-			accessible.setAccessible(true);
-			return null;
-		}
+		helpers = Collections.unmodifiableList(l);
 	}
 
 	/**
@@ -2155,4 +703,357 @@
 			return sb.toString();
 		}
 	}
+
+	/**
+	 * Return a Map wrapper around a Dictionary.
+	 *
+	 * @param <K> The type of the key.
+	 * @param <V> The type of the value.
+	 * @param dictionary The dictionary to wrap.
+	 * @return A Map object which wraps the specified dictionary. If the
+	 *         specified dictionary can be cast to a Map, then the specified
+	 *         dictionary is returned.
+	 * @since 1.10
+	 */
+	public static <K, V> Map<K,V> asMap(
+			Dictionary< ? extends K, ? extends V> dictionary) {
+		if (dictionary instanceof Map) {
+			@SuppressWarnings("unchecked")
+			Map<K,V> coerced = (Map<K,V>) dictionary;
+			return coerced;
+		}
+		return new DictionaryAsMap<>(dictionary);
+	}
+
+	private static class DictionaryAsMap<K, V> extends AbstractMap<K,V> {
+		private final Dictionary<K,V> dict;
+
+		@SuppressWarnings("unchecked")
+		DictionaryAsMap(Dictionary< ? extends K, ? extends V> dict) {
+			this.dict = (Dictionary<K,V>) requireNonNull(dict);
+		}
+
+		Iterator<K> keys() {
+			List<K> keys = new ArrayList<>(dict.size());
+			for (Enumeration<K> e = dict.keys(); e.hasMoreElements();) {
+				keys.add(e.nextElement());
+			}
+			return keys.iterator();
+		}
+
+		@Override
+		public int size() {
+			return dict.size();
+		}
+
+		@Override
+		public boolean isEmpty() {
+			return dict.isEmpty();
+		}
+
+		@Override
+		public boolean containsKey(Object key) {
+			if (key == null) {
+				return false;
+			}
+			return dict.get(key) != null;
+		}
+
+		@Override
+		public V get(Object key) {
+			if (key == null) {
+				return null;
+			}
+			return dict.get(key);
+		}
+
+		@Override
+		public V put(K key, V value) {
+			return dict.put(
+					requireNonNull(key,
+							"a Dictionary cannot contain a null key"),
+					requireNonNull(value,
+							"a Dictionary cannot contain a null value"));
+		}
+
+		@Override
+		public V remove(Object key) {
+			if (key == null) {
+				return null;
+			}
+			return dict.remove(key);
+		}
+
+		@Override
+		public void clear() {
+			for (Iterator<K> iter = keys(); iter.hasNext();) {
+				dict.remove(iter.next());
+			}
+		}
+
+		@Override
+		public Set<K> keySet() {
+			return new KeySet();
+		}
+
+		@Override
+		public Set<Map.Entry<K,V>> entrySet() {
+			return new EntrySet();
+		}
+
+		@Override
+		public String toString() {
+			return dict.toString();
+		}
+
+		final class KeySet extends AbstractSet<K> {
+			@Override
+			public Iterator<K> iterator() {
+				return new KeyIterator();
+			}
+
+			@Override
+			public int size() {
+				return DictionaryAsMap.this.size();
+			}
+
+			@Override
+			public boolean isEmpty() {
+				return DictionaryAsMap.this.isEmpty();
+			}
+
+			@Override
+			public boolean contains(Object key) {
+				return DictionaryAsMap.this.containsKey(key);
+			}
+
+			@Override
+			public boolean remove(Object key) {
+				return DictionaryAsMap.this.remove(key) != null;
+			}
+
+			@Override
+			public void clear() {
+				DictionaryAsMap.this.clear();
+			}
+		}
+
+		final class KeyIterator implements Iterator<K> {
+			private final Iterator<K>	keys	= DictionaryAsMap.this.keys();
+			private K					key		= null;
+
+			@Override
+			public boolean hasNext() {
+				return keys.hasNext();
+			}
+
+			@Override
+			public K next() {
+				return key = keys.next();
+			}
+
+			@Override
+			public void remove() {
+				if (key == null) {
+					throw new IllegalStateException();
+				}
+				DictionaryAsMap.this.remove(key);
+				key = null;
+			}
+		}
+
+		final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
+			@Override
+			public Iterator<Map.Entry<K,V>> iterator() {
+				return new EntryIterator();
+			}
+
+			@Override
+			public int size() {
+				return DictionaryAsMap.this.size();
+			}
+
+			@Override
+			public boolean isEmpty() {
+				return DictionaryAsMap.this.isEmpty();
+			}
+
+			@Override
+			public boolean contains(Object o) {
+				if (o instanceof Map.Entry) {
+					Map.Entry< ? , ? > e = (Map.Entry< ? , ? >) o;
+					return containsEntry(e);
+				}
+				return false;
+			}
+
+			private boolean containsEntry(Map.Entry< ? , ? > e) {
+				Object key = e.getKey();
+				if (key == null) {
+					return false;
+				}
+				Object value = e.getValue();
+				if (value == null) {
+					return false;
+				}
+				return Objects.equals(DictionaryAsMap.this.get(key), value);
+			}
+
+			@Override
+			public boolean remove(Object o) {
+				if (o instanceof Map.Entry) {
+					Map.Entry< ? , ? > e = (Map.Entry< ? , ? >) o;
+					if (containsEntry(e)) {
+						DictionaryAsMap.this.remove(e.getKey());
+						return true;
+					}
+				}
+				return false;
+			}
+
+			@Override
+			public void clear() {
+				DictionaryAsMap.this.clear();
+			}
+		}
+
+		final class EntryIterator implements Iterator<Map.Entry<K,V>> {
+			private final Iterator<K>	keys	= DictionaryAsMap.this.keys();
+			private K					key		= null;
+
+			@Override
+			public boolean hasNext() {
+				return keys.hasNext();
+			}
+
+			@Override
+			public Map.Entry<K,V> next() {
+				return new Entry(key = keys.next());
+			}
+
+			@Override
+			public void remove() {
+				if (key == null) {
+					throw new IllegalStateException();
+				}
+				DictionaryAsMap.this.remove(key);
+				key = null;
+			}
+		}
+
+		final class Entry extends SimpleEntry<K,V> {
+			private static final long serialVersionUID = 1L;
+
+			Entry(K key) {
+				super(key, DictionaryAsMap.this.get(key));
+			}
+
+			@Override
+			public V setValue(V value) {
+				DictionaryAsMap.this.put(getKey(), value);
+				return super.setValue(value);
+			}
+		}
+	}
+
+	/**
+	 * Return a Dictionary wrapper around a Map.
+	 *
+	 * @param <K> The type of the key.
+	 * @param <V> The type of the value.
+	 * @param map The map to wrap.
+	 * @return A Dictionary object which wraps the specified map. If the
+	 *         specified map can be cast to a Dictionary, then the specified map
+	 *         is returned.
+	 * @since 1.10
+	 */
+	public static <K, V> Dictionary<K,V> asDictionary(
+			Map< ? extends K, ? extends V> map) {
+		if (map instanceof Dictionary) {
+			@SuppressWarnings("unchecked")
+			Dictionary<K,V> coerced = (Dictionary<K,V>) map;
+			return coerced;
+		}
+		return new MapAsDictionary<>(map);
+	}
+
+	private static class MapAsDictionary<K, V> extends Dictionary<K,V> {
+		private final Map<K,V> map;
+
+		@SuppressWarnings("unchecked")
+		MapAsDictionary(Map< ? extends K, ? extends V> map) {
+			this.map = (Map<K,V>) requireNonNull(map);
+			boolean nullKey;
+			try {
+				nullKey = map.containsKey(null);
+			} catch (NullPointerException e) {
+				nullKey = false; // map does not allow null key
+			}
+			if (nullKey) {
+				throw new NullPointerException(
+						"a Dictionary cannot contain a null key");
+			}
+			boolean nullValue;
+			try {
+				nullValue = map.containsValue(null);
+			} catch (NullPointerException e) {
+				nullValue = false; // map does not allow null value
+			}
+			if (nullValue) {
+				throw new NullPointerException(
+						"a Dictionary cannot contain a null value");
+			}
+		}
+
+		@Override
+		public int size() {
+			return map.size();
+		}
+
+		@Override
+		public boolean isEmpty() {
+			return map.isEmpty();
+		}
+
+		@Override
+		public Enumeration<K> keys() {
+			return Collections.enumeration(map.keySet());
+		}
+
+		@Override
+		public Enumeration<V> elements() {
+			return Collections.enumeration(map.values());
+		}
+
+		@Override
+		public V get(Object key) {
+			if (key == null) {
+				return null;
+			}
+			return map.get(key);
+		}
+
+		@Override
+		public V put(K key, V value) {
+			return map.put(
+					requireNonNull(key,
+							"a Dictionary cannot contain a null key"),
+					requireNonNull(value,
+							"a Dictionary cannot contain a null value"));
+		}
+
+		@Override
+		public V remove(Object key) {
+			if (key == null) {
+				return null;
+			}
+			return map.remove(key);
+		}
+
+		@Override
+		public String toString() {
+			return map.toString();
+		}
+	}
+
 }
diff --git a/framework/src/main/java/org/osgi/framework/PackagePermission.java b/framework/src/main/java/org/osgi/framework/PackagePermission.java
index 1338daa..c10c211 100644
--- a/framework/src/main/java/org/osgi/framework/PackagePermission.java
+++ b/framework/src/main/java/org/osgi/framework/PackagePermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2000, 2017). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2000, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -54,7 +54,7 @@
  * deprecated, implies the {@code import} action.
  * 
  * @ThreadSafe
- * @author $Id: 264ccd683465cbe22d571b0cb7d0b19352d582f7 $
+ * @author $Id: cc8cd627f5ca1e77bd3420d6e64d07b1a9ba4684 $
  */
 
 public final class PackagePermission extends BasicPermission {
@@ -713,7 +713,7 @@
 			/* work our way up the tree... */
 			int last;
 			int offset = requestedName.length() - 1;
-			while ((last = requestedName.lastIndexOf(".", offset)) != -1) {
+			while ((last = requestedName.lastIndexOf('.', offset)) != -1) {
 				requestedName = requestedName.substring(0, last + 1) + "*";
 				pp = pc.get(requestedName);
 				if (pp != null) {
diff --git a/framework/src/main/java/org/osgi/framework/PrototypeServiceFactory.java b/framework/src/main/java/org/osgi/framework/PrototypeServiceFactory.java
index 864506f..9a3a3c8 100644
--- a/framework/src/main/java/org/osgi/framework/PrototypeServiceFactory.java
+++ b/framework/src/main/java/org/osgi/framework/PrototypeServiceFactory.java
@@ -65,7 +65,7 @@
  * @see ServiceObjects
  * @ThreadSafe
  * @since 1.8
- * @author $Id$
+ * @author $Id: 864506fa15679676e52eee91982a6fd5c1e9768f $
  */
 @ConsumerType
 public interface PrototypeServiceFactory<S> extends ServiceFactory<S> {
diff --git a/framework/src/main/java/org/osgi/framework/ServiceObjects.java b/framework/src/main/java/org/osgi/framework/ServiceObjects.java
index 8490189..9b8ca46 100644
--- a/framework/src/main/java/org/osgi/framework/ServiceObjects.java
+++ b/framework/src/main/java/org/osgi/framework/ServiceObjects.java
@@ -41,7 +41,7 @@
  * @see PrototypeServiceFactory
  * @ThreadSafe
  * @since 1.8
- * @author $Id$
+ * @author $Id: 84901895b763946d9f0e3819e47ecbf0ffa60f04 $
  */
 @ProviderType
 public interface ServiceObjects<S> {
diff --git a/framework/src/main/java/org/osgi/framework/ServicePermission.java b/framework/src/main/java/org/osgi/framework/ServicePermission.java
index e7d6c6f..693a787 100644
--- a/framework/src/main/java/org/osgi/framework/ServicePermission.java
+++ b/framework/src/main/java/org/osgi/framework/ServicePermission.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2000, 2017). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2000, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,7 +51,7 @@
  * to get the specific service.
  * 
  * @ThreadSafe
- * @author $Id: 8db61d0b1cadd57ab173cba677b6bfb353680800 $
+ * @author $Id: a6b52521c8ba68698c1e167d4596f3ac56aab8ca $
  */
 
 public final class ServicePermission extends BasicPermission {
@@ -866,7 +866,7 @@
 		// work our way up the tree...
 		int last;
 		int offset = requestedName.length() - 1;
-		while ((last = requestedName.lastIndexOf(".", offset)) != -1) {
+		while ((last = requestedName.lastIndexOf('.', offset)) != -1) {
 			requestedName = requestedName.substring(0, last + 1) + "*";
 			sp = pc.get(requestedName);
 			if (sp != null) {
diff --git a/framework/src/main/java/org/osgi/framework/ServiceReference.java b/framework/src/main/java/org/osgi/framework/ServiceReference.java
index 5d1b175..c512a44 100644
--- a/framework/src/main/java/org/osgi/framework/ServiceReference.java
+++ b/framework/src/main/java/org/osgi/framework/ServiceReference.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2000, 2017). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2000, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,10 +50,11 @@
  * @see BundleContext#getService(ServiceReference)
  * @see BundleContext#getServiceObjects(ServiceReference)
  * @ThreadSafe
- * @author $Id: 1454244c30992b7a52ac3838b03bc584c3495816 $
+ * @author $Id: adb91d7f0922417180e901dc6ec447f467b34921 $
  */
 @ProviderType
-public interface ServiceReference<S> extends Comparable<Object> {
+public interface ServiceReference<S>
+		extends Comparable<Object>, BundleReference {
 	/**
 	 * Returns the property value to which the specified property key is mapped
 	 * in the properties {@code Dictionary} object of the service referenced by
@@ -109,6 +110,7 @@
 	 *         already been unregistered.
 	 * @see BundleContext#registerService(String[],Object,Dictionary)
 	 */
+	@Override
 	public Bundle getBundle();
 
 	/**
@@ -131,14 +133,24 @@
 	 * <p>
 	 * This method performs the following checks:
 	 * <ol>
+	 * <li>If the specified bundle is equal to the bundle that registered the
+	 * service referenced by this {@code ServiceReference} (registrant bundle)
+	 * return {@code true}.</li>
 	 * <li>Get the package name from the specified class name.</li>
-	 * <li>For the bundle that registered the service referenced by this
-	 * {@code ServiceReference} (registrant bundle); find the source for the
-	 * package. If no source is found then return {@code true} if the registrant
-	 * bundle is equal to the specified bundle; otherwise return {@code false}.</li>
-	 * <li>If the package source of the registrant bundle is equal to the
-	 * package source of the specified bundle then return {@code true};
-	 * otherwise return {@code false}.</li>
+	 * <li>For the specified bundle; find the source for the package. If no
+	 * source is found then return {@code true} (use of reflection is assumed by
+	 * the specified bundle).</li>
+	 * <li>For the registrant bundle; find the source for the package. If the
+	 * package source is found then return {@code true} if the package source
+	 * equals the package source of the specified bundle; otherwise return
+	 * {@code false}.</li>
+	 * <li>If no package source is found for the registrant bundle then
+	 * determine the package source based on the service object. If the service
+	 * object is a {@code ServiceFactory} and the factory implementation is not
+	 * from the registrant bundle return {@code true}; otherwise attempt to find
+	 * the package source based on the service object class. If the package
+	 * source is found and is equal to package source of the specified bundle
+	 * return {@code true}; otherwise return {@code false}.</li>
 	 * </ol>
 	 * 
 	 * @param bundle The {@code Bundle} object to check.
@@ -148,8 +160,8 @@
 	 *         bundle use the same source for the package of the specified class
 	 *         name. Otherwise {@code false} is returned.
 	 * @throws IllegalArgumentException If the specified {@code Bundle} was not
-	 *         created by the same framework instance as this
-	 *         {@code ServiceReference}.
+	 *             created by the same framework instance as this
+	 *             {@code ServiceReference}.
 	 * @since 1.3
 	 */
 	public boolean isAssignableTo(Bundle bundle, String className);
@@ -212,4 +224,27 @@
 	 * @since 1.9
 	 */
 	public Dictionary<String,Object> getProperties();
+
+	/**
+	 * Adapt this {@code ServiceReference} object to the specified type.
+	 * <p>
+	 * Adapting this {@code ServiceReference} object to the specified type may
+	 * require certain checks, including security checks, to succeed. If a check
+	 * does not succeed, then this {@code ServiceReference} object cannot be
+	 * adapted and {@code null} is returned.
+	 * 
+	 * @param <A> The type to which this {@code ServiceReference} object is to
+	 *            be adapted.
+	 * @param type Class object for the type to which this
+	 *            {@code ServiceReference} object is to be adapted.
+	 * @return The object, of the specified type, to which this
+	 *         {@code ServiceReference} object has been adapted or {@code null}
+	 *         if this {@code ServiceReference} object cannot be adapted to the
+	 *         specified type.
+	 * @throws SecurityException If the caller does not have the appropriate
+	 *             {@code AdaptPermission[type,this,ADAPT]}, and the Java
+	 *             Runtime Environment supports permissions.
+	 * @since 1.10
+	 */
+	<A> A adapt(Class<A> type);
 }
diff --git a/framework/src/main/java/org/osgi/framework/connect/ConnectContent.java b/framework/src/main/java/org/osgi/framework/connect/ConnectContent.java
new file mode 100644
index 0000000..3a90329
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/connect/ConnectContent.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) OSGi Alliance (2019, 2020). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.framework.connect;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Optional;
+
+import org.osgi.annotation.versioning.ConsumerType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleRevisions;
+
+/**
+ * A {@code ConnectContent} provides a {@link Framework} instance access to the
+ * content of a {@link ConnectModule}.
+ * <p>
+ * A framework may {@link #open() open} and {@link #close() close} the content
+ * for a {@link ConnectModule} multiple times while the {@code ConnectContent}
+ * is in use by the framework. The framework must close the
+ * {@code ConnectContent} once the {@code ConnectContent} is no longer used as
+ * the content of a current bundle revision or an in use bundle revision.
+ * <p>
+ * An entry in a {@code ConnectContent} is identified by a path name that is a
+ * solidus (<code>'/' \u002F</code>) separated path. A {@code ConnectContent}
+ * may treat directories as entries. A directory entry path name will end with a
+ * solidus. A directory entry may be located using a path name that omits the
+ * trailing solidus.
+ * 
+ * @see BundleRevisions
+ * @ThreadSafe
+ * @author $Id: 9e455f9d467f0e38daea0ea52a59a5ccb8c81257 $
+ */
+@ConsumerType
+public interface ConnectContent {
+	/**
+	 * The {@code osgi.identity}
+	 * {@link IdentityNamespace#CAPABILITY_TAGS_ATTRIBUTE tags} attribute value
+	 * used by the framework to tag connect bundle revisions.
+	 */
+	String TAG_OSGI_CONNECT = "osgi.connect";
+
+	/**
+	 * Returns the Manifest headers and values of this {@code ConnectContent}.
+	 * 
+	 * @return An {@code Optional} containing the Manifest headers and values
+	 *         for this {@code ConnectContent}, or an empty {@code Optional} if
+	 *         the framework should handle parsing the Manifest of the content
+	 *         itself.
+	 * @throws IllegalStateException If this {@code ConnectContent} has been
+	 *             closed.
+	 */
+	Optional<Map<String,String>> getHeaders();
+
+	/**
+	 * Returns the entry names available in this {@code ConnectContent}.
+	 * 
+	 * @return An {@code Iterable} which can supply the available entry names.
+	 * @throws IOException If an error occurs reading this
+	 *             {@code ConnectContent}.
+	 * @throws IllegalStateException If this {@code ConnectContent} has been
+	 *             closed.
+	 */
+	Iterable<String> getEntries() throws IOException;
+
+	/**
+	 * Returns the {@link ConnectEntry} for the specified path name in this
+	 * content.
+	 * <p>
+	 * The {@link Optional#empty() empty} value is returned if an entry with the
+	 * specified path name does not exist. The path must not start with a
+	 * &quot;/&quot; and is relative to the root of this content. A connect
+	 * entry for a directory will have a path name that ends with a slash ('/').
+	 * 
+	 * @param path The path name of the entry.
+	 * @return An {@code Optional} containing the {@link ConnectEntry} for the
+	 *         specified path, or an empty {@code Optional} if no entry for
+	 *         specified path can be found.
+	 * @throws IllegalStateException If this {@code ConnectContent} has been
+	 *             closed.
+	 */
+	Optional<ConnectEntry> getEntry(String path);
+
+	/**
+	 * Returns a class loader for this {@code ConnectContent}.
+	 * <p>
+	 * This method is called by the framework for {@link Bundle#RESOLVED
+	 * resolved} bundles only and will be called at most once while a bundle is
+	 * resolved. If a bundle associated with a {@link ConnectModule} is
+	 * refreshed and resolved again, the framework will ask the
+	 * {@code ConnectContent} for the class loader again. This allows for a
+	 * {@code ConnectContent} to reuse or create a new class loader each time
+	 * the bundle revision is resolved.
+	 * 
+	 * @return An {@code Optional} containing the class loader for this
+	 *         {@code ConnectContent}, or an empty {@code Optional} if framework
+	 *         should handle creating a class loader for the bundle revision
+	 *         associated with this {@code ConnectContent}.
+	 * @throws IllegalStateException If this {@code ConnectContent} has been
+	 *             closed.
+	 */
+	Optional<ClassLoader> getClassLoader();
+
+	/**
+	 * Opens this {@code ConnectContent}.
+	 * <p>
+	 * The framework will open the content when it needs to access the content
+	 * for a bundle revision associated with this {@code ConnectContent}. The
+	 * framework may defer calling this method until requests to access the
+	 * bundle revision content are made.
+	 * 
+	 * @throws IOException If an error occurred opening this
+	 *             {@code ConnectContent}.
+	 */
+	void open() throws IOException;
+
+	/**
+	 * Closes this {@code ConnectContent}.
+	 * 
+	 * @throws IOException If an error occurred closing this
+	 *             {@code ConnectContent}.
+	 */
+	void close() throws IOException;
+
+	/**
+	 * Represents the entry of a {@code ConnectContent}.
+	 */
+	@ConsumerType
+	public interface ConnectEntry {
+		/**
+		 * Returns the path name of this entry.
+		 * 
+		 * @return The path name of this entry.
+		 */
+		String getName();
+
+		/**
+		 * Returns the content length of this entry.
+		 * 
+		 * @return The content length of the entry, or {@code -1} if the content
+		 *         length is not known.
+		 */
+		public long getContentLength();
+
+		/**
+		 * Returns the last modification time of this entry.
+		 * 
+		 * @return The last modification time of this entry measured in
+		 *         milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
+		 */
+		public long getLastModified();
+
+		/**
+		 * Returns the content of this entry.
+		 * 
+		 * @return The content of this entry.
+		 * @throws IOException If an error occurs reading the content.
+		 */
+		default byte[] getBytes() throws IOException {
+			long longLength = getContentLength();
+			if (longLength > Integer.MAX_VALUE - 8) {
+				throw new IOException(
+						"Entry is to big to fit into a byte[]: " + getName());
+			}
+
+			try (InputStream in = getInputStream()) {
+				int length = (int) longLength;
+				if (length > 0) {
+					int bytesread = 0;
+					byte[] result = new byte[length];
+					int readcount = 0;
+					while (bytesread < length) {
+						readcount = in.read(result, bytesread,
+								length - bytesread);
+						bytesread += readcount;
+						if (readcount <= 0) {
+							break;
+						}
+					}
+					return result;
+				} else {
+					ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+					int nRead;
+					byte[] data = new byte[1024];
+					while ((nRead = in.read(data, 0, data.length)) > 0) {
+						buffer.write(data, 0, nRead);
+					}
+					buffer.flush();
+					return buffer.toByteArray();
+				}
+			}
+		}
+
+		/**
+		 * Returns an input stream for the content of this entry.
+		 * 
+		 * @return An input stream for the content of this entry.
+		 * @throws IOException If an error occurs reading the content.
+		 */
+		InputStream getInputStream() throws IOException;
+	}
+}
diff --git a/framework/src/main/java/org/osgi/framework/connect/ConnectFrameworkFactory.java b/framework/src/main/java/org/osgi/framework/connect/ConnectFrameworkFactory.java
new file mode 100644
index 0000000..307f419
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/connect/ConnectFrameworkFactory.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) OSGi Alliance (2019, 2020). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.framework.connect;
+
+import java.util.Map;
+
+import org.osgi.annotation.versioning.ProviderType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.launch.Framework;
+
+/**
+ * A factory for creating {@link Framework} instances.
+ * <p>
+ * If a framework supports {@link ModuleConnector}, then the implementation jar
+ * must contain the following resource:
+ * 
+ * <pre>
+ * /META-INF/services/org.osgi.framework.connect.ConnectFrameworkFactory
+ * </pre>
+ * 
+ * This UTF-8 encoded resource must contain the name of the framework
+ * implementation's ConnectFrameworkFactory implementation class. Space and tab
+ * characters, including blank lines, in the resource must be ignored. The
+ * number sign ({@code '#'} &#92;u0023) and all characters following it on each
+ * line are a comment and must be ignored.
+ * <p>
+ * Launchers can find the name of the ConnectFrameworkFactory implementation
+ * class in the resource and then load and construct a ConnectFrameworkFactory
+ * object for the framework implementation. The ConnectFrameworkFactory
+ * implementation class must have a public, no-argument constructor. Java&#8482;
+ * SE 6 introduced the {@code ServiceLoader} class which can create a
+ * ConnectFrameworkFactory instance from the resource.
+ * 
+ * @ThreadSafe
+ * @author $Id: fee4e88754bbaa4a88bcee0c0eaefa54893df6a1 $
+ */
+@ProviderType
+public interface ConnectFrameworkFactory {
+	/**
+	 * Create a new {@link Framework} instance using the specified
+	 * {@link ModuleConnector module connector}.
+	 * 
+	 * @param configuration The framework properties to configure the new
+	 *            framework instance. If framework properties are not provided
+	 *            by the configuration argument, the created framework instance
+	 *            must use some reasonable default configuration appropriate for
+	 *            the current VM. For example, the system packages for the
+	 *            current execution environment should be properly exported. The
+	 *            specified configuration argument may be {@code null}. The
+	 *            created framework instance must copy any information needed
+	 *            from the specified configuration argument since the
+	 *            configuration argument can be changed after the framework
+	 *            instance has been created.
+	 * @param moduleConnector The module connector that the new framework
+	 *            instance will use. The specified module connector argument may
+	 *            be {@code null}.
+	 * @return A new, configured {@link Framework} instance. The framework
+	 *         instance must be in the {@link Bundle#INSTALLED} state.
+	 * @throws SecurityException If the caller does not have
+	 *             {@code AllPermission}, and the Java Runtime Environment
+	 *             supports permissions.
+	 * @see ModuleConnector
+	 */
+	Framework newFramework(Map<String,String> configuration,
+			ModuleConnector moduleConnector);
+}
diff --git a/framework/src/main/java/org/osgi/framework/connect/ConnectModule.java b/framework/src/main/java/org/osgi/framework/connect/ConnectModule.java
new file mode 100644
index 0000000..d3b8376
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/connect/ConnectModule.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) OSGi Alliance (2019, 2020). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.framework.connect;
+
+import java.io.IOException;
+
+import org.osgi.annotation.versioning.ConsumerType;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.wiring.BundleRevision;
+
+/**
+ * A {@code ConnectModule} is used by a {@link Framework} instance to access the
+ * content of the connected bundle.
+ * 
+ * @ThreadSafe
+ * @author $Id: d81245bffb9c6de8e3d2e9515f1443b0f6b47189 $
+ */
+@ConsumerType
+public interface ConnectModule {
+	/**
+	 * Returns the current content of this connect module.
+	 * <p>
+	 * The framework must call this method when it needs to access the content
+	 * for the current {@link BundleRevision bundle revision} of this
+	 * {@code ConnectModule}. The framework may defer opening the returned
+	 * {@link ConnectContent} until requests to access the bundle revision
+	 * content are made.
+	 * 
+	 * @return The current {@link ConnectContent} of this {@code ConnectModule}.
+	 * @throws IOException If an error occurred getting the content.
+	 * @see ModuleConnector#connect(String)
+	 */
+	ConnectContent getContent() throws IOException;
+}
diff --git a/framework/src/main/java/org/osgi/framework/connect/FrameworkUtilHelper.java b/framework/src/main/java/org/osgi/framework/connect/FrameworkUtilHelper.java
new file mode 100644
index 0000000..f7b54ab
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/connect/FrameworkUtilHelper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) OSGi Alliance (2019, 2020). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.framework.connect;
+
+import java.util.Optional;
+
+import org.osgi.annotation.versioning.ConsumerType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * A helper for the {@link FrameworkUtil} class.
+ * <p>
+ * This helper provides alternative implementations for methods on
+ * {@link FrameworkUtil}.
+ */
+@ConsumerType
+public interface FrameworkUtilHelper {
+	/**
+	 * Returns the {@link Bundle} associated with the specified class.
+	 * <p>
+	 * This helper method is called by {@link FrameworkUtil#getBundle(Class)} if
+	 * the standard implementation of {@link FrameworkUtil} is unable to find
+	 * the bundle.
+	 * 
+	 * @param classFromBundle A class associated with a bundle.
+	 * @return An {@code Optional} containing the {@link Bundle} for the
+	 *         specified class, or an empty {@code Optional} if the specified
+	 *         class is not from a bundle.
+	 */
+	default Optional<Bundle> getBundle(Class< ? > classFromBundle) {
+		return Optional.empty();
+	}
+}
diff --git a/framework/src/main/java/org/osgi/framework/connect/ModuleConnector.java b/framework/src/main/java/org/osgi/framework/connect/ModuleConnector.java
new file mode 100644
index 0000000..e62b0e0
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/connect/ModuleConnector.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) OSGi Alliance (2019, 2020). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.framework.connect;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Optional;
+
+import org.osgi.annotation.versioning.ConsumerType;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.launch.Framework;
+
+/**
+ * A {@code ModuleConnector} provides connections to instances of
+ * {@link ConnectModule} that are used by a {@link Framework} instance to
+ * connect installed bundles locations with content provided by the
+ * {@code ModuleConnector}.
+ * <p>
+ * This allows a {@code ModuleConnector} to provide content and classes for a
+ * connected bundle installed in the {@code Framework}. A
+ * {@code ModuleConnector} is provided when
+ * {@link ConnectFrameworkFactory#newFramework(Map, ModuleConnector) creating} a
+ * framework instance. Because a {@code ModuleConnector} instance can
+ * participate in the initialization of the {@code Framework} and the life cycle
+ * of a {@code Framework} instance the {@code ModuleConnector} instance should
+ * only be used with a single {@code Framework} instance at a time.
+ * 
+ * @ThreadSafe
+ * @author $Id: 5ee358acfec177e4bf92994fee8e61c9c841ec06 $
+ */
+@ConsumerType
+public interface ModuleConnector {
+
+	/**
+	 * Initializes this {@code ModuleConnector} with the
+	 * {@link Constants#FRAMEWORK_STORAGE framework persistent storage} file and
+	 * framework properties configured for a {@link Framework} instance.
+	 * <p>
+	 * This method is called once by a {@link Framework} instance and is called
+	 * before any other methods on this module connector are called.
+	 * 
+	 * @param storage The persistent storage area used by the {@link Framework}
+	 *            or {@code null} if the platform does not have file system
+	 *            support.
+	 * @param configuration An unmodifiable map of framework configuration
+	 *            properties that were used to configure the new framework
+	 *            instance.
+	 */
+	void initialize(File storage, Map<String,String> configuration);
+
+	/**
+	 * Connects a bundle location with a {@link ConnectModule}.
+	 * <p>
+	 * When the result is empty, then the framework must handle reading the
+	 * content of the bundle itself. Otherwise, the returned
+	 * {@link ConnectModule} must be used by the framework to access the content
+	 * of the bundle.
+	 * 
+	 * @param location The bundle location used to install a bundle.
+	 * @return An {@code Optional} containing the {@link ConnectModule} for the
+	 *         specified bundle location, or an empty {@code Optional} if the
+	 *         framework must handle reading the content of the bundle itself.
+	 * @throws BundleException If the location cannot be handled.
+	 */
+	Optional<ConnectModule> connect(String location) throws BundleException;
+
+	/**
+	 * Creates a new activator for this {@code ModuleConnector}.
+	 * <p>
+	 * This method is called by the framework during framework
+	 * {@link Framework#init(FrameworkListener...) initialization}. Returning an
+	 * activator allows this {@code ModuleConnector} to participate in the
+	 * framework life cycle. If an activator is returned:
+	 * <ul>
+	 * <li>The framework will call the activator's
+	 * {@link BundleActivator#start(BundleContext) start} method prior to
+	 * activating any extension bundles.</li>
+	 * <li>The framework will call the activator's
+	 * {@link BundleActivator#stop(BundleContext) stop} method after
+	 * deactivating any extension bundles.</li>
+	 * </ul>
+	 * 
+	 * @return An {@code Optional} containing a new {@link BundleActivator} for
+	 *         this {@code ModuleConnector}, or an empty {@code Optional} if no
+	 *         {@link BundleActivator} is necessary.
+	 */
+	Optional<BundleActivator> newBundleActivator();
+}
diff --git a/framework/src/main/java/org/osgi/framework/connect/package-info.java b/framework/src/main/java/org/osgi/framework/connect/package-info.java
new file mode 100644
index 0000000..d4e22a4
--- /dev/null
+++ b/framework/src/main/java/org/osgi/framework/connect/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) OSGi Alliance (2019, 2020). All Rights Reserved.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Framework Connect Package Version 1.0.
+ * <p>
+ * Bundles wishing to use this package must list the package in the
+ * Import-Package header of the bundle's manifest.
+ * <p>
+ * Example import for consumers using the API in this package:
+ * <p>
+ * {@code  Import-Package: org.osgi.framework.connect; version="[1.0,2.0)"}
+ * 
+ * @author $Id: b7344ca62c5cef84330e009ce4a0704e4d83b6c0 $
+ */
+
+@Version("1.0")
+package org.osgi.framework.connect;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/framework/src/main/java/org/osgi/framework/dto/BundleDTO.java b/framework/src/main/java/org/osgi/framework/dto/BundleDTO.java
index aa30709..6b83401 100644
--- a/framework/src/main/java/org/osgi/framework/dto/BundleDTO.java
+++ b/framework/src/main/java/org/osgi/framework/dto/BundleDTO.java
@@ -25,7 +25,7 @@
  * <p>
  * A Bundle can be adapted to provide a {@code BundleDTO} for the Bundle.
  * 
- * @author $Id$
+ * @author $Id: aa30709351d8fe70b19c9ea99456ebd15ecab7c3 $
  * @NotThreadSafe
  */
 public class BundleDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/framework/dto/FrameworkDTO.java b/framework/src/main/java/org/osgi/framework/dto/FrameworkDTO.java
index 7c52572..7b32f93 100644
--- a/framework/src/main/java/org/osgi/framework/dto/FrameworkDTO.java
+++ b/framework/src/main/java/org/osgi/framework/dto/FrameworkDTO.java
@@ -30,7 +30,7 @@
  * framework will contain only the launch properties of the framework. These
  * properties will not include the System properties.
  * 
- * @author $Id$
+ * @author $Id: 7c525727cbe877e888b460cd14d8f9054f99ee0c $
  * @NotThreadSafe
  */
 public class FrameworkDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/framework/dto/ServiceReferenceDTO.java b/framework/src/main/java/org/osgi/framework/dto/ServiceReferenceDTO.java
index 87265de..2f92fb1 100644
--- a/framework/src/main/java/org/osgi/framework/dto/ServiceReferenceDTO.java
+++ b/framework/src/main/java/org/osgi/framework/dto/ServiceReferenceDTO.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2012, 2014). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2012, 2019). All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,22 +17,23 @@
 package org.osgi.framework.dto;
 
 import java.util.Map;
+
 import org.osgi.dto.DTO;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
 
 /**
  * Data Transfer Object for a ServiceReference.
- * 
  * <p>
  * {@code ServiceReferenceDTO}s for all registered services can be obtained from
- * a {@link FrameworkDTO}. A started Bundle can be adapted to provide a
+ * a {@link FrameworkDTO}. A {@link ServiceReference} can be adapted to a
+ * {@code ServiceReferenceDTO}. A started Bundle can be adapted to provide a
  * {@code ServiceReferenceDTO[]} of the services registered by the Bundle. A
  * {@code ServiceReferenceDTO} obtained from a framework must convert service
  * property values which are not valid value types for DTOs to type
  * {@code String} using {@code String.valueOf(Object)}.
  * 
- * @author $Id$
+ * @author $Id: 8ac26a24b8adacdcd09f441dcdfc8d6a27bbaabd $
  * @NotThreadSafe
  */
 public class ServiceReferenceDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/framework/dto/package-info.java b/framework/src/main/java/org/osgi/framework/dto/package-info.java
index 2acfb6f..4499fc8 100644
--- a/framework/src/main/java/org/osgi/framework/dto/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/dto/package-info.java
@@ -32,7 +32,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.dto; version="[1.8,1.9)"}
  * 
- * @author $Id$
+ * @author $Id: 2acfb6f1633e18f1ceedd27c04e70131cae4f293 $
  */
 
 @Version("1.8")
diff --git a/framework/src/main/java/org/osgi/framework/hooks/bundle/package-info.java b/framework/src/main/java/org/osgi/framework/hooks/bundle/package-info.java
index 08c20ca..ce89815 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/bundle/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/bundle/package-info.java
@@ -26,7 +26,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.hooks.bundle; version="[1.1,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: 08c20cab669f1850f585c5cd4b3b897ce587b2bd $
  */
 
 @Version("1.1")
diff --git a/framework/src/main/java/org/osgi/framework/hooks/resolver/package-info.java b/framework/src/main/java/org/osgi/framework/hooks/resolver/package-info.java
index de05003..6333093 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/resolver/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/resolver/package-info.java
@@ -26,7 +26,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.hooks.resolver; version="[1.0,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: de050037c6b835045603f09b12ad58a6353d1229 $
  */
 
 @Version("1.0")
diff --git a/framework/src/main/java/org/osgi/framework/hooks/service/package-info.java b/framework/src/main/java/org/osgi/framework/hooks/service/package-info.java
index 74f0c41..5fd8020 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/service/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/service/package-info.java
@@ -26,7 +26,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.hooks.service; version="[1.1,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: 74f0c41d5ebf20181942ee393965d885deb3889c $
  */
 
 @Version("1.1")
diff --git a/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingException.java b/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingException.java
index 254dd9b..8768402 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingException.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2010, 2013). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2010, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,12 +18,11 @@
 
 /**
  * A weaving exception used to indicate that the class load should be failed but
- * the weaving hook must not be blacklisted by the framework.
- * 
+ * the weaving hook must not be deny listed by the framework.
  * <p>
  * This exception conforms to the general purpose exception chaining mechanism.
  * 
- * @author $Id: 7575fc1b015fea7c77397391df6c8d2085513e76 $
+ * @author $Id: 29ebe6460fbe20ce8037f14a162c72f633b1de31 $
  */
 
 public class WeavingException extends RuntimeException {
diff --git a/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingHook.java b/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingHook.java
index 760b00c..b3e7ac4 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingHook.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/weaving/WeavingHook.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2010, 2013). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2010, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,30 +36,29 @@
  * bytes as modified by previously called weaving hooks.
  * 
  * @ThreadSafe
- * @author $Id: 8d99df5b0f3e7ffa9573695923afe86de9835fde $
+ * @author $Id: e5b54121d2cab7caefbae3b718421b28e5a9ede7 $
  */
 @ConsumerType
 public interface WeavingHook {
 	/**
 	 * Weaving hook method.
-	 * 
+	 * <p>
 	 * This method can modify the specified woven class object to weave the
 	 * class being defined.
-	 * 
 	 * <p>
 	 * If this method throws any exception, the framework must log the exception
 	 * and fail the class load in progress. This weaving hook service must be
-	 * blacklisted by the framework and must not be called again. The
-	 * blacklisting of this weaving hook service must expire when this weaving
-	 * hook service is unregistered. However, this method can throw a
+	 * deny listed by the framework and must not be called again. The deny
+	 * listing of this weaving hook service must expire when this weaving hook
+	 * service is unregistered. However, this method can throw a
 	 * {@link WeavingException} to deliberately fail the class load in progress
-	 * without being blacklisted by the framework.
+	 * without being deny listed by the framework.
 	 * 
 	 * @param wovenClass The {@link WovenClass} object that represents the data
-	 *        that will be used to define the class.
+	 *            that will be used to define the class.
 	 * @throws WeavingException If this weaving hook wants to deliberately fail
-	 *         the class load in progress without being blacklisted by the
-	 *         framework
+	 *             the class load in progress without being deny listed by the
+	 *             framework
 	 */
 	public void weave(WovenClass wovenClass);
 }
diff --git a/framework/src/main/java/org/osgi/framework/hooks/weaving/WovenClassListener.java b/framework/src/main/java/org/osgi/framework/hooks/weaving/WovenClassListener.java
index 4a7a699..36e7303 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/weaving/WovenClassListener.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/weaving/WovenClassListener.java
@@ -47,7 +47,7 @@
  * 
  * @ThreadSafe
  * @since 1.1
- * @author $Id$
+ * @author $Id: 4a7a69943bffbc53738f050c51d49f11b67a13cb $
  */
 @ConsumerType
 public interface WovenClassListener {
diff --git a/framework/src/main/java/org/osgi/framework/hooks/weaving/package-info.java b/framework/src/main/java/org/osgi/framework/hooks/weaving/package-info.java
index 5b48f04..bffee3e 100644
--- a/framework/src/main/java/org/osgi/framework/hooks/weaving/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/hooks/weaving/package-info.java
@@ -28,7 +28,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.hooks.weaving; version="[1.1,2.0)"}
  * </p>
- * @author $Id$
+ * @author $Id: 5b48f041a1764e8b2979cdbbb528082054ab3e7f $
  */
 
 @Version("1.1")
diff --git a/framework/src/main/java/org/osgi/framework/launch/Framework.java b/framework/src/main/java/org/osgi/framework/launch/Framework.java
index 37a519d..fd6d325 100644
--- a/framework/src/main/java/org/osgi/framework/launch/Framework.java
+++ b/framework/src/main/java/org/osgi/framework/launch/Framework.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2008, 2018). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2008, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 import java.io.InputStream;
 import java.net.URL;
 import java.util.Enumeration;
+
 import org.osgi.annotation.versioning.ProviderType;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
@@ -35,7 +36,7 @@
  * instance.
  * 
  * @ThreadSafe
- * @author $Id: 7fa67978e59a43dfedd6e755ddfa5b1aa6ea9141 $
+ * @author $Id: bf960bdc39d19a780694a8cab5a555b3e0dc0fde $
  */
 @ProviderType
 public interface Framework extends Bundle {
@@ -117,37 +118,33 @@
 	 * A Framework Event is returned to indicate why this Framework has stopped.
 	 * 
 	 * @param timeout Maximum number of milliseconds to wait until this
-	 *        Framework has completely stopped. A value of zero will wait
-	 *        indefinitely.
+	 *            Framework has completely stopped. A value of zero will wait
+	 *            indefinitely.
 	 * @return A Framework Event indicating the reason this method returned. The
 	 *         following {@code FrameworkEvent} types may be returned by this
 	 *         method.
 	 *         <ul>
 	 *         <li>{@link FrameworkEvent#STOPPED STOPPED} - This Framework has
-	 *         been stopped. </li>
-	 * 
+	 *         been stopped.</li>
 	 *         <li>{@link FrameworkEvent#STOPPED_UPDATE STOPPED_UPDATE} - This
 	 *         Framework has been updated which has shutdown and will now
 	 *         restart.</li>
-	 * 
-	 *         <li> {@link FrameworkEvent#STOPPED_BOOTCLASSPATH_MODIFIED
-	 *         STOPPED_BOOTCLASSPATH_MODIFIED} - This Framework has been stopped
-	 *         and a bootclasspath extension bundle has been installed or
-	 *         updated. The VM must be restarted in order for the changed boot
-	 *         class path to take effect. </li>
-	 * 
+	 *         <li>{@link FrameworkEvent#STOPPED_SYSTEM_REFRESHED
+	 *         STOPPED_SYSTEM_REFRESHED} - The Framework has been stopped
+	 *         because of a refresh operation on the system bundle. A new class
+	 *         loader must be used to restart the Framework.</li>
 	 *         <li>{@link FrameworkEvent#ERROR ERROR} - The Framework
 	 *         encountered an error while shutting down or an error has occurred
-	 *         which forced the framework to shutdown. </li>
-	 * 
-	 *         <li> {@link FrameworkEvent#WAIT_TIMEDOUT WAIT_TIMEDOUT} - This
+	 *         which forced the framework to shutdown.</li>
+	 *         <li>{@link FrameworkEvent#WAIT_TIMEDOUT WAIT_TIMEDOUT} - This
 	 *         method has timed out and returned before this Framework has
 	 *         stopped.</li>
 	 *         </ul>
 	 * @throws InterruptedException If another thread interrupted the current
-	 *         thread before or while the current thread was waiting for this
-	 *         Framework to completely stop. The <i>interrupted status</i> of
-	 *         the current thread is cleared when this exception is thrown.
+	 *             thread before or while the current thread was waiting for
+	 *             this Framework to completely stop. The <i>interrupted
+	 *             status</i> of the current thread is cleared when this
+	 *             exception is thrown.
 	 * @throws IllegalArgumentException If the value of timeout is negative.
 	 */
 	FrameworkEvent waitForStop(long timeout) throws InterruptedException;
diff --git a/framework/src/main/java/org/osgi/framework/launch/FrameworkFactory.java b/framework/src/main/java/org/osgi/framework/launch/FrameworkFactory.java
index 6d656d3..f5e29a9 100644
--- a/framework/src/main/java/org/osgi/framework/launch/FrameworkFactory.java
+++ b/framework/src/main/java/org/osgi/framework/launch/FrameworkFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2009, 2013). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2009, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
 package org.osgi.framework.launch;
 
 import java.util.Map;
+
 import org.osgi.annotation.versioning.ProviderType;
 import org.osgi.framework.Bundle;
 
@@ -45,7 +46,7 @@
  * the resource.
  * 
  * @ThreadSafe
- * @author $Id: c1647bcb8416b6dfa9e37c6cc146bb54c7173526 $
+ * @author $Id: ecb53ee09a939a2c23a47ecad6f94fcfdf2f7c46 $
  */
 @ProviderType
 public interface FrameworkFactory {
diff --git a/framework/src/main/java/org/osgi/framework/launch/package-info.java b/framework/src/main/java/org/osgi/framework/launch/package-info.java
index db5e926..77d416f 100644
--- a/framework/src/main/java/org/osgi/framework/launch/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/launch/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2010, 2014). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2010, 2019). All Rights Reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.launch; version="[1.2,2.0)"}
  *
- * @author $Id$
+ * @author $Id: 70417d372c5e8200dc743847b14aeb300870b839 $
  */
 
 @Version("1.2")
diff --git a/framework/src/main/java/org/osgi/framework/namespace/HostNamespace.java b/framework/src/main/java/org/osgi/framework/namespace/HostNamespace.java
index 2f843a8..c6af9d8 100644
--- a/framework/src/main/java/org/osgi/framework/namespace/HostNamespace.java
+++ b/framework/src/main/java/org/osgi/framework/namespace/HostNamespace.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2012, 2014). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2012, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -54,19 +54,19 @@
  * </ul>
  * 
  * <p>
- * A non-fragment resource with the with the
- * {@link IdentityNamespace#TYPE_BUNDLE osgi.bundle} type
- * {@link IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE identity} provides zero or
- * one<sup>&#8224;</sup> host capabilities. A fragment resource with the
- * {@link IdentityNamespace#TYPE_FRAGMENT osgi.fragment} type
- * {@link IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE identity} must not declare
- * a host capability and must declare exactly one host requirement.
+ * A non-fragment resource with the {@link IdentityNamespace#TYPE_BUNDLE
+ * osgi.bundle} type {@link IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE
+ * identity} provides zero or one<sup>&#8224;</sup> host capabilities. A
+ * fragment resource with the {@link IdentityNamespace#TYPE_FRAGMENT
+ * osgi.fragment} type {@link IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE
+ * identity} must not declare a host capability and must declare exactly one
+ * host requirement.
  * <p>
  * &#8224; A resource with no bundle symbolic name must not provide a host
  * capability.
  * 
  * @Immutable
- * @author $Id: 9f789ca25dafcf9d5e9a4f45d377f943d62b134a $
+ * @author $Id: 79484b4d0c372d8cebf7c767ec12e134b60b8411 $
  */
 public final class HostNamespace extends AbstractWiringNamespace {
 
@@ -135,7 +135,6 @@
 	 * fragment. The default value is {@link #EXTENSION_FRAMEWORK framework}.
 	 * 
 	 * @see #EXTENSION_FRAMEWORK
-	 * @see #EXTENSION_BOOTCLASSPATH
 	 */
 	public final static String	REQUIREMENT_EXTENSION_DIRECTIVE				= "extension";
 
@@ -153,6 +152,7 @@
 	 * loaded by the boot class loader.
 	 * 
 	 * @see #REQUIREMENT_EXTENSION_DIRECTIVE
+	 * @deprecated As of 1.2.
 	 */
 	public final static String	EXTENSION_BOOTCLASSPATH						= "bootclasspath";
 
diff --git a/framework/src/main/java/org/osgi/framework/namespace/IdentityNamespace.java b/framework/src/main/java/org/osgi/framework/namespace/IdentityNamespace.java
index cfbb843..3ef3f11 100644
--- a/framework/src/main/java/org/osgi/framework/namespace/IdentityNamespace.java
+++ b/framework/src/main/java/org/osgi/framework/namespace/IdentityNamespace.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2012, 2013). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2012, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@
  * capability.
  * 
  * @Immutable
- * @author $Id: 7bc7a11c45b30538ffbb7572c4539f6160557684 $
+ * @author $Id: d2357fa05b1e68f952710e07a2fab46bf4ea3cec $
  */
 public final class IdentityNamespace extends Namespace {
 
@@ -105,6 +105,16 @@
 	public static final String	TYPE_UNKNOWN						= "unknown";
 
 	/**
+	 * The attribute value that contains tags for the resource. A tag is used to
+	 * identify an aspect of the resource that is not otherwise expressed by the
+	 * capabilities of the resource. The value of this attribute must be of type
+	 * {@code List<String>}.
+	 * 
+	 * @since 1.2
+	 */
+	public static final String	CAPABILITY_TAGS_ATTRIBUTE			= "tags";
+
+	/**
 	 * The capability attribute that contains a human readable copyright notice
 	 * for the resource. See the {@code Bundle-Copyright} manifest header.
 	 */
diff --git a/framework/src/main/java/org/osgi/framework/namespace/NativeNamespace.java b/framework/src/main/java/org/osgi/framework/namespace/NativeNamespace.java
index fc91420..4376082 100644
--- a/framework/src/main/java/org/osgi/framework/namespace/NativeNamespace.java
+++ b/framework/src/main/java/org/osgi/framework/namespace/NativeNamespace.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2012, 2016). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2012, 2020). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
  * 
  * @Immutable
  * @since 1.1
- * @author $Id$
+ * @author $Id: f252fb2850001968fe0ee4e741a66847560dce33 $
  */
 public final class NativeNamespace extends Namespace {
 
@@ -44,7 +44,7 @@
 	 * The capability attribute contains alias values of the
 	 * {@link Constants#FRAMEWORK_OS_NAME org.osgi.framework.os.name} launching
 	 * property value according to the
-	 * <a href="https://www.osgi.org/developer/specifications/reference/">OSGi
+	 * <a href="https://docs.osgi.org/reference/">OSGi
 	 * Specification References</a>. The value of this attribute must be of type
 	 * {@code List<String>}.
 	 */
@@ -62,7 +62,7 @@
 	 * The capability attribute contains alias values of the
 	 * {@link Constants#FRAMEWORK_PROCESSOR org.osgi.framework.processor}
 	 * launching property value according to the
-	 * <a href="https://www.osgi.org/developer/specifications/reference/">OSGi
+	 * <a href="https://docs.osgi.org/reference/">OSGi
 	 * Specification References</a>. The value of this attribute must be of type
 	 * {@code List<String>}.
 	 */
diff --git a/framework/src/main/java/org/osgi/framework/namespace/package-info.java b/framework/src/main/java/org/osgi/framework/namespace/package-info.java
index e89f34a..78fec59 100644
--- a/framework/src/main/java/org/osgi/framework/namespace/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/namespace/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2012, 2013). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2012, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,17 +15,17 @@
  */
 
 /**
- * Namespace Package Version 1.1.
+ * Namespace Package Version 1.2.
  * 
  * <p>
  * Bundles should not need to import this package at runtime since all
  * the types in this package just contain constants for capability and 
  * requirement namespaces specified by the OSGi Alliance.
  * 
- * @author $Id$
+ * @author $Id: 43f3804d39ad166dc082926b3a3679be67962fcc $
  */
 
-@Version("1.1")
+@Version("1.2")
 package org.osgi.framework.namespace;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/framework/src/main/java/org/osgi/framework/package-info.java b/framework/src/main/java/org/osgi/framework/package-info.java
index 818d4ae..44ad83a 100644
--- a/framework/src/main/java/org/osgi/framework/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) OSGi Alliance (2010, 2016). All Rights Reserved.
+ * Copyright (c) OSGi Alliance (2010, 2019). All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,19 +15,19 @@
  */
 
 /**
- * Framework Package Version 1.9.
+ * Framework Package Version 1.10.
  * <p>
  * Bundles wishing to use this package must list the package in the
  * Import-Package header of the bundle's manifest.
  * <p>
  * Example import for consumers using the API in this package:
  * <p>
- * {@code  Import-Package: org.osgi.framework; version="[1.9,2.0)"}
+ * {@code  Import-Package: org.osgi.framework; version="[1.10,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: 5b7956509a6fcf8b3f7ad23ca343dcae27cf37c6 $
  */
 
-@Version("1.9")
+@Version("1.10")
 package org.osgi.framework;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/framework/src/main/java/org/osgi/framework/startlevel/dto/BundleStartLevelDTO.java b/framework/src/main/java/org/osgi/framework/startlevel/dto/BundleStartLevelDTO.java
index 81430e2..c36575b 100644
--- a/framework/src/main/java/org/osgi/framework/startlevel/dto/BundleStartLevelDTO.java
+++ b/framework/src/main/java/org/osgi/framework/startlevel/dto/BundleStartLevelDTO.java
@@ -26,7 +26,7 @@
  * An installed Bundle can be adapted to provide a {@code BundleStartLevelDTO}
  * for the Bundle.
  * 
- * @author $Id$
+ * @author $Id: 81430e24483d75fd406e57214284ec43b4b3f6a7 $
  * @NotThreadSafe
  */
 public class BundleStartLevelDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/framework/startlevel/dto/FrameworkStartLevelDTO.java b/framework/src/main/java/org/osgi/framework/startlevel/dto/FrameworkStartLevelDTO.java
index 2d1de40..cf9ff88 100644
--- a/framework/src/main/java/org/osgi/framework/startlevel/dto/FrameworkStartLevelDTO.java
+++ b/framework/src/main/java/org/osgi/framework/startlevel/dto/FrameworkStartLevelDTO.java
@@ -26,7 +26,7 @@
  * The System Bundle can be adapted to provide a {@code FrameworkStartLevelDTO}
  * for the framework of the Bundle.
  * 
- * @author $Id$
+ * @author $Id: 2d1de40ba2b1d12832ebe0e5cdd4f789d439b6f6 $
  * @NotThreadSafe
  */
 public class FrameworkStartLevelDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/framework/startlevel/dto/package-info.java b/framework/src/main/java/org/osgi/framework/startlevel/dto/package-info.java
index 62262c5..cef11d9 100644
--- a/framework/src/main/java/org/osgi/framework/startlevel/dto/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/startlevel/dto/package-info.java
@@ -32,7 +32,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.startlevel.dto; version="[1.0,1.1)"}
  * 
- * @author $Id$
+ * @author $Id: 62262c52e6446fbe4a7b29725881f01791396c29 $
  */
 
 @Version("1.0")
diff --git a/framework/src/main/java/org/osgi/framework/startlevel/package-info.java b/framework/src/main/java/org/osgi/framework/startlevel/package-info.java
index 27d775b..3265948 100644
--- a/framework/src/main/java/org/osgi/framework/startlevel/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/startlevel/package-info.java
@@ -73,7 +73,7 @@
  * Import-Package: org.osgi.framework.startlevel; version=&quot;[1.0,2.0)&quot;
  * </pre>
  * 
- * @author $Id$
+ * @author $Id: 27d775bbe53df5e1dd5d7fe6f5e1c95b294c2301 $
  */
 
 @Version("1.0")
diff --git a/framework/src/main/java/org/osgi/framework/wiring/dto/BundleRevisionDTO.java b/framework/src/main/java/org/osgi/framework/wiring/dto/BundleRevisionDTO.java
index 43d7416..1358b2a 100644
--- a/framework/src/main/java/org/osgi/framework/wiring/dto/BundleRevisionDTO.java
+++ b/framework/src/main/java/org/osgi/framework/wiring/dto/BundleRevisionDTO.java
@@ -28,7 +28,7 @@
  * in use revisions of the Bundle can be obtained by adapting the bundle to
  * {@code BundleRevisionDTO[]}.
  * 
- * @author $Id$
+ * @author $Id: 43d74167b8b59468b148ceb9dcde4509b68c2a90 $
  * @NotThreadSafe
  */
 public class BundleRevisionDTO extends ResourceDTO {
diff --git a/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWireDTO.java b/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWireDTO.java
index ef923b4..b55e634 100644
--- a/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWireDTO.java
+++ b/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWireDTO.java
@@ -26,7 +26,7 @@
  * <p>
  * {@code BundleWireDTO}s are referenced {@link BundleWiringDTO.NodeDTO}s.
  * 
- * @author $Id$
+ * @author $Id: ef923b41460de6d3e34fad73dc794a788533c1e9 $
  * @NotThreadSafe
  */
 public class BundleWireDTO extends WireDTO {
diff --git a/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWiringDTO.java b/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWiringDTO.java
index e23e80f..a95c185 100644
--- a/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWiringDTO.java
+++ b/framework/src/main/java/org/osgi/framework/wiring/dto/BundleWiringDTO.java
@@ -30,7 +30,7 @@
  * wirings of the Bundle can be obtained by adapting the bundle to
  * {@code BundleWiringDTO[]}.
  * 
- * @author $Id$
+ * @author $Id: e23e80fcf8a14cbc9f565ec146821a4f22f2731b $
  * @NotThreadSafe
  */
 public class BundleWiringDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/framework/wiring/dto/package-info.java b/framework/src/main/java/org/osgi/framework/wiring/dto/package-info.java
index 47eb1ee..6a41ae5 100644
--- a/framework/src/main/java/org/osgi/framework/wiring/dto/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/wiring/dto/package-info.java
@@ -30,7 +30,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.framework.wiring.dto; version="[1.3,1.4)"}
  * 
- * @author $Id$
+ * @author $Id: 47eb1eed801ba77d58d8807aa85e9e000bca00d0 $
  */
 
 @Version("1.3")
diff --git a/framework/src/main/java/org/osgi/framework/wiring/package-info.java b/framework/src/main/java/org/osgi/framework/wiring/package-info.java
index 6fb7232..199d60c 100644
--- a/framework/src/main/java/org/osgi/framework/wiring/package-info.java
+++ b/framework/src/main/java/org/osgi/framework/wiring/package-info.java
@@ -25,7 +25,7 @@
  * Import-Package: org.osgi.framework.wiring; version=&quot;[1.2,2.0)&quot;
  * </pre>
  * 
- * @author $Id$
+ * @author $Id: 6fb7232ca6c7b389c20f75566a46c4022b8dd5d5 $
  */
 
 @Version("1.2")
diff --git a/framework/src/main/java/org/osgi/resource/dto/CapabilityDTO.java b/framework/src/main/java/org/osgi/resource/dto/CapabilityDTO.java
index 1f7fad1..941ec8f 100644
--- a/framework/src/main/java/org/osgi/resource/dto/CapabilityDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/CapabilityDTO.java
@@ -23,7 +23,7 @@
 /**
  * Data Transfer Object for a Capability.
  * 
- * @author $Id$
+ * @author $Id: CapabilityDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class CapabilityDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/CapabilityRefDTO.java b/framework/src/main/java/org/osgi/resource/dto/CapabilityRefDTO.java
index 81d5b85..4a94459 100644
--- a/framework/src/main/java/org/osgi/resource/dto/CapabilityRefDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/CapabilityRefDTO.java
@@ -21,7 +21,7 @@
 /**
  * Data Transfer Object for a reference to a Capability.
  * 
- * @author $Id$
+ * @author $Id: CapabilityRefDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class CapabilityRefDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/RequirementDTO.java b/framework/src/main/java/org/osgi/resource/dto/RequirementDTO.java
index dfa21db..d8e02a0 100644
--- a/framework/src/main/java/org/osgi/resource/dto/RequirementDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/RequirementDTO.java
@@ -23,7 +23,7 @@
 /**
  * Data Transfer Object for a Requirement.
  * 
- * @author $Id$
+ * @author $Id: RequirementDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class RequirementDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/RequirementRefDTO.java b/framework/src/main/java/org/osgi/resource/dto/RequirementRefDTO.java
index 8f913a7..9f8c316 100644
--- a/framework/src/main/java/org/osgi/resource/dto/RequirementRefDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/RequirementRefDTO.java
@@ -21,7 +21,7 @@
 /**
  * Data Transfer Object for a reference to a Requirement.
  * 
- * @author $Id$
+ * @author $Id: RequirementRefDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class RequirementRefDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/ResourceDTO.java b/framework/src/main/java/org/osgi/resource/dto/ResourceDTO.java
index 377b7af..5e35165 100644
--- a/framework/src/main/java/org/osgi/resource/dto/ResourceDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/ResourceDTO.java
@@ -23,7 +23,7 @@
 /**
  * Data Transfer Object for a Resource.
  * 
- * @author $Id$
+ * @author $Id: ResourceDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class ResourceDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/WireDTO.java b/framework/src/main/java/org/osgi/resource/dto/WireDTO.java
index 017ea7c..00b61d8 100644
--- a/framework/src/main/java/org/osgi/resource/dto/WireDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/WireDTO.java
@@ -22,7 +22,7 @@
 /**
  * Data Transfer Object for a Wire.
  * 
- * @author $Id$
+ * @author $Id: WireDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class WireDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/WiringDTO.java b/framework/src/main/java/org/osgi/resource/dto/WiringDTO.java
index 8dc3844..4484658 100644
--- a/framework/src/main/java/org/osgi/resource/dto/WiringDTO.java
+++ b/framework/src/main/java/org/osgi/resource/dto/WiringDTO.java
@@ -23,7 +23,7 @@
 /**
  * Data Transfer Object for a Wiring node.
  * 
- * @author $Id$
+ * @author $Id: WiringDTO.java 1614569 2014-07-30 07:22:32Z cziegeler $
  * @NotThreadSafe
  */
 public class WiringDTO extends DTO {
diff --git a/framework/src/main/java/org/osgi/resource/dto/package-info.java b/framework/src/main/java/org/osgi/resource/dto/package-info.java
index 6e316cd..de4e2c9 100644
--- a/framework/src/main/java/org/osgi/resource/dto/package-info.java
+++ b/framework/src/main/java/org/osgi/resource/dto/package-info.java
@@ -32,7 +32,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.resource.dto; version="[1.0,1.1)"}
  * 
- * @author $Id$
+ * @author $Id: package-info.java 1614569 2014-07-30 07:22:32Z cziegeler $
  */
 
 @Version("1.0")
diff --git a/framework/src/main/java/org/osgi/resource/package-info.java b/framework/src/main/java/org/osgi/resource/package-info.java
index c537ca9..c0349ff 100644
--- a/framework/src/main/java/org/osgi/resource/package-info.java
+++ b/framework/src/main/java/org/osgi/resource/package-info.java
@@ -25,7 +25,7 @@
  * Import-Package: org.osgi.resource; version=&quot;[1.0,2.0)&quot;
  * </pre>
  * 
- * @author $Id$
+ * @author $Id: package-info.java 1614569 2014-07-30 07:22:32Z cziegeler $
  */
 
 @Version("1.0")
diff --git a/framework/src/main/java/org/osgi/service/condition/Condition.java b/framework/src/main/java/org/osgi/service/condition/Condition.java
new file mode 100755
index 0000000..fcd6da8
--- /dev/null
+++ b/framework/src/main/java/org/osgi/service/condition/Condition.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) OSGi Alliance (2020). All Rights Reserved.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.condition;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Condition Service interface.
+ * <p>
+ * In dynamic systems, such as OSGi, one of the more challenging problems can be
+ * to define when a system or part of it is ready to do work. The answer can
+ * change depending on the individual perspective. The developer of a web server
+ * might say, the system is ready when the server starts listening on port 80.
+ * An application developer however would define the system as ready when the
+ * database connection is up and all servlets are registered. Taking the
+ * application developers view, the web server should start listening on port 80
+ * when the application is ready and not beforehand.
+ * <p>
+ * The {@code Condition} service interface is a marker interface designed to
+ * address this issue. Its role is to provide a dependency that can be tracked.
+ * It acts as a defined signal to other services.
+ * <p>
+ * A {@code Condition} service must be registered with the
+ * {@link Condition#CONDITION_ID} service property.
+ * 
+ * @ThreadSafe
+ * @author $Id: 9736e5e1c38c45254f733d73ed7ae2c0e253f544 $
+ */
+@ConsumerType
+public interface Condition {
+
+	/**
+	 * Service property identifying a condition's unique identifier.
+	 * <p>
+	 * Since a {@code Condition} service can potentially describe more then one
+	 * condition, the type of this service property is {@code String+}.
+	 */
+	String		CONDITION_ID		= "osgi.condition.id";
+
+	/**
+	 * The unique identifier for the default True condition.
+	 * <p>
+	 * The default True condition is registered by the framework during
+	 * framework initialization and therefore can always be relied upon.
+	 * 
+	 * @see Condition#CONDITION_ID
+	 */
+	String		CONDITION_ID_TRUE	= "true";
+
+	/**
+	 * A condition instance that can be used to register {@code Condition}
+	 * services.
+	 * <p>
+	 * This can be helpful to avoid a bundle having to implement this interface
+	 * to register a {@code Condition} service
+	 */
+	Condition	INSTANCE			= new ConditionImpl();
+}
+
+final class ConditionImpl implements Condition {
+	ConditionImpl() {
+	}
+}
diff --git a/framework/src/main/java/org/osgi/service/condition/package-info.java b/framework/src/main/java/org/osgi/service/condition/package-info.java
new file mode 100755
index 0000000..a3ddce5
--- /dev/null
+++ b/framework/src/main/java/org/osgi/service/condition/package-info.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) OSGi Alliance (2020). All Rights Reserved.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Condition Service Package Version 1.0.
+ * <p>
+ * Bundles wishing to use this package must list the package in the
+ * Import-Package header of the bundle's manifest. This package has two types of
+ * users: the consumers that use the API in this package and the providers that
+ * implement the API in this package.
+ * <p>
+ * Example import for consumers using the API in this package:
+ * <p>
+ * {@code  Import-Package: org.osgi.service.condition; version="[1.0,2.0)"}
+ * <p>
+ * Example import for providers implementing the API in this package:
+ * <p>
+ * {@code  Import-Package: org.osgi.service.condition; version="[1.0,1.1)"}
+ * 
+ * @author $Id: 52df94eac922bd9d4f2f680cdd98e6d405844958 $
+ */
+
+@Version("1.0")
+package org.osgi.service.condition;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/framework/src/main/java/org/osgi/service/packageadmin/package-info.java b/framework/src/main/java/org/osgi/service/packageadmin/package-info.java
index 41bb8d3..1baf3bd 100644
--- a/framework/src/main/java/org/osgi/service/packageadmin/package-info.java
+++ b/framework/src/main/java/org/osgi/service/packageadmin/package-info.java
@@ -31,7 +31,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.service.packageadmin; version="[1.2,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: package-info.java 1825132 2018-02-23 15:11:00Z pauls $
  */
 
 @Version("1.2")
diff --git a/framework/src/main/java/org/osgi/service/startlevel/package-info.java b/framework/src/main/java/org/osgi/service/startlevel/package-info.java
index 70b62bd..c0954f1 100644
--- a/framework/src/main/java/org/osgi/service/startlevel/package-info.java
+++ b/framework/src/main/java/org/osgi/service/startlevel/package-info.java
@@ -31,7 +31,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.service.startlevel; version="[1.1,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: package-info.java 1825132 2018-02-23 15:11:00Z pauls $
  */
 
 @Version("1.1")
diff --git a/framework/src/main/java/org/osgi/service/url/package-info.java b/framework/src/main/java/org/osgi/service/url/package-info.java
index 7053546..3e73e2b 100644
--- a/framework/src/main/java/org/osgi/service/url/package-info.java
+++ b/framework/src/main/java/org/osgi/service/url/package-info.java
@@ -26,7 +26,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.service.url; version="[1.0,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: package-info.java 1614569 2014-07-30 07:22:32Z cziegeler $
  */
 
 @Version("1.0")
diff --git a/framework/src/main/java/org/osgi/util/tracker/package-info.java b/framework/src/main/java/org/osgi/util/tracker/package-info.java
index b72e066..b0007b8 100644
--- a/framework/src/main/java/org/osgi/util/tracker/package-info.java
+++ b/framework/src/main/java/org/osgi/util/tracker/package-info.java
@@ -26,7 +26,7 @@
  * <p>
  * {@code  Import-Package: org.osgi.util.tracker; version="[1.5,2.0)"}
  * 
- * @author $Id$
+ * @author $Id: package-info.java 1825132 2018-02-23 15:11:00Z pauls $
  */
 
 @Version("1.5.2")
diff --git a/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/native-image.properties b/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/native-image.properties
new file mode 100644
index 0000000..85eb4e1
--- /dev/null
+++ b/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/native-image.properties
@@ -0,0 +1,5 @@
+Args = --initialize-at-build-time=\
+org.apache.felix.framework.util.Util,\
+org.apache.felix.framework.util.SecureAction,\
+org.apache.felix.framework.BundleWiringImpl,\
+org.apache.felix.framework.BundleRevisionImpl
diff --git a/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/reflect-config.json b/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/reflect-config.json
new file mode 100644
index 0000000..b1bc8ad
--- /dev/null
+++ b/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/reflect-config.json
@@ -0,0 +1,27 @@
+[
+  {
+    "name":"org.osgi.framework.Version",
+    "allPublicConstructors" : true,
+    "allPublicMethods" : true
+  },
+  {
+    "name":"org.osgi.framework.VersionRange",
+    "allPublicConstructors" : true,
+    "allPublicMethods" : true
+  },
+  {
+    "name" : "java.net.URL",
+    "allPublicConstructors" : true,
+    "allPublicMethods" : true,
+    "allDeclaredMethods" : true,
+    "allPublicFields" : true,
+    "allDeclaredFields" : true
+  },
+  {
+    "name" : "java.net.URLStreamHandler",
+    "allPublicMethods" : true,
+    "allDeclaredMethods" : true,
+    "allPublicFields" : true,
+    "allDeclaredFields" : true
+  }
+]
diff --git a/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/resource-config.json b/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/resource-config.json
new file mode 100644
index 0000000..cfeeac1
--- /dev/null
+++ b/framework/src/main/resources/META-INF/native-image/org.apache.felix/org.apache.felix.framework/resource-config.json
@@ -0,0 +1,9 @@
+{
+  "resources":[
+    {"pattern":"META-INF/services/org.osgi.framework.connect.ConnectFrameworkFactory"},
+    {"pattern":"META-INF/services/org.osgi.framework.launch.FrameworkFactory"},
+    {"pattern":"default.properties"},
+    {"pattern":"org/apache/felix/framework/Felix.properties"},
+    {"pattern":"org/apache/felix/framework/util/accessor.bytes"}
+  ]
+}
diff --git a/framework/src/main/resources/META-INF/services/org.osgi.framework.connect.ConnectFrameworkFactory b/framework/src/main/resources/META-INF/services/org.osgi.framework.connect.ConnectFrameworkFactory
new file mode 100644
index 0000000..3708ae5
--- /dev/null
+++ b/framework/src/main/resources/META-INF/services/org.osgi.framework.connect.ConnectFrameworkFactory
@@ -0,0 +1 @@
+org.apache.felix.framework.FrameworkFactory
\ No newline at end of file
diff --git a/framework/src/main/resources/default.properties b/framework/src/main/resources/default.properties
index d66362f..792bfdb 100644
--- a/framework/src/main/resources/default.properties
+++ b/framework/src/main/resources/default.properties
@@ -21,7 +21,7 @@
 
 # New-style generic execution environment capabilities.
 org.osgi.framework.system.capabilities= \
- ${dollar}{felix.service.caps} ${dollar}{eecap-${dollar}{java.specification.version}} ${dollar}{eecap-${dollar}{felix.detect.jpms}}
+ ${dollar}{felix.service.caps} ${dollar}{eecap-${dollar}{felix.detect.java.specification.version}} ${dollar}{eecap-${dollar}{felix.detect.jpms}}
  
 # Native Processor Aliases.  Format is felix.native.processor.alias.<normalized Processor Name>=alias1,alias2
 felix.native.processor.alias.68k=
@@ -88,7 +88,7 @@
 
 # Deprecated old-style execution environment properties.
 org.osgi.framework.executionenvironment= \
- ${dollar}{ee-${dollar}{java.specification.version}} ${dollar}{ee-${dollar}{felix.detect.jpms}}
+ ${dollar}{ee-${dollar}{felix.detect.java.specification.version}} ${dollar}{ee-${dollar}{felix.detect.jpms}}
 
 ee-1.8=JavaSE-1.8,JavaSE-1.7,JavaSE-1.6,J2SE-1.5,J2SE-1.4,J2SE-1.3, \
  J2SE-1.2,JRE-1.1,JRE-1.0,OSGi/Minimum-1.2,OSGi/Minimum-1.1, \
@@ -101,14 +101,20 @@
 
 # Default packages exported by system bundle.
 org.osgi.framework.system.packages=\
- org.osgi.framework;version="1.9", \
+ ${dollar}{osgi-exports} \
+ ${dollar}{jre-${dollar}{felix.detect.java.specification.version}} \
+ ${dollar}{jre-${dollar}{felix.detect.jpms}}
+
+osgi-exports= \
+ org.osgi.framework;version="1.10", \
+ org.osgi.framework.connect;version="1.0.0", \
  org.osgi.framework.dto;version="1.8";uses:="org.osgi.dto", \
  org.osgi.framework.hooks.bundle;version="1.1";uses:="org.osgi.framework", \
  org.osgi.framework.hooks.resolver;version="1.0";uses:="org.osgi.framework.wiring", \
  org.osgi.framework.hooks.service;version="1.1";uses:="org.osgi.framework", \
  org.osgi.framework.hooks.weaving;version="1.1";uses:="org.osgi.framework.wiring", \
  org.osgi.framework.launch;version="1.2";uses:="org.osgi.framework", \
- org.osgi.framework.namespace;version="1.1";uses:="org.osgi.resource", \
+ org.osgi.framework.namespace;version="1.2";uses:="org.osgi.resource", \
  org.osgi.framework.startlevel;version="1.0";uses:="org.osgi.framework", \
  org.osgi.framework.startlevel.dto;version="1.0";uses:="org.osgi.dto", \
  org.osgi.framework.wiring;version="1.2";uses:="org.osgi.framework,org.osgi.resource", \
@@ -120,9 +126,8 @@
  org.osgi.service.url;version="1.0", \
  org.osgi.service.resolver;version="1.1";uses:="org.osgi.resource", \
  org.osgi.util.tracker;version="1.5.2";uses:="org.osgi.framework", \
- org.osgi.dto;version="1.1" \
- ${dollar}{jre-${dollar}{felix.detect.java.specification.version}} \
- ${dollar}{jre-${dollar}{felix.detect.jpms}}
+ org.osgi.dto;version="1.1", \
+ org.osgi.service.condition;version="1.0"
 
 #
 # Java platform package export properties.
@@ -375,4 +380,4 @@
  java.util.function;version="${dollar}{felix.detect.java.version}", \
  java.util.stream;version="${dollar}{felix.detect.java.version}", \
  javax.crypto;uses:="javax.crypto.spec,javax.security.auth";version="${dollar}{felix.detect.java.version}", \
- javax.lang.model;uses:="javax.lang.model.element";version="${dollar}{felix.detect.java.version}"
\ No newline at end of file
+ javax.lang.model;uses:="javax.lang.model.element";version="${dollar}{felix.detect.java.version}"
diff --git a/framework/src/main/resources/org/apache/felix/framework/util/accessor.bytes b/framework/src/main/resources/org/apache/felix/framework/util/accessor.bytes
new file mode 100644
index 0000000..f395846
--- /dev/null
+++ b/framework/src/main/resources/org/apache/felix/framework/util/accessor.bytes
Binary files differ
diff --git a/framework/src/main/resources/org/apache/felix/framework/util/accessor.src b/framework/src/main/resources/org/apache/felix/framework/util/accessor.src
new file mode 100644
index 0000000..2fe3e07
--- /dev/null
+++ b/framework/src/main/resources/org/apache/felix/framework/util/accessor.src
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package java.net;
+
+import java.lang.reflect.AccessibleObject;
+import java.util.function.Consumer;
+
+public class Accessor implements Consumer<AccessibleObject[]>
+{
+    @Override
+    public void accept(AccessibleObject[] accessibleObjectStream)
+    {
+        AccessibleObject.setAccessible(accessibleObjectStream, true);
+    }
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java b/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java
index a04fe4a..0861de8 100644
--- a/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java
@@ -214,7 +214,7 @@
 
         try
         {
-            new BundleImpl(felixMock, null, archive);
+            new BundleImpl(felixMock, new BundleImpl(), archive);
             fail("Should have thrown a BundleException because the collision hook is not enabled");
         }
         catch (BundleException be)
@@ -290,7 +290,7 @@
 
         try
         {
-            new BundleImpl(felixMock, null, archive);
+            new BundleImpl(felixMock, new BundleImpl(), archive);
             fail("Should have thrown a BundleException because the installed bundle is not unique");
         }
         catch (BundleException be)
diff --git a/framework/src/test/java/org/apache/felix/framework/ConnectTest.java b/framework/src/test/java/org/apache/felix/framework/ConnectTest.java
new file mode 100644
index 0000000..0a6c3f0
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/ConnectTest.java
@@ -0,0 +1,369 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+
+import junit.framework.TestCase;
+import org.apache.felix.framework.util.StringMap;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.Version;
+import org.osgi.framework.connect.ConnectContent;
+import org.osgi.framework.connect.ModuleConnector;
+import org.osgi.framework.connect.ConnectModule;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+public class ConnectTest extends TestCase
+{
+    public void testSimpleConnect() throws Exception
+    {
+        File cacheDir = File.createTempFile("felix-cache", ".dir");
+        cacheDir.delete();
+        cacheDir.mkdirs();
+        String cache = cacheDir.getPath();
+
+        Map<String, String> params = new HashMap<String, String>();
+        params.put("felix.cache.profiledir", cache);
+        params.put("felix.cache.dir", cache);
+        params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+
+        FrameworkFactory factory = new FrameworkFactory();
+        Framework framework = null;
+        try
+        {
+            final AtomicReference<String> version = new AtomicReference<String>("1.0.0");
+            ModuleConnector connectFactory = new ModuleConnector()
+            {
+                @Override
+                public void initialize(File storage, Map<String, String> configuration)
+                {
+
+                }
+
+                @Override
+                public Optional<ConnectModule> connect(String location) throws IllegalStateException
+                {
+                    return location.startsWith("connect:foo") ? Optional.of(new ConnectModule()
+                    {
+                        @Override
+                        public ConnectContent getContent() throws IOException
+                        {
+                            return new ConnectContent()
+                            {
+                                @Override
+                                public Optional<ConnectEntry> getEntry(String name)
+                                {
+
+                                    return "foo.txt".equals(name) ? Optional.of(
+                                        new ConnectEntry()
+                                        {
+                                            @Override
+                                            public String getName()
+                                            {
+                                                return name;
+                                            }
+
+                                            @Override
+                                            public long getContentLength()
+                                            {
+                                                return 0;
+                                            }
+
+                                            @Override
+                                            public long getLastModified()
+                                            {
+                                                return 0;
+                                            }
+
+                                            @Override
+                                            public InputStream getInputStream() throws IOException
+                                            {
+                                                return null;
+                                            }
+                                        }
+                                    ) : Optional.empty();
+                                }
+
+                                @Override
+                                public Iterable<String> getEntries()
+                                {
+                                    return Arrays.asList("foo.txt");
+                                }
+
+                                @Override
+                                public Optional<ClassLoader> getClassLoader()
+                                {
+                                    return Optional.of(getClass().getClassLoader());
+                                }
+
+                                @Override
+                                public void open() throws IOException
+                                {
+                                }
+
+                                @Override
+                                public void close() throws IOException
+                                {
+                                }
+
+                                @Override
+                                public Optional<Map<String, String>> getHeaders()
+                                {
+                                    Map<String, String> headers = new HashMap<String, String>();
+                                    headers.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+                                    headers.put(Constants.BUNDLE_SYMBOLICNAME, "connect.foo");
+                                    headers.put(Constants.BUNDLE_VERSION, version.get());
+                                    headers.put(Constants.EXPORT_PACKAGE, ConnectTest.class.getPackage().getName());
+                                    return Optional.of(headers);
+                                }
+                            };
+                        }
+
+
+                    }) : location.startsWith("connect:extension") ? Optional.of(new ConnectModule()
+                    {
+                        @Override
+                        public ConnectContent getContent() throws IOException
+                        {
+                            return new ConnectContent()
+                            {
+                                @Override
+                                public Optional<ConnectEntry> getEntry(String name)
+                                {
+
+                                    return "foo.txt".equals(name) ? Optional.of(
+                                        new ConnectEntry()
+                                        {
+                                            @Override
+                                            public String getName()
+                                            {
+                                                return name;
+                                            }
+
+                                            @Override
+                                            public long getContentLength()
+                                            {
+                                                return 0;
+                                            }
+
+                                            @Override
+                                            public long getLastModified()
+                                            {
+                                                return 0;
+                                            }
+
+                                            @Override
+                                            public InputStream getInputStream() throws IOException
+                                            {
+                                                return null;
+                                            }
+                                        }
+                                    ) : Optional.empty();
+                                }
+
+                                @Override
+                                public Iterable<String> getEntries()
+                                {
+                                    return Arrays.asList("foo.txt");
+                                }
+
+                                @Override
+                                public Optional<ClassLoader> getClassLoader()
+                                {
+                                    return Optional.of(getClass().getClassLoader());
+                                }
+
+                                @Override
+                                public void open() throws IOException
+                                {
+
+                                }
+
+                                @Override
+                                public void close() throws IOException
+                                {
+
+                                }
+
+                                @Override
+                                public Optional<Map<String, String>> getHeaders()
+                                {
+                                    Map<String, String> headers = new HashMap<String, String>();
+                                    headers.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+                                    headers.put(Constants.BUNDLE_SYMBOLICNAME, "connect.extension");
+                                    headers.put(Constants.BUNDLE_VERSION, "1.0.0");
+                                    headers.put(Constants.FRAGMENT_HOST, "system.bundle;extension:=framework");
+                                    return Optional.of(headers);
+                                }
+                            };
+                        }
+
+
+                    })
+                        : Optional.empty();
+                }
+
+                @Override
+                public Optional<BundleActivator> newBundleActivator()
+                {
+                    return Optional.empty();
+                }
+            };
+
+            framework = factory.newFramework(params, connectFactory);
+
+            framework.start();
+            Bundle b = framework.getBundleContext().installBundle("connect:foo");
+
+            TestCase.assertNotNull(b);
+            TestCase.assertEquals("connect.foo", b.getSymbolicName());
+            TestCase.assertEquals(b, framework.getBundleContext().getBundle("connect:foo"));
+
+            TestCase.assertNotNull(b.getEntry("foo.txt"));
+            TestCase.assertNull(b.getEntry("bar.txt"));
+
+            Bundle extension = framework.getBundleContext().installBundle("connect:extension");
+
+            TestCase.assertEquals(Bundle.RESOLVED, extension.getState());
+
+            framework.stop();
+            framework.waitForStop(1000);
+            framework = factory.newFramework(params, connectFactory);
+            framework.start();
+
+            b = framework.getBundleContext().getBundle("connect:foo");
+            assertNotNull(b);
+            TestCase.assertEquals("connect.foo", b.getSymbolicName());
+            TestCase.assertNotNull(b.getEntry("foo.txt"));
+            TestCase.assertNull(b.getEntry("bar.txt"));
+
+            TestCase.assertEquals(ConnectTest.class, b.loadClass(ConnectTest.class.getName()));
+
+            String mf = "Bundle-SymbolicName: connect.test\n"
+                + "Bundle-Version: 1.0.0\n"
+                + "Bundle-ManifestVersion: 2\n"
+                + "Import-Package: " + ConnectTest.class.getPackage().getName()
+                + "\n";
+
+            File bundleFile = createBundle(mf, cacheDir, StringMap.class);
+
+            Bundle b2 = framework.getBundleContext().installBundle(bundleFile.toURI().toURL().toString());
+            b2.start();
+            TestCase.assertEquals(Bundle.ACTIVE, b2.getState());
+            TestCase.assertEquals(ConnectTest.class, b2.loadClass(ConnectTest.class.getName()));
+            TestCase.assertNotSame(StringMap.class, b2.loadClass(StringMap.class.getName()));
+            TestCase.assertEquals(Version.parseVersion("1.0.0"), b.getVersion());
+            Version revVersion = b.adapt(BundleRevision.class).getVersion();
+
+            TestCase.assertEquals(b.adapt(BundleRevision.class),
+                b2.adapt(BundleWiring.class).getRequiredWires(PackageNamespace.PACKAGE_NAMESPACE).get(0).getProvider());
+
+            version.set("2.0.0");
+
+            b.update();
+
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            framework.adapt(FrameworkWiring.class).refreshBundles(Arrays.asList(b), new FrameworkListener()
+            {
+                @Override
+                public void frameworkEvent(FrameworkEvent event)
+                {
+                    latch.countDown();
+                }
+            });
+
+            latch.await(1, TimeUnit.SECONDS);
+
+
+            TestCase.assertEquals(Version.parseVersion("2.0.0"), b.getVersion());
+
+            TestCase.assertEquals(Bundle.ACTIVE, b2.getState());
+            TestCase.assertEquals(ConnectTest.class, b2.loadClass(ConnectTest.class.getName()));
+            TestCase.assertNotSame(StringMap.class, b2.loadClass(StringMap.class.getName()));
+            TestCase.assertEquals(b.adapt(BundleRevision.class),
+                b2.adapt(BundleWiring.class).getRequiredWires(PackageNamespace.PACKAGE_NAMESPACE).get(0).getProvider());
+            TestCase.assertNotSame(revVersion, b2.adapt(BundleWiring.class).getRequiredWires(PackageNamespace.PACKAGE_NAMESPACE).get(0).getProvider());
+
+        }
+        finally
+        {
+            try
+            {
+                if (framework != null)
+                {
+                    framework.stop();
+                    framework.waitForStop(1000);
+                }
+            }
+            finally {
+                MultiReleaseVersionTest.deleteDir(cacheDir);
+            }
+        }
+    }
+
+    private static File createBundle(String manifest, File tempDir, Class... classes) throws IOException
+    {
+        File f = File.createTempFile("felix-bundle", ".jar", tempDir);
+
+        Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("utf-8")));
+        mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+
+        JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf);
+
+        for (Class c : classes)
+        {
+            String path = c.getName().replace('.', '/') + ".class";
+            os.putNextEntry(new ZipEntry(path));
+
+            InputStream is = c.getClassLoader().getResourceAsStream(path);
+            byte[] b = new byte[is.available()];
+            is.read(b);
+            is.close();
+            os.write(b);
+        }
+
+        os.close();
+        return f;
+    }
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/LaunchTest.java b/framework/src/test/java/org/apache/felix/framework/LaunchTest.java
new file mode 100644
index 0000000..fcb6295
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/LaunchTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework;
+
+import junit.framework.TestCase;
+import org.apache.felix.framework.util.FelixConstants;
+import org.osgi.framework.*;
+import org.osgi.framework.connect.ConnectModule;
+import org.osgi.framework.connect.ModuleConnector;
+import org.osgi.framework.launch.Framework;
+import org.osgi.service.resolver.Resolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class LaunchTest extends TestCase
+{
+    public void testInit() throws Exception
+    {
+        Map params = new HashMap();
+        File cacheDir = File.createTempFile("felix-cache", ".dir");
+        cacheDir.delete();
+        cacheDir.mkdirs();
+        String cache = cacheDir.getPath();
+        params.put("felix.cache.profiledir", cache);
+        params.put("felix.cache.dir", cache);
+        params.put(FelixConstants.LOG_LOGGER_PROP, new Logger()
+        {
+            @Override
+            protected void doLogOut(int level, String s, Throwable throwable) {
+
+            }
+        });
+        params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+        try
+        {
+            Framework f = new Felix(params, new ModuleConnector()
+            {
+                boolean first = true;
+                @Override
+                public void initialize(File storage, Map<String, String> configuration)
+                {
+                    if (first) {
+                        first = false;
+                        throw new IllegalStateException("Test");
+                    }
+                }
+
+                @Override
+                public Optional<ConnectModule> connect(String location)
+                {
+                    return Optional.empty();
+                }
+
+                @Override
+                public Optional<BundleActivator> newBundleActivator()
+                {
+                    return Optional.empty();
+                }
+            }){
+                boolean first = true;
+                @Override
+                synchronized BundleActivator getActivator()
+                {
+                    BundleActivator activator = super.getActivator();
+                    if (first) {
+                        first = false;
+                        return new BundleActivator() {
+                            @Override
+                            public void start(BundleContext context) throws Exception
+                            {
+                                activator.start(context);
+                                throw new IllegalStateException("TEst");
+                            }
+
+                            @Override
+                            public void stop(BundleContext context) throws Exception
+                            {
+                                activator.stop(context);
+                            }
+                        };
+                    }
+                    return super.getActivator();
+                }
+            };
+            try
+            {
+                f.init();
+                fail("Excepted init to fail");
+            }
+            catch (Exception ex)
+            {
+
+            }
+            assertEquals(Bundle.INSTALLED, f.getState());
+            try
+            {
+                f.init();
+                fail("Excepted init to fail");
+            }
+            catch (Exception ex)
+            {
+
+            }
+            assertEquals(Bundle.INSTALLED, f.getState());
+            f.init();
+            assertEquals(Bundle.STARTING, f.getState());
+            f.stop();
+            f.waitForStop(0);
+            assertEquals(Bundle.RESOLVED, f.getState());
+            f.init();
+            assertEquals(1,f.getBundleContext().getServiceReferences(Resolver.class, null).size());
+            assertEquals(Bundle.STARTING, f.getState());
+            f.start();
+            assertEquals(Bundle.ACTIVE, f.getState());
+            f.stop();
+            f.waitForStop(0);
+        }
+        finally
+        {
+            deleteDir(cacheDir);
+        }
+    }
+
+    private static void deleteDir(File root) throws IOException
+    {
+        if (root.isDirectory())
+        {
+            for (File file : root.listFiles())
+            {
+                deleteDir(file);
+            }
+        }
+        assertTrue(root.delete());
+    }
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/MultiReleaseVersionTest.java b/framework/src/test/java/org/apache/felix/framework/MultiReleaseVersionTest.java
index 27ba50f..be40789 100644
--- a/framework/src/test/java/org/apache/felix/framework/MultiReleaseVersionTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/MultiReleaseVersionTest.java
@@ -122,7 +122,7 @@
         return f;
     }
 
-    private static void deleteDir(File root) throws IOException
+    public static void deleteDir(File root) throws IOException
     {
         if (root.isDirectory())
         {
diff --git a/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
index ce1167c..a89e430 100644
--- a/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/ResourceLoadingTest.java
@@ -35,7 +35,6 @@
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.launch.Framework;
-import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWiring;
 
 public class ResourceLoadingTest extends TestCase
@@ -104,24 +103,33 @@
         assertNotNull(testBundle.getResource(name));
         assertNotNull(testBundle.getEntry(name));
 
-        BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.getResource(name).openStream()));
-        assertEquals("This is a Test", reader.readLine());
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.getResource(name).openStream())))
+        {
+            assertEquals("This is a Test", reader.readLine());
+        }
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.getEntry(name).openStream())))
+        {
+            assertEquals("This is a Test", reader.readLine());
+        }
 
-        reader = new BufferedReader(new InputStreamReader(testBundle.getEntry(name).openStream()));
-        assertEquals("This is a Test", reader.readLine());
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResourceAsStream(name))))
+        {
+            assertEquals("This is a Test", reader.readLine());
+        }
 
-        reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResourceAsStream(name)));
-        assertEquals("This is a Test", reader.readLine());
-
-        reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name).openStream()));
-        assertEquals("This is a Test", reader.readLine());
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name).openStream())))
+        {
+            assertEquals("This is a Test", reader.readLine());
+        }
 
         URL url = testBundle.adapt(BundleWiring.class).getClassLoader().getResource(name);
 
         URL testURL = new URL(url.getProtocol() + "://" +  url.getHost() + ":" +  url.getPort() + "/" + name);
 
-        reader = new BufferedReader(new InputStreamReader(testURL.openStream()));
-        assertEquals("This is a Test", reader.readLine());
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(testURL.openStream())))
+        {
+            assertEquals("This is a Test", reader.readLine());
+        }
     }
 
     private static void deleteDir(File root) throws IOException
diff --git a/framework/src/test/java/org/apache/felix/framework/cache/BundleCacheTest.java b/framework/src/test/java/org/apache/felix/framework/cache/BundleCacheTest.java
index fc83cb1..5dbfb74 100644
--- a/framework/src/test/java/org/apache/felix/framework/cache/BundleCacheTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/cache/BundleCacheTest.java
@@ -114,15 +114,15 @@
 
         output.close();
 
-        BundleArchive archive = cache.create(1, 1, "slip", new FileInputStream(bundle));
+        BundleArchive archive = cache.create(1, 1, "slip", new FileInputStream(bundle), null);
 
         testNoZipSlip(archive);
 
-        archive = cache.create(1, 1, bundle.toURI().toURL().toString(), null);
+        archive = cache.create(1, 1, bundle.toURI().toURL().toString(), null, null);
 
         testNoZipSlip(archive);
 
-        archive = cache.create(1, 1, "reference:" + bundle.toURI().toURL().toString(), null);
+        archive = cache.create(1, 1, "reference:" + bundle.toURI().toURL().toString(), null, null);
 
         testNoZipSlip(archive);
 
@@ -134,7 +134,7 @@
         test.createNewFile();
         test.deleteOnExit();
 
-        archive = cache.create(1, 1, "reference:" + dir.toURI().toURL().toString(), null);
+        archive = cache.create(1, 1, "reference:" + dir.toURI().toURL().toString(), null, null);
 
         testNoZipSlip(archive);
     }
@@ -172,7 +172,7 @@
 
     private void testBundle(String location, File file) throws Exception
     {
-        BundleArchive archive = cache.create(1, 1, location, file != null ? new FileInputStream(file) : null);
+        BundleArchive archive = cache.create(1, 1, location, file != null ? new FileInputStream(file) : null, null);
 
         assertNotNull(archive);
 
diff --git a/framework/src/test/java/org/apache/felix/framework/util/manifestparser/ManifestParserTest.java b/framework/src/test/java/org/apache/felix/framework/util/manifestparser/ManifestParserTest.java
index c95e021..93db1ce 100644
--- a/framework/src/test/java/org/apache/felix/framework/util/manifestparser/ManifestParserTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/util/manifestparser/ManifestParserTest.java
@@ -28,6 +28,9 @@
 import java.util.List;
 import java.util.Map;
 
+import org.apache.felix.framework.BundleRevisionImpl;
+import org.apache.felix.framework.cache.ConnectContentContent;
+import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.util.FelixConstants;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
@@ -35,6 +38,7 @@
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.Version;
+import org.osgi.framework.connect.ConnectContent;
 import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.framework.namespace.NativeNamespace;
 import org.osgi.framework.wiring.BundleCapability;
@@ -62,7 +66,7 @@
     {
         Map<String, Object> headers = new HashMap<String, Object>();
         headers.put(Constants.BUNDLE_MANIFESTVERSION, "2");
-        headers.put(Constants.BUNDLE_SYMBOLICNAME, "abc;singleton:=true");
+        headers.put(Constants.BUNDLE_SYMBOLICNAME, "abc;singleton:=true;foo=bar;" + IdentityNamespace.CAPABILITY_TAGS_ATTRIBUTE + "=test");
         headers.put(Constants.BUNDLE_VERSION, "1.2.3.something");
         String copyright = "(c) 2013 Apache Software Foundation";
         headers.put(Constants.BUNDLE_COPYRIGHT, copyright);
@@ -72,7 +76,13 @@
         headers.put(Constants.BUNDLE_DOCURL, docurl);
         String license = "http://www.apache.org/licenses/LICENSE-2.0";
         headers.put("Bundle-License", license);
-        ManifestParser mp = new ManifestParser(null, null, null, headers);
+
+        BundleRevisionImpl mockBundleRevision = mock(BundleRevisionImpl.class);
+
+        Content connectContent = mock(ConnectContentContent.class);
+        when(mockBundleRevision.getContent()).thenReturn(connectContent);
+
+        ManifestParser mp = new ManifestParser(null, null, mockBundleRevision, headers);
 
         BundleCapability ic = findCapability(mp.getCapabilities(), IdentityNamespace.IDENTITY_NAMESPACE);
         assertEquals("abc", ic.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE));
@@ -82,6 +92,8 @@
         assertEquals(description, ic.getAttributes().get(IdentityNamespace.CAPABILITY_DESCRIPTION_ATTRIBUTE));
         assertEquals(docurl, ic.getAttributes().get(IdentityNamespace.CAPABILITY_DOCUMENTATION_ATTRIBUTE));
         assertEquals(license, ic.getAttributes().get(IdentityNamespace.CAPABILITY_LICENSE_ATTRIBUTE));
+        assertEquals(Arrays.asList("test", ConnectContent.TAG_OSGI_CONNECT), ic.getAttributes().get(IdentityNamespace.CAPABILITY_TAGS_ATTRIBUTE));
+        assertEquals("bar", ic.getAttributes().get("foo"));
 
         assertEquals(1, ic.getDirectives().size());
         assertEquals("true", ic.getDirectives().get(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE));
@@ -97,7 +109,10 @@
         		"osgi.native.osversion:Version=\"7.0\";"+
         		"osgi.native.processor:List<String>=\"x86-64,amd64,em64t,x86_64\";"+
         		"osgi.native.language=\"en\"");
-        BundleRevision mockBundleRevision = mock(BundleRevision.class);
+
+        BundleRevisionImpl mockBundleRevision = mock(BundleRevisionImpl.class);
+
+        when(mockBundleRevision.getContent()).thenReturn(null);
         when(mockBundleRevision.getSymbolicName()).thenReturn(FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME);
     	
         ManifestParser mp = new ManifestParser(null, null, mockBundleRevision, headers);
@@ -123,7 +138,9 @@
         headers.put(Constants.REQUIRE_CAPABILITY,
                 "com.example.other;theList:List<String>=\"one,two,three\";theLong:Long=999");
 
-        BundleRevision mockBundleRevision = mock(BundleRevision.class);
+        BundleRevisionImpl mockBundleRevision = mock(BundleRevisionImpl.class);
+
+        when(mockBundleRevision.getContent()).thenReturn(null);
 
         when(mockBundleRevision.getSymbolicName()).thenReturn("com.example.test.sample");
 
@@ -154,7 +171,10 @@
         String[] languages = {"en", "se"};
         String selectionFilter = "(com.acme.windowing=win32)";
         NativeLibraryClause clause = new NativeLibraryClause(libraryFiles, osNames, processors, osVersions, languages, selectionFilter);
-        BundleRevision owner = mock(BundleRevision.class);
+
+        BundleRevisionImpl owner = mock(BundleRevisionImpl.class);
+
+        when(owner.getContent()).thenReturn(null);
         nativeLibraryClauses.add(clause);
         
         List<BundleRequirement> nativeBundleReq = ManifestParser.convertNativeCode(owner, nativeLibraryClauses, false);