Initial source commit.


git-svn-id: https://svn.apache.org/repos/asf/incubator/oscar/trunk@233031 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..21d3452
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,118 @@
+<project name="felix" default="all" basedir=".">
+
+    <!-- Set our global properties -->
+    <property name="src.dir" value="src"/>
+    <property name="lib.dir" value="lib"/>
+    <property name="output.dir" value="classes"/>
+    <property name="bundle.dir" value="bundle"/>
+    <property name="etc.dir" value="etc"/>
+    <property name="doc.dir" value="doc"/>
+    <property name="apidoc.dir" value="${doc.dir}/api"/>
+    <property name="dist.dir" value="dist"/>
+    <property name="debug.value" value="on"/>
+
+    <!-- Create class path from lib and output directories. -->
+    <path id="classpath">
+        <pathelement location="${output.dir}"/>
+        <fileset dir="${lib.dir}">
+            <include name="*.jar"/>
+        </fileset>
+    </path>
+
+    <target name="init">
+        <!-- Create lib directory. -->
+        <mkdir dir="${lib.dir}"/>
+        <!-- Create output directory. -->
+        <mkdir dir="${output.dir}"/>
+        <!-- Create bundle directory. -->
+        <mkdir dir="${bundle.dir}"/>
+    </target>
+
+    <!-- Compile and JAR everything -->
+    <target name="all" depends="init">
+        <antcall target="compile"/>
+        <antcall target="moduleloader"/>
+        <antcall target="osgi"/>
+        <antcall target="felix"/>
+        <antcall target="bundle"/>
+    </target>
+
+    <!-- Compile everything. -->
+    <target name="compile" depends="init">
+        <javac srcdir="${src.dir}" destdir="${output.dir}"
+         debug="${debug.value}" verbose="no" deprecation="no">
+            <classpath refid="classpath"/>
+            <include name="**/*.java"/>
+        </javac>
+    </target>
+
+    <!-- Create module loader JAR file. -->
+    <target name="moduleloader" depends="compile">
+        <jar jarfile="${lib.dir}/moduleloader.jar"
+            basedir="${output.dir}">
+            <include name="org/apache/osgi/moduleloader/"/>
+        </jar>
+    </target>
+
+    <!-- Create OSGi JAR file. -->
+    <target name="osgi" depends="compile">
+        <jar jarfile="${lib.dir}/osgi.jar" basedir="${output.dir}">
+            <include name="org/osgi/framework/**"/>
+            <include name="org/osgi/service/packageadmin/**"/>
+            <include name="org/osgi/service/startlevel/**"/>
+        </jar>
+    </target>
+
+    <!-- Create Felix JAR file. -->
+    <target name="felix" depends="compile">
+        <jar manifest="${src.dir}/org/apache/osgi/framework/manifest.mf"
+            jarfile="${lib.dir}/felix.jar"
+            basedir="${output.dir}">
+            <include name="org/apache/osgi/framework/"/>
+            <exclude name="org/apache/osgi/framework/installer/"/>
+        </jar>
+    </target>
+
+    <!-- Create shell bundle JAR files. -->
+    <target name="bundle" depends="compile">
+
+        <!-- Shell -->
+        <jar manifest="${src.dir}/org/apache/osgi/bundle/shell/manifest.mf"
+            jarfile="${bundle.dir}/shell.jar"
+            basedir="${output.dir}">
+            <include name="org/apache/osgi/service/shell/**"/>
+            <include name="org/ungoverned/osgi/service/shell/**"/>
+            <include name="org/apache/osgi/bundle/shell/**"/>
+        </jar>
+
+        <!-- Shell TUI -->
+        <jar manifest="${src.dir}/org/apache/osgi/bundle/shelltui/manifest.mf"
+            jarfile="${bundle.dir}/shelltui.jar"
+            basedir="${output.dir}">
+            <include name="org/apache/osgi/bundle/shelltui/**"/>
+        </jar>
+
+        <!-- OBR -->
+        <copy file="${lib.dir}/kxml.jar"
+         todir="${output.dir}/org/apache/osgi/bundle/bundlerepository/"/>
+
+        <jar manifest="${src.dir}/org/apache/osgi/bundle/bundlerepository/manifest.mf"
+            jarfile="${bundle.dir}/bundlerepository.jar"
+            basedir="${output.dir}">
+            <include name="org/apache/osgi/bundle/bundlerepository/**"/>
+            <include name="org/apache/osgi/service/bundlerepository/**"/>
+        </jar>
+
+    </target>
+
+
+    <!-- Clean up everything. -->
+    <target name="clean">
+        <delete dir="${output.dir}"/>
+        <delete dir="${bundle.dir}"/>
+        <delete file="${lib.dir}/osgi.jar"/>
+        <delete file="${lib.dir}/moduleloader.jar"/>
+        <delete file="${lib.dir}/felix.jar"/>
+    </target>
+
+</project>
diff --git a/etc/all.policy b/etc/all.policy
new file mode 100644
index 0000000..dc85222
--- /dev/null
+++ b/etc/all.policy
@@ -0,0 +1,3 @@
+grant {
+  permission java.security.AllPermission;
+};
diff --git a/etc/config.properties.gui b/etc/config.properties.gui
new file mode 100644
index 0000000..fb62d12
--- /dev/null
+++ b/etc/config.properties.gui
@@ -0,0 +1,4 @@
+felix.auto.start.1=file:bundle/shell.jar file:bundle/tablelayout.jar \
+    file:bundle/shellgui.jar file:bundle/shellplugin.jar \
+    file:bundle/bundlerepository.jar
+
diff --git a/etc/config.properties.text b/etc/config.properties.text
new file mode 100644
index 0000000..0486a4d
--- /dev/null
+++ b/etc/config.properties.text
@@ -0,0 +1,3 @@
+felix.auto.start.1=file:bundle/shell.jar file:bundle/shelltui.jar \
+    file:bundle/bundlerepository.jar
+
diff --git a/etc/example.policy b/etc/example.policy
new file mode 100644
index 0000000..961318a
--- /dev/null
+++ b/etc/example.policy
@@ -0,0 +1,40 @@
+grant codeBase "file:bundle/shellgui.jar" {
+  permission org.osgi.framework.ServicePermission "org.ungoverned.osgi.bundle.shellgui.Plugin", "get";
+};
+
+grant codeBase "file:bundle/shelltui.jar" {
+  permission org.osgi.framework.ServicePermission "org.ungoverned.osgi.service.shell.ShellService", "get";
+};
+
+grant codeBase "file:bundle/shellplugin.jar" {
+  permission java.security.AllPermission;
+};
+
+grant codeBase "file:bundle/bundlerepository.jar" {
+  permission java.security.AllPermission;
+};
+
+grant codeBase "file:bundle/permissionmanager.jar" {
+  permission java.security.AllPermission;
+};
+
+grant codeBase "file:bundle/shell.jar" {
+  permission java.security.AllPermission;
+};
+
+grant codeBase "file:${lib.dir}/felix.jar" {
+  permission java.security.AllPermission;
+};
+
+grant codeBase "file:${lib.dir}/osgi.jar" {
+  permission java.security.AllPermission;
+};
+
+grant codeBase "file:${lib.dir}/moduleloader.jar" {
+  permission java.security.AllPermission;
+};
+
+grant {
+  permission java.io.FilePermission "${user.home}${file.separator}.felix${file.separator}-", "read, write, delete";
+  permission org.osgi.framework.PackagePermission "*", "EXPORT";
+};
diff --git a/etc/felix.bat b/etc/felix.bat
new file mode 100644
index 0000000..ed82503
--- /dev/null
+++ b/etc/felix.bat
@@ -0,0 +1,16 @@
+@echo off

+

+rem

+rem The following 'set' command should be automatically

+rem assigned during install, if not, edit it to reflect

+rem your Java installation.

+rem

+

+set JAVA_HOME="%%Java directory%%"

+

+rem

+rem You do not need to edit the following.

+rem

+

+%JAVA_HOME%\bin\java -jar lib\felix.jar

+

diff --git a/etc/felix.sh b/etc/felix.sh
new file mode 100644
index 0000000..9ec3e09
--- /dev/null
+++ b/etc/felix.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+#
+# The following variable should be automatically
+# assigned during install, if not, edit it to reflect
+# your Java installation.
+#
+
+java_home=%%Java directory%%
+
+#
+# You don't need to edit the following line
+#
+
+exec ${java_home}/bin/java -jar lib/felix.jar
diff --git a/lib/config.properties b/lib/config.properties
new file mode 100644
index 0000000..91b4fac
--- /dev/null
+++ b/lib/config.properties
@@ -0,0 +1,26 @@
+#
+# Framework config properties.
+#
+org.osgi.framework.system.packages=javax.swing; \
+ javax.swing.plaf; \
+ javax.swing.event; \
+ javax.swing.table; \
+ javax.swing.border; \
+ javax.swing.tree; \
+ javax.swing.text; \
+ version=1.4
+#felix.cache.profile=foo
+felix.auto.start.1= \
+ file:bundle/shell.jar \
+ file:bundle/shelltui.jar \
+ file:bundle/bundlerepository.jar 
+felix.startlevel.framework=1
+felix.startlevel.bundle=1
+
+#
+# Bundle config properties.
+#
+org.osgi.service.http.port=8080
+osgi.shell.telnet=on
+#osgi.repository.url=file:/home/rickhall/projects/apache/repository.xml
+#osgi.repository.url=file:/home/rickhall/projects/apache/repository.xml http://www.knopflerfish.org/repo/repository.xml
diff --git a/lib/kxml.jar b/lib/kxml.jar
new file mode 100644
index 0000000..e1fc6db
--- /dev/null
+++ b/lib/kxml.jar
Binary files differ
diff --git a/src/org/apache/osgi/bundle/bundlerepository/Activator.java b/src/org/apache/osgi/bundle/bundlerepository/Activator.java
new file mode 100644
index 0000000..c50192e
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/Activator.java
@@ -0,0 +1,57 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator
+{
+    private transient BundleContext m_context = null;
+    private transient BundleRepositoryImpl m_br = null;
+
+    public void start(BundleContext context)
+    {
+        m_context = context;
+
+        // Register bundle repository service.
+        m_br = new BundleRepositoryImpl(m_context);
+        context.registerService(
+            org.apache.osgi.service.bundlerepository.BundleRepository.class.getName(),
+            m_br, null);
+
+        // We dynamically import the shell service API, so it
+        // might not actually be available, so be ready to catch
+        // the exception when we try to register the command service.
+        try
+        {
+            // Register "obr" shell command service as a
+            // wrapper for the bundle repository service.
+            context.registerService(
+                org.apache.osgi.service.shell.Command.class.getName(),
+                new ObrCommandImpl(m_context, m_br), null);
+        }
+        catch (Throwable th)
+        {
+            // Ignore.
+        }
+    }
+
+    public void stop(BundleContext context)
+    {
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java b/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java
new file mode 100644
index 0000000..07f5828
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java
@@ -0,0 +1,274 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.io.PrintStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.osgi.service.bundlerepository.*;
+import org.osgi.framework.*;
+
+public class BundleRepositoryImpl implements BundleRepository
+{
+    private BundleContext m_context = null;
+    private RepositoryState m_repo = null;
+
+    public BundleRepositoryImpl(BundleContext context)
+    {
+        m_context = context;
+        m_repo = new RepositoryState(m_context);
+    }
+
+    public String[] getRepositoryURLs()
+    {
+        return m_repo.getURLs();
+    }
+
+    public synchronized void setRepositoryURLs(String[] urls)
+    {
+        m_repo.setURLs(urls);
+    }
+
+    /**
+     * Get the number of bundles available in the repository.
+     * @return the number of available bundles.
+    **/
+    public synchronized BundleRecord[] getBundleRecords()
+    {
+        return m_repo.getRecords();
+    }
+
+    /**
+     * Get the specified bundle record from the repository.
+     * @param i the bundle record index to retrieve.
+     * @return the associated bundle record or <tt>null</tt>.
+    **/
+    public synchronized BundleRecord[] getBundleRecords(String symName)
+    {
+        return m_repo.getRecords(symName);
+    }
+
+    /**
+     * Get bundle record for the bundle with the specified name
+     * and version from the repository.
+     * @param name the bundle record name to retrieve.
+     * @param version three-interger array of the version associated with
+     *        the name to retrieve.
+     * @return the associated bundle record or <tt>null</tt>.
+    **/
+    public synchronized BundleRecord getBundleRecord(String symName, int[] version)
+    {
+        return m_repo.getRecord(symName, version);
+    }
+
+    public boolean deployBundle(
+        PrintStream out, PrintStream err, String symName, int[] version,
+        boolean isResolve, boolean isStart)
+    {
+        // List to hold bundles that need to be started.
+        List startList = null;
+
+        // Get the bundle record of the remote bundle to be deployed.
+        BundleRecord targetRecord = m_repo.getRecord(symName, version);
+        if (targetRecord == null)
+        {
+            err.println("No such bundle in repository.");
+            return false;
+        }
+
+        // Create an editable snapshot of the current set of
+        // locally installed bundles.
+        LocalState localState = new LocalState(m_context);
+
+        // If the precise bundle is already deployed, then we are done.
+        if (localState.findBundle(symName, version) != null)
+        {
+            return true;
+        }
+
+        // Create the transitive closure all bundles that must be
+        // deployed as a result of deploying the target bundle;
+        // use a list because this will keep everything in order.
+        BundleRecord[] deployRecords = null;
+        // If the resolve flag is set, then get its imports to
+        // calculate the transitive closure of its dependencies.
+        if (isResolve)
+        {
+//            Package[] imports = (Package[])
+//                targetRecord.getAttribute(BundleRecord.IMPORT_PACKAGE);
+            Filter[] reqs = (Filter[])
+                targetRecord.getAttribute("requirement");
+            try
+            {
+                deployRecords = m_repo.resolvePackages(localState, reqs);
+            }
+            catch (ResolveException ex)
+            {
+                err.println("Resolve error: " + ex.getPackage());
+                return false;
+            }
+        }
+
+        // Add the target bundle since it will not be
+        // included in the array of records to deploy.
+        if (deployRecords == null)
+        {
+            deployRecords = new BundleRecord[] { targetRecord };
+        }
+        else
+        {
+            // Create a new array containing the target and put it first,
+            // since the array will be process in reverse order.
+            BundleRecord[] newRecs = new BundleRecord[deployRecords.length + 1];
+            newRecs[0] = targetRecord;
+            System.arraycopy(deployRecords, 0, newRecs, 1, deployRecords.length);
+            deployRecords = newRecs;
+        }
+
+        // Now iterate through all bundles in the deploy list
+        // in reverse order and deploy each; the order is not
+        // so important, but by reversing them at least the
+        // dependencies will be printed first and perhaps it
+        // will avoid some ordering issues when we are starting
+        // bundles.
+        for (int i = 0; i < deployRecords.length; i++)
+        {
+            LocalState.LocalBundleRecord updateRecord =
+                localState.findUpdatableBundle(deployRecords[i]);
+            if (updateRecord != null)
+            {
+// TODO: Should check to make sure that update bundle isn't already the
+// correct version.
+                // Modify our copy of the local state to reflect
+                // that the bundle is now updated.
+                localState.update(updateRecord, deployRecords[i]);
+
+                // Print out an "updating" message.
+                if (deployRecords[i] != targetRecord)
+                {
+                    out.print("Updating dependency: ");
+                }
+                else
+                {
+                    out.print("Updating: ");
+                }
+                out.println(Util.getBundleName(updateRecord.getBundle()));
+
+                // Actually perform the update.
+                try
+                {
+                    URL url = new URL(
+                        (String) deployRecords[i].getAttribute(BundleRecord.BUNDLE_URL));
+                    updateRecord.getBundle().update(url.openStream());
+
+                    // If necessary, save the updated bundle to be
+                    // started later.
+                    if (isStart)
+                    {
+                        if (startList == null)
+                        {
+                            startList = new ArrayList();
+                        }
+                        startList.add(updateRecord.getBundle());
+                    }
+                }
+                catch (Exception ex)
+                {
+                    err.println("Update error: " + Util.getBundleName(updateRecord.getBundle()));
+                    ex.printStackTrace(err);
+                    return false;
+                }
+            }
+            else
+            {
+                // Print out an "installing" message.
+                if (deployRecords[i] != targetRecord)
+                {
+                    out.print("Installing dependency: ");
+                }
+                else
+                {
+                    out.print("Installing: ");
+                }
+                out.println(deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME));
+
+                try
+                {
+                    // Actually perform the install, but do not use the actual
+                    // bundle JAR URL for the bundle location, since this will
+                    // limit OBR's ability to manipulate bundle versions. Instead,
+                    // use a unique timestamp as the bundle location.
+                    URL url = new URL(
+                        (String) deployRecords[i].getAttribute(BundleRecord.BUNDLE_URL));
+                    Bundle bundle = m_context.installBundle(
+                        "obr://"
+                        + deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME)
+                        + "/" + System.currentTimeMillis(),
+                        url.openStream());
+
+                    // If necessary, save the installed bundle to be
+                    // started later.
+                    if (isStart)
+                    {
+                        if (startList == null)
+                        {
+                            startList = new ArrayList();
+                        }
+                        startList.add(bundle);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    err.println("Install error: "
+                        + deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME));
+                    ex.printStackTrace(err);
+                    return false;
+                }
+            }
+        }
+
+        // If necessary, start bundles after installing them all.
+        if (isStart)
+        {
+            for (int i = 0; (startList != null) && (i < startList.size()); i++)
+            {
+                Bundle bundle = (Bundle) startList.get(i);
+                try
+                {
+                    bundle.start();
+                }
+                catch (BundleException ex)
+                {
+                    err.println("Update error: " + Util.getBundleName(bundle));
+                    ex.printStackTrace();
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public BundleRecord[] resolvePackages(IPackage[] pkgs)
+        throws ResolveException
+    {
+// TODO: FIX
+//        return m_repo.resolvePackages(new LocalState(m_context), pkgs);
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java b/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java
new file mode 100644
index 0000000..9f27a0d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java
@@ -0,0 +1,168 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+public class FileUtil
+{
+    public static void downloadSource(
+        PrintStream out, PrintStream err,
+        String srcURL, String dirStr, boolean extract)
+    {
+        // Get the file name from the URL.
+        String fileName = (srcURL.lastIndexOf('/') > 0)
+            ? srcURL.substring(srcURL.lastIndexOf('/') + 1)
+            : srcURL;
+
+        try
+        {
+            out.println("Connecting...");
+
+            File dir = new File(dirStr);
+            if (!dir.exists())
+            {
+                err.println("Destination directory does not exist.");
+            }
+            File file = new File(dir, fileName);
+
+            OutputStream os = new FileOutputStream(file);
+            URLConnection conn = new URL(srcURL).openConnection();
+            int total = conn.getContentLength();
+            InputStream is = conn.getInputStream();
+
+            if (total > 0)
+            {
+                out.println("Downloading " + fileName
+                    + " ( " + total + " bytes ).");
+            }
+            else
+            {
+                out.println("Downloading " + fileName + ".");
+            }
+            byte[] buffer = new byte[4096];
+            int count = 0;
+            for (int len = is.read(buffer); len > 0; len = is.read(buffer))
+            {
+                count += len;
+                os.write(buffer, 0, len);
+            }
+
+            os.close();
+            is.close();
+
+            if (extract)
+            {
+                is = new FileInputStream(file);
+                JarInputStream jis = new JarInputStream(is);
+                out.println("Extracting...");
+                unjar(jis, dir);
+                jis.close();
+                file.delete();
+            }
+        }
+        catch (Exception ex)
+        {
+            err.println(ex);
+        }
+    }
+
+    public static void unjar(JarInputStream jis, File dir)
+        throws IOException
+    {
+        // Reusable buffer.
+        byte[] buffer = new byte[4096];
+
+        // Loop through JAR entries.
+        for (JarEntry je = jis.getNextJarEntry();
+             je != null;
+             je = jis.getNextJarEntry())
+        {
+            if (je.getName().startsWith("/"))
+            {
+                throw new IOException("JAR resource cannot contain absolute paths.");
+            }
+
+            File target = new File(dir, je.getName());
+
+            // Check to see if the JAR entry is a directory.
+            if (je.isDirectory())
+            {
+                if (!target.exists())
+                {
+                    if (!target.mkdirs())
+                    {
+                        throw new IOException("Unable to create target directory: "
+                            + target);
+                    }
+                }
+                // Just continue since directories do not have content to copy.
+                continue;
+            }
+
+            int lastIndex = je.getName().lastIndexOf('/');
+            String name = (lastIndex >= 0) ?
+                je.getName().substring(lastIndex + 1) : je.getName();
+            String destination = (lastIndex >= 0) ?
+                je.getName().substring(0, lastIndex) : "";
+
+            // JAR files use '/', so convert it to platform separator.
+            destination = destination.replace('/', File.separatorChar);
+            copy(jis, dir, name, destination, buffer);
+        }
+    }
+
+    public static void copy(
+        InputStream is, File dir, String destName, String destDir, byte[] buffer)
+        throws IOException
+    {
+        if (destDir == null)
+        {
+            destDir = "";
+        }
+
+        // Make sure the target directory exists and
+        // that is actually a directory.
+        File targetDir = new File(dir, destDir);
+        if (!targetDir.exists())
+        {
+            if (!targetDir.mkdirs())
+            {
+                throw new IOException("Unable to create target directory: "
+                    + targetDir);
+            }
+        }
+        else if (!targetDir.isDirectory())
+        {
+            throw new IOException("Target is not a directory: "
+                + targetDir);
+        }
+
+        BufferedOutputStream bos = new BufferedOutputStream(
+            new FileOutputStream(new File(targetDir, destName)));
+        int count = 0;
+        while ((count = is.read(buffer)) > 0)
+        {
+            bos.write(buffer, 0, count);
+        }
+        bos.close();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java b/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java
new file mode 100644
index 0000000..edd749f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java
@@ -0,0 +1,44 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+    private Iterator m_iter = null;
+
+    public IteratorToEnumeration(Iterator iter)
+    {
+        m_iter = iter;
+    }
+
+    public boolean hasMoreElements()
+    {
+        if (m_iter == null)
+            return false;
+        return m_iter.hasNext();
+    }
+
+    public Object nextElement()
+    {
+        if (m_iter == null)
+            return null;
+        return m_iter.next();
+    }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/LocalState.java b/src/org/apache/osgi/bundle/bundlerepository/LocalState.java
new file mode 100644
index 0000000..6c91952
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/LocalState.java
@@ -0,0 +1,378 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.IPackage;
+import org.osgi.framework.*;
+
+public class LocalState
+{
+    private BundleContext m_context = null;
+    private List m_localRecordList = new ArrayList();
+
+    public LocalState(BundleContext context)
+    {
+        m_context = context;
+        initialize();
+    }
+
+    public BundleRecord findBundle(String symName, int[] version)
+    {
+        for (int i = 0; i < m_localRecordList.size(); i++)
+        {
+            BundleRecord brLocal = (BundleRecord) m_localRecordList.get(i);
+            String localSymName = (String)
+                brLocal.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+            int[] localVersion = Util.parseVersionString((String)
+                brLocal.getAttribute(BundleRecord.BUNDLE_VERSION));
+            if ((localSymName != null) &&
+                localSymName.equals(symName) &&
+                (Util.compareVersion(localVersion, version) == 0))
+            {
+                return brLocal;
+            }
+        }
+        return null;
+    }
+
+    public BundleRecord[] findBundles(String symName)
+    {
+        List matchList = new ArrayList();
+        for (int i = 0; i < m_localRecordList.size(); i++)
+        {
+            BundleRecord brLocal = (BundleRecord) m_localRecordList.get(i);
+            String localSymName = (String)
+                brLocal.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+            if ((localSymName != null) && localSymName.equals(symName))
+            {
+                matchList.add(brLocal);
+            }
+        }
+        return (BundleRecord[]) matchList.toArray(new BundleRecord[matchList.size()]);
+    }
+
+    public void update(BundleRecord oldRecord, BundleRecord newRecord)
+    {
+        // To update the old record we need to replace it with
+        // a new one, since BundleRecords are immutable. Make
+        // a new record that contains the attributes of the new
+        // record, but is associated with the local bundle of
+        // the old record.
+        if (oldRecord instanceof LocalBundleRecord)
+        {
+            String[] keys = newRecord.getAttributes();
+            Map map = new HashMap();
+            for (int i = 0; i < keys.length; i++)
+            {
+                map.put(keys, newRecord.getAttribute(keys[i]));
+            }
+            BundleRecord updatedRecord =
+                new LocalBundleRecord(
+                    map, ((LocalBundleRecord) oldRecord).getBundle());
+            int idx = m_localRecordList.indexOf(oldRecord);
+            if (idx >= 0)
+            {
+                m_localRecordList.set(idx, updatedRecord);
+            }
+        }
+    }
+
+    public LocalBundleRecord findUpdatableBundle(BundleRecord record)
+    {
+        // Determine if any bundles with the specified symbolic
+        // name are already installed locally.
+        BundleRecord[] localRecords = findBundles(
+            (String)record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME));
+        if (localRecords != null)
+        {
+            // Since there are local bundles with the same symbolic
+            // name installed, then we must determine if we can
+            // update an existing bundle or if we must install
+            // another one. Loop through all local bundles with same
+            // symbolic name and find the first one that can be updated
+            // without breaking constraints of existing bundles.
+            for (int i = 0; i < localRecords.length; i++)
+            {
+                // Check to make sure that the version of the target
+                // record is greater than the local bundle version,
+                // since we do not want to perform a downgrade.
+//                int[] vLocal = Util.parseVersionString((String)
+//                    localRecords[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+//                int[] vTarget = Util.parseVersionString((String)
+//                    record.getAttribute(BundleRecord.BUNDLE_VERSION));
+// TODO: VERIFY WHAT IS GOING ON HERE.
+                // If the target bundle is a newer version and it is
+                // export compatible with the local bundle, then return it.
+                if (isUpdatable(localRecords[i], record))
+                {
+                    return (LocalBundleRecord) localRecords[i];
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean isUpdatable(BundleRecord oldVersion, BundleRecord newVersion)
+    {
+        // First get all of the potentially resolvable package declarations
+        // from the local bundles for the old version of the bundle.
+        Filter[] reqFilters = getResolvableImportDeclarations(oldVersion);
+        if (reqFilters == null)
+        {
+            return true;
+        }
+        // Now make sure that all of the resolvable import declarations
+        // for the old version of the bundle  can also be satisfied by
+        // the new version of the bundle.
+        Map[] capMaps = (Map[])
+            newVersion.getAttribute("capability");
+        if (capMaps == null)
+        {
+            return false;
+        }
+        MapToDictionary mapDict = new MapToDictionary(null);
+        for (int reqIdx = 0; reqIdx < reqFilters.length; reqIdx++)
+        {
+            boolean satisfied = false;
+            for (int capIdx = 0; !satisfied && (capIdx < capMaps.length); capIdx++)
+            {
+                mapDict.setSourceMap(capMaps[capIdx]);
+                if (reqFilters[reqIdx].match(mapDict))
+                {
+                    satisfied = true;
+                }
+            }
+
+            // If any of the previously resolvable package declarations
+            // cannot be resolved, then the bundle is not updatable.
+            if (!satisfied)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    public Filter[] getResolvableImportDeclarations(BundleRecord record)
+    {
+        Map[] capMaps = (Map[])
+            record.getAttribute("capability");
+        if ((capMaps != null) && (capMaps.length > 0))
+        {
+            List filterList = new ArrayList();
+            // Use brute force to determine if any of the exports
+            // could possibly resolve any of the imports.
+            MapToDictionary mapDict = new MapToDictionary(null);
+            for (int capIdx = 0; capIdx < capMaps.length; capIdx++)
+            {
+                boolean added = false;
+                for (int recIdx = 0; !added && (recIdx < m_localRecordList.size()); recIdx++)
+                {
+                    BundleRecord brLocal = (BundleRecord) m_localRecordList.get(recIdx);
+                    Filter[] reqFilters = (Filter[])
+                        brLocal.getAttribute("requirement");
+                    for (int reqIdx = 0;
+                        (reqFilters != null) && (reqIdx < reqFilters.length);
+                        reqIdx++)
+                    {
+                        mapDict.setSourceMap(capMaps[capIdx]);
+                        if (reqFilters[reqIdx].match(mapDict))
+                        {
+                            added = true;
+                            filterList.add(reqFilters[reqIdx]);
+                        }
+                    }
+                }
+            }
+            return (Filter[])
+                filterList.toArray(new Filter[filterList.size()]);
+        }
+        return null;
+    }
+
+    public boolean isResolvable(Filter reqFilter)
+    {
+        MapToDictionary mapDict = new MapToDictionary(null);
+        for (int brIdx = 0; brIdx < m_localRecordList.size(); brIdx++)
+        {
+            BundleRecord brLocal = (BundleRecord) m_localRecordList.get(brIdx);
+            Map[] capMaps = (Map[]) brLocal.getAttribute("capability");
+            for (int capIdx = 0; (capMaps != null) && (capIdx < capMaps.length); capIdx++)
+            {
+                mapDict.setSourceMap(capMaps[capIdx]);
+                if (reqFilter.match(mapDict))
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private void initialize()
+    {
+        Bundle[] bundles = m_context.getBundles();
+        for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+        {
+            Dictionary dict = bundles[i].getHeaders();
+            // Create a case-insensitive map.
+            Map bundleMap = new TreeMap(new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    return o1.toString().compareToIgnoreCase(o2.toString());
+                }
+            });
+
+            for (Enumeration keys = dict.keys(); keys.hasMoreElements(); )
+            {
+                Object key = keys.nextElement();
+                bundleMap.put(key, dict.get(key));
+            }
+            
+            // Remove and convert any import package declarations
+            // into requirement filters.
+            String target = (String) bundleMap.remove(BundleRecord.IMPORT_PACKAGE);
+            if (target != null)
+            {
+                IPackage[] pkgs = R4Package.parseImportOrExportHeader(target);
+                Filter[] filters = new Filter[(pkgs == null) ? 0 : pkgs.length];
+                for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+                {
+                    try
+                    {
+                        String low = pkgs[pkgIdx].getVersionLow().isInclusive()
+                            ? "(version>=" + pkgs[pkgIdx].getVersionLow() + ")"
+                            : "(!(version<=" + pkgs[pkgIdx].getVersionLow() + ")";
+
+                        if (pkgs[pkgIdx].getVersionHigh() != null)
+                        {
+                            String high = pkgs[pkgIdx].getVersionHigh().isInclusive()
+                                ? "(version<=" + pkgs[pkgIdx].getVersionHigh() + ")"
+                                : "(!(version>=" + pkgs[pkgIdx].getVersionHigh() + ")";
+                            filters[pkgIdx] = m_context.createFilter(
+                                "(&(type=Export-Package)(name="
+                                + pkgs[pkgIdx].getId() + ")"
+                                + low + high + ")");
+                        }
+                        else
+                        {
+                            filters[pkgIdx] = m_context.createFilter(
+                                "(&(type=Export-Package)(name="
+                                + pkgs[pkgIdx].getId() + ")"
+                                + low + ")");
+                        }
+                    }
+                    catch (InvalidSyntaxException ex)
+                    {
+                        // Ignore, since it should not happen.
+                    }
+                }
+                bundleMap.put("requirement", filters);
+            }
+
+            // Remove and convert any export package declarations
+            // into capability maps.
+            target = (String) bundleMap.remove(BundleRecord.EXPORT_PACKAGE);
+            if (target != null)
+            {
+                IPackage[] pkgs = R4Package.parseImportOrExportHeader(target);
+                Map[] capMaps = new Map[(pkgs == null) ? 0 : pkgs.length];
+                for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+                {
+                    // Create a case-insensitive map.
+                    capMaps[pkgIdx] = new TreeMap(new Comparator() {
+                        public int compare(Object o1, Object o2)
+                        {
+                            return o1.toString().compareToIgnoreCase(o2.toString());
+                        }
+                    });
+                    capMaps[pkgIdx].put("type", "Export-Package");
+                    capMaps[pkgIdx].put("name", pkgs[pkgIdx].getId());
+                    capMaps[pkgIdx].put("version", pkgs[pkgIdx].getVersionLow());
+                }
+                bundleMap.put("capability", capMaps);
+            }
+
+            // For the system bundle, add a special platform capability.
+            if (bundles[i].getBundleId() == 0)
+            {
+                // Create a case-insensitive map.
+                Map map = new TreeMap(new Comparator() {
+                    public int compare(Object o1, Object o2)
+                    {
+                        return o1.toString().compareToIgnoreCase(o2.toString());
+                    }
+                });
+                map.put(
+                    Constants.FRAMEWORK_VERSION,
+                    m_context.getProperty(Constants.FRAMEWORK_VERSION));
+                map.put(
+                    Constants.FRAMEWORK_VENDOR,
+                    m_context.getProperty(Constants.FRAMEWORK_VENDOR));
+                map.put(
+                    Constants.FRAMEWORK_LANGUAGE,
+                    m_context.getProperty(Constants.FRAMEWORK_LANGUAGE));
+                map.put(
+                    Constants.FRAMEWORK_OS_NAME,
+                    m_context.getProperty(Constants.FRAMEWORK_OS_NAME));
+                map.put(
+                    Constants.FRAMEWORK_OS_VERSION,
+                    m_context.getProperty(Constants.FRAMEWORK_OS_VERSION));
+                map.put(
+                    Constants.FRAMEWORK_PROCESSOR,
+                    m_context.getProperty(Constants.FRAMEWORK_PROCESSOR));
+//                map.put(
+//                    FelixConstants.FELIX_VERSION_PROPERTY,
+//                    m_context.getProperty(FelixConstants.FELIX_VERSION_PROPERTY));
+                Map[] capMaps = (Map[]) bundleMap.get("capability");
+                if (capMaps == null)
+                {
+                    capMaps = new Map[] { map };
+                }
+                else
+                {
+                    Map[] newCaps = new Map[capMaps.length + 1];
+                    newCaps[0] = map;
+                    System.arraycopy(capMaps, 0, newCaps, 1, capMaps.length);
+                    capMaps = newCaps;
+                }
+                bundleMap.put("capability", capMaps);
+            }
+            m_localRecordList.add(new LocalBundleRecord(bundleMap, bundles[i]));
+        }
+    }
+
+    public static class LocalBundleRecord extends BundleRecord
+    {
+        private Bundle m_bundle = null;
+
+        LocalBundleRecord(Map attrMap, Bundle bundle)
+        {
+            super(attrMap);
+            m_bundle = bundle;
+        }
+
+        public Bundle getBundle()
+        {
+            return m_bundle;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java b/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java
new file mode 100644
index 0000000..d7806c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java
@@ -0,0 +1,97 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutatable.
+**/
+public class MapToDictionary extends Dictionary
+{
+    /**
+     * Map source.
+    **/
+    private Map m_map = null;
+
+    public MapToDictionary(Map map)
+    {
+        m_map = map;
+    }
+
+    public void setSourceMap(Map map)
+    {
+        m_map = map;
+    }
+
+    public Enumeration elements()
+    {
+        if (m_map == null)
+        {
+            return null;
+        }
+        return new IteratorToEnumeration(m_map.values().iterator());
+    }
+
+    public Object get(Object key)
+    {
+        if (m_map == null)
+        {
+            return null;
+        }
+        return m_map.get(key);
+    }
+
+    public boolean isEmpty()
+    {
+        if (m_map == null)
+        {
+            return true;
+        }
+        return m_map.isEmpty();
+    }
+
+    public Enumeration keys()
+    {
+        if (m_map == null)
+        {
+            return null;
+        }
+        return new IteratorToEnumeration(m_map.keySet().iterator());
+    }
+
+    public Object put(Object key, Object value)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object remove(Object key)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public int size()
+    {
+        if (m_map == null)
+        {
+            return 0;
+        }
+        return m_map.size();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java b/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
new file mode 100644
index 0000000..10465a0
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
@@ -0,0 +1,1374 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.BundleRepository;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class ObrCommandImpl implements Command
+{
+    private static final String HELP_CMD = "help";
+    private static final String URLS_CMD = "urls";
+    private static final String LIST_CMD = "list";
+    private static final String INFO_CMD = "info";
+    private static final String DEPLOY_CMD = "deploy";
+//    private static final String INSTALL_CMD = "install";
+    private static final String START_CMD = "start";
+//    private static final String UPDATE_CMD = "update";
+    private static final String SOURCE_CMD = "source";
+
+    private static final String NODEPS_SWITCH = "-nodeps";
+    private static final String CHECK_SWITCH = "-check";
+    private static final String EXTRACT_SWITCH = "-x";
+
+    private BundleContext m_context = null;
+    private BundleRepository m_repo = null;
+
+    public ObrCommandImpl(BundleContext context, BundleRepository repo)
+    {
+        m_context = context;
+        m_repo = repo;
+    }
+
+    public String getName()
+    {
+        return "obr";
+    }
+
+    public String getUsage()
+    {
+        return "obr help";
+    }
+
+    public String getShortDescription()
+    {
+        return "OSGi bundle repository.";
+    }
+
+    public synchronized void execute(String commandLine, PrintStream out, PrintStream err)
+    {
+        try
+        {
+            // Parse the commandLine to get the OBR command.
+            StringTokenizer st = new StringTokenizer(commandLine);
+            // Ignore the invoking command.
+            st.nextToken();
+            // Try to get the OBR command, default is HELP command.
+            String command = HELP_CMD;
+            try
+            {
+                command = st.nextToken();
+            }
+            catch (Exception ex)
+            {
+                // Ignore.
+            }
+            
+            // Perform the specified command.
+            if ((command == null) || (command.equals(HELP_CMD)))
+            {
+                help(out, st);
+            }
+            else
+            {
+                if (command.equals(URLS_CMD))
+                {
+                    urls(commandLine, command, out, err);
+                }
+                else if (command.equals(LIST_CMD))
+                {
+                    list(commandLine, command, out, err);
+                }
+                else if (command.equals(INFO_CMD))
+                {
+                    info(commandLine, command, out, err);
+                }
+                else if (command.equals(DEPLOY_CMD) || command.equals(START_CMD))
+                {
+                    deploy(commandLine, command, out, err);
+                }
+/*
+                else if (command.equals(INSTALL_CMD) || command.equals(START_CMD))
+                {
+                    install(commandLine, command, out, err);
+                }
+                else if (command.equals(UPDATE_CMD))
+                {
+                    update(commandLine, command, out, err);
+                }
+*/
+                else if (command.equals(SOURCE_CMD))
+                {
+                    source(commandLine, command, out, err);
+                }
+                else
+                {
+                    err.println("Unknown command: " + command);
+                }
+            }
+        }
+        catch (InvalidSyntaxException ex)
+        {
+            err.println("Syntax error: " + ex.getMessage());
+        }
+        catch (IOException ex)
+        {
+            err.println("Error: " + ex);
+        }
+    }
+
+    private void urls(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException
+    {
+        // Parse the commandLine.
+        StringTokenizer st = new StringTokenizer(commandLine);
+        // Ignore the "obr" command.
+        st.nextToken();
+        // Ignore the "urls" command.
+        st.nextToken();
+
+        int count = st.countTokens();
+        String[] urls = new String[count];
+        for (int i = 0; i < count; i++)
+        {
+            urls[i] = st.nextToken();
+        }
+    
+        if (count > 0)
+        {
+            m_repo.setRepositoryURLs(urls);
+        }
+        else
+        {
+            urls = m_repo.getRepositoryURLs();
+            if (urls != null)
+            {
+                for (int i = 0; i < urls.length; i++)
+                {
+                    out.println(urls[i]);
+                }
+            }
+            else
+            {
+                out.println("No repository URLs are set.");
+            }
+        }
+    }
+
+    private void list(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+
+        String substr = null;
+    
+        for (type = tokenizer.nextToken();
+            type != StreamTokenizer.TT_EOF;
+            type = tokenizer.nextToken())
+        {
+            // Add a space in between tokens.
+            if (substr == null)
+            {
+                substr = "";
+            }
+            else
+            {
+                substr += " ";
+            }
+                        
+            if ((type == StreamTokenizer.TT_WORD) ||
+                (type == '\'') || (type == '"'))
+            {
+                substr += tokenizer.sval.toLowerCase();
+            }
+        }
+
+        boolean found = false;
+        BundleRecord[] records = m_repo.getBundleRecords();
+        for (int recIdx = 0; recIdx < records.length; recIdx++)
+        {
+            String name = (String)
+                records[recIdx].getAttribute(BundleRecord.BUNDLE_NAME);
+            String symName = (String)
+                records[recIdx].getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+            if ((substr == null) ||
+                ((name != null) && (name.toLowerCase().indexOf(substr) >= 0)) ||
+                ((symName != null) && (symName.toLowerCase().indexOf(substr) >= 0)))
+            {
+                found = true;
+                String version =
+                    (String) records[recIdx].getAttribute(BundleRecord.BUNDLE_VERSION);
+                if (version != null)
+                {
+                    out.println(name + " (" + version + ")");
+                }
+                else
+                {
+                    out.println(name);
+                }
+            }
+        }
+    
+        if (!found)
+        {
+            out.println("No matching bundles.");
+        }
+    }
+
+    private void info(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        ParsedCommand pc = parseInfo(commandLine);
+        for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)                
+        {
+            BundleRecord[] records = searchRepository(
+                pc.getTargetId(i), pc.getTargetVersion(i));
+            if (records == null)
+            {
+                err.println("Unknown bundle and/or version: "
+                    + pc.getTargetId(i));
+            }
+            else if (records.length > 1)
+            {
+                err.println("More than one version exists: "
+                    + pc.getTargetId(i));
+            }
+            else
+            {
+                records[0].printAttributes(out);
+            }
+        }
+    }
+
+    private void deploy(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        ParsedCommand pc = parseInstallStart(commandLine);
+        _deploy(pc, command, out, err);
+    }
+
+    private void _deploy(
+        ParsedCommand pc, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)                
+        {
+            // Find the target's bundle record.
+            BundleRecord record = selectNewestVersion(
+                searchRepository(pc.getTargetId(i), pc.getTargetVersion(i)));
+            if (record != null)
+            {
+                m_repo.deployBundle(
+                    out, // Output stream.
+                    err, // Error stream.
+                    (String) record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME),
+                    Util.parseVersionString((String)record.getAttribute(BundleRecord.BUNDLE_VERSION)),
+                    pc.isResolve(), // Resolve dependencies.
+                    command.equals(START_CMD)); // Start.
+            }
+            else
+            {
+                err.println("Unknown bundle or amiguous version: "
+                    + pc.getTargetId(i));
+            }
+        }
+    }
+/*
+    private void install(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        // Parse the command line to get all local targets to install.
+        ParsedCommand pc = parseInstallStart(commandLine);
+        
+        // Loop through each local target and try to find
+        // the corresponding bundle record from the repository.
+        for (int targetIdx = 0;
+            (pc != null) && (targetIdx < pc.getTargetCount());
+            targetIdx++)                
+        {
+            // Get the current target's name and version.
+            String targetName = pc.getTargetId(targetIdx);
+            String targetVersionString = pc.getTargetVersion(targetIdx);
+
+            // Make sure the bundle is not already installed.
+            Bundle bundle = findLocalBundle(targetName, targetVersionString);
+            if (bundle == null)
+            {
+                _deploy(pc, command, out, err);
+            }
+            else
+            {
+                err.println("Already installed: " + targetName);
+            }
+        }
+    }
+
+    private void update(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        // Parse the command line to get all local targets to update.
+        ParsedCommand pc = parseUpdate(commandLine);
+
+        if (pc.isCheck())        
+        {
+            updateCheck(out, err);
+        }
+        else
+        {
+            // Loop through each local target and try to find
+            // the corresponding bundle record from the repository.
+            for (int targetIdx = 0;
+                (pc != null) && (targetIdx < pc.getTargetCount());
+                targetIdx++)                
+            {
+                // Get the current target's name and version.
+                String targetName = pc.getTargetId(targetIdx);
+                String targetVersionString = pc.getTargetVersion(targetIdx);
+
+                // Make sure the bundle is not already installed.
+                Bundle bundle = findLocalBundle(targetName, targetVersionString);
+                if (bundle != null)
+                {
+                    _deploy(pc, command, out, err);
+                }
+                else
+                {
+                    err.println("Not installed: " + targetName);
+                }
+            }
+        }
+    }
+
+    private void updateCheck(PrintStream out, PrintStream err)
+        throws IOException
+    {
+        Bundle[] bundles = m_context.getBundles();
+
+        // Loop through each local target and try to find
+        // the corresponding locally installed bundle.
+        for (int bundleIdx = 0;
+            (bundles != null) && (bundleIdx < bundles.length);
+            bundleIdx++)
+        {
+            // Ignore the system bundle.
+            if (bundles[bundleIdx].getBundleId() == 0)
+            {
+                continue;
+            }
+
+            // Get the local bundle's update location.
+            String localLoc = (String)
+                bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_UPDATELOCATION);
+            if (localLoc == null)
+            {
+                // Without an update location, there is no way to
+                // check for an update, so ignore the bundle.
+                continue;
+            }
+
+            // Get the local bundle's version.
+            String localVersion = (String)
+                bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_VERSION);
+            localVersion = (localVersion == null) ? "0.0.0" : localVersion;
+
+            // Get the matching repository bundle records.
+            BundleRecord[] records = m_repo.getBundleRecords(
+                (String) bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_NAME));
+
+            // Loop through all records to see if there is an update.
+            for (int recordIdx = 0;
+                (records != null) && (recordIdx < records.length);
+                recordIdx++)
+            {
+                String remoteLoc = (String)
+                    records[recordIdx].getAttribute(BundleRecord.BUNDLE_UPDATELOCATION);
+                if (remoteLoc == null)
+                {
+                    continue;
+                }
+
+                // If the update locations are equal, then compare versions.
+                if (remoteLoc.equals(localLoc))
+                {
+                    String remoteVersion = (String)
+                        records[recordIdx].getAttribute(BundleRecord.BUNDLE_VERSION);
+                    if (remoteVersion != null)
+                    {
+                        int result = Util.compareVersion(
+                            Util.parseVersionString(remoteVersion),
+                            Util.parseVersionString(localVersion));
+                        if (result > 0)
+                        {
+                            out.println(
+                                records[recordIdx].getAttribute(BundleRecord.BUNDLE_NAME)
+                                + " update available.");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+*/
+    private void source(
+        String commandLine, String command, PrintStream out, PrintStream err)
+        throws IOException, InvalidSyntaxException
+    {
+        // Parse the command line to get all local targets to update.
+        ParsedCommand pc = parseSource(commandLine);
+        
+        for (int i = 0; i < pc.getTargetCount(); i++)
+        {
+            BundleRecord[] records =
+                searchRepository(pc.getTargetId(i), pc.getTargetVersion(i));
+            if (records == null)
+            {
+                err.println("Unknown bundle and/or version: "
+                    + pc.getTargetId(i));
+            }
+            else if (records.length > 1)
+            {
+                err.println("More than one version exists: "
+                    + pc.getTargetId(i));
+            }
+            else
+            {
+                String srcURL = (String)
+                records[0].getAttribute(BundleRecord.BUNDLE_SOURCEURL);
+                if (srcURL != null)
+                {
+                    FileUtil.downloadSource(
+                        out, err, srcURL, pc.getDirectory(), pc.isExtract());
+                }
+                else
+                {
+                    err.println("Missing source URL: " + pc.getTargetId(i));
+                }
+            }
+        }
+    }
+
+    private BundleRecord[] searchRepository(String targetId, String targetVersion)
+    {
+        // The targetId may be a bundle name or a bundle symbolic name.
+        // Query for symbolic name first, since it is more specific. If
+        // that can't be found, then compare bundle names.
+        BundleRecord[] records = null;
+        if (targetVersion != null)
+        {
+            BundleRecord record = m_repo.getBundleRecord(
+                targetId, Util.parseVersionString(targetVersion));
+            if (record != null)
+            {
+                records = new BundleRecord[] { record };
+            }
+        }
+        else
+        {
+            records = m_repo.getBundleRecords(targetId);
+        }
+
+        if (records == null)
+        {
+            List recordList = new ArrayList();
+            records = m_repo.getBundleRecords();
+            for (int i = 0; (records != null) && (i < records.length); i++)
+            {
+                if (targetId.compareToIgnoreCase((String)
+                    records[i].getAttribute(BundleRecord.BUNDLE_NAME)) == 0)
+                {
+                    int[] v1 = Util.parseVersionString(targetVersion);
+                    int[] v2 = Util.parseVersionString((String)
+                        records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+                    if ((targetVersion == null) ||
+                        ((targetVersion != null) && (Util.compareVersion(v1, v2) == 0)))
+                    {
+                        recordList.add(records[i]);
+                    }
+                }
+            }
+            records = (recordList.size() == 0)
+                ? null
+                : (BundleRecord[]) recordList.toArray(new BundleRecord[recordList.size()]);
+        }
+
+        return records;
+    }
+
+    public BundleRecord selectNewestVersion(BundleRecord[] records)
+    {
+        int idx = -1;
+        int[] v = null;
+        for (int i = 0; (records != null) && (i < records.length); i++)
+        {
+            if (i == 0)
+            {
+                idx = 0;
+                v = Util.parseVersionString((String)
+                    records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+            }
+            else
+            {
+                int[] vtmp = Util.parseVersionString((String)
+                    records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+                if (Util.compareVersion(vtmp, v) > 0)
+                {
+                    idx = i;
+                    v = vtmp;
+                }
+            }
+        }
+
+        return (idx < 0) ? null : records[idx];
+    }
+
+    private Bundle findLocalBundle(String name, String versionString)
+    {
+        Bundle bundle = null;
+
+        // Get the name only if there is no version, but error
+        // if there are multiple matches for the same name.
+        if (versionString == null)
+        {
+            // Perhaps the target name is a bundle ID and
+            // not a name, so try to interpret as a long.
+            try
+            {
+                bundle = m_context.getBundle(Long.parseLong(name));
+            }
+            catch (NumberFormatException ex)
+            {
+                // The bundle is not a number, so look for a local
+                // bundle with the same name.
+                Bundle[] matchingBundles = findLocalBundlesBySymbolicName(name);
+
+                // If only one matches, then select is.
+                if (matchingBundles.length == 1)
+                {
+                    bundle = matchingBundles[0];
+                }
+            }
+        }
+        else
+        {
+            // Find the local bundle by name and version.
+            bundle = findLocalBundleByVersion(
+                name, Util.parseVersionString(versionString));
+        }
+
+        return bundle;
+    }
+
+    private Bundle findLocalBundleByVersion(String symName, int[] version)
+    {
+        // Get bundles with matching name.
+        Bundle[] targets = findLocalBundlesBySymbolicName(symName);
+
+        // Find bundle with matching version.
+        if (targets.length > 0)
+        {
+            for (int i = 0; i < targets.length; i++)
+            {
+                String targetName = (String)
+                    targets[i].getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+                int[] targetVersion = Util.parseVersionString((String)
+                    targets[i].getHeaders().get(BundleRecord.BUNDLE_VERSION));
+            
+                if ((targetName != null) &&
+                    targetName.equalsIgnoreCase(symName) &&
+                    (Util.compareVersion(targetVersion, version) == 0))
+                {
+                    return targets[i];
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Bundle[] findLocalBundlesBySymbolicName(String symName)
+    {
+        // Get local bundles.
+        Bundle[] bundles = m_context.getBundles();
+
+        // Find bundles with matching name.
+        Bundle[] targets = new Bundle[0];
+        for (int i = 0; i < bundles.length; i++)
+        {
+            String targetName = (String)
+                bundles[i].getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+            if (targetName == null)
+            {
+                targetName = bundles[i].getLocation();
+            }
+            if ((targetName != null) && targetName.equalsIgnoreCase(symName))
+            {
+                Bundle[] newTargets = new Bundle[targets.length + 1];
+                System.arraycopy(targets, 0, newTargets, 0, targets.length);
+                newTargets[targets.length] = bundles[i];
+                targets = newTargets;
+            }
+        }
+
+        return targets;
+    }
+
+    private ParsedCommand parseInfo(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int TARGET = 4;
+        int VERSION = 8;
+        int VERSION_VALUE = 16;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a TARGET.
+        int expecting = (TARGET);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a target, the record it.
+                    if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private ParsedCommand parseInstallStart(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int TARGET = 4;
+        int VERSION = 8;
+        int VERSION_VALUE = 16;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a TARGET.
+        int expecting = (SWITCH | TARGET);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a command SWITCH and the token
+                    // equals a command SWITCH, then record it.
+                    if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(NODEPS_SWITCH))
+                    {
+                        pc.setResolve(false);
+                        expecting = (EOF | TARGET);
+                    }
+                    // If we are expecting a target, the record it.
+                    else if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private ParsedCommand parseUpdate(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int TARGET = 4;
+        int VERSION = 8;
+        int VERSION_VALUE = 16;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a TARGET.
+        int expecting = (SWITCH | TARGET);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a command SWITCH and the token
+                    // equals a NODEPS switch, then record it.
+                    if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(NODEPS_SWITCH))
+                    {
+                        pc.setResolve(false);
+                        expecting = (EOF | TARGET);
+                    }
+                    // If we are expecting a command SWITCH and the token
+                    // equals a CHECK swithc, then record it.
+                    else if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(CHECK_SWITCH))
+                    {
+                        pc.setCheck(true);
+                        expecting = (EOF);
+                    }
+                    // If we are expecting a target, the record it.
+                    else if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private ParsedCommand parseSource(String commandLine)
+        throws IOException, InvalidSyntaxException
+    {
+        // Create a stream tokenizer for the command line string,
+        // since the syntax for install/start is more sophisticated.
+        StringReader sr = new StringReader(commandLine);
+        StreamTokenizer tokenizer = new StreamTokenizer(sr);
+        tokenizer.resetSyntax();
+        tokenizer.quoteChar('\'');
+        tokenizer.quoteChar('\"');
+        tokenizer.whitespaceChars('\u0000', '\u0020');
+        tokenizer.wordChars('A', 'Z');
+        tokenizer.wordChars('a', 'z');
+        tokenizer.wordChars('0', '9');
+        tokenizer.wordChars('\u00A0', '\u00FF');
+        tokenizer.wordChars('.', '.');
+        tokenizer.wordChars('-', '-');
+        tokenizer.wordChars('_', '_');
+        tokenizer.wordChars('/', '/');
+    
+        // Ignore the invoking command name and the OBR command.
+        int type = tokenizer.nextToken();
+        type = tokenizer.nextToken();
+    
+        int EOF = 1;
+        int SWITCH = 2;
+        int DIRECTORY = 4;
+        int TARGET = 8;
+        int VERSION = 16;
+        int VERSION_VALUE = 32;
+
+        // Construct an install record.
+        ParsedCommand pc = new ParsedCommand();
+        String currentTargetName = null;
+
+        // The state machine starts by expecting either a
+        // SWITCH or a DIRECTORY.
+        int expecting = (SWITCH | DIRECTORY);
+        while (true)
+        {
+            // Get the next token type.
+            type = tokenizer.nextToken();
+            switch (type)
+            {
+                // EOF received.
+                case StreamTokenizer.TT_EOF:
+                    // Error if we weren't expecting EOF.
+                    if ((expecting & EOF) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Expecting more arguments.", null);
+                    }
+                    // Add current target if there is one.
+                    if (currentTargetName != null)
+                    {
+                        pc.addTarget(currentTargetName, null);
+                    }
+                    // Return cleanly.
+                    return pc;
+
+                // WORD or quoted WORD received.
+                case StreamTokenizer.TT_WORD:
+                case '\'':
+                case '\"':
+                    // If we are expecting a command SWITCH and the token
+                    // equals a command SWITCH, then record it.
+                    if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(EXTRACT_SWITCH))
+                    {
+                        pc.setExtract(true);
+                        expecting = (DIRECTORY);
+                    }
+                    // If we are expecting a directory, the record it.
+                    else if ((expecting & DIRECTORY) > 0)
+                    {
+                        // Set the directory for the command.
+                        pc.setDirectory(tokenizer.sval);
+                        expecting = (TARGET);
+                    }
+                    // If we are expecting a target, the record it.
+                    else if ((expecting & TARGET) > 0)
+                    {
+                        // Add current target if there is one.
+                        if (currentTargetName != null)
+                        {
+                            pc.addTarget(currentTargetName, null);
+                        }
+                        // Set the new target as the current target.
+                        currentTargetName = tokenizer.sval;
+                        expecting = (EOF | TARGET | VERSION);
+                    }
+                    else if ((expecting & VERSION_VALUE) > 0)
+                    {
+                        pc.addTarget(currentTargetName, tokenizer.sval);
+                        currentTargetName = null;
+                        expecting = (EOF | TARGET);
+                    }
+                    else
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting '" + tokenizer.sval + "'.", null);
+                    }
+                    break;
+
+                // Version separator character received.
+                case ';':
+                    // Error if we weren't expecting the version separator.
+                    if ((expecting & VERSION) == 0)
+                    {
+                        throw new InvalidSyntaxException(
+                            "Not expecting version.", null);
+                    }
+                    // Otherwise, we will only expect a version value next.
+                    expecting = (VERSION_VALUE);
+                    break;
+            }
+        }
+    }
+
+    private void help(PrintStream out, StringTokenizer st)
+    {
+        String command = HELP_CMD;
+        if (st.hasMoreTokens())
+        {
+            command = st.nextToken();
+        }
+        if (command.equals(URLS_CMD))
+        {
+            out.println("");
+            out.println("obr " + URLS_CMD + " [<repository-file-url> ...]");
+            out.println("");
+            out.println(
+                "This command gets or sets the URLs to the repository files\n" +                "used by OBR. Specify no arguments to get the current repository\n" + 
+                "URLs or specify a space-delimited list of URLs to change the\n" +
+                "URLs. Each URL should point to a file containing meta-data about\n" +                "available bundles in XML format.");
+            out.println("");
+        }
+        else if (command.equals(LIST_CMD))
+        {
+            out.println("");
+            out.println("obr " + LIST_CMD + " [<string> ...]");
+            out.println("");
+            out.println(
+                "This command lists bundles available in the bundle repository.\n" +
+                "If no arguments are specified, then all available bundles are\n" +
+                "listed, otherwise any arguments are concatenated with spaces\n" +
+                "and used as a substring filter on the bundle names.");
+            out.println("");
+        }
+        else if (command.equals(INFO_CMD))
+        {
+            out.println("");
+            out.println("obr " + INFO_CMD
+                + " <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command displays the meta-data for the specified bundles.\n" +
+                "If a bundle's name contains spaces, then it must be surrounded\n" +
+                "by quotes. It is also possible to specify a precise version\n" +
+                "if more than one version exists, such as:\n" +
+                "\n" +
+                "    obr info \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example retrieves the meta-data for version \"1.0.0\"\n" +
+                "of the bundle named \"Bundle Repository\".");
+            out.println("");
+        }
+        else if (command.equals(DEPLOY_CMD))
+        {
+            out.println("");
+            out.println("obr " + DEPLOY_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+            out.println("");
+            out.println(
+                "This command tries to install or update the specified bundles\n" +
+                "and all of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch\n" +
+                "to ignore dependencies. You can specify either the bundle name or\n" +
+                "the bundle identifier. If a bundle's name contains spaces, then\n" +
+                "it must be surrounded by quotes. It is also possible to specify a\n" +                "precise version if more than one version exists, such as:\n" +
+                "\n" +
+                "    obr deploy \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "For the above example, if version \"1.0.0\" of \"Bundle Repository\" is\n" +
+                "already installed locally, then the command will attempt to update it\n" +
+                "and all of its dependencies; otherwise, the command will install it\n" +
+                "and all of its dependencies.");
+            out.println("");
+        }
+/*
+        else if (command.equals(INSTALL_CMD))
+        {
+            out.println("");
+            out.println("obr " + INSTALL_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command installs the specified bundles and all of their\n" +
+                "dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch to ignore\n" +
+                "dependencies. If a bundle's name contains spaces, then it\n" +
+                "must be surrounded by quotes. If a specified bundle is already\n" +                "installed, then this command has no effect. It is also possible\n" +                "to specify a precise version if more than one version exists,\n" +                "such as:\n" +
+                "\n" +
+                "    obr install \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example installs version \"1.0.0\" of the bundle\n" +
+                "named \"Bundle Repository\" and its dependencies. ");
+            out.println("");
+        }
+*/
+        else if (command.equals(START_CMD))
+        {
+            out.println("");
+            out.println("obr " + START_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command installs and starts the specified bundles and all\n" +
+                "of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch to\n" +
+                "ignore dependencies. If a bundle's name contains spaces, then\n" +
+                "it must be surrounded by quotes. If a specified bundle is already\n" +                "installed, then this command has no effect. It is also possible\n" +                "to specify a precise version if more than one version exists,\n" +                "such as:\n" +
+                "\n" +
+                "    obr start \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example installs and starts version \"1.0.0\" of the\n" +
+                "bundle named \"Bundle Repository\" and its dependencies.");
+            out.println("");
+        }
+/*
+        else if (command.equals(UPDATE_CMD))
+        {
+            out.println("");
+            out.println("obr " + UPDATE_CMD + " " + CHECK_SWITCH);
+            out.println("");
+            out.println("obr " + UPDATE_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+            out.println("");
+            out.println(
+                "The first form of the command above checks for available updates\n" +                "and the second updates the specified locally installed bundles\n" +
+                "and all of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch\n" +
+                "to ignore dependencies. You can specify either the bundle name or\n" +
+                "the bundle identifier. If a bundle's name contains spaces, then\n" +
+                "it must be surrounded by quotes. If a specified bundle is not\n" +                "already installed, then this command has no effect. It is also\n" +                "possible to specify a precise version if more than one version\n" +                "exists, such as:\n" +
+                "\n" +
+                "    obr update \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example updates version \"1.0.0\" of the bundle named\n" +
+                "\"Bundle Repository\" and its dependencies. The update command may\n" +
+                "install new bundles if the updated bundles have new dependencies.");
+            out.println("");
+        }
+*/
+        else if (command.equals(SOURCE_CMD))
+        {
+            out.println("");
+            out.println("obr " + SOURCE_CMD
+                + " [" + EXTRACT_SWITCH
+                + "] <local-dir> <bundle-name>[;<version>] ...");
+            out.println("");
+            out.println(
+                "This command retrieves the source archives of the specified\n" +
+                "bundles and saves them to the specified local directory; use\n" +
+                "the \"" + EXTRACT_SWITCH + "\" switch to automatically extract the source archives.\n" +
+                "If a bundle name contains spaces, then it must be surrounded\n" +
+                "by quotes. It is also possible to specify a precise version if\n" +                "more than one version exists, such as:\n" +
+                "\n" +
+                "    obr source /home/rickhall/tmp \"Bundle Repository\";1.0.0\n" +
+                "\n" +
+                "The above example retrieves the source archive of version \"1.0.0\"\n" +
+                "of the bundle named \"Bundle Repository\" and saves it to the\n" +
+                "specified local directory.");
+            out.println("");
+        }
+        else
+        {
+            out.println("obr " + HELP_CMD
+                + " [" + URLS_CMD + " | " + LIST_CMD
+//                + " | " + INFO_CMD + " | " + INSTALL_CMD
+                + " | " + INFO_CMD
+                + " | " + DEPLOY_CMD + " | " + START_CMD
+//                + " | " + UPDATE_CMD + " | " + SOURCE_CMD + "]");
+                + " | " + SOURCE_CMD + "]");
+            out.println("obr " + URLS_CMD + " [<repository-file-url> ...]");
+            out.println("obr " + LIST_CMD + " [<string> ...]");
+            out.println("obr " + INFO_CMD
+                + " <bundle-name>[;<version>] ...");
+            out.println("obr " + DEPLOY_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+//            out.println("obr " + INSTALL_CMD
+//                + " [" + NODEPS_SWITCH
+//                + "] <bundle-name>[;<version>] ...");
+            out.println("obr " + START_CMD
+                + " [" + NODEPS_SWITCH
+                + "] <bundle-name>[;<version>] ...");
+//            out.println("obr " + UPDATE_CMD + " " + CHECK_SWITCH);
+//            out.println("obr " + UPDATE_CMD
+//                + " [" + NODEPS_SWITCH
+//                + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+            out.println("obr " + SOURCE_CMD
+                + " [" + EXTRACT_SWITCH
+                + "] <local-dir> <bundle-name>[;<version>] ...");
+        }
+    }
+
+    private static class ParsedCommand
+    {
+        private static final int NAME_IDX = 0;
+        private static final int VERSION_IDX = 1;
+
+        private boolean m_isResolve = true;
+        private boolean m_isCheck = false;
+        private boolean m_isExtract = false;
+        private String m_dir = null;
+        private String[][] m_targets = new String[0][];
+        
+        public boolean isResolve()
+        {
+            return m_isResolve;
+        }
+        
+        public void setResolve(boolean b)
+        {
+            m_isResolve = b;
+        }
+
+        public boolean isCheck()
+        {
+            return m_isCheck;
+        }
+        
+        public void setCheck(boolean b)
+        {
+            m_isCheck = b;
+        }
+
+        public boolean isExtract()
+        {
+            return m_isExtract;
+        }
+        
+        public void setExtract(boolean b)
+        {
+            m_isExtract = b;
+        }
+
+        public String getDirectory()
+        {
+            return m_dir;
+        }
+        
+        public void setDirectory(String s)
+        {
+            m_dir = s;
+        }
+
+        public int getTargetCount()
+        {
+            return m_targets.length;
+        }
+        
+        public String getTargetId(int i)
+        {
+            if ((i < 0) || (i >= getTargetCount()))
+            {
+                return null;
+            }
+            return m_targets[i][NAME_IDX];
+        }
+        
+        public String getTargetVersion(int i)
+        {
+            if ((i < 0) || (i >= getTargetCount()))
+            {
+                return null;
+            }
+            return m_targets[i][VERSION_IDX];
+        }
+
+        public void addTarget(String name, String version)
+        {
+            String[][] newTargets = new String[m_targets.length + 1][];
+            System.arraycopy(m_targets, 0, newTargets, 0, m_targets.length);
+            newTargets[m_targets.length] = new String[] { name, version };
+            m_targets = newTargets;
+        }
+    }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java b/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
new file mode 100644
index 0000000..d100711
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
@@ -0,0 +1,57 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import org.apache.osgi.service.bundlerepository.IAttribute;
+
+public class R4Attribute implements IAttribute
+{
+    private String m_name = "";
+    private String m_value = "";
+    private boolean m_isMandatory = false;
+    
+    public R4Attribute(String name, String value, boolean isMandatory)
+    {
+        m_name = name;
+        m_value = value;
+        m_isMandatory = isMandatory;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Attribute#getName()
+    **/
+    public String getName()
+    {
+        return m_name;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Attribute#getValue()
+    **/
+    public String getValue()
+    {
+        return m_value;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Attribute#isMandatory()
+    **/
+    public boolean isMandatory()
+    {
+        return m_isMandatory;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java b/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
new file mode 100644
index 0000000..719dd89
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
@@ -0,0 +1,47 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import org.apache.osgi.service.bundlerepository.IDirective;
+
+public class R4Directive implements IDirective
+{
+    private String m_name = "";
+    private String m_value = "";
+    
+    public R4Directive(String name, String value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Directive#getName()
+    **/
+    public String getName()
+    {
+        return m_name;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Directive#getValue()
+    **/
+    public String getValue()
+    {
+        return m_value;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Package.java b/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
new file mode 100644
index 0000000..a055133
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
@@ -0,0 +1,501 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.*;
+import org.osgi.framework.Constants;
+
+//
+// This class is essentially the same as the R4Package class in Felix,
+// except that I had to add the parseDelimitedString() method. These
+// two classes should be unified.
+//
+
+/**
+ * This is a simple class to encapsulate a package declaration for
+ * bundle imports and exports for the bundle repository.
+**/
+public class R4Package implements IPackage
+{
+    private String m_id = "";
+    private IDirective[] m_directives = null;
+    private IAttribute[] m_attrs = null;
+    private IVersion m_versionLow = null;
+    private IVersion m_versionHigh = null;
+    private boolean m_isOptional = false;
+
+    protected R4Package(R4Package pkg)
+    {
+        m_id = pkg.m_id;
+        m_directives = pkg.m_directives;
+        m_attrs = pkg.m_attrs;
+        m_versionLow = pkg.m_versionLow;
+        m_versionHigh = pkg.m_versionHigh;
+        m_isOptional = pkg.m_isOptional;
+    }
+
+    public R4Package(String id, IDirective[] directives, IAttribute[] attrs)
+    {
+        m_id = id;
+        m_directives = (directives == null) ? new IDirective[0] : directives;
+        m_attrs = (attrs == null) ? new IAttribute[0] : attrs;
+
+        // Find mandatory and resolution directives, if present.
+        String mandatory = "";
+        for (int i = 0; i < m_directives.length; i++)
+        {
+            if (m_directives[i].getName().equals(Constants.MANDATORY_DIRECTIVE))
+            {
+                mandatory = m_directives[i].getValue();
+            }
+            else if (m_directives[i].getName().equals(Constants.RESOLUTION_DIRECTIVE))
+            {
+                m_isOptional = m_directives[i].getValue().equals(Constants.RESOLUTION_OPTIONAL);
+            }
+        }
+
+        // Parse mandatory directive and mark specified
+        // attributes as mandatory.
+        StringTokenizer tok = new StringTokenizer(mandatory, "");
+        while (tok.hasMoreTokens())
+        {
+            // Get attribute name.
+            String attrName = tok.nextToken().trim();
+            // Find attribute and mark it as mandatory.
+            boolean found = false;
+            for (int i = 0; (!found) && (i < m_attrs.length); i++)
+            {
+                if (m_attrs[i].getName().equals(attrName))
+                {
+                    m_attrs[i] = new R4Attribute(
+                        m_attrs[i].getName(), m_attrs[i].getValue(), true);
+                    found = true;
+                }
+            }
+            // If a specified mandatory attribute was not found,
+            // then error.
+            if (!found)
+            {
+                throw new IllegalArgumentException(
+                    "Mandatory attribute '" + attrName + "' does not exist.");
+            }
+        }
+
+        // Find and parse version attribute, if present.
+        String versionInterval = "0.0.0";
+        for (int i = 0; i < m_attrs.length; i++)
+        {
+            if (m_attrs[i].getName().equals(Constants.VERSION_ATTRIBUTE) ||
+                m_attrs[i].getName().equals(Constants.PACKAGE_SPECIFICATION_VERSION))
+            {
+                // Normalize version attribute name.
+                m_attrs[i] = new R4Attribute(
+                    Constants.VERSION_ATTRIBUTE, m_attrs[i].getValue(),
+                    m_attrs[i].isMandatory());
+                versionInterval = m_attrs[i].getValue();
+                break;
+            }
+        }
+        
+        IVersion[] versions = parseVersionInterval(versionInterval);
+        m_versionLow = versions[0];
+        if (versions.length == 2)
+        {
+            m_versionHigh = versions[1];
+        }
+    }
+
+    public String getId()
+    {
+        return m_id;
+    }
+
+    public IDirective[] getDirectives()
+    {
+        return m_directives;
+    }
+
+    public IAttribute[] getAttributes()
+    {
+        return m_attrs;
+    }
+
+    public IVersion getVersionLow()
+    {
+        return m_versionLow;
+    }
+
+    public IVersion getVersionHigh()
+    {
+        return m_versionHigh;
+    }
+
+    public boolean isOptional()
+    {
+        return m_isOptional;
+    }
+
+    // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+    public boolean doesSatisfy(IPackage pkg)
+    {
+        // For packages to be compatible, they must have the
+        // same name.
+        if (!m_id.equals(pkg.getId()))
+        {
+            return false;
+        }
+        
+        return isVersionInRange(m_versionLow, pkg.getVersionLow(), pkg.getVersionHigh())
+            && doAttributesMatch(pkg);
+    }
+
+    // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+    public static boolean isVersionInRange(IVersion version, IVersion low, IVersion high)
+    {
+        // We might not have an upper end to the range.
+        if (high == null)
+        {
+            return (version.compareTo(low) >= 0);
+        }
+        else if (low.isInclusive() && high.isInclusive())
+        {
+            return (version.compareTo(low) >= 0) && (version.compareTo(high) <= 0);
+        }
+        else if (high.isInclusive())
+        {
+            return (version.compareTo(low) > 0) && (version.compareTo(high) <= 0);
+        }
+        else if (low.isInclusive())
+        {
+            return (version.compareTo(low) >= 0) && (version.compareTo(high) < 0);
+        }
+
+        return (version.compareTo(low) > 0) && (version.compareTo(high) < 0);
+    }
+
+    private boolean doAttributesMatch(IPackage pkg)
+    {
+        // Cycle through all attributes of the specified package
+        // and make sure their values match the attribute values
+        // of this package.
+        for (int attrIdx = 0; attrIdx < pkg.getAttributes().length; attrIdx++)
+        {
+            // Get current attribute from specified package.
+            IAttribute attr = pkg.getAttributes()[attrIdx];
+
+            // Ignore version attribute, since it is a special case that
+            // has already been compared using isVersionInRange() before
+            // the call to this method was made.
+            if (attr.getName().equals(Constants.VERSION_ATTRIBUTE))
+            {
+                continue;
+            }
+
+            // Check if this package has the same attribute.
+            boolean found = false;
+            for (int thisAttrIdx = 0;
+                (!found) && (thisAttrIdx < m_attrs.length);
+                thisAttrIdx++)
+            {
+                // Get current attribute for this package.
+                IAttribute thisAttr = m_attrs[thisAttrIdx];
+                // Check if the attribute names are equal.
+                if (attr.getName().equals(thisAttr.getName()))
+                {
+                    // If the values are not equal, then return false immediately.
+                    // We should not compare version values here, since they are
+                    // a special case and have already been compared by a call to
+                    // isVersionInRange() before getting here; however, it is
+                    // possible for version to be mandatory, so make sure it is
+                    // present below.
+                    if (!attr.getValue().equals(thisAttr.getValue()))
+                    {
+                        return false;
+                    }
+                    found = true;
+                }
+            }
+            // If the attribute was not found, then return false.
+            if (!found)
+            {
+                return false;
+            }
+        }
+
+        // Now, cycle through all attributes of this package and verify that
+        // all mandatory attributes are present in the speceified package.
+        for (int thisAttrIdx = 0; thisAttrIdx < m_attrs.length; thisAttrIdx++)
+        {
+            // Get current attribute for this package.
+            IAttribute thisAttr = m_attrs[thisAttrIdx];
+            
+            // If the attribute is mandatory, then make sure
+            // the specified package has the attribute.
+            if (thisAttr.isMandatory())
+            {
+                boolean found = false;
+                for (int attrIdx = 0;
+                    (!found) && (attrIdx < pkg.getAttributes().length);
+                    attrIdx++)
+                {
+                    // Get current attribute from specified package.
+                    IAttribute attr = pkg.getAttributes()[attrIdx];
+        
+                    // Check if the attribute names are equal
+                    // and set found flag.
+                    if (thisAttr.getName().equals(attr.getName()))
+                    {
+                        found = true;
+                    }
+                }
+                // If not found, then return false.
+                if (!found)
+                {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public String toString()
+    {
+        String msg = getId();
+        for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
+        {
+            msg = msg + " [" + m_directives[i].getName() + ":="+ m_directives[i].getName() + "]";
+        }
+        for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+        {
+            msg = msg + " [" + m_attrs[i].getValue() + "="+ m_attrs[i].getValue() + "]";
+        }
+        return msg;
+    }
+
+    // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+    //            pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static IPackage[] parseImportOrExportHeader(String s)
+    {
+        IPackage[] pkgs = null;
+        if (s != null)
+        {
+            if (s.length() == 0)
+            {
+                throw new IllegalArgumentException(
+                    "The import and export headers cannot be an empty string.");
+            }
+            String[] ss = parseDelimitedString(s, ","); // FelixConstants.CLASS_PATH_SEPARATOR
+            pkgs = parsePackageStrings(ss);
+        }
+        return (pkgs == null) ? new IPackage[0] : pkgs;
+    }
+
+    // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static IPackage[] parsePackageStrings(String[] ss)
+        throws IllegalArgumentException
+    {
+        if (ss == null)
+        {
+            return null;
+        }
+
+        List completeList = new ArrayList();
+        for (int ssIdx = 0; ssIdx < ss.length; ssIdx++)
+        {
+            // Break string into semi-colon delimited pieces.
+            String[] pieces = parseDelimitedString(
+                ss[ssIdx], ";"); // FelixConstants.PACKAGE_SEPARATOR
+
+            // Count the number of different packages; packages
+            // will not have an '=' in their string. This assumes
+            // that packages come first, before directives and
+            // attributes.
+            int pkgCount = 0;
+            for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
+            {
+                if (pieces[pieceIdx].indexOf('=') >= 0)
+                {
+                    break;
+                }
+                pkgCount++;
+            }
+
+            // Error if no packages were specified.
+            if (pkgCount == 0)
+            {
+                throw new IllegalArgumentException(
+                    "No packages specified on import: " + ss[ssIdx]);
+            }
+
+            // Parse the directives/attributes.
+            IDirective[] dirs = new IDirective[pieces.length - pkgCount];
+            IAttribute[] attrs = new IAttribute[pieces.length - pkgCount];
+            int dirCount = 0, attrCount = 0;
+            int idx = -1;
+            String sep = null;
+            for (int pieceIdx = pkgCount; pieceIdx < pieces.length; pieceIdx++)
+            {
+                // Check if it is a directive.
+                if ((idx = pieces[pieceIdx].indexOf(":=")) >= 0) // FelixConstants.DIRECTIVE_SEPARATOR
+                {
+                    sep = ":="; // FelixConstants.DIRECTIVE_SEPARATOR
+                }
+                // Check if it is an attribute.
+                else if ((idx = pieces[pieceIdx].indexOf("=")) >= 0) // FelixConstants.ATTRIBUTE_SEPARATOR
+                {
+                    sep = "="; // FelixConstants.ATTRIBUTE_SEPARATOR
+                }
+                // It is an error.
+                else
+                {
+                    throw new IllegalArgumentException(
+                        "Not a directive/attribute: " + ss[ssIdx]);
+                }
+
+                String key = pieces[pieceIdx].substring(0, idx).trim();
+                String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+                // Remove quotes, if value is quoted.
+                if (value.startsWith("\"") && value.endsWith("\""))
+                {
+                    value = value.substring(1, value.length() - 1);
+                }
+
+                // Save the directive/attribute in the appropriate array.
+                if (sep.equals(":=")) // FelixConstants.DIRECTIVE_SEPARATOR
+                {
+                    dirs[dirCount++] = new R4Directive(key, value);
+                }
+                else
+                {
+                    attrs[attrCount++] = new R4Attribute(key, value, false);
+                }
+            }
+
+            // Shrink directive array.
+            IDirective[] dirsFinal = new IDirective[dirCount];
+            System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
+            // Shrink attribute array.
+            IAttribute[] attrsFinal = new IAttribute[attrCount];
+            System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+
+            // Create package attributes for each package and
+            // set directives/attributes. Add each package to
+            // completel list of packages.
+            IPackage[] pkgs = new IPackage[pkgCount];
+            for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++)
+            {
+                pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirsFinal, attrsFinal);
+                completeList.add(pkgs[pkgIdx]);
+            }
+        }
+    
+        IPackage[] ips = (IPackage[])
+            completeList.toArray(new IPackage[completeList.size()]);
+        return ips;
+    }
+
+    public static IVersion[] parseVersionInterval(String interval)
+    {
+        // Check if the version is an interval.
+        if (interval.indexOf(',') >= 0)
+        {
+            String s = interval.substring(1, interval.length() - 1);
+            String vlo = s.substring(0, s.indexOf(','));
+            String vhi = s.substring(s.indexOf(',') + 1, s.length());
+            return new IVersion[] {
+                new R4Version(vlo, (interval.charAt(0) == '[')),
+                new R4Version(vhi, (interval.charAt(interval.length() - 1) == ']'))
+            };
+        }
+        else
+        {
+            return new IVersion[] { new R4Version(interval, true) };
+        }
+    }
+
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return an array of string tokens or null if there were no tokens.
+    **/
+    public static String[] parseDelimitedString(String value, String delim)
+    {
+        if (value == null)
+        {
+           value = "";
+        }
+
+        List list = new ArrayList();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+        
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+            boolean isQuote = (c == '"');
+
+            if (isDelimiter && ((expecting & DELIMITER) > 0))
+            {
+                list.add(sb.toString().trim());
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            }
+            else if (isQuote && ((expecting & STARTQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            }
+            else if (isQuote && ((expecting & ENDQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            }
+            else if ((expecting & CHAR) > 0)
+            {
+                sb.append(c);
+            }
+            else
+            {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+        }
+
+        if (sb.length() > 0)
+        {
+            list.add(sb.toString().trim());
+        }
+
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Version.java b/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
new file mode 100644
index 0000000..eb44a9a
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
@@ -0,0 +1,216 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.bundlerepository.IVersion;
+
+public class R4Version implements Comparable, IVersion
+{
+    private int m_major = 0;
+    private int m_minor = 0;
+    private int m_micro = 0;
+    private String m_qualifier = "";
+    private boolean m_isInclusive = true;
+
+    private static final String SEPARATOR = ".";
+
+    public R4Version(String versionString)
+    {
+        this(versionString, true);
+    }
+
+    public R4Version(String versionString, boolean isInclusive)
+    {
+        if (versionString == null)
+        {
+            versionString = "0.0.0";
+        }
+        Object[] objs = parseVersion(versionString);
+        m_major = ((Integer) objs[0]).intValue();
+        m_minor = ((Integer) objs[1]).intValue();
+        m_micro = ((Integer) objs[2]).intValue();
+        m_qualifier = (String) objs[3];
+        m_isInclusive = isInclusive;
+    }
+
+    private static Object[] parseVersion(String versionString)
+    {
+        String s = versionString.trim();
+        Object[] objs = new Object[4];
+        objs[0] = objs[1] = objs[2] = new Integer(0);
+        objs[3] = "";
+        StringTokenizer tok = new StringTokenizer(s, SEPARATOR);
+        try
+        {
+            objs[0] = Integer.valueOf(tok.nextToken());
+            if (tok.hasMoreTokens())
+            {
+                objs[1] = Integer.valueOf(tok.nextToken());
+                if (tok.hasMoreTokens())
+                {
+                    objs[2] = Integer.valueOf(tok.nextToken());
+                    if (tok.hasMoreTokens())
+                    {
+                        objs[3] = tok.nextToken();
+                    }
+                }
+            }
+        }
+        catch (NumberFormatException ex)
+        {
+            throw new IllegalArgumentException("Invalid version: " + versionString);
+        }
+
+        if ((((Integer) objs[0]).intValue() < 0) ||
+            (((Integer) objs[0]).intValue() < 0) ||
+            (((Integer) objs[0]).intValue() < 0))
+        {
+            throw new IllegalArgumentException("Invalid version: " + versionString);
+        }
+
+        return objs;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#equals(java.lang.Object)
+    **/
+    public boolean equals(Object object)
+    {
+        if (!(object instanceof R4Version))
+        {
+            return false;
+        }
+        IVersion v = (IVersion) object;
+        return
+            (v.getMajorComponent() == m_major) &&
+            (v.getMinorComponent() == m_minor) &&
+            (v.getMicroComponent() == m_micro) &&
+            (v.getQualifierComponent().equals(m_qualifier));
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getMajorComponent()
+    **/
+    public int getMajorComponent()
+    {
+        return m_major;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getMinorComponent()
+    **/
+    public int getMinorComponent()
+    {
+        return m_minor;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getMicroComponent()
+    **/
+    public int getMicroComponent()
+    {
+        return m_micro;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#getQualifierComponent()
+    **/
+    public String getQualifierComponent()
+    {
+        return m_qualifier;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#isInclusive()
+    **/
+    public boolean isInclusive()
+    {
+        return m_isInclusive;
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#compareTo(java.lang.Object)
+    **/
+    public int compareTo(Object o)
+    {
+        if (!(o instanceof R4Version))
+            throw new ClassCastException();
+
+        if (equals(o))
+            return 0;
+
+        if (isGreaterThan((IVersion) o))
+            return 1;
+
+        return -1;
+    }
+
+    private boolean isGreaterThan(IVersion v)
+    {
+        if (v == null)
+        {
+            return false;
+        }
+
+        if (m_major > v.getMajorComponent())
+        {
+            return true;
+        }
+        if (m_major < v.getMajorComponent())
+        {
+            return false;
+        }
+        if (m_minor > v.getMinorComponent())
+        {
+            return true;
+        }
+        if (m_minor < v.getMinorComponent())
+        {
+            return false;
+        }
+        if (m_micro > v.getMicroComponent())
+        {
+            return true;
+        }
+        if (m_micro < v.getMicroComponent())
+        {
+            return false;
+        }
+        if (m_qualifier.compareTo(v.getQualifierComponent()) > 0)
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.ungoverned.osgi.service.bundlerepository.Version#toString()
+    **/
+    public String toString()
+    {
+        if (m_qualifier.length() == 0)
+        {
+            return m_major + "." + m_minor + "." + m_micro; 
+        }
+        return m_major + "." + m_minor + "." + m_micro + "." + m_qualifier; 
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java b/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java
new file mode 100644
index 0000000..b8b52d8
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java
@@ -0,0 +1,563 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXParser;
+import org.apache.osgi.bundle.bundlerepository.metadataparser.MultivalueMap;
+import org.apache.osgi.bundle.bundlerepository.metadataparser.XmlCommonHandler;
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.ResolveException;
+import org.osgi.framework.*;
+
+public class RepositoryState
+{
+    private BundleContext m_context = null;
+    private String[] m_urls = null;
+    private Map m_recordMap = new HashMap();
+    private BundleRecord[] m_recordArray = null;
+    private boolean m_initialized = false;
+
+    private int m_hopCount = 1;
+
+    private static final String[] DEFAULT_REPOSITORY_URL = {
+        "http://oscar-osgi.sf.net/alpha/repository.xml"
+    };
+    public static final String REPOSITORY_URL_PROP = "osgi.repository.url";
+    public static final String EXTERN_REPOSITORY_TAG = "extern-repositories";
+
+    public RepositoryState(BundleContext context)
+    {
+        m_context = context;
+
+        String urlStr = m_context.getProperty(REPOSITORY_URL_PROP);
+        if (urlStr != null)
+        {
+            StringTokenizer st = new StringTokenizer(urlStr);
+            if (st.countTokens() > 0)
+            {
+                m_urls = new String[st.countTokens()];
+                for (int i = 0; i < m_urls.length; i++)
+                {
+                    m_urls[i] = st.nextToken();
+                }
+            }
+        }
+
+        // Use the default URL if none were specified.
+        if (m_urls == null)
+        {
+            m_urls = DEFAULT_REPOSITORY_URL;
+        }
+    }
+
+    public String[] getURLs()
+    {
+        // Return a copy because the array is mutable.
+        return (m_urls == null) ? null : (String[]) m_urls.clone();
+    }
+
+    public void setURLs(String[] urls)
+    {
+        if (urls != null)
+        {
+            m_urls = urls;
+            initialize();
+        }
+    }
+    
+    public BundleRecord[] getRecords()
+    {
+        if (!m_initialized)
+        {
+            initialize();
+        }
+
+        // Returned cached array of bundle records.
+        return m_recordArray;
+    }
+
+    public BundleRecord[] getRecords(String symName)
+    {
+        if (!m_initialized)
+        {
+            initialize();
+        }
+
+        // Return a copy of the array, since it would be mutable
+        // otherwise.
+        BundleRecord[] records = (BundleRecord[]) m_recordMap.get(symName);
+        // Return a copy because the array is mutable.
+        return (records == null) ? null : (BundleRecord[]) records.clone();
+    }
+
+    public BundleRecord getRecord(String symName, int[] version)
+    {
+        if (!m_initialized)
+        {
+            initialize();
+        }
+
+        BundleRecord[] records = (BundleRecord[]) m_recordMap.get(symName);
+        if ((records != null) && (records.length > 0))
+        {
+            for (int i = 0; i < records.length; i++)
+            {
+                int[] targetVersion = Util.parseVersionString(
+                    (String) records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+            
+                if (Util.compareVersion(targetVersion, version) == 0)
+                {
+                    return records[i];
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public BundleRecord[] resolvePackages(LocalState localState, Filter[] reqFilters)
+        throws ResolveException
+    {
+        // Create a list that will contain the transitive closure of
+        // all import dependencies; use a list because this will keep
+        // everything in order.
+        List deployList = new ArrayList();
+        // Add the target bundle
+        resolvePackages(localState, reqFilters, deployList);
+        
+        // Convert list of symbolic names to an array of bundle
+        // records and return it.
+        BundleRecord[] records = new BundleRecord[deployList.size()];
+        return (BundleRecord[]) deployList.toArray(records);
+    }
+
+    private void resolvePackages(
+        LocalState localState, Filter[] reqFilters, List deployList)
+        throws ResolveException
+    {
+        for (int reqIdx = 0;
+            (reqFilters != null) && (reqIdx < reqFilters.length);
+            reqIdx++)
+        {
+            // If the package can be locally resolved, then
+            // it can be completely ignored; otherwise, try
+            // to find a resolving bundle.
+            if (!localState.isResolvable(reqFilters[reqIdx]))
+            {
+                // Select resolving bundle for current package.
+                BundleRecord source = selectResolvingBundle(
+                    deployList, localState, reqFilters[reqIdx]);
+                // If there is no resolving bundle, then throw a
+                // resolve exception.
+                if (source == null)
+                {
+throw new IllegalArgumentException("HACK: SHOULD THROW RESOLVE EXCEPTION: " + reqFilters[reqIdx]);
+//                    throw new ResolveException(reqFilters[reqIdx]);
+                }
+                // If the resolving bundle is already in the deploy list,
+                // then just ignore it; otherwise, add it to the deploy
+                // list and resolve its packages.
+                if (!deployList.contains(source))
+                {
+                    deployList.add(source);
+                    Filter[] filters = (Filter[])
+                        source.getAttribute("requirements");
+                    resolvePackages(localState, filters, deployList);
+                }
+            }
+        }
+    }
+
+    /**
+     * Selects a single source bundle record for the target package from
+     * the repository. The algorithm tries to select a source bundle record
+     * if it is already installed locally in the framework; this approach
+     * favors updating already installed bundles rather than installing
+     * new ones. If no matching bundles are installed locally, then the
+     * first bundle record providing the target package is returned.
+     * @param targetPkg the target package for which to select a source
+     *        bundle record.
+     * @return the selected bundle record or <tt>null</tt> if no sources
+     *         could be found.
+    **/
+    private BundleRecord selectResolvingBundle(
+        List deployList, LocalState localState, Filter targetFilter)
+    {
+        BundleRecord[] exporters = findExporters(targetFilter);
+        if (exporters == null)
+        {
+            return null;
+        }
+
+        // Try to select a source bundle record that is already
+        // in the deployed list to minimize the number of bundles
+        // that need to be deployed. If this is not possible, then
+        // try to select a bundle that is already installed locally,
+        // since it might be possible to update this bundle to
+        // minimize the number of bundles installed in the framework.
+        for (int i = 0; i < exporters.length; i++)
+        {
+            if (deployList.contains(exporters[i]))
+            {
+                return exporters[i];
+            }
+            else
+            {
+                String symName = (String)
+                    exporters[i].getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+                if (symName != null)
+                {
+                    BundleRecord[] records = localState.findBundles(symName);
+                    if (records != null)
+                    {
+                        return exporters[i];
+                    }
+                }
+            }
+        }
+            
+        // If none of the sources are installed locally, then
+        // just pick the first one.
+        return exporters[0];
+    }
+
+    /**
+     * Returns an array of bundle records that resolve the supplied
+     * package declaration.
+     * @param target the package declaration to resolve.
+     * @return an array of bundle records that resolve the package
+     *         declaration or <tt>null</tt> if none are found.
+    **/
+    private BundleRecord[] findExporters(Filter targetFilter)
+    {
+        MapToDictionary mapDict = new MapToDictionary(null);
+
+        // Create a list for storing bundles that can resolve package.
+        List resolveList = new ArrayList();
+        for (int recIdx = 0; recIdx < m_recordArray.length; recIdx++)
+        {
+            Map[] capMaps = (Map[]) m_recordArray[recIdx].getAttribute("capability");
+            for (int capIdx = 0; capIdx < capMaps.length; capIdx++)
+            {
+                mapDict.setSourceMap(capMaps[capIdx]);
+                if (targetFilter.match(mapDict))
+                {
+                    resolveList.add(m_recordArray[recIdx]);
+                }
+            }
+        }
+
+        // If no resolving bundles were found, return null.
+        if (resolveList.size() == 0)
+        {
+            return null;
+        }
+
+        // Otherwise, return an array containing resolving bundles.
+        return (BundleRecord[]) resolveList.toArray(new BundleRecord[resolveList.size()]);
+    }
+
+    private boolean isUpdateAvailable(
+        PrintStream out, PrintStream err, Bundle bundle)
+    {
+        // Get the bundle's update location.
+        String symname =
+            (String) bundle.getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+
+        // Get associated repository bundle recorded for the
+        // local bundle and see if an update is necessary.
+        BundleRecord[] records = getRecords(symname);
+        if (records == null)
+        {
+            err.println(Util.getBundleName(bundle) + " not in repository.");
+            return false;
+        }
+        
+        // Check bundle version againts bundle record version.
+        for (int i = 0; i < records.length; i++)
+        {
+            int[] bundleVersion = Util.parseVersionString(
+                (String) bundle.getHeaders().get(BundleRecord.BUNDLE_VERSION));
+            int[] recordVersion = Util.parseVersionString(
+                (String) records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+            if (Util.compareVersion(recordVersion, bundleVersion) > 0)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void initialize()
+    {
+        m_initialized = true;
+        m_recordMap.clear();
+
+        for (int urlIdx = 0; (m_urls != null) && (urlIdx < m_urls.length); urlIdx++)
+        {
+            parseRepositoryFile(m_hopCount, m_urls[urlIdx]);
+        }
+        
+        // Cache a sorted array of all bundle records.
+        List list = new ArrayList();
+        for (Iterator i = m_recordMap.entrySet().iterator(); i.hasNext(); )
+        {
+            BundleRecord[] records = (BundleRecord[]) ((Map.Entry) i.next()).getValue();
+            for (int recIdx = 0; recIdx < records.length; recIdx++)
+            {
+                list.add(records[recIdx]);
+            }
+        }
+        m_recordArray = (BundleRecord[]) list.toArray(new BundleRecord[list.size()]);
+        Arrays.sort(m_recordArray, new Comparator() {
+            public int compare(Object o1, Object o2)
+            {
+                BundleRecord r1 = (BundleRecord) o1;
+                BundleRecord r2 = (BundleRecord) o2;
+                String name1 = (String) r1.getAttribute(BundleRecord.BUNDLE_NAME);
+                String name2 = (String) r2.getAttribute(BundleRecord.BUNDLE_NAME);
+                return name1.compareToIgnoreCase(name2);
+            }
+        });
+    }
+
+    private void parseRepositoryFile(int hopCount, String urlStr)
+    {
+        InputStream is = null;
+        BufferedReader br = null;
+
+        try
+        {
+            // Do it the manual way to have a chance to 
+            // set request properties as proxy auth (EW).
+            URL url = new URL(urlStr);
+            URLConnection conn = url.openConnection(); 
+
+            // Support for http proxy authentication
+            String auth = System.getProperty("http.proxyAuth");
+            if ((auth != null) && (auth.length() > 0))
+            {
+                if ("http".equals(url.getProtocol()) ||
+                    "https".equals(url.getProtocol()))
+                {
+                    String base64 = Util.base64Encode(auth);
+                    conn.setRequestProperty(
+                        "Proxy-Authorization", "Basic " + base64);
+                }
+            }
+            is = conn.getInputStream();
+
+            // Create the parser Kxml
+            XmlCommonHandler handler = new XmlCommonHandler();
+            handler.addType("bundles", ArrayList.class);
+            handler.addType("repository", HashMap.class);
+            handler.addType("extern-repositories", ArrayList.class);
+            handler.addType("bundle", MultivalueMap.class);
+            handler.addType("requirement", String.class);
+            handler.addType("capability", ArrayList.class);
+            handler.addType("property", HashMap.class);
+            handler.setDefaultType(String.class);
+
+            br = new BufferedReader(new InputStreamReader(is));
+            KXmlSAXParser parser;
+            parser = new KXmlSAXParser(br);
+            try
+            {
+                parser.parseXML(handler);
+            }
+            catch (Exception ex)
+            {
+                ex.printStackTrace();
+                return;
+            }
+
+            List root = (List) handler.getRoot();
+            for (int bundleIdx = 0; bundleIdx < root.size(); bundleIdx++)
+            {
+                Object obj = root.get(bundleIdx);
+                
+                // The elements of the root will either be a HashMap for
+                // the repository tag or a MultivalueMap for the bundle
+                // tag, as indicated above when we parsed the file.
+                
+                // If HashMap, then read repository information.
+                if (obj instanceof HashMap)
+                {
+                    // Create a case-insensitive map.
+                    Map repoMap = new TreeMap(new Comparator() {
+                        public int compare(Object o1, Object o2)
+                        {
+                            return o1.toString().compareToIgnoreCase(o2.toString());
+                        }
+                    });
+                    repoMap.putAll((Map) obj);
+
+                    // Process external repositories if hop count is
+                    // greater than zero.
+                    if (hopCount > 0)
+                    {
+                        // Get the external repository list.
+                        List externList = (List) repoMap.get(EXTERN_REPOSITORY_TAG);
+                        for (int i = 0; (externList != null) && (i < externList.size()); i++)
+                        {
+                            parseRepositoryFile(hopCount - 1, (String) externList.get(i));
+                        }
+                    }
+                }
+                // Else if mulitvalue map, then create a bundle record
+                // for the associated bundle meta-data.
+                else if (obj instanceof MultivalueMap)
+                {
+                    // Create a case-insensitive map.
+                    Map bundleMap = new TreeMap(new Comparator() {
+                        public int compare(Object o1, Object o2)
+                        {
+                            return o1.toString().compareToIgnoreCase(o2.toString());
+                        }
+                    });
+                    bundleMap.putAll((Map) obj);
+
+                    // Convert capabilities into case-insensitive maps.
+                    List list = (List) bundleMap.get("capability");
+                    Map[] capabilityMaps = convertCapabilities(list);
+                    bundleMap.put("capability", capabilityMaps);
+
+                    // Convert requirements info filters.
+                    list = (List) bundleMap.get("requirement");
+                    Filter[] filters = convertRequirements(list);
+                    bundleMap.put("requirement", filters);
+
+                    // Convert any remaining single-element lists into
+                    // the element itself.
+                    for (Iterator i = bundleMap.keySet().iterator(); i.hasNext(); )
+                    {
+                        Object key = i.next();
+                        Object value = bundleMap.get(key);
+                        if ((value instanceof List) &&
+                            (((List) value).size() == 1))
+                        {
+                            bundleMap.put(key, ((List) value).get(0));
+                        }
+                    }
+
+                    // Create a bundle record using the map.
+                    BundleRecord record = new BundleRecord(bundleMap);
+                    // TODO: Filter duplicates.
+                    BundleRecord[] records =
+                        (BundleRecord[]) m_recordMap.get(
+                            record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME));
+                    if (records == null)
+                    {
+                        records = new BundleRecord[] { record };
+                    }
+                    else
+                    {
+                        BundleRecord[] newRecords = new BundleRecord[records.length + 1];
+                        System.arraycopy(records, 0, newRecords, 0, records.length);
+                        newRecords[records.length] = record;
+                        records = newRecords;
+                    }
+                    m_recordMap.put(
+                        record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME), records);
+                }
+            }
+        }
+        catch (MalformedURLException ex)
+        {
+            ex.printStackTrace(System.err);
+//            System.err.println("Error: " + ex);
+        }
+        catch (IOException ex)
+        {
+            ex.printStackTrace(System.err);
+//            System.err.println("Error: " + ex);
+        }
+        finally
+        {
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex)
+            {
+                // Not much we can do.
+            }
+        }
+    }
+
+    private Map[] convertCapabilities(List capLists)
+    {
+        Map[] capabilityMaps = new Map[(capLists == null) ? 0 : capLists.size()];
+        for (int capIdx = 0; (capLists != null) && (capIdx < capLists.size()); capIdx++)
+        {
+            // Create a case-insensitive map.
+            capabilityMaps[capIdx] = new TreeMap(new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    return o1.toString().compareToIgnoreCase(o2.toString());
+                }
+            });
+
+            List capList = (List) capLists.get(capIdx);
+            
+            for (int propIdx = 0; propIdx < capList.size(); propIdx++)
+            {
+                Map propMap = (Map) capList.get(propIdx);
+                String name = (String) propMap.get("name");
+                String type = (String) propMap.get("type");
+                String value = (String) propMap.get("value");
+                try
+                {
+                    Class clazz = this.getClass().getClassLoader().loadClass(type);
+                    Object o = clazz
+                        .getConstructor(new Class[] { String.class })
+                            .newInstance(new Object[] { value });
+                    capabilityMaps[capIdx].put(name, o);
+                }
+                catch (Exception ex)
+                {
+// TODO: DETERMINE WHAT TO DO HERE.
+                    // Two options here, we can either ignore the
+                    // entire capability or we can just ignore the
+                    // property. For now, just ignore the property.
+                    continue;
+                }
+            }
+        }
+        return capabilityMaps;
+    }
+
+    private Filter[] convertRequirements(List reqsList)
+    {
+        Filter[] filters = new Filter[(reqsList == null) ? 0 : reqsList.size()];
+        for (int i = 0; (reqsList != null) && (i < reqsList.size()); i++)
+        {
+            try
+            {
+                filters[i] = m_context.createFilter((String) reqsList.get(i));
+            }
+            catch (InvalidSyntaxException ex)
+            {
+            }
+        }
+        return filters;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/Util.java b/src/org/apache/osgi/bundle/bundlerepository/Util.java
new file mode 100644
index 0000000..7c40551
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/Util.java
@@ -0,0 +1,208 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+public class Util
+{
+    public static String getBundleName(Bundle bundle)
+    {
+        String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+        return (name == null)
+            ? "Bundle " + Long.toString(bundle.getBundleId())
+            : name;
+    }
+
+    public static int compareVersion(int[] v1, int[] v2)
+    {
+        if (v1[0] > v2[0])
+        {
+            return 1;
+        }
+        else if (v1[0] < v2[0])
+        {
+            return -1;
+        }
+        else if (v1[1] > v2[1])
+        {
+            return 1;
+        }
+        else if (v1[1] < v2[1])
+        {
+            return -1;
+        }
+        else if (v1[2] > v2[2])
+        {
+            return 1;
+        }
+        else if (v1[2] < v2[2])
+        {
+            return -1;
+        }
+        return 0;
+    }
+
+    public static int[] parseVersionString(String s)
+    {
+        int[] version = new int[] { 0, 0, 0 };
+
+        if (s != null)
+        {
+            StringTokenizer st = new StringTokenizer(s, ".");
+            if (st.hasMoreTokens())
+            {
+                try
+                {
+                    version[0] = Integer.parseInt(st.nextToken());
+                    if (st.hasMoreTokens())
+                    {
+                        version[1] = Integer.parseInt(st.nextToken());
+                        if (st.hasMoreTokens())
+                        {
+                            version[2] = Integer.parseInt(st.nextToken());
+                        }
+                    }
+                    return version;
+                }
+                catch (NumberFormatException ex)
+                {
+                    throw new IllegalArgumentException(
+                        "Improper version number.");
+                }
+            }
+        }
+
+        return version;
+    }
+
+    private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
+        0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
+        0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64,
+        0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
+        0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31,
+        0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f };
+
+    private static final byte decTab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1,
+        -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1,
+        -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+        18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
+        30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+        48, 49, 50, 51, -1, -1, -1, -1, -1 };
+
+    public static String base64Encode(String s) throws IOException
+    {
+        return encode(s.getBytes(), 0);
+    }
+
+    /**
+     * Encode a raw byte array to a Base64 String.
+     * 
+     * @param in Byte array to encode.
+     * @param len Length of Base64 lines. 0 means no line breaks.
+    **/
+    public static String encode(byte[] in, int len) throws IOException
+    {
+        ByteArrayOutputStream baos = null;
+        ByteArrayInputStream bais = null;
+        try
+        {
+            baos = new ByteArrayOutputStream();
+            bais = new ByteArrayInputStream(in);
+            encode(bais, baos, len);
+            // ASCII byte array to String
+            return (new String(baos.toByteArray()));
+        }
+        finally
+        {
+            if (baos != null)
+            {
+                baos.close();
+            }
+            if (bais != null)
+            {
+                bais.close();
+            }
+        }
+    }
+
+    public static void encode(InputStream in, OutputStream out, int len)
+        throws IOException
+    {
+
+        // Check that length is a multiple of 4 bytes
+        if (len % 4 != 0)
+        {
+            throw new IllegalArgumentException("Length must be a multiple of 4");
+        }
+
+        // Read input stream until end of file
+        int bits = 0;
+        int nbits = 0;
+        int nbytes = 0;
+        int b;
+
+        while ((b = in.read()) != -1)
+        {
+            bits = (bits << 8) | b;
+            nbits += 8;
+            while (nbits >= 6)
+            {
+                nbits -= 6;
+                out.write(encTab[0x3f & (bits >> nbits)]);
+                nbytes++;
+                // New line
+                if (len != 0 && nbytes >= len)
+                {
+                    out.write(0x0d);
+                    out.write(0x0a);
+                    nbytes -= len;
+                }
+            }
+        }
+
+        switch (nbits)
+        {
+            case 2:
+                out.write(encTab[0x3f & (bits << 4)]);
+                out.write(0x3d); // 0x3d = '='
+                out.write(0x3d);
+                break;
+            case 4:
+                out.write(encTab[0x3f & (bits << 2)]);
+                out.write(0x3d);
+                break;
+        }
+
+        if (len != 0)
+        {
+            if (nbytes != 0)
+            {
+                out.write(0x0d);
+                out.write(0x0a);
+            }
+            out.write(0x0d);
+            out.write(0x0a);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java
new file mode 100644
index 0000000..b6a8e1d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java
@@ -0,0 +1,68 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository.kxmlsax;
+
+import java.util.Properties;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Interface for SAX handler with kXML
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public interface KXmlSAXHandler {
+
+	/**
+	* Method called when parsing text
+	*
+	* @param   ch
+	* @param   offset
+	* @param   length
+	* @exception   SAXException
+	*/
+	public void characters(char[] ch, int offset, int length) throws Exception;
+
+	/**
+	* Method called when a tag opens
+	*
+	* @param   uri
+	* @param   localName
+	* @param   qName
+	* @param   attrib
+	* @exception   SAXException
+	**/
+	public void startElement(
+		String uri,
+		String localName,
+		String qName,
+		Properties attrib)
+		throws Exception;
+	/**
+	* Method called when a tag closes
+	*
+	* @param   uri
+	* @param   localName
+	* @param   qName
+	* @exception   SAXException
+	*/
+	public void endElement(
+		java.lang.String uri,
+		java.lang.String localName,
+		java.lang.String qName)
+		throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java
new file mode 100644
index 0000000..1bb9b25
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java
@@ -0,0 +1,79 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository.kxmlsax;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Properties;
+
+import org.kxml.Attribute;
+import org.kxml.Xml;
+import org.kxml.parser.ParseEvent;
+import org.kxml.parser.XmlParser;
+
+/**
+ * The KXmlSAXParser extends the XmlParser from kxml. This is a very
+ * simple parser that does not take into account the DTD
+ *
+ * @version 	1.0 08 Nov 2002
+ * @version 	1.1 24 Apr 2004
+ * @author 	Humberto Cervantes, Didier Donsez
+ */
+public class KXmlSAXParser extends XmlParser {
+	/**
+	* The constructor for a parser, it receives a java.io.Reader.
+	*
+	* @param   reader  The reader
+	* @exception   IOException thrown by the superclass
+	*/
+	public KXmlSAXParser(Reader r) throws IOException {
+		super(r);
+	}
+
+	/**
+	* Parser from the reader provided in the constructor, and call
+	* the startElement and endElement in a KxmlHandler
+	*
+	* @param   reader  The reader
+	* @exception   Exception thrown by the superclass
+	*/
+	public void parseXML(KXmlSAXHandler handler) throws Exception {
+		ParseEvent evt = null;
+		do {
+			evt = read();
+			if (evt.getType() == Xml.START_TAG) {
+				Properties props = new Properties();
+				for (int i = 0; i < evt.getAttributeCount(); i++) {
+					Attribute attr = evt.getAttribute(i);
+					props.put(attr.getName(), attr.getValue());
+				}
+				handler.startElement(
+					"uri",
+					evt.getName(),
+					evt.getName(),
+					props);
+			} else if (evt.getType() == Xml.END_TAG) {
+				handler.endElement("uri", evt.getName(), evt.getName());
+			} else if (evt.getType() == Xml.TEXT) {
+				String text = evt.getText();
+				handler.characters(text.toCharArray(),0,text.length());
+			} else {
+				// do nothing
+			}
+		} while (evt.getType() != Xml.END_DOCUMENT);
+	}
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/manifest.mf b/src/org/apache/osgi/bundle/bundlerepository/manifest.mf
new file mode 100644
index 0000000..ad49efe
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/manifest.mf
@@ -0,0 +1,10 @@
+Bundle-Name: Bundle Repository
+Bundle-SymbolicName: org.apache.osgi.bundle.bundlerepository
+Bundle-Description: A simple bundle repository for Felix.
+Bundle-Activator: org.apache.osgi.bundle.bundlerepository.Activator
+Bundle-ClassPath: .,org/apache/osgi/bundle/bundlerepository/kxml.jar
+Bundle-Version: 2.0.0.alpha2
+Import-Package: org.osgi.framework
+DynamicImport-Package: org.apache.osgi.service.shell
+Export-Package: 
+ org.apache.osgi.service.bundlerepository; specification-version="1.1.0"
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java
new file mode 100644
index 0000000..fd9b6e9
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java
@@ -0,0 +1,97 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository.metadataparser;
+
+/**
+ * This class provides methods to process class name
+ */
+
+public class ClassUtility {
+
+	/**
+	 * This method capitalizes the first character in the provided string.
+	 * @return resulted string
+	 */
+	public static String capitalize(String name) {
+
+		int len=name.length();
+		StringBuffer sb=new StringBuffer(len);
+		boolean setCap=true;
+		for(int i=0; i<len; i++){
+			char c=name.charAt(i);
+			if(c=='-' || c=='_') {
+				setCap=true;			
+			} else {
+				if(setCap){
+					sb.append(Character.toUpperCase(c));
+					setCap=false;
+				} else {
+					sb.append(c);
+				}
+			}
+		} 
+ 
+		return sb.toString();
+	}
+
+	/**
+	 * This method capitalizes all characters in the provided string.
+	 * @return resulted string
+	 */
+	public static String finalstaticOf(String membername) {
+		int len=membername.length();
+		StringBuffer sb=new StringBuffer(len+2);
+		for(int i=0; i<len; i++){
+			char c=membername.charAt(i);
+			if(Character.isLowerCase(c) ) {
+				sb.append(Character.toUpperCase(c));
+			} else if(Character.isUpperCase(c) ) {
+				sb.append('_').append(c);
+			} else {
+				sb.append(c);				
+			}
+		} 
+ 
+		return sb.toString();
+	}
+	
+	/**
+	 * This method returns the package name in a full class name
+	 * @return resulted string
+	 */
+	public static String packageOf(String fullclassname) {
+		int index=fullclassname.lastIndexOf(".");
+		if(index>0) {
+			return fullclassname.substring(0,index);
+		} else {
+			return "";	
+		}
+	}
+
+	/**
+	 * This method returns the package name in a full class name
+	 * @return resulted string
+	 */
+	public static String classOf(String fullclassname) {
+		int index=fullclassname.lastIndexOf(".");
+		if(index>0) {
+			return fullclassname.substring(index+1);
+		} else {
+			return fullclassname;	
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java
new file mode 100644
index 0000000..077a551
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java
@@ -0,0 +1,63 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository.metadataparser;
+
+import java.io.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXParser;
+
+
+/**
+ * handles the metadata in XML format
+ * (use kXML (http://kxml.enhydra.org/) a open-source very light weight XML parser
+ * @version 	1.00 11 Nov 2003
+ * @author 	Didier Donsez
+ */
+public class KXmlMetadataHandler /*implements MetadataHandler*/ {
+
+	private XmlCommonHandler handler;
+
+	public KXmlMetadataHandler() {
+		handler = new XmlCommonHandler();
+	}
+
+	/**
+	* Called to parse the InputStream and set bundle list and package hash map
+	*/
+	public void parse(InputStream is) throws Exception {
+		BufferedReader br = new BufferedReader(new InputStreamReader(is));
+		KXmlSAXParser parser;
+		parser = new KXmlSAXParser(br);
+		parser.parseXML(handler);
+	}
+
+	/**
+	 * return the metadata
+	 * @return a Objet
+	 */
+	public Object getMetadata() {
+		return handler.getRoot();
+	}
+
+	public void addType(String qname, Class clazz) {
+		handler.addType(qname, clazz);
+	}
+
+	public void setDefaultType(Class clazz) {
+		handler.setDefaultType(clazz);
+	}
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java
new file mode 100644
index 0000000..ba29423
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java
@@ -0,0 +1,142 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository.metadataparser;
+
+import java.util.*;
+
+public class MultivalueMap implements Map {
+	private Map m_map = null;
+
+	public MultivalueMap() {
+		m_map = new HashMap();
+	}
+
+	public MultivalueMap(Map map) {
+		m_map = map;
+	}
+
+	/**
+	 * @see java.util.Map#size()
+	 */
+	public int size() {
+		return m_map.size();
+	}
+
+	/**
+	 * @see java.util.Map#clear()
+	 */
+	public void clear() {
+		m_map.clear();
+	}
+
+	/**
+	 * @see java.util.Map#isEmpty()
+	 */
+	public boolean isEmpty() {
+		return m_map.isEmpty();
+	}
+
+	/**
+	 * @see java.util.Map#containsKey(java.lang.Object)
+	 */
+	public boolean containsKey(Object arg0) {
+		return m_map.containsKey(arg0);
+	}
+
+	/**
+	 * @see java.util.Map#containsValue(java.lang.Object)
+	 */
+	public boolean containsValue(Object arg0) {
+		return false;
+	}
+
+	/**
+	 * @see java.util.Map#values()
+	 */
+	public Collection values() {
+		return null;
+	}
+
+	/**
+	 * @see java.util.Map#putAll(java.util.Map)
+	 */
+	public void putAll(Map arg0) {
+	}
+
+	/**
+	 * @see java.util.Map#entrySet()
+	 */
+	public Set entrySet() {
+		return m_map.entrySet();
+	}
+
+	/**
+	 * @see java.util.Map#keySet()
+	 */
+	public Set keySet() {
+		return m_map.keySet();
+	}
+
+	/**
+	 * @see java.util.Map#get(java.lang.Object)
+	 */
+	public Object get(Object key) {
+		return m_map.get(key);
+	}
+
+	/**
+	 * @see java.util.Map#remove(java.lang.Object)
+	 */
+	public Object remove(Object arg0) {
+		return m_map.remove(arg0);
+	}
+
+	/**
+	 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
+	 */
+	public Object put(Object key, Object value) {
+		Object prev = m_map.get(key);
+		if (prev == null) {
+            List list = new ArrayList();
+            list.add(value);
+			m_map.put(key, list);
+			return list;
+		} else {
+            ((List) prev).add(value);
+            return prev;
+		}
+	}
+
+	public String toString() {
+		StringBuffer sb=new StringBuffer();
+		sb.append("[MultivalueMap:");
+		if(m_map.isEmpty()) {
+			sb.append("empty");
+		} else {
+			Set keys=m_map.keySet();
+			Iterator iter=keys.iterator();
+			while(iter.hasNext()){
+				String key=(String)iter.next();
+				sb.append("\n\"").append(key).append("\":");
+				sb.append(m_map.get(key).toString());		
+			}
+			sb.append('\n');
+		}
+		sb.append(']');
+		return sb.toString();
+	}
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java
new file mode 100644
index 0000000..628b4c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java
@@ -0,0 +1,405 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.bundlerepository.metadataparser;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * SAX handler for the XML OBR file
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public class XmlCommonHandler implements KXmlSAXHandler {
+
+    private static final String PI_MAPPING="mapping";
+
+    private int columnNumber;
+
+    private int lineNumber;
+
+    //
+    // Data
+    //
+
+    private Object root;
+
+    private Stack objectStack;
+    private Stack qnameStack;
+
+    private Map types;
+    private Class defaultType;
+
+    private StringBuffer currentText;
+
+    public XmlCommonHandler() {
+        objectStack = new Stack();
+        qnameStack = new Stack();
+        types = new HashMap();
+    }
+
+    public void addType(String qname, Class clazz) {
+        types.put(qname, clazz);
+    }
+
+    public void setDefaultType(Class clazz) {
+        defaultType=clazz;
+    }
+
+    public Object getRoot() {
+        return root;
+    }
+
+    /* for PCDATA */
+    public void characters(char[] ch, int offset, int length)
+        throws Exception {
+        if (currentText != null)
+            currentText.append(ch, offset, length);
+    }
+
+    private String adderOf(Class clazz) {
+        return "add"
+            + ClassUtility.capitalize(ClassUtility.classOf(clazz.getName()));
+    }
+
+    private String adderOf(String key) {
+        return "add" + ClassUtility.capitalize(key);
+    }
+
+    private String setterOf(Class clazz) {
+        return "set"
+            + ClassUtility.capitalize(ClassUtility.classOf(clazz.getName()));
+    }
+
+    private String setterOf(String key) {
+        return "set" + ClassUtility.capitalize(key);
+    }
+
+    /**
+    * Method called when a tag opens
+    *
+    * @param   uri
+    * @param   localName
+    * @param   qName
+    * @param   attrib
+    * @exception   SAXException
+    **/
+    public void startElement(
+        String uri,
+        String localName,
+        String qName,
+        Properties attrib)
+        throws Exception {
+
+        trace("START ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+        Class clazz = (Class) types.get(qName);
+        // TODO: should add uri in the future
+
+        if(clazz==null && defaultType!=null)
+            clazz=defaultType;
+
+        Object obj;
+        if (clazz != null) {
+
+            try {
+                obj = clazz.newInstance();
+            } catch (InstantiationException e) {
+                throw new Exception(lineNumber+","+columnNumber+":"+
+                    "class "+clazz.getName()+" for element " + qName + " should have an empty constructor");
+            } catch (IllegalAccessException e) {
+                throw new Exception(lineNumber+","+columnNumber+":"+
+                    "illegal access on the empty constructor of class "+clazz.getName()+" for element " + qName);
+            }
+
+            Set keyset = attrib.keySet();
+            Iterator iter = keyset.iterator();
+            while (iter.hasNext()) {
+                String key = (String) iter.next();
+
+                if (obj instanceof Map) {
+                    ((Map) obj).put(key, attrib.get(key));
+                } else if (obj instanceof List) {
+                    throw new Exception(lineNumber+","+columnNumber+":"+
+                        "List element " + qName + " cannot have any attribute");
+                } else if (obj instanceof String) {
+                    if(key.equals("value")){
+                        obj=(String)attrib.get(key);
+                    } else {
+                        throw new Exception(lineNumber+","+columnNumber+":"+
+                            "String element " + qName + " cannot have other attribute than value");
+                    }
+                } else {
+                    Method method = null;
+                    try {
+                        method =
+                            clazz.getMethod(
+                                setterOf(key),
+                                new Class[] { String.class });
+                    } catch (NoSuchMethodException e) {
+                        // do nothing
+                    }
+                    if (method == null)
+                        try {
+                            method =
+                                clazz.getMethod(
+                                    adderOf(key),
+                                    new Class[] { String.class });
+
+                        } catch (NoSuchMethodException e) {
+                            throw new Exception(lineNumber+","+columnNumber+":"+
+                                "element "
+                                    + qName
+                                    + " does not support the attribute "
+                                    + key);
+                        }
+                    if (method != null)
+                        method.invoke(
+                            obj,
+                            new String[] {(String) attrib.get(key)});
+                }
+
+            }
+
+        } else {
+            throw new Exception(lineNumber+","+columnNumber+":"+
+                "this element " + qName + " has not corresponding class");
+        }
+
+        if (root == null)
+            root = obj;
+        objectStack.push(obj);
+        qnameStack.push(qName);
+        currentText = new StringBuffer();
+
+        trace("START/ ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+    }
+
+    /**
+    * Method called when a tag closes
+    *
+    * @param   uri
+    * @param   localName
+    * @param   qName
+    * @exception   SAXException
+    */
+    public void endElement(
+        java.lang.String uri,
+        java.lang.String localName,
+        java.lang.String qName)
+        throws Exception {
+
+        trace("END ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+        Object obj = objectStack.pop();
+
+        if (currentText != null && currentText.length() != 0) {
+            if (obj instanceof Map) {
+                ((Map) obj).put(qName, currentText.toString().trim());
+            } else if (obj instanceof List) {
+                throw new Exception(lineNumber+","+columnNumber+":"+
+                    "List element " + qName + " cannot have PCDATAs");
+            } else if (obj instanceof String) {
+                String str=(String)obj;
+                if(str.length()!=0){
+                    throw new Exception(lineNumber+","+columnNumber+":"+
+                        "String element " + qName + " cannot have both PCDATA and an attribute value");
+                } else {
+                    obj=currentText.toString().trim();
+                }
+            } else {
+                Method method = null;
+                try {
+                    method =
+                        obj.getClass().getMethod(
+                            "addText",
+                            new Class[] { String.class });
+                } catch (NoSuchMethodException e) {
+                    // do nothing
+                }
+                if (method != null) {
+                    method.invoke(obj, new String[] { currentText.toString().trim()});
+                }
+            }
+        }
+
+        currentText = null;
+
+        if (!objectStack.isEmpty()) {
+
+            Object parent = objectStack.peek();
+            String parentName = (String) qnameStack.peek();
+
+            if (parent instanceof Map) {
+                ((Map) parent).put(qName, obj);
+            } else if (parent instanceof List) {
+                ((List) parent).add(obj);
+            } else {
+                Method method = null;
+                try {
+                    method =
+                        parent.getClass().getMethod(
+                            adderOf(ClassUtility.capitalize(qName)),
+                            new Class[] { obj.getClass()});
+                } catch (NoSuchMethodException e) {
+                    trace(
+                        "NoSuchMethodException: "
+                            + adderOf(ClassUtility.capitalize(qName)));
+                    // do nothing
+                }
+                if (method == null)
+                    try {
+                        method =
+                            parent.getClass().getMethod(
+                                setterOf(ClassUtility.capitalize(qName)),
+                                new Class[] { obj.getClass()});
+                    } catch (NoSuchMethodException e) {
+                        trace(
+                            "NoSuchMethodException: "
+                                + setterOf(ClassUtility.capitalize(qName)));
+                        // do nothing
+                    }
+                if (method == null)
+                    try {
+                        method =
+                            parent.getClass().getMethod(
+                                adderOf(obj.getClass()),
+                                new Class[] { obj.getClass()});
+                    } catch (NoSuchMethodException e) {
+                        trace(
+                            "NoSuchMethodException: "
+                                + adderOf(obj.getClass()));
+                        // do nothing
+                    }
+                if (method == null)
+                    try {
+                        method =
+                            parent.getClass().getMethod(
+                                setterOf(obj.getClass()),
+                                new Class[] { obj.getClass()});
+                    } catch (NoSuchMethodException e) {
+                        trace(
+                            "NoSuchMethodException: "
+                                + setterOf(obj.getClass()));
+                        // do nothing
+                    }
+
+                if (method != null) {
+                    trace(method.getName());
+                    method.invoke(parent, new Object[] { obj });
+                } else {
+                    throw new Exception(lineNumber+","+columnNumber+":"+
+                        " element " + parentName + " cannot have an attribute " + qName + " of type " + obj.getClass());
+                }
+            }
+
+        }
+
+        trace("END/ ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+    }
+
+    private void trace(String msg) {
+        if (false)
+            System.err.println(msg);
+    }
+
+    /**
+     * @see kxml.sax.KXmlSAXHandler#setLineNumber(int)
+     */
+    public void setLineNumber(int lineNumber) {
+        this.lineNumber=lineNumber;
+    }
+
+    /**
+     * @see kxml.sax.KXmlSAXHandler#setColumnNumber(int)
+     */
+    public void setColumnNumber(int columnNumber) {
+        this.columnNumber=columnNumber;
+
+    }
+
+    /**
+     * @see kxml.sax.KXmlSAXHandler#processingInstruction(java.lang.String, java.lang.String)
+     */
+    public void processingInstruction(String target, String data) throws Exception {
+        trace("pi:"+target+";"+data);
+        if(target==null){ // TODO kXML
+            if(!data.startsWith(PI_MAPPING)) return;
+        } else if(!target.equals(PI_MAPPING))return;
+
+
+        // defaultclass attribute
+        String datt="defaultclass=\"";
+        int dstart=data.indexOf(datt);
+        if(dstart!=-1) {
+            int dend=data.indexOf("\"",dstart+datt.length());
+            if(dend==-1)
+                throw new Exception(lineNumber+","+columnNumber+":"+
+                    " \"defaultclass\" attribute in \"mapping\" PI is not quoted");
+
+            String classname=data.substring(dstart+datt.length(),dend);
+            Class clazz=null;
+            try {
+                clazz=getClass().getClassLoader().loadClass(classname);
+            } catch (ClassNotFoundException e) {
+                throw new Exception(lineNumber+","+columnNumber+":"+
+                    " cannot found class "+ classname+" for \"mapping\" PI");
+            }
+            setDefaultType(clazz);
+            return;
+        }
+
+        // element attribute
+        String eatt="element=\"";
+        int estart=data.indexOf(eatt);
+        if(estart==-1)
+            throw new Exception(lineNumber+","+columnNumber+":"+
+                " missing \"element\" attribute in \"mapping\" PI");
+        int eend=data.indexOf("\"",estart+eatt.length());
+        if(eend==-1)
+        throw new Exception(lineNumber+","+columnNumber+":"+
+            " \"element\" attribute in \"mapping\" PI is not quoted");
+
+        String element=data.substring(estart+eatt.length(),eend);
+
+        // element class
+        String catt="class=\"";
+        int cstart=data.indexOf(catt);
+        if(cstart==-1)
+            throw new Exception(lineNumber+","+columnNumber+":"+
+                " missing \"class\" attribute in \"mapping\" PI");
+        int cend=data.indexOf("\"",cstart+catt.length());
+        if(cend==-1)
+        throw new Exception(lineNumber+","+columnNumber+":"+
+            " \"class\" attribute in \"mapping\" PI is not quoted");
+
+        String classname=data.substring(cstart+catt.length(),cend);
+
+        Class clazz=null;
+        try {
+            clazz=getClass().getClassLoader().loadClass(classname);
+        } catch (ClassNotFoundException e) {
+            throw new Exception(lineNumber+","+columnNumber+":"+
+                " cannot found class "+ classname+" for \"mapping\" PI");
+        }
+        addType(element,clazz);
+    }
+}
diff --git a/src/org/apache/osgi/bundle/shell/Activator.java b/src/org/apache/osgi/bundle/shell/Activator.java
new file mode 100644
index 0000000..348d586
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/Activator.java
@@ -0,0 +1,351 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.security.*;
+import java.util.*;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class Activator implements BundleActivator
+{
+    private transient BundleContext m_context = null;
+    private transient ShellServiceImpl m_shell = null;
+
+    public void start(BundleContext context)
+    {
+        m_context = context;
+
+        // Register shell service implementation.
+        String[] classes = {
+            org.apache.osgi.service.shell.ShellService.class.getName(),
+            org.ungoverned.osgi.service.shell.ShellService.class.getName()
+        };
+        context.registerService(classes, m_shell = new ShellServiceImpl(), null);
+
+        // Listen for registering/unregistering of shell command
+        // services so that we can automatically add/remove them
+        // from our list of available commands.
+        ServiceListener sl = new ServiceListener() {
+            public void serviceChanged(ServiceEvent event)
+            {
+                if (event.getType() == ServiceEvent.REGISTERED)
+                {
+                    m_shell.addCommand(event.getServiceReference());
+                }
+                else if (event.getType() == ServiceEvent.UNREGISTERING)
+                {
+                    m_shell.removeCommand(event.getServiceReference());
+                }
+                else
+                {
+                }
+            }
+        };
+
+        try
+        {
+            m_context.addServiceListener(sl,
+                "(|(objectClass="
+                + org.apache.osgi.service.shell.Command.class.getName()
+                + ")(objectClass="
+                + org.ungoverned.osgi.service.shell.Command.class.getName()
+                + "))");
+        }
+        catch (InvalidSyntaxException ex)
+        {
+            System.err.println("Activator: Cannot register service listener.");
+            System.err.println("Activator: " + ex);
+        }
+
+        // Now manually try to find any commands that have already
+        // been registered (i.e., we didn't see their service events).
+        initializeCommands();
+
+        // Register "exports" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new BundleLevelCommandImpl(m_context), null);
+
+        // Register "cd" command service.
+        classes = new String[2];
+        classes[0] = org.apache.osgi.service.shell.Command.class.getName();
+        classes[1] = org.apache.osgi.service.shell.CdCommand.class.getName();
+        context.registerService(
+            classes, new CdCommandImpl(m_context), null);
+
+        // Register "exports" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new PackagesCommandImpl(m_context), null);
+
+        // Register "headers" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new HeadersCommandImpl(m_context), null);
+
+        // Register "help" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new HelpCommandImpl(m_context), null);
+
+        // Register "install" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new InstallCommandImpl(m_context), null);
+
+        // Register "ps" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new PsCommandImpl(m_context), null);
+
+        // Register "refresh" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new RefreshCommandImpl(m_context), null);
+
+        // Register "services" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new ServicesCommandImpl(m_context), null);
+
+        // Register "startlevel" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new StartLevelCommandImpl(m_context), null);
+
+        // Register "shutdown" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new ShutdownCommandImpl(m_context), null);
+
+        // Register "start" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new StartCommandImpl(m_context), null);
+
+        // Register "stop" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new StopCommandImpl(m_context), null);
+
+        // Register "uninstall" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new UninstallCommandImpl(m_context), null);
+
+        // Register "update" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new UpdateCommandImpl(m_context), null);
+
+        // Register "version" command service.
+        context.registerService(
+            org.apache.osgi.service.shell.Command.class.getName(),
+            new VersionCommandImpl(m_context), null);
+    }
+
+    public void stop(BundleContext context)
+    {
+        m_shell.clearCommands();
+    }
+
+    private void initializeCommands()
+    {
+        synchronized (m_shell)
+        {
+            try
+            {
+                ServiceReference[] refs = m_context.getServiceReferences(
+                    org.apache.osgi.service.shell.Command.class.getName(), null);
+                if (refs != null)
+                {
+                    for (int i = 0; i < refs.length; i++)
+                    {
+                        m_shell.addCommand(refs[i]);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                System.err.println("Activator: " + ex);
+            }
+        }
+    }
+
+    private class ShellServiceImpl implements
+        org.apache.osgi.service.shell.ShellService,
+        org.ungoverned.osgi.service.shell.ShellService
+    {
+        private HashMap m_commandRefMap = new HashMap();
+        private TreeMap m_commandNameMap = new TreeMap();
+
+        public synchronized String[] getCommands()
+        {
+            Set ks = m_commandNameMap.keySet();
+            String[] cmds = (ks == null)
+                ? new String[0] : (String[]) ks.toArray(new String[ks.size()]);
+            return cmds;
+        }
+
+        public synchronized String getCommandUsage(String name)
+        {
+            Command command = (Command) m_commandNameMap.get(name);
+            return (command == null) ? null : command.getUsage();
+        }
+
+        public synchronized String getCommandDescription(String name)
+        {
+            Command command = (Command) m_commandNameMap.get(name);
+            return (command == null) ? null : command.getShortDescription();
+        }
+
+        public synchronized ServiceReference getCommandReference(String name)
+        {
+            return (ServiceReference) m_commandNameMap.get(name);
+        }
+
+        public synchronized void removeCommand(ServiceReference ref)
+        {
+            Command command = (Command) m_commandRefMap.remove(ref);
+            if (command != null)
+            {
+                m_commandNameMap.remove(command.getName());
+            }
+        }
+
+        public synchronized void executeCommand(
+            String commandLine, PrintStream out, PrintStream err) throws Exception
+        {
+            commandLine = commandLine.trim();
+            String commandName = (commandLine.indexOf(' ') >= 0)
+                ? commandLine.substring(0, commandLine.indexOf(' ')) : commandLine;
+            Command command = getCommand(commandName);
+            if (command != null)
+            {
+                if (System.getSecurityManager() != null)
+                {
+                    try
+                    {
+                        AccessController.doPrivileged(
+                            new ExecutePrivileged(command, commandLine, out, err));
+                    }
+                    catch (PrivilegedActionException ex)
+                    {
+                        throw ex.getException();
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        command.execute(commandLine, out, err);
+                    }
+                    catch (Throwable ex)
+                    {
+                        err.println("Unable to execute command: " + ex);
+                        ex.printStackTrace(err);
+                    }
+                }
+            }
+            else
+            {
+                err.println("Command not found.");
+            }
+        }
+
+        protected synchronized Command getCommand(String name)
+        {
+            Command command = (Command) m_commandNameMap.get(name);
+            return (command == null) ? null : command;
+        }
+
+        protected synchronized void addCommand(ServiceReference ref)
+        {
+            Object cmdObj = m_context.getService(ref);
+            Command command =
+                (cmdObj instanceof org.ungoverned.osgi.service.shell.Command)
+                ? new OldCommandWrapper((org.ungoverned.osgi.service.shell.Command) cmdObj)
+                : (Command) cmdObj;
+            m_commandRefMap.put(ref, command);
+            m_commandNameMap.put(command.getName(), command);
+        }
+
+        protected synchronized void clearCommands()
+        {
+            m_commandRefMap.clear();
+            m_commandNameMap.clear();
+        }
+    }
+
+    private static class OldCommandWrapper implements Command
+    {
+        private org.ungoverned.osgi.service.shell.Command m_oldCommand = null;
+
+        public OldCommandWrapper(org.ungoverned.osgi.service.shell.Command oldCommand)
+        {
+            m_oldCommand = oldCommand;
+        }
+
+        public String getName()
+        {
+            return m_oldCommand.getName();
+        }
+
+        public String getUsage()
+        {
+            return m_oldCommand.getUsage();
+        }
+
+        public String getShortDescription()
+        {
+            return m_oldCommand.getShortDescription();
+        }
+
+        public void execute(String line, PrintStream out, PrintStream err)
+        {
+            m_oldCommand.execute(line, out, err);
+        }
+    }
+
+    public static class ExecutePrivileged implements PrivilegedExceptionAction
+    {
+        private Command m_command = null;
+        private String m_commandLine = null;
+        private PrintStream m_out = null;
+        private PrintStream m_err = null;
+
+        public ExecutePrivileged(
+            Command command, String commandLine,
+            PrintStream out, PrintStream err)
+            throws Exception
+        {
+            m_command = command;
+            m_commandLine = commandLine;
+            m_out = out;
+            m_err = err;
+        }
+
+        public Object run() throws Exception
+        {
+            m_command.execute(m_commandLine, m_out, m_err);
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/BundleLevelCommandImpl.java b/src/org/apache/osgi/bundle/shell/BundleLevelCommandImpl.java
new file mode 100644
index 0000000..06fc95c
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/BundleLevelCommandImpl.java
@@ -0,0 +1,167 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.startlevel.StartLevel;
+
+public class BundleLevelCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public BundleLevelCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "bundlelevel";
+    }
+
+    public String getUsage()
+    {
+        return "bundlelevel <level> <id> ... | <id>";
+    }
+
+    public String getShortDescription()
+    {
+        return "set or get bundle start level.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        // Get start level service.
+        ServiceReference ref = m_context.getServiceReference(
+            org.osgi.service.startlevel.StartLevel.class.getName());
+        if (ref == null)
+        {
+            out.println("StartLevel service is unavailable.");
+            return;
+        }
+
+        StartLevel sl = (StartLevel) m_context.getService(ref);
+        if (sl == null)
+        {
+            out.println("StartLevel service is unavailable.");
+            return;
+        }
+
+        // Parse command line.
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // If there is only one token, then assume it is
+        // a bundle ID for which we must retrieve the bundle
+        // level.
+        if (st.countTokens() == 1)
+        {
+            // Get the bundle and display start level.
+            Bundle bundle = null;
+            String token = null;
+            try
+            {
+                token = st.nextToken();
+                long id = Long.parseLong(token);
+                bundle = m_context.getBundle(id);
+                if (bundle != null)
+                {
+                    out.println("Bundle " + token + " is level "
+                        + sl.getBundleStartLevel(bundle));
+                }
+                else
+                {
+                    err.println("Bundle ID " + token + " is invalid.");
+                }
+            }
+            catch (NumberFormatException ex)
+            {
+                err.println("Unable to parse integer '" + token + "'.");
+            }
+            catch (Exception ex)
+            {
+                err.println(ex.toString());
+            }
+        }
+        // If there is more than one token, assume the first
+        // token is the new start level and the remaining
+        // tokens are the bundle IDs whose start levels should
+        // be changed.
+        else if (st.countTokens() > 1)
+        {
+            // Get the bundle.
+            Bundle bundle = null;
+            String token = null;
+            int startLevel = -1;
+
+            try
+            {
+                token = st.nextToken();
+                startLevel = Integer.parseInt(token);
+            }
+            catch (NumberFormatException ex)
+            {
+                err.println("Unable to parse start level '" + token + "'.");
+            }
+
+            // Ignore invalid start levels.
+            if (startLevel > 0)
+            {
+                // Set the start level for each specified bundle.
+                while (st.hasMoreTokens())
+                {
+                    try
+                    {
+                        token = st.nextToken();
+                        long id = Long.parseLong(token);
+                        bundle = m_context.getBundle(id);
+                        if (bundle != null)
+                        {
+                            sl.setBundleStartLevel(bundle, startLevel);
+                        }
+                        else
+                        {
+                            err.println("Bundle ID '" + token + "' is invalid.");
+                        }
+                    }
+                    catch (NumberFormatException ex)
+                    {
+                        err.println("Unable to parse bundle ID '" + token + "'.");
+                    }
+                    catch (Exception ex)
+                    {
+                        err.println(ex.toString());
+                    }
+                }
+            }
+            else
+            {
+                err.println("Invalid start level.");
+            }
+        }
+        else
+        {
+            err.println("Incorrect number of arguments.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/CdCommandImpl.java b/src/org/apache/osgi/bundle/shell/CdCommandImpl.java
new file mode 100644
index 0000000..c876550
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/CdCommandImpl.java
@@ -0,0 +1,86 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.CdCommand;
+import org.osgi.framework.BundleContext;
+
+public class CdCommandImpl implements CdCommand
+{
+    private BundleContext m_context = null;
+    private String m_baseURL = "";
+
+    public CdCommandImpl(BundleContext context)
+    {
+        m_context = context;
+
+        // See if the initial base URL is specified.
+        String baseURL = m_context.getProperty(BASE_URL_PROPERTY);
+        setBaseURL(baseURL);
+    }
+
+    public String getName()
+    {
+        return "cd";
+    }
+
+    public String getUsage()
+    {
+        return "cd [<base-URL>]";
+    }
+
+    public String getShortDescription()
+    {
+        return "change or display base URL.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // No more tokens means to display the base URL,
+        // otherwise set the base URL.
+        if (st.countTokens() == 0)
+        {
+            out.println(m_baseURL);
+        }
+        else if (st.countTokens() == 1)
+        {
+            setBaseURL(st.nextToken());
+        }
+        else
+        {
+            err.println("Incorrect number of arguments");
+        }
+    }
+
+    public String getBaseURL()
+    {
+        return m_baseURL;
+    }
+
+    public void setBaseURL(String s)
+    {
+        m_baseURL = (s == null) ? "" : s;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/HeadersCommandImpl.java b/src/org/apache/osgi/bundle/shell/HeadersCommandImpl.java
new file mode 100644
index 0000000..bb341d8
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/HeadersCommandImpl.java
@@ -0,0 +1,111 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.*;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class HeadersCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public HeadersCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "headers";
+    }
+
+    public String getUsage()
+    {
+        return "headers [<id> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "display bundle header properties.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // Print the specified bundles or all if none are specified.
+        if (st.hasMoreTokens())
+        {
+            while (st.hasMoreTokens())
+            {
+                String id = st.nextToken().trim();
+
+                try
+                {
+                    long l = Long.parseLong(id);
+                    Bundle bundle = m_context.getBundle(l);
+                    if (bundle != null)
+                    {
+                        printHeaders(out, bundle);
+                    }
+                    else
+                    {
+                        err.println("Bundle ID " + id + " is invalid.");
+                    }
+                }
+                catch (NumberFormatException ex)
+                {
+                    err.println("Unable to parse id '" + id + "'.");
+                }
+                catch (Exception ex)
+                {
+                    err.println(ex.toString());
+                }
+            }
+        }
+        else
+        {
+            Bundle[] bundles = m_context.getBundles();
+            for (int i = 0; i < bundles.length; i++)
+            {
+                printHeaders(out, bundles[i]);
+            }
+        }
+    }
+
+    private void printHeaders(PrintStream out, Bundle bundle)
+    {
+        String title = Util.getBundleName(bundle);
+        out.println("\n" + title);
+        out.println(Util.getUnderlineString(title));
+        Dictionary dict = bundle.getHeaders();
+        Enumeration keys = dict.keys();
+        while (keys.hasMoreElements())
+        {
+            Object k = (String) keys.nextElement();
+            Object v = dict.get(k);
+            out.println(k + " = " + Util.getValueString(v));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/HelpCommandImpl.java b/src/org/apache/osgi/bundle/shell/HelpCommandImpl.java
new file mode 100644
index 0000000..b2d969f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/HelpCommandImpl.java
@@ -0,0 +1,97 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.apache.osgi.service.shell.ShellService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class HelpCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public HelpCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "help";
+    }
+
+    public String getUsage()
+    {
+        return "help";
+    }
+
+    public String getShortDescription()
+    {
+        return "display shell commands.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        try {
+            // Get a reference to the shell service.
+            ServiceReference ref = m_context.getServiceReference(
+                org.apache.osgi.service.shell.ShellService.class.getName());
+
+            if (ref != null)
+            {
+                ShellService ss = (ShellService) m_context.getService(ref);
+                String[] cmds = ss.getCommands();
+                String[] usage = new String[cmds.length];
+                String[] desc = new String[cmds.length];
+                int maxUsage = 0;
+                for (int i = 0; i < cmds.length; i++)
+                {
+                    usage[i] = ss.getCommandUsage(cmds[i]);
+                    desc[i] = ss.getCommandDescription(cmds[i]);
+                    // Just in case the command has gone away.
+                    if ((usage[i] != null) && (desc[i] != null))
+                    {
+                        maxUsage = Math.max(maxUsage, usage[i].length());
+                    }
+                }
+                StringBuffer sb = new StringBuffer();
+                for (int i = 0; i < cmds.length; i++)
+                {
+                    // Just in case the command has gone away.
+                    if ((usage[i] != null) && (desc[i] != null))
+                    {
+                        sb.delete(0, sb.length());
+                        for (int j = 0; j < (maxUsage - usage[i].length()); j++)
+                        {
+                            sb.append(' ');
+                        }
+                        out.println(usage[i] + sb + " - " + desc[i]);
+                    }
+                }
+            }
+            else
+            {
+                err.println("No ShellService is unavailable.");
+            }
+        } catch (Exception ex) {
+            err.println(ex.toString());
+        }
+    }
+}
diff --git a/src/org/apache/osgi/bundle/shell/InstallCommandImpl.java b/src/org/apache/osgi/bundle/shell/InstallCommandImpl.java
new file mode 100644
index 0000000..4603cc7
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/InstallCommandImpl.java
@@ -0,0 +1,152 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.CdCommand;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class InstallCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public InstallCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "install";
+    }
+
+    public String getUsage()
+    {
+        return "install <URL> [<URL> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "install bundle(s).";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // There should be at least one URL.
+        if (st.countTokens() >= 1)
+        {
+            while (st.hasMoreTokens())
+            {
+                String location = st.nextToken().trim();
+                install(location, out, err);
+            }
+        }
+        else
+        {
+            err.println("Incorrect number of arguments");
+        }
+    }
+
+    protected Bundle install(String location, PrintStream out, PrintStream err)
+    {
+        String abs = absoluteLocation(location);
+        if (abs == null)
+        {
+            err.println("Malformed location: " + location);
+        }
+        else
+        {
+            try
+            {
+                return m_context.installBundle(abs, null);
+            }
+            catch (IllegalStateException ex)
+            {
+                err.println(ex.toString());
+            }
+            catch (BundleException ex)
+            {
+                if (ex.getNestedException() != null)
+                {
+                    err.println(ex.getNestedException().toString());
+                }
+                else
+                {
+                    err.println(ex.toString());
+                }
+            }
+            catch (Exception ex)
+            {
+                err.println(ex.toString());
+            }
+        }
+        return null;
+    }
+
+    private String absoluteLocation(String location)
+    {
+        if (!location.endsWith(".jar"))
+        {
+            location = location + ".jar";
+        }
+        try
+        {
+            new URL(location);
+        }
+        catch (MalformedURLException ex)
+        {
+            // Try to create a valid URL using the base URL
+            // contained in the "cd" command service.
+            String baseURL = "";
+
+            try
+            {
+                // Get a reference to the "cd" command service.
+                ServiceReference ref = m_context.getServiceReference(
+                    org.apache.osgi.service.shell.CdCommand.class.getName());
+
+                if (ref != null)
+                {
+                    CdCommand cd = (CdCommand) m_context.getService(ref);
+                    baseURL = cd.getBaseURL();
+                    baseURL = (baseURL == null) ? "" : baseURL;
+                    m_context.ungetService(ref);
+                }
+
+                String theURL = baseURL + location;
+                new URL(theURL);
+
+            }
+            catch (Exception ex2)
+            {
+                return null;
+            }
+            location = baseURL + location;
+        }
+        return location;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/PackagesCommandImpl.java b/src/org/apache/osgi/bundle/shell/PackagesCommandImpl.java
new file mode 100644
index 0000000..2722cdd
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/PackagesCommandImpl.java
@@ -0,0 +1,121 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class PackagesCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public PackagesCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "packages";
+    }
+
+    public String getUsage()
+    {
+        return "packages [<id> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "list exported packages.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        // Get package admin service.
+        ServiceReference ref = m_context.getServiceReference(
+            org.osgi.service.packageadmin.PackageAdmin.class.getName());
+        if (ref == null)
+        {
+            out.println("PackageAdmin service is unavailable.");
+            return;
+        }
+
+        PackageAdmin pa = (PackageAdmin) m_context.getService(ref);
+        if (pa == null)
+        {
+            out.println("PackageAdmin service is unavailable.");
+            return;
+        }
+
+        // Parse command line.
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        if (st.hasMoreTokens())
+        {
+            while (st.hasMoreTokens())
+            {
+                String id = st.nextToken();
+                try
+                {
+                    long l = Long.parseLong(id);
+                    Bundle bundle = m_context.getBundle(l);
+                    ExportedPackage[] exports = pa.getExportedPackages(bundle);
+                    printExports(out, bundle, exports);
+                }
+                catch (NumberFormatException ex)
+                {
+                    err.println("Unable to parse id '" + id + "'.");
+                }
+                catch (Exception ex)
+                {
+                    err.println(ex.toString());
+                }
+            }
+        }
+        else
+        {
+            ExportedPackage[] exports = pa.getExportedPackages((Bundle) null);
+            printExports(out, null, exports);
+        }
+    }
+
+    private void printExports(PrintStream out, Bundle target, ExportedPackage[] exports)
+    {
+        if ((exports != null) && (exports.length > 0))
+        {
+            for (int i = 0; i < exports.length; i++)
+            {
+                Bundle bundle = exports[i].getExportingBundle();
+                out.print(Util.getBundleName(bundle));
+                out.println(": " + exports[i]);
+            }
+        }
+        else
+        {
+            out.println(Util.getBundleName(target)
+                + ": No active exported packages.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/PsCommandImpl.java b/src/org/apache/osgi/bundle/shell/PsCommandImpl.java
new file mode 100644
index 0000000..5099815
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/PsCommandImpl.java
@@ -0,0 +1,178 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.Dictionary;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.startlevel.StartLevel;
+
+public class PsCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public PsCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "ps";
+    }
+
+    public String getUsage()
+    {
+        return "ps [-l | -u]";
+    }
+
+    public String getShortDescription()
+    {
+        return "list installed bundles.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        // Get start level service.
+        ServiceReference ref = m_context.getServiceReference(
+            org.osgi.service.startlevel.StartLevel.class.getName());
+        StartLevel sl = null;
+        if (ref != null)
+        {
+            sl = (StartLevel) m_context.getService(ref);
+        }
+
+        if (sl == null)
+        {
+            out.println("StartLevel service is unavailable.");
+        }
+
+        // Parse command line.
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // Check for optional argument.
+        boolean showLoc = false;
+        boolean showUpdate = false;
+        if (st.countTokens() >= 1)
+        {
+            while (st.hasMoreTokens())
+            {
+                String token = st.nextToken().trim();
+                if (token.equals("-l"))
+                {
+                    showLoc = true;
+                }
+                else if (token.equals("-u"))
+                {
+                    showUpdate = true;
+                }
+            }
+        }
+        Bundle[] bundles = m_context.getBundles();
+        if (bundles != null)
+        {
+            // Display active start level.
+            if (sl != null)
+            {
+                out.println("START LEVEL " + sl.getStartLevel());
+            }
+
+            // Print column headers.
+            String msg = " Name";
+            if (showLoc)
+            {
+               msg = " Location";
+            }
+            else if (showUpdate)
+            {
+               msg = " Update location";
+            }
+            String level = (sl == null) ? "" : "  Level ";
+            out.println("   ID " + "  State       " + level + msg);
+            for (int i = 0; i < bundles.length; i++)
+            {
+                // Get the bundle name or location.
+                String name = (String)
+                    bundles[i].getHeaders().get(Constants.BUNDLE_NAME);
+                if (showLoc)
+                {
+                    name = bundles[i].getLocation();
+                }
+                else if (showUpdate)
+                {
+                    Dictionary dict = bundles[i].getHeaders();
+                    name = (String) dict.get(Constants.BUNDLE_UPDATELOCATION);
+                    if (name == null)
+                    {
+                        name = bundles[i].getLocation();
+                    }
+                }
+                // Show bundle version if not showing location.
+                String version = (String)
+                    bundles[i].getHeaders().get(Constants.BUNDLE_VERSION);
+                name = (!showLoc && !showUpdate && (version != null))
+                    ? name + " (" + version + ")" : name;
+                long l = bundles[i].getBundleId();
+                String id = String.valueOf(l);
+                if (sl == null)
+                {
+                    level = "1";
+                }
+                else
+                {
+                    level = String.valueOf(sl.getBundleStartLevel(bundles[i]));
+                }
+                while (level.length() < 5)
+                {
+                    level = " " + level;
+                }
+                while (id.length() < 4)
+                {
+                    id = " " + id;
+                }
+                out.println("[" + id + "] ["
+                    + getStateString(bundles[i].getState())
+                    + "] [" + level + "] " + name);
+            }
+        }
+        else
+        {
+            out.println("There are no installed bundles.");
+        }
+    }
+
+    public String getStateString(int i)
+    {
+        if (i == Bundle.ACTIVE)
+            return "Active     ";
+        else if (i == Bundle.INSTALLED)
+            return "Installed  ";
+        else if (i == Bundle.RESOLVED)
+            return "Resolved   ";
+        else if (i == Bundle.STARTING)
+            return "Starting   ";
+        else if (i == Bundle.STOPPING)
+            return "Stopping   ";
+        return "Unknown    ";
+    }
+}
diff --git a/src/org/apache/osgi/bundle/shell/RefreshCommandImpl.java b/src/org/apache/osgi/bundle/shell/RefreshCommandImpl.java
new file mode 100644
index 0000000..25d063b
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/RefreshCommandImpl.java
@@ -0,0 +1,70 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class RefreshCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public RefreshCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "refresh";
+    }
+
+    public String getUsage()
+    {
+        return "refresh";
+    }
+
+    public String getShortDescription()
+    {
+        return "refresh packages.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        // Get package admin service.
+        ServiceReference ref = m_context.getServiceReference(
+            org.osgi.service.packageadmin.PackageAdmin.class.getName());
+        if (ref == null)
+        {
+            out.println("PackageAdmin service is unavailable.");
+            return;
+        }
+
+        PackageAdmin pa = (PackageAdmin) m_context.getService(ref);
+        if (pa == null)
+        {
+            out.println("PackageAdmin service is unavailable.");
+            return;
+        }
+
+        pa.refreshPackages(null);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/ServicesCommandImpl.java b/src/org/apache/osgi/bundle/shell/ServicesCommandImpl.java
new file mode 100644
index 0000000..af6bb3f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/ServicesCommandImpl.java
@@ -0,0 +1,253 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.*;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class ServicesCommandImpl implements Command
+{
+    private static final String IN_USE_SWITCH = "-u";
+    private static final String SHOW_ALL_SWITCH = "-a";
+
+    private BundleContext m_context = null;
+
+    public ServicesCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "services";
+    }
+
+    public String getUsage()
+    {
+        return "services [-u] [-a] [<id> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "list registered or used services.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // Put the remaining tokens into a list.
+        List tokens = new ArrayList();
+        for (int i = 0; st.hasMoreTokens(); i++)
+        {
+            tokens.add(st.nextToken());
+        }
+
+        // Default switch values.
+        boolean inUse = false;
+        boolean showAll = false;
+
+        // Check for "in use" switch.
+        if (tokens.contains(IN_USE_SWITCH))
+        {
+            // Remove the switch and set boolean flag.
+            tokens.remove(IN_USE_SWITCH);
+            inUse = true;
+        }
+
+        // Check for "show all" switch.
+        if (tokens.contains(SHOW_ALL_SWITCH))
+        {
+            // Remove the switch and set boolean flag.
+            tokens.remove(SHOW_ALL_SWITCH);
+            showAll = true;
+        }
+
+        // If there are bundle IDs specified then print their
+        // services and associated service properties, otherwise
+        // list all bundles and services.
+        if (tokens.size() >= 1)
+        {
+            while (tokens.size() > 0)
+            {
+                String id = (String) tokens.remove(0);
+
+                boolean headerPrinted = false;
+                boolean needSeparator = false;
+
+                try
+                {
+                    long l = Long.parseLong(id);
+                    Bundle bundle = m_context.getBundle(l);
+                    if (bundle != null)
+                    {
+                        ServiceReference[] refs = null;
+                        
+                        // Get registered or in-use services.
+                        if (inUse)
+                        {
+                            refs = bundle.getServicesInUse();
+                        }
+                        else
+                        {
+                            refs = bundle.getRegisteredServices();
+                        }
+
+                        // Print properties for each service.
+                        for (int refIdx = 0;
+                            (refs != null) && (refIdx < refs.length);
+                            refIdx++)
+                        {
+                            String[] objectClass = (String[])
+                                refs[refIdx].getProperty("objectClass");
+
+                            // Determine if we need to print the service, depending
+                            // on whether it is a command service or not.
+                            boolean print = true;
+                            for (int ocIdx = 0;
+                                !showAll && (ocIdx < objectClass.length);
+                                ocIdx++)
+                            {
+                                if (objectClass[ocIdx].equals(
+                                    org.apache.osgi.service.shell.Command.class.getName()))
+                                {
+                                    print = false;
+                                }
+                            }
+
+                            // Print header if we have not already done so.
+                            if (!headerPrinted)
+                            {
+                                headerPrinted = true;
+                                String title = Util.getBundleName(bundle);
+                                title = (inUse)
+                                    ? title + " uses:"
+                                    : title + " provides:";
+                                out.println("");
+                                out.println(title);
+                                out.println(Util.getUnderlineString(title));
+                            }
+
+                            if (showAll || print)
+                            {
+                                // Print service separator if necessary.
+                                if (needSeparator)
+                                {
+                                    out.println("----");
+                                }
+
+                                // Print service properties.
+                                String[] keys = refs[refIdx].getPropertyKeys();
+                                for (int keyIdx = 0;
+                                    (keys != null) && (keyIdx < keys.length);
+                                    keyIdx++)
+                                {
+                                    Object v = refs[refIdx].getProperty(keys[keyIdx]);
+                                    out.println(
+                                        keys[keyIdx] + " = " + Util.getValueString(v));
+                                }
+                                
+                                needSeparator = true;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        err.println("Bundle ID " + id + " is invalid.");
+                    }
+                }
+                catch (NumberFormatException ex)
+                {
+                    err.println("Unable to parse id '" + id + "'.");
+                }
+                catch (Exception ex)
+                {
+                    err.println(ex.toString());
+                }
+            }
+        }
+        else
+        {
+            Bundle[] bundles = m_context.getBundles();
+            if (bundles != null)
+            {
+                // TODO: Sort list.
+                for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+                {
+                    boolean headerPrinted = false;
+                    ServiceReference[] refs = null;
+
+                    // Get registered or in-use services.
+                    if (inUse)
+                    {
+                        refs = bundles[bundleIdx].getServicesInUse();
+                    }
+                    else
+                    {
+                        refs = bundles[bundleIdx].getRegisteredServices();
+                    }
+
+                    for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+                    { 
+                        String[] objectClass = (String[])
+                            refs[refIdx].getProperty("objectClass");
+
+                        // Determine if we need to print the service, depending
+                        // on whether it is a command service or not.
+                        boolean print = true;
+                        for (int ocIdx = 0;
+                            !showAll && (ocIdx < objectClass.length);
+                            ocIdx++)
+                        {
+                            if (objectClass[ocIdx].equals(
+                                org.apache.osgi.service.shell.Command.class.getName()))
+                            {
+                                print = false;
+                            }
+                        }
+
+                        // Print the service if necessary.
+                        if (showAll || print)
+                        {
+                            if (!headerPrinted)
+                            {
+                                headerPrinted = true;
+                                String title = Util.getBundleName(bundles[bundleIdx]);
+                                title = (inUse)
+                                    ? title + " uses:"
+                                    : title + " provides:";
+                                out.println("\n" + title);
+                                out.println(Util.getUnderlineString(title));
+                            }
+                            out.println(Util.getValueString(objectClass));
+                        }
+                    }
+                }
+            }
+            else
+            {
+                out.println("There are no registered services.");
+            }
+        }
+    }
+}
diff --git a/src/org/apache/osgi/bundle/shell/ShutdownCommandImpl.java b/src/org/apache/osgi/bundle/shell/ShutdownCommandImpl.java
new file mode 100644
index 0000000..b92e7c5
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/ShutdownCommandImpl.java
@@ -0,0 +1,62 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class ShutdownCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public ShutdownCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "shutdown";
+    }
+
+    public String getUsage()
+    {
+        return "shutdown";
+    }
+
+    public String getShortDescription()
+    {
+        return "shutdown framework.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        // Get system bundle and use it to shutdown Felix.
+        try
+        {
+            Bundle bundle = m_context.getBundle(0);
+            bundle.stop();
+        }
+        catch (Exception ex)
+        {
+            err.println(ex.toString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/StartCommandImpl.java b/src/org/apache/osgi/bundle/shell/StartCommandImpl.java
new file mode 100644
index 0000000..a03405d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/StartCommandImpl.java
@@ -0,0 +1,108 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class StartCommandImpl extends InstallCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public StartCommandImpl(BundleContext context)
+    {
+        super(context);
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "start";
+    }
+
+    public String getUsage()
+    {
+        return "start <id> [<id> <URL> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "start bundle(s).";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // There should be at least one bundle id.
+        if (st.countTokens() >= 1)
+        {
+            while (st.hasMoreTokens())
+            {
+                String id = st.nextToken().trim();
+
+                try {
+                    Bundle bundle = null;
+
+                    // The id may be a number or a URL, so check.
+                    if (Character.isDigit(id.charAt(0)))
+                    {
+                        long l = Long.parseLong(id);
+                        bundle = m_context.getBundle(l);
+                    }
+                    else
+                    {
+                        bundle = install(id, out, err);
+                    }
+
+                    if (bundle != null)
+                    {
+                        bundle.start();
+                    }
+                    else
+                    {
+                        err.println("Bundle ID " + id + " is invalid.");
+                    }
+                } catch (NumberFormatException ex) {
+                    err.println("Unable to parse id '" + id + "'.");
+                } catch (BundleException ex) {
+                    if (ex.getNestedException() != null)
+                    {
+                        ex.printStackTrace();
+                        err.println(ex.getNestedException().toString());
+                    }
+                    else
+                    {
+                        err.println(ex.toString());
+                    }
+                } catch (Exception ex) {
+                    err.println(ex.toString());
+                }
+            }
+        }
+        else
+        {
+            err.println("Incorrect number of arguments");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/StartLevelCommandImpl.java b/src/org/apache/osgi/bundle/shell/StartLevelCommandImpl.java
new file mode 100644
index 0000000..d2c0303
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/StartLevelCommandImpl.java
@@ -0,0 +1,93 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.startlevel.StartLevel;
+
+public class StartLevelCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public StartLevelCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "startlevel";
+    }
+
+    public String getUsage()
+    {
+        return "startlevel [<level>]";
+    }
+
+    public String getShortDescription()
+    {
+        return "get or set framework start level.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        // Get start level service.
+        ServiceReference ref = m_context.getServiceReference(
+            org.osgi.service.startlevel.StartLevel.class.getName());
+        if (ref == null)
+        {
+            out.println("StartLevel service is unavailable.");
+            return;
+        }
+
+        StartLevel sl = (StartLevel) m_context.getService(ref);
+        if (sl == null)
+        {
+            out.println("StartLevel service is unavailable.");
+            return;
+        }
+
+        // Parse command line.
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        if (st.countTokens() == 0)
+        {
+            out.println("Level " + sl.getStartLevel());
+        }
+        else if (st.countTokens() >= 1)
+        {
+            String levelStr = st.nextToken().trim();
+
+            try {
+                int level = Integer.parseInt(levelStr);
+                sl.setStartLevel(level);
+            } catch (NumberFormatException ex) {
+                err.println("Unable to parse integer '" + levelStr + "'.");
+            } catch (Exception ex) {
+                err.println(ex.toString());
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/StopCommandImpl.java b/src/org/apache/osgi/bundle/shell/StopCommandImpl.java
new file mode 100644
index 0000000..5ae2713
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/StopCommandImpl.java
@@ -0,0 +1,91 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class StopCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public StopCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "stop";
+    }
+
+    public String getUsage()
+    {
+        return "stop <id> [<id> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "stop bundle(s).";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // There should be at least one bundle id.
+        if (st.countTokens() >= 1)
+        {
+            while (st.hasMoreTokens())
+            {
+                String id = st.nextToken().trim();
+
+                try {
+                    long l = Long.parseLong(id);
+                    Bundle bundle = m_context.getBundle(l);
+                    if (bundle != null)
+                    {
+                        bundle.stop();
+                    }
+                    else
+                    {
+                        err.println("Bundle ID " + id + " is invalid.");
+                    }
+                } catch (NumberFormatException ex) {
+                    err.println("Unable to parse id '" + id + "'.");
+                } catch (BundleException ex) {
+                    if (ex.getNestedException() != null)
+                        err.println(ex.getNestedException().toString());
+                    else
+                        err.println(ex.toString());
+                } catch (Exception ex) {
+                    err.println(ex.toString());
+                }
+            }
+        }
+        else
+        {
+            err.println("Incorrect number of arguments");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/UninstallCommandImpl.java b/src/org/apache/osgi/bundle/shell/UninstallCommandImpl.java
new file mode 100644
index 0000000..2182d6b
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/UninstallCommandImpl.java
@@ -0,0 +1,91 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class UninstallCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public UninstallCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "uninstall";
+    }
+
+    public String getUsage()
+    {
+        return "uninstall <id> [<id> ...]";
+    }
+
+    public String getShortDescription()
+    {
+        return "uninstall bundle(s).";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // There must be at least one bundle ID.
+        if (st.countTokens() >= 1)
+        {
+            while (st.hasMoreTokens())
+            {
+                String id = st.nextToken().trim();
+
+                try {
+                    long l = Long.parseLong(id);
+                    Bundle bundle = m_context.getBundle(l);
+                    if (bundle != null)
+                    {
+                        bundle.uninstall();
+                    }
+                    else
+                    {
+                        err.println("Bundle ID " + id + " is invalid.");
+                    }
+                } catch (NumberFormatException ex) {
+                    err.println("Unable to parse id '" + id + "'.");
+                } catch (BundleException ex) {
+                    if (ex.getNestedException() != null)
+                        err.println(ex.getNestedException().toString());
+                    else
+                        err.println(ex.toString());
+                } catch (Exception ex) {
+                    err.println(ex.toString());
+                }
+            }
+        }
+        else
+        {
+            err.println("Incorrect number of arguments");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/UpdateCommandImpl.java b/src/org/apache/osgi/bundle/shell/UpdateCommandImpl.java
new file mode 100644
index 0000000..45c895c
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/UpdateCommandImpl.java
@@ -0,0 +1,178 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.CdCommand;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class UpdateCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public UpdateCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "update";
+    }
+
+    public String getUsage()
+    {
+        return "update <id> [<URL>]";
+    }
+
+    public String getShortDescription()
+    {
+        return "update bundle.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        StringTokenizer st = new StringTokenizer(s, " ");
+
+        // Ignore the command name.
+        st.nextToken();
+
+        // There should be at least a bundle ID, but there may
+        // also be a URL.
+        if ((st.countTokens() == 1) || (st.countTokens() == 2))
+        {
+            String id = st.nextToken().trim();
+            String location = st.countTokens() == 0 ? null : st.nextToken().trim();
+
+            if (location != null)
+            {
+                location = absoluteLocation(location);
+
+                if (location == null)
+                {
+                    err.println("Malformed location: " + location);
+                }
+            }
+
+            try
+            {
+                // Get the bundle id.
+                long l = Long.parseLong(id);
+
+                // Get the bundle.
+                Bundle bundle = m_context.getBundle(l);
+                if (bundle != null)
+                {
+                    // Create input stream from location if present
+                    // and use it to update, otherwise just update.
+                    if (location != null)
+                    {
+                        InputStream is = new URL(location).openStream();
+                        bundle.update(is);
+                    }
+                    else
+                    {
+                        bundle.update();
+                    }
+                }
+                else
+                {
+                    err.println("Bundle ID " + id + " is invalid.");
+                }
+            }
+            catch (NumberFormatException ex)
+            {
+                err.println("Unable to parse id '" + id + "'.");
+            }
+            catch (MalformedURLException ex)
+            {
+                err.println("Unable to parse URL.");
+            }
+            catch (IOException ex)
+            {
+                err.println("Unable to open input stream: " + ex);
+            }
+            catch (BundleException ex)
+            {
+                if (ex.getNestedException() != null)
+                {
+                    err.println(ex.getNestedException().toString());
+                }
+                else
+                {
+                    err.println(ex.toString());
+                }
+            }
+            catch (Exception ex)
+            {
+                err.println(ex.toString());
+            }
+        }
+        else
+        {
+            err.println("Incorrect number of arguments");
+        }
+    }
+
+    private String absoluteLocation(String location)
+    {
+        if (!location.endsWith(".jar"))
+        {
+            location = location + ".jar";
+        }
+        try
+        {
+            new URL(location);
+        }
+        catch (MalformedURLException ex)
+        {
+            // Try to create a valid URL using the base URL
+            // contained in the "cd" command service.
+            String baseURL = "";
+
+            try
+            {
+                // Get a reference to the "cd" command service.
+                ServiceReference ref = m_context.getServiceReference(
+                    org.apache.osgi.service.shell.CdCommand.class.getName());
+
+                if (ref != null)
+                {
+                    CdCommand cd = (CdCommand) m_context.getService(ref);
+                    baseURL = cd.getBaseURL();
+                    baseURL = (baseURL == null) ? "" : baseURL;
+                    m_context.ungetService(ref);
+                }
+
+                String theURL = baseURL + location;
+                new URL(theURL);
+
+            }
+            catch (Exception ex2)
+            {
+                return null;
+            }
+            location = baseURL + location;
+        }
+        return location;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/Util.java b/src/org/apache/osgi/bundle/shell/Util.java
new file mode 100644
index 0000000..5eb0149
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/Util.java
@@ -0,0 +1,101 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+public class Util
+{
+    public static String getBundleName(Bundle bundle)
+    {
+        if (bundle != null)
+        {
+            String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+            return (name == null)
+                ? "Bundle " + Long.toString(bundle.getBundleId())
+                : name + " (" + Long.toString(bundle.getBundleId()) + ")";
+        }
+        return "[STALE BUNDLE]";
+    }
+
+    private static StringBuffer m_sb = new StringBuffer();
+
+    public static String getUnderlineString(String s)
+    {
+        synchronized (m_sb)
+        {
+            m_sb.delete(0, m_sb.length());
+            for (int i = 0; i < s.length(); i++)
+            {
+                m_sb.append('-');
+            }
+            return m_sb.toString();
+        }
+    }
+
+    public static String getValueString(Object obj)
+    {
+        synchronized (m_sb)
+        {
+            if (obj instanceof String)
+            {
+                return (String) obj;
+            }
+            else if (obj instanceof String[])
+            {
+                String[] array = (String[]) obj;
+                m_sb.delete(0, m_sb.length());
+                for (int i = 0; i < array.length; i++)
+                {
+                    if (i != 0)
+                    {
+                        m_sb.append(", ");
+                    }
+                    m_sb.append(array[i].toString());
+                }
+                return m_sb.toString();
+            }
+            else if (obj instanceof Boolean)
+            {
+                return ((Boolean) obj).toString();
+            }
+            else if (obj instanceof Long)
+            {
+                return ((Long) obj).toString();
+            }
+            else if (obj instanceof Integer)
+            {
+                return ((Integer) obj).toString();
+            }
+            else if (obj instanceof Short)
+            {
+                return ((Short) obj).toString();
+            }
+            else if (obj instanceof Double)
+            {
+                return ((Double) obj).toString();
+            }
+            else if (obj instanceof Float)
+            {
+                return ((Float) obj).toString();
+            }
+
+            return "<unknown value type>";
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/VersionCommandImpl.java b/src/org/apache/osgi/bundle/shell/VersionCommandImpl.java
new file mode 100644
index 0000000..39133af
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/VersionCommandImpl.java
@@ -0,0 +1,52 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.BundleContext;
+
+public class VersionCommandImpl implements Command
+{
+    private BundleContext m_context = null;
+
+    public VersionCommandImpl(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public String getName()
+    {
+        return "version";
+    }
+
+    public String getUsage()
+    {
+        return "version";
+    }
+
+    public String getShortDescription()
+    {
+        return "display version of framework.";
+    }
+
+    public void execute(String s, PrintStream out, PrintStream err)
+    {
+        out.println(m_context.getProperty("felix.version"));
+    }
+}
diff --git a/src/org/apache/osgi/bundle/shell/manifest.mf b/src/org/apache/osgi/bundle/shell/manifest.mf
new file mode 100644
index 0000000..6406fdb
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/manifest.mf
@@ -0,0 +1,13 @@
+Bundle-Name: Shell Service
+Bundle-SymbolicName: org.apache.osgi.bundle.shell
+Bundle-Description: A simple command shell service for Felix.
+Bundle-Version: 1.0.2
+Bundle-Activator: org.apache.osgi.bundle.shell.Activator
+Bundle-ClassPath: .
+Import-Package: 
+ org.osgi.framework,
+ org.osgi.service.startlevel,
+ org.osgi.service.packageadmin
+Export-Package: 
+ org.apache.osgi.service.shell; specification-version="1.0.0",
+ org.ungoverned.osgi.service.shell; specification-version="1.0.0"
diff --git a/src/org/apache/osgi/bundle/shelltui/Activator.java b/src/org/apache/osgi/bundle/shelltui/Activator.java
new file mode 100644
index 0000000..39840c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shelltui/Activator.java
@@ -0,0 +1,160 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.bundle.shelltui;
+
+import java.io.*;
+
+import org.apache.osgi.service.shell.ShellService;
+import org.osgi.framework.*;
+
+public class Activator implements BundleActivator
+{
+    private transient BundleContext m_context = null;
+    private transient ShellTuiRunnable m_runnable = null;
+    private transient ServiceReference m_shellRef = null;
+    private transient ShellService m_shell = null;
+
+    public void start(BundleContext context)
+    {
+        m_context = context;
+
+        // Listen for registering/unregistering shell service.
+        ServiceListener sl = new ServiceListener() {
+            public void serviceChanged(ServiceEvent event)
+            {
+                synchronized (Activator.this)
+                {
+                    // Ignore additional services if we already have one.
+                    if ((event.getType() == ServiceEvent.REGISTERED)
+                        && (m_shellRef != null))
+                    {
+                        return;
+                    }
+                    // Initialize the service if we don't have one.
+                    else if ((event.getType() == ServiceEvent.REGISTERED)
+                        && (m_shellRef == null))
+                    {
+                        initializeService();
+                    }
+                    // Unget the service if it is unregistering.
+                    else if ((event.getType() == ServiceEvent.UNREGISTERING)
+                        && event.getServiceReference().equals(m_shellRef))
+                    {
+                        m_context.ungetService(m_shellRef);
+                        m_shellRef = null;
+                        m_shell = null;
+                        // Try to get another service.
+                        initializeService();
+                    }
+                }
+            }
+        };
+        try {
+            m_context.addServiceListener(sl,
+                "(objectClass="
+                + org.apache.osgi.service.shell.ShellService.class.getName()
+                + ")");
+        } catch (InvalidSyntaxException ex) {
+            System.err.println("ShellTuiActivator: Cannot add service listener.");
+            System.err.println("ShellTuiActivator: " + ex);
+        }
+
+        // Now try to manually initialize the shell service
+        // since one might already be available.
+        initializeService();
+
+        // Start shell thread.
+        new Thread(
+            m_runnable = new ShellTuiRunnable(),
+            "Felix Shell TUI").start();
+    }
+
+    private synchronized void initializeService()
+    {
+        if (m_shell != null)
+            return;
+        m_shellRef = m_context.getServiceReference(
+            org.apache.osgi.service.shell.ShellService.class.getName());
+        if (m_shellRef == null)
+            return;
+        m_shell = (ShellService) m_context.getService(m_shellRef);
+    }
+
+    public void stop(BundleContext context)
+    {
+        if (m_runnable != null)
+        {
+            m_runnable.stop();
+        }
+    }
+
+    private class ShellTuiRunnable implements Runnable
+    {
+        private boolean stop = false;
+
+        public void stop()
+        {
+            stop = true;
+        }
+
+        public void run()
+        {
+            String line = null;
+            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+
+            while (!stop)
+            {
+                System.out.print("-> ");
+
+                try {
+                    line = in.readLine();
+                } catch (IOException ex) {
+                    System.err.println("Could not read input, please try again.");
+                    continue;
+                }
+
+                synchronized (Activator.this)
+                {
+                    if (m_shell == null)
+                    {
+                        System.out.println("No shell service available.");
+                        continue;
+                    }
+
+                    if (line == null)
+                    {
+                        continue;
+                    }
+
+                    line = line.trim();
+
+                    if (line.length() == 0)
+                    {
+                        continue;
+                    }
+
+                    try {
+                        m_shell.executeCommand(line, System.out, System.err);
+                    } catch (Exception ex) {
+                        System.err.println("ShellTuiActivator: " + ex);
+                        ex.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/org/apache/osgi/bundle/shelltui/manifest.mf b/src/org/apache/osgi/bundle/shelltui/manifest.mf
new file mode 100644
index 0000000..f8f17d4
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shelltui/manifest.mf
@@ -0,0 +1,7 @@
+Bundle-Name: Shell TUI
+Bundle-SymbolicName: org.apache.osgi.bundle.shelltui
+Bundle-Description: A simple textual user interface for Felix's the shell service.
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.osgi.bundle.shelltui.Activator
+Bundle-ClassPath: .
+Import-Package: org.osgi.framework, org.apache.osgi.service.shell
diff --git a/src/org/apache/osgi/framework/BundleContextImpl.java b/src/org/apache/osgi/framework/BundleContextImpl.java
new file mode 100644
index 0000000..e0114d2
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleContextImpl.java
@@ -0,0 +1,284 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.apache.osgi.framework.ext.FelixBundleContext;
+import org.osgi.framework.*;
+
+class BundleContextImpl implements FelixBundleContext
+{
+    private Felix m_felix = null;
+    private BundleImpl m_bundle = null;
+
+    protected BundleContextImpl(Felix felix, BundleImpl bundle)
+    {
+        m_felix = felix;
+        m_bundle = bundle;
+    }
+
+    public void addImportPackage() throws BundleException
+    {
+        throw new BundleException("Not implemented yet.");
+    }
+
+    public void removeImportPackage() throws BundleException
+    {
+        throw new BundleException("Not implemented yet.");
+    }
+
+    public void addExportPackage() throws BundleException
+    {
+        throw new BundleException("Not implemented yet.");
+    }
+
+    public void removeExportPackage() throws BundleException
+    {
+        throw new BundleException("Not implemented yet.");
+    }
+
+    public String getProperty(String name)
+    {
+        return m_felix.getProperty(name);
+    }
+
+    public Bundle getBundle()
+    {
+        return m_bundle;
+    }
+
+    public Filter createFilter(String expr)
+        throws InvalidSyntaxException
+    {
+        return new FilterImpl(m_felix.getLogger(), expr);
+    }
+
+    public Bundle installBundle(String location)
+        throws BundleException
+    {
+        return installBundle(location, null);
+    }
+
+    public Bundle installBundle(String location, InputStream is)
+        throws BundleException
+    {
+        return m_felix.installBundle(location, is);
+    }
+
+    public Bundle getBundle(long id)
+    {
+        return m_felix.getBundle(id);
+    }
+
+    public Bundle[] getBundles()
+    {
+        return m_felix.getBundles();
+    }
+
+    public void addBundleListener(BundleListener l)
+    {
+        m_felix.addBundleListener(m_bundle, l);
+    }
+
+    public void removeBundleListener(BundleListener l)
+    {
+        m_felix.removeBundleListener(l);
+    }
+
+    public void addServiceListener(ServiceListener l)
+    {
+        try
+        {
+            addServiceListener(l, null);
+        }
+        catch (InvalidSyntaxException ex)
+        {
+            // This will not happen since the filter is null.
+        }
+    }
+
+    public void addServiceListener(ServiceListener l, String s)
+        throws InvalidSyntaxException
+    {
+        m_felix.addServiceListener(m_bundle, l, s);
+    }
+
+    public void removeServiceListener(ServiceListener l)
+    {
+        m_felix.removeServiceListener(l);
+    }
+
+    public void addFrameworkListener(FrameworkListener l)
+    {
+        m_felix.addFrameworkListener(m_bundle, l);
+    }
+
+    public void removeFrameworkListener(FrameworkListener l)
+    {
+        m_felix.removeFrameworkListener(l);
+    }
+
+    public ServiceRegistration registerService(
+        String clazz, Object svcObj, Dictionary dict)
+    {
+        return registerService(new String[] { clazz }, svcObj, dict);
+    }
+
+    public ServiceRegistration registerService(
+        String[] clazzes, Object svcObj, Dictionary dict)
+    {
+        return m_felix.registerService(m_bundle, clazzes, svcObj, dict);
+    }
+
+    public ServiceReference getServiceReference(String clazz)
+    {
+        try
+        {
+            ServiceReference[] refs = getServiceReferences(clazz, null);
+            return getBestServiceReference(refs);
+        }
+        catch (InvalidSyntaxException ex)
+        {
+            m_felix.getLogger().log(LogWrapper.LOG_ERROR, "BundleContextImpl: " + ex);
+        }
+        return null;
+    }
+
+    private ServiceReference getBestServiceReference(ServiceReference[] refs)
+    {
+        if (refs == null)
+        {
+            return null;
+        }
+
+        if (refs.length == 1)
+        {
+            return refs[0];
+        }
+
+        // Loop through all service references and return
+        // the "best" one according to its rank and ID.
+        ServiceReference bestRef = null;
+        Integer bestRank = null;
+        Long bestId = null;
+        for (int i = 0; i < refs.length; i++)
+        {
+            ServiceReference ref = refs[i];
+
+            // The first time through the loop just
+            // assume that the first reference is best.
+            if (bestRef == null)
+            {
+                bestRef = ref;
+                bestRank = (Integer) bestRef.getProperty("service.ranking");
+                // The spec says no ranking defaults to zero.
+                if (bestRank == null)
+                {
+                    bestRank = new Integer(0);
+                }
+                bestId = (Long) bestRef.getProperty("service.id");
+            }
+
+            // Compare current and best references to see if
+            // the current reference is a better choice.
+            Integer rank = (Integer) ref.getProperty("service.ranking");
+
+            // The spec says no ranking defaults to zero.
+            if (rank == null)
+            {
+                rank = new Integer(0);
+            }
+
+            // If the current reference ranking is greater than the
+            // best ranking, then keep the current reference.
+            if (bestRank.compareTo(rank) < 0)
+            {
+                bestRef = ref;
+                bestRank = rank;
+                bestId = (Long) bestRef.getProperty("service.id");
+            }
+            // If rankings are equal, then compare IDs and
+            // keep the smallest.
+            else if (bestRank.compareTo(rank) == 0)
+            {
+                Long id = (Long) ref.getProperty("service.id");
+                // If either reference has a null ID, then keep
+                // the one with a non-null ID.
+                if ((bestId == null) || (id == null))
+                {
+                    bestRef = (bestId == null) ? ref : bestRef;
+                    // bestRank = bestRank; // No need to update since they are equal.
+                    bestId = (Long) bestRef.getProperty("service.id");
+                }
+                // Otherwise compare IDs.
+                else
+                {
+                    // If the current reference ID is less than the
+                    // best ID, then keep the current reference.
+                    if (bestId.compareTo(id) > 0)
+                    {
+                        bestRef = ref;
+                        // bestRank = bestRank; // No need to update since they are equal.
+                        bestId = (Long) bestRef.getProperty("service.id");
+                    }
+                }
+            }
+        }
+
+        return bestRef;
+    }
+
+    public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException
+    {
+        // TODO: Implement BundleContext.getAllServiceReferences()
+        return null;
+    }
+
+    public ServiceReference[] getServiceReferences(String clazz, String filter)
+        throws InvalidSyntaxException
+    {
+        return m_felix.getServiceReferences(m_bundle, clazz, filter);
+    }
+
+    public Object getService(ServiceReference ref)
+    {
+        if (ref == null)
+        {
+            throw new NullPointerException("Specified service reference cannot be null.");
+        }
+        return m_felix.getService(m_bundle, ref);
+    }
+
+    public boolean ungetService(ServiceReference ref)
+    {
+        if (ref == null)
+        {
+            throw new NullPointerException("Specified service reference cannot be null.");
+        }
+
+        // Unget the specified service.
+        return m_felix.ungetService(m_bundle, ref);
+    }
+
+    public File getDataFile(String s)
+    {
+        return m_felix.getDataFile(m_bundle, s);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleImpl.java b/src/org/apache/osgi/framework/BundleImpl.java
new file mode 100644
index 0000000..0b1d056
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleImpl.java
@@ -0,0 +1,185 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import org.osgi.framework.*;
+
+class BundleImpl implements Bundle
+{
+    private Felix m_felix = null;
+    private BundleInfo m_info = null;
+
+    protected BundleImpl(Felix felix, BundleInfo info)
+    {
+        m_felix = felix;
+        m_info = info;
+    }
+
+    Felix getFelix() // package protected
+    {
+        return m_felix;
+    }
+
+    BundleInfo getInfo() // package protected
+    {
+        return m_info;
+    }
+
+    void setInfo(BundleInfo info) // package protected
+    {
+        m_info = info;
+    }
+
+    public long getBundleId()
+    {
+        return m_info.getBundleId();
+    }
+
+    public Dictionary getHeaders()
+    {
+        return m_felix.getBundleHeaders(this);
+    }
+
+    public String getLocation()
+    {
+        return m_felix.getBundleLocation(this);
+    }
+
+    /**
+     * Returns a URL to a named resource in the bundle.
+     *
+     * @return a URL to named resource, or null if not found.
+    **/
+    public URL getResource(String name)
+    {
+        return m_felix.getBundleResource(this, name);
+    }
+
+    /**
+     * Returns an array of service references corresponding to
+     * the bundle's registered services.
+     *
+     * @return an array of service references or null.
+    **/
+    public ServiceReference[] getRegisteredServices()
+    {
+        return m_felix.getBundleRegisteredServices(this);
+    }
+
+    public ServiceReference[] getServicesInUse()
+    {
+        return m_felix.getBundleServicesInUse(this);
+    }
+
+    public int getState()
+    {
+        return m_info.getState();
+    }
+
+    public boolean hasPermission(Object obj)
+    {
+        return m_felix.bundleHasPermission(this, obj);
+    }
+
+    public void start() throws BundleException
+    {
+        m_felix.startBundle(this, true);
+    }
+
+    public void update() throws BundleException
+    {
+        update(null);
+    }
+
+    public void update(InputStream is) throws BundleException
+    {
+        m_felix.updateBundle(this, is);
+    }
+
+    public void stop() throws BundleException
+    {
+        m_felix.stopBundle(this, true);
+    }
+
+    public void uninstall() throws BundleException
+    {
+        m_felix.uninstallBundle(this);
+    }
+
+    public String toString()
+    {
+        return "[" + getBundleId() +"]";
+    }
+
+    //
+    // PLACE FOLLOWING METHODS INTO PROPER LOCATION ONCE IMPLEMENTED.
+    //
+
+    public Dictionary getHeaders(String locale)
+    {
+        // TODO: Implement Bundle.getHeaders()
+        return null;
+    }
+
+    public String getSymbolicName()
+    {
+        // TODO: Implement Bundle.getSymbolicName()
+        return null;
+    }
+
+    public Class loadClass(String name) throws ClassNotFoundException
+    {
+        // TODO: Implement Bundle.loadClass()
+        return null;
+    }
+
+    public Enumeration getResources(String name) throws IOException
+    {
+        // TODO: Implement Bundle.getResources()
+        return null;
+    }
+
+    public Enumeration getEntryPaths(String path)
+    {
+        // TODO: Implement Bundle.getEntryPaths()
+        return null;
+    }
+
+    public URL getEntry(String name)
+    {
+        // TODO: Implement Bundle.getEntry()
+        return null;
+    }
+
+    public long getLastModified()
+    {
+        // TODO: Implement Bundle.getLastModified()
+        return 0;
+    }
+
+    public Enumeration findEntries(String path, String filePattern, boolean recurse)
+    {
+        // TODO: Implement Bundle.findEntries()
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleInfo.java b/src/org/apache/osgi/framework/BundleInfo.java
new file mode 100644
index 0000000..5d589bb
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleInfo.java
@@ -0,0 +1,372 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.util.Map;
+
+import org.apache.osgi.framework.cache.BundleArchive;
+import org.apache.osgi.moduleloader.Module;
+import org.osgi.framework.*;
+
+class BundleInfo
+{
+    private LogWrapper m_logger = null;
+    private BundleArchive m_archive = null;
+    private Module[] m_modules = null;
+    private int m_state = 0;
+    private BundleActivator m_activator = null;
+    private BundleContext m_context = null;
+    // Indicates that the bundle was either updated
+    // or uninstalled and is waiting to be removed or refreshed.
+    private boolean m_removalPending = false;
+
+    // Used for bundle locking.
+    private int m_lockCount = 0;
+    private Thread m_lockThread = null;
+
+    protected BundleInfo(LogWrapper logger, BundleArchive archive, Module module)
+        throws Exception
+    {
+        m_logger = logger;
+        m_archive = archive;
+        m_modules = (module == null) ? new Module[0] : new Module[] { module };
+
+        m_state = Bundle.INSTALLED;
+        m_removalPending = false;
+        m_activator = null;
+        m_context = null;
+    }
+
+    /**
+     *  Returns the bundle archive associated with this bundle.
+     * @return the bundle archive associated with this bundle.
+    **/
+    public BundleArchive getArchive()
+    {
+        return m_archive;
+    }
+
+    /**
+     * Returns an array of all modules associated with the bundle represented by
+     * this <tt>BundleInfo</tt> object. A module in the array corresponds to a
+     * revision of the bundle's JAR file and is ordered from oldest to newest.
+     * Multiple revisions of a bundle JAR file might exist if a bundle is
+     * updated, without refreshing the framework. In this case, exports from
+     * the prior revisions of the bundle JAR file are still offered; the
+     * current revision will be bound to packages from the prior revision,
+     * unless the packages were not offered by the prior revision. There is
+     * no limit on the potential number of bundle JAR file revisions.
+     * @return array of modules corresponding to the bundle JAR file revisions.
+    **/
+    public Module[] getModules()
+    {
+        return m_modules;
+    }
+
+    /**
+     * Determines if the specified module is associated with this bundle.
+     * @param module the module to determine if it is associate with this bundle.
+     * @return <tt>true</tt> if the specified module is in the array of modules
+     *         associated with this bundle, <tt>false</tt> otherwise.
+    **/
+    public boolean hasModule(Module module)
+    {
+        for (int i = 0; i < m_modules.length; i++)
+        {
+            if (m_modules[i] == module)
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the newest module, which corresponds to the last module
+     * in the module array.
+     * @return the newest module.
+    **/
+    public Module getCurrentModule()
+    {
+        return m_modules[m_modules.length - 1];
+    }
+    
+    /**
+     * Add a module that corresponds to a new bundle JAR file revision for
+     * the bundle associated with this <tt>BundleInfo</tt> object.
+     * @param module the module to add.
+    **/
+    public void addModule(Module module)
+    {
+        Module[] dest = new Module[m_modules.length + 1];
+        System.arraycopy(m_modules, 0, dest, 0, m_modules.length);
+        dest[m_modules.length] = module;
+        m_modules = dest;
+    }
+
+    public long getBundleId()
+    {
+        return m_archive.getId();
+    }
+    
+    public String getLocation()
+    {
+        try
+        {
+            return m_archive.getLocation();
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error reading location from bundle archive.",
+                ex);
+            return null;
+        }
+    }
+
+    public int getStartLevel(int defaultLevel)
+    {
+        try
+        {
+            return m_archive.getStartLevel();
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error reading start level from bundle archive.",
+                ex);
+            return defaultLevel;
+        }
+    }
+
+    public void setStartLevel(int i)
+    {
+        try
+        {
+            m_archive.setStartLevel(i);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error writing start level to bundle archive.",
+                ex);
+        }
+    }
+
+    public Map getCurrentHeader()
+    {
+        try
+        {
+            // Return the header for the most recent bundle revision only,
+            // since we shouldn't ever need access to older revisions.
+            return m_archive.getManifestHeader(m_archive.getRevisionCount() - 1);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error reading manifest from bundle archive.",
+                ex);
+            return null;
+        }
+    }
+
+    public int getState()
+    {
+        return m_state;
+    }
+
+    public void setState(int i)
+    {
+        m_state = i;
+    }
+
+    public int getPersistentState()
+    {
+        try
+        {
+            return m_archive.getPersistentState();
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error reading persistent state from bundle archive.",
+                ex);
+            return Bundle.INSTALLED;
+        }
+    }
+
+    public void setPersistentStateInactive()
+    {
+        try
+        {
+            m_archive.setPersistentState(Bundle.INSTALLED);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR,
+                "Error writing persistent state to bundle archive.",
+                ex);
+        }
+    }
+
+    public void setPersistentStateActive()
+    {
+        try
+        {
+            m_archive.setPersistentState(Bundle.ACTIVE);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error writing persistent state to bundle archive.",
+                ex);
+        }
+    }
+
+    public void setPersistentStateUninstalled()
+    {
+        try
+        {
+            m_archive.setPersistentState(Bundle.UNINSTALLED);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Error writing persistent state to bundle archive.",
+                ex);
+        }
+    }
+
+    public BundleContext getContext()
+    {
+        return m_context;
+    }
+
+    public void setContext(BundleContext context)
+    {
+        m_context = context;
+    }
+
+    public BundleActivator getActivator()
+    {
+        return m_activator;
+    }
+
+    public void setActivator(BundleActivator activator)
+    {
+        m_activator = activator;
+    }
+
+    public boolean isRemovalPending()
+    {
+        return m_removalPending;
+    }
+
+    public void setRemovalPending()
+    {
+        m_removalPending = true;
+    }
+
+    //
+    // Locking related methods.
+    // NOTE: These methods are not synchronized because it is assumed they
+    // will only ever be called when the caller is in a synchronized block.
+    //
+
+    public boolean isLockable()
+    {
+        return (m_lockCount == 0) || (m_lockThread == Thread.currentThread());
+    }
+
+    public void lock()
+    {
+        if ((m_lockCount > 0) && (m_lockThread != Thread.currentThread()))
+        {
+            throw new IllegalStateException("Bundle is locked by another thread.");
+        }
+        m_lockCount++;
+        m_lockThread = Thread.currentThread();
+    }
+
+    public void unlock()
+    {
+        if (m_lockCount == 0)
+        {
+            throw new IllegalStateException("Bundle is not locked.");
+        }
+        if ((m_lockCount > 0) && (m_lockThread != Thread.currentThread()))
+        {
+            throw new IllegalStateException("Bundle is locked by another thread.");
+        }
+        m_lockCount--;
+        if (m_lockCount == 0)
+        {
+            m_lockThread = null;
+        }
+    }
+
+    public void syncLock(BundleInfo info)
+    {
+        m_lockCount = info.m_lockCount;
+        m_lockThread = info.m_lockThread;
+    }
+
+    /**
+     * Converts a module identifier to a bundle identifier. Module IDs
+     * are typically <tt>&lt;bundle-id&gt;.&lt;revision&gt;</tt>; this
+     * method returns only the portion corresponding to the bundle ID.
+    **/
+    protected static long getBundleIdFromModuleId(String id)
+    {
+        try
+        {
+            String bundleId = (id.indexOf('.') >= 0)
+                ? id.substring(0, id.indexOf('.')) : id;
+            return Long.parseLong(bundleId);
+        }
+        catch (NumberFormatException ex)
+        {
+            return -1;
+        }
+    }
+
+    /**
+     * Converts a module identifier to a bundle identifier. Module IDs
+     * are typically <tt>&lt;bundle-id&gt;.&lt;revision&gt;</tt>; this
+     * method returns only the portion corresponding to the revision.
+    **/
+    protected static int getModuleRevisionFromModuleId(String id)
+    {
+        try
+        {
+            String rev = (id.indexOf('.') >= 0)
+                ? id.substring(id.indexOf('.') + 1) : id;
+            return Integer.parseInt(rev);
+        }
+        catch (NumberFormatException ex)
+        {
+            return -1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleURLConnection.java b/src/org/apache/osgi/framework/BundleURLConnection.java
new file mode 100644
index 0000000..ca890b2
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleURLConnection.java
@@ -0,0 +1,158 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+import org.apache.osgi.moduleloader.*;
+
+class BundleURLConnection extends URLConnection
+{
+    private ModuleManager m_mgr = null;
+    private int contentLength;
+    private long contentTime;
+    private String contentType;
+    private InputStream is;
+
+    public BundleURLConnection(ModuleManager mgr, URL url)
+    {
+        super(url);
+        m_mgr = mgr;
+    }
+
+    public void connect() throws IOException
+    {
+        if (!connected)
+        {
+            // The URL is constructed like this:
+            // bundle://<module-id>/<source-idx>/<resource-path>
+
+            Module module = m_mgr.getModule(url.getHost());
+            if (module == null)
+            {
+                throw new IOException("Unable to find bundle's module.");
+            }
+
+            String resource = url.getFile();
+            if (resource == null)
+            {
+                throw new IOException("Unable to find resource: " + url.toString());
+            }
+            if (resource.startsWith("/"))
+            {
+                resource = resource.substring(1);
+            }
+            int rsIdx = -1;
+            try
+            {
+                rsIdx = Integer.parseInt(resource.substring(0, resource.indexOf("/")));
+            }
+            catch (NumberFormatException ex)
+            {
+                new IOException("Error parsing resource index.");
+            }
+            resource = resource.substring(resource.indexOf("/") + 1);
+
+            // Get the resource bytes from the resource source.
+            byte[] bytes = null;
+            ResourceSource[] resSources = module.getResourceSources();
+            if ((resSources != null) && (rsIdx < resSources.length))
+            {
+                if (resSources[rsIdx].hasResource(resource))
+                {
+                    bytes = resSources[rsIdx].getBytes(resource);
+                }
+            }
+
+            if (bytes == null)
+            {
+                throw new IOException("Unable to find resource: " + url.toString());
+            }
+
+            is = new ByteArrayInputStream(bytes);
+            contentLength = bytes.length;
+            contentTime = 0L;  // TODO: Change this.
+            contentType = URLConnection.guessContentTypeFromName(resource);
+            connected = true;
+        }
+    }
+
+    public InputStream getInputStream()
+        throws IOException
+    {
+        if (!connected)
+        {
+            connect();
+        }
+        return is;
+    }
+
+    public int getContentLength()
+    {
+        if (!connected)
+        {
+            try {
+                connect();
+            } catch(IOException ex) {
+                return -1;
+            }
+        }
+        return contentLength;
+    }
+
+    public long getLastModified()
+    {
+        if (!connected)
+        {
+            try {
+                connect();
+            } catch(IOException ex) {
+                return 0;
+            }
+        }
+        if(contentTime != -1L)
+            return contentTime;
+        else
+            return 0L;
+    }
+
+    public String getContentType()
+    {
+        if (!connected)
+        {
+            try {
+                connect();
+            } catch(IOException ex) {
+                return null;
+            }
+        }
+        return contentType;
+    }
+
+    public Permission getPermission()
+    {
+        // TODO: This should probably return a FilePermission
+        // to access the bundle JAR file, but we don't have the
+        // necessary information here to construct the absolute
+        // path of the JAR file...so it would take some
+        // re-arranging to get this to work.
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleURLStreamHandler.java b/src/org/apache/osgi/framework/BundleURLStreamHandler.java
new file mode 100644
index 0000000..3948c63
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleURLStreamHandler.java
@@ -0,0 +1,37 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.IOException;
+import java.net.*;
+
+import org.apache.osgi.moduleloader.ModuleManager;
+
+class BundleURLStreamHandler extends URLStreamHandler
+{
+    private ModuleManager m_mgr = null;
+
+    public BundleURLStreamHandler(ModuleManager mgr)
+    {
+        m_mgr = mgr;
+    }
+
+    protected URLConnection openConnection(URL url) throws IOException
+    {
+        return new BundleURLConnection(m_mgr, url);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ExportedPackageImpl.java b/src/org/apache/osgi/framework/ExportedPackageImpl.java
new file mode 100644
index 0000000..19cbecc
--- /dev/null
+++ b/src/org/apache/osgi/framework/ExportedPackageImpl.java
@@ -0,0 +1,111 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.apache.osgi.framework.searchpolicy.R4Version;
+import org.osgi.framework.Bundle;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+class ExportedPackageImpl implements ExportedPackage
+{
+    private Felix m_felix = null;
+    private BundleImpl m_exporter = null;
+    private String m_name = null;
+    private R4Version m_version = null;
+    private String m_toString = null;
+    private String m_versionString = null;
+
+    public ExportedPackageImpl(
+        Felix felix, BundleImpl exporter, String name, R4Version version)
+    {
+        m_felix = felix;
+        m_exporter = exporter;
+        m_name = name;
+        m_version = version;
+    }
+
+    public Bundle getExportingBundle()
+    {
+        // If remove is pending due to a bundle update, then
+        // return null per the spec.
+        if (m_exporter.getInfo().isRemovalPending())
+        {
+            return null;
+        }
+        return m_exporter;
+    }
+
+    /**
+     * Returns the exporting bundle whether the package is state or
+     * not. This is called internally to get access to the exporting
+     * bundle during a refresh operation, which is not possible using
+     * <tt>getExportingBundle</tt> since the specification says that
+     * method must return <tt>null</tt> for stale packages.
+     * @return the exporting bundle for the package.
+    **/
+    protected Bundle getExportingBundleInternal()
+    {
+        return m_exporter;
+    }
+    
+    public Bundle[] getImportingBundles()
+    {
+        // If remove is pending due to a bundle update, then
+        // return null per the spec.
+        if (m_exporter.getInfo().isRemovalPending())
+        {
+            return null;
+        }
+        return m_felix.getImportingBundles(this);
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public String getSpecificationVersion()
+    {
+        if (m_versionString == null)
+        {
+            if (m_version == null)
+            {
+                m_versionString = "0.0.0";
+            }
+            else
+            {
+                m_versionString = m_version.toString();
+            }
+        }
+        return m_versionString;
+    }
+
+    public boolean isRemovalPending()
+    {
+        return m_exporter.getInfo().isRemovalPending();
+    }
+
+    public String toString()
+    {
+        if (m_toString == null)
+        {
+            m_toString = m_name
+                + "; version=" + getSpecificationVersion();
+        }
+        return m_toString;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/FakeURLStreamHandler.java b/src/org/apache/osgi/framework/FakeURLStreamHandler.java
new file mode 100644
index 0000000..363c10f
--- /dev/null
+++ b/src/org/apache/osgi/framework/FakeURLStreamHandler.java
@@ -0,0 +1,39 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.IOException;
+import java.net.*;
+
+/**
+ * This class implements a fake stream handler. This class is necessary in
+ * some cases when assigning <tt>CodeSource</tt>s to classes in
+ * <tt>BundleClassLoader</tt>. In general, the bundle location is an URL
+ * and this URL is used as the code source for the bundle's associated
+ * classes. The OSGi specification does not require that the bundle
+ * location be an URL, though, so in that case we try to generate a
+ * fake URL for the code source of the bundle, which is just the location
+ * string prepended with the "location:" protocol, by default. We need
+ * this fake handler to avoid an unknown protocol exception.
+**/
+class FakeURLStreamHandler extends URLStreamHandler
+{
+    protected URLConnection openConnection(URL url) throws IOException
+    {
+        return null;
+    }
+}
diff --git a/src/org/apache/osgi/framework/Felix.java b/src/org/apache/osgi/framework/Felix.java
new file mode 100644
index 0000000..5fa6092
--- /dev/null
+++ b/src/org/apache/osgi/framework/Felix.java
@@ -0,0 +1,3675 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.*;
+import java.net.*;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.util.*;
+
+import org.apache.osgi.framework.cache.*;
+import org.apache.osgi.framework.searchpolicy.*;
+import org.apache.osgi.framework.util.*;
+import org.apache.osgi.framework.util.Util;
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.ResolveException;
+import org.apache.osgi.moduleloader.search.ResolveListener;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+public class Felix
+{
+    // Logging related member variables.
+    private LogWrapper m_logger = new LogWrapper();
+    // Config properties.
+    private PropertyResolver m_config = new ConfigImpl();
+    // Configuration properties passed into constructor.
+    private MutablePropertyResolver m_configProps = null;
+
+    // MODULE MANAGER.
+    private ModuleManager m_mgr = null;
+
+    // Object used as a lock when calculating which bundles
+    // when performing an operation on one or more bundles.
+    private Object[] m_bundleLock = new Object[0];
+
+    // Maps a bundle location to a bundle location;
+    // used to reserve a location when installing a bundle.
+    private Map m_installRequestMap = null;
+    // This lock must be acquired to modify m_installRequestMap;
+    // to help avoid deadlock this lock as priority 1 and should
+    // be acquired before locks with lower priority.
+    private Object[] m_installRequestLock_Priority1 = new Object[0];
+
+    // Maps a bundle location to a bundle.
+    private HashMap m_installedBundleMap = null;
+    // This lock must be acquired to modify m_installedBundleMap;
+    // to help avoid deadlock this lock as priority 2 and should
+    // be acquired before locks with lower priority.
+    private Object[] m_installedBundleLock_Priority2 = new Object[0];
+
+    // An array of uninstalled bundles before a refresh occurs.
+    private BundleImpl[] m_uninstalledBundles = null;
+    // This lock must be acquired to modify m_uninstalledBundles;
+    // to help avoid deadlock this lock as priority 3 and should
+    // be acquired before locks with lower priority.
+    private Object[] m_uninstalledBundlesLock_Priority3 = new Object[0];
+
+    // Status flag for framework.
+    public static final int INITIAL_STATUS  = -1;
+    public static final int RUNNING_STATUS  = 0;
+    public static final int STARTING_STATUS = 1;
+    public static final int STOPPING_STATUS = 2;
+    private int m_frameworkStatus = INITIAL_STATUS;
+
+    // Framework's active start level.
+    private int m_activeStartLevel =
+        FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
+
+    // Local file system cache.
+    private BundleCache m_cache = null;
+
+    // Next available bundle identifier.
+    private long m_nextId = 1L;
+
+    // List of event listeners.
+    private FelixDispatchQueue m_dispatchQueue = null;
+    // Re-usable event dispatchers.
+    private Dispatcher m_frameworkDispatcher = null;
+    private Dispatcher m_bundleDispatcher = null;
+    private Dispatcher m_serviceDispatcher = null;
+
+    // Service registry.
+    private ServiceRegistry m_registry = null;
+
+    // Reusable admin permission object for all instances
+    // of the BundleImpl.
+    private static AdminPermission m_adminPerm = new AdminPermission();
+
+    /**
+     * <p>
+     * This method starts the framework instance; instances of the framework
+     * are dormant until this method is called. The caller may also provide
+     * <tt>MutablePropertyResolver</tt> implementations that the instance will
+     * use to obtain configuration or framework properties. Configuration
+     * properties are used internally by the framework and its extensions to alter
+     * its default behavior. Framework properties are used by bundles
+     * and are accessible from <tt>BundleContext.getProperty()</tt>.
+     * </p>
+     * <p>
+     * Configuration properties are the sole means to configure the framework's
+     * default behavior; the framework does not refer to any system properties for
+     * configuration information. If a <tt>MutablePropertyResolver</tt> is
+     * supplied to this method for configuration properties, then the framework will
+     * consult the <tt>MutablePropertyResolver</tt> instance for any and all
+     * configuration properties. It is possible to specify a <tt>null</tt>
+     * configuration property resolver, in which case the framework will use its
+     * default behavior in all cases. However, if the
+     * <a href="cache/DefaultBundleCache.html"><tt>DefaulBundleCache</tt></a>
+     * is used, then at a minimum a profile name or profile directory must
+     * be specified.
+     * </p>
+     * <p>
+     * The following configuration properties can be specified:
+     * </p>
+     * <ul>
+     *   <li><tt>felix.cache.class</tt> - The class name to be used when
+     *       creating an instance for the bundle cache; this class must
+     *       implement the <tt>BundleCache</tt> interface and have a default
+     *       constructor. By default, the framework will create an instance of
+     *       <tt>DefaultBundleCache</tt> for the bundle cache.
+     *   </li>
+     *   <li><tt>felix.auto.install.&lt;n&gt;</tt> - Space-delimited list of
+     *       bundles to automatically install into start level <tt>n</tt> when
+     *       the framework is started. Append a specific start level to this
+     *       property name to assign the bundles' start level
+     *       (e.g., <tt>felix.auto.install.2</tt>).
+     *   </li>
+     *   <li><tt>felix.auto.start.&lt;n&gt;</tt> - Space-delimited list of
+     *       bundles to automatically install and start into start level
+     *       <tt>n</tt> when the framework is started. Append a
+     *       specific start level to this property name to assign the
+     *       bundles' start level(e.g., <tt>felix.auto.start.2</tt>).
+     *   </li>
+     *   <li><tt>felix.startlevel.framework</tt> - The initial start level
+     *       of the framework once it starts execution; the default
+     *       value is 1.
+     *   </li>
+     *   <li><tt>felix.startlevel.bundle</tt> - The default start level for
+     *       newly installed bundles; the default value is 1.
+     *   </li>
+     *   <li><tt>felix.embedded.execution</tt> - Flag to indicate whether
+     *       the framework is embedded into a host application; the default value is
+     *       "<tt>false</tt>". If this flag is "<tt>true</tt>" then the framework
+     *       will not called <tt>System.exit()</tt> upon termination.
+     *   </li>
+     *   <li><tt>felix.strict.osgi</tt> - Flag to indicate whether the framework is
+     *       running in strict OSGi mode; the default value is "<tt>true</tt>".
+     *       If this flag is "<tt>false</tt>" it enables a non-OSGi-compliant
+     *       feature by persisting <tt>BundleActivator</tt>s that implement
+     *       <tt>Serializable</tt>. This feature is not recommended since
+     *       it is non-compliant.
+     *   </li>
+     * </ul>
+     * <p>
+     * Besides the above framework configuration properties, it is also
+     * possible to specify properties for the bundle cache. The available
+     * bundle cache properties depend on the cache implementation
+     * being used. For the properties of the default bundle cache, refer to the
+     * <a href="cache/DefaultBundleCache.html"><tt>DefaulBundleCache</tt></a>
+     * API documentation.
+     * </p>
+     * <p>
+     * Framework properties are somewhat misnamed, since they are not used by
+     * the framework, but by bundles via <tt>BundleContext.getProperty()</tt>.
+     * Please refer to bundle documentation of your specific bundle for any
+     * available properties.
+     * </p>
+     * <p>
+     * The <a href="Main.html"><tt>Main</tt></a> class implements some
+     * functionality for default property file handling, which makes it
+     * possible to specify configuration properties and framework properties
+     * in files that are automatically loaded when starting the framework. If you
+     * plan to create your own framework instance, you may be
+     * able to take advantage of the features it provides; refer to its
+     * class documentation for more information.
+     * </p>
+     * 
+     * @param configProps An object for obtaining configuration properties,
+     *        may be <tt>null</tt>.
+     * @param frameworkProps An object for obtaining framework properties,
+     *        may be <tt>null</tt>.
+     * @param activatorList A list of System Bundle activators.
+    **/
+    public synchronized void start(
+        MutablePropertyResolver configProps,
+        List activatorList)
+    {
+        if (m_frameworkStatus != INITIAL_STATUS)
+        {
+            throw new IllegalStateException("Invalid framework status: " + m_frameworkStatus);
+        }
+
+        // The framework is now in its startup sequence.
+        m_frameworkStatus = STARTING_STATUS;
+
+        // Initialize member variables.
+        m_mgr = null;
+        m_configProps = (configProps == null)
+            ? new MutablePropertyResolverImpl(new CaseInsensitiveMap()) : configProps;
+        m_activeStartLevel = FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
+        m_installRequestMap = new HashMap();
+        m_installedBundleMap = new HashMap();
+        m_uninstalledBundles = null;
+        m_cache = null;
+        m_nextId = 1L;
+        m_dispatchQueue = null;
+        m_registry = new ServiceRegistry(m_logger);
+
+        // Add a listener to the service registry; this is
+        // used to distribute service registry events to
+        // service listeners.
+        m_registry.addServiceListener(new ServiceListener() {
+            public void serviceChanged(ServiceEvent event)
+            {
+                fireServiceEvent(event);
+            }
+        });
+
+        // Create default storage system from the specified cache class
+        // or use the default cache if no custom cache was specified.
+        String className = m_config.get(FelixConstants.CACHE_CLASS_PROP);
+        if (className == null)
+        {
+            className = DefaultBundleCache.class.getName();
+        }
+
+        try
+        {
+            Class clazz = Class.forName(className);
+            m_cache = (BundleCache) clazz.newInstance();
+            m_cache.initialize(m_config, m_logger);
+        }
+        catch (Exception ex)
+        {
+            System.err.println("Error creating bundle cache:");
+            ex.printStackTrace();
+
+            // Only shutdown the JVM if the framework is running stand-alone.
+            String embedded = m_config.get(
+                FelixConstants.EMBEDDED_EXECUTION_PROP);
+            boolean isEmbedded = (embedded == null)
+                ? false : embedded.equals("true");
+            if (!isEmbedded)
+            {
+                System.exit(-1);
+            }
+            else
+            {
+                throw new RuntimeException(ex.toString());
+            }
+        }
+
+        // Create search policy for module loader.
+        R4SearchPolicy searchPolicy = new R4SearchPolicy(m_logger);
+
+        // Add a resolver listener to the search policy
+        // so that we will be notified when modules are resolved
+        // in order to update the bundle state.
+        searchPolicy.addResolverListener(new ResolveListener() {
+            public void moduleResolved(ModuleEvent event)
+            {
+                BundleImpl bundle = null;
+                try
+                {
+                    long id = BundleInfo.getBundleIdFromModuleId(
+                        event.getModule().getId());
+                    if (id >= 0)
+                    {
+                        // Update the bundle's state to resolved when the
+                        // current module is resolved; just ignore resolve
+                        // events for older revisions since this only occurs
+                        // when an update is done on an unresolved bundle
+                        // and there was no refresh performed.
+                        bundle = (BundleImpl) getBundle(id);
+
+                        // Lock the bundle first.
+                        try
+                        {
+                            acquireBundleLock(bundle);
+                            if (bundle.getInfo().getCurrentModule() == event.getModule())
+                            {
+                                bundle.getInfo().setState(Bundle.RESOLVED);
+                            }
+                        }
+                        catch (BundleException ex)
+                        {
+                            // This should not happen, but if it does
+                            // there isn't much we can do.
+                        }
+                        finally
+                        {
+                            releaseBundleLock(bundle);
+                        }
+                    }
+                }
+                catch (NumberFormatException ex)
+                {
+                    // Ignore.
+                }
+            }
+
+            public void moduleUnresolved(ModuleEvent event)
+            {
+                // We can ignore this, because the only time it
+                // should happen is when a refresh occurs. The
+                // refresh operation resets the bundle's state
+                // by calling BundleInfo.reset(), thus it is not
+                // necessary for us to reset the bundle's state
+                // here.
+            }
+        });
+
+        m_mgr = new ModuleManager(searchPolicy, new OSGiURLPolicy(this));
+
+        // Initialize dispatch queue.
+        m_dispatchQueue = new FelixDispatchQueue(m_logger);
+
+        // Initialize framework properties.
+        initializeFrameworkProperties();
+
+        // Before we reload any cached bundles, let's create a system
+        // bundle that is responsible for providing specific container
+        // related services.
+        SystemBundle systembundle = null;
+        try
+        {
+            // Create a simple bundle info for the system bundle.
+            BundleInfo info = new BundleInfo(
+                m_logger, new SystemBundleArchive(), null);
+            systembundle = new SystemBundle(this, info, activatorList);
+            systembundle.getInfo().addModule(
+                m_mgr.addModule(
+                    "0", systembundle.getAttributes(),
+                    systembundle.getResourceSources(),
+                    systembundle.getLibrarySources(),
+                    true)); // HACK ALERT! This flag indicates that we will
+                            // use the parent class loader as a resource source.
+            m_installedBundleMap.put(
+                systembundle.getInfo().getLocation(), systembundle);
+
+            // Manually resolve the System Bundle, which will cause its
+            // state to be set to RESOLVED.
+            try
+            {
+                searchPolicy.resolve(systembundle.getInfo().getCurrentModule());
+            }
+            catch (ResolveException ex)
+            {
+                // This should never happen.
+                throw new BundleException(
+                        "Unresolved package in System Bundle:"
+                        + ex.getPackage());
+            }
+
+            // Start the system bundle; this will set its state
+            // to STARTING, we must set its state to ACTIVE after
+            // all bundles are restarted below according to the spec.
+            systembundle.start();
+        }
+        catch (Exception ex)
+        {
+            m_mgr = null;
+            DispatchQueue.shutdown();
+            m_logger.log(LogWrapper.LOG_ERROR, "Unable to start system bundle.", ex);
+            throw new RuntimeException("Unable to start system bundle.");
+        }
+        
+        // Reload and cached bundles.
+        BundleArchive[] archives = null;
+
+        // First get cached bundle identifiers.
+        try
+        {
+            archives = m_cache.getArchives();
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Unable to list saved bundles: " + ex, ex);
+            archives = null;
+        }
+
+        BundleImpl bundle = null;
+
+        // Now install all cached bundles.
+        for (int i = 0; (archives != null) && (i < archives.length); i++)
+        {
+            // Make sure our id generator is not going to overlap.
+            // TODO: This is not correct since it may lead to re-used
+            // ids, which is not okay according to OSGi.
+            m_nextId = Math.max(m_nextId, archives[i].getId() + 1);
+
+            try
+            {
+                // It is possible that a bundle in the cache was previously
+                // uninstalled, but not completely deleted (perhaps because
+                // of a crash or a locked file), so if we see an archive
+                // with an UNINSTALLED persistent state, then try to remove
+                // it now.
+                if (archives[i].getPersistentState() == Bundle.UNINSTALLED)
+                {
+                    m_cache.remove(archives[i]);
+                }
+                // Otherwise re-install the cached bundle.
+                else
+                {
+                    // Install the cached bundle.
+                    bundle = (BundleImpl) installBundle(
+                        archives[i].getId(), archives[i].getLocation(), null);
+                }
+            }
+            catch (Exception ex)
+            {
+                fireFrameworkEvent(FrameworkEvent.ERROR, bundle, ex);
+                try
+                {
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Unable to re-install " + archives[i].getLocation(),
+                        ex);
+                }
+                catch (Exception ex2)
+                {
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Unable to re-install bundle " + archives[i].getId(),
+                        ex);
+                }
+                // TODO: Perhaps we should remove the cached bundle?
+            }
+        }
+
+        // Get the framework's default start level.
+        int startLevel = FelixConstants.FRAMEWORK_DEFAULT_STARTLEVEL;
+        String s = m_config.get(FelixConstants.FRAMEWORK_STARTLEVEL_PROP);
+        if (s != null)
+        {
+            try
+            {
+                startLevel = Integer.parseInt(s);
+            }
+            catch (NumberFormatException ex)
+            {
+                startLevel = FelixConstants.FRAMEWORK_DEFAULT_STARTLEVEL;
+            }
+        }
+
+        // Load bundles from auto-install and auto-start properties;
+        processAutoProperties();
+
+        // This will restart bundles if necessary.
+        setFrameworkStartLevel(startLevel);
+
+        // The framework is now running.
+        m_frameworkStatus = RUNNING_STATUS;
+
+        // Set the system bundle state to ACTIVE.
+        systembundle.getInfo().setState(Bundle.ACTIVE);
+
+        // Fire started event for system bundle.
+        fireBundleEvent(BundleEvent.STARTED, systembundle);
+
+        // Send a framework event to indicate the framework has started.
+        fireFrameworkEvent(FrameworkEvent.STARTED, getBundle(0), null);
+    }
+
+    /**
+     * This method cleanly shuts down the framework, it must be called at the
+     * end of a session in order to shutdown all active bundles.
+    **/
+    public synchronized void shutdown()
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Change framework status from running to stopping.
+        // If framework is not running, then just return.
+        if (m_frameworkStatus != RUNNING_STATUS)
+        {
+            return;
+        }
+
+        // The framework is now in its shutdown sequence.
+        m_frameworkStatus = STOPPING_STATUS;
+
+        // Set the start level to zero in order to stop
+        // all bundles in the framework.
+        setFrameworkStartLevel(0);
+
+        // Just like initialize() called the system bundle's start()
+        // method, we must call its stop() method here so that it
+        // can perform any necessary clean up.
+        try
+        {
+            getBundle(0).stop();
+        }
+        catch (Exception ex)
+        {
+            fireFrameworkEvent(FrameworkEvent.ERROR, getBundle(0), ex);
+            m_logger.log(LogWrapper.LOG_ERROR, "Error stopping system bundle.", ex);
+        }
+
+        // Loop through all bundles and update any updated bundles.
+        Bundle[] bundles = getBundles();
+        for (int i = 0; i < bundles.length; i++)
+        {
+            BundleImpl bundle = (BundleImpl) bundles[i];
+            if (bundle.getInfo().isRemovalPending())
+            {
+                try
+                {
+                    purgeBundle(bundle);
+                }
+                catch (Exception ex)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, bundle, ex);
+                    m_logger.log(LogWrapper.LOG_ERROR, "Unable to purge bundle "
+                        + bundle.getInfo().getLocation(), ex);
+                }
+            }
+        }
+
+        // Remove any uninstalled bundles.
+        for (int i = 0;
+            (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+            i++)
+        {
+            try
+            {
+                garbageCollectBundle(m_uninstalledBundles[i]);
+            }
+            catch (Exception ex)
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "Unable to remove "
+                    + m_uninstalledBundles[i].getInfo().getLocation(), ex);
+            }
+        }
+
+        // Shutdown event dispatching queue.
+        DispatchQueue.shutdown();
+
+        // The framework is no longer in a usable state.
+        m_frameworkStatus = INITIAL_STATUS;
+    }
+
+    public int getStatus()
+    {
+        return m_frameworkStatus;
+    }
+
+    /**
+     * Returns the active start level of the framework; this method
+     * implements functionality for the Start Level service.
+     * @return The active start level of the framework.
+    **/
+    protected int getStartLevel()
+    {
+        return m_activeStartLevel;
+    }
+
+    /**
+     * Implements the functionality of the <tt>setStartLevel()</tt>
+     * method for the StartLevel service, but does not do the security or
+     * parameter check. The security and parameter check are done in the
+     * StartLevel service implementation because this method is called on
+     * a separate thread and the caller's thread would already be gone if
+     * we did the checks in this method.
+     * @param requestedLevel The new start level of the framework.
+    **/
+    protected synchronized void setFrameworkStartLevel(int requestedLevel)
+    {
+        // Determine if we are lowering or raising the
+        // active start level.
+        boolean lowering = (requestedLevel < m_activeStartLevel);
+
+        // Record new start level.
+        m_activeStartLevel = requestedLevel;
+
+        // Get array of all installed bundles.
+        Bundle[] bundles = getBundles();
+
+        // Sort bundle array by start level either ascending or
+        // descending depending on whether the start level is being
+        // lowered or raised.
+        Comparator comparator = null;
+        if (lowering)
+        {
+            // Sort descending to stop highest start level first.
+            comparator = new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    BundleImpl b1 = (BundleImpl) o1;
+                    BundleImpl b2 = (BundleImpl) o2;
+                    if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        < b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return 1;
+                    }
+                    else if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        > b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+        }
+        else
+        {
+            // Sort ascending to start lowest start level first.
+            comparator = new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    BundleImpl b1 = (BundleImpl) o1;
+                    BundleImpl b2 = (BundleImpl) o2;
+                    if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        > b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return 1;
+                    }
+                    else if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        < b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+        }
+
+        Arrays.sort(bundles, comparator);
+
+        // Stop or start the bundles according to the start level.
+        for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+        {
+            BundleImpl impl = (BundleImpl) bundles[i];
+
+            // Ignore the system bundle, since its start() and
+            // stop() methods get called explicitly in initialize()
+            // and shutdown(), respectively.
+            if (impl.getInfo().getBundleId() == 0)
+            {
+                continue;
+            }
+
+            // Start the bundle if necessary.
+            if ((impl.getInfo().getPersistentState() == Bundle.ACTIVE) &&
+                (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                    <= m_activeStartLevel))
+            {
+                try
+                {
+                    startBundle(impl, false);
+                }
+                catch (Throwable th)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, impl, th);
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Error starting " + impl.getInfo().getLocation(), th);
+                }
+            }
+            // Stop the bundle if necessary.
+            else if (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                > m_activeStartLevel)
+            {
+                try
+                {
+                    stopBundle(impl, false);
+                }
+                catch (Throwable th)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, impl, th);
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Error stopping " + impl.getInfo().getLocation(), th);
+                }
+            }
+        }
+
+        fireFrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, getBundle(0), null);
+    }
+
+    /**
+     * Returns the start level into which newly installed bundles will
+     * be placed by default; this method implements functionality for
+     * the Start Level service.
+     * @return The default start level for newly installed bundles.
+    **/
+    protected int getInitialBundleStartLevel()
+    {
+        String s = m_config.get(FelixConstants.BUNDLE_STARTLEVEL_PROP);
+
+        if (s != null)
+        {
+            try
+            {
+                int i = Integer.parseInt(s);
+                return (i > 0) ? i : FelixConstants.BUNDLE_DEFAULT_STARTLEVEL;
+            }
+            catch (NumberFormatException ex)
+            {
+                // Ignore and return the default value.
+            }
+        }
+        return FelixConstants.BUNDLE_DEFAULT_STARTLEVEL;
+    }
+
+    /**
+     * Sets the default start level into which newly installed bundles
+     * will be placed; this method implements functionality for the Start
+     * Level service.
+     * @param startLevel The new default start level for newly installed
+     *        bundles.
+     * @throws java.lang.IllegalArgumentException If the specified start
+     *         level is not greater than zero.
+     * @throws java.security.SecurityException If the caller does not
+     *         have <tt>AdminPermission</tt>.
+    **/
+    protected void setInitialBundleStartLevel(int startLevel)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        if (startLevel <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Initial start level must be greater than zero.");
+        }
+
+        m_configProps.put(
+            FelixConstants.BUNDLE_STARTLEVEL_PROP, Integer.toString(startLevel));
+    }
+
+    /**
+     * Returns the start level for the specified bundle; this method
+     * implements functionality for the Start Level service.
+     * @param bundle The bundle to examine.
+     * @return The start level of the specified bundle.
+     * @throws java.lang.IllegalArgumentException If the specified
+     *          bundle has been uninstalled.
+    **/
+    protected int getBundleStartLevel(Bundle bundle)
+    {
+        if (bundle.getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalArgumentException("Bundle is uninstalled.");
+        }
+
+        return ((BundleImpl) bundle).getInfo().getStartLevel(getInitialBundleStartLevel());
+    }
+
+    /**
+     * Sets the start level of the specified bundle; this method
+     * implements functionality for the Start Level service.
+     * @param bundle The bundle whose start level is to be modified.
+     * @param startLevel The new start level of the specified bundle.
+     * @throws java.lang.IllegalArgumentException If the specified
+     *          bundle is the system bundle or if the bundle has been
+     *          uninstalled.
+     * @throws java.security.SecurityException If the caller does not
+     *          have <tt>AdminPermission</tt>.
+    **/
+    protected void setBundleStartLevel(Bundle bundle, int startLevel)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Cannot change the system bundle.
+        if (bundle.getBundleId() == 0)
+        {
+            throw new IllegalArgumentException(
+                "Cannot change system bundle start level.");
+        }
+
+        // Acquire bundle lock.
+        try
+        {
+            acquireBundleLock((BundleImpl) bundle);
+        }
+        catch (BundleException ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "Unable to acquire lock to set start level.", ex);
+            return;
+        }
+        
+        Throwable rethrow = null;
+
+        try
+        {
+            if (bundle.getState() == Bundle.UNINSTALLED)
+            {
+                throw new IllegalArgumentException("Bundle is uninstalled.");
+            }
+
+            if (startLevel >= 1)
+            {
+                BundleImpl impl = (BundleImpl) bundle;
+                impl.getInfo().setStartLevel(startLevel);
+    
+                try
+                {
+                    // Start the bundle if necessary.
+                    if ((impl.getInfo().getPersistentState() == Bundle.ACTIVE) &&
+                        (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                            <= m_activeStartLevel))
+                    {
+                        startBundle(impl, false);
+                    }
+                    // Stop the bundle if necessary.
+                    else if (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        > m_activeStartLevel)
+                    {
+                        stopBundle(impl, false);
+                    }
+                }
+                catch (Throwable th)
+                {
+                    rethrow = th;
+                    m_logger.log(LogWrapper.LOG_ERROR, "Error starting/stopping bundle.", th);
+                }
+            }
+            else
+            {
+                m_logger.log(LogWrapper.LOG_WARNING, "Bundle start level must be greater than zero.");
+            }
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock((BundleImpl) bundle);
+        }
+
+        if (rethrow != null)
+        {
+            fireFrameworkEvent(FrameworkEvent.ERROR, bundle, rethrow);
+        }
+    }
+
+    /**
+     * Returns whether a bundle is persistently started; this is an
+     * method implementation for the Start Level service.
+     * @param bundle The bundle to examine.
+     * @return <tt>true</tt> if the bundle is marked as persistently
+     *          started, <tt>false</tt> otherwise.
+     * @throws java.lang.IllegalArgumentException If the specified
+     *          bundle has been uninstalled.
+    **/
+    protected boolean isBundlePersistentlyStarted(Bundle bundle)
+    {
+        if (bundle.getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalArgumentException("Bundle is uninstalled.");
+        }
+
+        return (((BundleImpl) bundle).getInfo().getPersistentState() == Bundle.ACTIVE);
+    }
+
+    //
+    // Implementation of Bundle interface methods.
+    //
+
+    /**
+     * Implementation for Bundle.getHeaders().
+    **/
+    protected Dictionary getBundleHeaders(BundleImpl bundle)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        return new MapToDictionary(bundle.getInfo().getCurrentHeader());
+    }
+
+    /**
+     * Implementation for Bundle.getLocation().
+    **/
+    protected String getBundleLocation(BundleImpl bundle)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        return bundle.getInfo().getLocation();
+    }
+
+    /**
+     * Implementation for Bundle.getResource().
+    **/
+    protected URL getBundleResource(BundleImpl bundle, String name)
+    {
+        if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+        else if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        return bundle.getInfo().getCurrentModule().getClassLoader().getResource(name);
+    }
+
+    protected ServiceReference[] getBundleRegisteredServices(BundleImpl bundle)
+    {
+        if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+
+        // Filter list of registered service references.
+        ServiceReference[] refs = m_registry.getRegisteredServices(bundle);
+        List list = new ArrayList();
+        for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+        {
+            // Check that the current security context has permission
+            // to get at least one of the service interfaces; the
+            // objectClass property of the service stores its service
+            // interfaces.
+            boolean hasPermission = false;
+            if (System.getSecurityManager() != null)
+            {
+                String[] objectClass = (String[])
+                    refs[refIdx].getProperty(Constants.OBJECTCLASS);
+                if (objectClass == null)
+                {
+                    return null;
+                }
+                for (int ifcIdx = 0;
+                    !hasPermission && (ifcIdx < objectClass.length);
+                    ifcIdx++)
+                {
+                    try
+                    {
+                        ServicePermission perm =
+                            new ServicePermission(
+                                objectClass[ifcIdx], ServicePermission.GET);
+                        AccessController.checkPermission(perm);
+                        hasPermission = true;
+                    }
+                    catch (Exception ex)
+                    {
+                    }
+                }
+            }
+            else
+            {
+                hasPermission = true;
+            }
+
+            if (hasPermission)
+            {
+                list.add(refs[refIdx]);
+            }
+        }
+
+        if (list.size() > 0)
+        {
+            return (ServiceReference[])
+                list.toArray(new ServiceReference[list.size()]);
+        }
+
+        return null;
+    }
+
+    protected ServiceReference[] getBundleServicesInUse(Bundle bundle)
+    {
+        // Filter list of "in use" service references.
+        ServiceReference[] refs = m_registry.getServicesInUse(bundle);
+        List list = new ArrayList();
+        for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+        {
+            // Check that the current security context has permission
+            // to get at least one of the service interfaces; the
+            // objectClass property of the service stores its service
+            // interfaces.
+            boolean hasPermission = false;
+            if (System.getSecurityManager() != null)
+            {
+                String[] objectClass = (String[])
+                    refs[refIdx].getProperty(Constants.OBJECTCLASS);
+                if (objectClass == null)
+                {
+                    return null;
+                }
+                for (int ifcIdx = 0;
+                    !hasPermission && (ifcIdx < objectClass.length);
+                    ifcIdx++)
+                {
+                    try
+                    {
+                        ServicePermission perm =
+                            new ServicePermission(
+                                objectClass[ifcIdx], ServicePermission.GET);
+                        AccessController.checkPermission(perm);
+                        hasPermission = true;
+                    }
+                    catch (Exception ex)
+                    {
+                    }
+                }
+            }
+            else
+            {
+                hasPermission = true;
+            }
+
+            if (hasPermission)
+            {
+                list.add(refs[refIdx]);
+            }
+        }
+
+        if (list.size() > 0)
+        {
+            return (ServiceReference[])
+                list.toArray(new ServiceReference[list.size()]);
+        }
+
+        return null;
+    }
+
+    protected boolean bundleHasPermission(BundleImpl bundle, Object obj)
+    {
+        if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+
+// TODO: IMPLEMENT THIS CORRECTLY.
+        return true;
+    }
+
+    /**
+     * Implementation for Bundle.start().
+    **/
+    protected void startBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // CONCURRENCY NOTE:
+        // Starting a bundle may actually impact many bundles, since
+        // the bundle being started my need to be resolved, which in
+        // turn may need to resolve other bundles. Despite this fact,
+        // we only acquire the lock for the bundle being started, because
+        // when resolve is called on this bundle, it will eventually
+        // call resolve on the module loader search policy, which does
+        // its own locking on the module manager instance. Since the 
+        // resolve algorithm is locking the module manager instance, it
+        // is not possible for other bundles to be installed or removed,
+        // so we don't have to worry about these possibilities.
+        //
+        // Further, if other bundles are started during this operation,
+        // then either they will resolve first because they got the lock
+        // on the module manager or we will resolve first since we got
+        // the lock on the module manager, so there should be no interference.
+        // If other bundles are stopped or uninstalled, this should pose
+        // no problems, since this does not impact their resolved state.
+        // If a refresh occurs, then the refresh algorithm ulimately has
+        // to acquire the module manager instance lock too before it can
+        // completely purge old modules, so it should also complete either
+        // before or after this bundle is started. At least that's the
+        // theory.
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _startBundle(bundle, record);
+        }
+        finally
+        {
+            // Release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void _startBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        // Set and save the bundle's persistent state to active
+        // if we are supposed to record state change.
+        if (record)
+        {
+            bundle.getInfo().setPersistentStateActive();
+        }
+
+        // Try to start the bundle.
+        BundleInfo info = bundle.getInfo();
+
+        // Ignore bundles whose persistent state is not active
+        // or whose start level is greater than the framework's.
+        if ((info.getPersistentState() != Bundle.ACTIVE)
+            || (info.getStartLevel(getInitialBundleStartLevel()) > getStartLevel()))
+        {
+            return;
+        }
+
+        switch (info.getState())
+        {
+            case Bundle.UNINSTALLED:
+                throw new IllegalStateException("Cannot start an uninstalled bundle.");
+            case Bundle.STARTING:
+            case Bundle.STOPPING:
+                throw new BundleException("Starting a bundle that is starting or stopping is currently not supported.");
+            case Bundle.ACTIVE:
+                return;
+            case Bundle.INSTALLED:
+                _resolveBundle(bundle);
+            case Bundle.RESOLVED:
+                info.setState(Bundle.STARTING);
+        }
+
+        try
+        {
+            // Set the bundle's activator.
+            bundle.getInfo().setActivator(createBundleActivator(bundle.getInfo()));
+
+            // Activate the bundle if it has an activator.
+            if (bundle.getInfo().getActivator() != null)
+            {
+                if (info.getContext() == null)
+                {
+                    info.setContext(new BundleContextImpl(this, bundle));
+                }
+
+                if (System.getSecurityManager() != null)
+                {
+//                    m_startStopPrivileged.setAction(StartStopPrivileged.START_ACTION);
+//                    m_startStopPrivileged.setBundle(bundle);
+//                    AccessController.doPrivileged(m_startStopPrivileged);
+                }
+                else
+                {
+                    info.getActivator().start(info.getContext());
+                }
+            }
+
+            info.setState(Bundle.ACTIVE);
+
+            fireBundleEvent(BundleEvent.STARTED, bundle);
+        }
+        catch (Throwable th)
+        {
+            // If there was an error starting the bundle,
+            // then reset its state to RESOLVED.
+            info.setState(Bundle.RESOLVED);
+
+            // Unregister any services offered by this bundle.
+            m_registry.unregisterServices(bundle);
+
+            // Release any services being used by this bundle.
+            m_registry.ungetServices(bundle);
+
+            // Remove any listeners registered by this bundle.
+            removeListeners(bundle);
+
+            // The spec says to expect BundleException or
+            // SecurityException, so rethrow these exceptions.
+            if (th instanceof BundleException)
+            {
+                throw (BundleException) th;
+            }
+            else if (th instanceof SecurityException)
+            {
+                throw (SecurityException) th;
+            }
+            // Convert a privileged action exception to the
+            // nested exception.
+            else if (th instanceof PrivilegedActionException)
+            {
+                th = ((PrivilegedActionException) th).getException();
+            }
+
+            // Rethrow all other exceptions as a BundleException.
+            throw new BundleException("Activator start error.", th);
+        }
+    }
+
+    protected void _resolveBundle(BundleImpl bundle)
+        throws BundleException
+    {
+        // If a security manager is installed, then check for permission
+        // to import the necessary packages.
+        if (System.getSecurityManager() != null)
+        {
+            URL url = null;
+            try
+            {
+                url = new URL(bundle.getInfo().getLocation());
+            }
+            catch (MalformedURLException ex)
+            {
+                throw new BundleException("Cannot resolve, bad URL "
+                    + bundle.getInfo().getLocation());
+            }
+
+//            try
+//            {
+//                AccessController.doPrivileged(new CheckImportsPrivileged(url, bundle));
+//            }
+//            catch (PrivilegedActionException ex)
+//            {
+//                Exception thrown = ((PrivilegedActionException) ex).getException();
+//                if (thrown instanceof AccessControlException)
+//                {
+//                    throw (AccessControlException) thrown;
+//                }
+//                else
+//                {
+//                    throw new BundleException("Problem resolving: " + ex);
+//                }
+//            }
+        }
+
+        // Get the import search policy.
+        R4SearchPolicy search = (R4SearchPolicy) m_mgr.getSearchPolicy();
+
+        Module module = bundle.getInfo().getCurrentModule();
+        try
+        {
+            search.resolve(module);
+        }
+        catch (ResolveException ex)
+        {
+            if (ex.getModule() != null)
+            {
+                throw new BundleException(
+                    "Unresolved package in bundle "
+                    + BundleInfo.getBundleIdFromModuleId(ex.getModule().getId())
+                    + ": " + ex.getPackage());
+            }
+            else
+            {
+                throw new BundleException(ex.getMessage());
+            }
+        }
+
+        bundle.getInfo().setState(Bundle.RESOLVED);
+    }
+
+    protected void updateBundle(BundleImpl bundle, InputStream is)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _updateBundle(bundle, is);
+        }
+        finally
+        {
+            // Release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    protected void _updateBundle(BundleImpl bundle, InputStream is)
+        throws BundleException
+    {
+        // We guarantee to close the input stream, so put it in a
+        // finally clause.
+    
+        try
+        {
+            // Variable to indicate whether bundle is active or not.
+            Exception rethrow = null;
+
+            // Cannot update an uninstalled bundle.
+            BundleInfo info = bundle.getInfo();
+            if (info.getState() == Bundle.UNINSTALLED)
+            {
+                throw new IllegalStateException("The bundle is uninstalled.");
+            }
+
+            // First get the update-URL from our header.
+            String updateLocation = (String)
+                info.getCurrentHeader().get(Constants.BUNDLE_UPDATELOCATION);
+
+            // If no update location specified, use original location.
+            if (updateLocation == null)
+            {
+                updateLocation = info.getLocation();
+            }
+
+            // Stop the bundle, but do not change the persistent state.
+            stopBundle(bundle, false);
+
+            try
+            {
+                // Get the URL input stream if necessary.
+                if (is == null)
+                {
+                    // Do it the manual way to have a chance to 
+                    // set request properties such as proxy auth.
+                    URL url = new URL(updateLocation);
+                    URLConnection conn = url.openConnection(); 
+
+                    // Support for http proxy authentication.
+                    String auth = System.getProperty("http.proxyAuth");
+                    if ((auth != null) && (auth.length() > 0))
+                    {
+                        if ("http".equals(url.getProtocol()) ||
+                            "https".equals(url.getProtocol()))
+                        {
+                            String base64 = Util.base64Encode(auth);
+                            conn.setRequestProperty(
+                                "Proxy-Authorization", "Basic " + base64);
+                        }
+                    }
+                    is = conn.getInputStream();
+                }
+
+                // Get the bundle's archive.
+                BundleArchive archive = m_cache.getArchive(info.getBundleId());
+                // Update the bundle; this operation will increase
+                // the revision count for the bundle.
+                m_cache.update(archive, is);
+                // Create a module for the new revision; the revision is
+                // base zero, so subtract one from the revision count to
+                // get the revision of the new update.
+                Module module = createModule(
+                    info.getBundleId(),
+                    archive.getRevisionCount() - 1,
+                    info.getCurrentHeader());
+                // Add module to bundle info.
+                info.addModule(module);
+            }
+            catch (Exception ex)
+            {
+                m_logger.log(LogWrapper.LOG_ERROR, "Unable to update the bundle.", ex);
+                rethrow = ex;
+            }
+
+            info.setState(Bundle.INSTALLED);
+
+            // Mark as needing a refresh.
+            info.setRemovalPending();
+    
+            // Fire updated event if successful.
+            if (rethrow == null)
+            {
+                fireBundleEvent(BundleEvent.UPDATED, bundle);
+            }
+    
+            // Restart bundle, but do not change the persistent state.
+            // This will not start the bundle if it was not previously
+            // active.
+            startBundle(bundle, false);
+    
+            // If update failed, rethrow exception.
+            if (rethrow != null)
+            {
+                throw new BundleException("Update failed.", rethrow);
+            }
+        }
+        finally
+        {
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex)
+            {
+                m_logger.log(LogWrapper.LOG_ERROR, "Unable to close input stream.", ex);
+            }
+        }
+    }
+
+    protected void stopBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _stopBundle(bundle, record);
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void _stopBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        Throwable rethrow = null;
+    
+        // Set the bundle's persistent state to inactive if necessary.
+        if (record)
+        {
+            bundle.getInfo().setPersistentStateInactive();
+        }
+
+        BundleInfo info = bundle.getInfo();
+        
+        switch (info.getState())
+        {
+            case Bundle.UNINSTALLED:
+                throw new IllegalStateException("Cannot stop an uninstalled bundle.");
+            case Bundle.STARTING:
+            case Bundle.STOPPING:
+                throw new BundleException("Stopping a bundle that is starting or stopping is currently not supported.");
+            case Bundle.INSTALLED:
+            case Bundle.RESOLVED:
+                return;
+            case Bundle.ACTIVE:
+                // Set bundle state..
+                info.setState(Bundle.STOPPING);
+        }
+            
+        try
+        {
+            if (bundle.getInfo().getActivator() != null)
+            {
+                if (System.getSecurityManager() != null)
+                {
+//                    m_startStopPrivileged.setAction(StartStopPrivileged.STOP_ACTION);
+//                    m_startStopPrivileged.setBundle(bundle);
+//                    AccessController.doPrivileged(m_startStopPrivileged);
+                }
+                else
+                {
+                    info.getActivator().stop(info.getContext());
+                }
+            }
+        
+            // Try to save the activator in the cache.
+            // NOTE: This is non-standard OSGi behavior and only
+            // occurs if strictness is disabled.
+            String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
+            boolean isStrict = (strict == null) ? true : strict.equals("true");
+            if (!isStrict)
+            {
+                try
+                {
+                    m_cache.getArchive(info.getBundleId())
+                        .setActivator(info.getActivator());
+                }
+                catch (Exception ex)
+                {
+                    // Problem saving activator, so ignore it.
+                    // TODO: Perhaps we should handle this some other way?
+                }
+            }
+        }
+        catch (Throwable th)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "Error stopping bundle.", th);
+            rethrow = th;
+        }
+                  
+        // Unregister any services offered by this bundle.
+        m_registry.unregisterServices(bundle);
+        
+        // Release any services being used by this bundle.
+        m_registry.ungetServices(bundle);
+        
+        // The spec says that we must remove all event
+        // listeners for a bundle when it is stopped.
+        removeListeners(bundle);
+        
+        info.setState(Bundle.RESOLVED);
+        fireBundleEvent(BundleEvent.STOPPED, bundle);
+        
+        // Throw activator error if there was one.
+        if (rethrow != null)
+        {
+            // The spec says to expect BundleException or
+            // SecurityException, so rethrow these exceptions.
+            if (rethrow instanceof BundleException)
+            {
+                throw (BundleException) rethrow;
+            }
+            else if (rethrow instanceof SecurityException)
+            {
+                throw (SecurityException) rethrow;
+            }
+            else if (rethrow instanceof PrivilegedActionException)
+            {
+                rethrow = ((PrivilegedActionException) rethrow).getException();
+            }
+    
+            // Rethrow all other exceptions as a BundleException.
+            throw new BundleException("Activator stop error.", rethrow);
+        }
+    }
+
+    protected void uninstallBundle(BundleImpl bundle) throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _uninstallBundle(bundle);
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void _uninstallBundle(BundleImpl bundle) throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        BundleException rethrow = null;
+
+        BundleInfo info = bundle.getInfo();
+        if (info.getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+
+        // The spec says that uninstall should always succeed, so
+        // catch an exception here if stop() doesn't succeed and
+        // rethrow it at the end.
+        try
+        {
+            stopBundle(bundle, true);
+        }
+        catch (BundleException ex)
+        {
+            rethrow = ex;
+        }
+
+        // Remove the bundle from the installed map.
+        BundleImpl target = null;
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            target = (BundleImpl) m_installedBundleMap.remove(info.getLocation());
+        }
+
+        // Finally, put the uninstalled bundle into the
+        // uninstalled list for subsequent refreshing.
+        if (target != null)
+        {
+            // Set the bundle's persistent state to uninstalled.
+            target.getInfo().setPersistentStateUninstalled();
+
+            // Mark bundle for removal.
+            target.getInfo().setRemovalPending();
+
+            // Put bundle in uninstalled bundle array.
+            rememberUninstalledBundle(bundle);
+        }
+        else
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR, "Unable to remove bundle from installed map!");
+        }
+
+        // Set state to uninstalled.
+        info.setState(Bundle.UNINSTALLED);
+
+        // Fire bundle event.
+        fireBundleEvent(BundleEvent.UNINSTALLED, bundle);
+
+        if (rethrow != null)
+        {
+            throw rethrow;
+        }
+    }
+
+    //
+    // Implementation of BundleContext interface methods.
+    //
+
+    /**
+     * Implementation for BundleContext.getProperty(). Returns
+     * environment property associated with the framework.
+     *
+     * @param key The name of the property to retrieve.
+     * @return The value of the specified property or null.
+    **/
+    protected String getProperty(String key)
+    {
+        // First, check the config properties.
+        String val = (String) m_configProps.get(key);
+        // If not found, then try the system properties.
+        return (val == null) ? System.getProperty(key) : val;
+    }
+
+    protected Bundle installBundle(String location, InputStream is)
+        throws BundleException
+    {
+        return installBundle(-1, location, is);
+    }
+
+    private Bundle installBundle(long id, String location, InputStream is)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+    
+        BundleImpl bundle = null;
+
+        // Acquire an install lock.
+        acquireInstallLock(location);
+
+        try
+        {
+            // Check to see if the framework is still running;
+            if ((getStatus() == Felix.STOPPING_STATUS) ||
+                (getStatus() == Felix.INITIAL_STATUS))
+            {
+                throw new BundleException("The framework has been shutdown.");
+            }
+
+            // If bundle location is already installed, then
+            // return it as required by the OSGi specification.
+            bundle = (BundleImpl) getBundle(location);
+            if (bundle != null)
+            {
+                return bundle;
+            }
+
+            // Determine if this is a new or existing bundle.
+            boolean isNew = (id < 0);
+
+            // If the bundle is new we must cache its JAR file.
+            if (isNew)
+            {
+                // First generate an identifier for it.
+                id = getNextId();
+
+                try
+                {
+                    // Get the URL input stream if necessary.
+                    if (is == null)
+                    {
+                        // Do it the manual way to have a chance to 
+                        // set request properties such as proxy auth.
+                        URL url = new URL(location);
+                        URLConnection conn = url.openConnection(); 
+
+                        // Support for http proxy authentication.
+                        String auth = System.getProperty("http.proxyAuth");
+                        if ((auth != null) && (auth.length() > 0))
+                        {
+                            if ("http".equals(url.getProtocol()) ||
+                                "https".equals(url.getProtocol()))
+                            {
+                                String base64 = Util.base64Encode(auth);
+                                conn.setRequestProperty(
+                                    "Proxy-Authorization", "Basic " + base64);
+                            }
+                        }
+                        is = conn.getInputStream();
+                    }
+                    // Add the bundle to the cache.
+                    m_cache.create(id, location, is);
+                }
+                catch (Exception ex)
+                {
+                    throw new BundleException(
+                        "Unable to cache bundle: " + location, ex);
+                }
+                finally
+                {
+                    try
+                    {
+                        if (is != null) is.close();
+                    }
+                    catch (IOException ex)
+                    {
+                        m_logger.log(
+                            LogWrapper.LOG_ERROR,
+                            "Unable to close input stream.", ex);
+                    }
+                }
+            }
+            else
+            {
+                // If the bundle we are installing is not new,
+                // then try to purge old revisions before installing
+                // it; this is done just in case a "refresh"
+                // didn't occur last session...this would only be
+                // due to an error or system crash.
+                try
+                {
+                    if (m_cache.getArchive(id).getRevisionCount() > 1)
+                    {
+                        m_cache.purge(m_cache.getArchive(id));
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ex.printStackTrace();
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Could not purge bundle.", ex);
+                }
+            }
+
+            try
+            {
+                BundleArchive archive = m_cache.getArchive(id);
+                bundle = new BundleImpl(this, createBundleInfo(archive));
+            }
+            catch (Exception ex)
+            {
+                // If the bundle is new, then remove it from the cache.
+                // TODO: Perhaps it should be removed if it is not new too.
+                if (isNew)
+                {
+                    try
+                    {
+                        m_cache.remove(m_cache.getArchive(id));
+                    }
+                    catch (Exception ex1)
+                    {
+                        m_logger.log(
+                            LogWrapper.LOG_ERROR,
+                            "Could not remove from cache.", ex1);
+                    }
+                }
+                throw new BundleException("Could not create bundle object.", ex);
+            }
+
+            // If the bundle is new, then set its start level; existing
+            // bundles already have their start level set.
+            if (isNew)
+            {
+                // This will persistently set the bundle's start level.
+                bundle.getInfo().setStartLevel(getInitialBundleStartLevel());
+            }
+
+            synchronized (m_installedBundleLock_Priority2)
+            {
+                m_installedBundleMap.put(location, bundle);
+            }
+        }
+        finally
+        {
+            // Always release install lock.
+            releaseInstallLock(location);
+
+            // Always try to close the input stream.
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex)
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "Unable to close input stream.", ex);
+                // Not much else we can do.
+            }
+        }
+    
+        // Fire bundle event.
+        fireBundleEvent(BundleEvent.INSTALLED, bundle);
+    
+        // Return new bundle.
+        return bundle;
+    }
+
+    /**
+     * Retrieves a bundle from its location.
+     *
+     * @param location The location of the bundle to retrieve.
+     * @return The bundle associated with the location or null if there
+     *         is no bundle associated with the location.
+    **/
+    protected Bundle getBundle(String location)
+    {
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            return (Bundle) m_installedBundleMap.get(location);
+        }
+    }
+
+    /**
+     * Implementation for BundleContext.getBundle(). Retrieves a
+     * bundle from its identifier.
+     *
+     * @param id The identifier of the bundle to retrieve.
+     * @return The bundle associated with the identifier or null if there
+     *         is no bundle associated with the identifier.
+    **/
+    protected Bundle getBundle(long id)
+    {
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            BundleImpl bundle = null;
+
+            for (Iterator i = m_installedBundleMap.values().iterator(); i.hasNext(); )
+            {
+                bundle = (BundleImpl) i.next();
+                if (bundle.getInfo().getBundleId() == id)
+                {
+                    return bundle;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    // Private member for method below.
+    private Comparator m_comparator = null;
+
+    /**
+     * Implementation for BundleContext.getBundles(). Retrieves
+     * all installed bundles.
+     *
+     * @return An array containing all installed bundles or null if
+     *         there are no installed bundles.
+    **/
+    protected Bundle[] getBundles()
+    {
+        if (m_comparator == null)
+        {
+            m_comparator = new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    Bundle b1 = (Bundle) o1;
+                    Bundle b2 = (Bundle) o2;
+                    if (b1.getBundleId() > b2.getBundleId())
+                        return 1;
+                    else if (b1.getBundleId() < b2.getBundleId())
+                        return -1;
+                    return 0;
+                }
+            };
+        }
+
+        Bundle[] bundles = null;
+
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            if (m_installedBundleMap.size() == 0)
+            {
+                return null;
+            }
+
+            bundles = new Bundle[m_installedBundleMap.size()];
+            int counter = 0;
+            for (Iterator i = m_installedBundleMap.values().iterator(); i.hasNext(); )
+            {
+                bundles[counter++] = (Bundle) i.next();
+            }
+        }
+
+        Arrays.sort(bundles, m_comparator);
+
+        return bundles;
+    }
+
+    protected void addBundleListener(Bundle bundle, BundleListener l)
+    {
+        // The spec says do nothing if the listener is
+        // already registered.
+        BundleListenerWrapper old = (BundleListenerWrapper)
+            m_dispatchQueue.getListener(BundleListener.class, l);
+        if (old == null)
+        {
+            l = new BundleListenerWrapper(bundle, l);
+            m_dispatchQueue.addListener(BundleListener.class, l);
+        }
+    }
+
+    protected void removeBundleListener(BundleListener l)
+    {
+        m_dispatchQueue.removeListener(BundleListener.class, l);
+    }
+
+    /**
+     * Implementation for BundleContext.addServiceListener().
+     * Adds service listener to the listener list so that is
+     * can listen for <code>ServiceEvent</code>s.
+     *
+     * @param bundle The bundle that registered the listener.
+     * @param l The service listener to add to the listener list.
+     * @param f The filter for the listener; may be null.
+    **/
+    protected void addServiceListener(Bundle bundle, ServiceListener l, String f)
+        throws InvalidSyntaxException
+    {
+        // The spec says if the listener is already registered,
+        // then replace filter.
+        ServiceListenerWrapper old = (ServiceListenerWrapper)
+            m_dispatchQueue.getListener(ServiceListener.class, l);
+        if (old != null)
+        {
+            old.setFilter((f == null) ? null : new FilterImpl(m_logger, f));
+        }
+        else
+        {
+            l = new ServiceListenerWrapper(
+                bundle, l, (f == null) ? null : new FilterImpl(m_logger, f));
+            m_dispatchQueue.addListener(ServiceListener.class, l);
+        }
+    }
+
+    /**
+     * Implementation for BundleContext.removeServiceListener().
+     * Removes service listeners from the listener list.
+     *
+     * @param l The service listener to remove from the listener list.
+    **/
+    protected void removeServiceListener(ServiceListener l)
+    {
+        m_dispatchQueue.removeListener(ServiceListener.class, l);
+    }
+
+    protected void addFrameworkListener(Bundle bundle, FrameworkListener l)
+    {
+        // The spec says do nothing if the listener is
+        // already registered.
+        FrameworkListenerWrapper old = (FrameworkListenerWrapper)
+            m_dispatchQueue.getListener(FrameworkListener.class, l);
+        if (old == null)
+        {
+            l = new FrameworkListenerWrapper(bundle, l);
+            m_dispatchQueue.addListener(FrameworkListener.class, l);
+        }
+    }
+
+    protected void removeFrameworkListener(FrameworkListener l)
+    {
+        m_dispatchQueue.removeListener(FrameworkListener.class, l);
+    }
+
+    /**
+     * Remove all of the specified bundle's event listeners from
+     * the framework.
+     * @param bundle The bundle whose listeners are to be removed.
+    **/
+    private void removeListeners(Bundle bundle)
+    {
+        if (bundle == null)
+        {
+            return;
+        }
+
+        // Remove all listeners associated with the supplied bundle;
+        // it is only possible to know the bundle associated with a
+        // listener if the listener was wrapper by a ListenerWrapper,
+        // so look for those.
+        Object[] listeners = m_dispatchQueue.getListeners();
+        for (int i = listeners.length - 2; i >= 0; i -= 2)
+        {
+            // Check for listener wrappers and then compare the bundle.
+            if (listeners[i + 1] instanceof ListenerWrapper)
+            {
+                ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+                if ((lw.getBundle() != null) && (lw.getBundle().equals(bundle)))
+                {
+                    m_dispatchQueue.removeListener(
+                        (Class) listeners[i], (EventListener) listeners[i+1]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Implementation for BundleContext.registerService(). Registers
+     * a service for the specified bundle bundle.
+     *
+     * @param classNames A string array containing the names of the classes
+     *                under which the new service is available.
+     * @param svcObj The service object or <code>ServiceFactory</code>.
+     * @param dict A dictionary of properties that further describe the
+     *             service or null.
+     * @return A <code>ServiceRegistration</code> object or null.
+    **/
+    protected ServiceRegistration registerService(
+        BundleImpl bundle, String[] classNames, Object svcObj, Dictionary dict)
+    {
+        if (classNames == null)
+        {
+            throw new NullPointerException("Service class names cannot be null.");
+        }
+        else if (svcObj == null)
+        {
+            throw new IllegalArgumentException("Service object cannot be null.");
+        }
+
+        // Check for permission to register all passed in interface names.
+        if (System.getSecurityManager() != null)
+        {
+            for (int i = 0; i < classNames.length; i++)
+            {
+                ServicePermission perm = new ServicePermission(
+                    classNames[i], ServicePermission.REGISTER);
+                AccessController.checkPermission(perm);
+            }
+        }
+
+        // Acquire bundle lock.
+        try
+        {
+            acquireBundleLock(bundle);
+        }
+        catch (BundleException ex)
+        {
+            // This would probably only happen when the bundle is uninstalled.
+            throw new IllegalStateException(
+                "Can only register services while bundle is active or activating.");
+        }
+
+        ServiceRegistration reg = null;
+
+        try
+        {
+            BundleInfo info = bundle.getInfo();
+
+            // Can only register services if starting or active.
+            if ((info.getState() & (Bundle.STARTING | Bundle.ACTIVE)) == 0)
+            {
+                throw new IllegalStateException(
+                    "Can only register services while bundle is active or activating.");
+            }
+
+            // Check to make sure that the service object is
+            // an instance of all service classes; ignore if
+            // service object is a service factory.
+            if (!(svcObj instanceof ServiceFactory))
+            {
+                for (int i = 0; i < classNames.length; i++)
+                {
+                    Class clazz = loadClassUsingClass(svcObj.getClass(), classNames[i]);
+                    if (clazz == null)
+                    {
+                        throw new IllegalArgumentException(
+                            "Cannot cast service: " + classNames[i]);
+                    }
+                    else if (!clazz.isAssignableFrom(svcObj.getClass()))
+                    {
+                        throw new IllegalArgumentException(
+                            "Service object is not an instance of \""
+                            + classNames[i] + "\".");
+                    }
+                }
+            }
+
+            reg = m_registry.registerService(bundle, classNames, svcObj, dict);
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock(bundle);
+        }
+        
+        // NOTE: The service registered event is fired from the service
+        // registry to the framework, where it is then redistributed to
+        // interested service event listeners.
+
+        return reg;
+    }
+
+    /**
+     * <p>
+     * This is a simple utility class that attempts to load the named
+     * class using the class loader of the supplied class or
+     * the class loader of one of its super classes or their implemented
+     * interfaces. This is necessary during service registration to test
+     * whether a given service object implements its declared service
+     * interfaces.
+     * </p>
+     * <p>
+     * To perform this test, the framework must try to load
+     * the classes associated with the declared service interfaces, so
+     * it must choose a class loader. The class loader of the registering
+     * bundle cannot be used, since this disallows third parties to
+     * register service on behalf of another bundle. Consequently, the
+     * class loader of the service object must be used. However, this is
+     * also not sufficient since the class loader of the service object
+     * may not have direct access to the class in question.
+     * </p>
+     * <p>
+     * The service object's class loader may not have direct access to
+     * its service interface if it extends a super class from another
+     * bundle which implements the service interface from an imported
+     * bundle or if it implements an extension of the service interface
+     * from another bundle which imports the base interface from another
+     * bundle. In these cases, the service object's class loader only has
+     * access to the super class's class or the extended service interface,
+     * respectively, but not to the actual service interface.
+     * </p>
+     * <p>
+     * Thus, it is necessary to not only try to load the service interface
+     * class from the service object's class loader, but from the class
+     * loaders of any interfaces it implements and the class loaders of
+     * all super classes.
+     * </p>
+     * @param svcObj the class that is the root of the search.
+     * @param name the name of the class to load.
+     * @return the loaded class or <tt>null</tt> if it could not be
+     *         loaded.
+    **/
+    private static Class loadClassUsingClass(Class clazz, String name)
+    {
+        while (clazz != null)
+        {
+            // Get the class loader of the current class object.
+            ClassLoader loader = clazz.getClassLoader();
+            // A null class loader represents the system class loader.
+            loader = (loader == null) ? ClassLoader.getSystemClassLoader() : loader;
+            try
+            {
+                return loader.loadClass(name);
+            }
+            catch (ClassNotFoundException ex)
+            {
+                // Ignore and try interface class loaders.
+            }
+
+            // Try to see if we can load the class from
+            // one of the class's implemented interface
+            // class loaders.
+            Class[] ifcs = clazz.getInterfaces();
+            for (int i = 0; i < ifcs.length; i++)
+            {
+                clazz = loadClassUsingClass(ifcs[i], name);
+                if (clazz != null)
+                {
+                    return clazz;
+                }
+            }
+
+            // Try to see if we can load the class from
+            // the super class class loader.
+            clazz = clazz.getSuperclass();
+        }
+
+        return null;
+    }
+
+    protected ServiceReference[] getServiceReferences(
+        BundleImpl bundle, String className, String expr)
+        throws InvalidSyntaxException
+    {
+        // Define filter if expression is not null.
+        Filter filter = null;
+        if (expr != null)
+        {
+            filter = new FilterImpl(m_logger, expr);
+        }
+
+        // Ask the service registry for all matching service references.
+        List refList = m_registry.getServiceReferences(className, filter);
+
+        // The returned reference list must be filtered for two cases:
+        // 1) The requesting bundle may not be wired to the same class
+        //    as the providing bundle (i.e, different versions), so filter
+        //    any services for which the requesting bundle might get a
+        //    class cast exception.
+        // 2) Security is enabled and the requesting bundle does not have
+        //    permission access the service.
+        for (int refIdx = 0; (refList != null) && (refIdx < refList.size()); refIdx++)
+        {
+            // Get the current service reference.
+            ServiceReference ref = (ServiceReference) refList.get(refIdx);
+
+            // Get the service's objectClass property.
+            String[] objectClass = (String[]) ref.getProperty(FelixConstants.OBJECTCLASS);
+
+            // Boolean flag.
+            boolean allow = false;
+
+            // Filter the service reference if the requesting bundle
+            // does not have permission.
+            if (System.getSecurityManager() != null)
+            {
+                for (int classIdx = 0;
+                    !allow && (classIdx < objectClass.length);
+                    classIdx++)
+                {
+                    try
+                    {
+                        ServicePermission perm = new ServicePermission(
+                            objectClass[classIdx], ServicePermission.GET);
+                        AccessController.checkPermission(perm);
+                        // The bundle only needs permission for one
+                        // of the service interfaces, so break out
+                        // of the loop when permission is granted.
+                        allow = true;
+                    }
+                    catch (Exception ex)
+                    {
+                        // We do not throw this exception since the bundle
+                        // is not supposed to know about the service at all
+                        // if it does not have permission.
+                        m_logger.log(LogWrapper.LOG_ERROR, ex.getMessage());
+                    }
+                }
+                
+                if (!allow)
+                {
+                    refList.remove(refIdx);
+                    refIdx--;
+                    continue;
+                }
+            }
+
+            // Now check for castability.
+            if (!isServiceAssignable(bundle, ref))
+            {
+                refList.remove(refIdx);
+                refIdx--;
+            }
+        }
+
+        if (refList.size() > 0)
+        {
+            return (ServiceReference[]) refList.toArray(new ServiceReference[refList.size()]);
+        }
+
+        return null;
+    }
+
+    /**
+     * This method determines if the requesting bundle is able to cast
+     * the specified service reference based on class visibility rules
+     * of the underlying modules.
+     * @param requester The bundle requesting the service.
+     * @param ref The service in question.
+     * @return <tt>true</tt> if the requesting bundle is able to case
+     *         the service object to a known type.
+    **/
+    protected boolean isServiceAssignable(BundleImpl requester, ServiceReference ref)
+    {
+        // Boolean flag.
+        boolean allow = true;
+        // Get the service's objectClass property.
+        String[] objectClass = (String[]) ref.getProperty(FelixConstants.OBJECTCLASS);
+
+        // The the service reference is not assignable when the requesting
+        // bundle is wired to a different version of the service object.
+        // NOTE: We are pessimistic here, if any class in the service's
+        // objectClass is not usable by the requesting bundle, then we
+        // disallow the service reference.
+        for (int classIdx = 0; (allow) && (classIdx < objectClass.length); classIdx++)
+        {
+            if (!ref.isAssignableTo(requester, objectClass[classIdx]))
+            {
+                allow = false;
+            }
+        }
+        return allow;
+    }
+
+    protected Object getService(Bundle bundle, ServiceReference ref)
+    {
+        // Check that the bundle has permission to get at least
+        // one of the service interfaces; the objectClass property
+        // of the service stores its service interfaces.
+        String[] objectClass = (String[])
+            ref.getProperty(Constants.OBJECTCLASS);
+        if (objectClass == null)
+        {
+            return null;
+        }
+
+        boolean hasPermission = false;
+        if (System.getSecurityManager() != null)
+        {
+            for (int i = 0;
+                !hasPermission && (i < objectClass.length);
+                i++)
+            {
+                try
+                {
+                    ServicePermission perm =
+                        new ServicePermission(
+                            objectClass[i], ServicePermission.GET);
+                    AccessController.checkPermission(perm);
+                    hasPermission = true;
+                }
+                catch (Exception ex)
+                {
+                }
+            }
+        }
+        else
+        {
+            hasPermission = true;
+        }
+
+        // If the bundle does not permission to access the service,
+        // then return null.
+        if (!hasPermission)
+        {
+            return null;
+        }
+
+        return m_registry.getService(bundle, ref);
+    }
+
+    protected boolean ungetService(Bundle bundle, ServiceReference ref)
+    {
+        return m_registry.ungetService(bundle, ref);
+    }
+
+    protected File getDataFile(BundleImpl bundle, String s)
+    {
+        // The spec says to throw an error if the bundle
+        // is stopped, which I assume means not active,
+        // starting, or stopping.
+        if ((bundle.getInfo().getState() != Bundle.ACTIVE) &&
+            (bundle.getInfo().getState() != Bundle.STARTING) &&
+            (bundle.getInfo().getState() != Bundle.STOPPING))
+        {
+            throw new IllegalStateException("Only active bundles can create files.");
+        }
+        try
+        {
+            return m_cache.getArchive(
+                bundle.getInfo().getBundleId()).getDataFile(s);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, ex.getMessage());
+            return null;
+        }
+    }
+
+    //
+    // PackageAdmin related methods.
+    //
+
+    /**
+     * Returns the exported package associated with the specified
+     * package name. This is used by the PackageAdmin service
+     * implementation.
+     *
+     * @param name The name of the exported package to find.
+     * @return The exported package or null if no matching package was found.
+    **/
+    protected ExportedPackage getExportedPackage(String pkgName)
+    {
+        // First, find the bundle exporting the package.
+        BundleImpl bundle = null;
+        R4SearchPolicy search = (R4SearchPolicy) m_mgr.getSearchPolicy();
+        Module[] exporters = search.getInUseExporters(new R4Package(pkgName, null, null));
+        if (exporters != null)
+        {
+            // Since OSGi R4 there may be more than one exporting, so just
+            // take the first one.
+            bundle = (BundleImpl) getBundle(
+                BundleInfo.getBundleIdFromModuleId(exporters[0].getId()));
+        }
+
+        // If we have found the exporting bundle, then return the
+        // exported package interface instance.
+        if (bundle != null)
+        {
+            // We need to find the version of the exported package, but this
+            // is tricky since there may be multiple versions of the package
+            // offered by a given bundle, since multiple revisions of the
+            // bundle JAR file may exist if the bundle was updated without
+            // refreshing the framework. In this case, each revision of the
+            // bundle JAR file is represented as a module in the BundleInfo
+            // module array, which is ordered from oldest to newest. We assume
+            // that the first module found to be exporting the package is the
+            // provider of the package, which makes sense since it must have
+            // been resolved first.
+            Module[] modules = bundle.getInfo().getModules();
+            for (int modIdx = 0; modIdx < modules.length; modIdx++)
+            {
+                R4Package pkg = R4SearchPolicy.getExportPackage(modules[modIdx], pkgName);
+                if (pkg != null)
+                {
+                    return new ExportedPackageImpl(this, bundle, pkgName, pkg.getVersionLow());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns an array of all actively exported packages from the specified
+     * bundle or if the specified bundle is <tt>null</tt> an array
+     * containing all actively exported packages by all bundles.
+     *
+     * @param b The bundle whose exported packages are to be retrieved
+     *        or <tt>null</tt> if the exported packages of all bundles are
+     *        to be retrieved.
+     * @return An array of exported packages.
+    **/
+    protected ExportedPackage[] getExportedPackages(Bundle b)
+    {
+        List list = new ArrayList();
+
+        // If a bundle is specified, then return its
+        // exported packages.
+        if (b != null)
+        {
+            BundleImpl bundle = (BundleImpl) b;
+            getExportedPackages(bundle, list);
+        }
+        // Otherwise return all exported packages.
+        else
+        {
+            // To create a list of all exported packages, we must look
+            // in the installed and uninstalled sets of bundles. To
+            // ensure a somewhat consistent view, we will gather all
+            // of this information from within the installed bundle
+            // lock.
+            synchronized (m_installedBundleLock_Priority2)
+            {
+                // First get exported packages from uninstalled bundles.
+                synchronized (m_uninstalledBundlesLock_Priority3)
+                {
+                    for (int bundleIdx = 0;
+                        (m_uninstalledBundles != null) && (bundleIdx < m_uninstalledBundles.length);
+                        bundleIdx++)
+                    {
+                        BundleImpl bundle = m_uninstalledBundles[bundleIdx];
+                        getExportedPackages(bundle, list);
+                    }
+                }
+
+                // Now get exported packages from installed bundles.
+                Bundle[] bundles = getBundles();
+                for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+                {
+                    BundleImpl bundle = (BundleImpl) bundles[bundleIdx];
+                    getExportedPackages(bundle, list);
+                }
+            }
+        }
+
+        return (ExportedPackage[]) list.toArray(new ExportedPackage[list.size()]);
+    }
+
+    /**
+     * Adds any current active exported packages from the specified bundle
+     * to the passed in list.
+     * @param bundle The bundle from which to retrieve exported packages.
+     * @param list The list to which the exported packages are added
+    **/
+    private void getExportedPackages(BundleImpl bundle, List list)
+    {
+        R4SearchPolicy policy = (R4SearchPolicy) m_mgr.getSearchPolicy();
+
+        // Since a bundle may have many modules associated with it,
+        // one for each revision in the cache, search each module
+        // for each revision to get all exports.
+        Module[] modules = bundle.getInfo().getModules();
+        for (int modIdx = 0; modIdx < modules.length; modIdx++)
+        {
+            R4Package[] exports = R4SearchPolicy.getExportsAttr(modules[modIdx]);
+            if (exports.length > 0)
+            {
+                for (int expIdx = 0; expIdx < exports.length; expIdx++)
+                {
+                    // See if the target bundle's module is one of the
+                    // "in use" exporters of the package.
+                    Module[] inUseModules = policy.getInUseExporters(exports[expIdx]);
+                    if (R4SearchPolicy.isModuleInArray(inUseModules, modules[modIdx]))
+                    {
+                        list.add(new ExportedPackageImpl(
+                            this, bundle, exports[expIdx].getId(), exports[expIdx].getVersionLow()));
+                    }
+                }
+            }
+        }
+    }
+
+    protected Bundle[] getImportingBundles(ExportedPackage ep)
+    {
+        // Get exporting bundle; we need to use this internal
+        // method because the spec says ep.getExportingBundle()
+        // should return null if the package is stale.
+        BundleImpl exporter = (BundleImpl)
+            ((ExportedPackageImpl) ep).getExportingBundleInternal();
+        BundleInfo exporterInfo = exporter.getInfo();
+
+        // Create list for storing importing bundles.
+        List list = new ArrayList();
+        Bundle[] bundles = getBundles();
+
+        // Check all bundles to see who imports the package.
+        for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+        {
+            BundleImpl importer = (BundleImpl) bundles[bundleIdx];
+
+            // Ignore the bundle if it imports from itself.
+            if (exporter != importer)
+            {
+                // Check the import wires of all modules for all bundles.
+                Module[] modules = importer.getInfo().getModules();
+                for (int modIdx = 0; modIdx < modules.length; modIdx++)
+                {
+                    R4Wire wire = R4SearchPolicy.getWire(modules[modIdx], ep.getName());
+    
+                    // If the resolving module is associated with the
+                    // exporting bundle, then add current bundle to
+                    // import list.
+                    if ((wire != null) && exporterInfo.hasModule(wire.m_module))
+                    {
+                        // Add the bundle to the list of importers.
+                        list.add(bundles[bundleIdx]);
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Return the results.
+        if (list.size() > 0)
+        {
+            return (Bundle[]) list.toArray(new Bundle[list.size()]);
+        }
+
+        return null;
+    }
+
+    protected void refreshPackages(Bundle[] targets)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire locks for all impacted bundles.
+        BundleImpl[] bundles = acquireBundleRefreshLocks(targets);
+
+        // Remove any targeted bundles from the uninstalled bundles
+        // array, since they will be removed from the system after
+        // the refresh.
+        for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+        {
+            forgetUninstalledBundle(bundles[i]);
+        }
+
+        try
+        {
+            // If there are targets, then refresh each one.
+            if (bundles != null)
+            {
+                // At this point the map contains every bundle that has been
+                // updated and/or removed as well as all bundles that import
+                // packages from these bundles.
+                
+                // Create refresh helpers for each bundle.
+                RefreshHelper[] helpers = new RefreshHelper[bundles.length];
+                for (int i = 0; i < bundles.length; i++)
+                {
+                    helpers[i] = new RefreshHelper(bundles[i]);
+                }
+
+                // Stop, purge or remove, and reinitialize all bundles first.
+                for (int i = 0; i < helpers.length; i++)
+                {
+                    helpers[i].stop();
+                    helpers[i].purgeOrRemove();
+                    helpers[i].reinitialize();
+                }
+
+                // Then restart all bundles that were previously running.
+                for (int i = 0; i < helpers.length; i++)
+                {
+                    helpers[i].restart();
+                }
+            }
+        }
+        finally
+        {
+            // Always release all bundle locks.
+            releaseBundleLocks(bundles);
+        }
+
+        fireFrameworkEvent(FrameworkEvent.PACKAGES_REFRESHED, getBundle(0), null);
+    }
+
+    private void populateImportGraph(BundleImpl target, Map map)
+    {
+        // Get the exported packages for the specified bundle.
+        ExportedPackage[] pkgs = getExportedPackages(target);
+
+        for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+        {
+            // Get all imports of this package.
+            Bundle[] importers = getImportingBundles(pkgs[pkgIdx]);
+
+            for (int impIdx = 0;
+                (importers != null) && (impIdx < importers.length);
+                impIdx++)
+            {
+                    // Add each importing bundle to map.
+                    map.put(importers[impIdx], importers[impIdx]);
+                    // Now recurse into each bundle to get its importers.
+                    populateImportGraph(
+                        (BundleImpl) importers[impIdx], map);
+            }
+        }
+    }
+
+    //
+    // Miscellaneous private methods.
+    //
+
+    private BundleInfo createBundleInfo(BundleArchive archive)
+        throws Exception
+    {
+        // Get the bundle manifest.
+        Map headerMap = null;
+        try
+        {
+            // Although there should only ever be one revision at this
+            // point, get the header for the current revision to be safe.
+            headerMap = archive.getManifestHeader(archive.getRevisionCount() - 1);
+        }
+        catch (Exception ex)
+        {
+            throw new BundleException("Unable to read JAR manifest.", ex);
+        }
+
+        // We can't do anything without the manifest header.
+        if (headerMap == null)
+        {
+            throw new BundleException("Unable to read JAR manifest header.");
+        }
+
+        // Create the module for the bundle; although there should only
+        // ever be one revision at this point, create the module for
+        // the current revision to be safe.
+        Module module = createModule(
+            archive.getId(), archive.getRevisionCount() - 1, headerMap);
+
+        // Finally, create an return the bundle info.
+        return new BundleInfo(m_logger, archive, module);
+    }
+
+    /**
+     * Creates a module for a given bundle by reading the bundle's
+     * manifest meta-data and converting it to work with the underlying
+     * import/export search policy of the module loader.
+     * @param id The identifier of the bundle for which the module should
+     *        be created.
+     * @param headers The headers map associated with the bundle.
+     * @return The initialized and/or newly created module.
+    **/
+    private Module createModule(long id, int revision, Map headerMap)
+        throws Exception
+    {
+        // Get the manifest version.
+        String version = (String) headerMap.get(FelixConstants.BUNDLE_MANIFESTVERSION);
+        version = (version == null) ? "1" : version;
+        if (!version.equals("1") && !version.equals("2"))
+        {
+            throw new BundleException("Unknown 'Bundle-ManifestVersion' value: " + version);
+        }
+
+        // Create the resource sources for the bundle. The resource sources
+        // are comprised of the bundle's class path values (as JarResourceSources).
+        ResourceSource[] resSources = null;
+        try
+        {
+            // Get bundle class path for the specified revision from cache.
+            String[] classPath = m_cache.getArchive(id).getClassPath(revision);
+
+            // Create resource sources for everything.
+            resSources = new ResourceSource[classPath.length];
+            for (int i = 0; i < classPath.length; i++)
+            {
+                resSources[i] = new JarResourceSource(classPath[i]);
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+            throw new BundleException("Error in class path: " + ex);
+        }
+
+        // Get import packages from bundle manifest.
+        R4Package[] imports = R4Package.parseImportOrExportHeader(
+            (String) headerMap.get(Constants.IMPORT_PACKAGE));
+
+
+        // Check to make sure that R3 bundles have only specified
+        // the 'specification-version' attribute and no directives.
+        if (version.equals("1"))
+        {
+            for (int i = 0; (imports != null) && (i < imports.length); i++)
+            {
+                if (imports[i].getDirectives().length != 0)
+                {
+                    throw new BundleException("R3 imports cannot contain directives.");
+                }
+                // NOTE: This is checking for "version" rather than "specification-version"
+                // because the package class normalizes to "version" to avoid having
+                // future special cases. This could be changed if more strict behavior
+                // is required.
+                if ((imports[i].getVersionHigh() != null) ||
+                    (imports[i].getAttributes().length > 1) ||
+                    ((imports[i].getAttributes().length == 1) &&
+                        (!imports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
+                {
+                    throw new BundleException(
+                        "Import does not conform to R3 syntax: " + imports[i]);
+                }
+            }
+        }
+
+        // Get export packages from bundle manifest.
+        R4Package[] exports = R4Package.parseImportOrExportHeader(
+            (String) headerMap.get(Constants.EXPORT_PACKAGE));
+
+        // Check to make sure that R3 bundles have only specified
+        // the 'specification-version' attribute and no directives.
+        // In addition, all R3 exports imply imports, so add a
+        // corresponding import for each export.
+        if (version.equals("1"))
+        {
+            for (int i = 0; (exports != null) && (i < exports.length); i++)
+            {
+                if (exports[i].getDirectives().length != 0)
+                {
+                    throw new BundleException("R3 exports cannot contain directives.");
+                }
+                // NOTE: This is checking for "version" rather than "specification-version"
+                // because the package class normalizes to "version" to avoid having
+                // future special cases. This could be changed if more strict behavior
+                // is required.
+                if ((exports[i].getAttributes().length > 1) ||
+                    ((exports[i].getAttributes().length == 1) &&
+                        (!exports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
+                {
+                    throw new BundleException(
+                        "Export does not conform to R3 syntax: " + imports[i]);
+                }
+            }
+            
+            R4Package[] newImports = new R4Package[imports.length + exports.length];
+            System.arraycopy(imports, 0, newImports, 0, imports.length);
+            System.arraycopy(exports, 0, newImports, imports.length, exports.length);
+            imports = newImports;
+        }
+
+        // For R3 bundles, add a "uses" directive onto each export
+        // that references every other import (which will include
+        // exports, since export implies import); this is
+        // necessary since R3 bundles assumed a single class space,
+        // but R4 allows for multiple class spaces.
+        if (version.equals("1"))
+        {
+            String usesValue = "";
+            for (int i = 0; (imports != null) && (i < imports.length); i++)
+            {
+                usesValue = usesValue
+                    + ((usesValue.length() > 0) ? "," : "")
+                    + imports[i].getId();
+            }
+            R4Directive uses = new R4Directive(
+                FelixConstants.USES_DIRECTIVE, usesValue);
+            for (int i = 0; (exports != null) && (i < exports.length); i++)
+            {
+                exports[i] = new R4Package(
+                    exports[i].getId(),
+                    new R4Directive[] { uses },
+                    exports[i].getAttributes());
+            }
+        }
+
+// TODO: CHECK FOR DUPLICATE IMPORTS/EXPORTS HERE.
+
+        // Get dynamic import packages from bundle manifest.
+        R4Package[] dynamics = R4Package.parseImportOrExportHeader(
+            (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+
+        // Check to make sure that R3 bundles have no attributes or
+        // directives.
+        if (version.equals("1"))
+        {
+            for (int i = 0; (dynamics != null) && (i < dynamics.length); i++)
+            {
+                if (dynamics[i].getDirectives().length != 0)
+                {
+                    throw new BundleException("R3 dynamic imports cannot contain directives.");
+                }
+                if (dynamics[i].getAttributes().length != 0)
+                {
+                    throw new BundleException("R3 dynamic imports cannot contain attributes.");
+                }
+            }
+        }
+
+        Object[][] attributes = {
+            new Object[] { R4SearchPolicy.EXPORTS_ATTR, exports },
+            new Object[] { R4SearchPolicy.IMPORTS_ATTR, imports },
+            new Object[] { R4SearchPolicy.DYNAMICIMPORTS_ATTR, dynamics }
+        };
+
+        // Get native library entry names for module library sources.
+        LibraryInfo[] libraries =
+            Util.parseLibraryStrings(
+                Util.parseDelimitedString(
+                    (String) headerMap.get(Constants.BUNDLE_NATIVECODE), ","));
+        LibrarySource[] libSources = {
+            new OSGiLibrarySource(
+                m_logger, m_cache, id, revision,
+                getProperty(Constants.FRAMEWORK_OS_NAME),
+                getProperty(Constants.FRAMEWORK_PROCESSOR),
+                libraries)
+        };
+
+        Module module =
+            m_mgr.addModule(
+                Long.toString(id) + "." + Integer.toString(revision),
+                attributes, resSources, libSources);
+
+        return module;
+    }
+
+    private BundleActivator createBundleActivator(BundleInfo info)
+        throws Exception
+    {
+        // CONCURRENCY NOTE:
+        // This method is called indirectly from startBundle() (via _startBundle()),
+        // which has the exclusion lock, so there is no need to do any locking here.
+    
+        BundleActivator activator = null;
+    
+        String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
+        boolean isStrict = (strict == null) ? true : strict.equals("true");
+        if (!isStrict)
+        {
+            try
+            {
+                activator =
+                    m_cache.getArchive(info.getBundleId())
+                        .getActivator(info.getCurrentModule().getClassLoader());
+            }
+            catch (Exception ex)
+            {
+                activator = null;
+            }
+        }
+    
+        // If there was no cached activator, then get the activator
+        // class from the bundle manifest.
+        if (activator == null)
+        {
+            // Get the associated bundle archive.
+            BundleArchive ba = m_cache.getArchive(info.getBundleId());
+            // Get the manifest from the current revision; revision is
+            // base zero so subtract one from the count to get the
+            // current revision.
+            Map headerMap = ba.getManifestHeader(ba.getRevisionCount() - 1);
+            // Get the activator class attribute.
+            String className = (String) headerMap.get(Constants.BUNDLE_ACTIVATOR);
+            // Try to instantiate activator class if present.
+            if (className != null)
+            {
+                className = className.trim();
+                Class clazz = info.getCurrentModule().getClassLoader().loadClass(className);
+                if (clazz == null)
+                {
+                    throw new BundleException("Not found: "
+                        + className);
+                }
+                activator = (BundleActivator) clazz.newInstance();
+            }
+        }
+    
+        return activator;
+    }
+
+    private void purgeBundle(BundleImpl bundle) throws Exception
+    {
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            BundleInfo info = bundle.getInfo();
+    
+            // In case of a refresh, then we want to physically
+            // remove the bundle's modules from the module manager.
+            // This is necessary for two reasons: 1) because
+            // under Windows we won't be able to delete the bundle
+            // because files might be left open in the resource
+            // sources of its modules and 2) we want to make sure
+            // that no references to old modules exist since they
+            // will all be stale after the refresh. The only other
+            // way to do this is to remove the bundle, but that
+            // would be incorrect, because this is a refresh operation
+            // and should not trigger bundle REMOVE events.
+            Module[] modules = info.getModules();
+            for (int i = 0; i < modules.length; i++)
+            {
+                m_mgr.removeModule(modules[i]);
+            }
+
+            // Purge all bundle revisions, but the current one.
+            m_cache.purge(m_cache.getArchive(info.getBundleId()));
+        }
+        finally
+        {
+            // Always release the bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void garbageCollectBundle(BundleImpl bundle) throws Exception
+    {
+        // CONCURRENCY NOTE: There is no reason to lock this bundle,
+        // because this method is only called during shutdown or a
+        // refresh operation and these are already guarded by locks.
+
+        // Remove the bundle's associated modules from
+        // the module manager.
+        Module[] modules = bundle.getInfo().getModules();
+        for (int i = 0; i < modules.length; i++)
+        {
+            m_mgr.removeModule(modules[i]);
+        }
+
+        // Remove the bundle from the cache.
+        m_cache.remove(m_cache.getArchive(bundle.getInfo().getBundleId()));
+    }
+
+    //
+    // Event-related methods.
+    //
+
+    /**
+     * Fires bundle events.
+    **/
+    private void fireFrameworkEvent(
+        int type, Bundle bundle, Throwable throwable)
+    {
+        if (m_frameworkDispatcher == null)
+        {
+            m_frameworkDispatcher = new Dispatcher() {
+                public void dispatch(EventListener l, EventObject eventObj)
+                {
+                    ((FrameworkListener) l)
+                        .frameworkEvent((FrameworkEvent) eventObj);
+                }
+            };
+        }
+        FrameworkEvent event = new FrameworkEvent(type, bundle, throwable);
+        m_dispatchQueue.dispatch(
+            m_frameworkDispatcher, FrameworkListener.class, event);
+    }
+
+    /**
+     * Fires bundle events.
+     *
+     * @param type The type of bundle event to fire.
+     * @param bundle The bundle associated with the event.
+    **/
+    private void fireBundleEvent(int type, Bundle bundle)
+    {
+        if (m_bundleDispatcher == null)
+        {
+            m_bundleDispatcher = new Dispatcher() {
+                public void dispatch(EventListener l, EventObject eventObj)
+                {
+                    ((BundleListener) l)
+                        .bundleChanged((BundleEvent) eventObj);
+                }
+            };
+        }
+        BundleEvent event = null;
+        event = new BundleEvent(type, bundle);
+        m_dispatchQueue.dispatch(m_bundleDispatcher,
+            BundleListener.class, event);
+    }
+
+    /**
+     * Fires service events.
+     *
+     * @param type The type of service event to fire.
+     * @param ref The service reference associated with the event.
+    **/
+    private void fireServiceEvent(ServiceEvent event)
+    {
+        if (m_serviceDispatcher == null)
+        {
+            m_serviceDispatcher = new Dispatcher() {
+                public void dispatch(EventListener l, EventObject eventObj)
+                {
+// TODO: Filter service events based on service permissions.
+                    if (l instanceof ListenerWrapper)
+                    {
+                        BundleImpl bundle = (BundleImpl) ((ServiceListenerWrapper) l).getBundle();
+                        if (isServiceAssignable(bundle, ((ServiceEvent) eventObj).getServiceReference()))
+                        {
+                            ((ServiceListener) l)
+                                .serviceChanged((ServiceEvent) eventObj);
+                        }
+                    }
+                    else
+                    {
+                        ((ServiceListener) l)
+                            .serviceChanged((ServiceEvent) eventObj);
+                    }
+                }
+            };
+        }
+        m_dispatchQueue.dispatch(m_serviceDispatcher,
+            ServiceListener.class, event);
+    }
+
+    //
+    // Property related methods.
+    //
+
+    private void initializeFrameworkProperties()
+    {
+        // Standard OSGi properties.
+        m_configProps.put(
+            FelixConstants.FRAMEWORK_VERSION,
+            FelixConstants.FRAMEWORK_VERSION_VALUE);
+        m_configProps.put(
+            FelixConstants.FRAMEWORK_VENDOR,
+            FelixConstants.FRAMEWORK_VENDOR_VALUE);
+        m_configProps.put(
+            FelixConstants.FRAMEWORK_LANGUAGE,
+            System.getProperty("user.language"));
+        m_configProps.put(
+            FelixConstants.FRAMEWORK_OS_VERSION,
+            System.getProperty("os.version"));
+
+        String s = null;
+        s = OSGiLibrarySource.normalizePropertyValue(
+            FelixConstants.FRAMEWORK_OS_NAME,
+            System.getProperty("os.name"));
+        m_configProps.put(FelixConstants.FRAMEWORK_OS_NAME, s);
+        s = OSGiLibrarySource.normalizePropertyValue(
+            FelixConstants.FRAMEWORK_PROCESSOR,
+            System.getProperty("os.arch"));
+        m_configProps.put(FelixConstants.FRAMEWORK_PROCESSOR, s);
+
+        // The framework version property.
+        m_configProps.put(
+            FelixConstants.FELIX_VERSION_PROPERTY,
+            FelixConstants.FELIX_VERSION_VALUE);
+    }
+
+    private void processAutoProperties()
+    {
+        // The auto-install property specifies a space-delimited list of
+        // bundle URLs to be automatically installed into each new profile;
+        // the start level to which the bundles are assigned is specified by
+        // appending a ".n" to the auto-install property name, where "n" is
+        // the desired start level for the list of bundles.
+        String[] keys = m_config.getKeys();
+        for (int i = 0; (keys != null) && (i < keys.length); i++)
+        {
+            if (keys[i].startsWith(FelixConstants.AUTO_INSTALL_PROP))
+            {
+                int startLevel = 1;
+                try
+                {
+                    startLevel = Integer.parseInt(keys[i].substring(keys[i].lastIndexOf('.') + 1));
+                }
+                catch (NumberFormatException ex)
+                {
+                    m_logger.log(LogWrapper.LOG_ERROR, "Invalid property: " + keys[i]);
+                }
+                StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+                if (st.countTokens() > 0)
+                {
+                    String location = null;
+                    do
+                    {
+                        location = nextLocation(st);
+                        if (location != null)
+                        {
+                            try
+                            {
+                                BundleImpl b = (BundleImpl) installBundle(location, null);
+                                b.getInfo().setStartLevel(startLevel);
+                            }
+                            catch (Exception ex)
+                            {
+                                m_logger.log(
+                                    LogWrapper.LOG_ERROR, "Auto-properties install.", ex);
+                            }
+                        }
+                    }
+                    while (location != null);
+                }
+            }
+        }
+
+        // The auto-start property specifies a space-delimited list of
+        // bundle URLs to be automatically installed and started into each
+        // new profile; the start level to which the bundles are assigned
+        // is specified by appending a ".n" to the auto-start property name,
+        // where "n" is the desired start level for the list of bundles.
+        // The following code starts bundles in two passes, first it installs
+        // them, then it starts them.
+        for (int i = 0; (keys != null) && (i < keys.length); i++)
+        {
+            if (keys[i].startsWith(FelixConstants.AUTO_START_PROP))
+            {
+                int startLevel = 1;
+                try
+                {
+                    startLevel = Integer.parseInt(keys[i].substring(keys[i].lastIndexOf('.') + 1));
+                }
+                catch (NumberFormatException ex)
+                {
+                    m_logger.log(LogWrapper.LOG_ERROR, "Invalid property: " + keys[i]);
+                }
+                StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+                if (st.countTokens() > 0)
+                {
+                    String location = null;
+                    do
+                    {
+                        location = nextLocation(st);
+                        if (location != null)
+                        {
+                            try
+                            {
+                                BundleImpl b = (BundleImpl) installBundle(location, null);
+                                b.getInfo().setStartLevel(startLevel);
+                            }
+                            catch (Exception ex)
+                            {
+                                m_logger.log(LogWrapper.LOG_ERROR, "Auto-properties install.", ex);
+                            }
+                        }
+                    }
+                    while (location != null);
+                }
+            }
+        }
+
+        // Now loop through and start the installed bundles.
+        for (int i = 0; (keys != null) && (i < keys.length); i++)
+        {
+            if (keys[i].startsWith(FelixConstants.AUTO_START_PROP))
+            {
+                StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+                if (st.countTokens() > 0)
+                {
+                    String location = null;
+                    do
+                    {
+                        location = nextLocation(st);
+                        if (location != null)
+                        {
+                            // Installing twice just returns the same bundle.
+                            try
+                            {
+                                BundleImpl bundle = (BundleImpl) installBundle(location, null);
+                                if (bundle != null)
+                                {
+                                    startBundle(bundle, true);
+                                }
+                            }
+                            catch (Exception ex)
+                            {
+                                m_logger.log(
+                                    LogWrapper.LOG_ERROR, "Auto-properties start.", ex);
+                            }
+                        }
+                    }
+                    while (location != null);
+                }
+            }
+        }
+    }
+
+    private String nextLocation(StringTokenizer st)
+    {
+        String retVal = null;
+
+        if (st.countTokens() > 0)
+        {
+            String tokenList = "\" ";
+            StringBuffer tokBuf = new StringBuffer(10);
+            String tok = null;
+            boolean inQuote = false;
+            boolean tokStarted = false;
+            boolean exit = false;
+            while ((st.hasMoreTokens()) && (!exit))
+            {
+                tok = st.nextToken(tokenList);
+                if (tok.equals("\""))
+                {
+                    inQuote = ! inQuote;
+                    if (inQuote)
+                    {
+                        tokenList = "\"";
+                    }
+                    else
+                    {
+                        tokenList = "\" ";
+                    }
+
+                }
+                else if (tok.equals(" "))
+                {
+                    if (tokStarted)
+                    {
+                        retVal = tokBuf.toString();
+                        tokStarted=false;
+                        tokBuf = new StringBuffer(10);
+                        exit = true;
+                    }
+                }
+                else
+                {
+                    tokStarted = true;
+                    tokBuf.append(tok.trim());
+                }
+            }
+
+            // Handle case where end of token stream and
+            // still got data
+            if ((!exit) && (tokStarted))
+            {
+                retVal = tokBuf.toString();
+            }
+        }
+
+        return retVal;
+    }
+
+    //
+    // Private utility methods.
+    //
+
+    /**
+     * Generated the next valid bundle identifier.
+    **/
+    private synchronized long getNextId()
+    {
+        return m_nextId++;
+    }
+
+    //
+    // Configuration methods and inner classes.
+    //
+
+    public PropertyResolver getConfig()
+    {
+        return m_config;
+    }
+
+    private class ConfigImpl implements PropertyResolver
+    {
+        public String get(String key)
+        {
+            return (m_configProps == null) ? null : m_configProps.get(key);
+        }
+
+        public String[] getKeys()
+        {
+            return m_configProps.getKeys();
+        }
+    }
+
+    //
+    // Logging methods and inner classes.
+    //
+
+    public LogWrapper getLogger()
+    {
+        return m_logger;
+    }
+
+    /**
+     * Simple class that is used in <tt>refreshPackages()</tt> to embody
+     * the refresh logic in order to keep the code clean. This class is
+     * not static because it needs access to framework event firing methods.
+    **/
+    private class RefreshHelper
+    {
+        private BundleImpl m_bundle = null;
+
+        public RefreshHelper(Bundle bundle)
+        {
+            m_bundle = (BundleImpl) bundle;
+        }
+
+        public void stop()
+        {
+            if (m_bundle.getInfo().getState() == Bundle.ACTIVE)
+            {
+                try
+                {
+                    stopBundle(m_bundle, false);
+                }
+                catch (BundleException ex)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+                }
+            }
+        }
+
+        public void purgeOrRemove()
+        {
+            try
+            {
+                BundleInfo info = m_bundle.getInfo();
+
+                // Remove or purge the bundle depending on its
+                // current state.
+                if (info.getState() == Bundle.UNINSTALLED)
+                {
+                    // This physically removes the bundle from memory
+                    // as well as the bundle cache.
+                    garbageCollectBundle(m_bundle);
+                    m_bundle = null;
+                }
+                else
+                {
+                    // This physically removes all old revisions of the
+                    // bundle from memory and only maintains the newest
+                    // version in the bundle cache.
+                    purgeBundle(m_bundle);
+                }
+            }
+            catch (Exception ex)
+            {
+                fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+            }
+        }
+
+        public void reinitialize()
+        {
+            if (m_bundle != null)
+            {
+                try
+                {
+                    BundleInfo info = m_bundle.getInfo();
+                    BundleInfo newInfo = createBundleInfo(info.getArchive());
+                    newInfo.syncLock(info);
+                    m_bundle.setInfo(newInfo);
+                }
+                catch (Exception ex)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+                }
+            }
+        }
+
+        public void restart()
+        {
+            if (m_bundle != null)
+            {
+                try
+                {
+                    startBundle(m_bundle, false);
+                }
+                catch (BundleException ex)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+                }
+            }
+        }
+    }
+
+    //
+    // Locking related methods.
+    //
+
+    private void rememberUninstalledBundle(BundleImpl bundle)
+    {
+        synchronized (m_uninstalledBundlesLock_Priority3)
+        {
+            // Verify that the bundle is not already in the array.
+            for (int i = 0;
+                (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+                i++)
+            {
+                if (m_uninstalledBundles[i] == bundle)
+                {
+                    return;
+                }
+            }
+
+            if (m_uninstalledBundles != null)
+            {
+                BundleImpl[] newBundles =
+                    new BundleImpl[m_uninstalledBundles.length + 1];
+                System.arraycopy(m_uninstalledBundles, 0,
+                    newBundles, 0, m_uninstalledBundles.length);
+                newBundles[m_uninstalledBundles.length] = bundle;
+                m_uninstalledBundles = newBundles;
+            }
+            else
+            {
+                m_uninstalledBundles = new BundleImpl[] { bundle };
+            }
+        }
+    }
+
+    private void forgetUninstalledBundle(BundleImpl bundle)
+    {
+        synchronized (m_uninstalledBundlesLock_Priority3)
+        {
+            if (m_uninstalledBundles == null)
+            {
+                return;
+            }
+            
+            int idx = -1;
+            for (int i = 0; i < m_uninstalledBundles.length; i++)
+            {
+                if (m_uninstalledBundles[i] == bundle)
+                {
+                    idx = i;
+                    break;
+                }
+            }
+    
+            if (idx >= 0)
+            {
+                // If this is the only bundle, then point to empty list.
+                if ((m_uninstalledBundles.length - 1) == 0)
+                {
+                    m_uninstalledBundles = new BundleImpl[0];
+                }
+                // Otherwise, we need to do some array copying.
+                else
+                {
+                    BundleImpl[] newBundles =
+                        new BundleImpl[m_uninstalledBundles.length - 1];
+                    System.arraycopy(m_uninstalledBundles, 0, newBundles, 0, idx);
+                    if (idx < newBundles.length)
+                    {
+                        System.arraycopy(
+                            m_uninstalledBundles, idx + 1,
+                            newBundles, idx, newBundles.length - idx);
+                    }
+                    m_uninstalledBundles = newBundles;
+                }
+            }
+        }
+    }
+
+    protected void acquireInstallLock(String location)
+        throws BundleException
+    {
+        synchronized (m_installRequestLock_Priority1)
+        {
+            while (m_installRequestMap.get(location) != null)
+            {
+                try
+                {
+                    m_installRequestLock_Priority1.wait();
+                }
+                catch (InterruptedException ex)
+                {
+                    throw new BundleException("Unable to install, thread interrupted.");
+                }
+            }
+            
+            m_installRequestMap.put(location, location);
+        }
+    }
+    
+    protected void releaseInstallLock(String location)
+    {
+        synchronized (m_installRequestLock_Priority1)
+        {
+            m_installRequestMap.remove(location);
+            m_installRequestLock_Priority1.notifyAll();
+        }
+    }
+
+    protected void acquireBundleLock(BundleImpl bundle)
+        throws BundleException
+    {
+        synchronized (m_bundleLock)
+        {
+            while (!bundle.getInfo().isLockable())
+            {
+                try
+                {
+                    m_bundleLock.wait();
+                }
+                catch (InterruptedException ex)
+                {
+                    // Ignore and just keep waiting.
+                }
+            }
+            bundle.getInfo().lock();
+        }
+    }
+    
+    protected void releaseBundleLock(BundleImpl bundle)
+    {
+        synchronized (m_bundleLock)
+        {
+            bundle.getInfo().unlock();
+            m_bundleLock.notifyAll();
+        }
+    }
+
+    protected BundleImpl[] acquireBundleRefreshLocks(Bundle[] targets)
+    {
+        // Hold bundles to be locked.
+        BundleImpl[] bundles = null;
+
+        synchronized (m_bundleLock)
+        {
+            boolean success = false;
+            while (!success)
+            {
+                // If targets is null, then refresh all pending bundles.
+                Bundle[] newTargets = targets;
+                if (newTargets == null)
+                {
+                    List list = new ArrayList();
+
+                    // First add all uninstalled bundles.
+                    synchronized (m_uninstalledBundlesLock_Priority3)
+                    {
+                        for (int i = 0;
+                            (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+                            i++)
+                        {
+                            list.add(m_uninstalledBundles[i]);
+                        }
+                    }
+
+                    // Then add all updated bundles.
+                    synchronized (m_installedBundleLock_Priority2)
+                    {
+                        Iterator iter = m_installedBundleMap.values().iterator();
+                        while (iter.hasNext())
+                        {
+                            BundleImpl bundle = (BundleImpl) iter.next();
+                            if (bundle.getInfo().isRemovalPending())
+                            {
+                                list.add(bundle);
+                            }
+                        }
+                    }
+
+                    // Create an array.
+                    if (list.size() > 0)
+                    {
+                        newTargets = (Bundle[]) list.toArray(new Bundle[list.size()]);
+                    }
+                }
+
+                // If there are targets, then find all dependencies
+                // for each one.
+                if (newTargets != null)
+                {
+                    // Create map of bundles that import the packages
+                    // from the target bundles.
+                    Map map = new HashMap();
+                    for (int targetIdx = 0; targetIdx < newTargets.length; targetIdx++)
+                    {
+                        // Add the current target bundle to the map of
+                        // bundles to be refreshed.
+                        BundleImpl target = (BundleImpl) newTargets[targetIdx];
+                        map.put(target, target);
+                        // Add all importing bundles to map.
+                        populateImportGraph(target, map);
+                    }
+                    
+                    bundles = (BundleImpl[]) map.values().toArray(new BundleImpl[map.size()]);
+                }
+                
+                // Check if all corresponding bundles can be locked
+                boolean lockable = true;
+                if (bundles != null)
+                {
+                    for (int i = 0; lockable && (i < bundles.length); i++)
+                    {
+                        lockable = bundles[i].getInfo().isLockable();
+                    }
+        
+                    // If we can lock all bundles, then lock them.
+                    if (lockable)
+                    {
+                        for (int i = 0; i < bundles.length; i++)
+                        {
+                            bundles[i].getInfo().lock();
+                        }
+                        success = true;
+                    }
+                    // Otherwise, wait and try again.
+                    else
+                    {
+                        try
+                        {
+                            m_bundleLock.wait();
+                        }
+                        catch (InterruptedException ex)
+                        {
+                            // Ignore and just keep waiting.
+                        }
+                    }
+                }
+                else
+                {
+                    // If there were no bundles to lock, then we can just
+                    // exit the lock loop.
+                    success = true;
+                }
+            }
+        }
+
+        return bundles;
+    }
+
+    protected void releaseBundleLocks(BundleImpl[] bundles)
+    {
+        // Always unlock any locked bundles.
+        synchronized (m_bundleLock)
+        {
+            for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+            {
+                bundles[i].getInfo().unlock();
+            }
+            m_bundleLock.notifyAll();
+        }
+    }
+}
diff --git a/src/org/apache/osgi/framework/FilterImpl.java b/src/org/apache/osgi/framework/FilterImpl.java
new file mode 100644
index 0000000..4381d28
--- /dev/null
+++ b/src/org/apache/osgi/framework/FilterImpl.java
@@ -0,0 +1,240 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.apache.osgi.framework.util.ldap.*;
+import org.osgi.framework.*;
+
+/**
+ * This class implements an RFC 1960-based filter. The syntax of the
+ * filter string is the string representation of LDAP search filters
+ * as defined in RFC 1960. These filters are used to search for services
+ * and to track services using <tt>ServiceTracker</tt> objects.
+**/
+public class FilterImpl implements Filter
+{
+    private LogWrapper m_logger = null;
+    private String m_toString = null;
+    private Evaluator m_evaluator = null;
+    private SimpleMapper m_mapper = null;
+
+// TODO: FilterImpl needs a logger, this is a hack to get FrameworkUtil to compile.
+    public FilterImpl(String expr) throws InvalidSyntaxException
+    {
+        this(null, expr);
+    }
+
+    /**
+     * Construct a filter for a given filter expression string.
+     * @param expr the filter expression string for the filter.
+    **/
+    public FilterImpl(LogWrapper logger, String expr) throws InvalidSyntaxException
+    {
+        m_logger = logger;
+        if (expr == null)
+        {
+            throw new InvalidSyntaxException("Filter cannot be null", null);
+        }
+
+        if (expr != null)
+        {
+            CharArrayReader car = new CharArrayReader(expr.toCharArray());
+            LdapLexer lexer = new LdapLexer(car);
+            Parser parser = new Parser(lexer);
+            try
+            {
+                if (!parser.start())
+                {
+                    throw new InvalidSyntaxException(
+                        "Failed to parse LDAP query.", expr);
+                }
+            }
+            catch (ParseException ex)
+            {
+                throw new InvalidSyntaxException(
+                    ex.getMessage(), expr);
+            }
+            catch (IOException ex)
+            {
+                throw new InvalidSyntaxException(
+                    ex.getMessage(), expr);
+            }
+            m_evaluator = new Evaluator(parser.getProgram());
+            m_mapper = new SimpleMapper();
+        }
+    }
+
+    /**
+     * Compares the <tt>Filter</tt> object to another.
+     * @param o the object to compare this <tt>Filter</tt> against.
+     * @return If the other object is a <tt>Filter</tt> object, it
+     *         returns <tt>this.toString().equals(obj.toString())</tt>;
+     *         <tt>false</tt> otherwise.
+    **/
+    public boolean equals(Object o)
+    {
+        if (o == null)
+        {
+            return false;
+        }
+        else if (o instanceof Filter)
+        {
+            return toString().equals(o.toString());
+        }
+        return false;
+    }
+
+    /**
+     * Returns the hash code for the <tt>Filter</tt> object.
+     * @return The value <tt>this.toString().hashCode()</tt>.
+    **/
+    public int hashCode()
+    {
+        return toString().hashCode();
+    }
+
+    /**
+     * Filter using a <tt>Dictionary</tt> object. The <tt>Filter</tt>
+     * is executed using the <tt>Dictionary</tt> object's keys and values.
+     * @param dict the <tt>Dictionary</tt> object whose keys and values
+     *             are used to determine a match.
+     * @return <tt>true</tt> if the <tt>Dictionary</tt> object's keys
+     *         and values match this filter; <tt>false</tt> otherwise.
+     * @throws IllegalArgumentException if the dictionary contains case
+     *         variants of the same key name.
+    **/
+    public boolean match(Dictionary dict)
+        throws IllegalArgumentException
+    {
+        try
+        {
+            m_mapper.setSource(dict);
+            return m_evaluator.evaluate(m_mapper);
+        }
+        catch (AttributeNotFoundException ex)
+        {
+            m_logger.log(LogWrapper.LOG_DEBUG, "FilterImpl: " + ex);
+        }
+        catch (EvaluationException ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "FilterImpl: " + toString(), ex);
+        }
+        return false;
+    }
+
+    /**
+     * Filter using a service's properties. The <tt>Filter</tt>
+     * is executed using the properties of the referenced service.
+     * @param ref A reference to the service whose properties
+     *             are used to determine a match.
+     * @return <tt>true</tt> if the service's properties match this
+     *         filter; <tt>false</tt> otherwise.
+    **/
+    public boolean match(ServiceReference ref)
+    {
+        try
+        {
+            m_mapper.setSource(ref);
+            return m_evaluator.evaluate(m_mapper);
+        }
+        catch (AttributeNotFoundException ex)
+        {
+            m_logger.log(LogWrapper.LOG_DEBUG, "FilterImpl: " + ex);
+        }
+        catch (EvaluationException ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "FilterImpl: " + toString(), ex);
+        }
+        return false;
+    }
+
+    public boolean matchCase(Dictionary dictionary)
+    {
+        // TODO: Implement Filter.matchCase()
+        return false;
+    }
+
+    /**
+     * Returns the <tt>Filter</tt> object's filter string.
+     * @return Filter string.
+    **/
+    public String toString()
+    {
+        if (m_toString == null)
+        {
+            m_toString = m_evaluator.toStringInfix();
+        }
+        return m_toString;
+    }
+
+    static class SimpleMapper implements Mapper
+    {
+        private ServiceReference m_ref = null;
+        private Map m_map = null;
+
+        public void setSource(ServiceReference ref)
+        {
+            m_ref = ref;
+            m_map = null;
+        }
+
+        public void setSource(Dictionary dict)
+        {
+            if (m_map == null)
+            {
+                m_map = new CaseInsensitiveMap();
+            }
+            else
+            {
+                m_map.clear();
+            }
+
+            if (dict != null)
+            {
+                Enumeration keys = dict.keys();
+                while (keys.hasMoreElements())
+                {
+                    Object key = keys.nextElement();
+                    if (m_map.get(key) == null)
+                    {
+                        m_map.put(key, dict.get(key));
+                    }
+                    else
+                    {
+                        throw new IllegalArgumentException(
+                            "Duplicate attribute: " + key.toString());
+                    }
+                }
+            }
+            m_ref = null;
+        }
+
+        public Object lookup(String name)
+        {
+            if (m_map == null)
+            {
+                return m_ref.getProperty(name);
+            }
+            return m_map.get(name);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/FrameworkUtil.java b/src/org/apache/osgi/framework/FrameworkUtil.java
new file mode 100644
index 0000000..c20aea6
--- /dev/null
+++ b/src/org/apache/osgi/framework/FrameworkUtil.java
@@ -0,0 +1,29 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+
+public class FrameworkUtil
+{
+	public static Filter createFilter(String filter)
+        throws InvalidSyntaxException
+    {
+		return new FilterImpl(filter);
+	}
+}
diff --git a/src/org/apache/osgi/framework/LogWrapper.java b/src/org/apache/osgi/framework/LogWrapper.java
new file mode 100644
index 0000000..a99341c
--- /dev/null
+++ b/src/org/apache/osgi/framework/LogWrapper.java
@@ -0,0 +1,143 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * <p>
+ * This class mimics the standard OSGi <tt>LogService</tt> interface. An
+ * instance of this class will be used by the framework for all logging. Currently,
+ * the implementation of this class just sends log messages to standard output,
+ * but in the future it will be modified to use a log service if one is
+ * installed in the framework. To do so, it will need to use reflection to
+ * call the log service methods, since it will not have access to the
+ * <tt>LogService</tt> class.
+ * </p>
+**/
+// TODO: Modify LogWrapper to get LogService service object and invoke with reflection.
+public class LogWrapper
+{
+    public static final int LOG_ERROR = 1;
+    public static final int LOG_WARNING = 2;
+    public static final int LOG_INFO = 3;
+    public static final int LOG_DEBUG = 4;
+
+    private Object m_logObj = null;
+
+    public LogWrapper()
+    {
+    }
+
+    public void log(int level, String msg)
+    {
+        synchronized (this)
+        {
+            if (m_logObj != null)
+            {
+// Will use reflection.
+//                m_logObj.log(level, msg);
+            }
+            else
+            {
+                _log(null, level, msg, null);
+            }
+        }
+    }
+
+    public void log(int level, String msg, Throwable ex)
+    {
+        synchronized (this)
+        {
+            if (m_logObj != null)
+            {
+// Will use reflection.
+//                m_logObj.log(level, msg);
+            }
+            else
+            {
+                _log(null, level, msg, ex);
+            }
+        }
+    }
+
+    public void log(ServiceReference sr, int level, String msg)
+    {
+        synchronized (this)
+        {
+            if (m_logObj != null)
+            {
+// Will use reflection.
+//                m_logObj.log(level, msg);
+            }
+            else
+            {
+                _log(sr, level, msg, null);
+            }
+        }
+    }
+
+    public void log(ServiceReference sr, int level, String msg, Throwable ex)
+    {
+        synchronized (this)
+        {
+            if (m_logObj != null)
+            {
+// Will use reflection.
+//                m_logObj.log(level, msg);
+            }
+            else
+            {
+                _log(sr, level, msg, ex);
+            }
+        }
+    }
+    
+    private void _log(ServiceReference sr, int level, String msg, Throwable ex)
+    {
+        String s = (sr == null) ? null : "SvcRef " + sr;
+        s = (s == null) ? msg : s + " " + msg;
+        s = (ex == null) ? s : s + " (" + ex + ")";
+        switch (level)
+        {
+            case LOG_DEBUG:
+                System.out.println("DEBUG: " + s);
+                break;
+            case LOG_ERROR:
+                System.out.println("ERROR: " + s);
+                if (ex != null)
+                {
+                    if ((ex instanceof BundleException) &&
+                        (((BundleException) ex).getNestedException() != null))
+                    {
+                        ex = ((BundleException) ex).getNestedException();
+                    }
+                    ex.printStackTrace();
+                }
+                break;
+            case LOG_INFO:
+                System.out.println("INFO: " + s);
+                break;
+            case LOG_WARNING:
+                System.out.println("WARNING: " + s);
+                break;
+            default:
+                System.out.println("UNKNOWN[" + level + "]: " + s);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/Main.java b/src/org/apache/osgi/framework/Main.java
new file mode 100644
index 0000000..c17b184
--- /dev/null
+++ b/src/org/apache/osgi/framework/Main.java
@@ -0,0 +1,481 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.apache.osgi.framework.cache.DefaultBundleCache;
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.apache.osgi.framework.util.MutablePropertyResolverImpl;
+
+/**
+ * <p>
+ * This class is the default way to instantiate and execute the framework. It is not
+ * intended to be the only way to instantiate and execute the framework; rather, it is
+ * one example of how to do so. When embedding the framework in a host application,
+ * this class can serve as a simple guide of how to do so. It may even be
+ * worthwhile to reuse some of its property handling capabilities. This class
+ * is completely static and is only intended to start a single instance of
+ * the framework.
+ * </p>
+**/
+public class Main
+{
+    /**
+     * The system property name used to specify an URL to the system
+     * property file.
+    **/
+    public static final String SYSTEM_PROPERTIES_PROP = "felix.system.properties";
+    /**
+     * The default name used for the system properties file.
+    **/
+    public static final String SYSTEM_PROPERTIES_FILE_VALUE = "system.properties";
+    /**
+     * The system property name used to specify an URL to the configuration
+     * property file to be used for the created the framework instance.
+    **/
+    public static final String CONFIG_PROPERTIES_PROP = "felix.config.properties";
+    /**
+     * The default name used for the configuration properties file.
+    **/
+    public static final String CONFIG_PROPERTIES_FILE_VALUE = "config.properties";
+
+    private static Felix m_felix = null;
+
+    /**
+     * <p>
+     * This method performs the main task of constructing an framework instance
+     * and starting its execution. The following functions are performed
+     * when invoked:
+     * </p>
+     * <ol>
+     *   <li><i><b>Read the system properties file.<b></i> This is a file
+     *       containing properties to be pushed into <tt>System.setProperty()</tt>
+     *       before starting the framework. This mechanism is mainly shorthand
+     *       for people starting the framework from the command line to avoid having
+     *       to specify a bunch of <tt>-D</tt> system property definitions.
+     *       The only properties defined in this file that will impact the framework's
+     *       behavior are the those concerning setting HTTP proxies, such as
+     *       <tt>http.proxyHost</tt>, <tt>http.proxyPort</tt>, and
+     *       <tt>http.proxyAuth</tt>.
+     *   </li>
+     *   <li><i><b>Perform system property variable substitution on system
+     *       properties.</b></i> Any system properties in the system property
+     *       file whose value adheres to <tt>${&lt;system-prop-name&gt;}</tt>
+     *       syntax will have their value substituted with the appropriate
+     *       system property value.
+     *   </li>
+     *   <li><i><b>Read the framework's configuration property file.</b></i> This is
+     *       a file containing properties used to configure the framework
+     *       instance and to pass configuration information into
+     *       bundles installed into the framework instance. The configuration
+     *       property file is called <tt>config.properties</tt> by default
+     *       and is located in the same directory as the <tt>felix.jar</tt>
+     *       file, which is typically in the <tt>lib/</tt> directory of the
+     *       Felix installation directory. It is possible to use a different
+     *       location for the property file by specifying the desired URL
+     *       using the <tt>felix.config.properties</tt> system property;
+     *       this should be set using the <tt>-D</tt> syntax when executing
+     *       the JVM. Refer to the
+     *       <a href="Felix.html#start(org.apache.osgi.framework.util.MutablePropertyResolver, org.apache.osgi.framework.util.MutablePropertyResolver, java.util.List)">
+     *       <tt>Felix.start()</tt></a> method documentation for more
+     *       information on the framework configuration options.
+     *   </li>
+     *   <li><i><b>Perform system property variable substitution on configuration
+     *       properties.</b></i> Any configuration properties whose value adheres to
+     *       <tt>${&lt;system-prop-name&gt;}</tt> syntax will have their value
+     *       substituted with the appropriate system property value.
+     *   </li>
+     *   <li><i><b>Ensure the default bundle cache has sufficient information to
+     *       initialize.</b></i> The default implementation of the bundle cache
+     *       requires either a profile name or a profile directory in order to
+     *       start. The configuration properties are checked for at least one
+     *       of the <tt>felix.cache.profile</tt> or <tt>felix.cache.profiledir</tt>
+     *       properties. If neither is found, the user is asked to supply a profile
+     *       name that is added to the configuration property set. See the
+     *       <a href="cache/DefaultBundleCache.html"><tt>DefaultBundleCache</tt></a>
+     *       documentation for more details its configuration options.
+     *   </li>
+     *   <li><i><b>Creates and starts a framework instance.</b></i> A simple
+     *       <a href="util/MutablePropertyResolver.html"><tt>MutablePropertyResolver</tt></a>
+     *       is created for the configuration property file and is passed
+     *       into the framework when it is started.
+     *   </li>
+     * </ol>
+     * <p>
+     * It should be noted that simply starting an instance of the framework is not enough
+     * to create an interactive session with it. It is necessary to install
+     * and start bundles that provide an interactive shell; this is generally
+     * done by specifying an "auto-start" property in the framework configuration
+     * property file. If no interactive shell bundles are installed or if
+     * the configuration property file cannot be found, the framework will appear to
+     * be hung or deadlocked. This is not the case, it is executing correctly,
+     * there is just no way to interact with it. Refer to the
+     * <a href="Felix.html#start(org.apache.osgi.framework.util.MutablePropertyResolver, org.apache.osgi.framework.util.MutablePropertyResolver, java.util.List)">
+     * <tt>Felix.start()</tt></a> method documentation for more information on
+     * framework configuration options.
+     * </p>
+     * @param argv An array of arguments, all of which are ignored.
+     * @throws Exception If an error occurs.
+    **/
+    public static void main(String[] argv) throws Exception
+    {
+        // Load system properties.
+        Main.loadSystemProperties();
+
+        // Read configuration properties.
+        Properties configProps = Main.readConfigProperties();
+
+        // See if the profile name property was specified.
+        String profileName = configProps.getProperty(DefaultBundleCache.CACHE_PROFILE_PROP);
+
+        // See if the profile directory property was specified.
+        String profileDirName = configProps.getProperty(DefaultBundleCache.CACHE_PROFILE_DIR_PROP);
+
+        // Print welcome banner.
+        System.out.println("\nWelcome to Felix.");
+        System.out.println("=================\n");
+
+        // If no profile or profile directory is specified in the
+        // properties, then ask for a profile name.
+        if ((profileName == null) && (profileDirName == null))
+        {
+            System.out.print("Enter profile name: ");
+            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+            try
+            {
+                profileName = in.readLine();
+            }
+            catch (IOException ex)
+            {
+                System.err.println("Could not read input.");
+                System.exit(-1);
+            }
+            System.out.println("");
+            if (profileName.length() != 0)
+            {
+                configProps.setProperty(DefaultBundleCache.CACHE_PROFILE_PROP, profileName);
+            }
+        }
+
+        // A profile directory or name must be specified.
+        if ((profileDirName == null) && (profileName.length() == 0))
+        {
+            System.err.println("You must specify a profile name or directory.");
+            System.exit(-1);
+        }
+
+        try
+        {
+            // Now create an instance of the framework.
+            m_felix = new Felix();
+            m_felix.start(
+                new MutablePropertyResolverImpl(new CaseInsensitiveMap(configProps)),
+                null);
+        }
+        catch (Exception ex)
+        {
+            System.err.println("Could not create framework: " + ex);
+            ex.printStackTrace();
+            System.exit(-1);
+        }
+    }
+
+    /**
+     * <p>
+     * Loads the properties in the system property file associated with the
+     * framework installation into <tt>System.setProperty()</tt>. These properties
+     * are not directly used by the framework in anyway. By default, the system property
+     * file is located in the same directory as the <tt>felix.jar</tt> file and
+     * is called "<tt>system.properties</tt>". This may be changed by setting the
+     * "<tt>felix.system.properties</tt>" system property to an
+     * arbitrary URL.
+     * </p>
+    **/
+    public static void loadSystemProperties()
+    {
+        // The system properties file is either specified by a system
+        // property or it is in the same directory as the Felix JAR file.
+        // Try to load it from one of these places.
+
+        // See if the property URL was specified as a property.
+        URL propURL = null;
+        String custom = System.getProperty(SYSTEM_PROPERTIES_PROP);
+        if (custom != null)
+        {
+            try
+            {
+                propURL = new URL(custom);
+            }
+            catch (MalformedURLException ex)
+            {
+                System.err.print("Main: " + ex);
+                return;
+            }
+        }
+        else
+        {
+            // Determine where felix.jar is located by looking at the
+            // system class path.
+            String jarLoc = null;
+            String classpath = System.getProperty("java.class.path");
+            int index = classpath.toLowerCase().indexOf("felix.jar");
+            int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
+            if (index > start)
+            {
+                jarLoc = classpath.substring(start, index);
+                if (jarLoc.length() == 0)
+                {
+                    jarLoc = ".";
+                }
+            }
+            else
+            {
+                // Can't figure it out so use the current directory as default.
+                jarLoc = System.getProperty("user.dir");
+            }
+
+            try
+            {
+                propURL = new File(jarLoc, SYSTEM_PROPERTIES_FILE_VALUE).toURL();
+            }
+            catch (MalformedURLException ex)
+            {
+                System.err.print("Main: " + ex);
+                return;
+            }
+        }
+
+        // Read the properties file.
+        Properties props = new Properties();
+        InputStream is = null;
+        try
+        {
+            is = propURL.openConnection().getInputStream();
+            props.load(is);
+            is.close();
+        }
+        catch (FileNotFoundException ex)
+        {
+            // Ignore file not found.
+        }
+        catch (Exception ex)
+        {
+            System.err.println(
+                "Main: Error loading system properties from " + propURL);
+            System.err.println("Main: " + ex);
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex2)
+            {
+                // Nothing we can do.
+            }
+            return;
+        }
+
+        // Perform variable substitution for system properties.
+        for (Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+        {
+            String name = (String) e.nextElement();
+            System.setProperty(name, substVars((String) props.getProperty(name)));
+        }
+    }
+
+    /**
+     * <p>
+     * Reads the configuration properties in the configuration property
+     * file associated with the framework installation; these properties are
+     * only accessible to the framework and are intended for configuration
+     * purposes. By default, the configuration property file is located in
+     * the same directory as the <tt>felix.jar</tt> file and is called
+     * "<tt>config.properties</tt>". This may be changed by setting the
+     * "<tt>felix.config.properties</tt>" system property to an
+     * arbitrary URL.
+     * </p>
+     * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an error.
+    **/
+    public static Properties readConfigProperties()
+    {
+        // The config properties file is either specified by a system
+        // property or it is in the same directory as the Felix JAR file.
+        // Try to load it from one of these places.
+
+        // See if the property URL was specified as a property.
+        URL propURL = null;
+        String custom = System.getProperty(CONFIG_PROPERTIES_PROP);
+        if (custom != null)
+        {
+            try
+            {
+                propURL = new URL(custom);
+            }
+            catch (MalformedURLException ex)
+            {
+                System.err.print("Main: " + ex);
+                return null;
+            }
+        }
+        else
+        {
+            // Determine where felix.jar is located by looking at the
+            // system class path.
+            String jarLoc = null;
+            String classpath = System.getProperty("java.class.path");
+            int index = classpath.toLowerCase().indexOf("felix.jar");
+            int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
+            if (index > start)
+            {
+                jarLoc = classpath.substring(start, index);
+                if (jarLoc.length() == 0)
+                {
+                    jarLoc = ".";
+                }
+            }
+            else
+            {
+                // Can't figure it out so use the current directory as default.
+                jarLoc = System.getProperty("user.dir");
+            }
+
+            try
+            {
+                propURL = new File(jarLoc, CONFIG_PROPERTIES_FILE_VALUE).toURL();
+            }
+            catch (MalformedURLException ex)
+            {
+                System.err.print("Main: " + ex);
+                return null;
+            }
+        }
+
+        // Read the properties file.
+        Properties props = new Properties();
+        InputStream is = null;
+        try
+        {
+            is = propURL.openConnection().getInputStream();
+            props.load(is);
+            is.close();
+        }
+        catch (FileNotFoundException ex)
+        {
+            // Ignore file not found.
+        }
+        catch (Exception ex)
+        {
+            System.err.println(
+                "Error loading config properties from " + propURL);
+            System.err.println("Main: " + ex);
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex2)
+            {
+                // Nothing we can do.
+            }
+            return null;
+        }
+
+        // Perform variable substitution for system properties.
+        for (Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+        {
+            String name = (String) e.nextElement();
+            props.setProperty(name, substVars((String) props.getProperty(name)));
+        }
+
+        return props;
+    }
+
+    private static final String DELIM_START = "${";
+    private static final char DELIM_STOP  = '}';
+    private static final int DELIM_START_LEN = 2;
+    private static final int DELIM_STOP_LEN  = 1;
+
+    /**
+     * <p>
+     * This method performs system property variable substitution on the
+     * specified string value. If the specified string contains the
+     * syntax <tt>${&lt;system-prop-name&gt;}</tt>, then the corresponding
+     * system property value is substituted for the marker.
+     * </p>
+     * @param val The string on which to perform system property substitution.
+     * @return The value of the specified string after system property substitution.
+     * @throws IllegalArgumentException If there was a syntax error in the
+     *         system property variable marker syntax.
+    **/
+    public static String substVars(String val)
+        throws IllegalArgumentException
+    {
+        StringBuffer sbuf = new StringBuffer();
+
+        if (val == null)
+        {
+            return val;
+        }
+
+        int i = 0;
+        int j, k;
+
+        while (true)
+        {
+            j = val.indexOf(DELIM_START, i);
+            if (j == -1)
+            {
+                if (i == 0)
+                {
+                    return val;
+                }
+                else
+                {
+                    sbuf.append(val.substring(i, val.length()));
+                    return sbuf.toString();
+                }
+            }
+            else
+            {
+                sbuf.append(val.substring(i, j));
+                k = val.indexOf(DELIM_STOP, j);
+                if (k == -1)
+                {
+                    throw new IllegalArgumentException(
+                    '"' + val +
+                    "\" has no closing brace. Opening brace at position "
+                    + j + '.');
+                }
+                else
+                {
+                    j += DELIM_START_LEN;
+                    String key = val.substring(j, k);
+                    // Try system properties.
+                    String replacement = System.getProperty(key, null);
+                    if (replacement != null)
+                    {
+                        sbuf.append(replacement);
+                    }
+                    i = k + DELIM_STOP_LEN;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/OSGiLibrarySource.java b/src/org/apache/osgi/framework/OSGiLibrarySource.java
new file mode 100644
index 0000000..9196be3
--- /dev/null
+++ b/src/org/apache/osgi/framework/OSGiLibrarySource.java
@@ -0,0 +1,179 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.apache.osgi.framework.cache.BundleCache;
+import org.apache.osgi.framework.util.LibraryInfo;
+import org.apache.osgi.moduleloader.LibrarySource;
+import org.osgi.framework.Constants;
+
+public class OSGiLibrarySource implements LibrarySource
+{
+    private LogWrapper m_logger = null;
+    private boolean m_opened = false;
+    private BundleCache m_cache = null;
+    private long m_bundleId = -1;
+    private int m_revision = -1;
+    private String m_os = null;
+    private String m_processor = null;
+    private LibraryInfo[] m_libraries = null;
+
+    public OSGiLibrarySource(
+        LogWrapper logger, BundleCache cache, long bundleId, int revision,
+        String os, String processor, LibraryInfo[] libraries)
+    {
+        m_logger = logger;
+        m_cache = cache;
+        m_bundleId = bundleId;
+        m_revision = revision;
+        m_os = normalizePropertyValue(Constants.FRAMEWORK_OS_NAME, os);
+        m_processor = normalizePropertyValue(Constants.FRAMEWORK_PROCESSOR, processor);
+        m_libraries = libraries;
+    }
+
+    public void open()
+    {
+        m_opened = true;
+    }
+
+    public void close()
+    {
+        m_opened = false;
+    }
+
+    public String getPath(String name) throws IllegalStateException
+    {
+        if (!m_opened)
+        {
+            throw new IllegalStateException("OSGiLibrarySource is not open");
+        }
+
+        if (m_libraries != null)
+        {
+            String libname = System.mapLibraryName(name);
+
+            // Check to see if we have a matching library.
+            // TODO: This "matching" algorithm does not fully
+            // match the spec and should be improved.
+            LibraryInfo library = null;
+            for (int i = 0; (library == null) && (i < m_libraries.length); i++)
+            {
+                boolean osOkay = checkOS(m_libraries[i].getOSNames());
+                boolean procOkay = checkProcessor(m_libraries[i].getProcessors());
+                if (m_libraries[i].getName().endsWith(libname) && osOkay && procOkay)
+                {
+                    library = m_libraries[i];
+                }
+            }
+
+            if (library != null)
+            {
+                try {
+                    return m_cache.getArchive(m_bundleId)
+                        .findLibrary(m_revision, library.getName());
+                } catch (Exception ex) {
+                    m_logger.log(LogWrapper.LOG_ERROR, "OSGiLibrarySource: Error finding library.", ex);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private boolean checkOS(String[] osnames)
+    {
+        for (int i = 0; (osnames != null) && (i < osnames.length); i++)
+        {
+            String osname =
+                normalizePropertyValue(Constants.FRAMEWORK_OS_NAME, osnames[i]);
+            if (m_os.equals(osname))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkProcessor(String[] processors)
+    {
+        for (int i = 0; (processors != null) && (i < processors.length); i++)
+        {
+            String processor =
+                normalizePropertyValue(Constants.FRAMEWORK_PROCESSOR, processors[i]);
+            if (m_processor.equals(processor))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This is simply a hack to try to create some standardized
+     * property values, since there seems to be many possible
+     * values for each JVM implementation.  Currently, this
+     * focuses on Windows and Linux and will certainly need
+     * to be changed in the future or at least edited.
+    **/
+    public static String normalizePropertyValue(String prop, String value)
+    {
+        prop = prop.toLowerCase();
+        value = value.toLowerCase();
+
+        if (prop.equals(Constants.FRAMEWORK_OS_NAME))
+        {
+            if (value.startsWith("linux"))
+            {
+                return "linux";
+            }
+            else if (value.startsWith("win"))
+            {
+                String os = "win";
+                if (value.indexOf("95") >= 0)
+                {
+                    os = "win95";
+                }
+                else if (value.indexOf("98") >= 0)
+                {
+                    os = "win98";
+                }
+                else if (value.indexOf("NT") >= 0)
+                {
+                    os = "winnt";
+                }
+                else if (value.indexOf("2000") >= 0)
+                {
+                    os = "win2000";
+                }
+                else if (value.indexOf("xp") >= 0)
+                {
+                    os = "winxp";
+                }
+                return os;
+            }
+        }
+        else if (prop.equals(Constants.FRAMEWORK_PROCESSOR))
+        {
+            if (value.endsWith("86"))
+            {
+                return "x86";
+            }
+        }
+
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/OSGiURLPolicy.java b/src/org/apache/osgi/framework/OSGiURLPolicy.java
new file mode 100644
index 0000000..b2d50c3
--- /dev/null
+++ b/src/org/apache/osgi/framework/OSGiURLPolicy.java
@@ -0,0 +1,133 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+
+import org.apache.osgi.framework.util.FelixConstants;
+import org.apache.osgi.moduleloader.*;
+
+public class OSGiURLPolicy implements URLPolicy
+{
+    private Felix m_felix = null;
+    private BundleURLStreamHandler m_handler = null;
+    private FakeURLStreamHandler m_fakeHandler = null;
+
+    public OSGiURLPolicy(Felix felix)
+    {
+        m_felix = felix;
+    }
+
+    public URL createCodeSourceURL(ModuleManager mgr, Module module)
+    {
+        URL url = null;
+/*
+        BundleImpl bundle = null;
+        try
+        {
+            bundle = (BundleImpl)
+                m_felix.getBundle(BundleInfo.getBundleIdFromModuleId(module.getId()));
+            if (bundle != null)
+            {
+                url = new URL(bundle.getInfo().getLocation());
+            }
+        }
+        catch (NumberFormatException ex)
+        {
+            url = null;
+        }
+        catch (MalformedURLException ex)
+        {
+            if (m_fakeHandler == null)
+            {
+                m_fakeHandler = new FakeURLStreamHandler();
+            }
+            try
+            {
+                url = new URL(null,
+                    FelixConstants.FAKE_URL_PROTOCOL_VALUE
+                    + "//" + bundle.getLocation(), m_fakeHandler);
+            }
+            catch (Exception ex2)
+            {
+                url = null;
+            }
+        }
+*/
+        return url;
+    }
+
+    public URL createResourceURL(ModuleManager mgr, Module module, int rsIdx, String name)
+    {
+        if (m_handler == null)
+        {
+            m_handler = new BundleURLStreamHandler(mgr);
+        }
+
+        // Add a slash if there is one already, otherwise
+        // the is no slash separating the host from the file
+        // in the resulting URL.
+        if (!name.startsWith("/"))
+        {
+            name = "/" + name;
+        }
+
+        try
+        {
+            if (System.getSecurityManager() != null)
+            {
+                return (URL) AccessController.doPrivileged(
+                    new CreateURLPrivileged(module.getId(), rsIdx, name));
+            }
+            else
+            {
+                return new URL(FelixConstants.BUNDLE_URL_PROTOCOL,
+                    module.getId(), -1, "/" + rsIdx + name, m_handler);
+            }
+        }
+        catch (Exception ex)
+        {
+            System.err.println("OSGiURLPolicy: " + ex);
+            return null;
+        }
+    }
+
+    /**
+     * This simple class is used to perform the privileged action of
+     * creating a URL using the "bundle:" protocol stream handler.
+    **/
+    private class CreateURLPrivileged implements PrivilegedExceptionAction
+    {
+        private String m_id = null;
+        private int m_rsIdx = 0;
+        private String m_name = null;
+
+        public CreateURLPrivileged(String id, int rsIdx, String name)
+        {
+            m_id = id;
+            m_rsIdx = rsIdx;
+            m_name = name;
+        }
+
+        public Object run() throws Exception
+        {
+            return new URL("bundle", m_id, -1, "/" + m_rsIdx + m_name, m_handler);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/PackageAdminActivator.java b/src/org/apache/osgi/framework/PackageAdminActivator.java
new file mode 100644
index 0000000..4e07315
--- /dev/null
+++ b/src/org/apache/osgi/framework/PackageAdminActivator.java
@@ -0,0 +1,43 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.osgi.framework.*;
+
+class PackageAdminActivator implements BundleActivator
+{
+    private Felix m_felix = null;
+    private ServiceRegistration m_reg = null;
+
+    public PackageAdminActivator(Felix felix)
+    {
+        m_felix = felix;
+    }
+
+    public void start(BundleContext context) throws Exception
+    {
+        m_reg = context.registerService(
+            org.osgi.service.packageadmin.PackageAdmin.class.getName(),
+            new PackageAdminImpl(m_felix),
+            null);
+    }
+
+    public void stop(BundleContext context) throws Exception
+    {
+        m_reg.unregister();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/PackageAdminImpl.java b/src/org/apache/osgi/framework/PackageAdminImpl.java
new file mode 100644
index 0000000..5a029c0
--- /dev/null
+++ b/src/org/apache/osgi/framework/PackageAdminImpl.java
@@ -0,0 +1,189 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.packageadmin.*;
+
+class PackageAdminImpl implements PackageAdmin, Runnable
+{
+    private Felix m_felix = null;
+    private Bundle[][] m_reqBundles = null;
+
+    public PackageAdminImpl(Felix felix)
+    {
+        m_felix = felix;
+
+        // Start a thread to perform asynchronous package refreshes.
+        Thread t = new Thread(this, "FelixPackageAdmin");
+        t.setDaemon(true);
+        t.start();
+    }
+
+    /**
+     * Returns the exported package associated with the specified
+     * package name.
+     *
+     * @param name the name of the exported package to find.
+     * @return the exported package or null if no matching package was found.
+    **/
+    public ExportedPackage getExportedPackage(String name)
+    {
+        return m_felix.getExportedPackage(name);
+    }
+
+    /**
+     * Returns the packages exported by the specified bundle.
+     *
+     * @param bundle the bundle whose exported packages are to be returned.
+     * @return an array of packages exported by the bundle or null if the
+     *         bundle does not export any packages.
+    **/
+    public ExportedPackage[] getExportedPackages(Bundle b)
+    {
+        return m_felix.getExportedPackages(b);
+    }
+
+    /**
+     * The OSGi specification states that refreshing packages is
+     * asynchronous; this method simply notifies the package admin
+     * thread to do a refresh.
+     * @param bundles array of bundles to refresh or <tt>null</tt> to refresh
+     *                any bundles in need of refreshing.
+    **/
+    public synchronized void refreshPackages(Bundle[] bundles)
+        throws SecurityException
+    {
+        // Save our request parameters and notify all.
+        if (m_reqBundles == null)
+        {
+            m_reqBundles = new Bundle[][] { bundles };
+        }
+        else
+        {
+            Bundle[][] newReqBundles = new Bundle[m_reqBundles.length + 1][];
+            System.arraycopy(m_reqBundles, 0,
+                newReqBundles, 0, m_reqBundles.length);
+            newReqBundles[m_reqBundles.length] = bundles;
+            m_reqBundles = newReqBundles;
+        }
+        notifyAll();
+    }
+
+    /**
+     * The OSGi specification states that package refreshes happen
+     * asynchronously; this is the run() method for the package
+     * refreshing thread.
+    **/
+    public void run()
+    {
+        // This thread loops forever, thus it should
+        // be a daemon thread.
+        Bundle[] bundles = null;
+        while (true)
+        {
+            synchronized (this)
+            {
+                // Wait for a refresh request.
+                while (m_reqBundles == null)
+                {
+                    try
+                    {
+                        wait();
+                    }
+                    catch (InterruptedException ex)
+                    {
+                    }
+                }
+
+                // Get the bundles parameter for the current
+                // refresh request.
+                if (m_reqBundles != null)
+                {
+                    bundles = m_reqBundles[0];
+                }
+            }
+
+            // Perform refresh.
+            m_felix.refreshPackages(bundles);
+
+            // Remove the first request since it is now completed.
+            synchronized (this)
+            {
+                if (m_reqBundles.length == 1)
+                {
+                    m_reqBundles = null;
+                }
+                else
+                {
+                    Bundle[][] newReqBundles = new Bundle[m_reqBundles.length - 1][];
+                    System.arraycopy(m_reqBundles, 1,
+                        newReqBundles, 0, m_reqBundles.length - 1);
+                    m_reqBundles = newReqBundles;
+                }
+            }
+        }
+    }
+
+    public ExportedPackage[] getExportedPackages(String name)
+    {
+        // TODO: Implement PackageAdmin.getExportedPackages()
+        return null;
+    }
+
+    public boolean resolveBundles(Bundle[] bundles)
+    {
+        // TODO: Implement PackageAdmin.resolveBundles()
+        return false;
+    }
+
+    public RequiredBundle[] getRequiredBundles(String symbolicName)
+    {
+        // TODO: Implement PackageAdmin.getRequiredBundles()
+        return null;
+    }
+
+    public Bundle[] getBundles(String symbolicName, String versionRange)
+    {
+        // TODO: Implement PackageAdmin.getBundles()
+        return null;
+    }
+
+    public Bundle[] getFragments(Bundle bundle)
+    {
+        // TODO: Implement PackageAdmin.getFragments()
+        return null;
+    }
+
+    public Bundle[] getHosts(Bundle bundle)
+    {
+        // TODO: Implement PackageAdmin.getHosts()
+        return null;
+    }
+
+    public Bundle getBundle(Class clazz)
+    {
+        // TODO: Implement PackageAdmin.getBundle()
+        return null;
+    }
+
+    public int getBundleType(Bundle bundle)
+    {
+        // TODO: Implement PackageAdmin.getBundleType()
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ServiceReferenceImpl.java b/src/org/apache/osgi/framework/ServiceReferenceImpl.java
new file mode 100644
index 0000000..8cf5ae4
--- /dev/null
+++ b/src/org/apache/osgi/framework/ServiceReferenceImpl.java
@@ -0,0 +1,175 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.apache.osgi.framework.searchpolicy.R4SearchPolicy;
+import org.apache.osgi.framework.searchpolicy.R4Wire;
+import org.apache.osgi.framework.util.FelixConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+class ServiceReferenceImpl implements ServiceReference
+{
+    private ServiceRegistrationImpl m_registration = null;
+    private Bundle m_bundle = null;
+
+    public ServiceReferenceImpl(ServiceRegistrationImpl reg, Bundle bundle)
+    {
+        m_registration = reg;
+        m_bundle = bundle;
+    }
+
+    protected ServiceRegistrationImpl getServiceRegistration()
+    {
+        return m_registration;
+    }
+
+    public Object getProperty(String s)
+    {
+        return m_registration.getProperty(s);
+    }
+
+    public String[] getPropertyKeys()
+    {
+        return m_registration.getPropertyKeys();
+    }
+
+    public Bundle getBundle()
+    {
+        return m_bundle;
+    }
+
+    public Bundle[] getUsingBundles()
+    {
+        return m_registration.getUsingBundles();
+    }
+
+    public boolean equals(Object obj)
+    {
+        try
+        {
+            ServiceReferenceImpl ref = (ServiceReferenceImpl) obj;
+            return ref.m_registration == m_registration;
+        }
+        catch (ClassCastException ex)
+        {
+            // Ignore and return false.
+        }
+        catch (NullPointerException ex)
+        {
+            // Ignore and return false.
+        }
+
+        return false;
+    }
+
+    public int hashCode()
+    {
+        if (m_registration.getReference() != null)
+        {
+            if (m_registration.getReference() != this)
+            {
+                return m_registration.getReference().hashCode();
+            }
+            return super.hashCode();
+        }
+        return 0;
+    }
+
+    public String toString()
+    {
+        String[] ocs = (String[]) getProperty("objectClass");
+        String oc = "[";
+        for(int i = 0; i < ocs.length; i++)
+        {
+            oc = oc + ocs[i];
+            if (i < ocs.length - 1)
+                oc = oc + ", ";
+        }
+        oc = oc + "]";
+        return oc;
+    }
+
+    public boolean isAssignableTo(Bundle requester, String className)
+    {
+        // Always return true if the requester is the same as the provider.
+        if (requester == m_bundle)
+        {
+            return true;
+        }
+
+        // Boolean flag.
+        boolean allow = true;
+        // Get the package.
+        String pkgName =
+            org.apache.osgi.moduleloader.Util.getClassPackage(className);
+        // Get package wiring from service provider and requester.
+        R4Wire requesterWire = R4SearchPolicy.getWire(
+            ((BundleImpl) requester).getInfo().getCurrentModule(), pkgName);
+        R4Wire providerWire = R4SearchPolicy.getWire(
+            ((BundleImpl) m_bundle).getInfo().getCurrentModule(), pkgName);
+
+        // There are three situations that may occur here:
+        //   1. The requester does not have a wire for the package.
+        //   2. The provider does not have a wire for the package.
+        //   3. Both have a wire for the package.
+        // For case 1, we do not filter the service reference since we
+        // assume that the bundle is using reflection or that it won't
+        // use that class at all since it does not import it. For
+        // case 2, we have to try to load the class from the class
+        // loader of the service object and then compare the class
+        // loaders to determine if we should filter the service
+        // refernce. In case 3, we simply compare the exporting
+        // modules from the package wiring to determine if we need
+        // to filter the service reference.
+        
+        // Case 1: Always include service reference.
+        if (requesterWire == null)
+        {
+            // This is an intentional no-op.
+        }
+        // Case 2: Only include service reference if the service
+        // object uses the same class as the requester.
+        else if (providerWire == null)
+        {
+            try
+            {
+                // Load the class from the requesting bundle.
+                Class requestClass =
+                    ((BundleImpl) requester).getInfo().getCurrentModule().getClassLoader()
+                        .loadClass(className);
+                // Get the service registration and ask it to check
+                // if the service object is assignable to the requesting
+                // bundle's class.
+                allow = getServiceRegistration().isClassAccessible(requestClass);
+            }
+            catch (Exception ex)
+            {
+                // This should not happen, filter to be safe.
+                allow = false;
+            }
+        }
+        // Case 3: Include service reference if the wires have the
+        // same source module.
+        else
+        {
+            allow = providerWire.m_module.equals(requesterWire.m_module);
+        }
+
+        return allow;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ServiceRegistrationImpl.java b/src/org/apache/osgi/framework/ServiceRegistrationImpl.java
new file mode 100644
index 0000000..9ba8b28
--- /dev/null
+++ b/src/org/apache/osgi/framework/ServiceRegistrationImpl.java
@@ -0,0 +1,276 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.util.*;
+
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.osgi.framework.*;
+
+class ServiceRegistrationImpl implements ServiceRegistration
+{
+    // Service registry.
+    private ServiceRegistry m_registry = null;
+    // Bundle implementing the service.
+    private Bundle m_bundle = null;
+    // Interfaces associated with the service object.
+    private String[] m_classes = null;
+    // Service Id associated with the service object.
+    private Long m_serviceId = null;
+    // Service object.
+    private Object m_svcObj = null;
+    // Service factory interface.
+    private ServiceFactory m_factory = null;
+    // Associated property dictionary.
+    private Map m_propMap = null;
+    // Re-usable service reference.
+    private ServiceReferenceImpl m_ref = null;
+
+    public ServiceRegistrationImpl(
+        ServiceRegistry registry, Bundle bundle,
+        String[] classes, Long serviceId,
+        Object svcObj, Dictionary dict)
+    {
+        m_registry = registry;
+        m_bundle = bundle;
+        m_classes = classes;
+        m_serviceId = serviceId;
+        m_svcObj = svcObj;
+        m_factory = (m_svcObj instanceof ServiceFactory)
+            ? (ServiceFactory) m_svcObj : null;
+
+        initializeProperties(dict);
+
+        // This reference is the "standard" reference for this
+        // service and will always be returned by getReference().
+        // Since all reference to this service are supposed to
+        // be equal, we use the hashcode of this reference for
+        // a references to this service in ServiceReference.
+        m_ref = new ServiceReferenceImpl(this, m_bundle);
+    }
+
+    protected boolean isValid()
+    {
+        return (m_svcObj != null);
+    }
+
+    public ServiceReference getReference()
+    {
+        return m_ref;
+    }
+
+    public void setProperties(Dictionary dict)
+    {
+        // Make sure registration is valid.
+        if (!isValid())
+        {
+            throw new IllegalStateException(
+                "The service registration is no longer valid.");
+        }
+        // Set the properties.
+        initializeProperties(dict);
+        // Tell registry about it.
+        m_registry.servicePropertiesModified(this);
+    }
+
+    public void unregister()
+    {
+        m_registry.unregisterService(m_bundle, this);
+        m_svcObj = null;
+        m_factory = null;
+    }
+
+    //
+    // Utility methods.
+    //
+
+    /**
+     * This method determines if the class loader of the service object
+     * has access to the specified class.
+     * @param clazz the class to test for reachability.
+     * @return <tt>true</tt> if the specified class is reachable from the
+     *         service object's class loader, <tt>false</tt> otherwise.
+    **/
+    protected boolean isClassAccessible(Class clazz)
+    {
+        ClassLoader loader = (m_factory != null)
+            ? m_factory.getClass().getClassLoader()
+            : m_svcObj.getClass().getClassLoader();
+        try
+        {
+            Class target = loader.loadClass(clazz.getName());
+            return (target.getClassLoader() == clazz.getClassLoader());
+        }
+        catch (Exception ex)
+        {
+        }
+        return false;
+    }
+
+    protected Object getProperty(String key)
+    {
+        return m_propMap.get(key);
+    }
+
+    private transient ArrayList m_list = new ArrayList();
+
+    protected String[] getPropertyKeys()
+    {
+        synchronized (m_list)
+        {
+            m_list.clear();
+            Iterator i = m_propMap.entrySet().iterator();
+            while (i.hasNext())
+            {
+                Map.Entry entry = (Map.Entry) i.next();
+                m_list.add(entry.getKey());
+            }
+            return (String[]) m_list.toArray(new String[m_list.size()]);
+        }
+    }
+
+    protected Bundle[] getUsingBundles()
+    {
+        return m_registry.getUsingBundles(m_ref);
+    }
+
+    protected Object getService(Bundle acqBundle)
+    {
+        // If the service object is a service factory, then
+        // let it create the service object.
+        if (m_factory != null)
+        {
+            try
+            {
+                if (System.getSecurityManager() != null)
+                {
+                    return AccessController.doPrivileged(
+                        new ServiceFactoryPrivileged(acqBundle, null));
+                }
+                else
+                {
+                    return getFactoryUnchecked(acqBundle);
+                }
+            }
+            catch (Exception ex)
+            {
+                m_registry.getLogger().log(
+                    LogWrapper.LOG_ERROR, "ServiceRegistrationImpl: Error getting service.", ex);
+                return null;
+            }
+        }
+        else
+        {
+            return m_svcObj;
+        }
+    }
+
+    protected void ungetService(Bundle relBundle, Object svcObj)
+    {
+        // If the service object is a service factory, then
+        // let is release the service object.
+        if (m_factory != null)
+        {
+            try
+            {
+                if (System.getSecurityManager() != null)
+                {
+                    AccessController.doPrivileged(
+                        new ServiceFactoryPrivileged(relBundle, svcObj));
+                }
+                else
+                {
+                    ungetFactoryUnchecked(relBundle, svcObj);
+                }
+            }
+            catch (Exception ex)
+            {
+                m_registry.getLogger().log(
+                    LogWrapper.LOG_ERROR, "ServiceRegistrationImpl: Error ungetting service.", ex);
+            }
+        }
+    }
+
+    private void initializeProperties(Dictionary dict)
+    {
+        // Create a case insensitive map.
+        if (m_propMap == null)
+        {
+            m_propMap = new CaseInsensitiveMap();
+        }
+        else
+        {
+            m_propMap.clear();
+        }
+
+        if (dict != null)
+        {
+            Enumeration keys = dict.keys();
+            while (keys.hasMoreElements())
+            {
+                Object key = keys.nextElement();
+                m_propMap.put(key, dict.get(key));
+            }
+        }
+
+        // Add the framework assigned properties.
+        m_propMap.put(Constants.OBJECTCLASS, m_classes);
+        m_propMap.put(Constants.SERVICE_ID, m_serviceId);
+    }
+
+    private Object getFactoryUnchecked(Bundle bundle)
+    {
+        return m_factory.getService(bundle, this);
+    }
+
+    private void ungetFactoryUnchecked(Bundle bundle, Object svcObj)
+    {
+        m_factory.ungetService(bundle, this, svcObj);
+    }
+
+    /**
+     * This simple class is used to ensure that when a service factory
+     * is called, that no other classes on the call stack interferes
+     * with the permissions of the factory itself.
+    **/
+    private class ServiceFactoryPrivileged implements PrivilegedExceptionAction
+    {
+        private Bundle m_bundle = null;
+        private Object m_svcObj = null;
+
+        public ServiceFactoryPrivileged(Bundle bundle, Object svcObj)
+        {
+            m_bundle = bundle;
+            m_svcObj = svcObj;
+        }
+
+        public Object run() throws Exception
+        {
+            if (m_svcObj == null)
+            {
+                return getFactoryUnchecked(m_bundle);
+            }
+            else
+            {
+                ungetFactoryUnchecked(m_bundle, m_svcObj);
+            }
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ServiceRegistry.java b/src/org/apache/osgi/framework/ServiceRegistry.java
new file mode 100644
index 0000000..02f206b
--- /dev/null
+++ b/src/org/apache/osgi/framework/ServiceRegistry.java
@@ -0,0 +1,522 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.util.*;
+
+import org.apache.osgi.framework.util.FelixConstants;
+import org.osgi.framework.*;
+
+public class ServiceRegistry
+{
+    private LogWrapper m_logger = null;
+    private long m_currentServiceId = 1L;
+    // Maps bundle to an array of service registrations.
+    private Map m_serviceRegsMap = new HashMap();
+    // Maps bundle to an array of usage counts.
+    private Map m_inUseMap = new HashMap();
+
+    private ServiceListener m_serviceListener = null;
+
+    public ServiceRegistry(LogWrapper logger)
+    {
+        m_logger = logger;
+    }
+
+    public synchronized ServiceReference[] getRegisteredServices(Bundle bundle)
+    {
+        ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+        if (regs != null)
+        {
+            ServiceReference[] refs = new ServiceReference[regs.length];
+            for (int i = 0; i < refs.length; i++)
+            {
+                refs[i] = regs[i].getReference();
+            }
+            return refs;
+        }
+        return null;
+    }
+
+    public ServiceRegistration registerService(
+        Bundle bundle, String[] classNames, Object svcObj, Dictionary dict)
+    {
+        ServiceRegistration reg = null;
+
+        synchronized (this)
+        {
+            // Create the service registration.
+            reg = new ServiceRegistrationImpl(
+                this, bundle, classNames, new Long(m_currentServiceId++), svcObj, dict);
+            // Get the bundles current registered services.
+            ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+            m_serviceRegsMap.put(bundle, addServiceRegistration(regs, reg));
+        }
+        fireServiceChanged(new ServiceEvent(ServiceEvent.REGISTERED, reg.getReference()));
+        return reg;
+    }
+
+    public void unregisterService(Bundle bundle, ServiceRegistration reg)
+    {
+        synchronized (this)
+        {
+            ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+            m_serviceRegsMap.put(bundle, removeServiceRegistration(regs, reg));
+        }
+        fireServiceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, reg.getReference()));
+    }
+
+    public void unregisterServices(Bundle bundle)
+    {
+        // Simply remove all service registrations for the bundle.
+        ServiceRegistration[] regs = null;
+        synchronized (this)
+        {
+            regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+            m_serviceRegsMap.remove(bundle);
+        }
+
+        // Fire all events outside of synchronized block.
+        for (int i = 0; (regs != null) && (i < regs.length); i++)
+        {
+            fireServiceChanged(
+                new ServiceEvent(ServiceEvent.UNREGISTERING, regs[i].getReference()));
+        }
+    }
+
+    public synchronized List getServiceReferences(String className, Filter filter)
+        throws InvalidSyntaxException
+    {
+        // Create a filtered list of service references.
+        List list = new ArrayList();
+
+        // Iterator over all service registrations.
+        for (Iterator i = m_serviceRegsMap.entrySet().iterator(); i.hasNext(); )
+        {
+            Map.Entry entry = (Map.Entry) i.next();
+            ServiceRegistration[] regs = (ServiceRegistration[]) entry.getValue();
+
+            for (int regIdx = 0;
+                (regs != null) && (regIdx < regs.length);
+                regIdx++)
+            {
+                // Determine if the registered services matches
+                // the search criteria.
+                boolean matched = false;
+
+                // If className is null, then look at filter only.
+                if ((className == null) &&
+                    ((filter == null) || filter.match(regs[regIdx].getReference())))
+                {
+                    matched = true;
+                }
+                // If className is not null, then first match the
+                // objectClass property before looking at the
+                // filter.
+                else if (className != null)
+                {
+                    String[] objectClass = (String[])
+                        ((ServiceRegistrationImpl) regs[regIdx]).getProperty(FelixConstants.OBJECTCLASS);
+                    for (int classIdx = 0;
+                        classIdx < objectClass.length;
+                        classIdx++)
+                    {
+                        if (objectClass[classIdx].equals(className) &&
+                            ((filter == null) || filter.match(regs[regIdx].getReference())))
+                        {
+                            matched = true;
+                            break;
+                        }
+                    }
+                }
+
+                // Add reference if it was a match.
+                if (matched)
+                {
+                    list.add(regs[regIdx].getReference());
+                }
+            }
+        }
+
+        return list;
+    }
+
+    public synchronized ServiceReference[] getServicesInUse(Bundle bundle)
+    {
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+        if (usages != null)
+        {
+            ServiceReference[] refs = new ServiceReference[usages.length];
+            for (int i = 0; i < refs.length; i++)
+            {
+                refs[i] = usages[i].m_ref;
+            }
+            return refs;
+        }
+        return null;
+    }
+
+    public synchronized Object getService(Bundle bundle, ServiceReference ref)
+    {
+        // Get usage counts for specified bundle.
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+
+        // Make sure the service registration is still valid.
+        if (!((ServiceReferenceImpl) ref).getServiceRegistration().isValid())
+        {
+            // If the service registration is not valid, then this means
+            // that the service provider unregistered the service. The spec
+            // says that calls to get an unregistered service should always
+            // return null (assumption: even if it is currently cached
+            // by the bundle). So in this case, flush the service reference
+            // from the cache and return null.
+            m_inUseMap.put(bundle, removeUsageCount(usages, ref));
+
+            // It is not necessary to unget the service object from
+            // the providing bundle, since the associated service is
+            // unregistered and hence not in the list of registered services
+            // of the providing bundle. This is precisely why the service
+            // registration was not found above in the first place.
+            return null;
+        }
+
+        // Get the service registration.
+        ServiceRegistrationImpl reg = ((ServiceReferenceImpl) ref).getServiceRegistration();
+
+        // Get the usage count, if any.
+        UsageCount usage = getUsageCount(usages, ref);
+       
+        // If the service object is cached, then increase the usage
+        // count and return the cached service object.
+        Object svcObj = null;
+        if (usage != null)
+        {
+            usage.m_count++;
+            svcObj = usage.m_svcObj;
+        }
+        else
+        {
+            // Get service object from service registration.
+            svcObj = reg.getService(bundle);
+
+            // Cache the service object.
+            if (svcObj != null)
+            {
+                m_inUseMap.put(bundle, addUsageCount(usages, ref, svcObj));
+            }
+        }
+
+        return svcObj;
+    }
+
+    public synchronized boolean ungetService(Bundle bundle, ServiceReference ref)
+    {
+        // Get usage count.
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+        UsageCount usage = getUsageCount(usages, ref);
+
+        // If no usage count, then return.
+        if (usage == null)
+        {
+            return false;
+        }
+
+        // Make sure the service registration is still valid.
+        if (!((ServiceReferenceImpl) ref).getServiceRegistration().isValid())
+        {
+            // If the service registration is not valid, then this means
+            // that the service provider unregistered the service. The spec
+            // says that calls to get an unregistered service should always
+            // return null (assumption: even if it is currently cached
+            // by the bundle). So in this case, flush the service reference
+            // from the cache and return null.
+            m_inUseMap.put(bundle, removeUsageCount(usages, ref));
+            return false;
+        }
+
+        // Decrement usage count.
+        usage.m_count--;
+
+        // Remove reference when usage count goes to zero
+        // and unget the service object from the exporting
+        // bundle.
+        if (usage.m_count == 0)
+        {
+            m_inUseMap.put(bundle, removeUsageCount(usages, ref));
+            ServiceRegistrationImpl reg =
+                ((ServiceReferenceImpl) ref).getServiceRegistration();
+            reg.ungetService(bundle, usage.m_svcObj);
+            usage.m_svcObj = null;
+        }
+
+        // Return true if the usage count is greater than zero.
+        return (usage.m_count > 0);
+    }
+
+
+    /**
+     * This is a utility method to release all services being
+     * used by the specified bundle.
+     * @param bundle the bundle whose services are to be released.
+    **/
+    public synchronized void ungetServices(Bundle bundle)
+    {
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+        if (usages == null)
+        {
+            return;
+        }
+
+        // Remove each service object from the
+        // service cache.
+        for (int i = 0; i < usages.length; i++)
+        {
+            // Keep ungetting until all usage count is zero.
+            while (ungetService(bundle, usages[i].m_ref))
+            {
+                // Empty loop body.
+            }
+        }
+    }
+
+    public synchronized Bundle[] getUsingBundles(ServiceReference ref)
+    {
+        Bundle[] bundles = null;
+        for (Iterator iter = m_inUseMap.entrySet().iterator(); iter.hasNext(); )
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            Bundle bundle = (Bundle) entry.getKey();
+            UsageCount[] usages = (UsageCount[]) entry.getValue();
+            for (int useIdx = 0; useIdx < usages.length; useIdx++)
+            {
+                if (usages[useIdx].m_ref.equals(ref))
+                {
+                    // Add the bundle to the array to be returned.
+                    if (bundles == null)
+                    {
+                        bundles = new Bundle[] { bundle };
+                    }
+                    else
+                    {
+                        Bundle[] nbs = new Bundle[bundles.length + 1];
+                        System.arraycopy(bundles, 0, nbs, 0, bundles.length);
+                        nbs[bundles.length] = bundle;
+                        bundles = nbs;
+                    }
+                }
+            }
+        }
+        return bundles;
+    }
+
+    public void servicePropertiesModified(ServiceRegistration reg)
+    {
+        fireServiceChanged(new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()));
+    }
+
+    public LogWrapper getLogger()
+    {
+        return m_logger;
+    }
+
+    private static ServiceRegistration[] addServiceRegistration(
+        ServiceRegistration[] regs, ServiceRegistration reg)
+    {
+        if (regs == null)
+        {
+            regs = new ServiceRegistration[] { reg };
+        }
+        else
+        {
+            ServiceRegistration[] newRegs = new ServiceRegistration[regs.length + 1];
+            System.arraycopy(regs, 0, newRegs, 0, regs.length);
+            newRegs[regs.length] = reg;
+            regs = newRegs;
+        }
+        return regs;
+    }
+
+    private static ServiceRegistration[] removeServiceRegistration(
+        ServiceRegistration[] regs, ServiceRegistration reg)
+    {
+        for (int i = 0; (regs != null) && (i < regs.length); i++)
+        {
+            if (regs[i].equals(reg))
+            {
+                // If this is the only usage, then point to empty list.
+                if ((regs.length - 1) == 0)
+                {
+                    regs = new ServiceRegistration[0];
+                }
+                // Otherwise, we need to do some array copying.
+                else
+                {
+                    ServiceRegistration[] newRegs = new ServiceRegistration[regs.length - 1];
+                    System.arraycopy(regs, 0, newRegs, 0, i);
+                    if (i < newRegs.length)
+                    {
+                        System.arraycopy(
+                            regs, i + 1, newRegs, i, newRegs.length - i);
+                    }
+                    regs = newRegs;
+                }
+            }
+        }      
+        return regs;
+    }
+
+    public synchronized void addServiceListener(ServiceListener l)
+    {
+        m_serviceListener = ServiceListenerMulticaster.add(m_serviceListener, l);
+    }
+
+    public synchronized void removeServiceListener(ServiceListener l)
+    {
+        m_serviceListener = ServiceListenerMulticaster.remove(m_serviceListener, l);
+    }
+
+    protected void fireServiceChanged(ServiceEvent event)
+    {
+        // Grab a copy of the listener list.
+        ServiceListener listener = m_serviceListener;
+        // If not null, then dispatch event.
+        if (listener != null)
+        {
+            m_serviceListener.serviceChanged(event);
+        }
+    }
+
+    private static class ServiceListenerMulticaster implements ServiceListener
+    {
+        protected ServiceListener m_a = null, m_b = null;
+
+        protected ServiceListenerMulticaster(ServiceListener a, ServiceListener b)    
+        {        
+            m_a = a;        
+            m_b = b;    
+        }    
+    
+        public void serviceChanged(ServiceEvent e)    
+        {
+            m_a.serviceChanged(e);
+            m_b.serviceChanged(e);
+        }
+    
+        public static ServiceListener add(ServiceListener a, ServiceListener b)
+        {
+            if (a == null)
+            {
+                return b;
+            }
+            else if (b == null)
+            {
+                return a;
+            }
+            else
+            {
+                return new ServiceListenerMulticaster(a, b);
+            }
+        }
+    
+        public static ServiceListener remove(ServiceListener a, ServiceListener b)
+        {
+            if ((a == null) || (a == b))
+            {
+                return null;
+            }
+            else if (a instanceof ServiceListenerMulticaster)
+            {
+                return add(
+                    remove(((ServiceListenerMulticaster) a).m_a, b),
+                    remove(((ServiceListenerMulticaster) a).m_b, b));
+            }
+            else
+            {
+                return a;
+            }
+        }
+    }
+
+    private static UsageCount getUsageCount(UsageCount[] usages, ServiceReference ref)
+    {
+        for (int i = 0; (usages != null) && (i < usages.length); i++)
+        {
+            if (usages[i].m_ref.equals(ref))
+            {
+                return usages[i];
+            }
+        }
+        return null;
+    }
+
+    private static UsageCount[] addUsageCount(UsageCount[] usages, ServiceReference ref, Object svcObj)
+    {
+        UsageCount usage = new UsageCount();
+        usage.m_ref = ref;
+        usage.m_svcObj = svcObj;
+        usage.m_count++;
+
+        if (usages == null)
+        {
+            usages = new UsageCount[] { usage };
+        }
+        else
+        {
+            UsageCount[] newUsages = new UsageCount[usages.length + 1];
+            System.arraycopy(usages, 0, newUsages, 0, usages.length);
+            newUsages[usages.length] = usage;
+            usages = newUsages;
+        }
+        return usages;
+    }
+
+    private static UsageCount[] removeUsageCount(UsageCount[] usages, ServiceReference ref)
+    {
+        for (int i = 0; (usages != null) && (i < usages.length); i++)
+        {
+            if (usages[i].m_ref.equals(ref))
+            {
+                // If this is the only usage, then point to empty list.
+                if ((usages.length - 1) == 0)
+                {
+                    usages = new UsageCount[0];
+                }
+                // Otherwise, we need to do some array copying.
+                else
+                {
+                    UsageCount[] newUsages= new UsageCount[usages.length - 1];
+                    System.arraycopy(usages, 0, newUsages, 0, i);
+                    if (i < newUsages.length)
+                    {
+                        System.arraycopy(
+                            usages, i + 1, newUsages, i, newUsages.length - i);
+                    }
+                    usages = newUsages;
+                }
+            }
+        }
+        
+        return usages;
+    }
+
+    private static class UsageCount
+    {
+        public int m_count = 0;
+        public ServiceReference m_ref = null;
+        public Object m_svcObj = null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/StartLevelActivator.java b/src/org/apache/osgi/framework/StartLevelActivator.java
new file mode 100644
index 0000000..222b55e
--- /dev/null
+++ b/src/org/apache/osgi/framework/StartLevelActivator.java
@@ -0,0 +1,43 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import org.osgi.framework.*;
+
+class StartLevelActivator implements BundleActivator
+{
+    private Felix m_felix = null;
+    private ServiceRegistration m_reg = null;
+
+    public StartLevelActivator(Felix felix)
+    {
+        m_felix = felix;
+    }
+
+    public void start(BundleContext context) throws Exception
+    {
+        m_reg = context.registerService(
+            org.osgi.service.startlevel.StartLevel.class.getName(),
+            new StartLevelImpl(m_felix),
+            null);
+    }
+
+    public void stop(BundleContext context) throws Exception
+    {
+        m_reg.unregister();
+    }
+}
diff --git a/src/org/apache/osgi/framework/StartLevelImpl.java b/src/org/apache/osgi/framework/StartLevelImpl.java
new file mode 100644
index 0000000..831af64
--- /dev/null
+++ b/src/org/apache/osgi/framework/StartLevelImpl.java
@@ -0,0 +1,147 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.service.startlevel.StartLevel;
+
+/**
+ * @author rickhall
+ *
+ * To change the template for this generated type comment go to
+ * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
+**/
+public class StartLevelImpl implements StartLevel, Runnable
+{
+    private Felix m_felix = null;
+    private List m_requestList = null;
+    // Reusable admin permission.
+    private static AdminPermission m_adminPerm = new AdminPermission();
+    
+    public StartLevelImpl(Felix felix)
+    {
+        m_felix = felix;
+        m_requestList = new ArrayList();
+
+        // Start a thread to perform asynchronous package refreshes.
+        Thread t = new Thread(this, "FelixStartLevel");
+        t.setDaemon(true);
+        t.start();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#getStartLevel()
+    **/
+    public int getStartLevel()
+    {
+        return m_felix.getStartLevel();
+    }
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#setStartLevel(int)
+    **/
+    public void setStartLevel(int startlevel)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        else if (startlevel <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Start level must be greater than zero.");
+        }
+        synchronized (m_requestList)
+        {
+            m_requestList.add(new Integer(startlevel));
+            m_requestList.notifyAll();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#getBundleStartLevel(org.osgi.framework.Bundle)
+    **/
+    public int getBundleStartLevel(Bundle bundle)
+    {
+        return m_felix.getBundleStartLevel(bundle);
+    }
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#setBundleStartLevel(org.osgi.framework.Bundle, int)
+    **/
+    public void setBundleStartLevel(Bundle bundle, int startlevel)
+    {
+        m_felix.setBundleStartLevel(bundle, startlevel);
+    }
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#getInitialBundleStartLevel()
+    **/
+    public int getInitialBundleStartLevel()
+    {
+        return m_felix.getInitialBundleStartLevel();
+    }
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#setInitialBundleStartLevel(int)
+    **/
+    public void setInitialBundleStartLevel(int startlevel)
+    {
+        m_felix.setInitialBundleStartLevel(startlevel);
+    }
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.startlevel.StartLevel#isBundlePersistentlyStarted(org.osgi.framework.Bundle)
+    **/
+    public boolean isBundlePersistentlyStarted(Bundle bundle)
+    {
+        return m_felix.isBundlePersistentlyStarted(bundle);
+    }
+
+    public void run()
+    {
+        int startLevel = 0;
+
+        // This thread loops forever, thus it should
+        // be a daemon thread.
+        while (true)
+        {
+            synchronized (m_requestList)
+            {
+                // Wait for a request.
+                while (m_requestList.size() == 0)
+                {
+                    try {
+                        m_requestList.wait();
+                    } catch (InterruptedException ex) {
+                    }
+                }
+                
+                // Get the requested start level.
+                startLevel = ((Integer) m_requestList.remove(0)).intValue();
+            }
+
+            // Set the new start level.
+            m_felix.setFrameworkStartLevel(startLevel);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/SystemBundle.java b/src/org/apache/osgi/framework/SystemBundle.java
new file mode 100644
index 0000000..d201e7d
--- /dev/null
+++ b/src/org/apache/osgi/framework/SystemBundle.java
@@ -0,0 +1,277 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+import org.apache.osgi.framework.searchpolicy.*;
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.apache.osgi.framework.util.FelixConstants;
+import org.apache.osgi.moduleloader.LibrarySource;
+import org.apache.osgi.moduleloader.ResourceSource;
+import org.osgi.framework.*;
+
+
+class SystemBundle extends BundleImpl
+{
+    private List m_activatorList = null;
+    private BundleActivator m_activator = null;
+    private Thread m_shutdownThread = null;
+    private Object[][] m_attributes = null;
+    private ResourceSource[] m_resSources = null;
+    private LibrarySource[] m_libSources = null;
+
+    protected SystemBundle(Felix felix, BundleInfo info, List activatorList)
+        throws BundleException
+    {
+        super(felix, info);
+
+        // Create an activator list if necessary.
+        if (activatorList == null)
+        {
+            activatorList = new ArrayList();
+        }
+
+        // Add the bundle activator for the package admin service.
+        activatorList.add(new PackageAdminActivator(felix));
+
+        // Add the bundle activator for the start level service.
+        activatorList.add(new StartLevelActivator(felix));
+
+        m_activatorList = activatorList;
+
+        // The system bundle exports framework packages as well as
+        // arbitrary user-defined packages from the system class path.
+        // We must construct the system bundle's export metadata.
+
+        // Get system property that specifies which class path
+        // packages should be exported by the system bundle.
+        R4Package[] classPathPkgs = null;
+        try
+        {
+            classPathPkgs = R4Package.parseImportOrExportHeader(
+                getFelix().getConfig().get(FelixConstants.FRAMEWORK_SYSTEMPACKAGES));
+        }
+        catch (Exception ex)
+        {
+            getFelix().getLogger().log(
+                LogWrapper.LOG_ERROR,
+                "Error parsing system bundle export statement.", ex);
+        }
+
+        // Now, create the list of standard framework exports for
+        // the system bundle.
+        R4Package[] exports = new R4Package[classPathPkgs.length + 3];
+
+        exports[0] = new R4Package(
+            "org.osgi.framework",
+            new R4Directive[0],
+            new R4Attribute[] { new R4Attribute("version", "1.2.0", false) });
+
+        exports[1] = new R4Package(
+            "org.osgi.service.packageadmin",
+            new R4Directive[0],
+            new R4Attribute[] { new R4Attribute("version", "1.2.0", false) });
+
+        exports[2] = new R4Package(
+            "org.osgi.service.startlevel",
+            new R4Directive[0],
+            new R4Attribute[] { new R4Attribute("version", "1.0.0", false) });
+
+        // Copy the class path exported packages.
+        System.arraycopy(classPathPkgs, 0, exports, 3, classPathPkgs.length);
+
+        m_attributes = new Object[][] {
+            new Object[] { R4SearchPolicy.EXPORTS_ATTR, exports },
+            new Object[] { R4SearchPolicy.IMPORTS_ATTR, new R4Package[0] }
+        };
+
+        m_resSources = new ResourceSource[0];
+
+        m_libSources = null;
+
+        String exportString = "";
+        for (int i = 0; i < exports.length; i++)
+        {
+            exportString = exportString +
+            exports[i].getId()
+            + "; specification-version=\""
+            + exports[i].getVersionLow().toString() + "\"";
+
+            if (i < (exports.length - 1))
+            {
+                exportString = exportString + ", ";
+                exportString = exportString +
+                    exports[i].getId()
+                    + "; specification-version=\""
+                    + exports[i].getVersionLow().toString() + "\", ";
+            }
+        }
+
+        // Initialize header map as a case insensitive map.
+        Map map = new CaseInsensitiveMap();
+        map.put(FelixConstants.BUNDLE_VERSION, FelixConstants.FELIX_VERSION_VALUE);
+        map.put(FelixConstants.BUNDLE_NAME, "System Bundle");
+        map.put(FelixConstants.BUNDLE_DESCRIPTION,
+            "This bundle is system specific; it implements various system services.");
+        map.put(FelixConstants.EXPORT_PACKAGE, exportString);
+        ((SystemBundleArchive) getInfo().getArchive()).setManifestHeader(map);
+    }
+
+    public Object[][] getAttributes()
+    {
+        return m_attributes;
+    }
+
+    public ResourceSource[] getResourceSources()
+    {
+        return m_resSources;
+    }
+
+    public LibrarySource[] getLibrarySources()
+    {
+        return m_libSources;
+    }
+
+    public synchronized void start() throws BundleException
+    {
+        // The system bundle is only started once and it
+        // is started by the framework.
+        if (getState() == Bundle.ACTIVE)
+        {
+            throw new BundleException("Cannot start the system bundle.");
+        }
+
+        getInfo().setState(Bundle.STARTING);
+
+        try {
+            getInfo().setContext(new BundleContextImpl(getFelix(), this));
+            getActivator().start(getInfo().getContext());
+        } catch (Throwable throwable) {
+throwable.printStackTrace();
+            throw new BundleException(
+                "Unable to start system bundle.", throwable);
+        }
+
+        // Do NOT set the system bundle state to active yet, this
+        // must be done after all other bundles have been restarted.
+        // This will be done after the framework is initialized.
+    }
+
+    public synchronized void stop() throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(new AdminPermission());
+        }
+
+        // Spec says stop() on SystemBundle should return immediately and
+        // shutdown framework on another thread.
+        if (getFelix().getStatus() == Felix.RUNNING_STATUS)
+        {
+            // Initial call of stop, so kick off shutdown.
+            m_shutdownThread = new Thread("FelixShutdown") {
+                public void run()
+                {
+                    try
+                    {
+                        getFelix().shutdown();
+                    }
+                    catch (Exception ex)
+                    {
+                        getFelix().getLogger().log(
+                            LogWrapper.LOG_ERROR,
+                            "SystemBundle: Error while shutting down.", ex);
+                    }
+
+                    // Only shutdown the JVM if the framework is running stand-alone.
+                    String embedded = getFelix().getConfig()
+                        .get(FelixConstants.EMBEDDED_EXECUTION_PROP);
+                    boolean isEmbedded = (embedded == null)
+                        ? false : embedded.equals("true");
+                    if (!isEmbedded)
+                    {
+                        if (System.getSecurityManager() != null)
+                        {
+                            AccessController.doPrivileged(new PrivilegedAction() {
+                                public Object run()
+                                {
+                                    System.exit(0);
+                                    return null;
+                                }
+                            });
+                        }
+                        else
+                        {
+                            System.exit(0);
+                        }
+                    }
+                }
+            };
+            getInfo().setState(Bundle.STOPPING);
+            m_shutdownThread.start();
+        }
+        else if ((getFelix().getStatus() == Felix.STOPPING_STATUS) &&
+                 (Thread.currentThread() == m_shutdownThread))
+        {
+            // Callback from shutdown thread, so do our own stop.
+            try
+            {
+                getActivator().stop(getInfo().getContext());
+            }
+            catch (Throwable throwable)
+            {
+                throw new BundleException(
+                        "Unable to stop system bundle.", throwable);
+            }
+        }
+    }
+
+    public synchronized void uninstall() throws BundleException
+    {
+        throw new BundleException("Cannot uninstall the system bundle.");
+    }
+
+    public synchronized void update() throws BundleException
+    {
+        update(null);
+    }
+
+    public synchronized void update(InputStream is) throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(new AdminPermission());
+        }
+
+        // TODO: This is supposed to stop and then restart the framework.
+        throw new BundleException("System bundle update not implemented yet.");
+    }
+
+    protected BundleActivator getActivator()
+        throws Exception
+    {
+        if (m_activator == null)
+        {
+            m_activator = new SystemBundleActivator(getFelix(), m_activatorList);
+        }
+        return m_activator;
+    }
+}
diff --git a/src/org/apache/osgi/framework/SystemBundleActivator.java b/src/org/apache/osgi/framework/SystemBundleActivator.java
new file mode 100644
index 0000000..0dbd1a7
--- /dev/null
+++ b/src/org/apache/osgi/framework/SystemBundleActivator.java
@@ -0,0 +1,61 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.util.List;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+class SystemBundleActivator implements BundleActivator
+{
+    private Felix m_felix = null;
+    private List m_activatorList = null;
+    private BundleContext m_context = null;
+
+    SystemBundleActivator(Felix felix, List activatorList)
+    {
+        this.m_felix = felix;
+        this.m_activatorList = activatorList;
+    }
+
+    public void start(BundleContext context) throws Exception
+    {
+        this.m_context = context;
+
+        // Start all activators.
+        if (m_activatorList != null)
+        {
+            for (int i = 0; i < m_activatorList.size(); i++)
+            {
+                ((BundleActivator) m_activatorList.get(i)).start(context);
+            }
+        }
+    }
+
+    public void stop(BundleContext context) throws Exception
+    {
+        if (m_activatorList != null)
+        {
+            // Stop all activators.
+            for (int i = 0; i < m_activatorList.size(); i++)
+            {
+                ((BundleActivator) m_activatorList.get(i)).stop(context);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/SystemBundleArchive.java b/src/org/apache/osgi/framework/SystemBundleArchive.java
new file mode 100644
index 0000000..611d350
--- /dev/null
+++ b/src/org/apache/osgi/framework/SystemBundleArchive.java
@@ -0,0 +1,109 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.osgi.framework.cache.BundleArchive;
+import org.apache.osgi.framework.util.FelixConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+
+public class SystemBundleArchive implements BundleArchive
+{
+    private Map m_headerMap = null;
+
+    public long getId()
+    {
+        return 0;
+    }
+    
+    public String getLocation()
+        throws Exception
+    {
+        return FelixConstants.SYSTEM_BUNDLE_LOCATION;
+    }
+
+    public int getPersistentState()
+        throws Exception
+    {
+        return Bundle.ACTIVE;
+    }
+
+    public void setPersistentState(int state)
+        throws Exception
+    {
+    }
+
+    public int getStartLevel()
+        throws Exception
+    {
+        return FelixConstants.SYSTEMBUNDLE_DEFAULT_STARTLEVEL;
+    }
+
+    public void setStartLevel(int level)
+        throws Exception
+    {
+    }
+
+    public File getDataFile(String fileName)
+        throws Exception
+    {
+        return null;
+    }
+
+    public BundleActivator getActivator(ClassLoader loader)
+        throws Exception
+    {
+        return null;
+    }
+
+    public void setActivator(Object obj)
+        throws Exception
+    {
+    }
+
+    public int getRevisionCount()
+        throws Exception
+    {
+        return 1;
+    }
+
+    public Map getManifestHeader(int revision)
+        throws Exception
+    {
+        return m_headerMap;
+    }
+    
+    protected void setManifestHeader(Map headerMap)
+    {
+        m_headerMap = headerMap;
+    }
+
+    public String[] getClassPath(int revision)
+        throws Exception
+    {
+        return null;
+    }
+
+    public String findLibrary(int revision, String libName)
+        throws Exception
+    {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/BundleArchive.java b/src/org/apache/osgi/framework/cache/BundleArchive.java
new file mode 100644
index 0000000..9ad76c1
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/BundleArchive.java
@@ -0,0 +1,194 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.cache;
+
+import java.io.File;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+
+/**
+ * <p>
+ * This interface represents an individual cached bundle in the
+ * bundle cache. Felix uses this interface to access all information
+ * about the associated bundle's cached information. Classes that implement
+ * this interface will be related to a specific implementation of the
+ * <tt>BundleCache</tt> interface.
+ * </p>
+ * @see org.apache.osgi.framework.BundleCache
+**/
+public interface BundleArchive
+{
+    /**
+     * <p>
+     * Returns the identifier of the bundle associated with this archive.
+     * </p>
+     * @return the identifier of the bundle associated with this archive.
+    **/
+    public long getId();
+    
+    /**
+     * <p>
+     * Returns the location string of the bundle associated with this archive.
+     * </p>
+     * @return the location string of the bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public String getLocation()
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the persistent state of the bundle associated with the archive;
+     * this value will be either <tt>Bundle.INSTALLED</tt> or <tt>Bundle.ACTIVE</tt>.
+     * </p>
+     * @return the persistent state of the bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public int getPersistentState()
+        throws Exception;
+
+    /**
+     * <p>
+     * Sets the persistent state of the bundle associated with this archive;
+     * this value will be either <tt>Bundle.INSTALLED</tt> or <tt>Bundle.ACTIVE</tt>.
+     * </p>
+     * @param state the new bundle state to write to the archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public void setPersistentState(int state)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the start level of the bundle associated with this archive.
+     * </p>
+     * @return the start level of the bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public int getStartLevel()
+        throws Exception;
+
+    /**
+     * <p>
+     * Sets the start level of the bundle associated with this archive.
+     * </p>
+     * @param level the new bundle start level to write to the archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public void setStartLevel(int level)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns an appropriate data file for the bundle associated with the
+     * archive using the supplied file name.
+     * </p>
+     * @return a <tt>File</tt> corresponding to the requested data file for
+     *         the bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public File getDataFile(String fileName)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the persistent bundle activator of the bundle associated with
+     * this archive; this is a non-standard OSGi method that is only called
+     * when Felix is running in non-strict OSGi mode.
+     * </p>
+     * @param loader the class loader to use when trying to instantiate
+     *        the bundle activator.
+     * @return the persistent bundle activator of the bundle associated with
+     *         this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public BundleActivator getActivator(ClassLoader loader)
+        throws Exception;
+
+    /**
+     * <p>
+     * Sets the persistent bundle activator of the bundle associated with
+     * this archive; this is a non-standard OSGi method that is only called
+     * when Felix is running in non-strict OSGi mode.
+     * </p>
+     * @param obj the new persistent bundle activator to write to the archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public void setActivator(Object obj)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the number of revisions of the bundle associated with the
+     * archive. When a bundle is updated, the previous version of the bundle
+     * is maintained along with the new revision until old revisions are
+     * purged. The revision count reflects how many revisions of the bundle
+     * are currently available in the cache.
+     * </p>
+     * @return the number of revisions of the bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public int getRevisionCount()
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the main attributes of the JAR file manifest header of the
+     * specified revision of the bundle associated with this archive. The
+     * returned map should be case insensitive.
+     * </p>
+     * @param revision the specified revision.
+     * @return the case-insensitive JAR file manifest header of the specified
+     *         revision of the bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public Map getManifestHeader(int revision)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns an array of <tt>String</tt>s that represent the class path of
+     * the specified revision of the bundle associated with this archive.
+     * Currently, these values are restricted to absolute paths in the file
+     * system, but this may be lifted in the future (perhaps they should be
+     * <tt>ResourceSource</tt>s from the Module Loader.
+     * </p>
+     * @param revision the specified revision.
+     * @return a <tt>String</tt> array of the absolute path names that
+     *         comprise the class path of the specified revision of the
+     *         bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public String[] getClassPath(int revision)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the absolute file path for the specified native library of the
+     * specified revision of the bundle associated with this archive.
+     * </p>
+     * @param revision the specified revision.
+     * @param libName the name of the library.
+     * @return a <tt>String</tt> that contains the absolute path name to
+     *         the requested native library of the specified revision of the
+     *         bundle associated with this archive.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public String findLibrary(int revision, String libName)
+        throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/BundleCache.java b/src/org/apache/osgi/framework/cache/BundleCache.java
new file mode 100644
index 0000000..87c0ee9
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/BundleCache.java
@@ -0,0 +1,148 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.cache;
+
+import java.io.InputStream;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.framework.util.PropertyResolver;
+
+/**
+ * <p>
+ * This interface represents the storage mechanism that Felix uses for
+ * caching bundles. It is possible for multiple implementations of
+ * this interface to exist for different storage technologies, such as the
+ * file system, memory, or a database. Felix includes a default implementation
+ * of this interface that uses the file system. Felix allows you to specify
+ * alternative implementations to use by specifying a class name via the
+ * <tt>felix.cache.class</tt> system property. Bundle cache implemenations
+ * should implement this interface and provide a default constructor.
+ * </p>
+ * @see org.apache.osgi.framework.BundleArchive
+**/
+public interface BundleCache
+{
+    /**
+     * <p>
+     * This method is called before using the BundleCache implementation
+     * to initialize it and to pass it a reference to its associated
+     * configuration property resolver and logger. The <tt>BundleCache</tt>
+     * implementation should not use <tt>System.getProperty()</tt> directly
+     * for configuration properties, it should use the property resolver
+     * instance passed into this method. The property resolver
+     * provides access to properties passed into the Felix instance's
+     * constructor. This approach allows multiple instances of Felix to
+     * exist in memory at the same time, but for
+     * them to be configured differently. For example, an application may
+     * want two instances of Felix, where each instance stores their cache
+     * in a different location in the file system. When using multiple
+     * instances of Felix in memory at the same time, system properties
+     * should be avoided and all properties should be passed in to Felix's
+     * constructor.
+     * </p>
+     * @param cfg the property resolver for obtaining configuration properties.
+     * @param logger the logger to use for reporting errors.
+     * @throws Exception if any error occurs.
+    **/
+    public void initialize(PropertyResolver cfg, LogWrapper logger)
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns all cached bundle archives.
+     * </p>
+     * @return an array of all cached bundle archives.
+     * @throws Exception if any error occurs.
+    **/
+    public BundleArchive[] getArchives()
+        throws Exception;
+
+    /**
+     * <p>
+     * Returns the bundle archive associated with the specified
+     * bundle indentifier.
+     * </p>
+     * @param id the identifier of the bundle archive to retrieve.
+     * @return the bundle archive assocaited with the specified bundle identifier.
+     * @throws Exception if any error occurs.
+    **/
+    public BundleArchive getArchive(long id)
+        throws Exception;
+
+    /**
+     * <p>
+     * Creates a new bundle archive for the specified bundle
+     * identifier using the supplied location string and input stream. The
+     * contents of the bundle JAR file should be read from the supplied
+     * input stream, which will not be <tt>null</tt>. The input stream is
+     * closed by the caller; the implementation is only responsible for
+     * closing streams it opens. If this method completes successfully, then
+     * it means that the initial bundle revision of the specified bundle was
+     * successfully cached.
+     * </p>
+     * @param id the identifier of the bundle associated with the new archive.
+     * @param location the location of the bundle associated with the new archive.
+     * @param is the input stream to the bundle's JAR file.
+     * @return the created bundle archive.
+     * @throws Exception if any error occurs.
+    **/
+    public BundleArchive create(long id, String location, InputStream is)
+        throws Exception;
+
+    /**
+     * <p>
+     * Saves an updated revision of the specified bundle to
+     * the bundle cache using the supplied input stream. The contents of the
+     * updated bundle JAR file should be read from the supplied input stream,
+     * which will not be <tt>null</tt>. The input stream is closed by the
+     * caller; the implementation is only responsible for closing streams
+     * it opens. Updating a bundle in the cache does not replace the current
+     * revision of the bundle, it makes a new revision available. If this
+     * method completes successfully, then it means that the number of
+     * revisions of the specified bundle has increased by one.
+     * </p>
+     * @param ba the bundle archive of the bundle to update.
+     * @param is the input stream to the bundle's updated JAR file.
+     * @throws Exception if any error occurs.
+    **/
+    public void update(BundleArchive ba, InputStream is)
+        throws Exception;
+
+    /**
+     * <p>
+     * Purges all old revisions of the specified bundle from
+     * the cache. If this method completes successfully, then it means that
+     * only the most current revision of the bundle should exist in the cache.
+     * </p>
+     * @param ba the bundle archive of the bundle to purge.
+     * @throws Exception if any error occurs.
+    **/
+    public void purge(BundleArchive ba)
+        throws Exception;
+
+    /**
+     * <p>
+     * Removes the specified bundle from the cache. If this method
+     * completes successfully, there should be no trace of the removed bundle
+     * in the cache.
+     * </p>
+     * @param ba the bundle archive of the bundle to remove.
+     * @throws Exception if any error occurs.
+    **/
+    public void remove(BundleArchive ba)
+        throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/DefaultBundleArchive.java b/src/org/apache/osgi/framework/cache/DefaultBundleArchive.java
new file mode 100644
index 0000000..18d04c0
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/DefaultBundleArchive.java
@@ -0,0 +1,1471 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.cache;
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.framework.util.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * <p>
+ * This class, combined with <tt>DefaultBundleCache</tt>, implements the
+ * default file system-based bundle cache for Felix.
+ * </p>
+ * @see org.apache.osgi.framework.util.DefaultBundleCache
+**/
+public class DefaultBundleArchive implements BundleArchive
+{
+    private static final transient String BUNDLE_JAR_FILE = "bundle.jar";
+    private static final transient String BUNDLE_LOCATION_FILE = "bundle.location";
+    private static final transient String BUNDLE_STATE_FILE = "bundle.state";
+    private static final transient String BUNDLE_START_LEVEL_FILE = "bundle.startlevel";
+    private static final transient String REFRESH_COUNTER_FILE = "refresh.counter";
+    private static final transient String BUNDLE_ACTIVATOR_FILE = "bundle.activator";
+
+    private static final transient String REVISION_DIRECTORY = "version";
+    private static final transient String EMBEDDED_DIRECTORY = "embedded";
+    private static final transient String LIBRARY_DIRECTORY = "lib";
+    private static final transient String DATA_DIRECTORY = "data";
+
+    private static final transient String ACTIVE_STATE = "active";
+    private static final transient String INSTALLED_STATE = "installed";
+    private static final transient String UNINSTALLED_STATE = "uninstalled";
+
+    private LogWrapper m_logger = null;
+    private long m_id = -1;
+    private File m_dir = null;
+    private String m_location = null;
+    private int m_persistentState = -1;
+    private int m_startLevel = -1;
+    private Map m_currentHeader = null;
+
+    private long m_refreshCount = -1;
+    private int m_revisionCount = -1;
+
+    public DefaultBundleArchive(LogWrapper logger, File dir, long id, String location, InputStream is)    
+        throws Exception
+    {
+        this(logger, dir, id);
+        m_location = location;
+
+        // Try to save and pre-process the bundle JAR.
+        try
+        {
+            initialize(is);
+        }
+        catch (Exception ex)
+        {
+            if (!deleteDirectoryTree(dir))
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "Unable to delete the archive directory: " + id);
+            }
+            throw ex;
+        }
+    }
+
+    public DefaultBundleArchive(LogWrapper logger, File dir, long id)
+    {
+        m_logger = logger;
+        m_dir = dir;
+        m_id = id;
+        if (m_id <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Bundle ID cannot be less than or equal to zero.");
+        }
+    }
+
+    private void initialize(InputStream is)
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.INITIALIZE_ACTION, this, is));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            initializeUnchecked(is);
+        }
+    }
+
+    private void initializeUnchecked(InputStream is)
+        throws Exception
+    {
+        FileWriter fw = null;
+        BufferedWriter bw = null;
+
+        try
+        {
+            // Create archive directory.
+            if (!m_dir.mkdir())
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "DefaultBundleArchive: Unable to create archive directory.");
+                throw new IOException("Unable to create archive directory.");
+            }
+
+            // Save location string.
+            File file = new File(m_dir, BUNDLE_LOCATION_FILE);
+            fw = new FileWriter(file);
+            bw = new BufferedWriter(fw);
+            bw.write(m_location, 0, m_location.length());
+
+            // Create version/revision directory for bundle JAR.
+            // Since this is only called when the bundle JAR is
+            // first saved, the update and revision will always
+            // be "0.0" for the directory name.
+            File revisionDir = new File(m_dir, REVISION_DIRECTORY + "0.0");
+            if (!revisionDir.mkdir())
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "DefaultBundleArchive: Unable to create revision directory.");
+                throw new IOException("Unable to create revision directory.");
+            }
+
+            // Save the bundle jar file.
+            file = new File(revisionDir, BUNDLE_JAR_FILE);
+            copy(is, file);
+
+            // This will always be revision zero.
+            preprocessBundleJar(0, revisionDir);
+
+        }
+        finally
+        {
+            if (is != null) is.close();
+            if (bw != null) bw.close();
+            if (fw != null) fw.close();
+        }
+    }
+
+    public File getDirectory()
+    {
+        return m_dir;
+    }
+
+    public long getId()
+    {
+        return m_id;
+    }
+
+    public String getLocation()
+        throws Exception
+    {
+        if (m_location != null)
+        {
+            return m_location;
+        }
+        else if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                return (String) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.GET_LOCATION_ACTION, this));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            return getLocationUnchecked();
+        }
+    }
+
+    private String getLocationUnchecked()
+        throws Exception
+    {
+        // Get bundle location file.
+        File locFile = new File(m_dir, BUNDLE_LOCATION_FILE);
+
+        // Read bundle location.
+        FileReader fr = null;
+        BufferedReader br = null;
+        try
+        {
+            fr = new FileReader(locFile);
+            br = new BufferedReader(fr);
+            m_location = br.readLine();
+            return m_location;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (fr != null) fr.close();
+        }
+    }
+
+    public int getPersistentState()
+        throws Exception
+    {
+        if (m_persistentState >= 0)
+        {
+            return m_persistentState;
+        }
+        else if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                return ((Integer) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.GET_PERSISTENT_STATE_ACTION, this))).intValue();
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            return getPersistentStateUnchecked();
+        }
+    }
+
+    private int getPersistentStateUnchecked()
+        throws Exception
+    {
+        // Get bundle state file.
+        File stateFile = new File(m_dir, BUNDLE_STATE_FILE);
+
+        // If the state file doesn't exist, then
+        // assume the bundle was installed.
+        if (!stateFile.exists())
+        {
+            return Bundle.INSTALLED;
+        }
+
+        // Read the bundle state.
+        FileReader fr = null;
+        BufferedReader br= null;
+        try
+        {
+            fr = new FileReader(stateFile);
+            br = new BufferedReader(fr);
+            String s = br.readLine();
+            if (s.equals(ACTIVE_STATE))
+            {
+                m_persistentState = Bundle.ACTIVE;
+            }
+            else if (s.equals(UNINSTALLED_STATE))
+            {
+                m_persistentState = Bundle.UNINSTALLED;
+            }
+            else
+            {
+                m_persistentState = Bundle.INSTALLED;
+            }
+            return m_persistentState;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (fr != null) fr.close();
+        }
+    }
+
+    public void setPersistentState(int state)
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.SET_PERSISTENT_STATE_ACTION, this, state));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            setPersistentStateUnchecked(state);
+        }
+    }
+
+    private void setPersistentStateUnchecked(int state)
+        throws Exception
+    {
+        // Get bundle state file.
+        File stateFile = new File(m_dir, BUNDLE_STATE_FILE);
+
+        // Write the bundle state.
+        FileWriter fw = null;
+        BufferedWriter bw= null;
+        try
+        {
+            fw = new FileWriter(stateFile);
+            bw = new BufferedWriter(fw);
+            String s = null;
+            switch (state)
+            {
+                case Bundle.ACTIVE:
+                    s = ACTIVE_STATE;
+                    break;
+                case Bundle.UNINSTALLED:
+                    s = UNINSTALLED_STATE;
+                    break;
+                default:
+                    s = INSTALLED_STATE;
+                    break;
+            }
+            bw.write(s, 0, s.length());
+            m_persistentState = state;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "DefaultBundleArchive: Unable to record state: " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (fw != null) fw.close();
+        }
+    }
+
+    public int getStartLevel()
+        throws Exception
+    {
+        if (m_startLevel >= 0)
+        {
+            return m_startLevel;
+        }
+        else if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                return ((Integer) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.GET_START_LEVEL_ACTION, this))).intValue();
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            return getStartLevelUnchecked();
+        }
+    }
+
+    private int getStartLevelUnchecked()
+        throws Exception
+    {
+        // Get bundle start level file.
+        File levelFile = new File(m_dir, BUNDLE_START_LEVEL_FILE);
+
+        // If the start level file doesn't exist, then
+        // return an error.
+        if (!levelFile.exists())
+        {
+            return -1;
+        }
+
+        // Read the bundle start level.
+        FileReader fr = null;
+        BufferedReader br= null;
+        try
+        {
+            fr = new FileReader(levelFile);
+            br = new BufferedReader(fr);
+            m_startLevel = Integer.parseInt(br.readLine());
+            return m_startLevel;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (fr != null) fr.close();
+        }
+    }
+
+    public void setStartLevel(int level)
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.SET_START_LEVEL_ACTION, this, level));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            setStartLevelUnchecked(level);
+        }
+    }
+
+    private void setStartLevelUnchecked(int level)
+        throws Exception
+    {
+        // Get bundle start level file.
+        File levelFile = new File(m_dir, BUNDLE_START_LEVEL_FILE);
+
+        // Write the bundle start level.
+        FileWriter fw = null;
+        BufferedWriter bw = null;
+        try
+        {
+            fw = new FileWriter(levelFile);
+            bw = new BufferedWriter(fw);
+            String s = Integer.toString(level);
+            bw.write(s, 0, s.length());
+            m_startLevel = level;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "DefaultBundleArchive: Unable to record start leel: " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (fw != null) fw.close();
+        }
+    }
+
+    public File getDataFile(String fileName)
+        throws Exception
+    {
+        // Do some sanity checking.
+        if ((fileName.length() > 0) && (fileName.charAt(0) == File.separatorChar))
+            throw new IllegalArgumentException("The data file path must be relative, not absolute.");
+        else if (fileName.indexOf("..") >= 0)
+            throw new IllegalArgumentException("The data file path cannot contain a reference to the \"..\" directory.");
+
+        // Get bundle data directory.
+        File dataDir = new File(m_dir, DATA_DIRECTORY);
+
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.CREATE_DATA_DIR_ACTION, this, dataDir));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            createDataDirectoryUnchecked(dataDir);
+        }
+
+        // Return the data file.
+        return new File(dataDir, fileName);
+    }
+
+    private void createDataDirectoryUnchecked(File dir)
+        throws Exception
+    {
+        // Create data directory if necessary.
+        if (!dir.exists())
+        {
+            if (!dir.mkdir())
+            {
+                throw new IOException("Unable to create bundle data directory.");
+            }
+        }
+    }
+
+    public BundleActivator getActivator(ClassLoader loader)
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                return (BundleActivator) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.GET_ACTIVATOR_ACTION, this, loader));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            return getActivatorUnchecked(loader);
+        }
+    }
+
+    private BundleActivator getActivatorUnchecked(ClassLoader loader)
+        throws Exception
+    {
+        // Get bundle activator file.
+        File activatorFile = new File(m_dir, BUNDLE_ACTIVATOR_FILE);
+        // If the activator file doesn't exist, then
+        // assume there isn't one.
+        if (!activatorFile.exists())
+            return null;
+
+        // Deserialize the activator object.
+        InputStream is = null;
+        ObjectInputStreamX ois = null;
+        try
+        {
+            is = new FileInputStream(activatorFile);
+            ois = new ObjectInputStreamX(is, loader);
+            Object o = ois.readObject();
+            return (BundleActivator) o;
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "DefaultBundleArchive: Trying to deserialize - " + ex);
+        }
+        finally
+        {
+            if (ois != null) ois.close();
+            if (is != null) is.close();
+        }
+
+        return null;
+    }
+
+    public void setActivator(Object obj)
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.SET_ACTIVATOR_ACTION, this, obj));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            setActivatorUnchecked(obj);
+        }
+    }
+
+    private void setActivatorUnchecked(Object obj)
+        throws Exception
+    {
+        if (!(obj instanceof Serializable))
+        {
+            return;
+        }
+
+        // Get bundle activator file.
+        File activatorFile = new File(m_dir, BUNDLE_ACTIVATOR_FILE);
+
+        // Serialize the activator object.
+        OutputStream os = null;
+        ObjectOutputStream oos = null;
+        try
+        {
+            os = new FileOutputStream(activatorFile);
+            oos = new ObjectOutputStream(os);
+            oos.writeObject(obj);
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "DefaultBundleArchive: Unable to serialize activator - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (oos != null) oos.close();
+            if (os != null) os.close();
+        }
+    }
+
+    public int getRevisionCount()
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                return ((Integer) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.GET_REVISION_COUNT_ACTION, this))).intValue();
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            return getRevisionCountUnchecked();
+        }
+    }
+
+    public int getRevisionCountUnchecked()
+    {
+        // We should always have at least one revision
+        // directory, so try to count them if the value
+        // has not been initialized yet.
+        if (m_revisionCount <= 0)
+        {
+            m_revisionCount = 0;
+            File[] children = m_dir.listFiles();
+            for (int i = 0; (children != null) && (i < children.length); i++)
+            {
+                if (children[i].getName().startsWith(REVISION_DIRECTORY))
+                {
+                    m_revisionCount++;
+                }
+            }
+        }
+        return m_revisionCount;
+    }
+
+    public Map getManifestHeader(int revision)
+        throws Exception
+    {
+        // If the request is for the current revision header,
+        // then return the cached copy if it is present.
+        if ((revision == (getRevisionCount() - 1)) && (m_currentHeader != null))
+        {
+            return m_currentHeader;
+        }
+
+        // Get the revision directory.
+        File revisionDir = new File(
+            m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision);
+
+        // Get the embedded resource.
+        JarFile jarFile = null;
+
+        try
+        {
+            // Create JarFile object using privileged block.
+            if (System.getSecurityManager() != null)
+            {
+                jarFile = (JarFile) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.OPEN_BUNDLE_JAR_ACTION, this, revisionDir));
+            }
+            else
+            {
+                jarFile = openBundleJarUnchecked(revisionDir);
+            }
+
+            // Error if no jar file.
+            if (jarFile == null)
+            {
+                throw new IOException("No JAR file found.");
+            }
+
+            // Get manifest.
+            Manifest mf = jarFile.getManifest();
+            // Create a case insensitive map of manifest attributes.
+            Map map = new CaseInsensitiveMap(mf.getMainAttributes());
+            // If the request is for the current revision's header,
+            // then cache it.
+            if (revision == (getRevisionCount() - 1))
+            {
+                m_currentHeader = map;
+            }
+            return map;
+
+        } catch (PrivilegedActionException ex) {
+            throw ((PrivilegedActionException) ex).getException();
+        } finally {
+            if (jarFile != null) jarFile.close();
+        }
+    }
+
+    private JarFile openBundleJarUnchecked(File revisionDir)
+        throws Exception
+    {
+        // Get bundle jar file.
+        File bundleJar = new File(revisionDir, BUNDLE_JAR_FILE);
+        // Get bundle jar file.
+        return new JarFile(bundleJar);
+    }
+
+    public String[] getClassPath(int revision)
+        throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                return (String []) AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.GET_CLASS_PATH_ACTION, this, revision));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            return getClassPathUnchecked(revision);
+        }
+    }
+
+    private String[] getClassPathUnchecked(int revision)
+        throws Exception
+    {
+        // Get the revision directory.
+        File revisionDir = new File(
+            m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision);
+
+        // Get the bundle's manifest header.
+        Map map = getManifestHeader(revision);
+        if (map == null)
+        {
+            map = new HashMap();
+        }
+
+        // Find class path meta-data.
+        String classPath = null;
+        Iterator iter = map.entrySet().iterator();
+        while ((classPath == null) && iter.hasNext())
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            if (entry.getKey().toString().toLowerCase().equals(
+                FelixConstants.BUNDLE_CLASSPATH.toLowerCase()))
+            {
+                classPath = entry.getValue().toString();
+            }
+        }
+
+        // Parse the class path into strings.
+        String[] classPathStrings = Util.parseDelimitedString(
+            classPath, FelixConstants.CLASS_PATH_SEPARATOR);
+
+        if (classPathStrings == null)
+        {
+            classPathStrings = new String[0];
+        }
+
+        // Now, check for "." in the class path.
+        boolean includeDot = false;
+        for (int i = 0; !includeDot && (i < classPathStrings.length); i++)
+        {
+            if (classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+            {
+                includeDot = true;
+            }
+        }
+
+        // Include all JARs in the embedded jar directory, since they
+        // were extracted when the bundle was initially saved.
+        File embedDir = new File(revisionDir, EMBEDDED_DIRECTORY);
+        String[] paths = null;
+        if (embedDir.exists())
+        {
+            // The size of the paths array is the number of
+            // embedded JAR files plus one, if we need to include
+            // ".", otherwise it is just the number of JAR files.
+            // If "." is included, then it will be added to the
+            // first place in the path array below.
+            File[] children = embedDir.listFiles();
+            int size = (children == null) ? 0 : children.length;
+            size = (includeDot) ? size + 1 : size;
+            paths = new String[size];
+            for (int i = 0; i < children.length; i++)
+            {
+                // If we are including "." then skip the first slot,
+                // because this is where we will put the bundle JAR file.
+                paths[(includeDot) ? i + 1 : i] = children[i].getPath();
+            }
+        }
+
+        // If there is nothing on the class path, then include
+        // "." by default, as per the spec.
+        if ((paths == null) || (paths.length == 0))
+        {
+            includeDot = true;
+            paths = new String[1];
+        }
+
+        // Put the bundle jar file first, if included.
+        if (includeDot)
+        {
+            paths[0] = revisionDir + File.separator + BUNDLE_JAR_FILE;
+        }
+
+        return paths;
+    }
+
+//  TODO: This will need to consider security.
+    public String findLibrary(int revision, String libName)
+        throws Exception
+    {
+        return findLibraryUnchecked(revision, libName);
+    }
+
+    private String findLibraryUnchecked(int revision, String libName)
+        throws Exception
+    {
+        // Get the revision directory.
+        File revisionDir = new File(
+            m_dir.getAbsoluteFile(),
+            REVISION_DIRECTORY + getRefreshCount() + "." + revision);
+
+        // Get bundle lib directory.
+        File libDir = new File(revisionDir, LIBRARY_DIRECTORY);
+        // Get lib file.
+        File libFile = new File(libDir, File.separatorChar + libName);
+        // Make sure that the library's parent directory exists;
+        // it may be in a sub-directory.
+        libDir = libFile.getParentFile();
+        if (!libDir.exists())
+        {
+            if (!libDir.mkdirs())
+            {
+                throw new IOException("Unable to create library directory.");
+            }
+        }
+        // Extract the library from the JAR file if it does not
+        // already exist.
+        if (!libFile.exists())
+        {
+            JarFile jarFile = null;
+            InputStream is = null;
+
+            try
+            {
+                jarFile = openBundleJarUnchecked(revisionDir);
+                ZipEntry ze = jarFile.getEntry(libName);
+                if (ze == null)
+                {
+                    throw new IOException("No JAR entry: " + libName);
+                }
+                is = new BufferedInputStream(
+                    jarFile.getInputStream(ze), DefaultBundleCache.BUFSIZE);
+                if (is == null)
+                {
+                    throw new IOException("No input stream: " + libName);
+                }
+
+                // Create the file.
+                copy(is, libFile);
+
+            }
+            finally
+            {
+                if (jarFile != null) jarFile.close();
+                if (is != null) is.close();
+            }
+        }
+
+        return libFile.toString();
+    }
+
+    /**
+     * This utility method is used to retrieve the current refresh
+     * counter value for the bundle. This value is used when generating
+     * the bundle JAR directory name where native libraries are extracted.
+     * This is necessary because Sun's JVM requires a one-to-one mapping
+     * between native libraries and class loaders where the native library
+     * is uniquely identified by its absolute path in the file system. This
+     * constraint creates a problem when a bundle is refreshed, because it
+     * gets a new class loader. Using the refresh counter to generate the name
+     * of the bundle JAR directory resolves this problem because each time
+     * bundle is refresh, the native library will have a unique name.
+     * As a result of the unique name, the JVM will then reload the
+     * native library without a problem.
+    **/
+    private long getRefreshCount()
+        throws Exception
+    {
+        // If we have already read the update counter file,
+        // then just return the result.
+        if (m_refreshCount >= 0)
+        {
+            return m_refreshCount;
+        }
+
+        // Get update counter file.
+        File counterFile = new File(m_dir, REFRESH_COUNTER_FILE);
+
+        // If the update counter file doesn't exist, then
+        // assume the counter is at zero.
+        if (!counterFile.exists())
+        {
+            return 0;
+        }
+
+        // Read the bundle update counter.
+        FileReader fr = null;
+        BufferedReader br = null;
+        try
+        {
+            fr = new FileReader(counterFile);
+            br = new BufferedReader(fr);
+            long counter = Long.parseLong(br.readLine());
+            return counter;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (fr != null) fr.close();
+        }
+    }
+
+    /**
+     * This utility method is used to retrieve the current refresh
+     * counter value for the bundle. This value is used when generating
+     * the bundle JAR directory name where native libraries are extracted.
+     * This is necessary because Sun's JVM requires a one-to-one mapping
+     * between native libraries and class loaders where the native library
+     * is uniquely identified by its absolute path in the file system. This
+     * constraint creates a problem when a bundle is refreshed, because it
+     * gets a new class loader. Using the refresh counter to generate the name
+     * of the bundle JAR directory resolves this problem because each time
+     * bundle is refresh, the native library will have a unique name.
+     * As a result of the unique name, the JVM will then reload the
+     * native library without a problem.
+    **/
+    private void setRefreshCount(long counter)
+        throws Exception
+    {
+        // Get update counter file.
+        File counterFile = new File(m_dir, REFRESH_COUNTER_FILE);
+
+        // Write the update counter.
+        FileWriter fw = null;
+        BufferedWriter bw = null;
+        try
+        {
+            fw = new FileWriter(counterFile);
+            bw = new BufferedWriter(fw);
+            String s = Long.toString(counter);
+            bw.write(s, 0, s.length());
+            m_refreshCount = counter;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "DefaultBundleArchive: Unable to write counter: " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (fw != null) fw.close();
+        }
+    }
+
+    //
+    // File-oriented utility methods.
+    //
+
+    protected static boolean deleteDirectoryTree(File target)
+    {
+        if (!target.exists())
+        {
+            return true;
+        }
+
+        if (target.isDirectory())
+        {
+            File[] files = target.listFiles();
+            for (int i = 0; i < files.length; i++)
+            {
+                deleteDirectoryTree(files[i]);
+            }
+        }
+
+        return target.delete();
+    }
+
+    /**
+     * This method copies an input stream to the specified file.
+     * <p>
+     * Security: This method must be called from within a <tt>doPrivileged()</tt>
+     * block since it accesses the disk.
+     * @param is the input stream to copy.
+     * @param outputFile the file to which the input stream should be copied.
+    **/
+    private void copy(InputStream is, File outputFile)
+        throws IOException
+    {
+        OutputStream os = null;
+
+        try
+        {
+            os = new BufferedOutputStream(
+                new FileOutputStream(outputFile), DefaultBundleCache.BUFSIZE);
+            byte[] b = new byte[DefaultBundleCache.BUFSIZE];
+            int len = 0;
+            while ((len = is.read(b)) != -1)
+                os.write(b, 0, len);
+        }
+        finally
+        {
+            if (is != null) is.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * This method pre-processes a bundle JAR file making it ready
+     * for use. This entails extracting all embedded JAR files and
+     * all native libraries.
+     * @throws java.lang.Exception if any error occurs while processing JAR file.
+    **/
+    private void preprocessBundleJar(int revision, File revisionDir)
+        throws Exception
+    {
+        //
+        // Create special directories so that we can avoid checking
+        // for their existence all the time.
+        //
+
+        File embedDir = new File(revisionDir, EMBEDDED_DIRECTORY);
+        if (!embedDir.exists())
+        {
+            if (!embedDir.mkdir())
+            {
+                throw new IOException("Could not create embedded JAR directory.");
+            }
+        }
+
+        File libDir = new File(revisionDir, LIBRARY_DIRECTORY);
+        if (!libDir.exists())
+        {
+            if (!libDir.mkdir())
+            {
+                throw new IOException("Unable to create native library directory.");
+            }
+        }
+
+        //
+        // This block extracts all embedded JAR files.
+        //
+
+        try
+        {
+            // Get the bundle's manifest header.
+            Map map = getManifestHeader(revision);
+            if (map == null)
+            {
+                map = new HashMap();
+            }
+
+            // Find class path meta-data.
+            String classPath = null;
+            Iterator iter = map.entrySet().iterator();
+            while ((classPath == null) && iter.hasNext())
+            {
+                Map.Entry entry = (Map.Entry) iter.next();
+                if (entry.getKey().toString().toLowerCase().equals(
+                    FelixConstants.BUNDLE_CLASSPATH.toLowerCase()))
+                {
+                    classPath = entry.getValue().toString();
+                }
+            }
+
+            // Parse the class path into strings.
+            String[] classPathStrings = Util.parseDelimitedString(
+                classPath, FelixConstants.CLASS_PATH_SEPARATOR);
+
+            if (classPathStrings == null)
+            {
+                classPathStrings = new String[0];
+            }
+
+            for (int i = 0; i < classPathStrings.length; i++)
+            {
+                if (!classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+                {
+                    extractEmbeddedJar(revisionDir, classPathStrings[i]);
+                }
+            }
+
+        }
+        catch (PrivilegedActionException ex)
+        {
+            throw ((PrivilegedActionException) ex).getException();
+        }
+    }
+
+    /**
+     * This method extracts an embedded JAR file from the bundle's
+     * JAR file.
+     * <p>
+     * Security: This method must be called from within a <tt>doPrivileged()</tt>
+     * block since it accesses the disk.
+     * @param id the identifier of the bundle that owns the embedded JAR file.
+     * @param jarPath the path to the embedded JAR file inside the bundle JAR file.
+    **/
+    private void extractEmbeddedJar(File revisionDir, String jarPath)
+        throws Exception
+    {
+        // Remove leading slash if present.
+        jarPath = (jarPath.charAt(0) == '/') ? jarPath.substring(1) : jarPath;
+        // Get only the JAR file name.
+        String jarName = (jarPath.lastIndexOf('/') >= 0)
+            ? jarPath.substring(jarPath.lastIndexOf('/') + 1) : jarPath;
+
+        // If JAR is already extracted, then don't
+        // re-extract it...
+        File embedFile = new File(
+            revisionDir, EMBEDDED_DIRECTORY + File.separatorChar + jarName);
+        if (!embedFile.exists())
+        {
+            JarFile jarFile = null;
+            InputStream is = null;
+
+            try
+            {
+                jarFile = openBundleJarUnchecked(revisionDir);
+                ZipEntry ze = jarFile.getEntry(jarPath);
+                if (ze == null)
+                {
+                    throw new IOException("No JAR entry: " + jarPath);
+                }
+                is = new BufferedInputStream(jarFile.getInputStream(ze), DefaultBundleCache.BUFSIZE);
+                if (is == null)
+                {
+                    throw new IOException("No input stream: " + jarPath);
+                }
+
+                // Create the file.
+                copy(is, embedFile);
+
+            }
+            finally
+            {
+                if (jarFile != null) jarFile.close();
+                if (is != null) is.close();
+            }
+        }
+    }
+
+    // INCREASES THE REVISION COUNT.    
+    protected void update(InputStream is) throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.UPDATE_ACTION, this, is));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            updateUnchecked(is);
+        }
+    }
+
+    // INCREASES THE REVISION COUNT.    
+    private void updateUnchecked(InputStream is) throws Exception
+    {
+        File revisionDir = null;
+
+        try
+        {
+            // Create the new revision directory.
+            int revision = getRevisionCountUnchecked();
+            revisionDir = new File(
+                m_dir, REVISION_DIRECTORY
+                + getRefreshCount() + "." + revision);
+            if (!revisionDir.mkdir())
+            {
+                throw new IOException("Unable to create revision directory.");
+            }
+
+            // Save the new revision bundle jar file.
+            File file = new File(revisionDir, BUNDLE_JAR_FILE);
+            copy(is, file);
+
+            preprocessBundleJar(revision, revisionDir);
+        }
+        catch (Exception ex)
+        {
+            if ((revisionDir != null) && revisionDir.exists())
+            {
+                try
+                {
+                    deleteDirectoryTree(revisionDir);
+                }
+                catch (Exception ex2)
+                {
+                    // There is very little we can do here.
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Unable to remove partial revision directory.", ex2);
+                }
+            }
+            throw ex;
+        }
+
+        // If everything was successful, then update
+        // the revision count.
+        m_revisionCount++;
+        // Clear the cached revision header, since it is
+        // no longer the current revision.
+        m_currentHeader = null;
+    }
+
+    // DECREASES THE REVISION COUNT.    
+    protected void purge() throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.PURGE_ACTION, this));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            purgeUnchecked();
+        }
+    }
+    
+    // DECREASES THE REVISION COUNT.    
+    private void purgeUnchecked() throws Exception
+    {
+        // Get the current update count.
+        long update = getRefreshCount();
+        // Get the current revision count.
+        int count = getRevisionCountUnchecked();
+
+        File revisionDir = null;
+        for (int i = 0; i < count - 1; i++)
+        {
+            revisionDir = new File(m_dir, REVISION_DIRECTORY + update + "." + i);
+            if (revisionDir.exists())
+            {
+                deleteDirectoryTree(revisionDir);
+            }
+        }
+        // Increment the update count.
+        setRefreshCount(update + 1);
+
+        // Rename the current revision to be the current update.
+        File currentDir = new File(m_dir, REVISION_DIRECTORY + (update + 1) + ".0");
+        revisionDir = new File(m_dir, REVISION_DIRECTORY + update + "." + (count - 1));
+        revisionDir.renameTo(currentDir);
+        
+        // If everything is successful, then set the revision
+        // count to one.
+        m_revisionCount = 1;
+        // Although the cached current header should stay the same
+        // here, clear it for consistency.
+        m_currentHeader = null;
+    }
+
+    protected void remove() throws Exception
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                AccessController.doPrivileged(
+                    new PrivilegedAction(
+                        PrivilegedAction.REMOVE_ACTION, this));
+            }
+            catch (PrivilegedActionException ex)
+            {
+                throw ((PrivilegedActionException) ex).getException();
+            }
+        }
+        else
+        {
+            removeUnchecked();
+        }
+    }
+    
+    private void removeUnchecked() throws Exception
+    {
+        deleteDirectoryTree(m_dir);
+    }
+
+    //
+    // Utility class for performing privileged actions.
+    //
+
+    private static class PrivilegedAction implements PrivilegedExceptionAction
+    {
+        private static final int INITIALIZE_ACTION = 0;
+        private static final int UPDATE_ACTION = 1;
+        private static final int PURGE_ACTION = 2;
+        private static final int REMOVE_ACTION = 3;
+        private static final int GET_REVISION_COUNT_ACTION = 4;
+        private static final int GET_LOCATION_ACTION = 5;
+        private static final int GET_PERSISTENT_STATE_ACTION = 6;
+        private static final int SET_PERSISTENT_STATE_ACTION = 7;
+        private static final int GET_START_LEVEL_ACTION = 8;
+        private static final int SET_START_LEVEL_ACTION = 9;
+        private static final int OPEN_BUNDLE_JAR_ACTION = 10;
+        private static final int CREATE_DATA_DIR_ACTION = 11;
+        private static final int GET_CLASS_PATH_ACTION = 12;
+        private static final int GET_ACTIVATOR_ACTION = 13;
+        private static final int SET_ACTIVATOR_ACTION = 14;
+
+        private int m_action = 0;
+        private DefaultBundleArchive m_archive = null;
+        private InputStream m_isArg = null;
+        private int m_intArg = 0;
+        private File m_fileArg = null;
+        private ClassLoader m_loaderArg = null;
+        private Object m_objArg = null;
+
+        public PrivilegedAction(int action, DefaultBundleArchive archive)
+        {
+            m_action = action;
+            m_archive = archive;
+        }
+
+        public PrivilegedAction(int action, DefaultBundleArchive archive, InputStream isArg)
+        {
+            m_action = action;
+            m_archive = archive;
+            m_isArg = isArg;
+        }
+
+        public PrivilegedAction(int action, DefaultBundleArchive archive, int intArg)
+        {
+            m_action = action;
+            m_archive = archive;
+            m_intArg = intArg;
+        }
+
+        public PrivilegedAction(int action, DefaultBundleArchive archive, File fileArg)
+        {
+            m_action = action;
+            m_archive = archive;
+            m_fileArg = fileArg;
+        }
+
+        public PrivilegedAction(int action, DefaultBundleArchive archive, ClassLoader loaderArg)
+        {
+            m_action = action;
+            m_archive = archive;
+            m_loaderArg = loaderArg;
+        }
+
+        public PrivilegedAction(int action, DefaultBundleArchive archive, Object objArg)
+        {
+            m_action = action;
+            m_archive = archive;
+            m_objArg = objArg;
+        }
+
+        public Object run() throws Exception
+        {
+            switch (m_action)
+            {
+                case INITIALIZE_ACTION:
+                    m_archive.initializeUnchecked(m_isArg);
+                    return null;
+                case UPDATE_ACTION:
+                    m_archive.updateUnchecked(m_isArg);
+                    return null;
+                case PURGE_ACTION:
+                    m_archive.purgeUnchecked();
+                    return null;
+                case REMOVE_ACTION:
+                    m_archive.removeUnchecked();
+                    return null;
+                case GET_REVISION_COUNT_ACTION:
+                    return new Integer(m_archive.getRevisionCountUnchecked());
+                case GET_LOCATION_ACTION:
+                    return m_archive.getLocationUnchecked();
+                case GET_PERSISTENT_STATE_ACTION:
+                    return new Integer(m_archive.getPersistentStateUnchecked());
+                case SET_PERSISTENT_STATE_ACTION:
+                    m_archive.setPersistentStateUnchecked(m_intArg);
+                    return null;
+                case GET_START_LEVEL_ACTION:
+                    return new Integer(m_archive.getStartLevelUnchecked());
+                case SET_START_LEVEL_ACTION:
+                    m_archive.setStartLevelUnchecked(m_intArg);
+                    return null;
+                case OPEN_BUNDLE_JAR_ACTION:
+                    return m_archive.openBundleJarUnchecked(m_fileArg);
+                case CREATE_DATA_DIR_ACTION:
+                    m_archive.createDataDirectoryUnchecked(m_fileArg);
+                    return null;
+                case GET_CLASS_PATH_ACTION:
+                    return m_archive.getClassPathUnchecked(m_intArg);
+                case GET_ACTIVATOR_ACTION:
+                    return m_archive.getActivatorUnchecked(m_loaderArg);
+                case SET_ACTIVATOR_ACTION:
+                    m_archive.setActivatorUnchecked(m_objArg);
+                    return null;
+            }
+
+            throw new IllegalArgumentException("Invalid action specified.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/DefaultBundleCache.java b/src/org/apache/osgi/framework/cache/DefaultBundleCache.java
new file mode 100644
index 0000000..eb6b0b7
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/DefaultBundleCache.java
@@ -0,0 +1,262 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.cache;
+
+import java.io.*;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.framework.util.PropertyResolver;
+
+/**
+ * <p>
+ * This class, combined with <tt>DefaultBundleArchive</tt>, implements the
+ * default file system-based bundle cache for Felix. It is possible to
+ * configure the default behavior of this class by passing properties into
+ * Felix constructor. The configuration properties for this class are:
+ * </p>
+ * <ul>
+ *   <li><tt>felix.cache.bufsize</tt> - Sets the buffer size to be used by
+ *       the cache; the default value is 4096. The integer
+ *       value of this string provides control over the size of the
+ *       internal buffer of the disk cache for performance reasons.
+ *   </li>
+ *   <li><tt>felix.cache.dir</tt> - Sets the directory to be used by the
+ *       cache as its cache directory. The cache directory is where all
+ *       profile directories are stored and a profile directory is where a
+ *       set of installed bundles are stored. By default, the cache
+ *       directory is <tt>.felix</tt> in the user's home directory. If
+ *       this property is specified, then its value will be used as the cache
+ *       directory instead of <tt>.felix</tt>. This directory will be created
+ *       if it does not exist.
+ *   </li>
+ *   <li><tt>felix.cache.profile</tt> - Sets the profile name that will be
+ *       used to create a profile directory inside of the cache directory.
+ *       The created directory will contained all installed bundles associated
+ *       with the profile.
+ *   </li>
+ *   <li><tt>felix.cache.profiledir</tt> - Sets the directory to use as the
+ *       profile directory for the bundle cache; by default the profile
+ *       name is used to create a directory in the <tt>.felix</tt> cache
+ *       directory. If this property is specified, then the cache directory
+ *       and profile name properties are ignored. The specified value of this
+ *       property is used directly as the directory to contain all cached
+ *       bundles. If this property is set, it is not necessary to set the
+ *       cache directory or profile name properties. This directory will be
+ *       created if it does not exist.
+ *   </li>
+ * </ul>
+ * <p>
+ * For specific information on how to configure Felix using system properties,
+ * refer to the Felix usage documentation.
+ * </p>
+ * @see org.apache.osgi.framework.util.DefaultBundleArchive
+**/
+public class DefaultBundleCache implements BundleCache
+{
+    public static final String CACHE_BUFSIZE_PROP = "felix.cache.bufsize";
+    public static final String CACHE_DIR_PROP = "felix.cache.dir";
+    public static final String CACHE_PROFILE_DIR_PROP = "felix.cache.profiledir";
+    public static final String CACHE_PROFILE_PROP = "felix.cache.profile";
+
+    protected static transient int BUFSIZE = 4096;
+    protected static transient final String CACHE_DIR_NAME = ".felix";
+    protected static transient final String BUNDLE_DIR_PREFIX = "bundle";
+
+    private PropertyResolver m_cfg = null;
+    private LogWrapper m_logger = null;
+    private File m_profileDir = null;
+    private BundleArchive[] m_archives = null;
+
+    public DefaultBundleCache()
+    {
+    }
+
+    public void initialize(PropertyResolver cfg, LogWrapper logger) throws Exception
+    {
+        // Save Properties reference.
+        m_cfg = cfg;
+        // Save LogService reference.
+        m_logger = logger;
+
+        // Get buffer size value.
+        try
+        {
+            String sBufSize = m_cfg.get(CACHE_BUFSIZE_PROP);
+            if (sBufSize != null)
+            {
+                BUFSIZE = Integer.parseInt(sBufSize);
+            }
+        }
+        catch (NumberFormatException ne)
+        {
+            // Use the default value.
+        }
+
+        // See if the profile directory is specified.
+        String profileDirStr = m_cfg.get(CACHE_PROFILE_DIR_PROP);
+        if (profileDirStr != null)
+        {
+            m_profileDir = new File(profileDirStr);
+        }
+        else
+        {
+            // Since no profile directory was specified, then the profile
+            // directory will be a directory in the cache directory named
+            // after the profile.
+
+            // First, determine the location of the cache directory; it
+            // can either be specified or in the default location.
+            String cacheDirStr = m_cfg.get(CACHE_DIR_PROP);
+            if (cacheDirStr == null)
+            {
+                // Since no cache directory was specified, put it
+                // ".felix" in the user's home by default.
+                cacheDirStr = System.getProperty("user.home");
+                cacheDirStr = cacheDirStr.endsWith(File.separator)
+                    ? cacheDirStr : cacheDirStr + File.separator;
+                cacheDirStr = cacheDirStr + CACHE_DIR_NAME;
+            }
+
+            // Now, get the profile name.
+            String profileName = m_cfg.get(CACHE_PROFILE_PROP);
+            if (profileName == null)
+            {
+                throw new IllegalArgumentException(
+                    "No profile name or directory has been specified.");
+            }
+            // Profile name cannot contain the File.separator char.
+            else if (profileName.indexOf(File.separator) >= 0)
+            {
+                throw new IllegalArgumentException(
+                    "The profile name cannot contain the file separator character.");
+            }
+
+            m_profileDir = new File(cacheDirStr, profileName);
+        }
+
+        // Create profile directory.
+        if (!m_profileDir.exists())
+        {
+            if (!m_profileDir.mkdirs())
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "Unable to create directory: " + m_profileDir);
+                throw new RuntimeException("Unable to create profile directory.");
+            }
+        }
+
+        // Create the existing bundle archives in the profile directory,
+        // if any exist.
+        File[] children = m_profileDir.listFiles();
+        int count = 0;
+        for (int i = 0; (children != null) && (i < children.length); i++)
+        {
+            // Count the legitimate bundle directories.
+            if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX))
+            {
+                count++;
+            }
+        }
+        m_archives = new BundleArchive[count];
+        count = 0;
+        for (int i = 0; (children != null) && (i < children.length); i++)
+        {
+            // Ignore directories that aren't bundle directories.
+            if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX))
+            {
+                String id = children[i].getName().substring(BUNDLE_DIR_PREFIX.length());
+                m_archives[count] = new DefaultBundleArchive(
+                    m_logger, children[i], Long.parseLong(id));
+                count++;
+            }
+        }
+    }
+
+    public BundleArchive[] getArchives()
+        throws Exception
+    {
+        return m_archives;
+    }
+
+    public BundleArchive getArchive(long id)
+        throws Exception
+    {
+        for (int i = 0; i < m_archives.length; i++)
+        {
+            if (m_archives[i].getId() == id)
+            {
+                return m_archives[i];
+            }
+        }
+        return null;
+    }
+
+    public BundleArchive create(long id, String location, InputStream is)
+        throws Exception
+    {
+        // Define new bundle's directory.
+        File bundleDir = new File(m_profileDir, "bundle" + id);
+
+        try
+        {
+            // Buffer the input stream.
+            is = new BufferedInputStream(is, DefaultBundleCache.BUFSIZE);
+            // Create an archive instance for the new bundle.
+            BundleArchive ba = new DefaultBundleArchive(
+                m_logger, bundleDir, id, location, is);
+            // Add the archive instance to the list of bundle archives.
+            BundleArchive[] bas = new BundleArchive[m_archives.length + 1];
+            System.arraycopy(m_archives, 0, bas, 0, m_archives.length);
+            bas[m_archives.length] = ba;
+            m_archives = bas;
+            return ba;
+        }
+        finally
+        {
+            if (is != null) is.close();
+        }
+    }
+
+    public void update(BundleArchive ba, InputStream is)
+        throws Exception
+    {
+        try
+        {
+            // Buffer the input stream.
+            is = new BufferedInputStream(is, DefaultBundleCache.BUFSIZE);
+            // Do the update.
+            ((DefaultBundleArchive) ba).update(is);
+        }
+        finally
+        {
+            if (is != null) is.close();
+        }
+    }
+
+    public void purge(BundleArchive ba)
+        throws Exception
+    {
+        ((DefaultBundleArchive) ba).purge();
+    }
+
+    public void remove(BundleArchive ba)
+        throws Exception
+    {
+        ((DefaultBundleArchive) ba).remove();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ext/FelixBundleContext.java b/src/org/apache/osgi/framework/ext/FelixBundleContext.java
new file mode 100644
index 0000000..e1d6cc9
--- /dev/null
+++ b/src/org/apache/osgi/framework/ext/FelixBundleContext.java
@@ -0,0 +1,28 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.ext;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+
+public interface FelixBundleContext extends BundleContext
+{
+    public void addImportPackage() throws BundleException;
+    public void removeImportPackage() throws BundleException;
+    public void addExportPackage() throws BundleException;
+    public void removeExportPackage() throws BundleException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/Artifact.java b/src/org/apache/osgi/framework/installer/Artifact.java
new file mode 100644
index 0000000..42f8227
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Artifact.java
@@ -0,0 +1,30 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+public interface Artifact
+{
+    public StringProperty getSourceName();
+    public StringProperty getDestinationDirectory();
+    public InputStream getInputStream(Status status) throws IOException;
+    public boolean localize();
+    public boolean process(Status status, Map propMap);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/BooleanProperty.java b/src/org/apache/osgi/framework/installer/BooleanProperty.java
new file mode 100644
index 0000000..f470bce
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/BooleanProperty.java
@@ -0,0 +1,23 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+public interface BooleanProperty extends Property
+{
+    public boolean getBooleanValue();
+    public void setBooleanValue(boolean b);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/Install.java b/src/org/apache/osgi/framework/installer/Install.java
new file mode 100644
index 0000000..7e6eae6
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Install.java
@@ -0,0 +1,431 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+
+import org.apache.osgi.framework.installer.artifact.*;
+import org.apache.osgi.framework.installer.editor.BooleanEditor;
+import org.apache.osgi.framework.installer.editor.FileEditor;
+import org.apache.osgi.framework.installer.property.*;
+import org.apache.osgi.framework.util.FelixConstants;
+
+public class Install extends JFrame
+{
+    private static transient final String PROPERTY_FILE = "property.xml";
+    private static transient final String ARTIFACT_FILE = "artifact.xml";
+
+    public static transient final String JAVA_DIR = "Java directory";
+    public static transient final String INSTALL_DIR = "Install directory";
+
+    private PropertyPanel m_propPanel = null;
+    private JButton m_okayButton = null;
+    private JButton m_cancelButton = null;
+    private JLabel m_statusLabel = null;
+
+    private java.util.List m_propList = null;
+    private java.util.List m_artifactList = null;
+
+    public Install()
+        throws Exception
+    {
+        super("Install");
+
+        // Load properties before resources, because resources
+        // refer to properties.
+        m_propList = loadPropertyList();
+        m_artifactList = loadArtifactList();
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(
+            m_propPanel = new PropertyPanel(m_propList), BorderLayout.CENTER);
+        getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
+        pack();
+        setResizable(true);
+        centerWindow(this);
+
+        // Make window closeable.
+        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+        addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent event)
+            {
+                doCancel();
+            }
+        });
+    }
+
+    public java.util.List loadPropertyList()
+    {
+        String installDir = System.getProperty("user.home");
+        if (!installDir.endsWith(File.separator))
+        {
+            installDir = installDir + File.separator;
+        }
+
+        Property prop = null;
+
+        // Eventually these should be read from a file.
+        java.util.List list = new ArrayList();
+
+        // Add the shell choice property.
+        prop = new BooleanPropertyImpl("Shell", true);
+        prop.setEditor(new BooleanEditor((BooleanProperty) prop, "Text", "GUI"));
+        list.add(prop);
+
+        // Add the java directory property.
+        prop = new StringPropertyImpl(JAVA_DIR, System.getProperty("java.home"));
+        prop.setEditor(new FileEditor((StringProperty) prop, true));
+        list.add(prop);
+
+        // Add the installation directory property.
+        prop = new StringPropertyImpl(INSTALL_DIR, installDir + "Felix");
+        prop.setEditor(new FileEditor((StringProperty) prop, true));
+        list.add(prop);
+
+        // Add the documentation URL property.
+        prop = new BooleanStringPropertyImpl(
+            "User documentation",
+            true,
+            "http://download.forge.objectweb.org/oscar/oscar-doc-"
+            + FelixConstants.FELIX_VERSION_VALUE + ".jar");
+        list.add(prop);
+
+        // Add the documentation URL property.
+        prop = new BooleanStringPropertyImpl(
+            "API documentation",
+            true,
+            "http://download.forge.objectweb.org/oscar/oscar-api-"
+            + FelixConstants.FELIX_VERSION_VALUE + ".jar");
+        list.add(prop);
+
+        return list;
+    }
+
+    public java.util.List loadArtifactList() throws Exception
+    {
+        // Eventually I will changed these to be read from a file.
+        java.util.List list = new ArrayList();
+        list.add(
+            new ArtifactHolder(
+                (BooleanProperty) getProperty("User documentation"),
+                new URLJarArtifact(
+                    (StringProperty) getProperty("User documentation"))));
+        list.add(
+            new ArtifactHolder(
+                (BooleanProperty) getProperty("API documentation"),
+                new URLJarArtifact(
+                    (StringProperty) getProperty("API documentation"))));
+        list.add(
+            new ArtifactHolder(
+                new ResourceJarArtifact(
+                    new StringPropertyImpl("sourceName", "package.jar"))));
+        list.add(
+            new ArtifactHolder(
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "src.jar"))));
+        list.add(
+            new ArtifactHolder(
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "LICENSE.txt"))));
+        list.add(
+            new ArtifactHolder(
+                (BooleanProperty) getProperty("Shell"),
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "config.properties.text"),
+                    new StringPropertyImpl("destName", "config.properties"),
+                    new StringPropertyImpl("destDir", "lib"))));
+        list.add(
+            new ArtifactHolder(
+                new NotBooleanPropertyImpl((BooleanProperty) getProperty("Shell")),
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "config.properties.gui"),
+                    new StringPropertyImpl("destName", "config.properties"),
+                    new StringPropertyImpl("destDir", "lib"))));
+        list.add(
+            new ArtifactHolder(
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "example.policy"))));
+        list.add(
+            new ArtifactHolder(
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "felix.bat"),
+                    new StringPropertyImpl("destName" , "felix.bat"),
+                    new StringPropertyImpl("destDir", ""),
+                    true)));
+        list.add(
+            new ArtifactHolder(
+                new ResourceFileArtifact(
+                    new StringPropertyImpl("sourceName", "felix.sh"),
+                    new StringPropertyImpl("destName" , "felix.sh"),
+                    new StringPropertyImpl("destDir", ""),
+                    true)));
+
+        return list;
+    }
+
+    private Property getProperty(String name)
+    {
+        for (int i = 0; i < m_propList.size(); i++)
+        {
+            Property prop = (Property) m_propList.get(i);
+            if (prop.getName().equals(name))
+            {
+                return prop;
+            }
+        }
+        return null;
+    }
+
+    protected void doOkay()
+    {
+        m_propPanel.setEnabled(false);
+        m_okayButton.setEnabled(false);
+        m_cancelButton.setEnabled(false);
+        new Thread(new InstallRunnable()).start();
+    }
+
+    protected void doCancel()
+    {
+        System.exit(0);
+    }
+
+    protected JPanel createButtonPanel()
+    {
+        JPanel buttonPanel = new JPanel();
+        buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
+
+        // Create and set layout.
+        GridBagLayout grid = new GridBagLayout();
+        GridBagConstraints c = new GridBagConstraints();
+
+        buttonPanel.setLayout(grid);
+
+        // Create labels and fields.
+        c.insets = new Insets(2, 2, 2, 2);
+
+        // Okay button.
+        c.gridx = 0;
+        c.gridy = 0;
+        c.gridwidth = 1;
+        c.gridheight = 1;
+        c.anchor = GridBagConstraints.EAST;
+        grid.setConstraints(m_okayButton = new JButton("OK"), c);
+        buttonPanel.add(m_okayButton);
+        m_okayButton.setDefaultCapable(true);
+        getRootPane().setDefaultButton(m_okayButton);
+
+        // Cancel button.
+        c.gridx = 1;
+        c.gridy = 0;
+        c.gridwidth = 1;
+        c.gridheight = 1;
+        c.anchor = GridBagConstraints.WEST;
+        grid.setConstraints(m_cancelButton = new JButton("Cancel"), c);
+        buttonPanel.add(m_cancelButton);
+
+        // Add action listeners.
+        m_okayButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                doOkay();
+            }
+        });
+
+        m_cancelButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                doCancel();
+            }
+        });
+
+        // Status label.
+        m_statusLabel = new JLabel("Felix installation");
+        m_statusLabel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
+
+        // Complete panel.
+        JPanel panel = new JPanel(new BorderLayout());
+        panel.add(buttonPanel, BorderLayout.CENTER);
+        panel.add(m_statusLabel, BorderLayout.SOUTH);
+        return panel;
+    }
+
+    public static void centerWindow(Component window)
+    {
+        Toolkit toolkit = Toolkit.getDefaultToolkit();
+        Dimension dim = toolkit.getScreenSize();
+        int screenWidth = dim.width;
+        int screenHeight = dim.height;
+        int x = (screenWidth - window.getSize().width) / 2;
+        int y = (screenHeight - window.getSize().height) / 2;
+        window.setLocation(x, y);
+    }
+
+    public static void main(String[] argv) throws Exception
+    {
+        String msg = "<html>"
+            + "<center><h1>Felix " + FelixConstants.FELIX_VERSION_VALUE + "</h1></center>"
+            + "You can download example bundles at the Felix shell prompt by<br>"
+            + "using the <b><tt>obr</tt></b> command to access the OSGi Bundle Repository;<br>"
+            + "type <b><tt>obr help</tt></b> at the Felix shell prompt for details."
+            + "</html>";
+        JLabel label = new JLabel(msg);
+        label.setFont(new Font("SansSerif", Font.PLAIN, 11));
+        final JDialog dlg = new JDialog((Frame) null, "Felix Install", true);
+        dlg.getContentPane().setLayout(new BorderLayout(10, 10));
+        dlg.getContentPane().add(label, BorderLayout.CENTER);
+        JPanel panel = new JPanel();
+        JButton button = new JButton("OK");
+        button.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                dlg.hide();
+            }
+        });
+        panel.add(button);
+        dlg.getContentPane().add(panel, BorderLayout.SOUTH);
+        // For spacing purposes...
+        dlg.getContentPane().add(new JPanel(), BorderLayout.NORTH);
+        dlg.getContentPane().add(new JPanel(), BorderLayout.EAST);
+        dlg.getContentPane().add(new JPanel(), BorderLayout.WEST);
+        dlg.pack();
+        centerWindow(dlg);
+        dlg.show();
+
+        Install obj = new Install();
+        obj.setVisible(true);
+    }
+
+    class InstallRunnable implements Runnable
+    {
+        public void run()
+        {
+            Map propMap = new HashMap();
+            for (int i = 0; i < m_propList.size(); i++)
+            {
+                Property prop = (Property) m_propList.get(i);
+                propMap.put(prop.getName(), prop);
+            }
+
+            String installDir = ((StringProperty) propMap.get(INSTALL_DIR)).getStringValue();
+
+            // Make sure the install directory ends with separator char.
+            if (!installDir.endsWith(File.separator))
+            {
+                installDir = installDir + File.separator;
+            }
+
+            // Make sure the install directory exists and
+            // that is actually a directory.
+            File file = new File(installDir);
+            if (!file.exists())
+            {
+                if (!file.mkdirs())
+                {
+                    JOptionPane.showMessageDialog(Install.this,
+                        "Unable to create install directory.",
+                        "Error", JOptionPane.ERROR_MESSAGE);
+                    System.exit(-1);
+                }
+            }
+            else if (!file.isDirectory())
+            {
+                JOptionPane.showMessageDialog(Install.this,
+                    "The selected install location is not a directory.",
+                    "Error", JOptionPane.ERROR_MESSAGE);
+                System.exit(-1);
+            }
+
+            // Status updater runnable.
+            StatusRunnable sr = new StatusRunnable();
+
+            // Loop through and process resources.
+            for (int i = 0; i < m_artifactList.size(); i++)
+            {
+                ArtifactHolder ah = (ArtifactHolder) m_artifactList.get(i);
+                if (ah.isIncluded())
+                {
+                    if (!ah.getArtifact().process(sr, propMap))
+                    {
+                        JOptionPane.showMessageDialog(Install.this,
+                            "An error occurred while processing the resources.",
+                            "Error", JOptionPane.ERROR_MESSAGE);
+                        System.exit(-1);
+                    }
+                }
+            }
+
+            System.exit(0);
+        }
+    }
+
+    class StatusRunnable implements Status, Runnable
+    {
+        private String text = null;
+
+        public void setText(String s)
+        {
+            text = s;
+            try {
+                SwingUtilities.invokeAndWait(this);
+            } catch (Exception ex) {
+                // Ignore.
+            }
+        }
+
+        public void run()
+        {
+            m_statusLabel.setText(text);
+        }
+    }
+
+    // Re-usable static member for ResourceHolder inner class.
+    private static BooleanProperty m_trueProp =
+        new BooleanPropertyImpl("mandatory", true);
+
+    class ArtifactHolder
+    {
+        private BooleanProperty m_isIncluded = null;
+        private Artifact m_artifact = null;
+        
+        public ArtifactHolder(Artifact artifact)
+        {
+            this(m_trueProp, artifact);
+        }
+        
+        public ArtifactHolder(BooleanProperty isIncluded, Artifact artifact)
+        {
+            m_isIncluded = isIncluded;
+            m_artifact = artifact;
+        }
+        
+        public boolean isIncluded()
+        {
+            return m_isIncluded.getBooleanValue();
+        }
+        
+        public Artifact getArtifact()
+        {
+            return m_artifact;
+        }
+    }
+}
diff --git a/src/org/apache/osgi/framework/installer/Property.java b/src/org/apache/osgi/framework/installer/Property.java
new file mode 100644
index 0000000..7c52c46
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Property.java
@@ -0,0 +1,27 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+import javax.swing.JComponent;
+
+public interface Property
+{
+    public String getName();
+    public JComponent getEditor();
+    public void setEditor(JComponent comp);
+    public String toString();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/PropertyPanel.java b/src/org/apache/osgi/framework/installer/PropertyPanel.java
new file mode 100644
index 0000000..46dd4a1
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/PropertyPanel.java
@@ -0,0 +1,87 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+
+public class PropertyPanel extends JPanel
+{
+    private List m_propList = null;
+    private Map m_propToCompMap = null;
+
+    public PropertyPanel(List paramList)
+    {
+        super();
+        m_propList = paramList;
+        m_propToCompMap = new HashMap();
+        layoutComponents();
+    }
+
+    public void setEnabled(boolean b)
+    {
+        for (int i = 0; i < m_propList.size(); i++)
+        {
+            Property prop = (Property) m_propList.get(i);
+            JComponent comp = (JComponent) m_propToCompMap.get(prop.getName());
+            comp.setEnabled(b);
+        }
+    }
+
+    public List getProperties()
+    {
+        return m_propList;
+    }
+
+    protected void layoutComponents()
+    {
+        // Create the field panel for entering query variables.
+        GridBagLayout grid = new GridBagLayout();
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.insets = new Insets(2, 2, 2, 2);
+        setLayout(grid);
+
+        for (int i = 0; i < m_propList.size(); i++)
+        {
+            Property prop = (Property) m_propList.get(i);
+            JLabel label = null;
+            JComponent component = null;
+
+            // Add label.
+            gbc.gridx = 0;
+            gbc.gridy = i;
+            gbc.gridheight = 1;
+            gbc.gridwidth = 1;
+            gbc.anchor = GridBagConstraints.EAST;
+            grid.setConstraints(label = new JLabel(prop.getName()), gbc);
+            add(label);
+
+            gbc.gridx = 1;
+            gbc.gridy = i;
+            gbc.gridheight = 1;
+            gbc.gridwidth = 3;
+            gbc.anchor = GridBagConstraints.WEST;
+            grid.setConstraints(component = prop.getEditor(), gbc);
+            add(component);
+
+            m_propToCompMap.put(prop.getName(), component);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/Status.java b/src/org/apache/osgi/framework/installer/Status.java
new file mode 100644
index 0000000..7070422
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Status.java
@@ -0,0 +1,22 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+public interface Status
+{
+    public void setText(String s);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/StringProperty.java b/src/org/apache/osgi/framework/installer/StringProperty.java
new file mode 100644
index 0000000..2597d47
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/StringProperty.java
@@ -0,0 +1,23 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer;
+
+public interface StringProperty extends Property
+{
+    public String getStringValue();
+    public void setStringValue(String s);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/AbstractArtifact.java b/src/org/apache/osgi/framework/installer/artifact/AbstractArtifact.java
new file mode 100644
index 0000000..c3ee33b
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/AbstractArtifact.java
@@ -0,0 +1,230 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.util.Map;
+
+import org.apache.osgi.framework.installer.*;
+import org.apache.osgi.framework.installer.property.StringPropertyImpl;
+
+public abstract class AbstractArtifact implements Artifact
+{
+    private StringProperty m_sourceName = null;
+    private StringProperty m_destDir = null;
+    private boolean m_localize = false;
+
+    // This following shared buffer assumes that there is
+    // no concurrency when processing resources.
+    protected static byte[] s_buffer = new byte[2048];
+
+    public AbstractArtifact(
+        StringProperty sourceName, StringProperty destDir, boolean localize)
+    {
+        if (destDir == null)
+        {
+            destDir = new StringPropertyImpl("empty", "");
+        }
+        m_sourceName = sourceName;
+        m_destDir = destDir;
+        m_localize = localize;
+    }
+
+    public StringProperty getSourceName()
+    {
+        return m_sourceName;
+    }
+
+    public StringProperty getDestinationDirectory()
+    {
+        return m_destDir;
+    }
+
+    public boolean localize()
+    {
+        return m_localize;
+    }
+
+    protected static void copy(
+        InputStream is, String installDir, String destName, String destDir)
+        throws IOException
+    {
+        if (destDir == null)
+        {
+            destDir = "";
+        }
+
+        // Make sure the target directory exists and
+        // that is actually a directory.
+        File targetDir = new File(installDir, destDir);
+        if (!targetDir.exists())
+        {
+            if (!targetDir.mkdirs())
+            {
+                throw new IOException("Unable to create target directory: "
+                    + targetDir);
+            }
+        }
+        else if (!targetDir.isDirectory())
+        {
+            throw new IOException("Target is not a directory: "
+                + targetDir);
+        }
+
+        BufferedOutputStream bos = new BufferedOutputStream(
+            new FileOutputStream(new File(targetDir, destName)));
+        int count = 0;
+        while ((count = is.read(s_buffer)) > 0)
+        {
+            bos.write(s_buffer, 0, count);
+        }
+        bos.close();
+    }
+
+    protected static void copyAndLocalize(
+        InputStream is, String installDir, String destName,
+        String destDir, Map propMap)
+        throws IOException
+    {
+        if (destDir == null)
+        {
+            destDir = "";
+        }
+
+        // Make sure the target directory exists and
+        // that is actually a directory.
+        File targetDir = new File(installDir, destDir);
+        if (!targetDir.exists())
+        {
+            if (!targetDir.mkdirs())
+            {
+                throw new IOException("Unable to create target directory: "
+                    + targetDir);
+            }
+        }
+        else if (!targetDir.isDirectory())
+        {
+            throw new IOException("Target is not a directory: "
+                + targetDir);
+        }
+
+        BufferedOutputStream bos = new BufferedOutputStream(
+            new FileOutputStream(new File(targetDir, destName)));
+        int i = 0;
+        while ((i = is.read()) > 0)
+        {
+            // Parameters start with "%%", so check to see if
+            // we have a parameter.
+            if ((char)i == '%')
+            {
+                // One of three possibilities, we have a parameter,
+                // we have an end of file, or we don't have a parameter.
+                int i2 = is.read();
+                if ((char) i2 == '%')
+                {
+                    Object obj = readParameter(is);
+
+                    // If the byte sequence was not a parameter afterall,
+                    // then a byte array is returned, otherwise a string
+                    // containing the parameter m_name is returned.
+                    if (obj instanceof byte[])
+                    {
+                        bos.write(i);
+                        bos.write(i2);
+                        bos.write((byte[]) obj);
+                    }
+                    else
+                    {
+                        Property prop = (Property) propMap.get(obj);
+                        String value = (prop == null) ? "" : prop.toString();
+                        bos.write(value.getBytes());
+                    }
+                }
+                else if (i2 == -1)
+                {
+                    bos.write(i);
+                }
+                else
+                {
+                    bos.write(i);
+                    bos.write(i2);
+                }
+            }
+            else
+            {
+                bos.write(i);
+            }
+        }
+        bos.close();
+    }
+
+    protected static Object readParameter(InputStream is)
+        throws IOException
+    {
+        int count = 0;
+        int i = 0;
+        while ((count < s_buffer.length) && ((i = is.read()) > 0))
+        {
+            if ((char) i == '%')
+            {
+                // One of three possibilities, we have the end of
+                // the parameter, we have an end of file, or we
+                // don't have the parameter end.
+                int i2 = is.read();
+                if ((char) i2 == '%')
+                {
+                    return new String(s_buffer, 0, count);
+                }
+                else if (i2 == -1)
+                {
+                    s_buffer[count] = (byte) i;
+                    byte[] b = new byte[count];
+                    for (int j = 0; j < count; j++)
+                        b[j] = s_buffer[j];
+                    return b;
+                }
+                else
+                {
+                    s_buffer[count++] = (byte) i;
+                    s_buffer[count++] = (byte) i2;
+                }
+            }
+            else
+            {
+                s_buffer[count++] = (byte) i;
+            }
+        }
+
+        byte[] b = new byte[count - 1];
+        for (int j = 0; j < (count - 1); j++)
+            b[j] = s_buffer[j];
+
+        return b;
+    }
+
+    public static String getPath(String s, char separator)
+    {
+        return (s.lastIndexOf(separator) < 0)
+            ? "" : s.substring(0, s.lastIndexOf(separator));
+    }
+
+    public static String getPathHead(String s, char separator)
+    {
+        return (s.lastIndexOf(separator) < 0)
+            ? s : s.substring(s.lastIndexOf(separator) + 1);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/AbstractFileArtifact.java b/src/org/apache/osgi/framework/installer/artifact/AbstractFileArtifact.java
new file mode 100644
index 0000000..4b46338
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/AbstractFileArtifact.java
@@ -0,0 +1,103 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import org.apache.osgi.framework.installer.*;
+
+public abstract class AbstractFileArtifact extends AbstractArtifact
+{
+    private StringProperty m_destName = null;
+
+    public AbstractFileArtifact(StringProperty sourceName)
+    {
+        this(sourceName, sourceName);
+    }
+
+    public AbstractFileArtifact(StringProperty sourceName, StringProperty destName)
+    {
+        this(sourceName, destName, null);
+    }
+
+    public AbstractFileArtifact(
+        StringProperty sourceName, StringProperty destName, StringProperty destDir)
+    {
+        this(sourceName, destName, destDir, false);
+    }
+
+    public AbstractFileArtifact(
+        StringProperty sourceName, StringProperty destName,
+        StringProperty destDir, boolean localize)
+    {
+        super(sourceName, destDir, localize);
+        m_destName = destName;
+    }
+
+    public StringProperty getDestinationName()
+    {
+        return m_destName;
+    }
+
+    public boolean process(Status status, Map propMap)
+    {
+        String installDir =
+            ((StringProperty) propMap.get(Install.INSTALL_DIR)).getStringValue();
+
+        try
+        {
+            InputStream is = getInputStream(status);
+
+            if (is == null)
+            {
+                return true;
+            }
+
+            if (localize())
+            {
+                status.setText("Copying and configuring "
+                    + getSourceName().getStringValue());
+                copyAndLocalize(
+                    is,
+                    installDir,
+                    getDestinationName().getStringValue(),
+                    getDestinationDirectory().getStringValue(),
+                    propMap);
+            }
+            else
+            {
+                status.setText("Copying " + getSourceName().getStringValue());
+                copy(
+                    is,
+                    installDir,
+                    getDestinationName().getStringValue(),
+                    getDestinationDirectory().getStringValue());
+            }
+
+            is.close();
+
+        }
+        catch (Exception ex)
+        {
+            System.err.println(ex);
+            return false;
+        }
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/AbstractJarArtifact.java b/src/org/apache/osgi/framework/installer/artifact/AbstractJarArtifact.java
new file mode 100644
index 0000000..fef3316
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/AbstractJarArtifact.java
@@ -0,0 +1,124 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.osgi.framework.installer.*;
+
+public abstract class AbstractJarArtifact extends AbstractArtifact
+{
+    public AbstractJarArtifact(StringProperty sourceName)
+    {
+        this(sourceName, null);
+    }
+
+    public AbstractJarArtifact(StringProperty sourceName, StringProperty destDir)
+    {
+        this(sourceName, destDir, false);
+    }
+
+    public AbstractJarArtifact(
+        StringProperty sourceName, StringProperty destDir, boolean localize)
+    {
+        super(sourceName, destDir, localize);
+    }
+
+    public boolean process(Status status, Map propMap)
+    {
+        try
+        {
+            InputStream is = getInputStream(status);
+
+            if (is == null)
+            {
+                return true;
+            }
+
+            JarInputStream jis = new JarInputStream(is);
+            status.setText("Extracting...");
+            unjar(jis, propMap);
+            jis.close();
+        }
+        catch (Exception ex)
+        {
+            System.err.println(this);
+            System.err.println(ex);
+            return false;
+        }
+
+        return true;
+    }
+
+    protected void unjar(JarInputStream jis, Map propMap)
+        throws IOException
+    {
+        String installDir =
+            ((StringProperty) propMap.get(Install.INSTALL_DIR)).getStringValue();
+
+        // Loop through JAR entries.
+        for (JarEntry je = jis.getNextJarEntry();
+             je != null;
+             je = jis.getNextJarEntry())
+        {
+            if (je.getName().startsWith("/"))
+            {
+                throw new IOException("JAR resource cannot contain absolute paths.");
+            }
+
+            File target =
+                new File(installDir, getDestinationDirectory().getStringValue());
+            target = new File(target, je.getName());
+
+            // Check to see if the JAR entry is a directory.
+            if (je.isDirectory())
+            {
+                if (!target.exists())
+                {
+                    if (!target.mkdirs())
+                    {
+                        throw new IOException("Unable to create target directory: "
+                            + target);
+                    }
+                }
+                // Just continue since directories do not have content to copy.
+                continue;
+            }
+
+            int lastIndex = je.getName().lastIndexOf('/');
+            String name = (lastIndex >= 0) ?
+                je.getName().substring(lastIndex + 1) : je.getName();
+            String destination = (lastIndex >= 0) ?
+                je.getName().substring(0, lastIndex) : "";
+
+            // JAR files use '/', so convert it to platform separator.
+            destination = destination.replace('/', File.separatorChar);
+
+            if (localize())
+            {
+                copyAndLocalize(jis, installDir, name, destination, propMap);
+            }
+            else
+            {
+                copy(jis, installDir, name, destination);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/ResourceFileArtifact.java b/src/org/apache/osgi/framework/installer/artifact/ResourceFileArtifact.java
new file mode 100644
index 0000000..599df964
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/ResourceFileArtifact.java
@@ -0,0 +1,61 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.resource.ResourceLoader;
+
+public class ResourceFileArtifact extends AbstractFileArtifact
+{
+    public ResourceFileArtifact(StringProperty sourceName)
+    {
+        this(sourceName, sourceName);
+    }
+
+    public ResourceFileArtifact(StringProperty sourceName, StringProperty destName)
+    {
+        this(sourceName, destName, null);
+    }
+
+    public ResourceFileArtifact(
+        StringProperty sourceName, StringProperty destName, StringProperty destDir)
+    {
+        this(sourceName, destName, destDir, false);
+    }
+
+    public ResourceFileArtifact(
+        StringProperty sourceName, StringProperty destName,
+        StringProperty destDir, boolean localize)
+    {
+        super(sourceName, destName, destDir, localize);
+    }
+
+    public InputStream getInputStream(Status status)
+        throws IOException
+    {
+        return ResourceLoader.getResourceAsStream(getSourceName().getStringValue());
+    }
+
+    public String toString()
+    {
+        return "RESOURCE FILE: " + getSourceName().getStringValue();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/ResourceJarArtifact.java b/src/org/apache/osgi/framework/installer/artifact/ResourceJarArtifact.java
new file mode 100644
index 0000000..59faea3
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/ResourceJarArtifact.java
@@ -0,0 +1,54 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.resource.ResourceLoader;
+
+public class ResourceJarArtifact extends AbstractJarArtifact
+{
+    public ResourceJarArtifact(StringProperty sourceName)
+    {
+        this(sourceName, null);
+    }
+
+    public ResourceJarArtifact(StringProperty sourceName, StringProperty destDir)
+    {
+        this(sourceName, destDir, false);
+    }
+
+    public ResourceJarArtifact(
+        StringProperty sourceName, StringProperty destDir, boolean localize)
+    {
+        super(sourceName, destDir, localize);
+    }
+
+    public InputStream getInputStream(Status status)
+        throws IOException
+    {
+        return ResourceLoader.getResourceAsStream(getSourceName().getStringValue());
+    }
+
+    public String toString()
+    {
+        return "RESOURCE JAR: " + getSourceName().getStringValue();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/URLFileArtifact.java b/src/org/apache/osgi/framework/installer/artifact/URLFileArtifact.java
new file mode 100644
index 0000000..b3729f0
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/URLFileArtifact.java
@@ -0,0 +1,90 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class URLFileArtifact extends AbstractFileArtifact
+{
+    public URLFileArtifact(StringProperty sourceName, StringProperty destName)
+    {
+        this(sourceName, destName, null);
+    }
+
+    public URLFileArtifact(
+        StringProperty sourceName, StringProperty destName, StringProperty destDir)
+    {
+        this(sourceName, destName, destDir, false);
+    }
+
+    public URLFileArtifact(
+        StringProperty sourceName, StringProperty destName,
+        StringProperty destDir, boolean localize)
+    {
+        super(sourceName, destName, destDir, localize);
+    }
+
+    public InputStream getInputStream(Status status)
+        throws IOException
+    {
+        String fileName = getSourceName().getStringValue();
+        fileName = (fileName.lastIndexOf('/') > 0)
+            ? fileName.substring(fileName.lastIndexOf('/') + 1)
+            : fileName;
+        
+        status.setText("Connecting...");
+
+        File file = File.createTempFile("felix-install.tmp", null);
+        file.deleteOnExit();
+
+        OutputStream os = new FileOutputStream(file);
+        URLConnection conn = new URL(getSourceName().getStringValue()).openConnection();
+        int total = conn.getContentLength();
+        InputStream is = conn.getInputStream();
+
+        int count = 0;
+        for (int len = is.read(s_buffer); len > 0; len = is.read(s_buffer))
+        {
+            count += len;
+            os.write(s_buffer, 0, len);
+            if (total > 0)
+            {
+                status.setText("Downloading " + fileName
+                    + " ( " + count + " bytes of " + total + " ).");
+            }
+            else
+            {
+                status.setText("Downloading " + fileName + " ( " + count + " bytes ).");
+            }
+        }
+
+        os.close();
+        is.close();
+
+        return new FileInputStream(file);
+    }
+
+    public String toString()
+    {
+        return "URL FILE: " + getSourceName().getStringValue();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/URLJarArtifact.java b/src/org/apache/osgi/framework/installer/artifact/URLJarArtifact.java
new file mode 100644
index 0000000..21e4751
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/URLJarArtifact.java
@@ -0,0 +1,88 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class URLJarArtifact extends AbstractJarArtifact
+{
+    public URLJarArtifact(StringProperty sourceName)
+    {
+        this(sourceName, null);
+    }
+
+    public URLJarArtifact(StringProperty sourceName, StringProperty destDir)
+    {
+        this(sourceName, destDir, false);
+    }
+
+    public URLJarArtifact(
+        StringProperty sourceName, StringProperty destDir, boolean localize)
+    {
+        super(sourceName, destDir, localize);
+    }
+
+    public InputStream getInputStream(Status status)
+        throws IOException
+    {
+        String fileName = getSourceName().getStringValue();
+        fileName = (fileName.lastIndexOf('/') > 0)
+            ? fileName.substring(fileName.lastIndexOf('/') + 1)
+            : fileName;
+        
+        status.setText("Connecting...");
+
+        File file = File.createTempFile("felix-install.tmp", null);
+        file.deleteOnExit();
+
+        OutputStream os = new FileOutputStream(file);
+        URLConnection conn = new URL(getSourceName().getStringValue()).openConnection();
+        int total = conn.getContentLength();
+        InputStream is = conn.getInputStream();
+
+        int count = 0;
+        for (int len = is.read(s_buffer); len > 0; len = is.read(s_buffer))
+        {
+            count += len;
+            os.write(s_buffer, 0, len);
+            if (total > 0)
+            {
+                status.setText("Downloading " + fileName
+                    + " ( " + count + " bytes of " + total + " ).");
+            }
+            else
+            {
+                status.setText("Downloading " + fileName + " ( " + count + " bytes ).");
+            }
+        }
+
+        os.close();
+        is.close();
+
+        return new FileInputStream(file);
+    }
+
+    public String toString()
+    {
+        return "URL JAR: " + getSourceName().getStringValue();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/BooleanEditor.java b/src/org/apache/osgi/framework/installer/editor/BooleanEditor.java
new file mode 100644
index 0000000..e11a654
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/BooleanEditor.java
@@ -0,0 +1,89 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.editor;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.Property;
+
+public class BooleanEditor extends JPanel
+{
+    private BooleanProperty m_prop = null;
+    private JRadioButton m_trueButton = null;
+    private JRadioButton m_falseButton = null;
+    private String m_trueString = null;
+    private String m_falseString = null;
+
+    public BooleanEditor(BooleanProperty prop)
+    {
+        this(prop, "true", "false");
+    }
+
+    public BooleanEditor(BooleanProperty prop, String trueString, String falseString)
+    {
+        m_prop = prop;
+        m_trueString = trueString;
+        m_falseString = falseString;
+        init();
+    }
+
+    public Property getProperty()
+    {
+        return m_prop;
+    }
+
+    public void setEnabled(boolean b)
+    {
+        m_trueButton.setEnabled(b);
+        m_falseButton.setEnabled(b);
+    }
+
+    protected void init()
+    {
+        add(m_trueButton = new JRadioButton(m_trueString));
+        add(m_falseButton = new JRadioButton(m_falseString));
+        ButtonGroup group = new ButtonGroup();
+        group.add(m_trueButton);
+        group.add(m_falseButton);
+        if (m_prop.getBooleanValue())
+        {
+            m_trueButton.setSelected(true);
+        }
+        else
+        {
+            m_falseButton.setSelected(true);
+        }
+
+        // Add action listeners.
+        m_trueButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                m_prop.setBooleanValue(true);
+            }
+        });
+        m_falseButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                m_prop.setBooleanValue(false);
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/BooleanStringEditor.java b/src/org/apache/osgi/framework/installer/editor/BooleanStringEditor.java
new file mode 100644
index 0000000..f64a96e
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/BooleanStringEditor.java
@@ -0,0 +1,125 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.editor;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+import org.apache.osgi.framework.installer.*;
+
+public class BooleanStringEditor extends JPanel
+{
+    private Property m_prop = null;
+    private JCheckBox m_includeButton = null;
+    private JTextField m_textField = null;
+
+    public BooleanStringEditor(Property prop)
+    {
+        if ((prop instanceof BooleanProperty) && (prop instanceof StringProperty))
+        {
+            m_prop = prop;
+        }
+        else
+        {
+            throw new IllegalArgumentException(
+                "Property must implement both boolean and string property interfaces.");
+        }
+        init();
+    }
+
+    public Property getProperty()
+    {
+        return m_prop;
+    }
+
+    public void setEnabled(boolean b)
+    {
+        m_includeButton.setEnabled(b);
+        m_textField.setEnabled(b && m_includeButton.isSelected());
+    }
+
+    protected void init()
+    {
+        // Set layout.
+        GridBagLayout grid = new GridBagLayout();
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.insets = new Insets(0, 2, 0, 2);
+        setLayout(grid);
+
+        // Add button.
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.gridheight = 1;
+        gbc.gridwidth = 1;
+        gbc.anchor = GridBagConstraints.WEST;
+        m_includeButton = new JCheckBox("");
+        grid.setConstraints(m_includeButton, gbc);
+        add(m_includeButton);
+
+        // Add field.
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        gbc.gridheight = 1;
+        gbc.gridwidth = 3;
+        gbc.anchor = GridBagConstraints.WEST;
+        m_textField = new JTextField(30);
+        m_textField.setText(((StringProperty) m_prop).getStringValue());
+        grid.setConstraints(m_textField, gbc);
+        add(m_textField);
+        m_textField.setEnabled(false);
+
+        // Add action listener.
+        m_includeButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                if (m_includeButton.isSelected())
+                {
+                    ((BooleanProperty) m_prop).setBooleanValue(true);
+                    m_textField.setEnabled(true);
+                }
+                else
+                {
+                    ((BooleanProperty) m_prop).setBooleanValue(false);
+                    m_textField.setEnabled(false);
+                }
+            }
+        });
+
+        // Add focus listener.
+        m_textField.addFocusListener(new FocusListener() {
+            public void focusGained(FocusEvent event)
+            {
+            }
+            public void focusLost(FocusEvent event)
+            {
+                if (!event.isTemporary())
+                {
+                    ((StringProperty) m_prop).setStringValue(m_textField.getText());
+                }
+            }
+        });
+
+        // Currently, the button is not selected. If the property
+        // is true, then click once to select button.        
+        if (((BooleanProperty) m_prop).getBooleanValue())
+        {
+            m_includeButton.doClick();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/FileEditor.java b/src/org/apache/osgi/framework/installer/editor/FileEditor.java
new file mode 100644
index 0000000..04a7bde
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/FileEditor.java
@@ -0,0 +1,139 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.editor;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+
+import javax.swing.*;
+
+import org.apache.osgi.framework.installer.Property;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class FileEditor extends JPanel
+{
+    private StringProperty m_prop = null;
+    private JTextField m_textField = null;
+    private JButton m_browseButton = null;
+    private boolean m_isDirectory = false;
+
+    public FileEditor(StringProperty prop, boolean isDirectory)
+    {
+        super();
+        m_prop = prop;
+        m_isDirectory = isDirectory;
+        init();
+    }
+
+    public Property getProperty()
+    {
+        return m_prop;
+    }
+
+    public void setEnabled(boolean b)
+    {
+        m_textField.setEnabled(b);
+        m_browseButton.setEnabled(b);
+    }
+
+    protected void init()
+    {
+        // Set layout.
+        GridBagLayout grid = new GridBagLayout();
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.insets = new Insets(0, 2, 0, 2);
+        setLayout(grid);
+
+        // Add field.
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.gridheight = 1;
+        gbc.gridwidth = 2;
+        gbc.anchor = GridBagConstraints.WEST;
+        m_textField = new JTextField(30);
+        m_textField.setText(m_prop.getStringValue());
+        grid.setConstraints(m_textField, gbc);
+        add(m_textField);
+
+        // Add button.
+        gbc.gridx = 2;
+        gbc.gridy = 0;
+        gbc.gridheight = 1;
+        gbc.gridwidth = 1;
+        gbc.anchor = GridBagConstraints.EAST;
+        m_browseButton = new JButton("Browse...");
+        m_browseButton.setMargin(new Insets(1, 1, 1, 1));
+        grid.setConstraints(m_browseButton, gbc);
+        add(m_browseButton);
+
+        // Add focus listener.
+        m_textField.addFocusListener(new FocusListener() {
+            public void focusGained(FocusEvent event)
+            {
+            }
+            public void focusLost(FocusEvent event)
+            {
+                if (!event.isTemporary())
+                {
+                    // Set the new value.
+                    m_prop.setStringValue(normalizeValue(m_textField.getText()));
+
+                }
+            }
+        });
+
+        // Add action listener.
+        m_browseButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event)
+            {
+                JFileChooser fileDlg = new JFileChooser();
+                if (m_isDirectory)
+                {
+                    fileDlg.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+                    fileDlg.setDialogTitle("Please select a directory...");
+                }
+                else
+                {
+                    fileDlg.setFileSelectionMode(JFileChooser.FILES_ONLY);
+                    fileDlg.setDialogTitle("Please select a file...");
+                }
+                fileDlg.setApproveButtonText("Select");
+                if (fileDlg.showOpenDialog(FileEditor.this) ==
+                    JFileChooser.APPROVE_OPTION)
+                {
+                    m_textField.setText(fileDlg.getSelectedFile().getAbsolutePath());
+                    m_prop.setStringValue(normalizeValue(m_textField.getText()));
+                }
+            }
+        });
+    }
+
+    private String normalizeValue(String value)
+    {
+        // Make sure that directories never end with a slash,
+        // for consistency.
+        if (m_isDirectory)
+        {
+            if (value.endsWith(File.separator))
+            {
+                value = value.substring(0, value.length() - 1);
+            }
+        }
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/StringEditor.java b/src/org/apache/osgi/framework/installer/editor/StringEditor.java
new file mode 100644
index 0000000..8d0c162
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/StringEditor.java
@@ -0,0 +1,83 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.editor;
+
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.apache.osgi.framework.installer.Property;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class StringEditor extends JPanel
+{
+    private StringProperty m_prop = null;
+    private JTextField m_textField = null;
+
+    public StringEditor(StringProperty prop)
+    {
+        m_prop = prop;
+        init();
+    }
+
+    public Property getProperty()
+    {
+        return m_prop;
+    }
+
+    public void setEnabled(boolean b)
+    {
+        m_textField.setEnabled(b);
+    }
+
+    protected void init()
+    {
+        // Set layout.
+        GridBagLayout grid = new GridBagLayout();
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.insets = new Insets(0, 2, 0, 2);
+        setLayout(grid);
+
+        // Add field.
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.gridheight = 1;
+        gbc.gridwidth = 2;
+        gbc.anchor = GridBagConstraints.WEST;
+        m_textField = new JTextField(20);
+        m_textField.setText(m_prop.getStringValue());
+        grid.setConstraints(m_textField, gbc);
+        add(m_textField);
+
+        // Add focus listener.
+        m_textField.addFocusListener(new FocusListener() {
+            public void focusGained(FocusEvent event)
+            {
+            }
+            public void focusLost(FocusEvent event)
+            {
+                if (!event.isTemporary())
+                {
+                    m_prop.setStringValue(m_textField.getText());
+                }
+            }
+        });
+    }
+}
diff --git a/src/org/apache/osgi/framework/installer/manifest.mf b/src/org/apache/osgi/framework/installer/manifest.mf
new file mode 100644
index 0000000..f25e0aa
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/manifest.mf
@@ -0,0 +1 @@
+Main-Class: org.apache.osgi.framework.installer.Install
diff --git a/src/org/apache/osgi/framework/installer/property/BooleanPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/BooleanPropertyImpl.java
new file mode 100644
index 0000000..cde63c1
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/BooleanPropertyImpl.java
@@ -0,0 +1,69 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.editor.BooleanEditor;
+
+public class BooleanPropertyImpl implements BooleanProperty
+{
+    private String m_name = null;
+    private boolean m_value = false;
+    private JComponent m_editor = null;
+
+    public BooleanPropertyImpl(String name, boolean value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public boolean getBooleanValue()
+    {
+        return m_value;
+    }
+
+    public void setBooleanValue(boolean b)
+    {
+        m_value = b;
+    }
+
+    public JComponent getEditor()
+    {
+        if (m_editor == null)
+        {
+            m_editor = new BooleanEditor(this);
+        }
+        return m_editor;
+    }
+
+    public void setEditor(JComponent comp)
+    {
+        m_editor = comp;
+    }
+
+    public String toString()
+    {
+        return (m_value) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/property/BooleanStringPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/BooleanStringPropertyImpl.java
new file mode 100644
index 0000000..da8ed1d
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/BooleanStringPropertyImpl.java
@@ -0,0 +1,82 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.editor.BooleanStringEditor;
+
+public class BooleanStringPropertyImpl implements BooleanProperty, StringProperty
+{
+    private String m_name = null;
+    private boolean m_boolean = false;
+    private String m_string = "";
+    private JComponent m_editor = null;
+
+    public BooleanStringPropertyImpl(String name, boolean b, String s)
+    {
+        m_name = name;
+        m_boolean = b;
+        m_string = s;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public boolean getBooleanValue()
+    {
+        return m_boolean;
+    }
+
+    public void setBooleanValue(boolean b)
+    {
+        m_boolean = b;
+    }
+
+    public String getStringValue()
+    {
+        return m_string;
+    }
+
+    public void setStringValue(String s)
+    {
+        m_string = s;
+    }
+
+    public JComponent getEditor()
+    {
+        if (m_editor == null)
+        {
+            m_editor = new BooleanStringEditor(this);
+        }
+        return m_editor;
+    }
+
+    public void setEditor(JComponent comp)
+    {
+        m_editor = comp;
+    }
+
+    public String toString()
+    {
+        return m_string;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/property/NotBooleanPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/NotBooleanPropertyImpl.java
new file mode 100644
index 0000000..e242552
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/NotBooleanPropertyImpl.java
@@ -0,0 +1,67 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.editor.BooleanEditor;
+
+public class NotBooleanPropertyImpl implements BooleanProperty
+{
+    private BooleanProperty m_prop = null;
+    private JComponent m_editor = null;
+
+    public NotBooleanPropertyImpl(BooleanProperty prop)
+    {
+        m_prop = prop;
+    }
+
+    public String getName()
+    {
+        return "NOT " + m_prop.getName();
+    }
+
+    public boolean getBooleanValue()
+    {
+        return !m_prop.getBooleanValue();
+    }
+
+    public void setBooleanValue(boolean b)
+    {
+        m_prop.setBooleanValue(!b);
+    }
+
+    public JComponent getEditor()
+    {
+        if (m_editor == null)
+        {
+            m_editor = new BooleanEditor(this);
+        }
+        return m_editor;
+    }
+
+    public void setEditor(JComponent comp)
+    {
+        m_editor = comp;
+    }
+
+    public String toString()
+    {
+        return (getBooleanValue()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/property/StringPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/StringPropertyImpl.java
new file mode 100644
index 0000000..47f8db1
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/StringPropertyImpl.java
@@ -0,0 +1,69 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.editor.StringEditor;
+
+public class StringPropertyImpl implements StringProperty
+{
+    private String m_name = null;
+    private String m_value = "";
+    private JComponent m_editor = null;
+
+    public StringPropertyImpl(String name, String value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public String getStringValue()
+    {
+        return m_value;
+    }
+
+    public void setStringValue(String s)
+    {
+        m_value = s;
+    }
+
+    public JComponent getEditor()
+    {
+        if (m_editor == null)
+        {
+            m_editor = new StringEditor(this);
+        }
+        return m_editor;
+    }
+
+    public void setEditor(JComponent comp)
+    {
+        m_editor = comp;
+    }
+
+    public String toString()
+    {
+        return m_value;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/resource/ResourceLoader.java b/src/org/apache/osgi/framework/installer/resource/ResourceLoader.java
new file mode 100644
index 0000000..ae7d675
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/resource/ResourceLoader.java
@@ -0,0 +1,33 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.installer.resource;
+
+import java.io.InputStream;
+import java.net.URL;
+
+public class ResourceLoader
+{
+    public static URL getResource(String name)
+    {
+        return ResourceLoader.class.getResource(name);
+    }
+
+    public static InputStream getResourceAsStream(String name)
+    {
+        return ResourceLoader.class.getResourceAsStream(name);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/manifest.mf b/src/org/apache/osgi/framework/manifest.mf
new file mode 100644
index 0000000..d9208d9
--- /dev/null
+++ b/src/org/apache/osgi/framework/manifest.mf
@@ -0,0 +1,2 @@
+Main-Class: org.apache.osgi.framework.Main
+Class-Path: osgi.jar moduleloader.jar
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Attribute.java b/src/org/apache/osgi/framework/searchpolicy/R4Attribute.java
new file mode 100644
index 0000000..a3161b9
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Attribute.java
@@ -0,0 +1,46 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.searchpolicy;
+
+public class R4Attribute
+{
+    private String m_name = "";
+    private String m_value = "";
+    private boolean m_isMandatory = false;
+    
+    public R4Attribute(String name, String value, boolean isMandatory)
+    {
+        m_name = name;
+        m_value = value;
+        m_isMandatory = isMandatory;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public String getValue()
+    {
+        return m_value;
+    }
+
+    public boolean isMandatory()
+    {
+        return m_isMandatory;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Directive.java b/src/org/apache/osgi/framework/searchpolicy/R4Directive.java
new file mode 100644
index 0000000..12de924
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Directive.java
@@ -0,0 +1,39 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.searchpolicy;
+
+public class R4Directive
+{
+    private String m_name = "";
+    private String m_value = "";
+    
+    public R4Directive(String name, String value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public String getValue()
+    {
+        return m_value;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Package.java b/src/org/apache/osgi/framework/searchpolicy/R4Package.java
new file mode 100755
index 0000000..380c5f0
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Package.java
@@ -0,0 +1,634 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.searchpolicy;
+
+import java.util.*;
+
+import org.apache.osgi.framework.util.FelixConstants;
+import org.apache.osgi.framework.util.Util;
+
+public class R4Package
+{
+    private String m_id = "";
+    private R4Directive[] m_directives = null;
+    private R4Attribute[] m_attrs = null;
+    private R4Version m_versionLow = null;
+    private R4Version m_versionHigh = null;
+    private String[] m_uses = null;
+    private boolean m_isOptional = false;
+    private String[][] m_includeFilter = null;
+    private String[][] m_excludeFilter = null;
+
+    protected R4Package(R4Package pkg)
+    {
+        m_id = pkg.m_id;
+        m_directives = pkg.m_directives;
+        m_attrs = pkg.m_attrs;
+        m_versionLow = pkg.m_versionLow;
+        m_versionHigh = pkg.m_versionHigh;
+        m_uses = pkg.m_uses;
+        m_isOptional = pkg.m_isOptional;
+        m_includeFilter = pkg.m_includeFilter;
+        m_excludeFilter = pkg.m_excludeFilter;
+    }
+
+    public R4Package(String id, R4Directive[] directives, R4Attribute[] attrs)
+    {
+        m_id = id;
+        m_directives = (directives == null) ? new R4Directive[0] : directives;
+        m_attrs = (attrs == null) ? new R4Attribute[0] : attrs;
+
+        // Find all directives: uses, mandatory, resolution, include, and exclude.
+        String mandatory = "", uses = "";
+        for (int i = 0; i < m_directives.length; i++)
+        {
+            if (m_directives[i].getName().equals(FelixConstants.USES_DIRECTIVE))
+            {
+                uses = m_directives[i].getValue();
+            }
+            else if (m_directives[i].getName().equals(FelixConstants.MANDATORY_DIRECTIVE))
+            {
+                mandatory = m_directives[i].getValue();
+            }
+            else if (m_directives[i].getName().equals(FelixConstants.RESOLUTION_DIRECTIVE))
+            {
+                m_isOptional = m_directives[i].getValue().equals(FelixConstants.RESOLUTION_OPTIONAL);
+            }
+            else if (m_directives[i].getName().equals(FelixConstants.INCLUDE_DIRECTIVE))
+            {
+                String[] ss = Util.parseDelimitedString(m_directives[i].getValue(), ",");
+                m_includeFilter = new String[ss.length][];
+                for (int filterIdx = 0; filterIdx < ss.length; filterIdx++)
+                {
+                    m_includeFilter[filterIdx] = parseSubstring(ss[filterIdx]);
+                }
+            }
+            else if (m_directives[i].getName().equals(FelixConstants.EXCLUDE_DIRECTIVE))
+            {
+                String[] ss = Util.parseDelimitedString(m_directives[i].getValue(), ",");
+                m_excludeFilter = new String[ss.length][];
+                for (int filterIdx = 0; filterIdx < ss.length; filterIdx++)
+                {
+                    m_excludeFilter[filterIdx] = parseSubstring(ss[filterIdx]);
+                }
+            }
+        }
+
+        // Parse these uses directive.
+        StringTokenizer tok = new StringTokenizer(uses, ",");
+        m_uses = new String[tok.countTokens()];
+        for (int i = 0; i < m_uses.length; i++)
+        {
+            m_uses[i] = tok.nextToken().trim();
+        }
+
+        // Parse mandatory directive and mark specified
+        // attributes as mandatory.
+        tok = new StringTokenizer(mandatory, ",");
+        while (tok.hasMoreTokens())
+        {
+            // Get attribute name.
+            String attrName = tok.nextToken().trim();
+            // Find attribute and mark it as mandatory.
+            boolean found = false;
+            for (int i = 0; (!found) && (i < m_attrs.length); i++)
+            {
+                if (m_attrs[i].getName().equals(attrName))
+                {
+                    m_attrs[i] = new R4Attribute(
+                        m_attrs[i].getName(), m_attrs[i].getValue(), true);
+                    found = true;
+                }
+            }
+            // If a specified mandatory attribute was not found,
+            // then error.
+            if (!found)
+            {
+                throw new IllegalArgumentException(
+                    "Mandatory attribute '" + attrName + "' does not exist.");
+            }
+        }
+
+        // Find and parse version attribute, if present.
+        String versionInterval = "0.0.0";
+        for (int i = 0; i < m_attrs.length; i++)
+        {
+            if (m_attrs[i].getName().equals(FelixConstants.VERSION_ATTRIBUTE) ||
+                m_attrs[i].getName().equals(FelixConstants.PACKAGE_SPECIFICATION_VERSION))
+            {
+                // Normalize version attribute name.
+                m_attrs[i] = new R4Attribute(
+                    FelixConstants.VERSION_ATTRIBUTE, m_attrs[i].getValue(),
+                    m_attrs[i].isMandatory());
+                versionInterval = m_attrs[i].getValue();
+                break;
+            }
+        }
+        
+        R4Version[] versions = parseVersionInterval(versionInterval);
+        m_versionLow = versions[0];
+        if (versions.length == 2)
+        {
+            m_versionHigh = versions[1];
+        }
+    }
+
+    public String getId()
+    {
+        return m_id;
+    }
+
+    public R4Directive[] getDirectives()
+    {
+        return m_directives;
+    }
+
+    public R4Attribute[] getAttributes()
+    {
+        return m_attrs;
+    }
+
+    public R4Version getVersionLow()
+    {
+        return m_versionLow;
+    }
+
+    public R4Version getVersionHigh()
+    {
+        return m_versionHigh;
+    }
+
+    public String[] getUses()
+    {
+        return m_uses;
+    }
+
+    public boolean isOptional()
+    {
+        return m_isOptional;
+    }
+
+    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 = org.apache.osgi.moduleloader.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.length);
+            i++)
+        {
+            included = checkSubstring(m_includeFilter[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.length);
+            i++)
+        {
+            excluded = checkSubstring(m_excludeFilter[i], className);
+        }
+        return included && !excluded;
+    }
+
+    // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+    public boolean doesSatisfy(R4Package pkg)
+    {
+        // For packages to be compatible, they must have the
+        // same name.
+        if (!m_id.equals(pkg.m_id))
+        {
+            return false;
+        }
+        
+        return isVersionInRange(m_versionLow, pkg.m_versionLow, pkg.m_versionHigh)
+            && doAttributesMatch(pkg);
+    }
+
+    // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+    public static boolean isVersionInRange(R4Version version, R4Version low, R4Version high)
+    {
+        // We might not have an upper end to the range.
+        if (high == null)
+        {
+            return (version.compareTo(low) >= 0);
+        }
+        else if (low.isInclusive() && high.isInclusive())
+        {
+            return (version.compareTo(low) >= 0) && (version.compareTo(high) <= 0);
+        }
+        else if (high.isInclusive())
+        {
+            return (version.compareTo(low) > 0) && (version.compareTo(high) <= 0);
+        }
+        else if (low.isInclusive())
+        {
+            return (version.compareTo(low) >= 0) && (version.compareTo(high) < 0);
+        }
+
+        return (version.compareTo(low) > 0) && (version.compareTo(high) < 0);
+    }
+
+    private boolean doAttributesMatch(R4Package pkg)
+    {
+        // Cycle through all attributes of the specified package
+        // and make sure their values match the attribute values
+        // of this package.
+        for (int attrIdx = 0; attrIdx < pkg.m_attrs.length; attrIdx++)
+        {
+            // Get current attribute from specified package.
+            R4Attribute attr = pkg.m_attrs[attrIdx];
+
+            // Ignore version attribute, since it is a special case that
+            // has already been compared using isVersionInRange() before
+            // the call to this method was made.
+            if (attr.getName().equals(FelixConstants.VERSION_ATTRIBUTE))
+            {
+                continue;
+            }
+
+            // Check if this package has the same attribute.
+            boolean found = false;
+            for (int thisAttrIdx = 0;
+                (!found) && (thisAttrIdx < m_attrs.length);
+                thisAttrIdx++)
+            {
+                // Get current attribute for this package.
+                R4Attribute thisAttr = m_attrs[thisAttrIdx];
+                // Check if the attribute names are equal.
+                if (attr.getName().equals(thisAttr.getName()))
+                {
+                    // If the values are not equal, then return false immediately.
+                    // We should not compare version values here, since they are
+                    // a special case and have already been compared by a call to
+                    // isVersionInRange() before getting here; however, it is
+                    // possible for version to be mandatory, so make sure it is
+                    // present below.
+                    if (!attr.getValue().equals(thisAttr.getValue()))
+                    {
+                        return false;
+                    }
+                    found = true;
+                }
+            }
+            // If the attribute was not found, then return false.
+            if (!found)
+            {
+                return false;
+            }
+        }
+
+        // Now, cycle through all attributes of this package and verify that
+        // all mandatory attributes are present in the speceified package.
+        for (int thisAttrIdx = 0; thisAttrIdx < m_attrs.length; thisAttrIdx++)
+        {
+            // Get current attribute for this package.
+            R4Attribute thisAttr = m_attrs[thisAttrIdx];
+            
+            // If the attribute is mandatory, then make sure
+            // the specified package has the attribute.
+            if (thisAttr.isMandatory())
+            {
+                boolean found = false;
+                for (int attrIdx = 0;
+                    (!found) && (attrIdx < pkg.m_attrs.length);
+                    attrIdx++)
+                {
+                    // Get current attribute from specified package.
+                    R4Attribute attr = pkg.m_attrs[attrIdx];
+        
+                    // Check if the attribute names are equal
+                    // and set found flag.
+                    if (thisAttr.getName().equals(attr.getName()))
+                    {
+                        found = true;
+                    }
+                }
+                // If not found, then return false.
+                if (!found)
+                {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public String toString()
+    {
+        String msg = getId();
+        for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
+        {
+            msg = msg + " [" + m_directives[i].getName() + ":="+ m_directives[i].getValue() + "]";
+        }
+        for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+        {
+            msg = msg + " [" + m_attrs[i].getName() + "="+ m_attrs[i].getValue() + "]";
+        }
+        return msg;
+    }
+
+    // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+    //            pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static R4Package[] parseImportOrExportHeader(String s)
+    {
+        R4Package[] pkgs = null;
+        if (s != null)
+        {
+            if (s.length() == 0)
+            {
+                throw new IllegalArgumentException(
+                    "The import and export headers cannot be an empty string.");
+            }
+            String[] ss = Util.parseDelimitedString(
+                s, FelixConstants.CLASS_PATH_SEPARATOR);
+            pkgs = parsePackageStrings(ss);
+        }
+        return (pkgs == null) ? new R4Package[0] : pkgs;
+    }
+
+    // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+    public static R4Package[] parsePackageStrings(String[] ss)
+        throws IllegalArgumentException
+    {
+        if (ss == null)
+        {
+            return null;
+        }
+
+        List completeList = new ArrayList();
+        for (int ssIdx = 0; ssIdx < ss.length; ssIdx++)
+        {
+            // Break string into semi-colon delimited pieces.
+            String[] pieces = Util.parseDelimitedString(
+                ss[ssIdx], FelixConstants.PACKAGE_SEPARATOR);
+
+            // Count the number of different packages; packages
+            // will not have an '=' in their string. This assumes
+            // that packages come first, before directives and
+            // attributes.
+            int pkgCount = 0;
+            for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
+            {
+                if (pieces[pieceIdx].indexOf('=') >= 0)
+                {
+                    break;
+                }
+                pkgCount++;
+            }
+
+            // Error if no packages were specified.
+            if (pkgCount == 0)
+            {
+                throw new IllegalArgumentException(
+                    "No packages specified on import: " + ss[ssIdx]);
+            }
+
+            // Parse the directives/attributes.
+            R4Directive[] dirs = new R4Directive[pieces.length - pkgCount];
+            R4Attribute[] attrs = new R4Attribute[pieces.length - pkgCount];
+            int dirCount = 0, attrCount = 0;
+            int idx = -1;
+            String sep = null;
+            for (int pieceIdx = pkgCount; pieceIdx < pieces.length; pieceIdx++)
+            {
+                // Check if it is a directive.
+                if ((idx = pieces[pieceIdx].indexOf(FelixConstants.DIRECTIVE_SEPARATOR)) >= 0)
+                {
+                    sep = FelixConstants.DIRECTIVE_SEPARATOR;
+                }
+                // Check if it is an attribute.
+                else if ((idx = pieces[pieceIdx].indexOf(FelixConstants.ATTRIBUTE_SEPARATOR)) >= 0)
+                {
+                    sep = FelixConstants.ATTRIBUTE_SEPARATOR;
+                }
+                // It is an error.
+                else
+                {
+                    throw new IllegalArgumentException(
+                        "Not a directive/attribute: " + ss[ssIdx]);
+                }
+
+                String key = pieces[pieceIdx].substring(0, idx).trim();
+                String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+                // Remove quotes, if value is quoted.
+                if (value.startsWith("\"") && value.endsWith("\""))
+                {
+                    value = value.substring(1, value.length() - 1);
+                }
+
+                // Save the directive/attribute in the appropriate array.
+                if (sep.equals(FelixConstants.DIRECTIVE_SEPARATOR))
+                {
+                    dirs[dirCount++] = new R4Directive(key, value);
+                }
+                else
+                {
+                    attrs[attrCount++] = new R4Attribute(key, value, false);
+                }
+            }
+
+            // Shrink directive array.
+            R4Directive[] dirsFinal = new R4Directive[dirCount];
+            System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
+            // Shrink attribute array.
+            R4Attribute[] attrsFinal = new R4Attribute[attrCount];
+            System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+
+            // Create package attributes for each package and
+            // set directives/attributes. Add each package to
+            // completel list of packages.
+            R4Package[] pkgs = new R4Package[pkgCount];
+            for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++)
+            {
+                pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirsFinal, attrsFinal);
+                completeList.add(pkgs[pkgIdx]);
+            }
+        }
+    
+        R4Package[] ips = (R4Package[])
+            completeList.toArray(new R4Package[completeList.size()]);
+        return ips;
+    }
+
+    public static R4Version[] parseVersionInterval(String interval)
+    {
+        // Check if the version is an interval.
+        if (interval.indexOf(',') >= 0)
+        {
+            String s = interval.substring(1, interval.length() - 1);
+            String vlo = s.substring(0, s.indexOf(','));
+            String vhi = s.substring(s.indexOf(',') + 1, s.length());
+            return new R4Version[] {
+                new R4Version(vlo, (interval.charAt(0) == '[')),
+                new R4Version(vhi, (interval.charAt(interval.length() - 1) == ']'))
+            };
+        }
+        else
+        {
+            return new R4Version[] { new R4Version(interval, true) };
+        }
+    }
+
+    //
+    // The following substring-related code was lifted and modified
+    // from the LDAP parser code.
+    //
+
+    private static String[] parseSubstring(String target)
+    {
+        List pieces = new ArrayList();
+        StringBuffer ss = new StringBuffer();
+        // int kind = SIMPLE; // assume until proven otherwise
+        boolean wasStar = false; // indicates last piece was a star
+        boolean leftstar = false; // track if the initial piece is a star
+        boolean rightstar = false; // track if the final piece is a star
+
+        int idx = 0;
+
+        // We assume (sub)strings can contain leading and trailing blanks
+loop:   for (;;)
+        {
+            if (idx >= target.length())
+            {
+                if (wasStar)
+                {
+                    // insert last piece as "" to handle trailing star
+                    rightstar = true;
+                }
+                else
+                {
+                    pieces.add(ss.toString());
+                    // accumulate the last piece
+                    // note that in the case of
+                    // (cn=); this might be
+                    // the string "" (!=null)
+                }
+                ss.setLength(0);
+                break loop;
+            }
+
+            char c = target.charAt(idx++);
+            if (c == '*')
+            {
+                if (wasStar)
+                {
+                    // encountered two successive stars;
+                    // I assume this is illegal
+                    throw new IllegalArgumentException("Invalid filter string: " + target);
+                }
+                if (ss.length() > 0)
+                {
+                    pieces.add(ss.toString()); // accumulate the pieces
+                    // between '*' occurrences
+                }
+                ss.setLength(0);
+                // if this is a leading star, then track it
+                if (pieces.size() == 0)
+                {
+                    leftstar = true;
+                }
+                ss.setLength(0);
+                wasStar = true;
+            }
+            else
+            {
+                wasStar = false;
+                ss.append(c);
+            }
+        }
+        if (leftstar || rightstar || pieces.size() > 1)
+        {
+            // insert leading and/or trailing "" to anchor ends
+            if (rightstar)
+            {
+                pieces.add("");
+            }
+            if (leftstar)
+            {
+                pieces.add(0, "");
+            }
+        }
+        return (String[]) pieces.toArray(new String[pieces.size()]);
+    }
+
+    private static boolean checkSubstring(String[] pieces, String s)
+    {
+        // Walk the pieces to match the string
+        // There are implicit stars between each piece,
+        // and the first and last pieces might be "" to anchor the match.
+        // assert (pieces.length > 1)
+        // minimal case is <string>*<string>
+
+        boolean result = false;
+        int len = pieces.length;
+
+loop:   for (int i = 0; i < len; i++)
+        {
+            String piece = (String) pieces[i];
+            int index = 0;
+            if (i == len - 1)
+            {
+                // this is the last piece
+                if (s.endsWith(piece))
+                {
+                    result = true;
+                }
+                else
+                {
+                    result = false;
+                }
+                break loop;
+            }
+            // initial non-star; assert index == 0
+            else if (i == 0)
+            {
+                if (!s.startsWith(piece))
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+            // assert i > 0 && i < len-1
+            else
+            {
+                // Sure wish stringbuffer supported e.g. indexOf
+                index = s.indexOf(piece, index);
+                if (index < 0)
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+            // start beyond the matching piece
+            index += piece.length();
+        }
+
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4SearchPolicy.java b/src/org/apache/osgi/framework/searchpolicy/R4SearchPolicy.java
new file mode 100755
index 0000000..5855c06
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4SearchPolicy.java
@@ -0,0 +1,1638 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.searchpolicy;
+
+import java.net.URL;
+import java.util.*;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.ResolveException;
+import org.apache.osgi.moduleloader.search.ResolveListener;
+
+public class R4SearchPolicy implements SearchPolicy, ModuleListener
+{
+    // Array of R4Package.
+    public static final String EXPORTS_ATTR = "exports";
+    // Array of R4Package.
+    public static final String IMPORTS_ATTR = "imports";
+    // Array of R4Package.
+    public static final String DYNAMICIMPORTS_ATTR = "dynamicimports";
+    // Array of R4Wire.
+    public static final String WIRING_ATTR = "wiring";
+    // Boolean.
+    public static final String RESOLVED_ATTR = "resolved";
+
+    private LogWrapper m_logger = null;
+    private ModuleManager m_mgr = null;
+    private Map m_availPkgMap = new HashMap();
+    private Map m_inUsePkgMap = new HashMap();
+
+    // Listener-related instance variables.
+    private static final ResolveListener[] m_emptyListeners = new ResolveListener[0];
+    private ResolveListener[] m_listeners = m_emptyListeners;
+
+    // Reusable empty arrays.
+    public static final Module[] m_emptyModules = new Module[0];
+    public static final R4Package[] m_emptyPackages = new R4Package[0];
+    public static final R4Wire[] m_emptyWires = new R4Wire[0];
+
+    // Re-usable security manager for accessing class context.
+    private static SecurityManagerX m_sm = new SecurityManagerX();
+
+    public R4SearchPolicy(LogWrapper logger)
+    {
+        m_logger = logger;
+    }
+
+    public void setModuleManager(ModuleManager mgr)
+        throws IllegalStateException
+    {
+        if (m_mgr == null)
+        {
+            m_mgr = mgr;
+            m_mgr.addModuleListener(this);
+        }
+        else
+        {
+            throw new IllegalStateException("Module manager is already initialized");
+        }
+    }
+
+    public Object[] definePackage(Module module, String pkgName)
+    {
+        R4Package pkg = R4SearchPolicy.getExportPackage(module, pkgName);
+        if (pkg != null)
+        {
+            return new Object[]  {
+                pkgName, // Spec title.
+                pkg.getVersionLow().toString(), // Spec version.
+                "", // Spec vendor.
+                "", // Impl title.
+                "", // Impl version.
+                ""  // Impl vendor.
+            };
+        }
+        return null;
+    }
+
+    public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+        throws ClassNotFoundException
+    {
+        // First, try to resolve the originating module.
+        try
+        {
+            resolve(module);
+        }
+        catch (ResolveException ex)
+        {
+            throw new ClassNotFoundException(name);
+        }
+
+        // Get the package of the target class.
+        String pkgName = Util.getClassPackage(name);
+
+        // Load all "java.*" classes from parent class loader;
+        // these packages cannot be provided by other bundles.
+        if (pkgName.startsWith("java."))
+        {
+            return (parent == null) ? null : parent.loadClass(name);
+        }
+
+        // We delegate to the module's wires to find the class.
+        R4Wire[] wires = getWiringAttr(module);
+        for (int i = 0; i < wires.length; i++)
+        {
+            // Only check when the package of the class is
+            // the same as the import package.
+            if (wires[i].m_pkg.getId().equals(pkgName))
+            {
+                // Before delegating to the module class loader to satisfy
+                // the class load, we must check the include/exclude filters
+                // from the target package to make sure that the class is
+                // actually visible. If the exporting module is the same as
+                // the requesting module, then filtering is not performed
+                // since a module has complete access to itself.
+// TODO: Determine if it is possible to modify Module Loader somehow
+// so that this check is done within the target module itself; it
+// doesn't totally make sense to do this check in the importing module.
+                if (wires[i].m_module != module)
+                {
+                    if (!wires[i].m_pkg.isIncluded(name))
+                    {
+                        throw new ClassNotFoundException(name);
+                    }
+                }
+
+                // Since the class is included, delegate to the exporting module.
+                try
+                {
+                    Class clazz = wires[i].m_module.getClassLoader().loadClassFromModule(name);
+                    if (clazz != null)
+                    {
+                        return clazz;
+                    }
+                }
+                catch (Throwable th)
+                {
+                    // Not much we can do here.
+                }
+                throw new ClassNotFoundException(name);
+            }
+        }
+
+        return null;
+    }
+
+    public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+        throws ClassNotFoundException
+    {
+        // At this point, the module's imports were searched and so was the
+        // the module's own resources. Now we make an attempt to load the
+        // class via a dynamic import, if possible.
+        String pkgName = Util.getClassPackage(name);
+        Module candidate = attemptDynamicImport(module, pkgName);
+        // If the dynamic import was successful, then this initial
+        // time we must directly return the result from dynamically
+        // selected candidate's class loader, but for subsequent
+        // requests for classes in the associated package will be
+        // processed as part of normal static imports.
+        if (candidate != null)
+        {
+            return candidate.getClassLoader().loadClass(name);
+        }
+
+        // At this point, the class could not be found by the bundle's static
+        // or dynamic imports, nor its own resources. Before we throw
+        // an exception, we will try to determine if the instigator of the
+        // class load was a class from a bundle or not. This is necessary
+        // because the specification mandates that classes on the class path
+        // should be hidden (except for java.*), but it does allow for these
+        // classes to be exposed by the system bundle as an export. However,
+        // in some situations classes on the class path make the faulty
+        // assumption that they can access everything on the class path from
+        // every other class loader that they come in contact with. This is
+        // not true if the class loader in question is from a bundle. Thus,
+        // this code tries to detect that situation. If the class
+        // instigating the class load was NOT from a bundle, then we will
+        // make the assumption that the caller actually wanted to use the
+        // parent class loader and we will delegate to it. If the class was
+        // from a bundle, then we will enforce strict class loading rules
+        // for the bundle and throw a class not found exception.
+
+        // Get the class context to see the classes on the stack.
+        Class[] classes = m_sm.getClassContext();
+        // Start from 1 to skip inner class.
+        for (int i = 1; i < classes.length; i++)
+        {
+            // Find the first class on the call stack that is neither
+            // a class loader or Class itself, because we want to ignore
+            // the calls to ClassLoader.loadClass() and Class.forName().
+            if (!ClassLoader.class.isAssignableFrom(classes[i]) &&
+                !Class.class.isAssignableFrom(classes[i]))
+            {
+                // If the instigating class was not from a bundle, then
+                // delegate to the parent class loader. Otherwise, break
+                // out of loop and throw an exception.
+                if (!ModuleClassLoader.class.isInstance(classes[i].getClassLoader()))
+                {
+                    return parent.loadClass(name);
+                }
+                break;
+            }
+        }
+        
+        throw new ClassNotFoundException(name);
+    }
+
+    public URL findResource(ClassLoader parent, Module module, String name)
+        throws ResourceNotFoundException
+    {
+        // First, try to resolve the originating module.
+        try
+        {
+            resolve(module);
+        }
+        catch (ResolveException ex)
+        {
+            return null;
+        }
+
+        // Get the package of the target resource.
+        String pkgName = Util.getResourcePackage(name);
+
+        // Load all "java.*" resources from parent class loader;
+        // these packages cannot be provided by other bundles.
+        if (pkgName.startsWith("java."))
+        {
+            return (parent == null) ? null : parent.getResource(name);
+        }
+
+        // We delegate to the module's wires to find the resource.
+        R4Wire[] wires = getWiringAttr(module);
+        for (int i = 0; i < wires.length; i++)
+        {
+            // Only check when the package of the resource is
+            // the same as the import package.
+            if (wires[i].m_pkg.getId().equals(pkgName))
+            {
+                try
+                {
+                    URL url = wires[i].m_module.getClassLoader().getResourceFromModule(name);
+                    if (url != null)
+                    {
+                        return url;
+                    }
+                }
+                catch (Throwable th)
+                {
+                    // Not much we can do here.
+                }
+                throw new ResourceNotFoundException(name);
+            }
+        }
+
+        // Check dynamic imports.
+// TODO: Dynamic imports should be searched after local sources.
+        Module candidate = attemptDynamicImport(module, pkgName);
+        // This initial time we must directly return the result from
+        // the candidate's class loaders, but since the candidate was
+        // added to the module's wiring attribute, subsequent class
+        // loads from the same package will be handled in the normal
+        // fashion for statically imported packaes.
+        return (candidate == null)
+            ? null : candidate.getClassLoader().getResource(name);
+    }
+
+    private Module attemptDynamicImport(Module module, String pkgName)
+    {
+        Module candidate = null;
+
+        // There is an overriding assumption here that a package is
+        // never split across bundles. If a package can be split
+        // across bundles, then this will fail.
+
+        try
+        {
+            // Check the dynamic import specs for a match of
+            // the target package.
+            R4Package[] dynamics = getDynamicImportsAttr(module);
+            R4Package pkgMatch = null;
+            for (int i = 0; (pkgMatch == null) && (i < dynamics.length); i++)
+            {
+                // Star matches everything.
+                if (dynamics[i].getId().equals("*"))
+                {
+                    // Create a package instance without wildcard.
+                    pkgMatch = new R4Package(
+                        pkgName,
+                        dynamics[i].getDirectives(),
+                        dynamics[i].getAttributes());
+                }
+                // Packages ending in ".*" must match starting strings.
+                else if (dynamics[i].getId().endsWith(".*"))
+                {
+                    if (pkgName.regionMatches(
+                        0, dynamics[i].getId(), 0, dynamics[i].getId().length() - 2))
+                    {
+                        // Create a package instance without wildcard.
+                        pkgMatch = new R4Package(
+                            pkgName,
+                            dynamics[i].getDirectives(),
+                            dynamics[i].getAttributes());
+                    }
+                }
+                // Or we can have a precise match.
+                else
+                {
+                    if (pkgName.equals(dynamics[i].getId()))
+                    {
+                        pkgMatch = dynamics[i];
+                    }
+                }
+            }
+
+            // If the target package does not match any dynamically imported
+            // packages or if the module is already wired for the target package,
+            // then just return null. The module may be already wired to the target
+            // package if the class being searched for does not actually exist.
+            if ((pkgMatch == null) || (getWire(module, pkgMatch.getId()) != null))
+            {
+                return null;
+            }
+
+            // At this point, the target package has matched a dynamically
+            // imported package spec. Now we must try to find a candidate
+            // exporter for target package and add it to the module's set
+            // of wires.
+
+            // Lock module manager instance to ensure that nothing changes.
+            synchronized (m_mgr)
+            {
+                // Try to add a new entry to the module's import attribute.
+                // Select the first candidate that successfully resolves.
+
+                // First check already resolved exports for a match.
+                Module[] candidates = getCompatibleExporters(
+                    (Module[]) m_inUsePkgMap.get(pkgMatch.getId()), pkgMatch);
+                // If there is an "in use" candidate, just take the first one.
+                if (candidates.length > 0)
+                {
+                    candidate = candidates[0];
+                }
+
+                // If there were no "in use" candidates, then try "available"
+                // candidates.
+                if (candidate == null)
+                {
+                    candidates = getCompatibleExporters(
+                        (Module[]) m_availPkgMap.get(pkgMatch.getId()), pkgMatch);
+                    for (int candIdx = 0;
+                        (candidate == null) && (candIdx < candidates.length);
+                        candIdx++)
+                    {
+                        try
+                        {
+                            resolve(module);
+                            candidate = candidates[candIdx];
+                        }
+                        catch (ResolveException ex)
+                        {
+                        }
+                    }
+                }
+
+                // If we found a candidate, then add it to the module's
+                // wiring attribute.
+                if (candidate != null)
+                {
+                    R4Wire[] wires = getWiringAttr(module);
+                    R4Wire[] newWires = new R4Wire[wires.length + 1];
+                    System.arraycopy(wires, 0, newWires, 0, wires.length);
+                    // Find the candidate's export package object and
+                    // use that for creating the wire; this is necessary
+                    // since it contains "uses" dependency information.
+                    newWires[wires.length] = new R4Wire(
+                        getExportPackage(candidate, pkgMatch.getId()), candidate);
+                    module.setAttribute(WIRING_ATTR, newWires);
+m_logger.log(LogWrapper.LOG_DEBUG, "WIRE: [" + module + "] " + newWires[wires.length]);
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "Unable to dynamically import package.", ex);
+        }
+
+        return candidate;
+    }
+
+    public Module[] getAvailableExporters(R4Package pkg)
+    {
+        // Synchronized on the module manager to make sure that no
+        // modules are added, removed, or resolved.
+        synchronized (m_mgr)
+        {
+            return getCompatibleExporters((Module[]) m_availPkgMap.get(pkg.getId()), pkg);
+        }
+    }
+
+    public Module[] getInUseExporters(R4Package pkg)
+    {
+        // Synchronized on the module manager to make sure that no
+        // modules are added, removed, or resolved.
+        synchronized (m_mgr)
+        {
+            return getCompatibleExporters((Module[]) m_inUsePkgMap.get(pkg.getId()), pkg);
+        }
+    }
+
+    public void resolve(Module rootModule)
+        throws ResolveException
+    {
+        // If the module is already resolved, then we can just return.
+        if (getResolvedAttr(rootModule).booleanValue())
+        {
+            return;
+        }
+
+        // This variable maps an unresolved module to a list of resolver
+        // nodes, where there is one resolver node for each import that
+        // must be resolved. A resolver node contains the potential
+        // candidates to resolve the import and the current selected
+        // candidate index.
+        Map resolverMap = new HashMap();
+
+        // This map will be used to hold the final wires for all
+        // resolved modules, which can then be used to fire resolved
+        // events outside of the synchronized block.
+        Map resolvedModuleWireMap = null;
+    
+        // Synchronize on the module manager, because we don't want
+        // any modules being added or removed while we are in the
+        // middle of this operation.
+        synchronized (m_mgr)
+        {
+            // The first step is to populate the resolver map. This
+            // will use the target module to populate the resolver map
+            // with all potential modules that need to be resolved as a
+            // result of resolving the target module. The key of the
+            // map is a potential module to be resolved and the value is
+            // a list of resolver nodes, one for each of the module's
+            // imports, where each resolver node contains the potential
+            // candidates for resolving the import. Not all modules in
+            // this map will be resolved, only the target module and
+            // any candidates selected to resolve its imports and the
+            // transitive imports this implies.
+            populateResolverMap(resolverMap, rootModule);
+
+//dumpResolverMap();
+
+            // The next step is to use the resolver map to determine if
+            // the class space for the root module is consistent. This
+            // is an iterative process that transitively walks the "uses"
+            // relationships of all currently selected potential candidates
+            // for resolving import packages checking for conflicts. If a
+            // conflict is found, it "increments" the configuration of
+            // currently selected potential candidates and tests them again.
+            // If this method returns, then it has found a consistent set
+            // of candidates; otherwise, a resolve exception is thrown if
+            // it exhausts all possible combinations and could not find a
+            // consistent class space.
+            findConsistentClassSpace(resolverMap, rootModule);
+
+            // The final step is to create the wires for the root module and
+            // transitively all modules that are to be resolved from the
+            // selected candidates for resolving the root module's imports.
+            // When this call returns, each module's wiring and resolved
+            // attributes are set. The resulting wiring map is used below
+            // to fire resolved events outside of the synchronized block.
+            // The resolved module wire map maps a module to its array of
+            // wires.
+            resolvedModuleWireMap = createWires(resolverMap, rootModule);
+            
+//dumpAvailablePackages();
+//dumpUsedPackages();
+
+        } // End of synchronized block on module manager.
+
+        // Fire resolved events for all resolved modules;
+        // the resolved modules array will only be set if the resolve
+        // was successful after the root module was resolved.
+        if (resolvedModuleWireMap != null)
+        {
+            Iterator iter = resolvedModuleWireMap.entrySet().iterator();
+            while (iter.hasNext())
+            {
+                fireModuleResolved((Module) ((Map.Entry) iter.next()).getKey());
+            }
+        }
+    }
+
+    private void populateResolverMap(Map resolverMap, Module module)
+        throws ResolveException
+    {
+        // Detect cycles.
+        if (resolverMap.get(module) != null)
+        {
+            return;
+        }
+
+        // Map to hold the bundle's import packages
+        // and their respective resolving candidates.
+        List nodeList = new ArrayList();
+
+        // Even though the node list is currently emptry, we
+        // record it in the resolver map early so we can use
+        // it to detect cycles.
+        resolverMap.put(module, nodeList);
+
+        // Loop through each import and calculate its resolving
+        // set of candidates.
+        R4Package[] imports = getImportsAttr(module);
+        for (int impIdx = 0; impIdx < imports.length; impIdx++)
+        {
+            // Get the candidates from the "in use" and "available"
+            // package maps. Candidates "in use" have higher priority
+            // than "available" ones, so put the "in use" candidates
+            // at the front of the list of candidates.
+            Module[] inuse = getCompatibleExporters(
+                (Module[]) m_inUsePkgMap.get(
+                    imports[impIdx].getId()), imports[impIdx]);
+            Module[] available = getCompatibleExporters(
+                (Module[]) m_availPkgMap.get(
+                    imports[impIdx].getId()), imports[impIdx]);
+            Module[] candidates = new Module[inuse.length + available.length];
+            System.arraycopy(inuse, 0, candidates, 0, inuse.length);
+            System.arraycopy(available, 0, candidates, inuse.length, available.length);
+
+            // If we have candidates, then we need to recursively populate
+            // the resolver map with each of them.
+            ResolveException rethrow = null;
+            if (candidates.length > 0)
+            {
+                for (int candIdx = 0; candIdx < candidates.length; candIdx++)
+                {
+                    try
+                    {
+                        // Only populate the resolver map with modules that
+                        // are not already resolved.
+                        if (!getResolvedAttr(candidates[candIdx]).booleanValue())
+                        {
+                            populateResolverMap(resolverMap, candidates[candIdx]);
+                        }
+                    }
+                    catch (ResolveException ex)
+                    {
+                        // If we received a resolve exception, then the
+                        // current candidate is not resolvable for some
+                        // reason and should be removed from the list of
+                        // candidates. For now, just null it.
+                        candidates[candIdx] = null;
+                        rethrow = ex;
+                    }
+                }
+
+                // Remove any nulled candidates to create the final list
+                // of available candidates.
+                candidates = shrinkModuleArray(candidates);
+            }
+
+            // If no candidates exist at this point, then throw a
+            // resolve exception unless the import is optional.
+            if ((candidates.length == 0) && !imports[impIdx].isOptional())
+            {
+                // If we have received an exception while trying to populate
+                // the resolver map, rethrow that exception since it might
+                // be useful. NOTE: This is not necessarily the "only"
+                // correct exception, since it is possible that multiple
+                // candidates were not resolvable, but it is better than
+                // nothing.
+                if (rethrow != null)
+                {
+                    throw rethrow;
+                }
+                else
+                {
+                    throw new ResolveException(
+                            "Unable to resolve.", module, imports[impIdx]);
+                }
+            }
+            else if (candidates.length > 0)
+            {
+                nodeList.add(
+                    new ResolverNode(module, imports[impIdx], candidates));
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This method searches the resolver solution space for a consistent
+     * set of modules to resolve all transitive imports that must be resolved
+     * as a result of resolving the root module. A consistent set of modules
+     * is one where the "uses" relationships of the exported packages for
+     * the selected provider modules do not conflict with each other. A
+     * conflict can occur when the constraints on two or more modules result
+     * in multiple providers for the same package in the same class space.
+     * </p>
+     * @param resolverMap a map containing all potential modules that may need
+     *        to be resolved and the candidates to resolve them.
+     * @param rootModule the module that is the root of the resolve operation.
+     * @throws ResolveException if no consistent set of candidates can be
+     *         found to resolve the root module.
+    **/
+    private void findConsistentClassSpace(Map resolverMap, Module rootModule)
+        throws ResolveException
+    {
+        List resolverList = null;
+
+        // Test the current set of candidates to determine if they
+        // are consistent. Keep looping until we find a consistent
+        // set or an exception is thrown.
+        Map cycleMap = new HashMap();
+        while (!isClassSpaceConsistent(resolverMap, rootModule, cycleMap))
+        {
+m_logger.log(
+    LogWrapper.LOG_DEBUG,
+    "Constraint violation detected, will try to repair.");
+
+            // The incrementCandidateConfiguration() method requires a
+            // ordered access to the resolver map, so we will create
+            // a reusable list once right here.
+            if (resolverList == null)
+            {
+                resolverList = new ArrayList();
+                for (Iterator iter = resolverMap.entrySet().iterator();
+                    iter.hasNext(); )
+                {
+                    resolverList.add((List) ((Map.Entry) iter.next()).getValue());
+                }
+            }
+
+            // Increment the candidate configuration so we can test again.
+            incrementCandidateConfiguration(resolverList);
+
+            // Clear the cycle map.
+            cycleMap.clear();
+        }
+    }
+
+    private boolean isClassSpaceConsistent(
+        Map resolverMap, Module rootModule, Map cycleMap)
+    {
+        // We do not need to verify that already resolved modules
+        // have consistent class spaces because they should be
+        // consistent by definition. Also, if the root module is
+        // part of a cycle, then just assume it is true.
+        if (getResolvedAttr(rootModule).booleanValue() ||
+            (cycleMap.get(rootModule) != null))
+        {
+            return true;
+        }
+
+        // Add to cycle map for future reference.
+        cycleMap.put(rootModule, rootModule);
+
+        // Create an implicit "uses" constraint for every exported package
+        // of the root module that is not also imported; uses constraints
+        // for exported packages that are also imported will be taken
+        // care of as part of normal import package processing.
+        R4Package[] exports = (R4Package[]) getExportsAttr(rootModule);
+        Map usesMap = new HashMap();
+        for (int i = 0; i < exports.length; i++)
+        {
+            // Ignore exports that are also imported, since they
+            // will be taken care of when verifying import constraints.
+            if (getImportPackage(rootModule, exports[i].getId()) == null)
+            {
+                usesMap.put(exports[i].getId(), rootModule);
+            }
+        }
+
+        // Loop through the current candidates for the module's imports
+        // (available in the resolver node list of the resolver map) and
+        // calculate the uses constraints for each of the currently
+        // selected candidates for resolving the imports. Compare each
+        // candidate's constraints to the existing constraints to check
+        // for conflicts.
+        List nodeList = (List) resolverMap.get(rootModule);
+        for (int nodeIdx = 0; nodeIdx < nodeList.size(); nodeIdx++)
+        {
+            // Verify that the current candidate does not violate
+            // any "uses" constraints of existing candidates by
+            // calculating the candidate's transitive "uses" constraints
+            // for the provided package and testing whether they
+            // overlap with existing constraints.
+
+            // First, get the resolver node.
+            ResolverNode node = (ResolverNode) nodeList.get(nodeIdx);
+
+            // Verify that the current candidate itself has a consistent
+            // class space.
+            if (!isClassSpaceConsistent(
+                resolverMap, node.m_candidates[node.m_idx], cycleMap))
+            {
+                return false;
+            }
+
+            // Get the exported package from the current candidate that
+            // will be used to resolve the root module's import.
+            R4Package candidatePkg = getExportPackage(
+                node.m_candidates[node.m_idx], node.m_pkg.getId());
+
+            // Calculate the "uses" dependencies implied by the candidate's
+            // exported package with respect to the currently selected
+            // candidates in the resolver map.
+            Map candUsesMap = calculateUsesDependencies(
+                resolverMap,
+                node.m_candidates[node.m_idx],
+                candidatePkg,
+                new HashMap());
+//System.out.println("MODULE " + rootModule + " USES    " + usesMap);
+//System.out.println("CANDIDATE " + node.m_candidates[node.m_idx] + " USES " + candUsesMap);
+
+            // Iterate through the root module's current set of transitive
+            // "uses" constraints and compare them with the candidate's
+            // transitive set of constraints.
+            Iterator usesIter = candUsesMap.entrySet().iterator();
+            while (usesIter.hasNext())
+            {
+                // If the candidate's uses constraints overlap with
+                // the existing uses constraints, but refer to a
+                // different provider, then the class space is not
+                // consistent; thus, return false.
+                Map.Entry entry = (Map.Entry) usesIter.next();
+                if ((usesMap.get(entry.getKey()) != null) &&
+                    (usesMap.get(entry.getKey()) != entry.getValue()))
+                {
+                    return false;
+                }
+            }
+
+            // Since the current candidate's uses constraints did not
+            // conflict with existing constraints, merge all constraints
+            // and keep testing the remaining candidates for the other
+            // imports of the root module.
+            usesMap.putAll(candUsesMap);
+        }
+
+        return true;
+    }
+
+    private Map calculateUsesDependencies(
+        Map resolverMap, Module module, R4Package exportPkg, Map usesMap)
+    {
+// TODO: CAN THIS BE OPTIMIZED?
+// TODO: IS THIS CYCLE CHECK CORRECT??
+// TODO: WHAT HAPPENS THERE ARE OVERLAPS WHEN CALCULATING USES??
+//       MAKE AN EXAMPLE WHERE TWO DEPENDENCIES PROVIDE SAME PACKAGE.
+        // Make sure we are not in a cycle.
+        if (usesMap.get(exportPkg.getId()) != null)
+        {
+            return usesMap;
+        }
+
+        // The target package at least uses itself,
+        // so add it to the uses map.
+        usesMap.put(exportPkg.getId(), module);
+
+        // Get the "uses" constraints for the target export
+        // package and calculate the transitive uses constraints
+        // of any used packages.
+        String[] uses = exportPkg.getUses();
+        List nodeList = (List) resolverMap.get(module);
+
+        // We need to walk the transitive closure of "uses" relationships
+        // for the current export package to calculate the entire set of
+        // "uses" constraints.
+        for (int usesIdx = 0; usesIdx < uses.length; usesIdx++)
+        {
+            // There are two possibilities at this point: 1) we are dealing
+            // with an already resolved bundle or 2) we are dealing with a
+            // bundle that has not yet been resolved. In case 1, there will
+            // be no resolver node in the resolver map, so we just need to
+            // examine the bundle directly to determine its exact constraints.
+            // In case 2, there will be a resolver node in the resolver map,
+            // so we will use that to determine the potential constraints of
+            // potential candidate for resolving the import.
+
+            // This is case 1, described in the comment above.
+            if (nodeList == null)
+            {
+                // Get the actual exporter from the wire or if there
+                // is no wire, then get the export is from the module
+                // itself.
+                R4Wire wire = getWire(module, uses[usesIdx]);
+                if (wire != null)
+                {
+                    usesMap = calculateUsesDependencies(
+                        resolverMap, wire.m_module, wire.m_pkg, usesMap);
+                }
+                else
+                {
+                    exportPkg = getExportPackage(module, uses[usesIdx]);
+                    if (exportPkg != null)
+                    {
+                        usesMap = calculateUsesDependencies(
+                            resolverMap, module, exportPkg, usesMap);
+                    }
+                }
+            }
+            // This is case 2, described in the comment above.
+            else
+            {
+                // First, get the resolver node for the "used" package.
+                ResolverNode node = null;
+                for (int nodeIdx = 0;
+                    (node == null) && (nodeIdx < nodeList.size());
+                    nodeIdx++)
+                {
+                    node = (ResolverNode) nodeList.get(nodeIdx);
+                    if (!node.m_pkg.getId().equals(uses[usesIdx]))
+                    {
+                        node = null;
+                    }
+                }
+    
+                // If there is a resolver node for the "used" package,
+                // then this means that the module imports the package
+                // and we need to recursively add the constraints of
+                // the potential exporting module.
+                if (node != null)
+                {
+                    usesMap = calculateUsesDependencies(
+                        resolverMap,
+                        node.m_candidates[node.m_idx],
+                        getExportPackage(node.m_candidates[node.m_idx], node.m_pkg.getId()),
+                        usesMap);
+                }
+                // If there was no resolver node for the "used" package,
+                // then this means that the module exports the package
+                // and we need to recursively add the constraints of this
+                // other exported package of this module.
+                else if (getExportPackage(module, uses[usesIdx]) != null)
+                {
+                    usesMap = calculateUsesDependencies(
+                        resolverMap,
+                        module,
+                        getExportPackage(module, uses[usesIdx]),
+                        usesMap);
+                }
+            }
+        }
+
+        return usesMap;
+    }
+
+    /**
+     * <p>
+     * This method <i>increments</i> the current candidate configuration
+     * in the specified resolver list, which contains resolver node lists
+     * for all of the candidates for all of the imports that need to be
+     * resolved. This method performs its function by treating the current
+     * candidate index variable in each resolver node as part of a big
+     * counter. In other words, it increments the least significant index.
+     * If the index overflows it sets it back to zero and carries the
+     * overflow to the next significant index and so on. Using this approach
+     * it checks every possible combination for a solution.
+     * </p>
+     * <p>
+     * This method is inefficient and a better approach is necessary. For
+     * example, it does not take into account which imports are actually
+     * being used, it just increments starting at the beginning of the list.
+     * This means that it could be modifying candidates that are not relevant
+     * to the current configuration and re-testing even though nothing has
+     * really changed. It needs to be smarter.
+     * </p>
+     * @param resolverList an ordered list of resolver node lists for all
+     *        the candidates of the potential imports that need to be
+     *        resolved.
+     * @throws ResolveException if the increment overflows the entire list,
+     *         signifying no consistent configurations exist.
+    **/
+    private void incrementCandidateConfiguration(List resolverList)
+        throws ResolveException
+    {
+        for (int i = 0; i < resolverList.size(); i++)
+        {
+            List nodeList = (List) resolverList.get(i);
+            for (int j = 0; j < nodeList.size(); j++)
+            {
+                ResolverNode node = (ResolverNode) nodeList.get(j);
+                // See if we can increment the node, without overflowing
+                // the candidate array bounds.
+                if ((node.m_idx + 1) < node.m_candidates.length)
+                {
+                    node.m_idx++;
+                    return;
+                }
+                // If the index will overflow the candidate array bounds,
+                // then set the index back to zero and try to increment
+                // the next candidate.
+                else
+                {
+                    node.m_idx = 0;
+                }
+            }
+        }
+        throw new ResolveException(
+            "Unable to resolve due to constraint violation.", null, null);
+    }
+
+    private Map createWires(Map resolverMap, Module rootModule)
+    {
+        Map resolvedModuleWireMap =
+            populateWireMap(resolverMap, rootModule, new HashMap());
+        Iterator iter = resolvedModuleWireMap.entrySet().iterator();
+        while (iter.hasNext())
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            Module module = (Module) entry.getKey();
+            R4Wire[] wires = (R4Wire[]) entry.getValue();
+
+            // Set the module's resolved and wiring attribute.
+            module.setAttribute(RESOLVED_ATTR, Boolean.TRUE);
+            // Only add wires attribute if some exist; export
+            // only modules may not have wires.
+            if (wires.length > 0)
+            {
+                module.setAttribute(WIRING_ATTR, wires);
+            }
+
+            // Remove the wire's exporting module from the "available"
+            // package map and put it into the "in use" package map;
+            // these steps may be a no-op.
+            for (int wireIdx = 0;
+                (wires != null) && (wireIdx < wires.length);
+                wireIdx++)
+            {
+m_logger.log(LogWrapper.LOG_DEBUG, "WIRE: [" + module + "] " + wires[wireIdx]);
+                // First remove the wire module from "available" package map.
+                Module[] modules = (Module[]) m_availPkgMap.get(wires[wireIdx].m_pkg.getId());
+                modules = removeModuleFromArray(modules, wires[wireIdx].m_module);
+                m_availPkgMap.put(wires[wireIdx].m_pkg.getId(), modules);
+
+                // Also remove any exported packages from the "available"
+                // package map that are from the module associated with
+                // the current wires where the exported packages were not
+                // actually exported; an export may not be exported if
+                // the module also imports the same package and was wired
+                // to a different module. If the exported package is not
+                // actually exported, then we just want to remove it
+                // completely, since it cannot be used.
+                if (wires[wireIdx].m_module != module)
+                {
+                    modules = (Module[]) m_availPkgMap.get(wires[wireIdx].m_pkg.getId());
+                    modules = removeModuleFromArray(modules, module);
+                    m_availPkgMap.put(wires[wireIdx].m_pkg.getId(), modules);
+                }
+
+                // Add the module of the wire to the "in use" package map.
+                modules = (Module[]) m_inUsePkgMap.get(wires[wireIdx].m_pkg.getId());
+                modules = addModuleToArray(modules, wires[wireIdx].m_module);
+                m_inUsePkgMap.put(wires[wireIdx].m_pkg.getId(), modules);
+            }
+        }
+        return resolvedModuleWireMap;
+    }
+
+    private Map populateWireMap(Map resolverMap, Module module, Map wireMap)
+    {
+        // If the module is already resolved or it is part of
+        // a cycle, then just return the wire map.
+        if (getResolvedAttr(module).booleanValue() ||
+            (wireMap.get(module) != null))
+        {
+            return wireMap;
+        }
+
+        List nodeList = (List) resolverMap.get(module);
+        R4Wire[] wires = new R4Wire[nodeList.size()];
+
+        // Put the module in the wireMap with an empty wire array;
+        // we do this early so we can use it to detect cycles.
+        wireMap.put(module, wires);
+
+        // Loop through each resolver node and create a wire
+        // for the selected candidate for the associated import.
+        for (int nodeIdx = 0; nodeIdx < nodeList.size(); nodeIdx++)
+        {
+            // Get the import's associated resolver node.
+            ResolverNode node = (ResolverNode) nodeList.get(nodeIdx);
+
+            // Add the candidate to the list of wires.
+            R4Package exportPkg =
+                getExportPackage(node.m_candidates[node.m_idx], node.m_pkg.getId());
+            wires[nodeIdx] = new R4Wire(exportPkg, node.m_candidates[node.m_idx]);
+
+            // Create the wires for the selected candidate module.
+            wireMap = populateWireMap(resolverMap, node.m_candidates[node.m_idx], wireMap);
+        }
+
+        return wireMap;
+    }
+
+// TODO: REMOVE THESE DEBUG METHODS.
+    private void dumpResolverMap(Map resolverMap)
+    {
+        Iterator iter = resolverMap.entrySet().iterator();
+        while (iter.hasNext())
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            ResolverNode node = (ResolverNode) entry.getValue();
+            System.out.println("MODULE " + node.m_module + " IMPORT " + node.m_pkg);
+            for (int i = 0; i < node.m_candidates.length; i++)
+            {
+                System.out.println("--> " + node.m_candidates[i]);
+            }
+        }
+    }
+
+    private void dumpAvailablePackages()
+    {
+        synchronized (m_mgr)
+        {
+            System.out.println("AVAILABLE PACKAGES:");
+            for (Iterator i = m_availPkgMap.entrySet().iterator(); i.hasNext(); )
+            {
+                Map.Entry entry = (Map.Entry) i.next();
+                System.out.println("  " + entry.getKey());
+                Module[] modules = (Module[]) entry.getValue();
+                for (int j = 0; j < modules.length; j++)
+                {
+                    System.out.println("    " + modules[j]);
+                }
+            }
+        }
+    }
+
+    private void dumpUsedPackages()
+    {
+        synchronized (m_mgr)
+        {
+            System.out.println("USED PACKAGES:");
+            for (Iterator i = m_inUsePkgMap.entrySet().iterator(); i.hasNext(); )
+            {
+                Map.Entry entry = (Map.Entry) i.next();
+                System.out.println("  " + entry.getKey());
+                Module[] modules = (Module[]) entry.getValue();
+                for (int j = 0; j < modules.length; j++)
+                {
+                    System.out.println("    " + modules[j]);
+                }
+            }
+        }
+    }
+
+    /**
+     * This method returns a list of modules that have an export
+     * that is compatible with the given import identifier and version.
+     * @param pkgMap a map of export packages to exporting modules.
+     * @param target the target import package.
+     * @return an array of modules that have compatible exports or <tt>null</tt>
+     *         if none are found.
+    **/
+    protected Module[] getCompatibleExporters(Module[] modules, R4Package target)
+    {
+        // Create list of compatible exporters.
+        Module[] candidates = null;
+        for (int modIdx = 0; (modules != null) && (modIdx < modules.length); modIdx++)
+        {
+            // Get the modules export package for the target package.
+            R4Package exportPkg = getExportPackage(modules[modIdx], target.getId());
+            // If compatible, then add the candidate to the list.
+            if ((exportPkg != null) && (exportPkg.doesSatisfy(target)))
+            {
+                candidates = addModuleToArray(candidates, modules[modIdx]);
+            }
+        }
+
+        if (candidates == null)
+        {
+            return m_emptyModules;
+        }
+
+        return candidates;
+    }
+
+    public void moduleAdded(ModuleEvent event)
+    {
+        // When a module is added to the system, we need to initialize
+        // its resolved and wiring attributes and add its exports to
+        // the map of available exports.
+
+        // Synchronize on the module manager, since we don't want any
+        // bundles to be installed or removed.
+        synchronized (m_mgr)
+        {
+            // Add wiring attribute.
+            event.getModule().setAttribute(WIRING_ATTR, null);
+            // Add resolved attribute.
+            event.getModule().setAttribute(RESOLVED_ATTR, Boolean.FALSE);
+            // Add exports to available package map.
+            R4Package[] exports = getExportsAttr(event.getModule());
+            for (int i = 0; i < exports.length; i++)
+            {
+                Module[] modules = (Module[]) m_availPkgMap.get(exports[i].getId());
+
+                // We want to add the module into the list of available
+                // exporters in sorted order (descending version and
+                // ascending bundle identifier). Insert using a simple
+                // binary search algorithm.
+                if (modules == null)
+                {
+                    modules = new Module[] { event.getModule() };
+                }
+                else
+                {
+                    int top = 0, bottom = modules.length - 1, middle = 0;
+                    R4Version middleVersion = null;
+                    while (top <= bottom)
+                    {
+                        middle = (bottom - top) / 2 + top;
+                        middleVersion = getExportPackage(
+                            modules[middle], exports[i].getId()).getVersionLow();
+                        // Sort in reverse version order.
+                        int cmp = middleVersion.compareTo(exports[i].getVersionLow());
+                        if (cmp < 0)
+                        {
+                            bottom = middle - 1;
+                        }
+                        else if (cmp == 0)
+                        {
+                            // Sort further by ascending bundle ID.
+                            long middleId = getBundleIdFromModuleId(modules[middle].getId());
+                            long exportId = getBundleIdFromModuleId(event.getModule().getId());
+                            if (middleId < exportId)
+                            {
+                                top = middle + 1;
+                            }
+                            else
+                            {
+                                bottom = middle - 1;
+                            }
+                        }
+                        else
+                        {
+                            top = middle + 1;
+                        }
+                    }
+
+                    Module[] newMods = new Module[modules.length + 1];
+                    System.arraycopy(modules, 0, newMods, 0, top);
+                    System.arraycopy(modules, top, newMods, top + 1, modules.length - top);
+                    newMods[top] = event.getModule();
+                    modules = newMods;
+                }
+
+                m_availPkgMap.put(exports[i].getId(), modules);
+            }
+        }
+    }
+
+    public void moduleReset(ModuleEvent event)
+    {
+        moduleRemoved(event);
+    }
+
+    public void moduleRemoved(ModuleEvent event)
+    {
+        // When a module is removed from the system, we need remove
+        // its exports from the "in use" and "available" package maps.
+
+        // Synchronize on the module manager, since we don't want any
+        // bundles to be installed or removed.
+        synchronized (m_mgr)
+        {
+            // Remove exports from package maps.
+            R4Package[] pkgs = getExportsAttr(event.getModule());
+            for (int i = 0; i < pkgs.length; i++)
+            {
+                // Remove from "available" package map.
+                Module[] modules = (Module[]) m_availPkgMap.get(pkgs[i].getId());
+                if (modules != null)
+                {
+                    modules = removeModuleFromArray(modules, event.getModule());
+                    m_availPkgMap.put(pkgs[i].getId(), modules);
+                }
+                // Remove from "in use" package map.
+                modules = (Module[]) m_inUsePkgMap.get(pkgs[i].getId());
+                if (modules != null)
+                {
+                    modules = removeModuleFromArray(modules, event.getModule());
+                    m_inUsePkgMap.put(pkgs[i].getId(), modules);
+                }
+            }
+        }
+    }
+
+    // This is duplicated from BundleInfo and probably shouldn't be,
+    // but its functionality is needed by the moduleAdded() callback.
+    protected static long getBundleIdFromModuleId(String id)
+    {
+        try
+        {
+            String bundleId = (id.indexOf('.') >= 0)
+                ? id.substring(0, id.indexOf('.')) : id;
+            return Long.parseLong(bundleId);
+        }
+        catch (NumberFormatException ex)
+        {
+            return -1;
+        }
+    }
+
+    //
+    // Event handling methods for validation events.
+    //
+
+    /**
+     * Adds a resolver listener to the search policy. Resolver
+     * listeners are notified when a module is resolve and/or unresolved
+     * by the search policy.
+     * @param l the resolver listener to add.
+    **/
+    public void addResolverListener(ResolveListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        // Use the m_noListeners object as a lock.
+        synchronized (m_emptyListeners)
+        {
+            // If we have no listeners, then just add the new listener.
+            if (m_listeners == m_emptyListeners)
+            {
+                m_listeners = new ResolveListener[] { l };
+            }
+            // Otherwise, we need to do some array copying.
+            // Notice, the old array is always valid, so if
+            // the dispatch thread is in the middle of a dispatch,
+            // then it has a reference to the old listener array
+            // and is not affected by the new value.
+            else
+            {
+                ResolveListener[] newList = new ResolveListener[m_listeners.length + 1];
+                System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+                newList[m_listeners.length] = l;
+                m_listeners = newList;
+            }
+        }
+    }
+
+    /**
+     * Removes a resolver listener to this search policy.
+     * @param l the resolver listener to remove.
+    **/
+    public void removeResolverListener(ResolveListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        // Use the m_emptyListeners object as a lock.
+        synchronized (m_emptyListeners)
+        {
+            // Try to find the instance in our list.
+            int idx = -1;
+            for (int i = 0; i < m_listeners.length; i++)
+            {
+                if (m_listeners[i].equals(l))
+                {
+                    idx = i;
+                    break;
+                }
+            }
+
+            // If we have the instance, then remove it.
+            if (idx >= 0)
+            {
+                // If this is the last listener, then point to empty list.
+                if (m_listeners.length == 1)
+                {
+                    m_listeners = m_emptyListeners;
+                }
+                // Otherwise, we need to do some array copying.
+                // Notice, the old array is always valid, so if
+                // the dispatch thread is in the middle of a dispatch,
+                // then it has a reference to the old listener array
+                // and is not affected by the new value.
+                else
+                {
+                    ResolveListener[] newList = new ResolveListener[m_listeners.length - 1];
+                    System.arraycopy(m_listeners, 0, newList, 0, idx);
+                    if (idx < newList.length)
+                    {
+                        System.arraycopy(m_listeners, idx + 1, newList, idx,
+                            newList.length - idx);
+                    }
+                    m_listeners = newList;
+                }
+            }
+        }
+    }
+
+    /**
+     * Fires a validation event for the specified module.
+     * @param module the module that was resolved.
+    **/
+    protected void fireModuleResolved(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ResolveListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(m_mgr, module);
+            }
+            listeners[i].moduleResolved(event);
+        }
+    }
+
+    /**
+     * Fires an unresolved event for the specified module.
+     * @param module the module that was unresolved.
+    **/
+    protected void fireModuleUnresolved(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ResolveListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(m_mgr, module);
+            }
+            listeners[i].moduleUnresolved(event);
+        }
+    }
+
+    //
+    // Static utility methods.
+    //
+
+    public static Boolean getResolvedAttr(Module m)
+    {
+        Boolean b =
+            (Boolean) m.getAttribute(RESOLVED_ATTR);
+        if (b == null)
+        {
+            b = Boolean.FALSE;
+        }
+        return b;
+    }
+
+    public static R4Package[] getExportsAttr(Module m)
+    {
+        R4Package[] attr =
+            (R4Package[]) m.getAttribute(EXPORTS_ATTR);
+        return (attr == null) ? m_emptyPackages : attr;
+    }
+
+    public static R4Package getExportPackage(Module m, String id)
+    {
+        R4Package[] pkgs = getExportsAttr(m);
+        for (int i = 0; (pkgs != null) && (i < pkgs.length); i++)
+        {
+            if (pkgs[i].getId().equals(id))
+            {
+                return pkgs[i];
+            }
+        }
+        return null;
+    }
+
+    public static R4Package[] getImportsAttr(Module m)
+    {
+        R4Package[] attr =
+            (R4Package[]) m.getAttribute(IMPORTS_ATTR);
+        return (attr == null) ? m_emptyPackages: attr;
+    }
+
+    public static R4Package getImportPackage(Module m, String id)
+    {
+        R4Package[] pkgs = getImportsAttr(m);
+        for (int i = 0; (pkgs != null) && (i < pkgs.length); i++)
+        {
+            if (pkgs[i].getId().equals(id))
+            {
+                return pkgs[i];
+            }
+        }
+        return null;
+    }
+
+    public static R4Package[] getDynamicImportsAttr(Module m)
+    {
+        R4Package[] attr =
+            (R4Package[]) m.getAttribute(DYNAMICIMPORTS_ATTR);
+        return (attr == null) ? m_emptyPackages: attr;
+    }
+
+    public static R4Package getDynamicImportPackage(Module m, String id)
+    {
+        R4Package[] pkgs = getDynamicImportsAttr(m);
+        for (int i = 0; (pkgs != null) && (i < pkgs.length); i++)
+        {
+            if (pkgs[i].getId().equals(id))
+            {
+                return pkgs[i];
+            }
+        }
+        return null;
+    }
+
+    public static R4Wire[] getWiringAttr(Module m)
+    {
+        R4Wire[] attr =
+            (R4Wire[]) m.getAttribute(WIRING_ATTR);
+        if (attr == null)
+        {
+            attr = m_emptyWires;
+        }
+        return attr;
+    }
+
+    public static R4Wire getWire(Module m, String id)
+    {
+        R4Wire[] wires = getWiringAttr(m);
+        for (int i = 0; (wires != null) && (i < wires.length); i++)
+        {
+            if (wires[i].m_pkg.getId().equals(id))
+            {
+                return wires[i];
+            }
+        }
+        return null;
+    }
+
+    public static boolean isModuleInArray(Module[] modules, Module m)
+    {
+        // Verify that the module is not already in the array.
+        for (int i = 0; (modules != null) && (i < modules.length); i++)
+        {
+            if (modules[i] == m)
+            {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+
+    public static Module[] addModuleToArray(Module[] modules, Module m)
+    {
+        // Verify that the module is not already in the array.
+        for (int i = 0; (modules != null) && (i < modules.length); i++)
+        {
+            if (modules[i] == m)
+            {
+                return modules;
+            }
+        }
+
+        if (modules != null)
+        {
+            Module[] newModules = new Module[modules.length + 1];
+            System.arraycopy(modules, 0, newModules, 0, modules.length);
+            newModules[modules.length] = m;
+            modules = newModules;
+        }
+        else
+        {
+            modules = new Module[] { m };
+        }
+
+        return modules;
+    }
+
+    public static Module[] removeModuleFromArray(Module[] modules, Module m)
+    {
+        if (modules == null)
+        {
+            return m_emptyModules;
+        }
+
+        int idx = -1;
+        for (int i = 0; i < modules.length; i++)
+        {
+            if (modules[i] == m)
+            {
+                idx = i;
+                break;
+            }
+        }
+
+        if (idx >= 0)
+        {
+            // If this is the module, then point to empty list.
+            if ((modules.length - 1) == 0)
+            {
+                modules = m_emptyModules;
+            }
+            // Otherwise, we need to do some array copying.
+            else
+            {
+                Module[] newModules= new Module[modules.length - 1];
+                System.arraycopy(modules, 0, newModules, 0, idx);
+                if (idx < newModules.length)
+                {
+                    System.arraycopy(
+                        modules, idx + 1, newModules, idx, newModules.length - idx);
+                }
+                modules = newModules;
+            }
+        }
+        return modules;
+    }
+
+// TODO: INVESTIGATE GENERIC ARRAY GROWING/SHRINKING.
+    private static R4Wire[] shrinkWireArray(R4Wire[] wires)
+    {
+        if (wires == null)
+        {
+            return m_emptyWires;
+        }
+
+        int count = 0;
+        for (int i = 0; i < wires.length; i++)
+        {
+            if (wires[i] == null)
+            {
+                count++;
+            }
+        }
+
+        if (count > 0)
+        {
+            R4Wire[] newWires = new R4Wire[wires.length - count];
+            count = 0;
+            for (int i = 0; i < wires.length; i++)
+            {
+                if (wires[i] != null)
+                {
+                    newWires[count++] = wires[i];
+                }
+            }
+            wires = newWires;
+        }
+
+        return wires;
+    }
+
+    private static Module[] shrinkModuleArray(Module[] modules)
+    {
+        if (modules == null)
+        {
+            return m_emptyModules;
+        }
+
+        int count = 0;
+        for (int i = 0; i < modules.length; i++)
+        {
+            if (modules[i] == null)
+            {
+                count++;
+            }
+        }
+
+        if (count > 0)
+        {
+            Module[] newModules = new Module[modules.length - count];
+            count = 0;
+            for (int i = 0; i < modules.length; i++)
+            {
+                if (modules[i] != null)
+                {
+                    newModules[count++] = modules[i];
+                }
+            }
+            modules = newModules;
+        }
+
+        return modules;
+    }
+
+    private static class ResolverNode
+    {
+        public Module m_module = null;
+        public R4Package m_pkg = null;
+        public Module[] m_candidates = null;
+        public int m_idx = 0;
+        public boolean m_visited = false;
+        public ResolverNode(Module module, R4Package pkg, Module[] candidates)
+        {
+            m_module = module;
+            m_pkg = pkg;
+            m_candidates = candidates;
+            if (getResolvedAttr(m_module).booleanValue())
+            {
+                m_visited = true;
+            }
+        }
+    }
+
+    // Utility class to get the class context from the security manager.
+    private static class SecurityManagerX extends SecurityManager
+    {
+        public Class[] getClassContext()
+        {
+            return super.getClassContext();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Version.java b/src/org/apache/osgi/framework/searchpolicy/R4Version.java
new file mode 100644
index 0000000..e7f11fc
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Version.java
@@ -0,0 +1,190 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.searchpolicy;
+
+import java.util.StringTokenizer;
+
+public class R4Version implements Comparable
+{
+    private int m_major = 0;
+    private int m_minor = 0;
+    private int m_micro = 0;
+    private String m_qualifier = "";
+    private boolean m_isInclusive = true;
+
+    private static final String SEPARATOR = ".";
+
+    public R4Version(String versionString)
+    {
+        this(versionString, true);
+    }
+
+    public R4Version(String versionString, boolean isInclusive)
+    {
+        if (versionString == null)
+        {
+            versionString = "0.0.0";
+        }
+        Object[] objs = parseVersion(versionString);
+        m_major = ((Integer) objs[0]).intValue();
+        m_minor = ((Integer) objs[1]).intValue();
+        m_micro = ((Integer) objs[2]).intValue();
+        m_qualifier = (String) objs[3];
+        m_isInclusive = isInclusive;
+    }
+
+    private static Object[] parseVersion(String versionString)
+    {
+        String s = versionString.trim();
+        Object[] objs = new Object[4];
+        objs[0] = objs[1] = objs[2] = new Integer(0);
+        objs[3] = "";
+        StringTokenizer tok = new StringTokenizer(s, SEPARATOR);
+        try
+        {
+            objs[0] = Integer.valueOf(tok.nextToken());
+            if (tok.hasMoreTokens())
+            {
+                objs[1] = Integer.valueOf(tok.nextToken());
+                if (tok.hasMoreTokens())
+                {
+                    objs[2] = Integer.valueOf(tok.nextToken());
+                    if (tok.hasMoreTokens())
+                    {
+                        objs[3] = tok.nextToken();
+                    }
+                }
+            }
+        }
+        catch (NumberFormatException ex)
+        {
+            throw new IllegalArgumentException("Invalid version: " + versionString);
+        }
+
+        if ((((Integer) objs[0]).intValue() < 0) ||
+            (((Integer) objs[0]).intValue() < 0) ||
+            (((Integer) objs[0]).intValue() < 0))
+        {
+            throw new IllegalArgumentException("Invalid version: " + versionString);
+        }
+
+        return objs;
+    }
+
+    public boolean equals(Object object)
+    {
+        if (!(object instanceof R4Version))
+        {
+            return false;
+        }
+        R4Version v = (R4Version) object;
+        return
+            (v.getMajorComponent() == m_major) &&
+            (v.getMinorComponent() == m_minor) &&
+            (v.getMicroComponent() == m_micro) &&
+            (v.getQualifierComponent().equals(m_qualifier));
+    }
+
+    public int getMajorComponent()
+    {
+        return m_major;
+    }
+
+    public int getMinorComponent()
+    {
+        return m_minor;
+    }
+
+    public int getMicroComponent()
+    {
+        return m_micro;
+    }
+
+    public String getQualifierComponent()
+    {
+        return m_qualifier;
+    }
+
+    public boolean isInclusive()
+    {
+        return m_isInclusive;
+    }
+
+    public int compareTo(Object o)
+    {
+        if (!(o instanceof R4Version))
+            throw new ClassCastException();
+
+        if (equals(o))
+            return 0;
+
+        if (isGreaterThan((R4Version) o))
+            return 1;
+
+        return -1;
+    }
+
+    public boolean isGreaterThan(R4Version v)
+    {
+        if (v == null)
+        {
+            return false;
+        }
+
+        if (m_major > v.getMajorComponent())
+        {
+            return true;
+        }
+        if (m_major < v.getMajorComponent())
+        {
+            return false;
+        }
+        if (m_minor > v.getMinorComponent())
+        {
+            return true;
+        }
+        if (m_minor < v.getMinorComponent())
+        {
+            return false;
+        }
+        if (m_micro > v.getMicroComponent())
+        {
+            return true;
+        }
+        if (m_micro < v.getMicroComponent())
+        {
+            return false;
+        }
+        if (m_qualifier.compareTo(v.getQualifierComponent()) > 0)
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    public String toString()
+    {
+        if (m_qualifier.length() == 0)
+        {
+            return m_major + "." + m_minor + "." + m_micro; 
+        }
+        return m_major + "." + m_minor + "." + m_micro + "." + m_qualifier; 
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Wire.java b/src/org/apache/osgi/framework/searchpolicy/R4Wire.java
new file mode 100755
index 0000000..52ab909
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Wire.java
@@ -0,0 +1,36 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.searchpolicy;
+
+import org.apache.osgi.moduleloader.Module;
+
+public class R4Wire
+{
+    public R4Package m_pkg = null;
+    public Module m_module = null;
+    
+    public R4Wire(R4Package pkg, Module module)
+    {
+        m_pkg = pkg;
+        m_module = module;
+    }
+    
+    public String toString()
+    {
+        return m_pkg.getId() + " -> " + m_module;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/BundleListenerWrapper.java b/src/org/apache/osgi/framework/util/BundleListenerWrapper.java
new file mode 100644
index 0000000..033bac6
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/BundleListenerWrapper.java
@@ -0,0 +1,65 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.framework.*;
+
+public class BundleListenerWrapper extends ListenerWrapper implements BundleListener
+{
+    public BundleListenerWrapper(Bundle bundle, BundleListener l)
+    {
+        super(bundle,
+            (l instanceof SynchronousBundleListener)
+                ? SynchronousBundleListener.class : BundleListener.class,
+            l);
+    }
+
+    public void bundleChanged(final BundleEvent event)
+    {
+        // A bundle listener is either synchronous or asynchronous.
+        // If the bundle listener is synchronous, then deliver the
+        // event to bundles with a state of STARTING, STOPPING, or
+        // ACTIVE. If the listener is asynchronous, then deliver the
+        // event only to bundles that are STARTING or ACTIVE.
+        if (((getListenerClass() == SynchronousBundleListener.class) &&
+            ((getBundle().getState() == Bundle.STARTING) ||
+            (getBundle().getState() == Bundle.STOPPING) ||
+            (getBundle().getState() == Bundle.ACTIVE)))
+            ||
+            ((getBundle().getState() == Bundle.STARTING) ||
+            (getBundle().getState() == Bundle.ACTIVE)))
+        {
+            if (System.getSecurityManager() != null)
+            {
+                AccessController.doPrivileged(new PrivilegedAction() {
+                    public Object run()
+                    {
+                        ((BundleListener) getListener()).bundleChanged(event);
+                        return null;
+                    }
+                });
+            }
+            else
+            {
+                ((BundleListener) getListener()).bundleChanged(event);
+            }
+        }
+    }
+}
diff --git a/src/org/apache/osgi/framework/util/CaseInsensitiveMap.java b/src/org/apache/osgi/framework/util/CaseInsensitiveMap.java
new file mode 100644
index 0000000..6d805aa
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/CaseInsensitiveMap.java
@@ -0,0 +1,50 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.*;
+
+/**
+ * Simple utility class that creates a case-insensitive map by
+ * extending <tt>TreeMap</tt> and to use a case-insensitive
+ * comparator. Any keys put into this map will be converted to
+ * a <tt>String</tt> using the <tt>toString()</tt> method,
+ * since it is intended to compare strings.
+**/
+public class CaseInsensitiveMap extends TreeMap
+{
+    public CaseInsensitiveMap()
+    {
+        super(new Comparator() {
+            public int compare(Object o1, Object o2)
+            {
+                return o1.toString().compareToIgnoreCase(o2.toString());
+            }
+        });
+    }
+    
+    public CaseInsensitiveMap(Map map)
+    {
+        this();
+        putAll(map);
+    }
+    
+    public Object put(Object key, Object value)
+    {
+        return super.put(key.toString(), value);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/DispatchQueue.java b/src/org/apache/osgi/framework/util/DispatchQueue.java
new file mode 100644
index 0000000..9d70a9c
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/DispatchQueue.java
@@ -0,0 +1,420 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.*;
+
+import org.apache.osgi.framework.LogWrapper;
+
+/**
+ * This class implements an event dispatching queue to simplify delivering
+ * events to a list of event listener. To use this class, simply create an
+ * instance and use it to keep track of your event listeners, much like
+ * <tt>javax.swing.event.EventListenerList</tt>. To dispatch an event,
+ * simply create an instance of a <tt>Dispatcher</tt> and pass the instance
+ * to <tt>DispatchQueue.dispatch()</tt> method, for example:
+ * <pre>
+ *  Dispatcher d = new Dispatcher() {
+ *      public void dispatch(EventListener l, Object eventObj)
+ *      {
+ *          ((FooListener) l).fooXXX((FooEvent) eventObj);
+ *      }
+ *  };
+ *  FooEvent event = new FooEvent(this);
+ *  dispatchQueue.dispatch(d, FooListener.class, event);
+ * </pre>
+ * In the above code substitute a specific listener and event for the
+ * <tt>Foo</tt> listener and event. Since <tt>Dispatcher</tt>s are
+ * reusable, it is probably a good idea to create one for each type of
+ * event to be delivered and just reuse them everytime to avoid unnecessary
+ * memory allocation.
+ * <p>
+ * Currently, the <tt>DispatchQueue</tt> creates an internal thread with
+ * which all events are delivered; this means that events are never delivered
+ * using the caller's thread.
+**/
+public class DispatchQueue
+{
+    // Representation of an empty listener list.
+    private static final Object[] m_emptyList = new Object[0];
+
+    // The event listeners for a particular queue instance.
+    private Object[] m_listeners = m_emptyList;
+
+    // A single thread is used to deliver events for all dispatchers.
+    private static Thread m_thread = null;
+    private static String m_threadLock = "thread lock";
+    private static boolean m_stopping = false;
+    private static boolean m_stopped = false;
+
+    // List of dispatch requests.
+    private static final ArrayList m_requestList = new ArrayList();
+    // Cached dispatch requests to avoid memory allocation.
+    private static final ArrayList m_requestCache = new ArrayList();
+    // The logger for dispatch queue.
+    private static LogWrapper m_logger = null;
+
+    /**
+     * Constructs a dispatch queue and starts a dispather thread if
+     * necessary.
+    **/
+    public DispatchQueue(LogWrapper logger)
+    {
+        synchronized (m_threadLock)
+        {
+            // Start event dispatching thread if necessary.
+            if (m_thread == null)
+            {
+                m_logger = logger;
+                m_thread = new Thread(new Runnable() {
+                    public void run()
+                    {
+                        DispatchQueue.run();
+                    }
+                }, "FelixDispatchQueue");
+                m_thread.start();
+            }
+        }
+    }
+
+    /**
+     * Terminates the dispatching thread for a graceful shutdown
+     * of the dispatching queue; the caller will block until the
+     * dispatching thread has completed all pending dispatches.
+     * Since there is only one thread per all instances of
+     * <tt>DispatchQueue</tt>, this method should only be called
+     * prior to exiting the JVM.
+    **/
+    public static void shutdown()
+    {
+        synchronized (m_threadLock)
+        {
+            // Return if already stopped.
+            if (m_stopped)
+            {
+                return;
+            }
+
+            // Signal dispatch thread.
+            m_stopping = true;
+            synchronized (m_requestList)
+            {
+                m_requestList.notify();
+            }
+
+            // Wait for dispatch thread to stop.
+            while (!m_stopped)
+            {
+                try {
+                    m_threadLock.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+    }
+
+    public static LogWrapper getLogger()
+    {
+        return m_logger;
+    }
+
+    /**
+     * Returns a pointer to the array of event listeners. The array stores pairs
+     * of associated <tt>Class</tt> and <tt>EventListener</tt> objects; a pair
+     * corresponds to the arguments passed in to the <tt>addListener()</tt> method.
+     * Even numbered array elements are the class object and odd numbered elements
+     * are the corresponding event listener instance.
+     *
+     * @return guaranteed to return a non-null object array.
+    **/
+    public Object[] getListeners()
+    {
+        return m_listeners;
+    }
+
+    /**
+     * Returns the listener if it is already in the dispatch queue.
+     * @param clazz the class of the listener to find.
+     * @param l the listener instance to find.
+     * @return the listener instance or <tt>null</tt> if the listener was
+     *         not found.
+    **/
+    public EventListener getListener(Class clazz, EventListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // Lock the object.
+        synchronized (this)
+        {
+            // Try to find the instance in our list.
+            for (int i = 0; i < m_listeners.length; i += 2)
+            {
+                if ((m_listeners[i] == clazz) &&
+                    (m_listeners[i + 1].equals(l)))
+                {
+                    return (EventListener) m_listeners[i + 1];
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Adds a listener to the dispatch queue's listener list; the listener
+     * is then able to receive events.
+     *
+     * @param clazz the class object associated with the event listener type.
+     * @param l the instance of the event listener to add.
+    **/
+    public void addListener(Class clazz, EventListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // Lock the object.
+        synchronized (this)
+        {
+            // If we have no listeners, then just add the new listener.
+            if (m_listeners == m_emptyList)
+            {
+                m_listeners = new Object[] { clazz, l };
+            }
+            // Otherwise, we need to do some array copying.
+            // Notice, the old array is always valid, so if
+            // the dispatch thread is in the middle of a dispatch,
+            // then it has a reference to the old listener array
+            // and is not affected by the new value.
+            else
+            {
+                Object[] newList = new Object[m_listeners.length + 2];
+                System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+                newList[m_listeners.length] = clazz;
+                newList[m_listeners.length + 1] = l;
+                m_listeners = newList;
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from the dispatch queue's listener list; the listener
+     * is no longer able to receive events.
+     *
+     * @param clazz the class object associated with the event listener type.
+     * @param l the instance of the event listener to remove.
+    **/
+    public void removeListener(Class clazz, EventListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // Lock the object.
+        synchronized (this)
+        {
+            // Try to find the instance in our list.
+            int idx = -1;
+            for (int i = 0; i < m_listeners.length; i += 2)
+            {
+                if ((m_listeners[i] == clazz) &&
+                    (m_listeners[i + 1].equals(l)))
+                {
+                    idx = i;
+                    break;
+                }
+            }
+
+            // If we have the instance, then remove it.
+            if (idx >= 0)
+            {
+                // If this is the last listener, then point to empty list.
+                if ((m_listeners.length - 2) == 0)
+                {
+                    m_listeners = m_emptyList;
+                }
+                // Otherwise, we need to do some array copying.
+                // Notice, the old array is always valid, so if
+                // the dispatch thread is in the middle of a dispatch,
+                // then it has a reference to the old listener array
+                // and is not affected by the new value.
+                else
+                {
+                    Object[] newList  = new Object[m_listeners.length - 2];
+                    System.arraycopy(m_listeners, 0, newList, 0, idx);
+                    if (idx < newList.length)
+                    {
+                        System.arraycopy(m_listeners, idx + 2, newList, idx,
+                            newList.length - idx);
+                    }
+                    m_listeners = newList;
+                }
+            }
+        }
+    }
+
+    /**
+     * Dispatches an event to a set of event listeners using a specified
+     * dispatcher object.
+     *
+     * @param d the dispatcher used to actually dispatch the event; this
+     *          varies according to the type of event listener.
+     * @param clazz the class associated with the target event listener type;
+     *              only event listeners of this type will receive the event.
+     * @param eventObj the actual event object to dispatch.
+    **/
+    public void dispatch(Dispatcher d, Class clazz, EventObject eventObj)
+    {
+        dispatch(m_listeners, d, clazz, eventObj);
+    }
+
+    protected void dispatch(
+        Object[] listeners, Dispatcher d, Class clazz, EventObject eventObj)
+    {
+        // If dispatch thread is stopped, then ignore dispatch request.
+        if (m_stopped)
+        {
+            return;
+        }
+
+        // First get a dispatch request from the cache or
+        // create one if necessary.
+        DispatchRequest dr = null;
+        synchronized (m_requestCache)
+        {
+            if (m_requestCache.size() > 0)
+                dr = (DispatchRequest) m_requestCache.remove(0);
+            else
+                dr = new DispatchRequest();
+        }
+
+        // Initialize dispatch request.
+        dr.m_listeners = listeners;
+        dr.m_dispatcher = d;
+        dr.m_clazz = clazz;
+        dr.m_eventObj = eventObj;
+
+        // Lock the request list.
+        synchronized (m_requestList)
+        {
+            // Add our request to the list.
+            m_requestList.add(dr);
+            // Notify the dispatch thread that there is
+            // work to do.
+            m_requestList.notify();
+        }
+    }
+
+    private static void run()
+    {
+        DispatchRequest dr = null;
+        while (true)
+        {
+            // Lock the request list so we can try to get a
+            // dispatch request from it.
+            synchronized (m_requestList)
+            {
+                // Wait while there are no requests to dispatch. If the
+                // dispatcher thread is supposed to stop, then let the
+                // dispatcher thread exit the loop and stop.
+                while ((m_requestList.size() == 0) && !m_stopping)
+                {
+                    // Wait until some signals us for work.
+                    try {
+                        m_requestList.wait();
+                    } catch (InterruptedException ex) {
+                        m_logger.log(LogWrapper.LOG_ERROR, "DispatchQueue: Thread interrupted.", ex);
+                    }
+                }
+
+                // If there are no events to dispatch and shutdown
+                // has been called then exit, otherwise dispatch event.
+                if ((m_requestList.size() == 0) && (m_stopping))
+                {
+                    synchronized (m_threadLock)
+                    {
+                        m_stopped = true;
+                        m_threadLock.notifyAll();
+                    }
+                    return;
+                }
+
+                // Get the dispatch request.
+                dr = (DispatchRequest) m_requestList.remove(0);
+            }
+
+            // Deliver event outside of synchronized block
+            // so that we don't block other requests from being
+            // queued during event processing.
+
+            // Try to dispatch to the listeners.
+            if (dr.m_listeners.length > 0)
+            {
+                // Notify appropriate listeners.
+                for (int i = dr.m_listeners.length - 2; i >= 0; i -= 2)
+                {
+                    if (dr.m_listeners[i] == dr.m_clazz)
+                    {
+                        try {
+                            dr.m_dispatcher.dispatch(
+                                (EventListener) dr.m_listeners[i + 1], dr.m_eventObj);
+                        } catch (Throwable th) {
+                            m_logger.log(LogWrapper.LOG_ERROR, "DispatchQueue: Error during dispatch.", th);
+                        }
+                    }
+                }
+            }
+
+            // Put dispatch request in cache.
+            synchronized (m_requestCache)
+            {
+                m_requestCache.add(dr);
+            }
+        }
+    }
+
+    private static class DispatchRequest
+    {
+        public Object[] m_listeners = null;
+        public Dispatcher m_dispatcher = null;
+        public Class m_clazz = null;
+        public EventObject m_eventObj = null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/Dispatcher.java b/src/org/apache/osgi/framework/util/Dispatcher.java
new file mode 100644
index 0000000..a3c0192
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/Dispatcher.java
@@ -0,0 +1,51 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+/**
+ * This interface is used by <tt>DispatchQueue</tt> to dispatch events.
+ * Generally speaking, each type of event to dispatch will have an instance
+ * of a <tt>Dispatcher</tt> so that the dispatch queue can dispatch to
+ * the appropriate listener method for the specific listener type,
+ * for example:
+ * <pre>
+ *  Dispatcher d = new Dispatcher() {
+ *      public void dispatch(EventListener l, EventObject eventObj)
+ *      {
+ *          ((FooListener) l).fooXXX((FooEvent) eventObj);
+ *      }
+ *  };
+ *  FooEvent event = new FooEvent(this);
+ *  dispatchQueue.dispatch(d, FooListener.class, event);
+ * </pre>
+ * In the above code substitute a specific listener and event for the
+ * <tt>Foo</tt> listener and event. <tt>Dispatcher</tt>s can be reused, so
+ * it is a good idea to cache them to avoid unnecessary memory allocations.
+**/
+public interface Dispatcher
+{
+    /**
+     * Dispatch an event to a specified event listener.
+     *
+     * @param l the event listener to receive the event.
+     * @param eventObj the event to dispatch.
+    **/
+    public void dispatch(EventListener l, EventObject eventObj);
+}
diff --git a/src/org/apache/osgi/framework/util/FelixConstants.java b/src/org/apache/osgi/framework/util/FelixConstants.java
new file mode 100644
index 0000000..a98e4e3
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/FelixConstants.java
@@ -0,0 +1,60 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+public interface FelixConstants extends org.osgi.framework.Constants
+{
+    // Framework constants and values.
+    public static final String FRAMEWORK_VERSION_VALUE = "4.0";
+    public static final String FRAMEWORK_VENDOR_VALUE = "Apache";
+
+    // Framework constants and values.
+    public static final String FELIX_VERSION_PROPERTY = "felix.version";
+    public static final String FELIX_VERSION_VALUE = "2.0.0.alpha8";
+
+    // Miscellaneous manifest constants.
+    public static final String DIRECTIVE_SEPARATOR = ":=";
+    public static final String ATTRIBUTE_SEPARATOR = "=";
+    public static final String CLASS_PATH_SEPARATOR = ",";
+    public static final String CLASS_PATH_DOT = ".";
+    public static final String PACKAGE_SEPARATOR = ";";
+    public static final String VERSION_SEGMENT_SEPARATOR = ".";
+    public static final int VERSION_SEGMENT_COUNT = 3;
+
+    // Miscellaneous OSGi constants.
+    public static final String BUNDLE_URL_PROTOCOL = "bundle";
+
+    // Miscellaneous framework configuration property names.
+    public static final String AUTO_INSTALL_PROP = "felix.auto.install";
+    public static final String AUTO_START_PROP = "felix.auto.start";
+    public static final String EMBEDDED_EXECUTION_PROP = "felix.embedded.execution";
+    public static final String STRICT_OSGI_PROP = "felix.strict.osgi";
+    public static final String CACHE_CLASS_PROP = "felix.cache.class";
+    public static final String FRAMEWORK_STARTLEVEL_PROP
+        = "felix.startlevel.framework";
+    public static final String BUNDLE_STARTLEVEL_PROP
+        = "felix.startlevel.bundle";
+
+    // Start level-related constants.
+    public static final int FRAMEWORK_INACTIVE_STARTLEVEL = 0;
+    public static final int FRAMEWORK_DEFAULT_STARTLEVEL = 1;
+    public static final int SYSTEMBUNDLE_DEFAULT_STARTLEVEL = 0;
+    public static final int BUNDLE_DEFAULT_STARTLEVEL = 1;
+
+    // Miscellaneous properties values.
+    public static final String FAKE_URL_PROTOCOL_VALUE = "location:";
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/FelixDispatchQueue.java b/src/org/apache/osgi/framework/util/FelixDispatchQueue.java
new file mode 100644
index 0000000..bed03a4
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/FelixDispatchQueue.java
@@ -0,0 +1,147 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.osgi.framework.*;
+
+/**
+ * This is a subclass of <tt>DispatchQueue</tt> that specifically adds support
+ * for <tt>SynchronousBundleListener</tt>s; the OSGi specification
+ * says that synchronous bundle listeners should receive their events
+ * immediately, i.e., they should not be delivered on a separate thread
+ * like is the case with the <tt>DispatchQueue</tt>. To achieve this
+ * functionality, this class overrides the dispatch method to automatically
+ * fire any bundle events to synchronous bundle listeners using the
+ * calling thread. In order to ensure that synchronous bundle listeners
+ * do not receive an event twice, it wraps the passed in <tt>Dipatcher</tt>
+ * instance so that it filters synchronous bundle listeners.
+**/
+public class FelixDispatchQueue extends DispatchQueue
+{
+    public FelixDispatchQueue(LogWrapper logger)
+    {
+        super(logger);
+    }
+
+    /**
+     * Dispatches an event to a set of event listeners using a specified
+     * dispatcher object. This overrides the definition of the super class
+     * to deliver events to <tt>ServiceListener</tt>s and
+     * <tt>SynchronousBundleListener</tt>s using
+     * the calling thread instead of the event dispatching thread. All
+     * other events are still delivered asynchronously.
+     *
+     * @param dispatcher the dispatcher used to actually dispatch the event; this
+     *          varies according to the type of event listener.
+     * @param clazz the class associated with the target event listener type;
+     *              only event listeners of this type will receive the event.
+     * @param eventObj the actual event object to dispatch.
+    **/
+    public void dispatch(Dispatcher dispatcher, Class clazz, EventObject eventObj)
+    {
+        Object[] listeners = getListeners();
+
+        // If this is an event for service listeners, then dispatch it
+        // immediately since service events are never asynchronous.
+        if ((clazz == ServiceListener.class) && (listeners.length > 0))
+        {
+            // Notify appropriate listeners.
+            for (int i = listeners.length - 2; i >= 0; i -= 2)
+            {
+                // If the original listener is a synchronous bundle listener
+                // or a service listener, then dispatch event immediately
+                // per the specification.
+                ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+                if (lw.getListenerClass() == ServiceListener.class)
+                {
+                    try {
+                        dispatcher.dispatch(
+                            (EventListener) lw, eventObj);
+                    } catch (Throwable th) {
+                        getLogger().log(
+                            LogWrapper.LOG_ERROR,
+                            "FelixDispatchQueue: Error during dispatch.", th);
+                    }
+                }
+            }
+        }
+        // Dispatch bundle events to synchronous bundle listeners immediately,
+        // but deliver to standard bundle listeners asynchronously.
+        else if ((clazz == BundleListener.class) && (listeners.length > 0))
+        {
+            // Notify appropriate listeners.
+            for (int i = listeners.length - 2; i >= 0; i -= 2)
+            {
+                // If the original listener is a synchronous bundle listener,
+                // then dispatch event immediately per the specification.
+                ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+                if (lw.getListenerClass() == SynchronousBundleListener.class)
+                {
+                    try {
+                        dispatcher.dispatch(
+                            (EventListener) lw, eventObj);
+                    } catch (Throwable th) {
+                        getLogger().log(
+                            LogWrapper.LOG_ERROR,
+                            "FelixDispatchQueue: Error during dispatch.", th);
+                    }
+                }
+            }
+
+            // Wrap the dispatcher so that it ignores synchronous
+            // bundle listeners since they have already been dispatched.
+            IgnoreSynchronousDispatcher ignoreDispatcher = new IgnoreSynchronousDispatcher();
+            ignoreDispatcher.setDispatcher(dispatcher);
+
+            // Dispatch the bundle listener asynchronously.
+            dispatch(listeners, ignoreDispatcher, clazz, eventObj);
+        }
+        // All other events are dispatched asynchronously.
+        else
+        {
+            dispatch(listeners, dispatcher, clazz, eventObj);
+        }
+    }
+
+    private static class IgnoreSynchronousDispatcher implements Dispatcher
+    {
+        private Dispatcher m_dispatcher = null;
+
+        public void setDispatcher(Dispatcher dispatcher)
+        {
+            m_dispatcher = dispatcher;
+        }
+
+        public void dispatch(EventListener l, EventObject eventObj)
+        {
+            if (l instanceof ListenerWrapper)
+            {
+                ListenerWrapper lw = (ListenerWrapper) l;
+                // Do not dispatch events to synchronous listeners,
+                // since they are dispatched immediately above.
+                if (!(lw.getListenerClass() == SynchronousBundleListener.class))
+                {
+                    m_dispatcher.dispatch(l, eventObj);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/FrameworkListenerWrapper.java b/src/org/apache/osgi/framework/util/FrameworkListenerWrapper.java
new file mode 100644
index 0000000..630c78c
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/FrameworkListenerWrapper.java
@@ -0,0 +1,55 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.framework.*;
+
+public class FrameworkListenerWrapper extends ListenerWrapper implements FrameworkListener
+{
+    public FrameworkListenerWrapper(Bundle bundle, FrameworkListener l)
+    {
+        super(bundle, FrameworkListener.class, l);
+    }
+
+    public void frameworkEvent(final FrameworkEvent event)
+    {
+        // The spec says only active bundles receive asynchronous events,
+        // but we will include starting bundles too otherwise
+        // it is impossible to see everything.
+        if ((getBundle().getState() == Bundle.STARTING) ||
+            (getBundle().getState() == Bundle.ACTIVE))
+        {
+            if (System.getSecurityManager() != null)
+            {
+                AccessController.doPrivileged(new PrivilegedAction() {
+                    public Object run()
+                    {
+                        ((FrameworkListener) getListener()).frameworkEvent(event);
+                        return null;
+                    }
+                });
+            }
+            else
+            {
+                ((FrameworkListener) getListener()).frameworkEvent(event);
+            }
+        }
+    }
+}
diff --git a/src/org/apache/osgi/framework/util/IteratorToEnumeration.java b/src/org/apache/osgi/framework/util/IteratorToEnumeration.java
new file mode 100644
index 0000000..c457827
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/IteratorToEnumeration.java
@@ -0,0 +1,44 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+    private Iterator m_iter = null;
+
+    public IteratorToEnumeration(Iterator iter)
+    {
+        m_iter = iter;
+    }
+
+    public boolean hasMoreElements()
+    {
+        if (m_iter == null)
+            return false;
+        return m_iter.hasNext();
+    }
+
+    public Object nextElement()
+    {
+        if (m_iter == null)
+            return null;
+        return m_iter.next();
+    }
+}
diff --git a/src/org/apache/osgi/framework/util/LibraryInfo.java b/src/org/apache/osgi/framework/util/LibraryInfo.java
new file mode 100644
index 0000000..f444ef9
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/LibraryInfo.java
@@ -0,0 +1,179 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.*;
+
+import org.osgi.framework.Constants;
+
+public class LibraryInfo
+{
+    private String m_name = null;
+    private String[] m_osnames = null;
+    private String[] m_osversions = null;
+    private String[] m_processors = null;
+    private String[] m_languages = null;
+
+    public LibraryInfo(String name, String[] osnames, String[] osversions,
+        String[] processors, String[] languages)
+    {
+        m_name = name;
+        m_osnames = osnames;
+        m_osversions = osversions;
+        m_processors = processors;
+        m_languages = languages;
+    }
+
+    public LibraryInfo(LibraryInfo library)
+    {
+        m_name = library.m_name;
+        m_osnames = library.m_osnames;
+        m_osversions = library.m_osversions;
+        m_processors = library.m_processors;
+        m_languages = library.m_languages;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public String[] getOSNames()
+    {
+        return m_osnames;
+    }
+
+    public String[] getOSVersions()
+    {
+        return m_osversions;
+    }
+
+    public String[] getProcessors()
+    {
+        return m_processors;
+    }
+
+    public static LibraryInfo[] parse(String s)
+    {
+        try
+        {
+            if ((s == null) || (s.length() == 0))
+            {
+                return null;
+            }
+
+            // The tokens are separated by semicolons and may include
+            // any number of libraries (whose name starts with a "/")
+            // along with one set of associated properties.
+            StringTokenizer st = new StringTokenizer(s, ";");
+            String[] libs = new String[st.countTokens()];
+            List osNameList = new ArrayList();
+            List osVersionList = new ArrayList();
+            List processorList = new ArrayList();
+            List languageList = new ArrayList();
+            int libCount = 0;
+            while (st.hasMoreTokens())
+            {
+                String token = st.nextToken().trim();
+                if (token.indexOf('=') < 0)
+                {
+                    // Remove the slash, if necessary.
+                    libs[libCount] = (token.charAt(0) == '/')
+                        ? token.substring(1)
+                        : token;
+                    libCount++;
+                }
+                else
+                {
+                    // Check for valid native library properties; defined as
+                    // a property name, an equal sign, and a value.
+                    StringTokenizer stProp = new StringTokenizer(token, "=");
+                    if (stProp.countTokens() != 2)
+                    {
+                        throw new IllegalArgumentException(
+                            "Bundle manifest native library entry malformed: " + token);
+                    }
+                    String property = stProp.nextToken().trim().toLowerCase();
+                    String value = stProp.nextToken().trim();
+                    
+                    // Values may be quoted, so remove quotes if present.
+                    if (value.charAt(0) == '"')
+                    {
+                        // This should always be true, otherwise the
+                        // value wouldn't be properly quoted, but we
+                        // will check for safety.
+                        if (value.charAt(value.length() - 1) == '"')
+                        {
+                            value = value.substring(1, value.length() - 1);
+                        }
+                        else
+                        {
+                            value = value.substring(1);
+                        }
+                    }
+                    // Add the value to its corresponding property list.
+                    if (property.equals(Constants.BUNDLE_NATIVECODE_OSNAME))
+                    {
+                        osNameList.add(value);
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION))
+                    {
+                        osVersionList.add(value);
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR))
+                    {
+                        processorList.add(value);
+                    }
+                    else if (property.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE))
+                    {
+                        languageList.add(value);
+                    }
+                }
+            }
+
+            if (libCount == 0)
+            {
+                return null;
+            }
+
+            LibraryInfo[] libraries = new LibraryInfo[libCount];
+            for (int i = 0; i < libCount; i++)
+            {
+                libraries[i] =
+                    new LibraryInfo(
+                        libs[i],
+                        (String[]) osNameList.toArray(new String[0]),
+                        (String[]) osVersionList.toArray(new String[0]),
+                        (String[]) processorList.toArray(new String[0]),
+                        (String[]) languageList.toArray(new String[0]));
+            }
+
+            return libraries;
+
+        }
+        catch (RuntimeException ex)
+        {
+            ex.printStackTrace();
+            throw ex;
+        }
+    }
+
+    public String toString()
+    {
+        return m_name;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ListenerWrapper.java b/src/org/apache/osgi/framework/util/ListenerWrapper.java
new file mode 100644
index 0000000..0265e8d
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ListenerWrapper.java
@@ -0,0 +1,66 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.EventListener;
+
+import org.osgi.framework.Bundle;
+
+public class ListenerWrapper
+{
+    // The bundle associated with the listener.
+    private Bundle m_bundle = null;
+    // Listener class.
+    private Class m_class = null;
+    // The original listener.
+    private EventListener m_listener = null;
+
+    public ListenerWrapper(Bundle bundle, Class clazz, EventListener l)
+    {
+        m_bundle = bundle;
+        m_class = clazz;
+        m_listener = l;
+    }
+
+    public Bundle getBundle()
+    {
+        return m_bundle;
+    }
+
+    protected Class getListenerClass()
+    {
+        return m_class;
+    }
+
+    protected EventListener getListener()
+    {
+        return m_listener;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj instanceof ListenerWrapper)
+        {
+            return (((ListenerWrapper) obj).m_listener == m_listener);
+        }
+        else if (obj instanceof EventListener)
+        {
+            return (obj == m_listener);
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/MapToDictionary.java b/src/org/apache/osgi/framework/util/MapToDictionary.java
new file mode 100644
index 0000000..0bdd510
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/MapToDictionary.java
@@ -0,0 +1,92 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutatable.
+**/
+public class MapToDictionary extends Dictionary
+{
+    /**
+     * Map source.
+    **/
+    private Map m_map = null;
+
+    public MapToDictionary(Map map)
+    {
+        m_map = map;
+    }
+
+    public Enumeration elements()
+    {
+        if (m_map == null)
+        {
+            return null;
+        }
+        return new IteratorToEnumeration(m_map.values().iterator());
+    }
+
+    public Object get(Object key)
+    {
+        if (m_map == null)
+        {
+            return null;
+        }
+        return m_map.get(key);
+    }
+
+    public boolean isEmpty()
+    {
+        if (m_map == null)
+        {
+            return true;
+        }
+        return m_map.isEmpty();
+    }
+
+    public Enumeration keys()
+    {
+        if (m_map == null)
+        {
+            return null;
+        }
+        return new IteratorToEnumeration(m_map.keySet().iterator());
+    }
+
+    public Object put(Object key, Object value)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object remove(Object key)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public int size()
+    {
+        if (m_map == null)
+        {
+            return 0;
+        }
+        return m_map.size();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/MutablePropertyResolver.java b/src/org/apache/osgi/framework/util/MutablePropertyResolver.java
new file mode 100644
index 0000000..f5aaf84
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/MutablePropertyResolver.java
@@ -0,0 +1,22 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+public interface MutablePropertyResolver extends PropertyResolver
+{
+    public String put(String key, String value);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/MutablePropertyResolverImpl.java b/src/org/apache/osgi/framework/util/MutablePropertyResolverImpl.java
new file mode 100644
index 0000000..3be8c37
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/MutablePropertyResolverImpl.java
@@ -0,0 +1,44 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.util.Map;
+
+public class MutablePropertyResolverImpl implements MutablePropertyResolver
+{
+    private Map m_props = null;
+    
+    public MutablePropertyResolverImpl(Map props)
+    {
+        m_props = props;
+    }
+
+    public synchronized String put(String key, String value)
+    {
+        return (String) m_props.put(key, value);
+    }
+
+    public synchronized String get(String key)
+    {
+        return (String) m_props.get(key);
+    }
+
+    public synchronized String[] getKeys()
+    {
+        return (String[]) m_props.keySet().toArray(new String[0]);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ObjectInputStreamX.java b/src/org/apache/osgi/framework/util/ObjectInputStreamX.java
new file mode 100644
index 0000000..e7b99be
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ObjectInputStreamX.java
@@ -0,0 +1,53 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.io.*;
+
+/**
+ * The ObjectInputStreamX class is a simple extension to the ObjectInputStream
+ * class.  The purpose of ObjectInputStreamX is to allow objects to be deserialized
+ * when their classes are not in the CLASSPATH (e.g., in a JAR file).
+ */
+public class ObjectInputStreamX extends ObjectInputStream
+{
+    private ClassLoader m_loader = null;
+
+    /**
+     * Construct an ObjectInputStreamX for the specified InputStream and the specified
+     * ClassLoader.
+     * @param in the input stream to read.
+     * @param loader the class loader used to resolve classes.
+     */
+    public ObjectInputStreamX(InputStream in, ClassLoader loader)
+        throws IOException, StreamCorruptedException
+    {
+        super(in);
+        this.m_loader = loader;
+    }
+
+    /**
+     * By overloading this method, the ObjectInputStreamX can resolve a class
+     * from the class loader that was passed into it at construction time.
+     */
+    protected Class resolveClass(ObjectStreamClass v)
+        throws IOException, ClassNotFoundException
+    {
+        Class clazz = m_loader.loadClass(v.getName());
+        return clazz;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/PropertyResolver.java b/src/org/apache/osgi/framework/util/PropertyResolver.java
new file mode 100644
index 0000000..cd8f994
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/PropertyResolver.java
@@ -0,0 +1,23 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+public interface PropertyResolver
+{
+    public String get(String key);
+    public String[] getKeys();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ServiceListenerWrapper.java b/src/org/apache/osgi/framework/util/ServiceListenerWrapper.java
new file mode 100644
index 0000000..0eba299
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ServiceListenerWrapper.java
@@ -0,0 +1,114 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.security.*;
+
+import org.osgi.framework.*;
+
+public class ServiceListenerWrapper extends ListenerWrapper implements ServiceListener
+{
+    // LDAP query filter.
+    private Filter m_filter = null;
+    // Remember the security context.
+    private AccessControlContext m_acc = null;
+
+    public ServiceListenerWrapper(Bundle bundle, ServiceListener l, Filter filter)
+    {
+        super(bundle, ServiceListener.class, l);
+        m_filter = filter;
+
+        // Remember security context for filtering
+        // events based on security.
+        if (System.getSecurityManager() != null)
+        {
+            m_acc = AccessController.getContext();
+        }
+    }
+
+    public void setFilter(Filter filter)
+    {
+        m_filter = filter;
+    }
+    
+    public void serviceChanged(final ServiceEvent event)
+    {
+        // Service events should be delivered to STARTING,
+        // STOPPING, and ACTIVE bundles.
+        if ((getBundle().getState() != Bundle.STARTING) &&
+            (getBundle().getState() != Bundle.STOPPING) &&
+            (getBundle().getState() != Bundle.ACTIVE))
+        {
+            return;
+        }
+
+        // Check that the bundle has permission to get at least
+        // one of the service interfaces; the objectClass property
+        // of the service stores its service interfaces.
+        ServiceReference ref = event.getServiceReference();
+        String[] objectClass = (String[]) ref.getProperty(Constants.OBJECTCLASS);
+
+        // On the safe side, if there is no objectClass property
+        // then ignore event altogether.
+        if (objectClass != null)
+        {
+            boolean hasPermission = false;
+            if (m_acc != null)
+            {
+                for (int i = 0;
+                    !hasPermission && (i < objectClass.length);
+                    i++)
+                {
+                    try {
+                        ServicePermission perm =
+                            new ServicePermission(
+                                objectClass[i], ServicePermission.GET);
+                        m_acc.checkPermission(perm);
+                        hasPermission = true;
+                    } catch (Exception ex) {
+                    }
+                }
+            }
+            else
+            {
+                hasPermission = true;
+            }
+
+            if (hasPermission)
+            {
+                // Dispatch according to the filter.
+                if ((m_filter == null) || m_filter.match(event.getServiceReference()))
+                {
+                    if (System.getSecurityManager() != null)
+                    {
+                        AccessController.doPrivileged(new PrivilegedAction() {
+                            public Object run()
+                            {
+                                ((ServiceListener) getListener()).serviceChanged(event);
+                                return null;
+                            }
+                        });
+                    }
+                    else
+                    {
+                        ((ServiceListener) getListener()).serviceChanged(event);
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/Util.java b/src/org/apache/osgi/framework/util/Util.java
new file mode 100644
index 0000000..89591ab
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/Util.java
@@ -0,0 +1,240 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Util
+{
+    /**
+     * Parses delimited string and returns an array containing the tokens. This
+     * parser obeys quotes, so the delimiter character will be ignored if it is
+     * inside of a quote. This method assumes that the quote character is not
+     * included in the set of delimiter characters.
+     * @param value the delimited string to parse.
+     * @param delim the characters delimiting the tokens.
+     * @return an array of string tokens or null if there were no tokens.
+    **/
+    public static String[] parseDelimitedString(String value, String delim)
+    {
+        if (value == null)
+        {
+           value = "";
+        }
+
+        List list = new ArrayList();
+
+        int CHAR = 1;
+        int DELIMITER = 2;
+        int STARTQUOTE = 4;
+        int ENDQUOTE = 8;
+
+        StringBuffer sb = new StringBuffer();
+
+        int expecting = (CHAR | DELIMITER | STARTQUOTE);
+        
+        for (int i = 0; i < value.length(); i++)
+        {
+            char c = value.charAt(i);
+
+            boolean isDelimiter = (delim.indexOf(c) >= 0);
+            boolean isQuote = (c == '"');
+
+            if (isDelimiter && ((expecting & DELIMITER) > 0))
+            {
+                list.add(sb.toString().trim());
+                sb.delete(0, sb.length());
+                expecting = (CHAR | DELIMITER | STARTQUOTE);
+            }
+            else if (isQuote && ((expecting & STARTQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = CHAR | ENDQUOTE;
+            }
+            else if (isQuote && ((expecting & ENDQUOTE) > 0))
+            {
+                sb.append(c);
+                expecting = (CHAR | STARTQUOTE | DELIMITER);
+            }
+            else if ((expecting & CHAR) > 0)
+            {
+                sb.append(c);
+            }
+            else
+            {
+                throw new IllegalArgumentException("Invalid delimited string: " + value);
+            }
+        }
+
+        if (sb.length() > 0)
+        {
+            list.add(sb.toString().trim());
+        }
+
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+
+    /**
+     * Parses native code manifest headers.
+     * @param libStrs an array of native library manifest header
+     *        strings from the bundle manifest.
+     * @return an array of <tt>LibraryInfo</tt> objects for the
+     *         passed in strings.
+    **/
+    public static LibraryInfo[] parseLibraryStrings(String[] libStrs)
+        throws IllegalArgumentException
+    {
+        if (libStrs == null)
+        {
+            return null;
+        }
+
+        List libList = new ArrayList();
+
+        for (int i = 0; i < libStrs.length; i++)
+        {
+            LibraryInfo[] libs = LibraryInfo.parse(libStrs[i]);
+            for (int libIdx = 0;
+                (libs != null) && (libIdx < libs.length);
+                libIdx++)
+            {
+                libList.add(libs[libIdx]);
+            }
+        }
+
+        if (libList.size() == 0)
+        {
+            return null;
+        }
+
+        return (LibraryInfo[]) libList.toArray(new LibraryInfo[libList.size()]);
+    }
+
+    private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
+        0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
+        0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64,
+        0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
+        0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31,
+        0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f };
+
+    private static final byte decTab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1,
+        -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1,
+        -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+        18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
+        30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+        48, 49, 50, 51, -1, -1, -1, -1, -1 };
+
+    public static String base64Encode(String s) throws IOException
+    {
+        return encode(s.getBytes(), 0);
+    }
+
+    /**
+     * Encode a raw byte array to a Base64 String.
+     * 
+     * @param in Byte array to encode.
+     * @param len Length of Base64 lines. 0 means no line breaks.
+    **/
+    public static String encode(byte[] in, int len) throws IOException
+    {
+        ByteArrayOutputStream baos = null;
+        ByteArrayInputStream bais = null;
+        try
+        {
+            baos = new ByteArrayOutputStream();
+            bais = new ByteArrayInputStream(in);
+            encode(bais, baos, len);
+            // ASCII byte array to String
+            return (new String(baos.toByteArray()));
+        }
+        finally
+        {
+            if (baos != null)
+            {
+                baos.close();
+            }
+            if (bais != null)
+            {
+                bais.close();
+            }
+        }
+    }
+
+    public static void encode(InputStream in, OutputStream out, int len)
+        throws IOException
+    {
+
+        // Check that length is a multiple of 4 bytes
+        if (len % 4 != 0)
+        {
+            throw new IllegalArgumentException("Length must be a multiple of 4");
+        }
+
+        // Read input stream until end of file
+        int bits = 0;
+        int nbits = 0;
+        int nbytes = 0;
+        int b;
+
+        while ((b = in.read()) != -1)
+        {
+            bits = (bits << 8) | b;
+            nbits += 8;
+            while (nbits >= 6)
+            {
+                nbits -= 6;
+                out.write(encTab[0x3f & (bits >> nbits)]);
+                nbytes++;
+                // New line
+                if (len != 0 && nbytes >= len)
+                {
+                    out.write(0x0d);
+                    out.write(0x0a);
+                    nbytes -= len;
+                }
+            }
+        }
+
+        switch (nbits)
+        {
+            case 2:
+                out.write(encTab[0x3f & (bits << 4)]);
+                out.write(0x3d); // 0x3d = '='
+                out.write(0x3d);
+                break;
+            case 4:
+                out.write(encTab[0x3f & (bits << 2)]);
+                out.write(0x3d);
+                break;
+        }
+
+        if (len != 0)
+        {
+            if (nbytes != 0)
+            {
+                out.write(0x0d);
+                out.write(0x0a);
+            }
+            out.write(0x0d);
+            out.write(0x0a);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/AttributeNotFoundException.java b/src/org/apache/osgi/framework/util/ldap/AttributeNotFoundException.java
new file mode 100644
index 0000000..69df531
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/AttributeNotFoundException.java
@@ -0,0 +1,25 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+public class AttributeNotFoundException extends EvaluationException
+{
+    public AttributeNotFoundException(String msg)
+    {
+        super(msg);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Driver.java b/src/org/apache/osgi/framework/util/ldap/Driver.java
new file mode 100644
index 0000000..f341255
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Driver.java
@@ -0,0 +1,150 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+import java.io.*;
+import java.util.*;
+
+public class Driver {
+
+    public static void main(String[] argv)
+    {
+    Mapper mapper = new DriverMapper();
+
+    if(argv== null || argv.length == 0) {
+        System.err.println("usage: Driver <ldap spec file>");
+        return;
+    }
+    LdapLexer lexer = new LdapLexer();
+    FileReader fr = null;
+    char[] line = null;
+    Evaluator engine = new Evaluator();
+
+    Parser parser = new Parser();
+//	parser.setDebug(System.out);
+
+    try {
+        File spec = new File(argv[0]);
+        fr = new FileReader(spec);
+
+        // The basic operation of the driver is:
+        // 1. read a line from the file
+        // 2. parse that line
+        // 3. print the resulting program
+        // 4. repeat 1 until eof
+
+        for(;;) {
+        line = getLine(fr);
+        if(line == null) break;
+        System.out.println("Driver: filter: "+new String(line));
+        CharArrayReader car = new CharArrayReader(line);
+        lexer.setReader(car);
+        parser.reset(lexer);
+        boolean status = false;
+        try {
+            status = parser.start();
+            if(!status) {
+            System.err.println("parse failed");
+            printErrorLocation(line,lexer.charno());
+            }
+        } catch (ParseException pe) {
+            System.err.println(pe.toString());
+            printErrorLocation(line,lexer.charno());
+        }
+        if(status) {
+            try {
+            engine.reset(parser.getProgram());
+//            System.out.println("Driver: program: "+engine.toString());
+            System.out.println("Driver: program: "+engine.toStringInfix());
+            System.out.println("Eval = " + engine.evaluate(mapper));
+            } catch (EvaluationException ee) {
+            System.err.print("Driver: ");
+            printEvaluationStack(engine.getOperands());
+            System.err.println(ee.toString());
+            }
+        }
+        }
+    } catch (Exception e) {
+        System.err.println(e.toString());
+        printErrorLocation(line,lexer.charno());
+        e.printStackTrace();
+    }
+    }
+
+    // Get a line of input at a time and return a char[] buffer
+    // containing the line
+
+    static char[] getLine(Reader reader) throws IOException
+    {
+    StringBuffer buf = new StringBuffer();
+    for(;;) {
+        int c = reader.read();
+        if(c == '\r') continue;
+        if(c < 0) {
+        if(buf.length() == 0) return null; // no more lines
+        break;
+        }
+        if(c == '\n') break;
+        buf.append((char)c);
+    }
+
+    char[] cbuf = new char[buf.length()];
+    buf.getChars(0,buf.length(),cbuf,0);
+    return cbuf;
+    }
+
+
+    static void printErrorLocation(char[] line, int charno)
+    {
+    System.err.print("|");
+    if(line != null) System.err.print(new String(line));
+    System.err.println("|");
+    for(int i=0;i<charno;i++) System.err.print(" ");
+    System.err.println("^");
+    }
+
+    // Report the final contents of the evaluation stack
+    static void printEvaluationStack(Stack stack)
+    {
+    System.err.print("Stack:");
+    // recast operands as Vector to make interior access easier
+    Vector operands = stack;
+    int len = operands.size();
+    for(int i=0;i<len;i++) System.err.print(" "+operands.elementAt(i));
+    System.err.println();
+    }
+
+}
+
+class DriverMapper implements Mapper {
+
+    Hashtable hash = new Hashtable();
+
+    public DriverMapper()
+    {
+        hash.put("cn","Babs Jensen");
+        hash.put("objectClass","Person");
+        hash.put("sn","Jensen");
+        hash.put("o","university of michigan");
+        hash.put("foo","bar");
+    }
+
+    public Object lookup(String key)
+    {
+        return hash.get(key);
+    }
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/EvaluationException.java b/src/org/apache/osgi/framework/util/ldap/EvaluationException.java
new file mode 100644
index 0000000..c5e1fc9
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/EvaluationException.java
@@ -0,0 +1,43 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+public class EvaluationException extends Exception
+{
+    private Class m_unsupportedType = null;
+
+    public EvaluationException(String msg)
+    {
+        super(msg);
+    }
+    
+    public EvaluationException(String msg, Class clazz)
+    {
+        super(msg);
+        m_unsupportedType = clazz;
+    }
+    
+    public boolean isUnsupportedType()
+    {
+        return (m_unsupportedType != null);
+    }
+    
+    public Class getUnsupportedType()
+    {
+        return m_unsupportedType;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Evaluator.java b/src/org/apache/osgi/framework/util/ldap/Evaluator.java
new file mode 100644
index 0000000..6b0ef58
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Evaluator.java
@@ -0,0 +1,186 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+import java.util.Stack;
+import java.util.Vector;
+
+public class Evaluator {
+
+    Object[] program = null;
+    Stack operands = new Stack();
+    Mapper mapper = null;
+
+    public Evaluator()
+    {
+        reset();
+    }
+
+    public Evaluator(Object[] prog)
+    {
+        reset(prog);
+    }
+
+    public void reset()
+    {
+        program = null;
+        mapper = null;
+        operands.clear();
+    }
+
+    public void reset(Object[] prog)
+    {
+        reset();
+        setProgram(prog);
+    }
+
+    public void setProgram(Object[] prog)
+    {
+        program = prog;
+    }
+
+    public void setMapper(Mapper mapper)
+    {
+        this.mapper = mapper;
+    }
+
+    public Stack getOperands()
+    {
+        return operands;
+    }
+
+    public boolean evaluate(Mapper mapper) throws EvaluationException
+    {
+        try
+        {
+            // The following code is a little complicated because it
+            // is trying to deal with evaluating a given filter expression
+            // when it contains an attribute that does not exist in the
+            // supplied mapper. In such a situation the code below
+            // catches the "attribute not found" exception and inserts
+            // an instance of Unknown, which is used as a marker for
+            // non-existent attributes. The Unknown instance forces the
+            // operator to throw an "unsupported type" exception, which
+            // the code below converts into a FALSE and this has the effect
+            // of evaluating the subexpression that contained the
+            // non-existent attribute to FALSE. The rest of the filter
+            // expression evaluates normally. Any other exceptions are
+            // rethrown.
+            setMapper(mapper);
+            for (int i = 0; i < program.length; i++)
+            {
+                try
+                {
+                    Operator op = (Operator) program[i];
+                    op.execute(operands, mapper);
+//                    printAction(op); // for debug output
+                }
+                catch (AttributeNotFoundException ex)
+                {
+                    operands.push(new Unknown());
+                }
+                catch (EvaluationException ex)
+                {
+                    // If the exception is for an unsupported type of
+                    // type Unknown, then just push FALSE onto the
+                    // operand stack because this type will only appear
+                    // if an attribute was not found.
+                    if (ex.isUnsupportedType() &&
+                        (ex.getUnsupportedType() == Unknown.class))
+                    {
+                        operands.push(Boolean.FALSE);
+                    }
+                    // Otherwise, rethrow the exception.
+                    else
+                    {
+                        throw ex;
+                    }
+                }
+            }
+
+            if (operands.empty())
+            {
+                throw new EvaluationException(
+                    "Evaluation.evalute: final stack is empty");
+            }
+
+            Object result = operands.pop();
+
+            if (!operands.empty())
+            {
+                throw new EvaluationException(
+                    "Evaluation.evalute: final stack has more than one result");
+            }
+
+            if (!(result instanceof Boolean))
+            {
+                throw new EvaluationException(
+                    "Evaluation.evalute: final result is not Boolean");
+            }
+
+            return ((Boolean) result).booleanValue();
+        }
+        finally
+        {
+            // Clear the operands just in case an exception was thrown,
+            // otherwise stuff will be left in the stack.
+            operands.clear();
+        }
+    }
+
+    // For debugging; Dump the operator and stack
+    void printAction(Operator op)
+    {
+        System.err.println("Operator:"+op.toString());
+        System.err.print("Stack After:");
+        // recast operands as Vector to make interior access easier
+        Vector v = operands;
+        int len = v.size();
+        for (int i = 0; i < len; i++)
+            System.err.print(" " + v.elementAt(i));
+        System.err.println();
+    }
+
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < program.length; i++)
+        {
+            buf.append((i==0) ? "{" : ";");
+                buf.append(((Operator) program[i]).toString());
+        }
+        buf.append("}");
+        return buf.toString();
+    }
+
+    public String toStringInfix()
+    {
+        // First, we "evaluate" the program
+        // but for the purpose of re-constructing
+        // a parsetree.
+        operands.clear();
+        for (int i = 0; i < program.length; i++)
+        {
+            ((Operator) program[i]).buildTree(operands);
+        }
+        StringBuffer b = new StringBuffer();
+        Object result = operands.pop();
+        ((Operator)result).toStringInfix(b);
+        operands.clear();
+        return b.toString();
+    }
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/LdapLexer.java b/src/org/apache/osgi/framework/util/ldap/LdapLexer.java
new file mode 100644
index 0000000..1617c4d
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/LdapLexer.java
@@ -0,0 +1,98 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+import java.io.IOException;
+import java.io.Reader;
+
+public class LdapLexer {
+
+    static final int EOF = -1;
+    static final int NOCHAR = 0; // signal no peeked char; different from EOF
+
+    public static final String WHITESPACE = " \t\n\r";
+
+    Reader reader = null;
+
+    int nextChar = NOCHAR; // last peeked character
+
+    public LdapLexer() {}
+
+    public LdapLexer(Reader r)
+    {
+    setReader(r);
+    charno = 1;
+    }
+
+    public void setReader(Reader r)
+    {
+    reader = r;
+    }
+
+    /*
+    The procedures get(),peek(),skipwhitespace(),getnw(), and peeknw()
+    provide the essential LdapLexer interface.
+    */
+
+    public int get() throws IOException // any next char
+    {
+    if(nextChar == NOCHAR) return readChar();
+    int c = nextChar;
+    nextChar = NOCHAR;
+    return c;
+    }
+
+    public int peek() throws IOException
+    {
+    if(nextChar == NOCHAR) {
+        nextChar = readChar();
+    }
+    return nextChar;
+    }
+
+    void skipwhitespace() throws IOException
+    {
+    while(WHITESPACE.indexOf(peek()) >= 0) get();
+    }
+
+    public int getnw() throws IOException // next non-whitespace char
+    {					   // (note: not essential but useful)
+    skipwhitespace();
+    return get();
+    }
+
+    public int peeknw() throws IOException // next non-whitespace char
+    {					   // (note: not essential but useful)
+    skipwhitespace();
+    return peek();
+    }
+
+    // Following is for error reporting
+
+    // Pass all character reads thru this so we can track char count
+
+    int charno; // 1-based
+
+    public int charno() {return charno;}
+
+    int readChar() throws IOException
+    {
+    charno++;
+    return reader.read();
+    }
+
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/Mapper.java b/src/org/apache/osgi/framework/util/ldap/Mapper.java
new file mode 100644
index 0000000..bf9af65
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Mapper.java
@@ -0,0 +1,22 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+public interface Mapper
+{
+    public Object lookup(String key);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Operator.java b/src/org/apache/osgi/framework/util/ldap/Operator.java
new file mode 100644
index 0000000..b733238
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Operator.java
@@ -0,0 +1,34 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+import java.util.Stack;
+
+public abstract class Operator
+{
+    public abstract void execute(Stack operands, Mapper mapper)
+        throws EvaluationException;
+
+    public abstract String toString();
+
+    public abstract void buildTree(Stack operands); // re-build the parsetree
+    public abstract void toStringInfix(StringBuffer b); // convert to canonical string
+
+    // Place to store the reconstructed parsetree
+    // Vector -> ArrayList is using jdk1.2 or later
+    public Operator[] children = null;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/ParseException.java b/src/org/apache/osgi/framework/util/ldap/ParseException.java
new file mode 100644
index 0000000..cdc482b
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/ParseException.java
@@ -0,0 +1,22 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+public class ParseException extends Exception {
+    public ParseException() {super();}
+    public ParseException(String msg) {super(msg);}
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/Parser.java b/src/org/apache/osgi/framework/util/ldap/Parser.java
new file mode 100644
index 0000000..d14cfe3
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Parser.java
@@ -0,0 +1,1696 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+
+public class Parser
+{
+    //
+    // Parser contants.
+    //
+
+    // End of file.
+    public static final int EOF = -1;
+
+    // Special characters in parse
+    public static final char LPAREN = '(';
+    public static final char RPAREN = ')';
+    public static final char STAR = '*';
+
+    // Define the list of legal leading and trailing
+    // characters in an attribute name.
+    public static final String ATTRIBUTECHARS0 =
+        ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+    // Define the list of legal internal characters in an attribute name.
+    public static final String ATTRIBUTECHARS1 =
+        ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_ ";
+
+    // Define an enum for substring procedure
+    public static final int SIMPLE = 0;
+    public static final int PRESENT = 1;
+    public static final int SUBSTRING = 2;
+
+    // different from =|>|<|~
+    public static final int NOOP = 0;
+
+    // Comparison operators.
+    public static final int EQUAL = 0;
+    public static final int GREATER_EQUAL = 1;
+    public static final int LESS_EQUAL = 2;
+    public static final int APPROX = 3;
+
+    // Criteria in % to accept something as approximate.
+    public static final int APPROX_CRITERIA = 10;
+
+    // Flag indicating presense of BigInteger/Decimal.
+    private static boolean m_hasBigNumbers = false;
+
+    static 
+    {
+        try
+        {
+            Class.forName("java.math.BigDecimal");
+            m_hasBigNumbers = true;
+        }
+        catch (Exception ex)
+        {
+            // Ignore.
+        }
+    }
+    //
+    // Instance variables.
+    //
+
+    private LdapLexer lexer = null;
+    private List program;
+
+    public Parser()
+    {
+        reset();
+    }
+
+    public Parser(LdapLexer l)
+    {
+        reset(l);
+    }
+
+    public void reset()
+    {
+        lexer = null;
+        if (program == null)
+        {
+            program = new ArrayList();
+        }
+        program.clear();
+    }
+
+    public void reset(LdapLexer l)
+    {
+        reset();
+        lexer = l;
+    }
+
+    public Object[] getProgram()
+    {
+        return program.toArray(new Object[program.size()]);
+    }
+
+    // Define the recursive descent procedures
+
+    /*
+    <start>::= <filter> <EOF>
+    */
+    public boolean start() throws ParseException, IOException
+    {
+        boolean ok = filter();
+        if (!ok)
+        {
+            return ok;
+        }
+        int ch = lexer.get();
+        if (ch != EOF)
+        {
+            throw new ParseException(
+                "expected <EOF>; found '" + ((char) ch) + "'");
+        }
+        return ok;
+    }
+
+    /*
+    <filter> ::= '(' <filtercomp> ')'
+     */
+    boolean filter() throws ParseException, IOException
+    {
+        debug("filter");
+        if (lexer.peeknw() != LPAREN)
+        {
+            return false;
+        }
+        lexer.get();
+        if (!filtercomp())
+        {
+            throw new ParseException("expected filtercomp");
+        }
+        if (lexer.getnw() != RPAREN)
+        {
+            throw new ParseException("expected )");
+        }
+        return true;
+    }
+
+    /*
+    <filtercomp> ::= <and> | <or> | <not> | <item>
+    <and> ::= '&' <filterlist>
+    <or> ::= '|' <filterlist>
+    <not> ::= '!' <filter>
+    */
+    boolean filtercomp() throws ParseException, IOException
+    {
+        debug("filtercomp");
+        int c = lexer.peeknw();
+        switch (c)
+        {
+            case '&' :
+            case '|' :
+                lexer.get();
+                int cnt = filterlist();
+                if (cnt == 0)
+                {
+                    return false;
+                }
+                // Code: [And|Or](cnt)
+                program.add(
+                    c == '&'
+                        ? (Operator) new AndOperator(cnt)
+                        : (Operator) new OrOperator(cnt));
+                return true;
+            case '!' :
+                lexer.get();
+                if (!filter())
+                {
+                    return false;
+                }
+                // Code: Not()
+                program.add(new NotOperator());
+                return true;
+            case EOF :
+                return false;
+            default :
+                // check for key
+                if (ATTRIBUTECHARS0.indexOf(c) <= 0)
+                {
+                    return false;
+                }
+                boolean b = item();
+                return b;
+        }
+    }
+
+    /*
+    <filterlist> ::= <filter> | <filter> <filterlist>
+    */
+    int filterlist() throws ParseException, IOException
+    {
+        debug("filterlist");
+        int cnt = 0;
+        if (filter())
+        {
+            do
+            {
+                cnt++;
+            }
+            while (filter());
+        }
+        return (cnt);
+    }
+
+    /*
+    <item> ::= <simple> | <present> | <substring>
+    <simple> ::= <attr> <filtertype> <value>
+    <filtertype> ::= <equal> | <approx> | <greater> | <less>
+    <present> ::= <attr> '=*'
+    <substring> ::= <attr> '=' <initial> <any> <final>
+    */
+    boolean item() throws ParseException, IOException
+    {
+        debug("item");
+
+        StringBuffer attr = new StringBuffer();
+        if (!attribute(attr))
+        {
+            return false;
+        }
+
+        lexer.skipwhitespace(); // assume allowable before equal operator
+        // note: I treat the =* case as = followed by a special substring
+        int op = equalop();
+        if (op == NOOP)
+        {
+            String oplist = "=|~=|>=|<=";
+            throw new ParseException("expected " + oplist);
+        }
+        ArrayList pieces = new ArrayList();
+        int kind = substring(pieces);
+        // Get some of the illegal cases out of the way
+        if (op != '=' && kind != SIMPLE)
+        {
+            // We assume that only the = operator can work
+            // with right sides containing stars.  If not correct
+            // then this code must change.
+            throw new ParseException("expected value|substring|*");
+        }
+
+        switch (kind)
+        {
+            case SIMPLE :
+                // Code: Push(attr); Constant(pieces.get(0)); <operator>();
+                program.add(new PushOperator(attr.toString()));
+                program.add(new ConstOperator(pieces.get(0)));
+                switch (op)
+                {
+                    case '<' :
+                        program.add(new LessEqualOperator());
+                        break;
+                    case '>' :
+                        program.add(new GreaterEqualOperator());
+                        break;
+                    case '~' :
+                        program.add(new ApproxOperator());
+                        break;
+                    case '=' :
+                    default :
+                        program.add(new EqualOperator());
+                }
+                break;
+            case PRESENT :
+                // Code: Present(attr);
+                program.add(new PresentOperator(attr.toString()));
+                break;
+            case SUBSTRING :
+                generateSubStringCode(attr.toString(), pieces);
+                break;
+            default :
+                throw new ParseException("expected value|substring|*");
+        }
+        return true;
+    }
+
+    // Generating code for substring right side is mildly
+    // complicated.
+
+    void generateSubStringCode(String attr, ArrayList pieces)
+    {
+        // Code: Push(attr)
+        program.add(new PushOperator(attr.toString()));
+
+        // Convert the pieces arraylist to a String[]
+        String[] list =
+            (String[]) pieces.toArray(new String[pieces.size()]);
+
+        // Code: SubString(list)
+        program.add(new SubStringOperator(list));
+    }
+
+    /*
+    <attr> is a string representing an attributte,
+    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.
+    Attribute names may have embedded spaces, but not leading or
+    trailing spaces.
+    */
+    boolean attribute(StringBuffer buf) throws ParseException, IOException
+    {
+        debug("attribute");
+        lexer.skipwhitespace();
+        buf.setLength(0);
+        int c = lexer.peek(); // need to make sure there
+        // is at least one KEYCHAR
+        if (c == EOF)
+        {
+            return false;
+        }
+        if (ATTRIBUTECHARS0.indexOf(c) < 0)
+        {
+            return false;
+        }
+
+        do
+        {
+            buf.append((char) lexer.get());
+        }
+        while (ATTRIBUTECHARS1.indexOf(lexer.peek()) >= 0);
+
+        // The above may have accumulated trailing blanks that must be removed
+        int i = buf.length() - 1;
+        while (i > 0 && buf.charAt(i) == ' ')
+        {
+            i--;
+        }
+        buf.setLength(i + 1);
+        return true;
+    }
+
+    /*
+       <equal> ::= '='
+       <approx> ::= '~='
+       <greater> ::= '>='
+       <less> ::= '<='
+       <present> ::= <attr> '=*'
+    */
+    int equalop() throws ParseException, IOException
+    {
+        debug("equalop");
+        lexer.skipwhitespace();
+        int op = lexer.peek();
+        switch (op)
+        {
+            case '=' :
+                lexer.get();
+                break;
+            case '~' :
+            case '<' :
+            case '>' :
+                // skip main operator char
+                int c = lexer.get();
+                // make sure that the next char is '='
+                c = lexer.get();
+                if (c != '=')
+                {
+                    throw new ParseException("expected ~=|>=|<=");
+                }
+                break;
+            default :
+                op = NOOP;
+        }
+        return op;
+    }
+
+    /*
+    <substring> ::= <attr> '=' <initial> <any> <final>
+    <initial> ::= NULL | <value>
+    <any> ::= '*' <starval>
+    <starval> ::= NULL | <value> '*' <starval>
+    <final> ::= NULL | <value>
+    <value> ::= ...
+    */
+    /*
+    This procedure handles all cases on right side of an item
+    */
+    int substring(ArrayList pieces) throws ParseException, IOException
+    {
+        debug("substring");
+
+        pieces.clear();
+        StringBuffer ss = new StringBuffer();
+        //        int kind = SIMPLE; // assume until proven otherwise
+        boolean wasStar = false; // indicates last piece was a star
+        boolean leftstar = false; // track if the initial piece is a star
+        boolean rightstar = false; // track if the final piece is a star
+
+        // We assume (sub)strings can contain leading and trailing blanks
+loop:   for (;;)
+        {
+            int c = lexer.peek();
+            switch (c)
+            {
+                case RPAREN :
+                    if (wasStar)
+                    {
+                        // insert last piece as "" to handle trailing star
+                        rightstar = true;
+                    }
+                    else
+                    {
+                        pieces.add(ss.toString());
+                        // accumulate the last piece
+                        // note that in the case of
+                        // (cn=); this might be
+                        // the string "" (!=null)
+                    }
+                    ss.setLength(0);
+                    break loop;
+                case '\\' :
+                    wasStar = false;
+                    lexer.get();
+                    c = lexer.get();
+                    if (c != EOF)
+                    {
+                        throw new ParseException("unexpected EOF");
+                    }
+                    ss.append((char) c);
+                    break;
+                case EOF :
+                    if (pieces.size() > 0)
+                    {
+                        throw new ParseException("expected ')'");
+                    }
+                    else
+                    {
+                        throw new ParseException("expected value|substring");
+                    }
+                case '*' :
+                    if (wasStar)
+                    {
+                        // encountered two successive stars;
+                        // I assume this is illegal
+                        throw new ParseException("unexpected '**'");
+                    }
+                    lexer.get();
+                    if (ss.length() > 0)
+                    {
+                        pieces.add(ss.toString()); // accumulate the pieces
+                        // between '*' occurrences
+                    }
+                    ss.setLength(0);
+                    // if this is a leading star, then track it
+                    if (pieces.size() == 0)
+                    {
+                        leftstar = true;
+                    }
+                    ss.setLength(0);
+                    wasStar = true;
+                    break;
+                default :
+                    wasStar = false;
+                    ss.append((char) lexer.get());
+            }
+        }
+        if (pieces.size() == 0)
+        {
+            return PRESENT;
+        }
+        if (leftstar || rightstar || pieces.size() > 1)
+        {
+            // insert leading and/or trailing "" to anchor ends
+            if (rightstar)
+            {
+                pieces.add("");
+            }
+            if (leftstar)
+            {
+                pieces.add(0, "");
+            }
+            return SUBSTRING;
+        }
+        // assert !leftstar && !rightstar && pieces.size == 1
+        return SIMPLE;
+    }
+
+    // Debug stuff
+
+    static boolean debug = false;
+
+    PrintStream dbgout = null;
+
+    public void setDebug(PrintStream out)
+    {
+        debug = true;
+        dbgout = out;
+    }
+
+    void debug(String proc)
+    {
+        if (!debug || dbgout == null)
+        {
+            return;
+        }
+        dbgout.println("parsing " + proc + ":" + lexer.charno());
+        dbgout.flush();
+    }
+
+    // Exclusive inner classes
+
+    private static class AndOperator extends Operator
+    {
+        private int operandCount;
+
+        public AndOperator(int opcnt)
+        {
+            operandCount = opcnt;
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            // Determine result using short-circuit evaluation.
+            boolean result = true;
+            for (int i = 0; i < operandCount; i++)
+            {
+                if (operands.empty())
+                {
+                    fewOperands("AND");
+                }
+
+                // For short-circuited evaluation, once the AND
+                // becomes false, we can ignore the remaining
+                // expressions, but we must still pop them off.
+                if (!result)
+                {
+                    operands.pop();
+                }
+                else
+                {
+                    result = ((Boolean) operands.pop()).booleanValue();
+                }
+            }
+            operands.push(new Boolean(result));
+        }
+
+        public String toString()
+        {
+            return "&(" + operandCount + ")";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[operandCount];
+            // need to preserve stack order
+            for (int i = 0; i < operandCount; i++)
+            {
+                children[(operandCount - 1) - i] =
+                    (Operator) operands.pop();
+            }
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(&");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class OrOperator extends Operator
+    {
+        private int operandCount;
+
+        public OrOperator(int opcnt)
+        {
+            operandCount = opcnt;
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            // Determine result using short-circuit evaluation.
+            boolean result = false;
+            for (int i = 0; i < operandCount; i++)
+            {
+                if (operands.empty())
+                {
+                    fewOperands("OR");
+                }
+
+                // For short-circuited evaluation, once the OR
+                // becomes true, we can ignore the remaining
+                // expressions, but we must still pop them off.
+                if (result)
+                {
+                    operands.pop();
+                }
+                else
+                {
+                    result = ((Boolean) operands.pop()).booleanValue();
+                }
+            }
+            operands.push(new Boolean(result));
+        }
+
+        public String toString()
+        {
+            return "|(" + operandCount + ")";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[operandCount];
+            // need to preserve stack order
+            for (int i = 0; i < operandCount; i++)
+            {
+                children[(operandCount - 1) - i] =
+                    (Operator) operands.pop();
+            }
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(|");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class NotOperator extends Operator
+    {
+        public NotOperator()
+        {
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            if (operands.empty())
+            {
+                fewOperands("NOT");
+            }
+            boolean result = !((Boolean) operands.pop()).booleanValue();
+            operands.push(new Boolean(result));
+        }
+
+        public String toString()
+        {
+            return "!()";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[1];
+            children[0] = (Operator) operands.pop();
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(!");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class EqualOperator extends Operator
+    {
+        public EqualOperator()
+        {
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            if (operands.empty())
+            {
+                fewOperands("=");
+            }
+
+            // We cheat and use the knowledge that top (right) operand
+            // will always be a string because of the way code was generated
+            String rhs = (String) operands.pop();
+            if (operands.empty())
+            {
+                fewOperands("=");
+            }
+
+            Object lhs = operands.pop();
+
+            operands.push(new Boolean(compare(lhs, rhs, EQUAL)));
+        }
+
+        public String toString()
+        {
+            return "=()";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[2];
+            // need to preserve stack order
+            for (int i = 0; i < 2; i++)
+            {
+                Operator o = (Operator) operands.pop();
+                children[1 - i] = o;
+            }
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                if (i > 0)
+                {
+                    b.append("=");
+                }
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class GreaterEqualOperator extends Operator
+    {
+        public GreaterEqualOperator()
+        {
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            if (operands.empty())
+            {
+                fewOperands(">=");
+            }
+            // We cheat and use the knowledge that top (right) operand
+            // will always be a string because of the way code was generated
+            String rhs = (String) operands.pop();
+            if (operands.empty())
+            {
+                fewOperands(">=");
+            }
+            Object lhs = operands.pop();
+
+            operands.push(new Boolean(compare(lhs, rhs, GREATER_EQUAL)));
+        }
+
+        public String toString()
+        {
+            return ">=()";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[2];
+            // need to preserve stack order
+            for (int i = 0; i < 2; i++)
+            {
+                children[1 - i] = (Operator) operands.pop();
+            }
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                if (i > 0)
+                {
+                    b.append(">=");
+                }
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class LessEqualOperator extends Operator
+    {
+        public LessEqualOperator()
+        {
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            if (operands.empty())
+            {
+                fewOperands("<=");
+            }
+            // We cheat and use the knowledge that top (right) operand
+            // will always be a string because of the way code was generated
+            String rhs = (String) operands.pop();
+            if (operands.empty())
+            {
+                fewOperands("<=");
+            }
+            Object lhs = (Object) operands.pop();
+            operands.push(new Boolean(compare(lhs, rhs, LESS_EQUAL)));
+        }
+
+        public String toString()
+        {
+            return "<=()";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[2];
+            // need to preserve stack order
+            for (int i = 0; i < 2; i++)
+            {
+                children[1 - i] = (Operator) operands.pop();
+            }
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                if (i > 0)
+                {
+                    b.append("<=");
+                }
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class ApproxOperator extends Operator
+    {
+        public ApproxOperator()
+        {
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            if (operands.empty())
+            {
+                fewOperands("~=");
+            }
+            // We cheat and use the knowledge that top (right) operand
+            // will always be a string because of the way code was generated
+            String rhs = (String) operands.pop();
+            if (operands.empty())
+            {
+                fewOperands("~=");
+            }
+            Object lhs = operands.pop();
+            operands.push(new Boolean(compare(lhs, rhs, APPROX)));
+        }
+
+        public String toString()
+        {
+            return "~=()";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[2];
+            // need to preserve stack order
+            for (int i = 0; i < 2; i++)
+            {
+                children[1 - i] = (Operator) operands.pop();
+            }
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(");
+            for (int i = 0; i < children.length; i++)
+            {
+                Operator o = (Operator) children[i];
+                if (i > 0)
+                {
+                    b.append("~=");
+                }
+                o.toStringInfix(b);
+            }
+            b.append(")");
+        }
+    }
+
+    private static class PresentOperator extends Operator
+    {
+        String attribute;
+
+        public PresentOperator(String attribute)
+        {
+            this.attribute = attribute;
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            Object value = mapper.lookup(attribute);
+            operands.push(new Boolean(value != null));
+        }
+
+        public String toString()
+        {
+            return attribute + "=*";
+        }
+
+        public void buildTree(Stack operands)
+        {
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(");
+            b.append(attribute + "=*");
+            b.append(")");
+        }
+    }
+
+    private static class PushOperator extends Operator
+    {
+        String attribute;
+
+        public PushOperator(String attribute)
+        {
+            this.attribute = attribute;
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            // find and push the value of a given attribute
+            Object value = mapper.lookup(attribute);
+            if (value == null)
+            {
+                throw new AttributeNotFoundException(
+                    "attribute " + attribute + " not found");
+            }
+            operands.push(value);
+        }
+
+        public String toString()
+        {
+            return "push(" + attribute + ")";
+        }
+
+        public String toStringInfix()
+        {
+            return attribute;
+        }
+
+        public void buildTree(Stack operands)
+        {
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append(attribute);
+        }
+    }
+
+    private static class ConstOperator extends Operator
+    {
+        Object val;
+
+        public ConstOperator(Object val)
+        {
+            this.val = val;
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            operands.push(val);
+        }
+
+        public String toString()
+        {
+            return "const(" + val + ")";
+        }
+
+        public String toStringInfix()
+        {
+            return val.toString();
+        }
+
+        public void buildTree(Stack operands)
+        {
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append(val.toString());
+        }
+    }
+
+    private static class SubStringOperator extends Operator
+        implements OperatorConstants
+    {
+        String[] pieces;
+
+        public SubStringOperator(String[] pieces)
+        {
+            this.pieces = pieces;
+        }
+
+        public void execute(Stack operands, Mapper mapper)
+            throws EvaluationException
+        {
+            if (operands.empty())
+            {
+                fewOperands("SUBSTRING");
+            }
+
+            Object op = operands.pop();
+
+            // The operand can either be a string or an array of strings.
+            if (op instanceof String)
+            {
+                operands.push(check((String) op));
+            }
+            else if (op instanceof String[])
+            {
+                // If one element of the array matches, then push true.
+                String[] ops = (String[]) op;
+                boolean result = false;
+                for (int i = 0; !result && (i < ops.length); i++)
+                {
+                    if (check(ops[i]) == Boolean.TRUE)
+                    {
+                        result = true;
+                    }
+                }
+
+                operands.push((result) ? Boolean.TRUE : Boolean.FALSE);
+            }
+            else
+            {
+                unsupportedType("SUBSTRING", op.getClass());
+            }
+        }
+
+        private Boolean check(String s)
+        {
+            // Walk the pieces to match the string
+            // There are implicit stars between each piece,
+            // and the first and last pieces might be "" to anchor the match.
+            // assert (pieces.length > 1)
+            // minimal case is <string>*<string>
+
+            Boolean result = Boolean.FALSE;
+            int len = pieces.length;
+
+            loop : for (int i = 0; i < len; i++)
+            {
+                String piece = (String) pieces[i];
+                int index = 0;
+                if (i == len - 1)
+                {
+                    // this is the last piece
+                    if (s.endsWith(piece))
+                    {
+                        result = Boolean.TRUE;
+                    }
+                    else
+                    {
+                        result = Boolean.FALSE;
+                    }
+                    break loop;
+                }
+                // initial non-star; assert index == 0
+                else if (i == 0)
+                {
+                    if (!s.startsWith(piece))
+                    {
+                        result = Boolean.FALSE;
+                        break loop;
+                    }
+                }
+                // assert i > 0 && i < len-1
+                else
+                {
+                    // Sure wish stringbuffer supported e.g. indexOf
+                    index = s.indexOf(piece, index);
+                    if (index < 0)
+                    {
+                        result = Boolean.FALSE;
+                        break loop;
+                    }
+                }
+                // start beyond the matching piece
+                index += piece.length();
+            }
+
+            return result;
+        }
+
+        public String toString()
+        {
+            StringBuffer b = new StringBuffer();
+            b.append("substring(");
+            for (int i = 0; i < pieces.length; i++)
+            {
+                String piece = pieces[i];
+                if (i > 0)
+                {
+                    b.append("*");
+                }
+                b.append(escape(piece));
+            }
+            b.append(")");
+            return b.toString();
+        }
+
+        public String escape(String s)
+        {
+            int len = s.length();
+            StringBuffer buf = new StringBuffer(len);
+            for (int i = 0; i < len; i++)
+            {
+                char c = s.charAt(i);
+                if (c == ')' || c == '*')
+                    buf.append('\\');
+                buf.append(c);
+            }
+            return buf.toString();
+        }
+
+        public void buildTree(Stack operands)
+        {
+            children = new Operator[1];
+            children[0] = (Operator) operands.pop();
+            operands.push(this);
+        }
+
+        public void toStringInfix(StringBuffer b)
+        {
+            b.append("(");
+            children[0].toStringInfix(b); // dump attribute
+            b.append("=");
+            for (int i = 0; i < pieces.length; i++)
+            {
+                String piece = (String) pieces[i];
+                if (i > 0)
+				{
+                    b.append("*");
+                }
+                b.append(piece);
+            }
+            b.append(")");
+        }
+    }
+
+    // Utility classes and Interfaces
+
+    private interface OperatorConstants
+    {
+        static final int SSINIT = 0;
+        static final int SSFINAL = 1;
+        static final int SSMIDDLE = 2;
+        static final int SSANY = 3;
+    }
+
+    /**
+     * Compare two operands in an expression with respect  
+     * to the following operators =, <=, >= and ~=
+     * 
+     * Example: value=100
+     *
+     * @param lhs an object that implements comparable or an array of
+     *            objects that implement comparable.
+     * @param rhs a string representing the right operand.
+     * @param operator an integer that represents the operator.
+     * @return <tt>true</tt> or <tt>false</tt> according to the evaluation.
+     * @throws EvaluationException if it is not possible to do the comparison.
+    **/
+    public static boolean compare(Object lhs, String rhs, int operator)
+        throws EvaluationException
+    {
+        // Determine class of LHS.
+        Class lhsClass = null;
+
+        // If LHS is an array, then call compare() on each element
+        // of the array until a match is found.
+        if (lhs.getClass().isArray())
+        {
+            // First, if this is an array of primitives, then convert
+            // the entire array to an array of the associated
+            // primitive wrapper class instances.
+            if (lhs.getClass().getComponentType().isPrimitive())
+            {
+                lhs = convertPrimitiveArray(lhs);
+            }
+
+            // Now call compare on each element of array.
+            Object[] array = (Object[]) lhs;
+            for (int i = 0; i < array.length; i++)
+            {
+                if (compare(array[i], rhs, operator))
+                {
+                    return true;
+                }
+            }
+        }
+        // If LHS is a vector, then call compare() on each element
+        // of the vector until a match is found.
+        else if (lhs instanceof Vector)
+        {
+            for (Enumeration e = ((Vector) lhs).elements(); e.hasMoreElements();)
+            {
+                if (compare(e.nextElement(), rhs, operator))
+                {
+                    return true;
+                }
+            }
+        }
+        else
+        {
+            // Get the class of LHS.
+            lhsClass = lhs.getClass();
+
+            // At this point we are expecting the LHS to be a comparable,
+            // but Boolean is a special case since it is the only primitive
+            // wrapper class that does not implement comparable; deal with
+            // Boolean separately.
+            if (lhsClass == Boolean.class)
+            {
+                return compareBoolean(lhs, rhs, operator);
+            }
+        
+            // If LHS is not a Boolean, then verify it is a comparable
+            // and perform comparison.
+            if (!(Comparable.class.isAssignableFrom(lhsClass)))
+            {
+                String opName = null;
+                switch (operator)
+                {
+                    case EQUAL :
+                        opName = "=";
+                    case GREATER_EQUAL :
+                        opName = ">=";
+                    case LESS_EQUAL :
+                        opName = "<=";
+                    case APPROX:
+                        opName = "~=";
+                    default:
+                        opName = "UNKNOWN OP";
+                }
+
+                unsupportedType(opName, lhsClass);
+            }
+
+            // We will try to create a comparable object from the
+            // RHS string.
+            Comparable rhsComparable = null;
+            try
+            {
+                // We are expecting to be able to construct a comparable
+                // instance from the RHS string by passing it into the
+                // constructor of the corresponing comparable class. The
+                // Character class is a special case, since its constructor
+                // does not take a string, so handle it separately.
+                if (lhsClass == Character.class)
+                {
+                    rhsComparable = new Character(rhs.charAt(0));
+                }
+                else
+                {
+                    rhsComparable = (Comparable) lhsClass
+                        .getConstructor(new Class[] { String.class })
+                            .newInstance(new Object[] { rhs });
+                }
+            }
+            catch (Exception ex)
+            {
+                String msg = (ex.getCause() == null)
+                    ? ex.toString() : ex.getCause().toString();
+                throw new EvaluationException(
+                    "Could not instantiate class "
+                        + lhsClass.getName()
+                        + " with constructor String parameter "
+                        + rhs + " " + msg);
+            }
+
+            Comparable lhsComparable = (Comparable) lhs;
+
+            switch (operator)
+            {
+                case EQUAL :
+                    return (lhsComparable.compareTo(rhsComparable) == 0);
+                case GREATER_EQUAL :
+                    return (lhsComparable.compareTo(rhsComparable) >= 0);
+                case LESS_EQUAL :
+                    return (lhsComparable.compareTo(rhsComparable) <= 0);
+                case APPROX:
+                    return compareToApprox(lhsComparable, rhsComparable);
+                default:
+                    throw new EvaluationException("Unknown comparison operator..."
+                        + operator);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * This is an ugly utility method to convert an array of primitives
+     * to an array of primitive wrapper objects. This method simplifies
+     * processing LDAP filters since the special case of primitive arrays
+     * can be ignored.
+     * @param array
+     * @return
+    **/
+    private static Object[] convertPrimitiveArray(Object array)
+    {
+        Class clazz = array.getClass().getComponentType();
+
+        if (clazz == Boolean.TYPE)
+        {
+            boolean[] src = (boolean[]) array;
+            array = new Boolean[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Boolean(src[i]);
+            }
+        }
+        else if (clazz == Character.TYPE)
+        {
+            char[] src = (char[]) array;
+            array = new Character[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Character(src[i]);
+            }
+        }
+        else if (clazz == Byte.TYPE)
+        {
+            byte[] src = (byte[]) array;
+            array = new Byte[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Byte(src[i]);
+            }
+        }
+        else if (clazz == Short.TYPE)
+        {
+            byte[] src = (byte[]) array;
+            array = new Byte[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Byte(src[i]);
+            }
+        }
+        else if (clazz == Integer.TYPE)
+        {
+            int[] src = (int[]) array;
+            array = new Integer[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Integer(src[i]);
+            }
+        }
+        else if (clazz == Long.TYPE)
+        {
+            long[] src = (long[]) array;
+            array = new Long[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Long(src[i]);
+            }
+        }
+        else if (clazz == Float.TYPE)
+        {
+            float[] src = (float[]) array;
+            array = new Float[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Float(src[i]);
+            }
+        }
+        else if (clazz == Double.TYPE)
+        {
+            double[] src = (double[]) array;
+            array = new Double[src.length];
+            for (int i = 0; i < src.length; i++)
+            {
+                ((Object[]) array)[i] = new Double(src[i]);
+            }
+        }
+
+        return (Object[]) array;
+    }
+
+    private static boolean compareBoolean(Object lhs, String rhs, int operator)
+        throws EvaluationException
+    {
+        Boolean rhsBoolean = new Boolean(rhs);
+        if (lhs.getClass().isArray())
+        {
+            Object[] objs = (Object[]) lhs;
+            for (int i = 0; i < objs.length; i++)
+            {
+                switch (operator)
+                {
+                    case EQUAL :
+                    case GREATER_EQUAL :
+                    case LESS_EQUAL :
+                    case APPROX:
+                        if (objs[i].equals(rhsBoolean))
+                        {
+                            return true;
+                        }
+                        break;
+                    default:
+                        throw new EvaluationException(
+                            "Unknown comparison operator: " + operator);   
+                }
+            }
+            return false;
+        }
+        else
+        {
+            switch (operator)
+            {
+                case EQUAL :
+                case GREATER_EQUAL :
+                case LESS_EQUAL :
+                case APPROX:
+                    return (lhs.equals(rhsBoolean));
+                default:
+                    throw new EvaluationException("Unknown comparison operator..."
+                        + operator);
+            }
+        }
+    }
+
+    /**
+     * Test if two objects are approximate. The two objects that are passed must
+     * have the same type.
+     * 
+     * Approximate for numerical values involves a difference of less than APPROX_CRITERIA
+     * Approximate for string values is calculated by using the Levenshtein distance
+     * between strings. Less than APPROX_CRITERIA of difference is considered as approximate.
+     * 
+     * Supported types only include the following subclasses of Number:
+     * - Byte
+     * - Double
+     * - Float
+     * - Int
+     * - Long
+     * - Short 
+     * - BigInteger
+     * - BigDecimal
+     * As subclasses of Number must provide methods to convert the represented numeric value 
+     * to byte, double, float, int, long, and short. (see API)
+     * 
+     * @param obj1
+     * @param obj2
+     * @return true if they are approximate
+     * @throws EvaluationException if it the two objects cannot be approximated
+    **/
+    private static boolean compareToApprox(Object obj1, Object obj2) throws EvaluationException
+    {
+        if (obj1 instanceof Byte)
+        {
+            byte value1 = ((Byte)obj1).byteValue();
+            byte value2 = ((Byte)obj2).byteValue();
+            return (value2 >= (value1-((Math.abs(value1)*(byte)APPROX_CRITERIA)/(byte)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(byte)APPROX_CRITERIA)/(byte)100)));
+        }
+        else if (obj1 instanceof Character)
+        {
+            char value1 = ((Character)obj1).charValue();
+            char value2 = ((Character)obj2).charValue();
+            return (value2 >= (value1-((Math.abs(value1)*(char)APPROX_CRITERIA)/(char)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(char)APPROX_CRITERIA)/(char)100)));
+        }
+        else if (obj1 instanceof Double)
+        {
+            double value1 = ((Double)obj1).doubleValue();
+            double value2 = ((Double)obj2).doubleValue();
+            return (value2 >= (value1-((Math.abs(value1)*(double)APPROX_CRITERIA)/(double)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(double)APPROX_CRITERIA)/(double)100)));
+        }
+        else if (obj1 instanceof Float)
+        {
+            float value1 = ((Float)obj1).floatValue();
+            float value2 = ((Float)obj2).floatValue();
+            return (value2 >= (value1-((Math.abs(value1)*(float)APPROX_CRITERIA)/(float)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(float)APPROX_CRITERIA)/(float)100)));
+        }
+        else if (obj1 instanceof Integer)
+        {
+            int value1 = ((Integer)obj1).intValue();
+            int value2 = ((Integer)obj2).intValue();
+            return (value2 >= (value1-((Math.abs(value1)*(int)APPROX_CRITERIA)/(int)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(int)APPROX_CRITERIA)/(int)100)));
+        }
+        else if (obj1 instanceof Long)
+        {
+            long value1 = ((Long)obj1).longValue();
+            long value2 = ((Long)obj2).longValue();
+            return (value2 >= (value1-((Math.abs(value1)*(long)APPROX_CRITERIA)/(long)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(long)APPROX_CRITERIA)/(long)100)));
+        }
+        else if (obj1 instanceof Short)
+        {
+            short value1 = ((Short)obj1).shortValue();
+            short value2 = ((Short)obj2).shortValue();
+            return (value2 >= (value1-((Math.abs(value1)*(short)APPROX_CRITERIA)/(short)100)) 
+                && value2 <= (value1+((Math.abs(value1)*(short)APPROX_CRITERIA)/(short)100)));
+        }
+        else if (obj1 instanceof String)
+        {
+            int distance = getDistance(obj1.toString(),obj2.toString());
+            int size = ((String)obj1).length();
+            return (distance <= ((size*APPROX_CRITERIA)/100));
+        }
+        else if (m_hasBigNumbers && (obj1 instanceof BigInteger))
+        {
+            BigInteger value1 = (BigInteger)obj1;
+            BigInteger value2 = (BigInteger)obj2;
+            BigInteger delta = value1.abs().multiply(
+                BigInteger.valueOf(APPROX_CRITERIA)
+                    .divide(BigInteger.valueOf(100)));
+            BigInteger low = value1.subtract(delta);
+            BigInteger high = value1.add(delta);
+            return (value2.compareTo(low) >= 0) && (value2.compareTo(high) <= 0);
+        }
+        else if (m_hasBigNumbers && (obj1 instanceof BigDecimal))
+        {
+            BigDecimal value1 = (BigDecimal)obj1;
+            BigDecimal value2 = (BigDecimal)obj2;
+            BigDecimal delta = value1.abs().multiply(
+                BigDecimal.valueOf(APPROX_CRITERIA)
+                    .divide(BigDecimal.valueOf(100), BigDecimal.ROUND_HALF_DOWN));
+            BigDecimal low = value1.subtract(delta);
+            BigDecimal high = value1.add(delta);
+            return (value2.compareTo(low) >= 0) && (value2.compareTo(high) <= 0);
+        }
+        throw new EvaluationException(
+            "Approximate operator not supported for type "
+            + obj1.getClass().getName());
+    }
+
+    /**
+     * Calculate the Levenshtein distance (LD) between two strings.
+     * The Levenshteing distance is a measure of the similarity between 
+     * two strings, which we will refer to as the source string (s) and 
+     * the target string (t). The distance is the number of deletions, 
+     * insertions, or substitutions required to transform s into t.
+     * 
+     * Algorithm from: http://www.merriampark.com/ld.htm
+     * 
+     * @param s the first string
+     * @param t the second string
+     * @return
+     */
+    private static int getDistance(String s, String t)
+    {
+        int d[][]; // matrix
+        int n; // length of s
+        int m; // length of t
+        int i; // iterates through s
+        int j; // iterates through t
+        char s_i; // ith character of s
+        char t_j; // jth character of t
+        int cost; // cost
+
+        // Step 1
+        n = s.length();
+        m = t.length();
+        if (n == 0)
+        {
+            return m;
+        }
+        if (m == 0)
+        {
+            return n;
+        }
+        d = new int[n + 1][m + 1];
+
+        // Step 2
+        for (i = 0; i <= n; i++)
+        {
+            d[i][0] = i;
+        }
+
+        for (j = 0; j <= m; j++)
+        {
+            d[0][j] = j;
+        }
+
+        // Step 3
+        for (i = 1; i <= n; i++)
+        {
+            s_i = s.charAt(i - 1);
+            // Step 4
+            for (j = 1; j <= m; j++)
+            {
+                t_j = t.charAt(j - 1);
+                // Step 5
+                if (s_i == t_j)
+                {
+                    cost = 0;
+                }
+                else
+                {
+                    cost = 1;
+                }
+                // Step 6
+                d[i][j] =
+                    Minimum(
+                        d[i - 1][j] + 1,
+                        d[i][j - 1] + 1,
+                        d[i - 1][j - 1] + cost);
+            }
+        }
+        // Step 7
+        return d[n][m];
+    }
+
+    /**
+     * Calculate the minimum between three values
+     * 
+     * @param a
+     * @param b
+     * @param c
+     * @return
+     */
+    private static int Minimum(int a, int b, int c)
+    {
+        int mi;
+        mi = a;
+        if (b < mi)
+        {
+            mi = b;
+        }
+        if (c < mi)
+        {
+            mi = c;
+        }
+        return mi;
+    }
+
+    private static void fewOperands(String op) throws EvaluationException
+    {
+        throw new EvaluationException(op + ": too few operands");
+    }
+
+    private static void unsupportedType(String opStr, Class clazz)
+        throws EvaluationException
+    {
+        throw new EvaluationException(
+            opStr + ": unsupported type " + clazz.getName(), clazz);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Unknown.java b/src/org/apache/osgi/framework/util/ldap/Unknown.java
new file mode 100644
index 0000000..8139f8b
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Unknown.java
@@ -0,0 +1,29 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.framework.util.ldap;
+
+/**
+ * This class is used to create simple marker instances that are inserted
+ * into the evaluation stack of a LDAP filter expression when an attribute
+ * is referenced that has no defined value. These invalid marker instances
+ * force the operators to throw an "unsupported type" exception, which the
+ * evaluator catches and then converts the entire subexpression containing
+ * the non-existent attribute to <tt>false</tt>.
+**/
+class Unknown
+{
+}
diff --git a/src/org/apache/osgi/moduleloader/DefaultURLPolicy.java b/src/org/apache/osgi/moduleloader/DefaultURLPolicy.java
new file mode 100644
index 0000000..55b2e96
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/DefaultURLPolicy.java
@@ -0,0 +1,97 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * This class implements a simple <tt>URLPolicy</tt> that the <tt>ModuleManager</tt>
+ * uses if the application does not specify one. This implementation always returns
+ * <tt>null</tt> for <tt>CodeSource</tt> <tt>URL</tt>s, which means that security
+ * is simply ignored. For resource <tt>URL</tt>s, it returns an <tt>URL</tt> in the
+ * form of:
+ * </p>
+ * <pre>
+ *     module://&lt;module-id&gt;/&lt;resource-path&gt;
+ * </pre>
+ * <p>
+ * In order to properly handle the "<tt>module:</tt>" protocol, this policy
+ * also defines a custom <tt>java.net.URLStreamHandler</tt> that it assigns
+ * to each <tt>URL</tt> as it is created. This custom handler is used to
+ * return a custom <tt>java.net.URLConnection</tt> that will correctly parse
+ * the above <tt>URL</tt> and retrieve the associated resource bytes using
+ * methods from <tt>ModuleManager</tt> and <tt>Module</tt>.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.URLPolicy
+**/
+public class DefaultURLPolicy implements URLPolicy
+{
+    private ModuleURLStreamHandler m_handler = null;
+
+    /**
+     * <p>
+     * This method is a stub and always returns <tt>null</tt>.
+     * </p>
+     * @param mgr the <tt>ModuleManager</tt> of the module.
+     * @param module the module for which the <tt>URL</tt> is to be created.
+     * @return <tt>null</tt>.
+    **/
+    public URL createCodeSourceURL(ModuleManager mgr, Module module)
+    {
+        return null;
+    }
+
+    /**
+     * <p>
+     * This method returns a <tt>URL</tt> that is suitable
+     * for accessing the bytes of the specified resource.
+     * </p>
+     * @param mgr the <tt>ModuleManager</tt> of the module.
+     * @param module the module for which the resource is being loaded.
+     * @param rsIdx the index of the <tt>ResourceSource</tt> containing the resource.
+     * @param name the name of the resource being loaded.
+     * @return an <tt>URL</tt> for retrieving the resource.
+    **/
+    public URL createResourceURL(ModuleManager mgr, Module module, int rsIdx, String name)
+    {
+        if (m_handler == null)
+        {
+            m_handler = new ModuleURLStreamHandler(mgr);
+        }
+
+        // Add a slash if there is one already, otherwise
+        // the is no slash separating the host from the file
+        // in the resulting URL.
+        if (!name.startsWith("/"))
+        {
+            name = "/" + name;
+        }
+
+        try
+        {
+            return new URL("module", module.getId(), -1, "/" + rsIdx + name, m_handler);
+        }
+        catch (Exception ex)
+        {
+            System.err.println("DefaultResourceURLPolicy: " + ex);
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/JarResourceSource.java b/src/org/apache/osgi/moduleloader/JarResourceSource.java
new file mode 100644
index 0000000..16cd8fc
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/JarResourceSource.java
@@ -0,0 +1,211 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.io.*;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+/**
+ * <p>
+ * This class implements a <tt>ResourceSource</tt> for retrieving resources
+ * from a JAR file. The approach used by this implementation is to defer
+ * opening the JAR file until a request for a resource is made.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ResourceSource
+**/
+public class JarResourceSource implements ResourceSource
+{
+    private static final int BUFSIZE = 4096;
+
+    private File m_file = null;
+    private JarFile m_jarFile = null;
+    private boolean m_opened = false;
+
+    /**
+     * <p>
+     * Constructs an instance using the specified file name as the source
+     * of the JAR file.
+     * </p>
+     * @param fileName the name of the JAR file to be used as the source.
+    **/
+    public JarResourceSource(String fileName)
+    {
+        m_file = new File(fileName);
+    }
+
+    /**
+     * <p>
+     * Constructs an instance using the specified file as the source
+     * of the JAR file.
+     * </p>
+     * @param file the JAR file to be used as the source.
+    **/
+    public JarResourceSource(File file)
+    {
+        m_file = file;
+    }
+
+    /**
+     * <p>
+     * Closes the JAR file if it has not already been closed.
+     * <p>
+    **/
+    protected void finalize()
+    {
+        if (m_jarFile != null)
+        {
+            try {
+                m_jarFile.close();
+            } catch (IOException ex) {
+                // Not much we can do, so ignore it.
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This method initializes the resource source. Since opening
+     * the JAR file is deferred until a request for a resource is
+     * actually made, this method really only sets a flag indicating
+     * that the resource source has been initialized.
+     * <p>
+    **/
+    public void open()
+    {
+        m_opened = true;
+    }
+
+    /**
+     * <p>
+     * This method deinitializes the resource source by closing
+     * the associated JAR file if it is open.
+     * <p>
+    **/
+    public synchronized void close()
+    {
+        try {
+            if (m_jarFile != null)
+            {
+                m_jarFile.close();
+            }
+        } catch (Exception ex) {
+            System.err.println("JarResourceSource: " + ex);
+        }
+
+        m_jarFile = null;
+        m_opened = false;
+    }
+
+    // JavaDoc comments are copied from ResourceSource.
+    public synchronized boolean hasResource(String name) throws IllegalStateException
+    {
+        if (!m_opened)
+        {
+            throw new IllegalStateException("JarResourceSource is not open");
+        }
+
+        // Open JAR file if not already opened.
+        if (m_jarFile == null)
+        {
+            try {
+                openJarFile();
+            } catch (IOException ex) {
+                System.err.println("JarResourceSource: " + ex);
+                return false;
+            }
+        }
+
+        try {
+            ZipEntry ze = m_jarFile.getEntry(name);
+            return ze != null;
+        } catch (Exception ex) {
+            return false;
+        } finally {
+        }
+    }
+
+    // JavaDoc comments are copied from ResourceSource.
+    public synchronized byte[] getBytes(String name) throws IllegalStateException
+    {
+        if (!m_opened)
+        {
+            throw new IllegalStateException("JarResourceSource is not open");
+        }
+
+        // Open JAR file if not already opened.
+        if (m_jarFile == null)
+        {
+            try {
+                openJarFile();
+            } catch (IOException ex) {
+                System.err.println("JarResourceSource: " + ex);
+                return null;
+            }
+        }
+
+        // Get the embedded resource.
+        InputStream is = null;
+        ByteArrayOutputStream baos = null;
+
+        try {
+            ZipEntry ze = m_jarFile.getEntry(name);
+            if (ze == null)
+            {
+                return null;
+            }
+            is = m_jarFile.getInputStream(ze);
+            if (is == null)
+            {
+                return null;
+            }
+            baos = new ByteArrayOutputStream(BUFSIZE);
+            byte[] buf = new byte[BUFSIZE];
+            int n = 0;
+            while ((n = is.read(buf, 0, buf.length)) >= 0)
+            {
+                baos.write(buf, 0, n);
+            }
+            return baos.toByteArray();
+
+        } catch (Exception ex) {
+            return null;
+        } finally {
+            try {
+                if (baos != null) baos.close();
+            } catch (Exception ex) {
+            }
+            try {
+                if (is != null) is.close();
+            } catch (Exception ex) {
+            }
+        }
+    }
+
+    private void openJarFile() throws IOException
+    {
+        if (m_jarFile == null)
+        {
+            m_jarFile = new JarFile(m_file);
+        }
+    }
+
+    public String toString()
+    {
+        return "JAR " + m_file.getPath();
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/LibrarySource.java b/src/org/apache/osgi/moduleloader/LibrarySource.java
new file mode 100644
index 0000000..775dcd7
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/LibrarySource.java
@@ -0,0 +1,67 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+/**
+ * <p>
+ * This interface represents a source for obtaining native libraries for a
+ * given module via the module's class loader. The main goal of a library
+ * source is to map a library name to a path in the file system.
+ * </p>
+ * <p>
+ * All library sources are initialized before first usage via a call
+ * to the <a href="#open()"><tt>LibrarySource.open()</tt></a> method and
+ * are also deinitialized via a call to
+ * <a href="#open()"><tt>LibrarySource.close()</tt></a>. Library sources
+ * should be implemented such that they can be opened, closed, and then
+ * re-opened.
+ * </p>
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+**/
+public interface LibrarySource
+{
+    /**
+     * <p>
+     * This method initializes the library source. It is called when
+     * the associated module is added to the <tt>ModuleManager</tt>. It
+     * is acceptable for implementations to ignore duplicate calls to this
+     * method if the library source is already opened.
+     * </p>
+    **/
+    public void open();
+
+    /**
+     * <p>
+     * This method de-initializes the library source. It is called when
+     * the associated module is removed from the <tt>ModuleManager</tt> or
+     * when the module is reset by the <tt>ModuleManager</tt>.
+     * </p>
+    **/
+    public void close();
+
+    /**
+     * <p>
+     * Returns a file system path to the specified library.
+     * </p>
+     * @param name the name of the library that is being requested.
+     * @return a file system path to the specified library.
+     * @throws java.lang.IllegalStateException if the resource source has not
+     *         been opened.
+    **/
+    public String getPath(String name) throws IllegalStateException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/Module.java b/src/org/apache/osgi/moduleloader/Module.java
new file mode 100644
index 0000000..c33a5fa
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/Module.java
@@ -0,0 +1,357 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+/**
+ * <p>
+ * The <tt>Module</tt> class is a grouping mechanism for application classes
+ * and resources. Conceptually, most applications are grouped into
+ * entities such as JAR files (containing classes and resources) and native
+ * libraries. In some cases, these entities are core application classes and
+ * resources, while in other cases, these entities are ancillary, such as
+ * dynamically loaded plug-ins. Applications place some level of semantics
+ * onto these types of entities or <i>modules</i>, but for the <tt>ModuleLoader</tt>,
+ * no particular semantics are attached to modules (other than they are a grouping
+ * mechanism for classes and resources). This means that the application
+ * is free to map itself into modules any way that is appropriate.
+ * </p>
+ * <p>
+ * A module has the following features:
+ * </p>
+ * <ul>
+ *   <li>A unique identifier within the scope of its <tt>ModuleManager</tt>.
+ *   </li>
+ *   <li>A set of key-value attribute pairs.
+ *   </li>
+ *   <li>A set of resource sources from which it is possible to
+ *       retrieve classes and resources.
+ *   </li>
+ *   <li>A set of native library sources from which it is possible
+ *       to retrieve native libraries.
+ *   </li>
+ * </ul>
+ * <p>
+ * A module's identifier must be unique within the scope of its
+ * <tt>ModuleManager</tt>, but there is no meaning associated with it. The
+ * set of attribute-value pairs attached to the module have no meaning to
+ * the <tt>ModuleManager</tt>, nor does it consult them at all. The point
+ * of these attributes is to attach meta-data for use by
+ * <a href="SearchPolicy.html"><tt>SearchPolicy</tt></a> implementations.
+ * Attributes are represented as an array of <tt>Object</tt>
+ * arrays, i.e., <tt>Object[][]</tt>. Each element in the attribute array is
+ * a two-element <tt>Object</tt> array, where <tt>Module.KEY_IDX</tt> is the attribute's
+ * key and <tt>Module.VALUE_IDX</tt> is the attribute's value.
+ * </p>
+ * <p>
+ * The actual contents of a module is contained in two sets of sources
+ * for its resources and native libraries,
+ * <a href="ResourceSource.html"><tt>ResourceSource</tt></a>s
+ * and <a href="LibrarySource.html"><tt>LibrarySource</tt></a>s, respectively.
+ * Each module also has a <a href="ModuleClassLoader.html"><tt>ModuleClassLoader</tt></a>
+ * associated with it. The <tt>ModuleClassLoader</tt> consults these two types
+ * of sources to find classes, resources, and native libraries.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+**/
+public class Module
+{
+    /**
+     * This is the index used to retrieve the key of an attribute;
+     * an attribute is represented as an <tt>Object[]</tt> instance.
+    **/
+    public static final int KEY_IDX = 0;
+    /**
+     * This is the index used to retrieve the value of an attribute;
+     * an attribute is represented as an <tt>Object[]</tt> instance.
+    **/
+    public static final int VALUE_IDX = 1;
+
+    private ModuleManager m_mgr = null;
+    private String m_id = null;
+    private boolean m_useParentSource = false;
+    private Map m_attributeMap = new HashMap();
+    private ResourceSource[] m_resSources = null;
+    private LibrarySource[] m_libSources = null;
+    private ModuleClassLoader m_loader = null;
+
+    /**
+     * <p>
+     * Constructs a <tt>Module</tt> instance that will be associated with
+     * the specified <tt>ModuleManager</tt> and will have the specified
+     * identifier, attributes, resource sources, and library sources. In general,
+     * modules should not be created directly, but should be created by making
+     * a call to <tt>ModuleManager.addModule()</tt>.
+     * </p>
+     * @param mgr the <tt>ModuleManager</tt> that will be associated to
+     *       the instance.
+     * @param id the identifier of the instance.
+     * @param attributes the set of attributes associated with the instance.
+     * @param resSources the set of <tt>ResourceSource</tt>s associated with
+     *        the instance.
+     * @param libSources the set of <tt>LibrarySource</tt>s associated with
+     *        the instance.
+     * @param useParentSource a flag indicating whether or not the parent
+     *        class loader should be used as a resource source; this is an
+     *        ugly hack to allow a module to masquerade as the system
+     *        class loader.
+     * @see org.apache.osgi.moduleloader.ModuleManager
+     * @see org.apache.osgi.moduleloader.ResourceSource
+     * @see org.apache.osgi.moduleloader.LibrarySource
+    **/
+    public Module(
+        ModuleManager mgr, String id, Object[][] attributes,
+        ResourceSource[] resSources, LibrarySource[] libSources,
+        boolean useParentSource)
+    {
+        m_mgr = mgr;
+        m_id = id;
+        m_useParentSource = useParentSource;
+        initialize(attributes, resSources, libSources);
+    }
+
+    /**
+     * <p>
+     * Returns the identifier of the module.
+     * </p>
+     * @return the identifier of the module.
+    **/
+    public String getId()
+    {
+        return m_id;
+    }
+
+    /**
+     * <p>
+     * Returns the attribute set associated with this module. Attributes
+     * are represented as an array of <tt>Object</tt> arrays, i.e.,
+     * <tt>Object[][]</tt>. Each element in the attribute array is
+     * two-element <tt>Object</tt> array, where <tt>Module.KEY_IDX</tt>
+     * is the index to the attribute key and <tt>Module.VALUE_IDX</tt>
+     * is the index to the attribute value. The returned array is a
+     * copy and may be freely modified.
+     * </p>
+     * @return the attribute set associated with this module.
+    **/
+    public synchronized Object[][] getAttributes()
+    {
+        Set s = m_attributeMap.entrySet();
+        Object[][] attributes = new Object[s.size()][];
+        Iterator iter = s.iterator();
+        for (int i = 0; iter.hasNext(); i++)
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            attributes[i] = new Object[] { entry.getKey(), entry.getValue() };
+        }
+        return attributes;
+    }
+
+    /**
+     * <p>
+     * Returns the attribute value associated with the specified key.
+     * </p>
+     * @param key the key of the attribute whose value is to be retrieved.
+     * @return the attribute's value or <tt>null</tt>.
+    **/
+    public synchronized Object getAttribute(String key)
+    {
+        return m_attributeMap.get(key);
+    }
+
+    /**
+     * <p>
+     * Sets the attribute value associated with the specified key. The
+     * attribute will be added if it does not currently exist.
+     * </p>
+     * @param key the key of the attribute whose value is to be set.
+     * @param value the new value to be associated with the attribute key.
+    **/
+    public synchronized void setAttribute(String key, Object value)
+    {
+        m_attributeMap.put(key, value);
+    }
+
+    /**
+     * <p>
+     * Returns the array of <tt>ResourceSource</tt>s associated with
+     * the module. The returned array is not a copy and therefore should
+     * not be modified.
+     * </p>
+     * @return the array of <tt>ResourceSource</tt>s associated with
+     *         the module.
+     * @see org.apache.osgi.moduleloader.ResourceSource
+    **/
+    public ResourceSource[] getResourceSources()
+    {
+        return m_resSources;
+    }
+
+    /**
+     * <p>
+     * Returns the array of <tt>LibrarySource</tt>s associated with
+     * the module. The returned array is not a copy and therefore should
+     * not be modified.
+     * </p>
+     * @return the array of <tt>LibrarySource</tt>s associated with
+     *         the module.
+     * @see org.apache.osgi.moduleloader.LibrarySource
+    **/
+    public LibrarySource[] getLibrarySources()
+    {
+        return m_libSources;
+    }
+
+    /**
+     * <p>
+     * Returns the <tt>ModuleClassLoader</tt> associated with this module.
+     * If a security manager is installed, then this method uses a privileged
+     * action to avoid a security exception being thrown to the caller.
+     * </p>
+     * @return the <tt>ModuleClassLoader</tt> associated with this module.
+     * @see org.apache.osgi.moduleloader.ModuleClassLoader
+    **/
+    public synchronized ModuleClassLoader getClassLoader()
+    {
+        if (m_loader == null)
+        {
+            if (System.getSecurityManager() != null)
+            {
+                m_loader = (ModuleClassLoader) AccessController.doPrivileged(
+                    new GetClassLoaderPrivileged(m_mgr, this, m_useParentSource));
+            }
+            else
+            {
+                m_loader = new ModuleClassLoader(m_mgr, this, m_useParentSource);
+            }
+        }
+
+        return m_loader;
+    }
+
+    /**
+     * <p>
+     * Returns the module's identifier.
+     * </p>
+     * @return the module's identifier.
+    **/
+    public String toString()
+    {
+        return m_id;
+    }
+
+    /**
+     * <p>
+     * Resets the module by throwing away its associated class loader and
+     * re-initializing its attributes, resource sources, and library sources
+     * with the specified values.
+     * </p>
+     * @param attributes the new attributes to be associated with the module.
+     * @param resSources the new resource sources to be associated with the module.
+     * @param libSources the new library sources to be associated with the module.
+     * @see org.apache.osgi.moduleloader.ResourceSource
+     * @see org.apache.osgi.moduleloader.LibrarySource
+    **/
+    protected synchronized void reset(
+        Object[][] attributes, ResourceSource[] resSources,
+        LibrarySource[] libSources)
+    {
+        // Throw away class loader.
+        m_loader = null;
+        // Clear attribute map.
+        m_attributeMap.clear();
+        // Close all sources.
+        dispose();
+        // Re-initialize.
+        initialize(attributes, resSources, libSources);
+    }
+
+    /**
+     * <p>
+     * Disposes the module by closing all resource and library sources.
+     * </p>
+    **/
+    protected synchronized void dispose()
+    {
+        // Close sources.
+        for (int i = 0; (m_resSources != null) && (i < m_resSources.length); i++)
+        {
+            m_resSources[i].close();
+        }
+        for (int i = 0; (m_libSources != null) && (i < m_libSources.length); i++)
+        {
+            m_libSources[i].close();
+        }
+    }
+
+    /**
+     * <p>
+     * Initializes the module by copying the specified attribute array into
+     * a map and opening all resource and library sources.
+     * </p>
+     * @param attributes the attributes to be put into a map.
+     * @param resSources the resource sources to be opened.
+     * @param libSources the library sources to be opened.
+     * @see org.apache.osgi.moduleloader.ResourceSource
+     * @see org.apache.osgi.moduleloader.LibrarySource
+    **/
+    private void initialize(
+        Object[][] attributes, ResourceSource[] resSources, LibrarySource[] libSources)
+    {
+        for (int i = 0; (attributes != null) && (i < attributes.length); i++)
+        {
+            m_attributeMap.put(attributes[i][KEY_IDX], attributes[i][VALUE_IDX]);
+        }
+
+        m_resSources = resSources;
+        m_libSources = libSources;
+
+        // Open sources.
+        for (int i = 0; (m_resSources != null) && (i < m_resSources.length); i++)
+        {
+            m_resSources[i].open();
+        }
+        for (int i = 0; (m_libSources != null) && (i < m_libSources.length); i++)
+        {
+            m_libSources[i].open();
+        }
+    }
+
+    private static class GetClassLoaderPrivileged implements PrivilegedAction
+    {
+        private ModuleManager m_mgr = null;
+        private Module m_module = null;
+        private boolean m_useParentSource = false;
+
+        public GetClassLoaderPrivileged(ModuleManager mgr, Module module, boolean useParentSource)
+        {
+            m_mgr = mgr;
+            m_module = module;
+            m_useParentSource = useParentSource;
+        }
+
+        public Object run()
+        {
+            return new ModuleClassLoader(m_mgr, m_module, m_useParentSource);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleClassLoader.java b/src/org/apache/osgi/moduleloader/ModuleClassLoader.java
new file mode 100644
index 0000000..e2c9b96
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleClassLoader.java
@@ -0,0 +1,473 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.SecureClassLoader;
+import java.security.cert.Certificate;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * <p>
+ * Each module that is managed by a <tt>ModuleManager</tt> has a
+ * <tt>ModuleClassLoader</tt> associated with it. The <tt>ModuleClassLoader</tt>
+ * is responsible for loading all classes, resources, and native libraries
+ * for its module. The <tt>ModuleClassLoader</tt> of a module
+ * is accessed using the <tt>Module.getClassLoader()</tt> method. The
+ * <tt>ModuleClassLoader</tt> uses its module's
+ * <a href="ResourceSource.html"><tt>ResourceSource</tt></a>s
+ * and <a href="LibrarySource.html"><tt>LibrarySource</tt></a>s
+ * to perform its function.
+ * </p>
+ * <p>
+ * When loading a class or resource, the <tt>ModuleClassLoader</tt> does
+ * not immediately search its module's <tt>ResourceSource</tt>s, instead
+ * it first delegates the request to the
+ * <a href="SearchPolicy.html"><tt>SearchPolicy</tt></a> of the
+ * <tt>ModuleManager</tt>; this allows applications to inject specific
+ * class/resource loading policies. When the <tt>ModuleClassLoader</tt> delegates
+ * to the search policy, the search policy uses application-specific behavior
+ * to typically service the request from the <tt>ResourceSource</tt>s of
+ * other modules. If the search policy returns a result, then this result is
+ * returned immediately by the <tt>ModuleClassLoader</tt>; otherwise, it searches
+ * the <tt>ResourceSource</tt>s its module in an attempt to satisfy the
+ * original request.
+ * </p>
+ * <p>
+ * <b><i>Important:</i></b> The search policy <i>searches</i> modules in
+ * some application-specific manner in order to find a class or resource.
+ * This <i>search</i> is instigated, either directly or indirectly, by calls
+ * to <tt>ModuleClassLoader.loadClass()</tt> and <tt>ModuleClassLoader.getResource()</tt>,
+ * respectively. In order for the search policy to load a class or resource,
+ * it must <b>not</b> use <tt>ModuleClassLoader.loadClass()</tt> or
+ * <tt>ModuleClassLoader.getResource()</tt> again, because this would result
+ * in an infinite loop. Instead, the <tt>ModuleClassLoader</tt> offers the
+ * the methods <tt>ModuleClassLoader.loadClassFromModule()</tt> and
+ * <tt>ModuleClassLoader.getResourceFromModule()</tt> to search a given module
+ * and to avoid an infinite loop. As an example, consider the following
+ * snippet of code that implements an "exhaustive" search policy:
+ * </p>
+ * <pre>
+ *     ...
+ *     public Class findClass(Module module, String name)
+ *     {
+ *         Module[] modules = m_mgr.getModules();
+ *         for (int i = 0; i < modules.length; i++)
+ *         {
+ *             try {
+ *                 Class clazz = modules[i].getClassLoader().loadClassFromModule(name);
+ *                 if (clazz != null)
+ *                 {
+ *                     return clazz;
+ *                 }
+ *             } catch (Throwable th) {
+ *             }
+ *         }
+ *
+ *         return null;
+ *     }
+ *     ...
+ * </pre>
+ * <p>
+ * In the above code, the search policy "exhaustively" searches every module in the
+ * <tt>ModuleManager</tt> to find the requested resource. Note that this policy
+ * will also search the module that originated the request, which is not totally
+ * necessary since returning <tt>null</tt> will cause the <tt>ModuleClassLoader</tt>
+ * to search the originating module's <tt>ResourceSource</tt>s.
+ * </p>
+**/
+public class ModuleClassLoader extends SecureClassLoader
+{
+    private ModuleManager m_mgr = null;
+    private Module m_module = null;
+    private boolean m_useParentSource = false;
+
+    /**
+     * <p>
+     * Constructs an instance using the specified <tt>ModuleManager</tt>, for
+     * the specified <tt>Module</tt>. This constructor is protected so that
+     * it cannot be created publicly.
+     * </p>
+     * @param mgr the <tt>ModuleManager</tt> of the <tt>Module</tt>.
+     * @param module the <tt>Module</tt> instance associated with the class loader.
+    **/
+    protected ModuleClassLoader(ModuleManager mgr, Module module, boolean useParentSource)
+    {
+        super(ModuleClassLoader.class.getClassLoader());
+        m_mgr = mgr;
+        m_module = module;
+        m_useParentSource = useParentSource;
+    }
+
+    /**
+     * <p>
+     * This method is nearly an exact copy of the ClassLoader.loadClass()
+     * method. The main difference is that it delegates to its associated
+     * <tt>ModuleManager</tt>'s search policy before calling the
+     * <tt>ClassLoader.findClass()</tt> method. Additionally, the synchronized
+     * modifier was removed from the superclass method; this change was necessary
+     * because ClassLoader class assumes a tree of class loaders, but the class
+     * loading structure in the <tt>ModuleManager</tt> might actually be a graph
+     * of class loaders; thus, it was necessary to loosen the concurrency locking
+     * to allow for cycles.
+     * </p>
+     * @param name the class to be loaded.
+     * @param resolve flag indicating whether the class should be resolved or not.
+     * @return the loaded class.
+     * @throws java.lang.ClassNotFoundException if the class could not be loaded.
+    **/
+    protected Class loadClass(String name, boolean resolve)
+        throws ClassNotFoundException
+    {
+        // Make sure the class was not already loaded.
+        Class c = findLoadedClass(name);
+        // Ask the search policy for the clas before consulting the module.
+        c = m_mgr.getSearchPolicy().findClassBeforeModule(getParent(), m_module, name);
+        // If the search policy didn't find it, then consult the module.
+        if (c == null)
+        {
+            c = findClass(name);
+        }
+        // If the module didn't find it, then consult the search policy
+        // one more time.
+        if (c == null)
+        {
+            c = m_mgr.getSearchPolicy().findClassAfterModule(getParent(), m_module, name);
+        }
+        // If still not found, then throw an exception.
+        if (c == null)
+        {
+            throw new ClassNotFoundException(name);
+        }
+        // Otherwise resolve the class.
+        if (resolve)
+        {
+            resolveClass(c);
+        }
+        return c;
+    }
+
+    /**
+     * <p>
+     * This method overriden from from <tt>ClassLoader</tt>.
+     * It is implemented such that it loads classes from the set of
+     * <tt>ResourceSource</tt>s from its associated module.
+     * </p>
+     * @param name the name of the resource to load.
+     * @return the loaded <tt>Class</tt> object.
+     * @throws java.lang.ClassNotFoundException if the class could not be loaded.
+    **/
+    protected Class findClass(String name) throws ClassNotFoundException
+    {
+        Class clazz = findLoadedClass(name);
+
+        // If the parent is used as a source, try to
+        // load the class from it.
+        // TODO: This is really a hack and should be generalized somehow.
+        if (m_useParentSource)
+        {
+            clazz = (getParent() == null) ? null : getParent().loadClass(name);
+        }
+
+        // Otherwise search for class in resource sources.
+        if (clazz == null)
+        {
+            String actual = name.replace('.', '/') + ".class";
+            ResourceSource[] sources = m_module.getResourceSources();
+            for (int i = 0;
+                (clazz == null) && (sources != null) && (i < sources.length);
+                i++)
+            {
+                byte[] bytes = sources[i].getBytes(actual);
+                if (bytes != null)
+                {
+                    // We need to try to define a Package object for the class
+                    // before we call defineClass(). Get the package name and
+                    // see if we have already created the package.
+                    String pkgName = Util.getClassPackage(name);
+                    if (pkgName.length() > 0)
+                    {
+                        if (getPackage(pkgName) == null)
+                        {
+                            Object[] params =
+                                m_mgr.getSearchPolicy().definePackage(m_module, pkgName);
+                            if (params != null)
+                            {
+                                definePackage(
+                                    pkgName,
+                                    (String) params[0],
+                                    (String) params[1],
+                                    (String) params[2],
+                                    (String) params[3],
+                                    (String) params[4],
+                                    (String) params[5],
+                                    null);
+                            }
+                        }
+                    }
+
+                    // Get the code source URL for this class. For concurrency
+                    // purposes, we are performing this call outside of the
+                    // synchronized block below since we call out to application
+                    // code, which might in turn need to call back into the
+                    // module loader code. Because of this, it is better to
+                    // not be holding any locks before making the call.
+                    URL url = m_mgr.getURLPolicy().createCodeSourceURL(
+                        m_mgr, m_module);
+
+                    // If we have a valid code source URL, then use it to
+                    // define the class for security purposes, otherwise
+                    // define the class without a code source.
+                    if (url != null)
+                    {
+                        CodeSource cs = new CodeSource(url, (Certificate[]) null);
+                        clazz = defineClass(name, bytes, 0, bytes.length, cs);
+                    }
+                    else
+                    {
+                        clazz = defineClass(name, bytes, 0, bytes.length);
+                    }
+                }
+            }
+        }
+
+        if (clazz != null)
+        {
+            return clazz;
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * This method is used by <tt>SearchPolicy</tt> instances when they want
+     * to load a class from a module. The search policy is initially invoked when
+     * <tt>ModuleClassLoader.loadClass()</tt> delegates a class loading
+     * request to it. In general, the ultimate goal of the search policy is to
+     * return a class from another module if possible. Unfortunately, if a search
+     * policy tries to directly load a class from another module's class loader, an
+     * infinite loop will result because the module's class loader will delegate the
+     * request back to the search policy. To avoid this situation, search policies
+     * must use this method when trying to load a class from a module.
+     * </p>
+     * @param name the name of the class to load.
+     * @return the loaded class or <tt>null</tt>.
+    **/
+    public Class loadClassFromModule(String name)
+    {
+        try
+        {
+            return findClass(name);
+        } catch (Throwable th) {
+            // Not much we can do.
+// TODO: Do something with this error message.
+//            System.err.println("ModuleClassLoader: " + th.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * <p>
+     * This method is nearly an exact copy of the ClassLoader.getResource()
+     * method. The main difference is that it delegates to its associated
+     * <tt>ModuleManager</tt>'s search policy before calling the
+     * <tt>ClassLoader.findResource()</tt> method.
+     * </p>
+     * @param name the class to be loaded.
+     * @return a URL to the resource or <tt>null</tt> if the resource was not found.
+    **/
+    public URL getResource(String name)
+    {
+        URL url = null;
+
+        // Ask the search policy for the resource.
+        if (m_mgr.getSearchPolicy() != null)
+        {
+            try
+            {
+                url = m_mgr.getSearchPolicy().findResource(getParent(), m_module, name);
+            }
+            catch (ResourceNotFoundException ex)
+            {
+                // We return null here because if SearchPolicy.findResource()
+                // throws an exception we interpret that to mean that the
+                // search should be stopped.
+                return null;
+            }
+        }
+
+        // If not found, then search locally.
+        if (url == null)
+        {
+            url = findResource(name);
+        }
+
+        return url;
+    }
+
+    /**
+     * <p>
+     * This method overriden from from <tt>ClassLoader</tt>.
+     * It is implemented such that it loads resources from the set of
+     * <tt>ResourceSource</tt>s from its associated module.
+     * </p>
+     * @param name the name of the resource to load.
+     * @return the <tt>URL</tt> associated with the resource or <tt>null</tt>.
+    **/
+    protected URL findResource(String name)
+    {
+        URL url = null;
+
+        // If the parent is used as a source, try to
+        // load the class from it.
+        if (m_useParentSource)
+        {
+            url = (getParent() == null) ? null : getParent().getResource(name);
+        }
+
+        // Try to load the resource from the module's resource
+        // sources.
+        if (url == null)
+        {
+            // Remove leading slash, if present.
+            if (name.startsWith("/"))
+            {
+                name = name.substring(1);
+            }
+
+            ResourceSource[] sources = m_module.getResourceSources();
+            for (int i = 0;
+                (url == null) && (sources != null) && (i < sources.length);
+                i++)
+            {
+                if (sources[i].hasResource(name))
+                {
+                    url = m_mgr.getURLPolicy().createResourceURL(m_mgr, m_module, i, name);
+                }
+            }
+        }
+
+        return url;
+    }
+
+    /**
+     * <p>
+     * This method is used by <tt>SearchPolicy</tt> instances when they want
+     * to load a resource from a module. The search policy is initially invoked when
+     * <tt>ModuleClassLoader.loadClass()</tt> delegates a resource loading
+     * request to it. In general, the ultimate goal of the search policy is to
+     * return a resource from another module if possible. Unfortunately, if a search
+     * policy tries to directly load a resource from another module's class loader, an
+     * infinite loop will result because the module's class loader will delegate the
+     * request back to the search policy. To avoid this situation, search policies
+     * must use this method when trying to load a resource from a module.
+     * </p>
+     * @param name the name of the resource to load.
+     * @return a URL to the resource or <tt>null</tt>.
+    **/
+    public URL getResourceFromModule(String name)
+    {
+        try
+        {
+            return findResource(name);
+        }
+        catch (Throwable th)
+        {
+            // Ignore and just return null.
+        }
+        return null;
+    }
+
+    protected Enumeration findResources(String name)
+    {
+        Vector v = new Vector();
+        // If the parent is used as a source, try to
+        // load the class from it.
+        if (m_useParentSource)
+        {
+            try
+            {
+                Enumeration e = (getParent() == null)
+                    ? null : getParent().getResources(name);
+                while ((e != null) && e.hasMoreElements())
+                {
+                    v.addElement(e.nextElement());
+                }
+            }
+            catch (IOException ex)
+            {
+                // What can we do?
+            }
+        }
+
+        // Remove leading slash, if present.
+        if (name.startsWith("/"))
+        {
+            name = name.substring(1);
+        }
+
+        // Try to load the resource from the module's resource
+        // sources.
+
+        ResourceSource[] sources = m_module.getResourceSources();
+        for (int i = 0; (sources != null) && (i < sources.length); i++)
+        {
+            if (sources[i].hasResource(name))
+            {
+                v.addElement(m_mgr.getURLPolicy().createResourceURL(m_mgr, m_module, i, name));
+            }
+        }
+
+        return v.elements();
+    }
+
+    /**
+     * <p>
+     * This method overriden from from <tt>ClassLoader</tt>. It maps a library
+     * name to a library path by consulting the <tt>LibrarySource</tt>s of the
+     * class loader's module.
+     * </p>
+     * @param name the name of the library to find.
+     * @return the file system path of library or <tt>null</tt>
+    **/
+    protected String findLibrary(String name)
+    {
+        // Remove leading slash, if present.
+        if (name.startsWith("/"))
+        {
+            name = name.substring(1);
+        }
+
+        LibrarySource[] sources = m_module.getLibrarySources();
+        for (int i = 0;
+            (sources != null) && (i < sources.length);
+            i++)
+        {
+            String path = sources[i].getPath(name);
+            if (path != null)
+            {
+                return path;
+            }
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleEvent.java b/src/org/apache/osgi/moduleloader/ModuleEvent.java
new file mode 100644
index 0000000..ee878bc
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleEvent.java
@@ -0,0 +1,61 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.util.EventObject;
+
+/**
+ * <p>
+ * This is an event class that is used by the <tt>ModuleManager</tt> to
+ * indicate when modules are added, removed, or reset. To receive these
+ * events, a <tt>ModuleListener</tt> must be added to the <tt>ModuleManager</tt>
+ * instance.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleListener
+**/
+public class ModuleEvent extends EventObject
+{
+    private Module m_module = null;
+
+    /**
+     * <p>
+     * Constructs a module event with the specified <tt>ModuleManager</tt>
+     * as the event source and the specified module as the subject of
+     * the event.
+     * </p>
+     * @param mgr the source of the event.
+     * @param module the subject of the event.
+    **/
+    public ModuleEvent(ModuleManager mgr, Module module)
+    {
+        super(mgr);
+        m_module = module;
+    }
+
+    /**
+     * <p>
+     * Returns the module that is the subject of the event.
+     * </p>
+     * @return the module that is the subject of the event.
+    **/
+    public Module getModule()
+    {
+        return m_module;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleListener.java b/src/org/apache/osgi/moduleloader/ModuleListener.java
new file mode 100644
index 0000000..aae6b3c
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleListener.java
@@ -0,0 +1,58 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.util.EventListener;
+
+/**
+ * <p>
+ * This interface is an event listener for <tt>ModuleEvent</tt> events.
+ * To receive events, an implementation of this listener must be added
+ * to the <tt>ModuleManager</tt> instance.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.ModuleEvent
+**/
+public interface ModuleListener extends EventListener
+{
+    /**
+     * <p>
+     * This method is called after a module is added to the
+     * <tt>ModuleManager</tt>.
+     * </p>
+     * @param event the event object containing the event details.
+    **/
+    public void moduleAdded(ModuleEvent event);
+
+    /**
+     * <p>
+     * This method is called after a module has been reset by the
+     * <tt>ModuleManager</tt>.
+     * </p>
+     * @param event the event object containing the event details.
+    **/
+    public void moduleReset(ModuleEvent event);
+
+    /**
+     * <p>
+     * This method is called after a module is remove from the
+     * <tt>ModuleManager</tt>.
+     * </p>
+     * @param event the event object containing the event details.
+    **/
+    public void moduleRemoved(ModuleEvent event);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleManager.java b/src/org/apache/osgi/moduleloader/ModuleManager.java
new file mode 100644
index 0000000..68a0cc2
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleManager.java
@@ -0,0 +1,524 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.util.*;
+
+/**
+ * <p>
+ * The <tt>ModuleManager</tt> class is the core facility for defining a
+ * re-usable, policy-driven class loader for applications that require
+ * flexible class loading mechanisms. The <tt>ModuleManager</tt> is not
+ * class loader itself, but it supports the concept of a
+ * <a href="Module.html"><tt>Module</tt></a>,
+ * which is a unit of organization for application classes and resources.
+ * The <tt>ModuleManager</tt> has only a handful of methods that allow
+ * an application to add, remove, reset, and query modules; the intent
+ * is to place as few assumptions in the <tt>ModuleManager</tt> as possible.
+ * </p>
+ * <p>
+ * The idea is simple, allow the application to map itself into modules
+ * however it sees fit and let the <tt>ModuleManager</tt> assume the
+ * responsibility of managing the modules and loading classes and resources
+ * from them as necessary via <a href="ModuleClassLoader.html"><tt>ModuleClassLoader</tt></a>s
+ * that are associated with each module. In order to achieve this goal, though, the
+ * <tt>ModuleManager</tt> must make at least one assumption on behalf of
+ * the application. This assumption is that the loading of classes and resources
+ * from the available modules must happen using a search algorithm
+ * that is particular to the application itself. As a result of this assumption,
+ * the <tt>ModuleManager</tt> requires that the application provide a concrete
+ * implementation of the <a href="SearchPolicy.html"><tt>SearchPolicy</tt></a>
+ * interface.
+ * </p>
+ * <p>
+ * The search policy allows the <tt>ModuleLoader</tt> to let applications inject
+ * their own particular class loading policies, without dictating strict or
+ * constraining base assumptions. Of course, it is likely that many applications
+ * will use the same or very similar search policies. Because of this, another
+ * goal of the <tt>ModuleLoader</tt> approach is to foster a common library of
+ * search policies that applications are free to use or customize as they see
+ * fit. These common search policies are analagous to patterns, where each search
+ * policy is viewable as a <i>class loading pattern</i>. Some initial search
+ * policies included with the <tt>ModuleLoader</tt> are
+ * <a href="search/ExhaustiveSearchPolicy.html"><tt>ExhaustiveSearchPolicy</tt></a>,
+ * <a href="search/SelfContainedSearchPolicy.html"><tt>SelfContainedSearchPolicy</tt></a>, and
+ * <a href="search/ImportSearchPolicy.html"><tt>ImportSearchPolicy</tt></a>.
+ * </p>
+ * <p>
+ * Due to the fact that class loaders are tied to security and resource loading,
+ * the search policy alone is not sufficient for the <tt>ModuleLoader</tt> to
+ * perform its function. To fulfill these other purposes, the <tt>ModuleLoader</tt>
+ * introduces another policy interface, called the <a href="URLPolicy.html"><tt>URLPolicy</tt></a>.
+ * The <tt>URLPolicy</tt> allows the application to inject its particular policy
+ * for to purposes:
+ * </p>
+ * <ol>
+ *   <li>Creating the <tt>URL</tt> associated with loading a resource, such as
+ *       the <tt>URL</tt> returned from a call to <tt>Class.getResource()</tt>.
+ *   </li>
+ *   <li>Creating the <tt>URL</tt> that will be associated with a class's
+ *       <tt>CodeSource</tt> when defining the class for purposes of security
+ *       and assigning permissions.
+ *   </li>
+ * </ol>
+ * <p>
+ * The <tt>ModuleLoader</tt> defines a default <tt>URLPolicy</tt>, called
+ * <a href="DefaultURLPolicy.html"><tt>DefaultURLPolicy</tt></a>, that provides
+ * a simple <tt>URLStreamHandler</tt> for accessing resources inside of modules
+ * and that returns <tt>null</tt> for the <tt>CodeSource</tt> <tt>URL</tt>.
+ * Applications only need to supply their own <tt>URLPolicy</tt> if the default
+ * one does not provide the appropriate behavior.
+ * </p>
+ * <p>
+ * It is possible for an application to create multiple instances of the
+ * <tt>ModuleManager</tt> within a single JVM, but it is not possible to
+ * share modules across multiple <tt>ModuleManager</tt>s. A given <tt>ModuleManager</tt>
+ * can only have one <tt>SelectionPolicy</tt> and one <tt>URLPolicy</tt>.
+ * </p>
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.URLPolicy
+ * @see org.apache.osgi.moduleloader.DefaultURLPolicy
+**/
+public class ModuleManager
+{
+    private List m_moduleList = new ArrayList();
+    private Map m_moduleMap = new HashMap();
+    private SearchPolicy m_searchPolicy = null;
+    private URLPolicy m_urlPolicy = null;
+    private ModuleListener[] m_listeners = null;
+    private static final ModuleListener[] m_noListeners = new ModuleListener[0];
+
+    /**
+     * <p>
+     * Constructs a <tt>ModuleManager</tt> instance using the specified
+     * search policy and the default <tt>URL</tt> policy.
+     * </p>
+     * @param searchPolicy the search policy that the instance should use.
+     * @see org.apache.osgi.moduleloader.SearchPolicy
+    **/
+    public ModuleManager(SearchPolicy searchPolicy)
+    {
+        this(searchPolicy, null);
+    }
+
+    /**
+     * <p>
+     * Constructs a <tt>ModuleManager</tt> instance using the specified
+     * search policy and the specified <tt>URL</tt> policy.
+     * </p>
+     * @param searchPolicy the search policy that the instance should use.
+     * @param urlPolicy the <tt>URL</tt> policy that the instance should use.
+     * @see org.apache.osgi.moduleloader.SearchPolicy
+     * @see org.apache.osgi.moduleloader.URLPolicy
+    **/
+    public ModuleManager(SearchPolicy searchPolicy, URLPolicy urlPolicy)
+    {
+        m_listeners = m_noListeners;
+        m_searchPolicy = searchPolicy;
+        m_searchPolicy.setModuleManager(this);
+
+        if (urlPolicy == null)
+        {
+            m_urlPolicy = new DefaultURLPolicy();
+        }
+        else
+        {
+            m_urlPolicy = urlPolicy;
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the <tt>URL</tt> policy used by this instance.
+     * </p>
+     * @return the <tt>URL</tt> policy used by this instance.
+     * @see org.apache.osgi.moduleloader.URLPolicy
+    **/
+    public URLPolicy getURLPolicy()
+    {
+        return m_urlPolicy;
+    }
+
+    /**
+     * <p>
+     * Returns the search policy used by this instance.
+     * </p>
+     * @return the search policy used by this instance.
+     * @see org.apache.osgi.moduleloader.SearchPolicy
+    **/
+    public SearchPolicy getSearchPolicy()
+    {
+        return m_searchPolicy;
+    }
+
+    /**
+     * <p>
+     * Returns an array of all modules being managed by the
+     * <tt>ModuleManager</tt> instance. The array contains a snapshot of
+     * all modules in the <tt>ModuleManager</tt> at the time when this
+     * method was called.
+     * </p>
+     * @return an array of all modules being managed by the <tt>ModuleManager</tt>
+     *         instance.
+     * @see org.apache.osgi.moduleloader.Module
+    **/
+    public synchronized Module[] getModules()
+    {
+        Module[] modules = new Module[m_moduleList.size()];
+        return (Module[]) m_moduleList.toArray(modules);
+    }
+
+    /**
+     * <p>
+     * Returns a module associated with the specified identifier.
+     * </p>
+     * @param id the identifier for the module to be retrieved.
+     * @return the module associated with the identifier or <tt>null</tt>.
+     * @see org.apache.osgi.moduleloader.Module
+    **/
+    public synchronized Module getModule(String id)
+    {
+        return (Module) m_moduleMap.get(id);
+    }
+
+    /**
+     * <p>
+     * Adds a module to the module manager. The module will have the specified
+     * unique identifier, with the associated attributes, resource sources, and
+     * library sources. If the identifier is not unique, then an exception is
+     * thrown.
+     * </p>
+     * @param id the unique identifier of the new module.
+     * @param attributes an array of key-value attribute pairs to
+     *        associate with the module.
+     * @param resSources an array of <tt>ResourceSource</tt>s to associate
+     *        with the module.
+     * @param libSources an array of <tt>LibrarySource</tt>s to associate
+     *        with the module.
+     * @return the newly created module.
+     * @throws java.lang.IllegalArgumentException if the module identifier
+     *         is not unique.
+     * @see org.apache.osgi.moduleloader.Module
+     * @see org.apache.osgi.moduleloader.ResourceSource
+     * @see org.apache.osgi.moduleloader.LibrarySource
+    **/
+    public Module addModule(String id, Object[][] attributes,
+        ResourceSource[] resSources, LibrarySource[] libSources)
+    {
+        return addModule(id, attributes, resSources, libSources, false);
+    }
+
+    public Module addModule(String id, Object[][] attributes,
+        ResourceSource[] resSources, LibrarySource[] libSources,
+        boolean useParentSource)
+    {
+        Module module = null;
+
+        // Use a synchronized block instead of synchronizing the
+        // method, so we can fire our event outside of the block.
+        synchronized (this)
+        {
+            if (m_moduleMap.get(id) == null)
+            {
+                module = new Module(this, id, attributes, resSources, libSources, useParentSource);
+                m_moduleList.add(module);
+                m_moduleMap.put(id, module);
+            }
+            else
+            {
+                throw new IllegalArgumentException("Module ID must be unique.");
+            }
+        }
+
+        // Fire event here instead of inside synchronized block.
+        fireModuleAdded(module);
+
+        return module;
+    }
+
+    /**
+     * <p>
+     * Resets a given module. In resetting a module, the module's associated
+     * class loader is thrown away; it is the application's responsibility to
+     * determine when and how that application code stops using classes (and
+     * subsequent instances) from the class loader of the reset module.
+     * This method allows the associated elements of the module (i.e.,
+     * attributes, resource sources, and library sources) to be changed also;
+     * if these elements have not changed then they simply need to be passed
+     * back in from the existing module. This method is useful in situations
+     * where the underlying module needs to be changed at run time, such as
+     * might be necessary if a module was updated.
+     * </p>
+     * <p>
+     * The same effect could be achieved by first removing and then re-adding
+     * a module, but with one subtle different. By removing and then re-adding
+     * a module, a new module is created and, thus, all existing references
+     * become invalid. By explicitly having this method, the <tt>ModuleManager</tt>
+     * maintains the integrity of the module reference, which is more intuitive
+     * in the case where an updated module is intended to be the same module,
+     * only updated.
+     * </p>
+     * @param module the module reset.
+     * @param attributes an array of key-value attribute pairs to
+     *        associate with the module.
+     * @param resSources an array of <tt>ResourceSource</tt>s to associate
+     *        with the module.
+     * @param libSources an array of <tt>LibrarySource</tt>s to associate
+     *        with the module.
+     * @see org.apache.osgi.moduleloader.Module
+     * @see org.apache.osgi.moduleloader.ResourceSource
+     * @see org.apache.osgi.moduleloader.LibrarySource
+    **/
+    public void resetModule(
+        Module module, Object[][] attributes,
+        ResourceSource[] resSources, LibrarySource[] libSources)
+    {
+        // Use a synchronized block instead of synchronizing the
+        // method, so we can fire our event outside of the block.
+        synchronized (this)
+        {
+            module = (Module) m_moduleMap.get(module.getId());
+            if (module != null)
+            {
+                module.reset(attributes, resSources, libSources);
+            }
+            else
+            {
+                // Don't fire event.
+                return;
+            }
+        }
+
+        // Fire event here instead of inside synchronized block.
+        fireModuleReset(module);
+    }
+
+    /**
+     * <p>
+     * Removes the specified module from the <tt>ModuleManager</tt>. Removing
+     * a module only removed the module from the <tt>ModuleManager</tt>. It is
+     * the application's responsibility to determine when and how application code
+     * stop using classes (and subsequent instances) that were loaded from
+     * the class loader of the removed module.
+     * </p>
+     * @param module the module to remove.
+    **/
+    public void removeModule(Module module)
+    {
+        // Use a synchronized block instead of synchronizing the
+        // method, so we can fire our event outside of the block.
+        synchronized (this)
+        {
+            if (m_moduleMap.get(module.getId()) != null)
+            {
+                // Remove from data structures.
+                m_moduleList.remove(module);
+                m_moduleMap.remove(module.getId());
+
+                // Dispose of the module.
+                module.dispose();
+            }
+            else
+            {
+                // Don't fire event.
+                return;
+            }
+        }
+
+        // Fire event here instead of inside synchronized block.
+        fireModuleRemoved(module);
+    }
+
+    /**
+     * <p>
+     * Adds a listener to the <tt>ModuleManager</tt> to listen for
+     * module added, reset, and removed events.
+     * </p>
+     * @param l the <tt>ModuleListener</tt> to add.
+    **/
+    public void addModuleListener(ModuleListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        // Use the m_noListeners object as a lock.
+        synchronized (m_noListeners)
+        {
+            // If we have no listeners, then just add the new listener.
+            if (m_listeners == m_noListeners)
+            {
+                m_listeners = new ModuleListener[] { l };
+            }
+            // Otherwise, we need to do some array copying.
+            // Notice, the old array is always valid, so if
+            // the dispatch thread is in the middle of a dispatch,
+            // then it has a reference to the old listener array
+            // and is not affected by the new value.
+            else
+            {
+                ModuleListener[] newList = new ModuleListener[m_listeners.length + 1];
+                System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+                newList[m_listeners.length] = l;
+                m_listeners = newList;
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Removes a listener from the <tt>ModuleManager</tt>.
+     * </p>
+     * @param l the <tt>ModuleListener</tt> to remove.
+    **/
+    public void removeModuleListener(ModuleListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        // Use the m_noListeners object as a lock.
+        synchronized (m_noListeners)
+        {
+            // Try to find the instance in our list.
+            int idx = -1;
+            for (int i = 0; i < m_listeners.length; i++)
+            {
+                if (m_listeners[i].equals(l))
+                {
+                    idx = i;
+                    break;
+                }
+            }
+
+            // If we have the instance, then remove it.
+            if (idx >= 0)
+            {
+                // If this is the last listener, then point to empty list.
+                if (m_listeners.length == 1)
+                {
+                    m_listeners = m_noListeners;
+                }
+                // Otherwise, we need to do some array copying.
+                // Notice, the old array is always valid, so if
+                // the dispatch thread is in the middle of a dispatch,
+                // then it has a reference to the old listener array
+                // and is not affected by the new value.
+                else
+                {
+                    ModuleListener[] newList = new ModuleListener[m_listeners.length - 1];
+                    System.arraycopy(m_listeners, 0, newList, 0, idx);
+                    if (idx < newList.length)
+                    {
+                        System.arraycopy(m_listeners, idx + 1, newList, idx,
+                            newList.length - idx);
+                    }
+                    m_listeners = newList;
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Fires an event indicating that the specified module was added
+     * to the <tt>ModuleManager</tt>.
+     * </p>
+     * @param module the module that was added.
+    **/
+    protected void fireModuleAdded(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ModuleListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(this, module);
+            }
+            listeners[i].moduleAdded(event);
+        }
+    }
+
+    /**
+     * <p>
+     * Fires an event indicating that the specified module was reset.
+     * </p>
+     * @param module the module that was reset.
+    **/
+    protected void fireModuleReset(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ModuleListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(this, module);
+            }
+            listeners[i].moduleReset(event);
+        }
+    }
+
+    /**
+     * <p>
+     * Fires an event indicating that the specified module was removed
+     * from the <tt>ModuleManager</tt>.
+     * </p>
+     * @param module the module that was removed.
+    **/
+    protected void fireModuleRemoved(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ModuleListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(this, module);
+            }
+            listeners[i].moduleRemoved(event);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleURLConnection.java b/src/org/apache/osgi/moduleloader/ModuleURLConnection.java
new file mode 100644
index 0000000..92cacf2
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleURLConnection.java
@@ -0,0 +1,159 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+class ModuleURLConnection extends URLConnection
+{
+    private ModuleManager m_mgr = null;
+    private int m_contentLength;
+    private long m_contentTime;
+    private String m_contentType;
+    private InputStream m_is;
+
+    public ModuleURLConnection(ModuleManager mgr, URL url)
+    {
+        super(url);
+        m_mgr = mgr;
+    }
+
+    public void connect() throws IOException
+    {
+        if (!connected)
+        {
+            // The URL is constructed like this:
+            // module://<module-id>/<source-idx>/<resource-path>
+            Module module = m_mgr.getModule(url.getHost());
+            if (module == null)
+            {
+                throw new IOException("Unable to find bundle's module.");
+            }
+
+            String resource = url.getFile();
+            if (resource == null)
+            {
+                throw new IOException("Unable to find resource: " + url.toString());
+            }
+            if (resource.startsWith("/"))
+            {
+                resource = resource.substring(1);
+            }
+            int rsIdx = -1;
+            try
+            {
+                rsIdx = Integer.parseInt(resource.substring(0, resource.indexOf("/")));
+            }
+            catch (NumberFormatException ex)
+            {
+                new IOException("Error parsing resource index.");
+            }
+            resource = resource.substring(resource.indexOf("/") + 1);
+
+            // Get the resource bytes from the resource source.
+            byte[] bytes = null;
+            ResourceSource[] resSources = module.getResourceSources();
+            if ((resSources != null) && (rsIdx < resSources.length))
+            {
+                if (resSources[rsIdx].hasResource(resource))
+                {
+                    bytes = resSources[rsIdx].getBytes(resource);
+                }
+            }
+
+            if (bytes == null)
+            {
+                throw new IOException("Unable to find resource: " + url.toString());
+            }
+
+            m_is = new ByteArrayInputStream(bytes);
+            m_contentLength = bytes.length;
+            m_contentTime = 0L;  // TODO: Change this.
+            m_contentType = URLConnection.guessContentTypeFromName(resource);
+            connected = true;
+        }
+    }
+
+    public InputStream getInputStream()
+        throws IOException
+    {
+        if (!connected)
+        {
+            connect();
+        }
+        return m_is;
+    }
+
+    public int getContentLength()
+    {
+        if (!connected)
+        {
+            try {
+                connect();
+            } catch(IOException ex) {
+                return -1;
+            }
+        }
+        return m_contentLength;
+    }
+
+    public long getLastModified()
+    {
+        if (!connected)
+        {
+            try {
+                connect();
+            } catch(IOException ex) {
+                return 0;
+            }
+        }
+        if (m_contentTime != -1L)
+        {
+            return m_contentTime;
+        }
+        else
+        {
+            return 0L;
+        }
+    }
+
+    public String getContentType()
+    {
+        if (!connected)
+        {
+            try {
+                connect();
+            } catch(IOException ex) {
+                return null;
+            }
+        }
+        return m_contentType;
+    }
+
+    public Permission getPermission()
+    {
+        // TODO: This should probably return a FilePermission
+        // to access the bundle JAR file, but we don't have the
+        // necessary information here to construct the absolute
+        // path of the JAR file...so it would take some
+        // re-arranging to get this to work.
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleURLStreamHandler.java b/src/org/apache/osgi/moduleloader/ModuleURLStreamHandler.java
new file mode 100644
index 0000000..3cba43a
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleURLStreamHandler.java
@@ -0,0 +1,35 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.io.IOException;
+import java.net.*;
+
+class ModuleURLStreamHandler extends URLStreamHandler
+{
+    private ModuleManager m_mgr = null;
+
+    public ModuleURLStreamHandler(ModuleManager mgr)
+    {
+        m_mgr = mgr;
+    }
+
+    protected URLConnection openConnection(URL url) throws IOException
+    {
+        return new ModuleURLConnection(m_mgr, url);
+    }
+}
diff --git a/src/org/apache/osgi/moduleloader/ResourceNotFoundException.java b/src/org/apache/osgi/moduleloader/ResourceNotFoundException.java
new file mode 100644
index 0000000..c10b451
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ResourceNotFoundException.java
@@ -0,0 +1,25 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+public class ResourceNotFoundException extends Exception
+{
+    public ResourceNotFoundException(String msg)
+    {
+        super(msg);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ResourceSource.java b/src/org/apache/osgi/moduleloader/ResourceSource.java
new file mode 100644
index 0000000..a710a60
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ResourceSource.java
@@ -0,0 +1,84 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+/**
+ * <p>
+ * This interface represents a source for obtaining resources for a
+ * given module via the module's class loader. A resource source is used
+ * for retrieving both classes and resources; at this level, classes are
+ * treated in an identical manner as an ordinary resource. Resource sources
+ * are completely arbitrary and implementations may load resources from a JAR
+ * file, the network, a database, or anywhere.
+ * </p>
+ * <p>
+ * All resource sources are initialized before first usage via a call
+ * to the <a href="#open()"><tt>ResourceSource.open()</tt></a> method and
+ * are also deinitialized via a call to
+ * <a href="#open()"><tt>ResourceSource.close()</tt></a>. Resource sources
+ * should be implemented such that they can be opened, closed, and then
+ * re-opened.
+ * </p>
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+**/
+public interface ResourceSource
+{
+    /**
+     * <p>
+     * This method initializes the resource source. It is called when
+     * the associated module is added to the <tt>ModuleManager</tt>. It
+     * is acceptable for implementations to ignore duplicate calls to this
+     * method if the resource source is already opened.
+     * </p>
+    **/
+    public void open();
+
+    /**
+     * <p>
+     * This method de-initializes the resource source. It is called when
+     * the associated module is removed from the <tt>ModuleManager</tt> or
+     * when the module is reset by the <tt>ModuleManager</tt>.
+     * </p>
+    **/
+    public void close();
+
+    /**
+     * <p>
+     * This method returns a boolean indicating whether the resource source
+     * contains the specified resource.
+     * </p>
+     * @param name the name of the resource whose existence is being checked.
+     * @param <tt>true</tt> if the resource source has the resource, <tt>false</tt>
+     *        otherwise.
+     * @throws java.lang.IllegalStateException if the resource source has not
+     *         been opened.
+    **/
+    public boolean hasResource(String name) throws IllegalStateException;
+
+    /**
+     * <p>
+     * This method returns a byte array of the specified resource's contents.
+     * </p>
+     * @param name the name of the resource to retrieve.
+     * @param a byte array of the resource's contents or <tt>null</tt>
+     *        if the resource was not found.
+     * @throws java.lang.IllegalStateException if the resource source has not
+     *         been opened.
+    **/
+    public byte[] getBytes(String name) throws IllegalStateException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/SearchPolicy.java b/src/org/apache/osgi/moduleloader/SearchPolicy.java
new file mode 100644
index 0000000..fac3d23
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/SearchPolicy.java
@@ -0,0 +1,234 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * This interface represents a policy to define the most basic behavior
+ * of how classes, resources, and native libraries within a specific instance
+ * of <tt>ModuleManager</tt> are found. A <tt>ModuleManager</tt> manages a set of
+ * <tt>Module</tt>s, each of which is a potential source of classes, resources,
+ * and native libraries. The search policy makes it possible to consult these
+ * sources without hard-coding assumptions about application behavior
+ * or structure. Applicaitons inject their own specific class loading policy
+ * by creating a custom search policy or by selecting a pre-existing search
+ * policy that matches their needs.
+ * </p>
+ * <p>
+ * The search policy is used by <tt>ModuleClassLoader</tt>, of which, there
+ * is one per <tt>Module</tt> within a given <tt>ModuleManager</tt> instance.
+ * The search policy is consulted by the <tt>ModuleClassLoader</tt> whenever
+ * there is a request for a class, resource, or native library. The search
+ * policy will generally search other modules in an application-specific
+ * way in order to find the requested item; for example, an application may
+ * use a policy where module's may import from one another. If the search
+ * policy provides an answer, then the <tt>ModuleClassLoader</tt> will use
+ * this to answer the originating request.
+ * </p>
+ * <p>
+ * <b><i>Important:</i></b> The search policy <i>searches</i> modules in
+ * some application-specific manner in order to find a class or resource.
+ * This <i>search</i> is instigated, either directly or indirectly, by calls
+ * to <tt>ModuleClassLoader.loadClass()</tt> and <tt>ModuleClassLoader.getResource()</tt>,
+ * respectively. In order for the search policy to load a class or resource,
+ * it must <b>not</b> use <tt>ModuleClassLoader.loadClass()</tt> or
+ * <tt>ModuleClassLoader.getResource()</tt> again, because this would result
+ * in an infinite loop. Instead, the <tt>ModuleClassLoader</tt> offers the
+ * the methods <tt>ModuleClassLoader.loadClassFromModule()</tt> and
+ * <tt>ModuleClassLoader.getResourceFromModule()</tt> to search a given module
+ * and to avoid an infinite loop.
+ * </p>
+ * <pre>
+ *     ...
+ *     public Class findClass(Module module, String name)
+ *     {
+ *         Module[] modules = m_mgr.getModules();
+ *         for (int i = 0; i < modules.length; i++)
+ *         {
+ *             try {
+ *                 Class clazz = modules[i].getClassLoader().loadClassFromModule(name);
+ *                 if (clazz != null)
+ *                 {
+ *                     return clazz;
+ *                 }
+ *             } catch (Throwable th) {
+ *             }
+ *         }
+ *
+ *         return null;
+ *     }
+ *     ...
+ * </pre>
+ * <p>
+ * In the above code, the search policy "exhaustively" searches every module in the
+ * <tt>ModuleManager</tt> to find the requested resource. Note that this policy
+ * will also search the module that originated the request, which is not totally
+ * necessary since returning <tt>null</tt> will cause the <tt>ModuleClassLoader</tt>
+ * to search the originating module's <tt>ResourceSource</tt>s.
+ * </p>
+**/
+public interface SearchPolicy
+{
+    /**
+     * <p>
+     * This method is called once by the <tt>ModuleManager</tt> to
+     * give the search policy instance a reference to its associated
+     * module manager. This method should be implemented such that
+     * it cannot be called twice; calling this method a second time
+     * should produce an illegal state exception.
+     * </p>
+     * @param mgr the module manager associated with this search policy.
+     * @throws java.lang.IllegalStateException if the method is called
+     *         more than once.
+    **/
+    public void setModuleManager(ModuleManager mgr)
+        throws IllegalStateException;
+
+    /**
+     * <p>
+     * The <tt>ModuleClassLoader</tt> calls this method before performing
+     * the call to <tt>ClassLoader.defineClass()</tt> to give the search policy
+     * an opportunity to define the <tt>Package</tt> object for the specified
+     * package. The method should return an array of <tt>String</tt> values for
+     * each of the following: specTitle, specVersion, specVendor, implTitle,
+     * implVersion, and implVendor. See <tt>ClassLoader.definePackage()</tt>
+     * for more details. The returned array may contain <tt>null</tt>s, but
+     * the return array must have six elements.
+     * </p>
+     * @param module the module requesting a class from the package.
+     * @param pkgName the package name of the class being requested.
+     * @return an array containing values for creating the <tt>Package</tt>
+     *         object for the specified package.
+    **/
+    public Object[] definePackage(Module module, String pkgName);
+
+    /**
+     * <p>
+     * When a module instigates a class load operation, this method is called
+     * to find the desired class for the instigating module. This method is
+     * called <b>before</b> searching the module's resource sources for the class.
+     * How the class is found is dependent upon the search policy implementation.
+     * </p>
+     * <p>
+     * This method may return <tt>null</tt> or throw an exception if the
+     * specified class is not found. Whether a specific search policy
+     * implementation should do one or the other depends on the details
+     * of the specific search policy. The <tt>ModuleClassLoader</tt>
+     * first delegates to this method, then to the local resources
+     * sources of the module, and then finally to then the
+     * <tt>SearchPolicy.findClassAfterModule()</tt> method. If this method
+     * returns null, then the search for the class will continue to these
+     * latter two steps. On the other hand, if this method returns a class
+     * or throws an exception, then the latter two steps will not be searched.
+     * </p>
+     * <p>
+     * <b>Important:</b> If the implementation of this method delegates
+     * the class loading to a <tt>ModuleClassLoader</tt> of another module,
+     * then it should <b>not</b> use the method <tt>ModuleClassLoader.loadClass()</tt>
+     * to load the class; it should use <tt>ModuleClassLoader.loadClassFromModule()</tt>
+     * instead. This is necessary to eliminate an infinite loop that would
+     * occur otherwise. Also, with respect to the <tt>ModuleLoader</tt> framework,
+     * this method will only be called by a single thread at a time and is only
+     * intended to be called by <tt>ModuleClassLoader.loadClass()</tt>.
+     * </p>
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the class.
+     * @param name the name of the class being loaded.
+     * @return the class if found, <tt>null</tt> otherwise.
+     * @throws java.lang.ClassNotFoundException if the class could not be
+     *         found and the entire search operation should fail.
+    **/
+    public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+        throws ClassNotFoundException;
+
+    /**
+     * <p>
+     * When a module instigates a class load operation, this method is called
+     * to find the desired class for the instigating module. This method is
+     * called <b>after</b> searching the module's resource sources for the class.
+     * How the class is found is dependent upon the search policy implementation.
+     * </p>
+     * <p>
+     * The <tt>ModuleClassLoader</tt> first delegates to the
+     * <tt>SearchPolicy.findClassBeforeModule() method, then to the local
+     * resources sources of the module, and then finally to this method.
+     * This method is the last attempt to find the class and if it fails
+     * (by either return <tt>null</tt> or throwing an exception), then the
+     * result of the entire class load will fail.
+     * </p>
+     * <p>
+     * <b>Important:</b> If the implementation of this method delegates
+     * the class loading to a <tt>ModuleClassLoader</tt> of another module,
+     * then it should <b>not</b> use the method <tt>ModuleClassLoader.loadClass()</tt>
+     * to load the class; it should use <tt>ModuleClassLoader.loadClassFromModule()</tt>
+     * instead. This is necessary to eliminate an infinite loop that would
+     * occur otherwise. Also, with respect to the <tt>ModuleLoader</tt> framework,
+     * this method will only be called by a single thread at a time and is only
+     * intended to be called by <tt>ModuleClassLoader.loadClass()</tt>.
+     * </p>
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the class.
+     * @param name the name of the class being loaded.
+     * @return the class if found, <tt>null</tt> otherwise.
+     * @throws java.lang.ClassNotFoundException if the class could not be
+     *         found and the entire search operation should fail.
+    **/
+    public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+        throws ClassNotFoundException;
+
+    /**
+     * <p>
+     * This method tries to find the specified resource for the specified
+     * module. How the resource is found or whether it is actually retrieved
+     * from the specified module is dependent upon the implementation. The
+     * default <tt>ModuleClassLoader.getResource()</tt> method does not do
+     * any searching on its own.
+     * </p>
+     * <p>
+     * This method may return <tt>null</tt> or throw an exception if the
+     * specified resource is not found. Whether a specific search policy
+     * implementation should do one or the other depends on the details
+     * of the specific search policy. The <tt>ModuleClassLoader</tt>
+     * first delegates to this method and then to the local resource
+     * sources of the module. If this method returns null, then the local
+     * resource sources will be searched. On the other hand, if this method
+     * throws an exception, then the local resource sources will not be
+     * searched.
+     * </p>
+     * <p>
+     * <b>Important:</b> If the implementation of this method delegates
+     * the resource loading to a <tt>ModuleClassLoader</tt> of another module,
+     * then it should not use the method <tt>ModuleClassLoader.getResource()</tt>
+     * to get the resource; it should use <tt>ModuleClassLoader.getResourceFromModule()</tt>
+     * instead. This is necessary to eliminate an infinite loop that would
+     * occur otherwise. Also, with respect to the <tt>ModuleLoader</tt> framework,
+     * this method will only be called by a single thread at a time and is not
+     * intended to be called directly.
+     * </p>
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the resource.
+     * @param name the name of the resource being loaded.
+     * @return a <tt>URL</tt> to the resource if found, <tt>null</tt> otherwise.
+     * @throws org.apache.osgi.moduleloader.ResourceNotFoundException if the
+     *         resource could not be found and the entire search operation
+     *         should fail.
+    **/
+    public URL findResource(ClassLoader parent, Module module, String name)
+        throws ResourceNotFoundException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/URLPolicy.java b/src/org/apache/osgi/moduleloader/URLPolicy.java
new file mode 100644
index 0000000..0372c96
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/URLPolicy.java
@@ -0,0 +1,74 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * This interface represents the <tt>ModuleLoader</tt>'s policy for creating
+ * <tt>URL</tt> for resource loading and security purposes. Java requires the
+ * use of <tt>URL</tt>s for resource loading and security. For resource loading,
+ * <tt>URL</tt>s are returned for requested resources. Subsequently, the resource
+ * <tt>URL</tt> is used to create an input stream for reading the resources
+ * bytes. With respect to security, <tt>URL</tt>s are used when defining a
+ * class in order to determine where the code came from, this concept is called
+ * a <tt>CodeSource</tt>. This approach enables Java to assign permissions to
+ * code that originates from particular locations.
+ * </p>
+ * <p>
+ * The <tt>ModuleManager</tt> requires a concrete implementation of this
+ * interface in order to function. Whenever the <tt>ModuleManager</tt> requires
+ * a <tt>URL</tt> for either resource loading or security, it delegates to
+ * the policy implementation. A default implementation is provided,
+ * called <a href="DefaultURLPolicy.html"><tt>DefaultURLPolicy</tt></a>, but
+ * it only supports resource loading, not security.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.DefaultURLPolicy
+**/
+public interface URLPolicy
+{
+    /**
+     * <p>
+     * This method should return a <tt>URL</tt> that represents the
+     * location from which the module originated. This <tt>URL</tt>
+     * can be used when assigning permissions to the module, such as
+     * is done in the Java permissions policy file.
+     * </p>
+     * @param mgr the <tt>ModuleManager</tt> of the module.
+     * @param module the module for which the <tt>URL</tt> is to be created.
+     * @return an <tt>URL</tt> to associate with the module.
+    **/
+    public URL createCodeSourceURL(ModuleManager mgr, Module module);
+
+    /**
+     * <p>
+     * This method should return a <tt>URL</tt> that is suitable
+     * for accessing the bytes of the specified resource. It must be possible
+     * open a connection to this <tt>URL</tt>, which may require that
+     * the implementer of this method also introduce a custom
+     * <tt>java.net.URLStreamHander</tt> when creating the <tt>URL</tt>.
+     * </p>
+     * @param mgr the <tt>ModuleManager</tt> of the module.
+     * @param module the module for which the resource is being loaded.
+     * @param rsIdx the index of the <tt>ResourceSource</tt> containing the resource.
+     * @param name the name of the resource being loaded.
+     * @return an <tt>URL</tt> for retrieving the resource.
+    **/
+    public URL createResourceURL(ModuleManager mgr, Module module, int rsIdx, String name);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/Util.java b/src/org/apache/osgi/moduleloader/Util.java
new file mode 100755
index 0000000..28a12da
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/Util.java
@@ -0,0 +1,64 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader;
+
+public class Util
+{
+    public static String getClassName(String className)
+    {
+        if (className == null)
+        {
+            className = "";
+        }
+        return (className.lastIndexOf('.') < 0)
+            ? "" : className.substring(className.lastIndexOf('.') + 1);
+    }
+
+    public static String getClassPackage(String className)
+    {
+        if (className == null)
+        {
+            className = "";
+        }
+        return (className.lastIndexOf('.') < 0)
+            ? "" : className.substring(0, className.lastIndexOf('.'));
+    }
+
+    public static String getResourcePackage(String resource)
+    {
+        if (resource == null)
+        {
+            resource = "";
+        }
+        // NOTE: The package of a resource is tricky to determine since
+        // resources do not follow the same naming conventions as classes.
+        // This code is pessimistic and assumes that the package of a
+        // resource is everything up to the last '/' character. By making
+        // this choice, it will not be possible to load resources from
+        // imports using relative resource names. For example, if a
+        // bundle exports "foo" and an importer of "foo" tries to load
+        // "/foo/bar/myresource.txt", this will not be found in the exporter
+        // because the following algorithm assumes the package name is
+        // "foo.bar", not just "foo". This only affects imported resources,
+        // local resources will work as expected.
+        String pkgName = (resource.startsWith("/")) ? resource.substring(1) : resource;
+        pkgName = (pkgName.lastIndexOf('/') < 0)
+            ? "" : pkgName.substring(0, pkgName.lastIndexOf('/'));
+        pkgName = pkgName.replace('/', '.');
+        return pkgName;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/CompatibilityPolicy.java b/src/org/apache/osgi/moduleloader/search/CompatibilityPolicy.java
new file mode 100644
index 0000000..2ebb074
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/CompatibilityPolicy.java
@@ -0,0 +1,62 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+/**
+ * <p>
+ * This interface represents the naming and version numbering policy of
+ * import and export identifiers for the <tt>ImportSearchPolicy</tt>. A concrete
+ * implementation of this interface is required to create an instance
+ * of <tt>ImportSearchPolicy</tt>. The sole purpose of this interface
+ * is to allow the <tt>ImportSearchPolicy</tt> to determine if one
+ * import/export identifier and version is compatible with another.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public interface CompatibilityPolicy
+{
+    /**
+     * Compares two import/export identifiers.
+     * @param leftId the identifier to test for compatibility.
+     * @param leftVersion the version number to test for compatibility.
+     * @param rightId the identifier used as the compatibility base line.
+     * @param rightVersion the version used as the compatibility base line.
+     * @return <tt>0</tt> if the identifiers are equal, <tt>-1</tt> if the
+     *         left identifier is less then the right identifier, and <tt>1</tt>
+     *         if the left identifier is greater than the right identifier.
+     * @throws java.lang.IllegalArgumentException if the two identifiers
+     *         are not comparable, i.e., they refer to intrinsically different
+     *         entities.
+    **/
+    public int compare(
+        Object leftId, Object leftVersion,
+        Object rightId, Object rightVersion);
+
+    /**
+     * Returns whether the first import/export identifer is compatible
+     * with the second; this method should not throw any exceptions.
+     * @param leftId the identifier to test for compatibility.
+     * @param leftVersion the version number to test for compatibility.
+     * @param rightId the identifier used as the compatibility base line.
+     * @param rightVersion the version used as the compatibility base line.
+     * @return <tt>true</tt> if the left version number object is compatible
+     *         with the right version number object, otherwise <tt>false</tt>.
+    **/
+    public boolean isCompatible(
+        Object leftId, Object leftVersion,
+        Object rightId, Object rightVersion);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ExhaustiveSearchPolicy.java b/src/org/apache/osgi/moduleloader/search/ExhaustiveSearchPolicy.java
new file mode 100644
index 0000000..cc7cf4d
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ExhaustiveSearchPolicy.java
@@ -0,0 +1,162 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import java.net.URL;
+
+import org.apache.osgi.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a <tt>ModuleLoader</tt> search policy that
+ * exhaustively and linearly searches all modules when trying to load
+ * a particular class or resource. As a result of this algorithm, every class loader
+ * for every module is essentially identical, meaning that each will
+ * load a given class or resource from the same class loader. This search policy
+ * provides behavior similar to the standard <tt>CLASSPATH</tt> environment
+ * variable approach. The main difference is that modules can be added
+ * to the module manager at run time; thus, the class path is dynamically
+ * extended. This search policy is not fully dynamic, since it does not
+ * support the removal of modules at run time; if a module is removed from
+ * the module manager at run time, there is no attempt to clean up its
+ * loaded classes.
+ * </p>
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ModuleManager
+**/
+public class ExhaustiveSearchPolicy implements SearchPolicy
+{
+    private ModuleManager m_mgr = null;
+
+    /**
+     * This method is part of the <tt>SearchPolicy</tt> interface.
+     * This method is called by the <tt>ModuleManager</tt> once to
+     * give the search policy instance a reference to its associated
+     * module manager. This method should be implemented such that
+     * it cannot be called twice; calling this method a second time
+     * should produce an illegal state exception.
+     * @param mgr the module manager associated with this search policy.
+     * @throws java.lang.IllegalStateException if the method is called
+     *         more than once.
+    **/
+    public void setModuleManager(ModuleManager mgr)
+        throws IllegalStateException
+    {
+        if (m_mgr == null)
+        {
+            m_mgr = mgr;
+        }
+        else
+        {
+            throw new IllegalStateException("Module manager is already initialized");
+        }
+    }
+
+    public Object[] definePackage(Module module, String pkgName)
+    {
+        return null;
+    }
+
+    /**
+     * This method finds the specified class for the specified module. It
+     * finds the class by linearly asking each module in the module manager
+     * for the specific class. As soon as the class is found, it is returned.
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the class.
+     * @param name the name of the class being loaded.
+     * @return the class if found, <tt>null</tt> otherwise.
+    **/
+    public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+    {
+        // First, try to load from parent.
+        if (parent != null)
+        {
+            try
+            {
+                Class c = parent.loadClass(name);
+                if (c != null)
+                {
+                    return c;
+                }
+            }
+            catch (ClassNotFoundException ex)
+            {
+                // Ignore and search modules.
+            }
+        }
+
+        Module[] modules = m_mgr.getModules();
+        for (int i = 0; i < modules.length; i++)
+        {
+            try {
+                Class clazz = modules[i].getClassLoader().loadClassFromModule(name);
+                if (clazz != null)
+                {
+                    return clazz;
+                }
+            } catch (Throwable th) {
+            }
+        }
+
+        return null;
+    }
+
+    public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+    {
+        return null;
+    }
+
+    /**
+     * This method finds the specified resource for the specified module. It
+     * finds the resource by linearly asking each module in the module manager
+     * for specific resource. As soon as the resource is found, a <tt>URL</tt>
+     * to it is returned.
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the resource.
+     * @param name the name of the resource being loaded.
+     * @return a <tt>URL</tt> to the resource if found, <tt>null</tt> otherwise.
+    **/
+    public URL findResource(ClassLoader parent, Module module, String name)
+    {
+        // First, try to load from parent.
+        if (parent != null)
+        {
+            URL url = parent.getResource(name);
+            if (url != null)
+            {
+                return url;
+            }
+        }
+
+        Module[] modules = m_mgr.getModules();
+        for (int i = 0; i < modules.length; i++)
+        {
+            try {
+                URL url = modules[i].getClassLoader().getResourceFromModule(name);
+                if (url != null)
+                {
+                    return url;
+                }
+            } catch (Throwable th) {
+            }
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ImportSearchPolicy.java b/src/org/apache/osgi/moduleloader/search/ImportSearchPolicy.java
new file mode 100644
index 0000000..1f776f9
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ImportSearchPolicy.java
@@ -0,0 +1,1322 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import java.net.URL;
+import java.util.*;
+
+import org.apache.osgi.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a <tt>ModuleLoader</tt> search policy to support
+ * modules that import and export classes and resources from/to one another.
+ * Modules import from other modules by specifying a set of import identifiers
+ * and associated version numbers. Modules export their classes and
+ * resources by specifying a set of export identifiers and associated
+ * versions. Exports for a given module are also treated as imports for that module,
+ * meaning that it is possible for a module that exports classes to not use
+ * the classes it exports, but to actually use classes that are exported from
+ * another module. This search policy requires the following meta-data
+ * attributes be attached to each module:
+ * </p>
+ * <ul>
+ *   <li><tt>ImportSearchPolicy.EXPORTS_ATTR</tt> - the "<tt>exports</tt>"
+ *       meta-data attribute is used to declare the module's exports,
+ *   </li>
+ *   <li><tt>ImportSearchPolicy.IMPORTS_ATTR</tt> - the "<tt>imports</tt>"
+ *       meta-data attribute is used to declare the module's imports,
+ *   </li>
+ *   <li><tt>ImportSearchPolicy.PROPAGATES_ATTR</tt> - the "<tt>propagates</tt>"
+ *       meta-data attribute is used to declare which imports are exposed or
+ *       "propagated" to clients of the module's exports, and
+ *   </li>
+ *   <li><tt>ImportSearchPolicy.VALID_ATTR</tt> - the "<tt>valid</tt>"
+ *       meta-data attribute signifies the current <i>validation</i> status
+ *       of the module (this will be defined more fully below).
+ *   </li>
+ * </ul>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.EXPORTS_ATTR</tt> attribute is
+ * an array of <tt>Object</tt> arrays, i.e., <tt>Object[][]</tt>. Each element
+ * in the array signifies a particular export that is offered by this
+ * associated module. Each element is an array triple of
+ * <tt>Object</tt>, where the index into this triple is:
+ * </p>
+ * <ul>
+ *   <li><tt>ImportSearchPolicy.IDENTIFIER_IDX</tt> - the first element
+ *       is the export identifier object, used to identify the
+ *       export target. The export identifier does not have any special
+ *       meaning to the search policy and any value is allowed. A
+ *       typical identifier might be the package name of the exported classes,
+ *       such as <tt>javax.servlet</tt>.
+ *   </li>
+ *   <li><tt>ImportSearchPolicy.VERSION_IDX</tt> - the second element
+ *       is the export version number. The version number does not have
+ *       any special meaning to the search policy and any value is allowed.
+ *       A typical version number might be major, minor, and release number.
+ *   </li>
+ *   <li><tt>ImportSearchPolicy.RESOLVING_MODULE_IDX</tt> - the third element
+ *       is the resolving module for this export; since exports are treated like
+ *       imports, it is possible that the resolving module will not be the
+ *       exporting module itself. This value is filled in automatically by the
+ *       search policy and is initially <tt>null</tt>.
+ *   </li>
+ * </ul>
+ * </p>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.IMPORTS_ATTR</tt> attribute is
+ * essentially the same as the <tt>ImportSearchPolicy.EXPORTS_ATTR</tt> defined
+ * above; the only difference is that the array of versioned identifiers denote
+ * import targets rather than exports.
+ * </p>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.PROPAGATES_ATTR</tt> attribute is
+ * an array of <tt>Object</tt>s, i.e., <tt>Object[]</tt>. Each element in the
+ * array is an identifier of a propagated import target from the
+ * <tt>ImportSearchPolicy.IMPORTS_ATTR</tt> attribute. Only identifiers for
+ * import targets are candidates for inclusion and the version number is
+ * unnecessary since it is assumed from the corresponding import target.
+ * </p>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.VALID_ATTR</tt> attribute is a
+ * <tt>Boolean</tt>. The value is initially set to <tt>Boolean.FALSE</tt>
+ * and indicates that the module has not yet been validated. After the module
+ * is validated, the value is set to <tt>Boolean.TRUE</tt>. The search policy
+ * automatically adds this attribute to all modules and maintains its value.
+ * </p>
+ * <p>
+ * These meta-data attributes help the search policy enforce consistency
+ * using a process called <i>validation</i>; validation ensures that classes
+ * and resources are only loaded from a module whose imports are satisfied.
+ * Therefore, a <i>valid</i> module is a module whose imports are satisfied and
+ * an <i>invalid</i> module is a module whose imports are not yet satisfied.
+ * An invalid module may be invalid for two reasons:
+ * </p>
+ * <p>
+ * <ol>
+ *   <li>Its imports are not available or</li>
+ *   <li>It has not yet been validated.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * These two possibilities arise due to the fact that module validation
+ * is not performed until it is necessary (i.e., lazy evaluation). A module
+ * is automatically validated when an attempt is made to get classes or
+ * resources from it, although it is possible to manually validate a module.
+ * For a given module, called <tt>M</tt>, the validation process attempts to
+ * find an exporting module for every import target of <tt>M</tt>. If an
+ * exporter is not found for a specific import target, then the validation of
+ * module <tt>M</tt> fails. If an exporting module is found, then this module
+ * is also validated, if it is not already. As a result, the validation of
+ * module <tt>M</tt> depends on the validation of the transitive closure of
+ * all modules on which <tt>M</tt> depends. It is also possible for modules
+ * to exhibit dependency cycles; circular dependencies are allowed.
+ * Initially, a module's <tt>VALID_ATTR</tt> is set to <tt>Boolean.FALSE</tt>,
+ * but after the module is successfully validated, this attribute is set to
+ * <tt>Boolean.TRUE</tt>.
+ * </p>
+ * <p>
+ * Besides ensuring that every import target is resolved to an appropriate
+ * exporting module, the validation process also attempts to maintain
+ * consistency along "propagation" chains. Propagation occurs when a module
+ * imports classes that are also visible from its own exports; for example,
+ * an HTTP server implementation may import classes from <tt>javax.servlet</tt>
+ * and export classes that have methods that use the type <tt>javax.servlet.Servlet</tt>
+ * in their signatures. Monitoring these types of occurences is important
+ * to uncover import source and version conflicts when multiple sources or
+ * versions of an import target are available within one virtual machine. When
+ * a module <tt>M</tt> is validated, the propagation information of each
+ * module that resolves the imports of <tt>M</tt> is checked to ensure
+ * that they do not propagate conflicting sources of <tt>M</tt>'s
+ * imports; specifically, it is verified that all propagators of a
+ * particular import target have the same source module for that import
+ * target.
+ * </p>
+ * <p>
+ * To facilitate applicability in as many scenarios as possible, this search
+ * policy delegates some decisions via additional policy interfaces. The following
+ * two policy interfaces must be specified by the code that instantiates the
+ * <tt>ImportSearchPolicy</tt> object:
+ * </p>
+ * <ul>
+ *   <li><tt>CompatibilityPolicy</tt> - this policy is used to determine
+ *       whether import/export version numbers are compatible.
+ *   </li>
+ *   <li><tt>SelectionPolicy</tt> - this policy is used to resolve a specific
+ *       import target when multiple candidate exporting modules exist.
+ *   </li>
+ * </ul>
+ * <p>
+ * Once an instance is created with definitions of the above policy interfaces,
+ * this search policy will operate largely self-contained. There are a few utility
+ * methods for manually validating modules, adding validation listeners, and
+ * access meta-data attributes, but for the most part these are not necessary
+ * except for implementing more sophisticated infrastructure.
+ * </p>
+ * <p>
+ * The follow snippet of code illustrates a typical usage scenario for
+ * this search policy:
+ * </p>
+ * <pre>
+ *     ...
+ *     ImportSearchPolicy searchPolicy =
+ *         new ImportSearchPolicy(
+ *             new MyCompatibilityPolicy(), new MySelectionPolicy());
+ *     ModuleManager mgr = new ModuleManager(searchPolicy);
+ *     ...
+ *     Object[][] exports = new Object[][] {
+ *         { "org.apache.jasper", "2.1.0", null }
+ *     };
+ *     Object[][] imports = new Object[][] {
+ *         { "javax.servlet", "2.3.1", null }
+ *     };
+ *     Object[][] attributes = new Object[][] {
+ *         new Object[] { ImportSearchPolicy.EXPORTS_ATTR, exports },
+ *         new Object[] { ImportSearchPolicy.IMPORTS_ATTR, imports },
+ *         new Object[] { ImportSearchPolicy.PROPAGATES_ATTR, new Object[] { "javax.servlet" } }
+ *      };
+ *     ResourceSource[] resSources = new ResourceSource[] {
+ *         new JarResourceSource(file1)
+ *         new JarResourceSource(file2)
+ *     };
+ *     Module module = mgr.addModule(id, attributes, resSources, null);
+ *     ClassLoader loader = module.getClassLoader();
+ *     // Assuming that all imports are satisfied...
+ *     Class clazz = loader.loadClass("org.foo.MyClass");
+ *     ...
+ * </pre>
+ * <p>
+ * The above code snippet illustrates creating a module with one export and one
+ * import, where the import is also propagated via the module's export. The module
+ * has multiple resource sources, but no library sources.
+ * </p>
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ModuleManager
+**/
+public class ImportSearchPolicy implements SearchPolicy, ModuleListener
+{
+    /**
+     * This is the name of the "exports" meta-data attribute that
+     * should be attached to each module. The value of this attribute
+     * is of type <tt>Object[][]</tt> and is described in the overview
+     * documentation for this class.
+    **/
+    public static final String EXPORTS_ATTR = "exports";
+    /**
+     * This is the name of the "imports" meta-data attribute that
+     * should be attached to each module. The value of this attribute
+     * is of type <tt>Object[][]</tt> and is described in the overview
+     * documentation for this class.
+    **/
+    public static final String IMPORTS_ATTR = "imports";
+    /**
+     * This is the name of the "propagates" meta-data attribute that
+     * should be attached to each module. The value of this attribute
+     * is of type <tt>Object[]</tt> and is described in the overview
+     * documentation for this class.
+    **/
+    public static final String PROPAGATES_ATTR = "propagates";
+    /**
+     * This is the name of the "valid" meta-data attribute that is
+     * automatically attached to each module. The value of this attribute
+     * is of type <tt>Boolean</tt> and is described in the overview
+     * documentation for this class.
+    **/
+    public static final String VALID_ATTR = "valid";
+
+    /**
+     * This is the index used to retrieve the import or export identifier
+     * from a given element of the <tt>EXPORTS_ATTR</tt> or the <tt>IMPORTS_ATTR</tt>
+     * attribute.
+    **/
+    public static final int IDENTIFIER_IDX = 0;
+    /**
+     * This is the index used to retrieve the import or export version number
+     * from a given element of the <tt>EXPORTS_ATTR</tt> or the <tt>IMPORTS_ATTR</tt>
+     * attribute.
+    **/
+    public static final int VERSION_IDX = 1;
+    /**
+     * This is the index used to retrieve the resolving module for an import
+     * or export target from a given element of the <tt>EXPORTS_ATTR</tt> or
+     * the <tt>IMPORTS_ATTR</tt> attribute.
+    **/
+    public static final int RESOLVING_MODULE_IDX = 2;
+
+    private ModuleManager m_mgr = null;
+    private CompatibilityPolicy m_compatPolicy = null;
+    private SelectionPolicy m_selectPolicy = null;
+    private ValidationListener[] m_listeners = null;
+    private String[] m_searchAttrs = { IMPORTS_ATTR, EXPORTS_ATTR };
+    private static final ValidationListener[] m_noListeners = new ValidationListener[0];
+
+    /**
+     * Constructs an import search policy instance with the supplied
+     * compatibility and selection policies.
+     * @param compatPolicy the compatibility policy implementation to be used
+     *        by the search policy.
+     * @param selectPolicy the selection policy implementation to be used
+     *        by the search policy.
+    **/
+    public ImportSearchPolicy(
+        CompatibilityPolicy compatPolicy,
+        SelectionPolicy selectPolicy)
+    {
+        m_compatPolicy = compatPolicy;
+        m_selectPolicy = selectPolicy;
+        m_listeners = m_noListeners;
+    }
+
+    /**
+     * Returns the compatibility policy used by this import search policy instance.
+     * @return the compatibility policy of this import search policy instance.
+    **/
+    public CompatibilityPolicy getCompatibilityPolicy()
+    {
+        return m_compatPolicy;
+    }
+
+    /**
+     * Returns the selection policy used by this import search policy instance.
+     * @return the selection policy of this import search policy instance.
+    **/
+    public SelectionPolicy getSelectionPolicy()
+    {
+        return m_selectPolicy;
+    }
+
+    // JavaDoc comment copied from SearchPolicy.
+    public void setModuleManager(ModuleManager mgr)
+        throws IllegalStateException
+    {
+        if (m_mgr == null)
+        {
+            m_mgr = mgr;
+            m_mgr.addModuleListener(this);
+        }
+        else
+        {
+            throw new IllegalStateException("Module manager is already initialized");
+        }
+    }
+
+    public Object[] definePackage(Module module, String pkgName)
+    {
+        return null;
+    }
+
+    /**
+     * This method is part of the <tt>SearchPolicy</tt> interface; it
+     * should not be called directly. This method finds a class
+     * based on the import/export meta-data attached to the module.
+     * It first attempts to validate the target module, if it cannot
+     * be validated, then a <tt>ClassNotFoundException</tt> is thrown.
+     * Once the module is validated, the module's imports are searched
+     * for the target class, then the module's exports are searched.
+     * If the class is found in either place, then it is returned;
+     * otherwise, <tt>null</tt> is returned.
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the class.
+     * @param name the name of the class being loaded.
+     * @return the class if found, <tt>null</tt> otherwise.
+     * @throws java.lang.ClassNotFoundException if the target module
+     *         could not be validated.
+    **/
+    public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+        throws ClassNotFoundException
+    {
+        // First, try to validate the originating module.
+        try {
+            validate(module);
+        } catch (ValidationException ex) {
+            throw new ClassNotFoundException(name);
+        }
+
+        // Try to load from parent.
+        if (parent != null)
+        {
+            try
+            {
+                Class c = parent.loadClass(name);
+                if (c != null)
+                {
+                    return c;
+                }
+            }
+            catch (ClassNotFoundException ex)
+            {
+                // Ignore and search imports/exports.
+            }
+        }
+
+        // Get the package of the target class.
+        String pkgName = Util.getClassPackage(name);
+
+        // We delegate to the module's imports for finding the
+        // desired class first, then we delegate to the module's
+        // exports for finding the desired class. We do this because
+        // implicitly a module imports everything that it exports.
+        // To avoid code duplication, we use a simple array of
+        // attribute names to loop through both of the imports
+        // and exports meta-data searching for the desired class.
+        for (int attrIdx = 0; attrIdx < m_searchAttrs.length; attrIdx++)
+        {
+            Object[][] imports = getImportsOrExports(module, m_searchAttrs[attrIdx]);
+
+            // If the module doesn't import anything, then just
+            // return null.
+            if ((imports != null) && (imports.length > 0))
+            {
+                for (int i = 0; i < imports.length; i++)
+                {
+                    // Only check when the package of the class is
+                    // the same as the import package.
+                    if (imports[i][IDENTIFIER_IDX].equals(pkgName))
+                    {
+                        Module resolvingModule = (Module) imports[i][RESOLVING_MODULE_IDX];
+                        try {
+                            Class clazz =
+                                resolvingModule.getClassLoader().loadClassFromModule(name);
+                            if (clazz != null)
+                            {
+                                return clazz;
+                            }
+                        } catch (Throwable th) {
+                            // Not much we can do.
+                            System.err.println("ImportSearchPolicy: " + th.getMessage());
+                        }
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+    {
+        return null;
+    }
+
+    /**
+     * This method is part of the <tt>SearchPolicy</tt> interface; it
+     * should not be called directly. This method finds a resource
+     * based on the import/export meta-data attached to the module.
+     * It first attempts to validate the target module, if it cannot
+     * be validated, then it returns <tt>null</tt>. Once the module is
+     * validated, the module's imports are searched for the target
+     * resource, then the module's exports are searched. If the resource
+     * is found in either place, then a <tt>URL</tt> to is is returned;
+     * otherwise, <tt>null</tt> is returned.
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the resource.
+     * @param name the name of the resource being loaded.
+     * @return a <tt>URL</tt> to the resource if found, <tt>null</tt> otherwise.
+    **/
+    public URL findResource(ClassLoader parent, Module module, String name)
+    {
+        // First, try to validate the originating module.
+        try
+        {
+            validate(module);
+        }
+        catch (ValidationException ex)
+        {
+            return null;
+        }
+
+        // Try to load from parent.
+        if (parent != null)
+        {
+            URL url = parent.getResource(name);
+            if (url != null)
+            {
+                return url;
+            }
+        }
+
+        // Get the package of the target resource.
+        String pkgName = Util.getResourcePackage(name);
+
+        // We delegate to the module's imports for finding the
+        // desired class first, then we delegate to the module's
+        // exports for finding the desired class. We do this because
+        // implicitly a module imports everything that it exports.
+        // To avoid code duplication, we use a simple array of
+        // attribute names to loop through both of the imports
+        // and exports meta-data searching for the desired class.
+        for (int attrIdx = 0; attrIdx < m_searchAttrs.length; attrIdx++)
+        {
+            Object[][] imports = getImportsOrExports(module, m_searchAttrs[attrIdx]);
+
+            // If the module doesn't import or export anything,
+            // then just return null.
+            if ((imports != null) && (imports.length > 0))
+            {
+                for (int i = 0; i < imports.length; i++)
+                {
+                    // Only check when the package of the resource is
+                    // the same as the import package.
+                    if (imports[i][IDENTIFIER_IDX].equals(pkgName))
+                    {
+                        Module resolvingModule = (Module) imports[i][RESOLVING_MODULE_IDX];
+                        try {
+                            URL url =
+                                resolvingModule.getClassLoader().getResourceFromModule(name);
+                            if (url != null)
+                            {
+                                return url;
+                            }
+                        } catch (Throwable th) {
+                        }
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Map m_validateMap = new HashMap();
+    private Module m_rootModule = null;
+
+    /**
+     * This method validates the specified target module. If the module
+     * is already validated, then this method returns immediately. This
+     * method synchronizes on the associated module manager to ensure that
+     * modules are not added or removed while the validation is occuring.
+     * Each import and export for the target module are resolved by first
+     * using the compatibility policy to create a list of candidate export
+     * modules, then using the selection policy to choose among the
+     * candidates. Each selected candidate is also recursively validated;
+     * this process validates a transitive closure of all dependent modules.
+     * After the selected candidate is validated, its propagated imports
+     * are checked to make sure that they do not conflict with any existing
+     * propagated imports. If no validation errors occur, then all dependent
+     * modules are marked as validated, if they are not already validated.
+     * If an error occurs, the valid state of all modules remains unchanged.
+     * @param module the module to validate.
+     * @throws org.apache.osgi.moduleloader.search.ValidationException if
+     *         the module or any dependent modules could not be validated.
+    **/
+    public void validate(Module module)
+        throws ValidationException
+    {
+        if (getValidAttribute(module).booleanValue())
+        {
+            return;
+        }
+
+        // Flag to indicate whether the bundle is valid or not.
+        boolean isValid = true;
+
+        // This list will be used to remember which bundles
+        // were validated so that the validation events can
+        // be fired outside of the synchronized block.
+        List fireValidatedList = null;
+
+        // Will hold the exception to be thrown or rethrown.
+        ValidationException invalidException = null;
+
+        // Synchronize on the module manager, because we don't want
+        // anything to change while we are in the middle of this
+        // operation.
+        synchronized (m_mgr)
+        {
+            // If we are already validating this module, then
+            // just return; this is necessary for cycles.
+            if (m_validateMap.get(module) != null)
+            {
+                return;
+            }
+
+            // Add the module to the validation map; this
+            // is necessary for cycles.
+            m_validateMap.put(module, module);
+
+            // Keep track of the root module that started
+            // the validation request; this is necessary
+            // for cycles.
+            if (m_rootModule == null)
+            {
+                m_rootModule = module;
+            }
+
+            // Now perform the validation algorithm.
+            Map propagateMap = new HashMap();
+  
+            // Validation binds the module's imports to a specific exporting
+            // module. A module also implicitly imports whatever it exports,
+            // so exports are validated in the same fashion as imports. It
+            // is possible, given the selection policy that a given export
+            // may actually be satisfied by a different module (i.e., a
+            // module is not guaranteed to be bound to what it exports). Since
+            // the imports and exports meta-data are validated in the same
+            // fashion, we will use the follow attribute array to loop and
+            // validate both imports and exports using the same code.
+            for (int attrIdx = 0; (isValid) && (attrIdx < m_searchAttrs.length); attrIdx++)
+            {
+                // Get the imports (exports are treated as imports to)
+                // for the current module.
+                Object[][] imports = getImportsOrExports(module, m_searchAttrs[attrIdx]);
+                // See if each import has available exporters.
+                for (int impIdx = 0; impIdx < imports.length; impIdx++)
+                {
+                    // Get all exporter candidates.
+                    Module[] candidates =
+                        getCompatibleModules(
+                            imports[impIdx][IDENTIFIER_IDX], imports[impIdx][VERSION_IDX]);
+                    // If there are no candidates, then prepare a
+                    // validation exception.
+                    if (candidates == null)
+                    {
+                        isValid = false;
+                        invalidException =
+                            new ValidationException(
+                                "Unable to validate module",
+                                module,
+                                imports[impIdx][IDENTIFIER_IDX],
+                                imports[impIdx][VERSION_IDX],
+                                false);
+                        break;
+                    }
+
+                    // Use selection policy to choose a single export candidate.
+                    Module exportingModule = m_selectPolicy.select(
+                        module, imports[impIdx][IDENTIFIER_IDX],
+                        imports[impIdx][VERSION_IDX], candidates, m_compatPolicy);
+                    // If there is no export module chosen, then prepare
+                    // a validation exception.
+                    if (exportingModule == null)
+                    {
+                        isValid = false;
+                        invalidException =
+                            new ValidationException(
+                                "Unable to validate module",
+                                module,
+                                imports[impIdx][IDENTIFIER_IDX],
+                                imports[impIdx][VERSION_IDX],
+                                false);
+                        break;
+                    }
+
+                    // Make sure that the export module is
+                    // also validated.
+                    try
+                    {
+                        validate(exportingModule);
+                    }
+                    catch (ValidationException ex)
+                    {
+                        // Prepare to rethrow the exception if
+                        // the exporter could not be validated.
+                        isValid = false;
+                        invalidException = ex;
+                        break;
+                    }
+
+                    // Keep track of all propagations from each module that this
+                    // module imports from. Verify that any given import always
+                    // comes form the same export module, otherwise there will be
+                    // class cast exceptions.
+                    Object[] propagates = getPropagatesAttribute(exportingModule);
+                    for (int propIdx = 0; propIdx < propagates.length; propIdx++)
+                    {
+                        // If the module does not import the propagated target,
+                        // then it can be safely ignored.
+                        if (doesImport(module, propagates[propIdx]))
+                        {
+                            Module sourceModule =
+                                (Module) propagateMap.get(propagates[propIdx]);
+
+                            // If the propagation source module has not already been
+                            // found, then remember the resolving module of the
+                            // exporting module as the source of the propagated
+                            // target.
+                            if (sourceModule == null)
+                            {
+                                propagateMap.put(
+                                    propagates[propIdx],
+                                    getImportResolvingModule(
+                                        exportingModule, propagates[propIdx]));
+                            }
+                            // If the propagation source module is found, then check to
+                            // see if it is propagating the import target from the same
+                            // module as previously determined for this module. If not,
+                            // then this is a propagation conflict.
+                            else if (sourceModule !=
+                                getImportResolvingModule(
+                                    exportingModule, propagates[propIdx]))
+                            {
+                                isValid = false;
+                                invalidException =
+                                    new ValidationException(
+                                        "Unable to validate module",
+                                        exportingModule,
+                                        propagates[propIdx],
+                                        null,
+                                        true);
+                                break;
+                            }
+                        }
+                    }
+
+                    // Set the chosen exporting module for the module
+                    // being validated.
+                    imports[impIdx][RESOLVING_MODULE_IDX] = exportingModule;
+                }
+            }
+
+            // Since this method is recursive, check to see it we are
+            // back at the root module that started the request, which
+            // would indicate that the request is finished.
+            if (m_rootModule == module)
+            {
+                // If the result is valid, then we have validated successfully.
+                if (isValid)
+                {
+                    // Loop through all modules in validate map
+                    // and mark them as valid.
+                    Iterator iter = m_validateMap.keySet().iterator();
+                    while (iter.hasNext())
+                    {
+                        Module m = (Module) iter.next();
+                        if (!getValidAttribute(m).booleanValue())
+                        {
+                            m.setAttribute(VALID_ATTR, Boolean.TRUE);
+                            if (fireValidatedList == null)
+                            {
+                                fireValidatedList = new ArrayList();
+                            }
+                            fireValidatedList.add(m);
+                        }
+                    }
+                }
+                // If we are here, then the validate failed, so we
+                // need to reset any partially validated modules.
+                else
+                {
+                    Iterator iter = m_validateMap.keySet().iterator();
+                    while (iter.hasNext())
+                    {
+                        Module m = (Module) iter.next();
+                        invalidate(
+                            m,
+                            m.getAttributes(),
+                            m.getResourceSources(),
+                            m.getLibrarySources());
+                    }
+                }
+
+                // Clear the root module and validation map
+                // before leaving the synchronized block.
+                m_rootModule = null;
+                m_validateMap.clear();
+            }
+        }
+
+        // (Re)throw the exception if invalid, otherwise
+        // fire validation events if the validated event
+        // list is not null.
+        if (!isValid)
+        {
+            throw invalidException;
+        }
+        else if (fireValidatedList != null)
+        {
+            for (int i = 0; i < fireValidatedList.size(); i++)
+            {
+                fireModuleValidated((Module) fireValidatedList.get(i));
+            }
+        }
+    }
+
+    /**
+     * This method returns a list of modules that have an export
+     * that is compatible with the given import identifier and version.
+     * @param identifier the import identifier.
+     * @param version the version of the import identifier.
+     * @return an array of modules that have compatible exports or <tt>null</tt>
+     *         if none are found.
+    **/
+    protected Module[] getCompatibleModules(Object identifier, Object version)
+    {
+        List list = null;
+        Module[] modules = m_mgr.getModules();
+        for (int modIdx = 0; modIdx < modules.length; modIdx++)
+        {
+            Object[][] exports = getExportsAttribute(modules[modIdx]);
+            for (int expIdx = 0; expIdx < exports.length; expIdx++)
+            {
+                // If the identifiers are comparable and compatible,
+                // then add the export identifier to the list.
+                if (m_compatPolicy.isCompatible(
+                        exports[expIdx][IDENTIFIER_IDX], exports[expIdx][VERSION_IDX],
+                        identifier, version))
+                {
+                    if (list == null)
+                    {
+                        list = new ArrayList();
+                    }
+                    list.add(modules[modIdx]);
+                }
+            }
+        }
+
+        if (list == null)
+        {
+            return null;
+        }
+
+        Module[] result = new Module[list.size()];
+        return (Module[]) list.toArray(result);
+    }
+
+    /**
+     * Invalidates a module by flushing its class loader and
+     * re-initializing its meta-data values.
+     * @param module the module to be invalidated.
+     * @param attributes the attributes associated with the module, since they
+     *        might have changed.
+     * @param resSources the resource sources associated wih the module, since they
+     *        might have changed.
+     * @param libSources the library sources associated wih the module, since they
+     *        might have changed.
+    **/
+    public void invalidate(
+        Module module, Object[][] attributes,
+        ResourceSource[] resSources, LibrarySource[] libSources)
+    {
+        // Synchronize on the module manager, because we don't want
+        // anything to change while we are in the middle of this
+        // operation.
+        synchronized (m_mgr)
+        {
+            m_mgr.resetModule(module, attributes, resSources, libSources);
+        }
+
+        // Fire invalidation event if necessary.
+        fireModuleInvalidated(m_mgr.getModule(module.getId()));
+    }
+
+    //
+    // Event handling methods for validation events.
+    //
+
+    /**
+     * Adds a validation listener to this import search policy. Validation
+     * listeners are notified when a module is validated and/or invalidated
+     * by the search policy.
+     * @param l the validation listener to add.
+    **/
+    public void addValidationListener(ValidationListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        // Use the m_noListeners object as a lock.
+        synchronized (m_noListeners)
+        {
+            // If we have no listeners, then just add the new listener.
+            if (m_listeners == m_noListeners)
+            {
+                m_listeners = new ValidationListener[] { l };
+            }
+            // Otherwise, we need to do some array copying.
+            // Notice, the old array is always valid, so if
+            // the dispatch thread is in the middle of a dispatch,
+            // then it has a reference to the old listener array
+            // and is not affected by the new value.
+            else
+            {
+                ValidationListener[] newList = new ValidationListener[m_listeners.length + 1];
+                System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+                newList[m_listeners.length] = l;
+                m_listeners = newList;
+            }
+        }
+    }
+
+    /**
+     * Removes a validation listener to this import search policy.
+     * @param l the validation listener to remove.
+    **/
+    public void removeValidationListener(ValidationListener l)
+    {
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        // Use the m_noListeners object as a lock.
+        synchronized (m_noListeners)
+        {
+            // Try to find the instance in our list.
+            int idx = -1;
+            for (int i = 0; i < m_listeners.length; i++)
+            {
+                if (m_listeners[i].equals(l))
+                {
+                    idx = i;
+                    break;
+                }
+            }
+
+            // If we have the instance, then remove it.
+            if (idx >= 0)
+            {
+                // If this is the last listener, then point to empty list.
+                if (m_listeners.length == 1)
+                {
+                    m_listeners = m_noListeners;
+                }
+                // Otherwise, we need to do some array copying.
+                // Notice, the old array is always valid, so if
+                // the dispatch thread is in the middle of a dispatch,
+                // then it has a reference to the old listener array
+                // and is not affected by the new value.
+                else
+                {
+                    ValidationListener[] newList = new ValidationListener[m_listeners.length - 1];
+                    System.arraycopy(m_listeners, 0, newList, 0, idx);
+                    if (idx < newList.length)
+                    {
+                        System.arraycopy(m_listeners, idx + 1, newList, idx,
+                            newList.length - idx);
+                    }
+                    m_listeners = newList;
+                }
+            }
+        }
+    }
+
+    /**
+     * Fires a validation event for the specified module.
+     * @param module the module that was validated.
+    **/
+    protected void fireModuleValidated(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ValidationListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(m_mgr, module);
+            }
+            listeners[i].moduleValidated(event);
+        }
+    }
+
+    /**
+     * Fires an invalidation event for the specified module.
+     * @param module the module that was invalidated.
+    **/
+    protected void fireModuleInvalidated(Module module)
+    {
+        // Event holder.
+        ModuleEvent event = null;
+
+        // Get a copy of the listener array, which is guaranteed
+        // to not be null.
+        ValidationListener[] listeners = m_listeners;
+
+        // Loop through listeners and fire events.
+        for (int i = 0; i < listeners.length; i++)
+        {
+            // Lazily create event.
+            if (event == null)
+            {
+                event = new ModuleEvent(m_mgr, module);
+            }
+            listeners[i].moduleInvalidated(event);
+        }
+    }
+
+    //
+    // ModuleListener methods.
+    //
+
+    /**
+     * Callback method for <tt>ModuleListener</tt>; this should not
+     * be called directly. This callback is used to initialize module
+     * meta-data attributes; it adds the <tt>VALID_ATTR</tt> attribute
+     * and initializes the resolving module entries in <tt>EXPORTS_ATTR</tt>
+     * and <tt>IMPORTS_ATTR</tt> to <tt>null</tt>.
+    **/
+    public void moduleAdded(ModuleEvent event)
+    {
+        synchronized (event.getModule())
+        {
+            // Add valid attribute to all modules.
+            event.getModule().setAttribute(VALID_ATTR, Boolean.FALSE);
+
+            for (int attrIdx = 0; attrIdx < m_searchAttrs.length; attrIdx++)
+            {
+                Object[][] imports =
+                    getImportsOrExports(event.getModule(), m_searchAttrs[attrIdx]);
+                for (int i = 0; i < imports.length; i++)
+                {
+                    imports[i][RESOLVING_MODULE_IDX] = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback method for <tt>ModuleListener</tt>; this should not
+     * be called directly. This callback is used to re-initialize module
+     * meta-data attributes; it adds the <tt>VALID_ATTR</tt> attribute
+     * and initializes the resolving module entries in <tt>EXPORTS_ATTR</tt>
+     * and <tt>IMPORTS_ATTR</tt> to <tt>null</tt>. It then invalidates
+     * all modules that import from the reset module.
+    **/
+    public void moduleReset(ModuleEvent event)
+    {
+        // This will reset module meta-data.
+        moduleAdded(event);
+
+// TODO: Synchronization?
+        ModuleManager m_mgr = (ModuleManager) event.getSource();
+        List list = createImporterList(m_mgr, event.getModule());
+        for (int i = 0; (list != null) && (i < list.size()); i++)
+        {
+            Module module = (Module) list.get(i);
+            invalidate(
+                module, module.getAttributes(),
+                module.getResourceSources(), module.getLibrarySources());
+        }
+    }
+
+    /**
+     * Callback method for <tt>ModuleListener</tt>; this should not
+     * be called directly. Used to listen for module removal events
+     * in order to invalidate all the modules that import form the
+     * removed moduled.
+    **/
+    public void moduleRemoved(ModuleEvent event)
+    {
+// TODO: Synchronization?
+        ModuleManager m_mgr = (ModuleManager) event.getSource();
+        List list = createImporterList(m_mgr, event.getModule());
+        for (int i = 0; (list != null) && (i < list.size()); i++)
+        {
+            Module module = (Module) list.get(i);
+            invalidate(
+                module, module.getAttributes(),
+                module.getResourceSources(), module.getLibrarySources());
+        }
+    }
+
+    //
+    // Instance utility methods.
+    //
+
+    /**
+     * This utility method returns the module that exports the
+     * specified import identifier and version. This method uses the
+     * <tt>validate()</tt> method to find the exporting module and,
+     * as a result, relies on the compatibility and selection
+     * policies associated with this <tt>ImportSearchPolicy</tt>
+     * instance. If successful, the returned module is guaranteed
+     * to be validated. This method only needs to be used for more
+     * advanced purposes (i.e., check import availability dynamically,
+     * etc.) and need not be used under normal circumstances.
+     * @param identifier the identifier of the import to resolve.
+     * @param version the version of the import to resolve.
+     * @return the exporting module selected to resolve the specified
+     *         import target.
+    **/
+    public Module resolveImportTarget(Object identifier, Object version)
+    {
+        // Create a fake module that imports the specified target
+        // and then try to validate it so we can get the exporting
+        // module that is used to satisfy the import.
+        Object[] targetImport = { identifier, version, null };
+        Object[][] attrs = new Object[][] {
+            new Object[] { EXPORTS_ATTR, new Object[0][0] },
+            new Object[] { IMPORTS_ATTR, new Object[][] { targetImport } },
+            new Object[] { PROPAGATES_ATTR, new Object[0] },
+            new Object[] { VALID_ATTR, Boolean.FALSE}
+        };
+        Module fake = new Module(m_mgr, "resolve import", attrs, null, null, false);
+        try {
+            validate(fake);
+        } catch (ValidationException ex) {
+            // Ignore this.
+        }
+        return (Module) targetImport[RESOLVING_MODULE_IDX];
+    }
+
+    //
+    // Static utility methods.
+    //
+
+    private static final Object[][] m_emptyImports = new Object[0][0];
+    private static final Object[] m_emptyProp = new Object[0];
+
+    /**
+     * Utility method that returns the <tt>VALID_ATTR</tt> attribute for
+     * the specified module.
+     * @param module the module whose <tt>VALID_ATTR</tt> attribute is to
+     *        be retrieved.
+     * @return an instance of <tt>Boolean</tt>.
+    **/
+    public static Boolean getValidAttribute(Module module)
+    {
+        Object value = module.getAttribute(VALID_ATTR);
+        if (value != null)
+        {
+            return (Boolean) value;
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * Utility method that returns the <tt>IMPORTS_ATTR</tt> attribute for
+     * the specified module.
+     * @param module the module whose <tt>IMPORTS_ATTR</tt> attribute is to
+     *        be retrieved.
+     * @return an <tt>Object[][]</tt> value or <tt>null</tt>.
+    **/
+    public static Object[][] getImportsAttribute(Module module)
+    {
+        Object value = module.getAttribute(IMPORTS_ATTR);
+        if (value != null)
+        {
+            return (Object[][]) value;
+        }
+        return m_emptyImports;
+    }
+
+    /**
+     * Utility method that returns the <tt>EXPORTS_ATTR</tt> attribute for
+     * the specified module.
+     * @param module the module whose <tt>EXPORTS_ATTR</tt> attribute is to
+     *        be retrieved.
+     * @return an <tt>Object[][]</tt> value or <tt>null</tt>.
+    **/
+    public static Object[][] getExportsAttribute(Module module)
+    {
+        Object value = module.getAttribute(EXPORTS_ATTR);
+        if (value != null)
+        {
+            return (Object[][]) value;
+        }
+        return m_emptyImports;
+    }
+
+    /**
+     * Utility method that returns the <tt>IMPORTS_ATTR</tt> or the
+     * <tt>EXPORTS_ATTR</tt> attribute for the specified module.
+     * @param module the module whose <tt>IMPORTS_ATTR</tt> or
+     *        <tt>EXPORTS_ATTR</tt> attribute is to be retrieved.
+     * @param name either <tt>IMPORTS_ATTR</tt> or <tt>EXPORTS_ATTR</tt>
+     *        depending on which attribute should be retrieved.
+     * @return an <tt>Object[][]</tt> value or <tt>null</tt>.
+    **/
+    public static Object[][] getImportsOrExports(Module module, String name)
+    {
+        Object value = module.getAttribute(name);
+        if (value != null)
+        {
+            return (Object[][]) value;
+        }
+        return m_emptyImports;
+    }
+
+    /**
+     * Utility method that returns the <tt>PROPAGATES_ATTR</tt> attribute for
+     * the specified module.
+     * @param module the module whose <tt>PROPAGATES_ATTR</tt> attribute is to
+     *        be retrieved.
+     * @return an <tt>Object[]</tt> value or <tt>null</tt>.
+    **/
+    public static Object[] getPropagatesAttribute(Module module)
+    {
+        Object value = module.getAttribute(PROPAGATES_ATTR);
+        if (value != null)
+        {
+            return (Object[]) value;
+        }
+        return m_emptyProp;
+    }
+
+    /**
+     * Utility method to determine if the specified module imports a given
+     * import identifier, regardless of version. This method checks both
+     * imports and exports, since a module is assumed to import what it exports.
+     * @param module the module to check.
+     * @param identifier the import identifier to check.
+     * @return <tt>true</tt> if the module imports the specified
+     *         import identifier or <tt>false</tt> if it does not.
+    **/
+    public static boolean doesImport(Module module, Object identifier)
+    {
+        Object[][] imports = getImportsAttribute(module);
+        for (int i = 0; i < imports.length; i++)
+        {
+            if (imports[i][IDENTIFIER_IDX].equals(identifier))
+            {
+                return true;
+            }
+        }
+        imports = getExportsAttribute(module);
+        for (int i = 0; i < imports.length; i++)
+        {
+            if (imports[i][IDENTIFIER_IDX].equals(identifier))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Utility method to create a list of modules that import from
+     * the specified module.
+     * @param mgr the module manager that contains the module.
+     * @param module the module for which to create an importer list.
+     * @return a list of modules that import from the specified module
+     *         or <tt>null</tt>.
+    **/
+    public static List createImporterList(ModuleManager mgr, Module module)
+    {
+        List list = null;
+        Module[] modules = mgr.getModules();
+        for (int modIdx = 0; modIdx < modules.length; modIdx++)
+        {
+            Object[][] imports = getImportsAttribute(modules[modIdx]);
+            for (int impIdx = 0; impIdx < imports.length; impIdx++)
+            {
+                if (imports[impIdx][RESOLVING_MODULE_IDX] == module)
+                {
+                    if (list == null)
+                    {
+                        list = new ArrayList();
+                    }
+                    list.add(modules[modIdx]);
+                    break;
+                }
+            }
+        }
+
+        return list;
+    }
+
+    /**
+     * Utility method to get the import version number associated with a specific
+     * import identifier of the specified module.
+     * @param module the module to investigate.
+     * @param identifier the import identifier for which the version should
+     *        be retrieved.
+     * @return the version number object or <tt>null</tt>.
+    **/
+    public static Object getImportVersion(Module module, Object identifier)
+    {
+        Object[][] imports = getImportsAttribute(module);
+        for (int i = 0; i < imports.length; i++)
+        {
+            if (imports[i][IDENTIFIER_IDX].equals(identifier))
+            {
+                return imports[i][VERSION_IDX];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Utility method to get the export version number associated with a specific
+     * export identifier of the specified module.
+     * @param module the module to investigate.
+     * @param identifier the export identifier for which the version should
+     *        be retrieved.
+     * @return the version number object or <tt>null</tt>.
+    **/
+    public static Object getExportVersion(Module module, Object identifier)
+    {
+        Object[][] exports = getExportsAttribute(module);
+        for (int i = 0; i < exports.length; i++)
+        {
+            if (exports[i][IDENTIFIER_IDX].equals(identifier))
+            {
+                return exports[i][VERSION_IDX];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Utility method to get the resolving module of a specific import
+     * identifier for the specified module.
+     * @param module the module to investigate.
+     * @param identifier the import identifier for which the resolving
+     *        module should be retrieved.
+     * @return the resolving module or <tt>null</tt>.
+    **/
+    public static Module getImportResolvingModule(Module module, Object identifier)
+    {
+        Object[][] imports = getImportsAttribute(module);
+
+        for (int i = 0; i < imports.length; i++)
+        {
+            if (imports[i][IDENTIFIER_IDX].equals(identifier))
+            {
+                return (Module) imports[i][RESOLVING_MODULE_IDX];
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Utility method to get the resolving module of a specific export
+     * identifier for the specified module.
+     * @param module the module to investigate.
+     * @param identifier the export identifier for which the resolving
+     *        module should be retrieved.
+     * @return the resolving module or <tt>null</tt>.
+    **/
+    public static Module getExportResolvingModule(Module module, Object identifier)
+    {
+        Object[][] exports = getExportsAttribute(module);
+
+        for (int i = 0; i < exports.length; i++)
+        {
+            if (exports[i][IDENTIFIER_IDX].equals(identifier))
+            {
+                return (Module) exports[i][RESOLVING_MODULE_IDX];
+            }
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ResolveException.java b/src/org/apache/osgi/moduleloader/search/ResolveException.java
new file mode 100755
index 0000000..efe6949
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ResolveException.java
@@ -0,0 +1,60 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import org.apache.osgi.framework.searchpolicy.R4Package;
+import org.apache.osgi.moduleloader.Module;
+
+/**
+ * <p>
+ * This exception is thrown if a module cannot be resolved. The module
+ * that failed to be resolved is recorded, along with the failed import target
+ * identifier and version number. If the error was a result of a propagation
+ * conflict, then the propagation error flag is set.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy#validate(org.apache.osgi.moduleloader.Module)
+**/
+public class ResolveException extends Exception
+{
+    private Module m_module = null;
+    private R4Package m_pkg = null;
+
+    /**
+     * Constructs an exception with the specified message, module,
+     * import identifier, import version number, and propagation flag.
+    **/
+    public ResolveException(String msg, Module module, R4Package pkg)
+    {
+        super(msg);
+        m_module = module;
+        m_pkg = pkg;
+    }
+
+    /**
+     * Returns the module that was being resolved.
+     * @return the module that was being resolved.
+    **/
+    public Module getModule()
+    {
+        return m_module;
+    }
+
+    public R4Package getPackage()
+    {
+        return m_pkg;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ResolveListener.java b/src/org/apache/osgi/moduleloader/search/ResolveListener.java
new file mode 100755
index 0000000..ebd8acd
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ResolveListener.java
@@ -0,0 +1,46 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import java.util.EventListener;
+
+import org.apache.osgi.moduleloader.ModuleEvent;
+
+/**
+ * <p>
+ * This is an event listener interface for listening to resolution
+ * events that are generated by the <tt>R4SearchPolicy</tt>. Events
+ * are fired when a module is resolved and when it is unresolved.
+ * </p>
+ * @see org.apache.osgi.framework.searchpolicy.R4SearchPolicy
+**/
+public interface ResolveListener extends EventListener
+{
+    /**
+     * This is an event callback method that indicates that
+     * a module was resolved.
+     * @param event the module event containing the event data.
+    **/
+    public void moduleResolved(ModuleEvent event);
+
+    /**
+     * This is an event callback method that indicates that
+     * a module was unresolved.
+     * @param event the module event containing the event data.
+    **/
+    public void moduleUnresolved(ModuleEvent event);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/SelectionPolicy.java b/src/org/apache/osgi/moduleloader/search/SelectionPolicy.java
new file mode 100644
index 0000000..6ec8c44
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/SelectionPolicy.java
@@ -0,0 +1,47 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import org.apache.osgi.moduleloader.Module;
+
+/**
+ * <p>
+ * This interface represents the policy for selecting a specific export
+ * target from multiple <i>compatible</i> candidate export targets when
+ * the <tt>ImportSearchPolicy</tt> is trying to resolve an import target
+ * for a given module. A concrete implementation of this interface is
+ * required to create an instance of <tt>ImportSearchPolicy</tt>.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public interface SelectionPolicy
+{
+    /**
+     * Selects a single module to resolve the specified import
+     * from the array of compatible candidate modules.
+     * @param module the module that is importing the target.
+     * @param identifier the identifier of the import target.
+     * @param version the version number of the import target.
+     * @param candidates array of compatible candidate modules from which to choose.
+     * @param compatPolicy the compatibility policy that is being used.
+     * @return the selected module or <tt>null</tt> if no module
+     *         can be selected.
+    **/
+    public Module select(
+        Module module, Object identifier, Object version, Module[] candidates,
+        CompatibilityPolicy compatPolicy);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/SelfContainedSearchPolicy.java b/src/org/apache/osgi/moduleloader/search/SelfContainedSearchPolicy.java
new file mode 100644
index 0000000..e03b061
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/SelfContainedSearchPolicy.java
@@ -0,0 +1,122 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import java.net.URL;
+
+import org.apache.osgi.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a <tt>ModuleLoader</tt> search policy that
+ * assumes that all modules are self-contained. In other words, when
+ * loading a class or resource for a particular module, only that
+ * particular module's resource sources are search. No classes or
+ * resources are shared among modules.
+ * </p>
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ModuleManager
+**/
+public class SelfContainedSearchPolicy implements SearchPolicy
+{
+    private ModuleManager m_mgr = null;
+
+    /**
+     * This method is part of the <tt>SearchPolicy</tt> interface.
+     * This method is called by the <tt>ModuleManager</tt> once to
+     * give the search policy instance a reference to its associated
+     * module manager. This method should be implemented such that
+     * it cannot be called twice; calling this method a second time
+     * should produce an illegal state exception.
+     * @param mgr the module manager associated with this search policy.
+     * @throws java.lang.IllegalStateException if the method is called
+     *         more than once.
+    **/
+    public void setModuleManager(ModuleManager mgr)
+        throws IllegalStateException
+    {
+        if (m_mgr == null)
+        {
+            m_mgr = mgr;
+        }
+        else
+        {
+            throw new IllegalStateException("Module manager is already initialized");
+        }
+    }
+
+    public Object[] definePackage(Module module, String pkgName)
+    {
+        return null;
+    }
+
+    /**
+     * Simply returns <tt>null</tt> which forces the module class
+     * loader to only search the target module's resource sources
+     * for the specified class.
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the class.
+     * @param name the name of the class being loaded.
+     * @return <tt>null</tt>.
+    **/
+    public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+    {
+        // First, try to load from parent.
+        if (parent != null)
+        {
+            try
+            {
+                Class c = parent.loadClass(name);
+                if (c != null)
+                {
+                    return c;
+                }
+            }
+            catch (ClassNotFoundException ex)
+            {
+                // Ignore.
+            }
+        }
+
+        return null;
+    }
+
+    public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+    {
+        return null;
+    }
+
+    /**
+     * Simply returns <tt>null</tt> which forces the module class
+     * loader to only search the target module's resource sources
+     * for the specified resource.
+     * @param parent the parent class loader of the delegating class loader.
+     * @param module the target module that is loading the class.
+     * @param name the name of the resource being loaded.
+     * @return <tt>null</tt>.
+    **/
+    public URL findResource(ClassLoader parent, Module module, String name)
+    {
+        if (parent != null)
+        {
+            return parent.getResource(name);
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ValidationException.java b/src/org/apache/osgi/moduleloader/search/ValidationException.java
new file mode 100644
index 0000000..15a8c5f
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ValidationException.java
@@ -0,0 +1,88 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import org.apache.osgi.moduleloader.Module;
+
+/**
+ * <p>
+ * This exception is thrown if a module cannot be validated. The module
+ * that failed to be validated is recorded, along with the failed import target
+ * identifier and version number. If the error was a result of a propagation
+ * conflict, then the propagation error flag is set.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy#validate(org.apache.osgi.moduleloader.Module)
+**/
+public class ValidationException extends Exception
+{
+    private Module m_module = null;
+    private Object m_identifier = null;
+    private Object m_version = null;
+    private boolean m_isPropagation = false;
+
+    /**
+     * Constructs an exception with the specified message, module,
+     * import identifier, import version number, and propagation flag.
+    **/
+    public ValidationException(String msg, Module module,
+        Object identifier, Object version, boolean isPropagation)
+    {
+        super(msg);
+        m_module = module;
+        m_identifier = identifier;
+        m_version = version;
+        m_isPropagation = isPropagation;
+    }
+
+    /**
+     * Returns the module that was being validated.
+     * @return the module that was being validated.
+    **/
+    public Module getModule()
+    {
+        return m_module;
+    }
+
+    /**
+     * Returns the identifier of the import target that could not be resolved.
+     * @return the identifier of the import target that could not be resolved.
+    **/
+    public Object getIdentifier()
+    {
+        return m_identifier;
+    }
+
+    /**
+     * Returns the version number of the import target that could not be resolved.
+     * @return the version number of the import target that could not be resolved.
+    **/
+    public Object getVersion()
+    {
+        return m_version;
+    }
+
+    /**
+     * Returns a flag indicating whether the exception was caused by a
+     * a propagation conflict.
+     * @return <tt>true</tt> if the exception was thrown due to a propagation
+     *         conflict, <tt>false</tt> otherwise.
+    **/
+    public boolean isPropagationError()
+    {
+        return m_isPropagation;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ValidationListener.java b/src/org/apache/osgi/moduleloader/search/ValidationListener.java
new file mode 100644
index 0000000..656b46e
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ValidationListener.java
@@ -0,0 +1,46 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search;
+
+import java.util.EventListener;
+
+import org.apache.osgi.moduleloader.ModuleEvent;
+
+/**
+ * <p>
+ * This is an event listener interface for listening to validation
+ * events that are generated by the <tt>ImportSearchPolicy</tt>. Events
+ * are fired when a module is validated and when it is invalidated.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public interface ValidationListener extends EventListener
+{
+    /**
+     * This is an event callback method that indicates that
+     * a module was validated.
+     * @param event the module event containing the event data.
+    **/
+    public void moduleValidated(ModuleEvent event);
+
+    /**
+     * This is an event callback method that indicates that
+     * a module was invalidated.
+     * @param event the module event containing the event data.
+    **/
+    public void moduleInvalidated(ModuleEvent event);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/compatibility/ExactCompatibilityPolicy.java b/src/org/apache/osgi/moduleloader/search/compatibility/ExactCompatibilityPolicy.java
new file mode 100644
index 0000000..61fca1c
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/compatibility/ExactCompatibilityPolicy.java
@@ -0,0 +1,72 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search.compatibility;
+
+import org.apache.osgi.moduleloader.search.CompatibilityPolicy;
+
+/**
+ * This class implements a simple version numbering compatibility policy for the
+ * <tt>ImportSearchPolicy</tt> where only exact version numbers are considered
+ * to be compatible.  This policy simply returns the result of
+ * "<tt>leftId.equals(rightId) && leftVersion.equals(rightVersion)</tt>". Any
+ * calls to the <tt>compare()</tt> method result in an exception since this
+ * policy has no basis for comparing identifiers and versions.
+ * @see org.apache.osgi.moduleloader.search.CompatibilityPolicy
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public class ExactCompatibilityPolicy implements CompatibilityPolicy
+{
+    /**
+     * Compares two versioned identifiers, but since this policy has
+     * no understanding of how to compare identifiers, it always throws
+     * an <tt>IllegalArgumentException</tt>.
+     * @param leftId the identifier to test for compatibility.
+     * @param leftVersion the version number to test for compatibility.
+     * @param rightId the identifier used as the compatibility base line.
+     * @param rightVersion the version used as the compatibility base line.
+     * @return <tt>0</tt> if the identifiers are equal, <tt>-1</tt> if the
+     *         left identifier is less then the right identifier, and <tt>1</tt>
+     *         if the left identifier is greater than the right identifier.
+     * @throws java.lang.IllegalArgumentException if the two identifiers
+     *         are not comparable, i.e., they refer to completely different
+     *         entities.
+    **/
+    public int compare(
+        Object leftId, Object leftVersion,
+        Object rightId, Object rightVersion)
+    {
+        throw new IllegalArgumentException("Identifiers are not comparable.");
+    }
+
+    /**
+     * Returns whether the first import/export target is compatible
+     * with the second. This method simply uses the "<tt>equals()</tt>" method
+     * to test both the identifier and the verison number.
+     * @param leftId the identifier to test for compatibility.
+     * @param leftVersion the version number to test for compatibility.
+     * @param rightId the identifier used as the compatibility base line.
+     * @param rightVersion the version used as the compatibility base line.
+     * @return <tt>true</tt> if the left version number object is compatible
+     *         with the right version number object, otherwise <tt>false</tt>.
+    **/
+    public boolean isCompatible(
+        Object leftId, Object leftVersion,
+        Object rightId, Object rightVersion)
+    {
+        return leftId.equals(rightId) && leftVersion.equals(rightVersion);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/selection/InteractiveSelectionPolicy.java b/src/org/apache/osgi/moduleloader/search/selection/InteractiveSelectionPolicy.java
new file mode 100644
index 0000000..06c234d
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/selection/InteractiveSelectionPolicy.java
@@ -0,0 +1,87 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search.selection;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import org.apache.osgi.moduleloader.Module;
+import org.apache.osgi.moduleloader.search.CompatibilityPolicy;
+import org.apache.osgi.moduleloader.search.SelectionPolicy;
+
+/**
+ * This class implements an interactive selection policy for the
+ * <tt>ImportSearchPolicy</tt>. This policy simply uses standard
+ * output to present the list of candidate modules and uses standard
+ * input to allow the user to select a specific module from the
+ * candidates. This selection policy is generally only useful for
+ * debugging purposes.
+ * @see org.apache.osgi.moduleloader.search.SelectionPolicy
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public class InteractiveSelectionPolicy implements SelectionPolicy
+{
+    /**
+     * Returns a single package from an array of packages.
+     * @param sources array of packages from which to choose.
+     * @return the selected package or <tt>null</tt> if no package
+     *         can be selected.
+    **/
+    public Module select(Module module, Object target,
+        Object version, Module[] candidates, CompatibilityPolicy compatPolicy)
+    {
+        try {
+            if (candidates.length == 1)
+            {
+                return candidates[0];
+            }
+            // Now start an interactive prompt.
+            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+            do
+            {
+                System.out.println("\nImporting '" + target
+                    + "(" + version + ")" + "' for '" + module + "'.");
+                System.out.println("");
+                for (int i = 0; i < candidates.length; i++)
+                {
+                    System.out.println((i + 1) + ". " + candidates[i]);
+                }
+                System.out.print("Select: ");
+                String s = br.readLine();
+
+                int choice = -1;
+                try {
+                    choice = Integer.parseInt(s);
+                } catch (Exception ex) {
+                }
+
+                if (choice == 0)
+                {
+                    break;
+                }
+                else if ((choice > 0) && (choice <= candidates.length))
+                {
+                    return candidates[choice - 1];
+                }
+            }
+            while (true);
+        } catch (Exception ex) {
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/selection/SimpleSelectionPolicy.java b/src/org/apache/osgi/moduleloader/search/selection/SimpleSelectionPolicy.java
new file mode 100644
index 0000000..9b2daa7
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/selection/SimpleSelectionPolicy.java
@@ -0,0 +1,145 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.moduleloader.search.selection;
+
+import java.util.*;
+
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.*;
+
+/**
+ * This class implements a reasonably simple selection policy for the
+ * <tt>ImportSearchPolicy</tt>. When given a choice, this selection
+ * policy will always select the newest version of the available
+ * candidates to satisfy the import identifier. In the case where
+ * a candidate has already been selected for a given import identifier,
+ * then the previously selected module will be returned, if possible.
+ * If it is not possible to return the previously selected module, then
+ * a <tt>null</tt> is returned. This policy assumes that classes are
+ * shared globally.
+**/
+public class SimpleSelectionPolicy implements SelectionPolicy, ModuleListener
+{
+    private Map m_resolvedPackageMap = new HashMap();
+    private Map m_resolvedModuleMap = new HashMap();
+
+    /**
+     * Selects a single module to resolve the specified import identifier
+     * from the array of compatible candidate modules. If the import
+     * identifier has not been resolved before, then this selection policy
+     * chooses the module that exports the newest version of the
+     * import identifer. If the import identifier has been resolved already,
+     * then the same module that was chosen before is chosen again.
+     * This ensures that all modules use the same version of all
+     * exported classes.
+     * @param module the module that is importing the target.
+     * @param identifier the identifier of the import target.
+     * @param version the version number of the import target.
+     * @param candidates array of compatible candidate modules from which to choose.
+     * @return the selected module or <tt>null</tt> if no module
+     *         can be selected.
+    **/
+    public synchronized Module select(Module module, Object identifier,
+        Object version, Module[] candidates, CompatibilityPolicy compatPolicy)
+    {
+        // See if package is already resolved.
+        Module selModule = (Module) m_resolvedPackageMap.get(identifier);
+
+        // If no module was previously selected to export the package,
+        // then try to choose one now.
+        if (selModule == null)
+        {
+            Object selVersion = null;
+
+            // Examine all exported instances of the identifier and
+            // choose the one with the newest version number. If
+            // there is more than one source for the newest version,
+            // then just select the first one found.
+            for (int i = 0; i < candidates.length; i++)
+            {
+                Object tmpVersion =
+                    ImportSearchPolicy.getExportVersion(candidates[i], identifier);
+
+                // If this is the first comparison, then
+                // just record it.
+                if (selVersion == null)
+                {
+                    selModule = candidates[i];
+                    selVersion = tmpVersion;
+                }
+                // If the current export package version is greater
+                // than the selected export package version, then
+                // record it instead.
+                else if (compatPolicy.compare(identifier, tmpVersion, identifier, selVersion) >= 0)
+                {
+                    selModule = candidates[i];
+                    selVersion = tmpVersion;
+                }
+            }
+
+            m_resolvedPackageMap.put(identifier, selModule);
+            m_resolvedModuleMap.put(selModule, selModule);
+        }
+        // See if the previously selected export module satisfies
+        // the current request, otherwise return null.
+        else
+        {
+            Object selVersion =
+                ImportSearchPolicy.getExportVersion(selModule, identifier);
+            Module tmpModule = selModule;
+            selModule = null;
+            if (compatPolicy.isCompatible(identifier, selVersion, identifier, version))
+            {
+                selModule = tmpModule;
+            }
+        }
+
+        return selModule;
+    }
+
+    public void moduleAdded(ModuleEvent event)
+    {
+    }
+
+    public void moduleReset(ModuleEvent event)
+    {
+        moduleRemoved(event);
+    }
+
+    public synchronized void moduleRemoved(ModuleEvent event)
+    {
+        // If the module that was removed was chosen for
+        // exporting identifier, then flush it from our
+        // data structures; we assume here that the application
+        // will flush references to the removed module's classes.
+        if (m_resolvedModuleMap.get(event.getModule()) != null)
+        {
+            // Remove from module map.
+            m_resolvedModuleMap.remove(event.getModule());
+            // Remove each exported package from package map.
+            Iterator iter = m_resolvedPackageMap.entrySet().iterator();
+            while (iter.hasNext())
+            {
+                Map.Entry entry = (Map.Entry) iter.next();
+                if (entry.getValue() == event.getModule())
+                {
+                    iter.remove();
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/BundleRecord.java b/src/org/apache/osgi/service/bundlerepository/BundleRecord.java
new file mode 100644
index 0000000..906979a
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/BundleRecord.java
@@ -0,0 +1,203 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+import java.io.PrintStream;
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * A simple interface used to hold meta-data about bundles
+ * contained in a bundle repository.
+**/
+public class BundleRecord
+{
+    public static final String BUNDLE_NAME = "Bundle-Name";
+    public static final String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName";
+    public static final String BUNDLE_VERSION = "Bundle-Version";
+    public static final String BUNDLE_UPDATELOCATION = "Bundle-UpdateLocation";
+    public static final String BUNDLE_URL = "Bundle-URL";
+    public static final String BUNDLE_SOURCEURL = "Bundle-SourceURL";
+    public static final String BUNDLE_DOCURL = "Bundle-DocURL";
+    public static final String BUNDLE_LICENSEURL = "Bundle-LicenseURL";
+    public static final String BUNDLE_DESCRIPTION = "Bundle-Description";
+    public static final String BUNDLE_CATEGORY = "Bundle-Category";
+    public static final String BUNDLE_VENDOR = "Bundle-Vendor";
+    public static final String BUNDLE_CONTACTADDRESS = "Bundle-ContactAddress";
+    public static final String BUNDLE_COPYRIGHT = "Bundle-Copyright";
+    public static final String BUNDLE_REQUIREDEXECUTIONENVIRONMENT = "Bundle-RequiredExecutionEnvironment";
+    public static final String BUNDLE_NATIVECODE = "Bundle-NativeCode";
+    public static final String IMPORT_PACKAGE = "Import-Package";
+    public static final String EXPORT_PACKAGE = "Export-Package";
+    public static final String DYNAMICIMPORT_PACKAGE = "DynamicImport-Package";
+    public static final String REQUIRE_SERVICE = "Require-Service";
+    public static final String PROVIDE_SERVICE = "Provide-Service";
+
+    private Map m_attrMap = null;
+    private Dictionary m_dict = null;
+
+    /**
+     * <p>
+     * Constructs a bundle record using the values of the supplied
+     * map as the meta-data values for the bundle. The supplied map
+     * is copied, but its values are not.
+     * </p>
+     * @param attrMap a map containing attribute-value pairs of meta-data
+     *        for a bundle.
+    **/
+    public BundleRecord(Map attrMap)
+    {
+        // Create a case insensitive map.
+        m_attrMap = new TreeMap(new Comparator() {
+            public int compare(Object o1, Object o2)
+            {
+                return o1.toString().compareToIgnoreCase(o2.toString());
+            }
+        });
+        m_attrMap.putAll(attrMap);
+    }
+
+    /**
+     * <p>
+     * Returns a dictionary object which can be used with
+     * <tt>org.osgi.framework.Filter</tt>, for example. The returned
+     * dictionary object is a minimum implementation, where only
+     * the <tt>get()</tt>, <tt>size()</tt>, and <tt>isEmpty()</tt>
+     * methods do anything useful.
+     * </p>
+     * @return a dictionary object for accessing the bundle record attributes.
+    **/
+    public synchronized Dictionary getDictionary()
+    {
+        if (m_dict == null)
+        {
+            m_dict = new Dictionary() {
+                public int size()
+                {
+                    return m_attrMap.size();
+                }
+
+                public boolean isEmpty()
+                {
+                    return m_attrMap.isEmpty();
+                }
+
+                public Enumeration elements()
+                {
+                    throw new UnsupportedOperationException("Not implemented.");
+                }
+
+                public Enumeration keys()
+                {
+                    throw new UnsupportedOperationException("Not implemented.");
+                }
+
+                public Object get(Object key)
+                {
+                    return m_attrMap.get(key);
+                }
+
+                public Object remove(Object key)
+                {
+                    throw new UnsupportedOperationException("Not implemented.");
+                }
+
+                public Object put(Object key, Object value)
+                {
+                    throw new UnsupportedOperationException("Not implemented.");
+                }
+            };
+        }
+
+        return m_dict;
+    }
+
+    /**
+     * <p>
+     * Returns an array containing all attribute names associated with
+     * the bundle record. The return array is a copy and can be freely
+     * modified.
+     * </p>
+     * @return an array containing the attribute names contained in the
+     *         bundle record.
+    **/
+    public String[] getAttributes()
+    {
+        return (String[]) m_attrMap.keySet().toArray(new String[m_attrMap.size()]);
+    }
+
+    /**
+     * <p>
+     * Returns the value of the specified attribute. If the value is an array,
+     * then a copy is returned.
+     * </p>
+     * @param name the attribute name for which to retrieve its value.
+     * @return the value of the specified attribute or <tt>null</tt> if
+     *         the specified attribute does not exist.
+    **/
+    public Object getAttribute(String name)
+    {
+        Object obj = m_attrMap.get(name);
+        // If the value is an array, then make a copy
+        // since arrays are mutable.
+        if ((obj != null) && obj.getClass().isArray())
+        {
+            Class clazz = obj.getClass().getComponentType();
+            int len = Array.getLength(obj);
+            Object copy = Array.newInstance(obj.getClass().getComponentType(), len);
+            System.arraycopy(obj, 0, copy, 0, len);
+            obj = copy;
+        }
+        return obj;
+    }
+
+    /**
+     * <p>
+     * Dumps the contents of the bundle record to the specified print stream.
+     * </p>
+     * @param out the print stream to use for printing.
+    **/    
+    public void printAttributes(PrintStream out)
+    {
+        String[] attrs = getAttributes();
+        if (attrs != null)
+        {
+            Arrays.sort(attrs);
+            for (int i = 0; (attrs != null) && (i < attrs.length); i++)
+            {
+                Object obj = getAttribute(attrs[i]);
+                if (obj.getClass().isArray())
+                {
+                    out.println(attrs[i] + ":");
+                    for (int j = 0; j < Array.getLength(obj); j++)
+                    {
+                        out.println("   " + Array.get(obj, j));
+                    }
+                }
+                else
+                {
+                    out.println(attrs[i] + ": " + getAttribute(attrs[i]));
+                }
+            }
+        }
+    }
+
+    public String toString()
+    {
+        return (String) m_attrMap.get(BUNDLE_NAME);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/BundleRepository.java b/src/org/apache/osgi/service/bundlerepository/BundleRepository.java
new file mode 100644
index 0000000..3fe266b
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/BundleRepository.java
@@ -0,0 +1,97 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+import java.io.PrintStream;
+
+/**
+ * This interface defines a simple bundle repository service.
+**/
+public interface BundleRepository
+{
+    /**
+     * Get URL list of repositories.
+     * @return a space separated list of URLs to use or <tt>null</tt>
+     *         to refresh the cached list of bundles.
+    **/
+    public String[] getRepositoryURLs();
+
+    /**
+     * Set URL list of repositories.
+     * @param url a space separated list of URLs to use or <tt>null</tt>
+     *        to refresh the cached list of bundles.
+    **/
+    public void setRepositoryURLs(String[] urls);
+
+    /**
+     * Returns an array of the bundle symbolic names available
+     * in the repository.
+     * @return An arry of available bundle symbolic names.
+    **/
+    public BundleRecord[] getBundleRecords();
+
+    /**
+     * Get the specified bundle record from the repository.
+     * @param i the bundle record index to retrieve.
+     * @return the associated bundle record or <tt>null</tt>.
+    **/
+    public BundleRecord[] getBundleRecords(String symname);
+
+    /**
+     * Get bundle record for the bundle with the specified name
+     * and version from the repository.
+     * @param name the bundle record name to retrieve.
+     * @param version three-interger array of the version associated with
+     *        the name to retrieve.
+     * @return the associated bundle record or <tt>null</tt>.
+    **/
+    public BundleRecord getBundleRecord(String symname, int[] version);
+
+    /**
+     * Deploys the bundle in the repository that corresponds to
+     * the specified update location. The action taken depends on
+     * whether the specified bundle is already installed in the local
+     * framework. If the bundle is already installed, then this
+     * method will attempt to update it. If the bundle is not already
+     * installed, then this method will attempt to install it.
+     * @param out the stream to use for informational messages.
+     * @param err the stream to use for error messages.
+     * @param symname the symbolic name of the bundle to deploy.
+     * @param isResolve a flag to indicates whether dependencies should
+     *        should be resolved.
+     * @param isStart a flag to indicate whether installed bundles should
+     *        be started.
+     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise.
+    **/
+    public boolean deployBundle(
+        PrintStream out, PrintStream err, String symname, int[] version,
+        boolean isResolve, boolean isStart);
+
+    /**
+     * Returns an array containing all bundle records in the
+     * repository that resolve the transitive closure of the
+     * passed in array of package declarations.
+     * @param pkgs an array of package declarations to resolve.
+     * @return an array containing all bundle records in the
+     *         repository that resolve the transitive closure of
+     *         the passed in array of package declarations.
+     * @throws ResolveException if any packages in the transitive
+     *         closure of packages cannot be resolved.
+    **/
+    public BundleRecord[] resolvePackages(IPackage[] pkgs)
+        throws ResolveException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IAttribute.java b/src/org/apache/osgi/service/bundlerepository/IAttribute.java
new file mode 100644
index 0000000..13d5f57
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IAttribute.java
@@ -0,0 +1,26 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+public interface IAttribute
+{
+    public String getName();
+
+    public String getValue();
+
+    public boolean isMandatory();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IDirective.java b/src/org/apache/osgi/service/bundlerepository/IDirective.java
new file mode 100644
index 0000000..1d407e3
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IDirective.java
@@ -0,0 +1,24 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+public interface IDirective
+{
+    public String getName();
+
+    public String getValue();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IPackage.java b/src/org/apache/osgi/service/bundlerepository/IPackage.java
new file mode 100644
index 0000000..0877c69
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IPackage.java
@@ -0,0 +1,34 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+public interface IPackage
+{
+    public String getId();
+
+    public IDirective[] getDirectives();
+
+    public IAttribute[] getAttributes();
+
+    public IVersion getVersionLow();
+
+    public IVersion getVersionHigh();
+
+    public boolean isOptional();
+
+    public boolean doesSatisfy(IPackage pkg);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IVersion.java b/src/org/apache/osgi/service/bundlerepository/IVersion.java
new file mode 100644
index 0000000..1433d9d
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IVersion.java
@@ -0,0 +1,36 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+public interface IVersion extends Comparable
+{
+    public boolean equals(Object object);
+
+    public int getMajorComponent();
+
+    public int getMinorComponent();
+
+    public int getMicroComponent();
+
+    public String getQualifierComponent();
+
+    public boolean isInclusive();
+
+    public int compareTo(Object o);
+
+    public String toString();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/ResolveException.java b/src/org/apache/osgi/service/bundlerepository/ResolveException.java
new file mode 100644
index 0000000..c3a52c9
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/ResolveException.java
@@ -0,0 +1,32 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.bundlerepository;
+
+public class ResolveException extends Exception
+{
+    private IPackage m_pkg = null;
+
+    public ResolveException(IPackage pkg)
+    {
+        m_pkg = pkg;
+    }
+    
+    public IPackage getPackage()
+    {
+        return m_pkg;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/shell/CdCommand.java b/src/org/apache/osgi/service/shell/CdCommand.java
new file mode 100644
index 0000000..1713175
--- /dev/null
+++ b/src/org/apache/osgi/service/shell/CdCommand.java
@@ -0,0 +1,49 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.shell;
+
+/**
+ * This interface defines the <tt>cd</tt> command service interface for the
+ * Felix shell service. The <tt>cd</tt> command does not really change the
+ * directory of the shell, rather it maintains a base URL for
+ * simplifying URL entry.
+ * <p>
+ * For example, if the base URL is <tt>http://www.foo.com/<tt> and you
+ * try to install a bundle <tt>foo.jar</tt>, the actual URL will be
+ * expanded to <tt>http://www.foo.com/foo.jar</tt>. Any bundles wishing
+ * to retrieve or set the current directory of the shell can use this
+ * service interface.
+**/
+public interface CdCommand extends Command
+{
+    /**
+     * Property used to configure the base URL.
+    **/
+    public static final String BASE_URL_PROPERTY = "felix.shell.baseurl";
+
+    /**
+     * Returns the current <i>directory</i> of the shell service.
+     * @return the current shell directory.
+    **/
+    public String getBaseURL();
+
+    /**
+     * Sets the current <i>directory</i> of the shell service.
+     * @param s the new value for the base URL.
+    **/
+    public void setBaseURL(String s);
+}
diff --git a/src/org/apache/osgi/service/shell/Command.java b/src/org/apache/osgi/service/shell/Command.java
new file mode 100644
index 0000000..65c5852
--- /dev/null
+++ b/src/org/apache/osgi/service/shell/Command.java
@@ -0,0 +1,68 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.shell;
+
+import java.io.PrintStream;
+
+/**
+ * This interface is used to define commands for the Felix shell
+ * service. Any bundle wishing to create commands for the
+ * shell service simply needs to create a service object that
+ * implements this interface and then register it with the OSGi
+ * framework. The shell service automatically includes any
+ * registered command services in its list of available commands.
+**/
+public interface Command
+{
+    /**
+     * Returns the name of the command that is implemented by the
+     * interface. The command name should not contain whitespace
+     * and should also be unique.
+     * @return the name of the command.
+    **/
+    public String getName();
+
+    /**
+     * Returns the usage string for the command. The usage string is
+     * a short string that illustrates how to use the command on the
+     * command line. This information is used when generating command
+     * help information. An example usage string for the <tt>install</tt>
+     * command is:
+     * <pre>
+     *     install <URL> [<URL> ...]
+     * </pre>
+     * @return the usage string for the command.
+    **/
+    public String getUsage();
+
+    /**
+     * Returns a short description of the command; this description
+     * should be as short as possible. This information is used when
+     * generating the command help information.
+     * @return a short description of the command.
+    **/
+    public String getShortDescription();
+
+    /**
+     * Executes the command using the supplied command line, output
+     * print stream, and error print stream.
+     * @param line the complete command line, including the command name.
+     * @param out the print stream to use for standard output.
+     * @param err the print stream to use for standard error.
+    **/
+    public void execute(String line, PrintStream out, PrintStream err);
+}
diff --git a/src/org/apache/osgi/service/shell/ShellService.java b/src/org/apache/osgi/service/shell/ShellService.java
new file mode 100644
index 0000000..4f1178d
--- /dev/null
+++ b/src/org/apache/osgi/service/shell/ShellService.java
@@ -0,0 +1,95 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.apache.osgi.service.shell;
+
+import java.io.PrintStream;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This interface defines the Felix shell service. The shell service
+ * is an extensible, user interface neutral shell for controlling and
+ * interacting with the framework. In general, the shell service assumes that
+ * it is operating in a command line fashion, i.e., it receives a
+ * complete command line, parses it, and executes the corresponding
+ * command, but graphical interfaces are also possible.
+ * <p>
+ * All commands in the shell service are actually implemented as OSGi
+ * services; these services implement the <tt>Command</tt> service
+ * interface. Any bundle can implement custom commands by creating
+ * command services and registering them with the OSGi framework.
+**/
+public interface ShellService
+{
+    /**
+     * Returns an array of command names available in the shell service.
+     * @return an array of available command names or an empty array.
+    **/
+    public String[] getCommands();
+
+    /**
+     * Returns the usage string associated with the specified command name.
+     * @param name the name of the target command.
+     * @return the usage string of the specified command or null.
+    **/
+    public String getCommandUsage(String name);
+
+    /**
+     * Returns the description associated with the specified command name.
+     * @param name the name of the target command.
+     * @return the description of the specified command or null.
+    **/
+    public String getCommandDescription(String name);
+
+    /**
+     * Returns the service reference associated with the specified
+     * command name.
+     * @param name the name of the target command.
+     * @return the description of the specified command or null.
+    **/
+    public ServiceReference getCommandReference(String name);
+
+    /**
+     *
+     * This method executes the supplied command line using the
+     * supplied output and error print stream. The assumption of
+     * this method is that a command line will be typed by the user
+     * (or perhaps constructed by a GUI) and passed into it for
+     * execution. The command line is interpretted in a very
+     * simplistic fashion; it takes the leading string of characters
+     * terminated by a space character (not including it) and
+     * assumes that this leading token is the command name. For an
+     * example, consider the following command line:
+     * </p>
+     * <pre>
+     *     update 3 http://www.foo.com/bar.jar
+     * </pre>
+     * <p>
+     * This is interpretted as an <tt>update</tt> command; as a
+     * result, the entire command line (include command name) is
+     * passed into the <tt>execute()</tt> method of the command
+     * service with the name <tt>update</tt> if one exists. If the
+     * corresponding command service is not found, then an error
+     * message is printed to the error print stream.
+     * @param commandLine the command line to execute.
+     * @param out the standard output print stream.
+     * @param err the standard error print stream.
+    **/
+    public void executeCommand(
+        String commandLine, PrintStream out, PrintStream err)
+        throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/AdminPermission.java b/src/org/osgi/framework/AdminPermission.java
new file mode 100644
index 0000000..05a0966
--- /dev/null
+++ b/src/org/osgi/framework/AdminPermission.java
@@ -0,0 +1,932 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/AdminPermission.java,v 1.12 2005/05/13 20:32:54 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.*;
+import java.util.*;
+import org.apache.osgi.framework.FilterImpl;
+
+/**
+ * Indicates the caller's authority to perform specific privileged administrative 
+ * operations on or to get sensitive information about a bundle.
+ * 
+ * <ul>
+ *   <li>The <code>{@link AdminPermission#METADATA}</code> action allows calls to
+ *   	<ul>
+ *         <li>{@link Bundle#getHeaders()}
+ *         <li>{@link Bundle#getHeaders(String)}
+ *         <li>{@link Bundle#getLocation()}
+ *         </ul>
+ *   <li>The <code>{@link AdminPermission#RESOURCE}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link Bundle#getResource(String)}
+ *   		<li>{@link Bundle#getEntry(String)}
+ *   		<li>{@link Bundle#getEntryPaths(String)}
+ *   		<li>Bundle resource/entry URL creation
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#METADATA}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link Bundle#loadClass(String)}
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#LIFECYCLE}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link BundleContext#installBundle(String)}
+ *   		<li>{@link BundleContext#installBundle(String, InputStream)}
+ *   		<li>{@link Bundle#update()}
+ *   		<li>{@link Bundle#update(InputStream)}
+ *   		<li>{@link Bundle#uninstall()}
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#EXECUTE}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link Bundle#start()}
+ *   		<li>{@link Bundle#stop()}
+ *   		<li>{@link org.osgi.service.startlevel.StartLevel#setBundleStartLevel(Bundle, int)}
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#LISTENER}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link BundleContext#addBundleListener(BundleListener)} for 
+ *   				<code>SynchronousBundleListener</code>
+ *   		<li>{@link BundleContext#removeBundleListener(BundleListener)} for 
+ *   				<code>SynchronousBundleListener</code>
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#PERMISSION}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link org.osgi.service.permissionadmin.PermissionAdmin#setPermissions(String, PermissionInfo[])}
+ *   		<li>{@link org.osgi.service.permissionadmin.PermissionAdmin#setDefaultPermissions(PermissionInfo[])}
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#RESOLVE}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link org.osgi.service.packageadmin.PackageAdmin#refreshPackages(Bundle[])}</code>
+ *   		<li>{@link org.osgi.service.packageadmin.PackageAdmin#resolveBundles(Bundle[])}</code>
+ *   	</ul>
+ *   <li>The <code>{@link AdminPermission#STARTLEVEL}</code> action allows calls to
+ *   	<ul>
+ *   		<li>{@link org.osgi.service.startlevel.StartLevel#setStartLevel(int)}
+ *   		<li>{@link org.osgi.service.startlevel.StartLevel#setInitialBundleStartLevel(int)}
+ *   	</ul>
+ * </ul>
+ * 
+ * The special action "*" will represent all actions.
+ * 
+ * @version $Revision: 1.12 $
+ */
+
+public final class AdminPermission extends Permission
+{
+	static final long	serialVersionUID	= 207051004521261705L;
+
+    /**
+     * The action string <code>class</code> (Value is "class").
+     */
+    public final static String CLASS = "class"; //$NON-NLS-1$
+
+    /**
+     * The action string <code>execute</code> (Value is "execute").
+     */
+    public final static String EXECUTE = "execute"; //$NON-NLS-1$
+    
+    /**
+     * The action string <code>lifecycle</code> (Value is "lifecycle").
+     */
+    public final static String LIFECYCLE = "lifecycle"; //$NON-NLS-1$
+    
+    /**
+     * The action string <code>listener</code> (Value is "listener").
+     */
+    public final static String LISTENER = "listener"; //$NON-NLS-1$
+    
+    /**
+     * The action string <code>metadata</code> (Value is "metadata").
+     */
+    public final static String METADATA = "metadata"; //$NON-NLS-1$
+
+    /**
+     * The action string <code>permission</code> (Value is "permission").
+     */
+    public final static String PERMISSION = "permission"; //$NON-NLS-1$
+	
+    /**
+     * The action string <code>resolve</code> (Value is "resolve").
+     */
+    public final static String RESOLVE = "resolve"; //$NON-NLS-1$
+
+    /**
+     * The action string <code>resource</code> (Value is "resource").
+     */
+    public final static String RESOURCE = "resource"; //$NON-NLS-1$
+    
+    /**
+     * The action string <code>startlevel</code> (Value is "startlevel").
+     */
+    public final static String STARTLEVEL = "startlevel"; //$NON-NLS-1$
+
+    /**
+     * The action string <code>extensionLifecycle</code> (Value is "extensionLifecycle").
+     */
+    public final static String EXTENSIONLIFECYCLE = "extensionLifecycle"; //$NON-NLS-1$
+
+    private final static int ACTION_CLASS				= 0x00000001;
+    private final static int ACTION_EXECUTE				= 0x00000002;
+    private final static int ACTION_LIFECYCLE			= 0x00000004;
+    private final static int ACTION_LISTENER			= 0x00000008;
+    private final static int ACTION_METADATA			= 0x00000010;
+    private final static int ACTION_PERMISSION			= 0x00000020;
+    private final static int ACTION_RESOLVE				= 0x00000040;
+    private final static int ACTION_RESOURCE			= 0x00000080;
+    private final static int ACTION_STARTLEVEL			= 0x00000100;
+	private final static int ACTION_EXTENSIONLIFECYCLE	= 0x00000200;
+    private final static int ACTION_ALL = 
+		ACTION_CLASS 				|
+		ACTION_EXECUTE 				|
+		ACTION_LIFECYCLE 			|
+		ACTION_LISTENER 			|
+    	ACTION_METADATA 			|
+		ACTION_PERMISSION			|
+		ACTION_RESOLVE 				|
+		ACTION_RESOURCE 			|
+		ACTION_STARTLEVEL			|
+		ACTION_EXTENSIONLIFECYCLE;
+    private final static int ACTION_NONE = 0;
+	
+	/**
+	 * Indicates that this AdminPermission refers to all bundles
+	 * @serial
+	 */
+	private boolean wildcard;
+	
+	/**
+	 * An x.500 distinguished name used to match a bundle's signature - only used if
+	 * wildcard is false and bundle = null
+	 * @serial
+	 */
+	private String filter;
+
+    /**
+     * The actions in canonical form.
+     *
+     * @serial
+     */
+    private String actions = null;
+
+    /**
+     * The actions mask.
+     */
+	private transient int action_mask = ACTION_NONE;
+
+	/**
+	 * The bundle governed by this AdminPermission - only used if 
+	 * wildcard is false and filter == null
+	 */
+	private transient Bundle bundle;
+
+    /**
+     * If this AdminPermission was constructed with a bundle, this dictionary holds
+     * the properties of that bundle, used to match a filter in implies.
+     * This is not initialized until necessary, and then cached in this object.
+     */
+    private transient Dictionary bundleProperties;
+    
+    /**
+     * If this AdminPermission was constructed with a filter, this dictionary holds
+     * a Filter matching object used to evaluate the filter in implies.
+     * This is not initialized until necessary, and then cached in this object
+     */
+    private transient Filter filterImpl;
+    
+	/**
+     * Creates a new <code>AdminPermission</code> object that matches 
+     * all bundles and has all actions.  Equivalent to 
+     * AdminPermission("*","*");
+     */
+    public AdminPermission()
+    {
+    	this("*",AdminPermission.ACTION_ALL); //$NON-NLS-1$
+    }
+    
+    /**
+     * Creates a new <code>AdminPermission</code> object for use by the <code>Policy</code>
+     * object to instantiate new <code>Permission</code> objects.
+     * 
+     * Null arguments are equivalent to "*"
+     *
+     * @param filter an X.500 Distinguished Name suffix or "*" to match all bundles
+     * @param actions <code>class</code>, <code>execute</code>, <code>lifecycle</code>, 
+     * <code>listener</code>, <code>metadata</code>, <code>permission</code>, <code>resolve</code>, 
+     * <code>resource</code>, <code>startlevel</code>, or "*" to indicate all actions
+     */
+    public AdminPermission(String filter, String actions)
+    {
+    	//arguments will be null if called from a PermissionInfo defined with
+    	//no args
+    	this(
+    			(filter == null ? "*" : filter), //$NON-NLS-1$
+				getMask((actions == null ? "*" : actions)) //$NON-NLS-1$
+				);
+    }
+
+    /**
+     * Creates a new <code>AdminPermission</code> object for use by the <code>Policy</code>
+     * object to instantiate new <code>Permission</code> objects.
+     * 
+     * @param bundle A bundle
+     * @param actions <code>class</code>, <code>execute</code>, <code>lifecycle</code>, 
+     * <code>listener</code>, <code>metadata</code>, <code>permission</code>, <code>resolve</code>, 
+     * <code>resource</code>, <code>startlevel</code>, or "*" to indicate all actions
+     */
+    public AdminPermission(Bundle bundle, String actions) {
+    	super(bundle.toString());
+    	this.bundle = bundle;
+    	this.wildcard = false;
+    	this.filter = null;
+    	this.action_mask = getMask(actions);
+    }
+ 
+    /**
+     * Package private constructor used by AdminPermissionCollection.
+     *
+     * @param filter name filter
+     * @param action_mask mask
+     */
+    AdminPermission(String filter, int action_mask) {
+    	super(filter);
+    	
+    	//name must be either * or a filter
+    	if (filter.equals("*")) { //$NON-NLS-1$
+    		this.wildcard = true;
+    		this.filter = null;
+    	} else {
+			this.wildcard = false;
+			this.filter = filter;
+    	}
+    	this.bundle = null;
+    	this.action_mask = action_mask;
+    }
+
+    /**
+     * Parse action string into action mask.
+     *
+     * @param actions Action string.
+     * @return action mask.
+     */
+    private static int getMask(String actions) {
+    
+    	boolean seencomma = false;
+
+    	int mask = ACTION_NONE;
+
+    	if (actions == null) {
+    		return mask;
+    	}
+
+    	char[] a = actions.toCharArray();
+
+    	int i = a.length - 1;
+    	if (i < 0)
+    		return mask;
+
+    	while (i != -1) {
+    		char c;
+
+    		// skip whitespace
+    		while ((i!=-1) && ((c = a[i]) == ' ' ||
+    				c == '\r' ||
+					c == '\n' ||
+					c == '\f' ||
+					c == '\t'))
+    			i--;
+
+    		// check for the known strings
+    		int matchlen;
+
+    		if (i >= 4 && 
+					(a[i-4] == 'c' || a[i-4] == 'C') &&
+					(a[i-3] == 'l' || a[i-3] == 'L') &&
+					(a[i-2] == 'a' || a[i-2] == 'A') &&
+					(a[i-1] == 's' || a[i-1] == 'S') &&
+					  (a[i] == 's' ||   a[i] == 'S'))
+			{
+				matchlen = 5;
+				mask |= ACTION_CLASS;
+	
+    		} else if (i >= 6 && 
+					(a[i-6] == 'e' || a[i-6] == 'E') &&
+					(a[i-5] == 'x' || a[i-5] == 'X') &&
+					(a[i-4] == 'e' || a[i-4] == 'E') &&
+					(a[i-3] == 'c' || a[i-3] == 'C') &&
+					(a[i-2] == 'u' || a[i-2] == 'U') &&
+					(a[i-1] == 't' || a[i-1] == 'T') &&
+					  (a[i] == 'e' ||   a[i] == 'E'))
+			{
+				matchlen = 7;
+				mask |= ACTION_EXECUTE;
+				
+			} else if (i >= 17 && 
+					(a[i-17] == 'e' || a[i-17] == 'E') &&
+					(a[i-16] == 'x' || a[i-16] == 'X') &&
+					(a[i-15] == 't' || a[i-15] == 'T') &&
+					(a[i-14] == 'e' || a[i-14] == 'E') &&
+					(a[i-13] == 'n' || a[i-13] == 'N') &&
+					(a[i-12] == 's' || a[i-12] == 'S') &&
+					(a[i-11] == 'i' || a[i-11] == 'I') &&
+					(a[i-10] == 'o' || a[i-10] == 'O') &&
+					(a[i-9] == 'n' || a[i-9] == 'N') &&
+					(a[i-8] == 'l' || a[i-8] == 'L') &&
+					(a[i-7] == 'i' || a[i-7] == 'I') &&
+					(a[i-6] == 'f' || a[i-6] == 'F') &&
+					(a[i-5] == 'e' || a[i-5] == 'E') &&
+					(a[i-4] == 'c' || a[i-4] == 'C') &&
+					(a[i-3] == 'y' || a[i-3] == 'Y') &&
+					(a[i-2] == 'c' || a[i-2] == 'C') &&
+					(a[i-1] == 'l' || a[i-1] == 'L') &&
+					  (a[i] == 'e' ||   a[i] == 'E'))
+    		{
+    			matchlen = 18;
+    			mask |= ACTION_EXTENSIONLIFECYCLE;
+
+    		} else if (i >= 8 && 
+					(a[i-8] == 'l' || a[i-8] == 'L') &&
+					(a[i-7] == 'i' || a[i-7] == 'I') &&
+					(a[i-6] == 'f' || a[i-6] == 'F') &&
+					(a[i-5] == 'e' || a[i-5] == 'E') &&
+					(a[i-4] == 'c' || a[i-4] == 'C') &&
+					(a[i-3] == 'y' || a[i-3] == 'Y') &&
+					(a[i-2] == 'c' || a[i-2] == 'C') &&
+					(a[i-1] == 'l' || a[i-1] == 'L') &&
+					  (a[i] == 'e' ||   a[i] == 'E'))
+			{
+				matchlen = 9;
+				mask |= ACTION_LIFECYCLE;
+				
+			} else if (i >= 7 && 
+					(a[i-7] == 'l' || a[i-7] == 'L') &&
+					(a[i-6] == 'i' || a[i-6] == 'I') &&
+					(a[i-5] == 's' || a[i-5] == 'S') &&
+					(a[i-4] == 't' || a[i-4] == 'T') &&
+					(a[i-3] == 'e' || a[i-3] == 'E') &&
+					(a[i-2] == 'n' || a[i-2] == 'N') &&
+					(a[i-1] == 'e' || a[i-1] == 'E') &&
+					  (a[i] == 'r' ||   a[i] == 'R'))
+			{
+				matchlen = 8;
+				mask |= ACTION_LISTENER;
+			
+			} else if (i >= 7 && 
+    				(a[i-7] == 'm' || a[i-7] == 'M') &&
+    	            (a[i-6] == 'e' || a[i-6] == 'E') &&
+    	            (a[i-5] == 't' || a[i-5] == 'T') &&
+    	            (a[i-4] == 'a' || a[i-4] == 'A') &&
+    	            (a[i-3] == 'd' || a[i-3] == 'D') &&
+    	            (a[i-2] == 'a' || a[i-2] == 'A') &&
+					(a[i-1] == 't' || a[i-1] == 'T') &&
+					  (a[i] == 'a' ||   a[i] == 'A'))
+    		{
+    			matchlen = 8;
+    			mask |= ACTION_METADATA;
+
+    		} else if (i >= 9 && 
+					(a[i-9] == 'p' || a[i-9] == 'P') &&
+					(a[i-8] == 'e' || a[i-8] == 'E') &&
+					(a[i-7] == 'r' || a[i-7] == 'R') &&
+					(a[i-6] == 'm' || a[i-6] == 'M') &&
+					(a[i-5] == 'i' || a[i-5] == 'I') &&
+					(a[i-4] == 's' || a[i-4] == 'S') &&
+					(a[i-3] == 's' || a[i-3] == 'S') &&
+					(a[i-2] == 'i' || a[i-2] == 'I') &&
+					(a[i-1] == 'o' || a[i-1] == 'O') &&
+					  (a[i] == 'n' ||   a[i] == 'N'))
+    		{
+    			matchlen = 10;
+    			mask |= ACTION_PERMISSION;
+			
+    		} else if (i >= 6 && 
+					(a[i-6] == 'r' || a[i-6] == 'R') &&
+					(a[i-5] == 'e' || a[i-5] == 'E') &&
+					(a[i-4] == 's' || a[i-4] == 'S') &&
+					(a[i-3] == 'o' || a[i-3] == 'O') &&
+					(a[i-2] == 'l' || a[i-2] == 'L') &&
+					(a[i-1] == 'v' || a[i-1] == 'V') &&
+					  (a[i] == 'e' ||   a[i] == 'E'))
+    		{
+    			matchlen = 7;
+    			mask |= ACTION_RESOLVE;
+    			
+    		} else if (i >= 7 && 
+    					(a[i-7] == 'r' || a[i-7] == 'R') &&
+						(a[i-6] == 'e' || a[i-6] == 'E') &&
+						(a[i-5] == 's' || a[i-5] == 'S') &&
+						(a[i-4] == 'o' || a[i-4] == 'O') &&
+						(a[i-3] == 'u' || a[i-3] == 'U') &&
+						(a[i-2] == 'r' || a[i-2] == 'R') &&
+						(a[i-1] == 'c' || a[i-1] == 'C') &&
+						  (a[i] == 'e' ||   a[i] == 'E'))
+			{
+    			matchlen = 8;
+    			mask |= ACTION_RESOURCE;
+
+    		} else if (i >= 9 && 
+					(a[i-9] == 's' || a[i-9] == 'S') &&
+					(a[i-8] == 't' || a[i-8] == 'T') &&
+					(a[i-7] == 'a' || a[i-7] == 'A') &&
+					(a[i-6] == 'r' || a[i-6] == 'R') &&
+					(a[i-5] == 't' || a[i-5] == 'T') &&
+					(a[i-4] == 'l' || a[i-4] == 'L') &&
+					(a[i-3] == 'e' || a[i-3] == 'E') &&
+					(a[i-2] == 'v' || a[i-2] == 'V') &&
+					(a[i-1] == 'e' || a[i-1] == 'E') &&
+					  (a[i] == 'l' ||   a[i] == 'L'))
+    		{
+    			matchlen = 10;
+    			mask |= ACTION_STARTLEVEL;
+
+    		} else if (i >= 0 && 
+					(a[i] == '*'))
+    		{
+    			matchlen = 1;
+    			mask |= ACTION_ALL;
+
+			} else {
+				// parse error
+				throw new IllegalArgumentException(
+						"invalid permission: " + actions);
+        }
+
+        // make sure we didn't just match the tail of a word
+        // like "ackbarfstartlevel".  Also, skip to the comma.
+        seencomma = false;
+        while (i >= matchlen && !seencomma) {
+        	switch(a[i-matchlen]) {
+        		case ',':
+        			seencomma = true;
+        			/*FALLTHROUGH*/
+        		case ' ': case '\r': case '\n':
+        		case '\f': case '\t':
+        			break;
+        		default:
+        			throw new IllegalArgumentException(
+        					"invalid permission: " + actions);
+        	}
+        	i--;
+        }
+
+        // point i at the location of the comma minus one (or -1).
+        i -= matchlen;
+    }
+
+    if (seencomma) {
+        throw new IllegalArgumentException("invalid permission: " +
+                        actions);
+    }
+
+    return mask;
+    }
+     
+    /**
+     * Called by <code><@link AdminPermission#implies(Permission)></code> on an AdminPermission
+     * which was constructed with a Bundle.  This method loads a dictionary with the
+     * filter-matchable properties of this bundle.  The dictionary is cached so this lookup
+     * only happens once.
+     * 
+     * This method should only be called on an AdminPermission which was constructed with a 
+     * bundle
+     * 
+     * @return a dictionary of properties for this bundle
+     */
+    private Dictionary getProperties() {
+    	if (bundleProperties == null) {
+    		bundleProperties = new Hashtable();
+
+    		AccessController.doPrivileged(new PrivilegedAction() {
+				public Object run() {
+		    		//set Id
+		    		bundleProperties.put("id",new Long(bundle.getBundleId())); //$NON-NLS-1$
+		    		
+		    		//set location
+		    		bundleProperties.put("location",bundle.getLocation()); //$NON-NLS-1$
+		    		
+		    		//set name
+		    		if (bundle.getSymbolicName() != null)
+		    			bundleProperties.put("name",bundle.getSymbolicName()); //$NON-NLS-1$
+		    		
+		    		//set signers
+//		    		bundleProperties.put("signer",new SignerWrapper(bundle)); //$NON-NLS-1$
+		    		
+		    		return null;
+				}
+			});
+    	}     		
+    	return bundleProperties;
+    }
+/*
+    private static class SignerWrapper extends Object {
+    	private Bundle bundle;
+    	private String pattern;
+    	public SignerWrapper(String pattern) {
+    		this.pattern = pattern;    			
+    	}
+    	SignerWrapper(Bundle bundle) {
+    		this.bundle = bundle;
+    	}
+    	
+		public boolean equals(Object o) {
+			if (!(o instanceof SignerWrapper))
+				return false;
+			SignerWrapper other = (SignerWrapper) o;
+			AbstractBundle matchBundle = (AbstractBundle) (bundle != null ? bundle : other.bundle);
+			String matchPattern = bundle != null ? other.pattern : pattern;
+			return matchBundle.getBundleData().matchDNChain(matchPattern);
+		}
+    }
+*/    
+
+    /**
+     * Called by <tt><@link AdminPermission#implies(Permission)></tt> on an AdminPermission
+     * which was constructed with a filter.  This method loads a FilterImpl with the
+     * filter specification of this AdminPermission.  The filter is cached so this work
+     * only happens once.
+     * 
+     * This method should only be called on an AdminPermission which was constructed with a 
+     * filter
+     * 
+     * @return a filterImpl for this bundle
+     */
+    private Filter getFilterImpl() {
+        if (filterImpl == null) {
+            try {
+                int pos = filter.indexOf("signer"); //$NON-NLS-1$
+                if (pos != -1){ 
+                
+                    //there may be a signer attribute 
+                    StringBuffer filterBuf = new StringBuffer(filter);
+                    int numAsteriskFound = 0; //use as offset to replace in buffer
+                    
+                    int walkbackPos; //temp pos
+
+                    //find occurences of (signer= and escape out *'s
+                    while (pos != -1) {
+
+                        //walk back and look for '(' to see if this is an attr
+                        walkbackPos = pos-1; 
+                        
+                        //consume whitespace
+                        while(walkbackPos >= 0 && Character.isWhitespace(filter.charAt(walkbackPos))) {
+                            walkbackPos--;
+                        }
+                        if (walkbackPos <0) {
+                            //filter is invalid - FilterImpl will throw error
+                            break;
+                        }
+                        
+                        //check to see if we have unescaped '('
+                        if (filter.charAt(walkbackPos) != '(' || (walkbackPos > 0 && filter.charAt(walkbackPos-1) == '\\')) {
+                            //'(' was escaped or not there
+                            pos = filter.indexOf("signer",pos+6); //$NON-NLS-1$
+                            continue;
+                        }                   
+                        pos+=6; //skip over 'signer'
+
+                        //found signer - consume whitespace before '='
+                        while (Character.isWhitespace(filter.charAt(pos))) {
+                            pos++;
+                        }
+
+                        //look for '='
+                        if (filter.charAt(pos) != '=') {
+                            //attr was signerx - keep looking
+                            pos = filter.indexOf("signer",pos); //$NON-NLS-1$
+                            continue;
+                        }
+                        pos++; //skip over '='
+                        
+                        //found signer value - escape '*'s
+                        while (!(filter.charAt(pos) == ')' && filter.charAt(pos-1) != '\\')) {
+                            if (filter.charAt(pos) == '*') {
+                                filterBuf.insert(pos+numAsteriskFound,'\\');
+                                numAsteriskFound++;
+                            }
+                            pos++;
+                        }
+
+                        //end of signer value - look for more?
+                        pos = filter.indexOf("signer",pos); //$NON-NLS-1$
+                    } //end while (pos != -1)
+                    filter = filterBuf.toString();
+                } //end if (pos != -1)
+
+                filterImpl = new FilterImpl(filter);
+            } catch (InvalidSyntaxException e) {
+                //we will return null
+            }
+        }           
+        return filterImpl;
+    }
+
+    /**
+     * Determines if the specified permission is implied by this object.
+     * This method throws an exception if the specified permission was not
+     * constructed with a bundle.
+     * 
+     * <p>This method returns <code>true</code> if
+     * The specified permission is an AdminPermission AND
+     * <ul>
+     * 	<li>this object's filter is an X.500 Distinguished name suffix that 
+     * matches the specified permission's bundle OR
+     * 	<li>this object's filter is "*" OR
+     * 	<li>this object's bundle is a equal to the specified permission's
+     * bundle
+     * </ul>
+     * AND this object's actions include all of the specified permission's actions 	 
+     *
+     * Special case: if the specified permission was constructed with "*", then this method
+     * returns <code>true</code> if this object's filter is "*" and this object's actions include
+     * all of the specified permission's actions
+     * 
+     * @param p The permission to interrogate.
+     *
+     * @return <code>true</code> if the specified permission is implied by
+     * this object; <code>false</code> otherwise.
+     * @throws RuntimeException if specified permission was not constructed with
+     * a bundle or "*"
+     */
+    public boolean implies(Permission p)
+    {
+    	if (!(p instanceof AdminPermission))
+    		return false;
+    	AdminPermission target = (AdminPermission)p;
+    	//check actions first - much faster
+    	if ((action_mask & target.action_mask)!=target.action_mask)
+    		return false;
+    	//if passed in a filter, puke
+    	if (target.filter != null)
+    		throw new RuntimeException("Cannot imply a filter");
+    	//special case - only wildcard implies wildcard
+    	if (target.wildcard)
+    		return wildcard;
+
+    	//check our name 
+    	if (filter != null) {
+    		//it's a filter
+    		Filter filterImpl = getFilterImpl();
+			return filterImpl != null && filterImpl.match(target.getProperties());
+    	} else if (wildcard) {
+    		//it's "*"
+    		return true;
+    	} else {
+    		//it's a bundle id
+    		return bundle.equals(target.bundle);
+    	}
+    	    	
+    }
+    
+    /**
+     * Returns the canonical string representation of the <code>AdminPermission</code> actions.
+     *
+     * <p>Always returns present <code>AdminPermission</code> actions in the following order:
+     * <code>CLASS</code>, <code>EXECUTE</code>, <code>LIFECYCLE</code>, <code>LISTENER</code>, 
+     * <code>METADATA</code>, <code>PERMISSION</code>, <code>RESOLVE</code>, <code>RESOURCE</code>, 
+     * <code>STARTLEVEL</code>.
+     * @return Canonical string representation of the <code>AdminPermission</code> actions.
+     */
+	public String getActions() {
+		if (actions == null) {
+			if (action_mask == ACTION_ALL) {
+				actions = "*"; //$NON-NLS-1$
+			} else {
+				StringBuffer sb = new StringBuffer();
+				
+				if ((action_mask & ACTION_CLASS) == ACTION_CLASS) {
+					sb.append(CLASS);
+					sb.append(',');
+				}
+
+				if ((action_mask & ACTION_EXECUTE) == ACTION_EXECUTE) {
+					sb.append(EXECUTE);
+					sb.append(',');
+				}
+	
+				if ((action_mask & ACTION_LIFECYCLE) == ACTION_LIFECYCLE) {
+					sb.append(LIFECYCLE);
+					sb.append(',');
+				}
+	
+				if ((action_mask & ACTION_LISTENER) == ACTION_LISTENER) {
+					sb.append(LISTENER);
+					sb.append(',');
+				}
+				
+				if ((action_mask & ACTION_METADATA) == ACTION_METADATA) {
+					sb.append(METADATA);
+					sb.append(',');
+				}
+	
+				if ((action_mask & ACTION_PERMISSION) == ACTION_PERMISSION) {
+					sb.append(PERMISSION);
+					sb.append(',');
+				}
+	
+				if ((action_mask & ACTION_RESOLVE) == ACTION_RESOLVE) {
+					sb.append(RESOLVE);
+					sb.append(',');
+				}
+	
+				if ((action_mask & ACTION_RESOURCE) == ACTION_RESOURCE) {
+					sb.append(RESOURCE);
+					sb.append(',');
+				}
+	
+				if ((action_mask & ACTION_STARTLEVEL) == ACTION_STARTLEVEL) {
+					sb.append(STARTLEVEL);
+					sb.append(',');
+				}
+
+				if ((action_mask & ACTION_EXTENSIONLIFECYCLE) == ACTION_EXTENSIONLIFECYCLE) {
+					sb.append(EXTENSIONLIFECYCLE);
+					sb.append(',');
+				}
+
+				//remove trailing comma
+				if (sb.length() > 0) {
+					sb.deleteCharAt(sb.length()-1);
+				}
+				
+				actions = sb.toString();
+			}
+		}
+		return actions;
+	}
+	
+    /**
+     * Determines the equality of two <code>AdminPermission</code> objects. <p>Two 
+     * <code>AdminPermission</code> objects are equal.
+     *
+     * @param obj The object being compared for equality with this object.
+     * @return <code>true</code> if <code>obj</code> is equivalent to this 
+     * <code>AdminPermission</code>; <code>false</code> otherwise.
+     */
+    public boolean equals(Object obj)
+    {
+        if (obj == this) {
+        	return true;
+        }
+        
+        if (!(obj instanceof AdminPermission))
+        {
+            return false;
+        }
+        
+        AdminPermission a = (AdminPermission) obj;
+
+        return (action_mask == a.action_mask) &&
+        		(wildcard == a.wildcard) &&
+        		(bundle == null ? a.bundle == null : (a.bundle == null ? false : bundle.getBundleId() == a.bundle.getBundleId())) &&
+				(filter == null ? a.filter == null : filter.equals(a.filter));
+    }
+
+    /**
+     * Returns the hash code value for this object.
+     *
+     * @return Hash code value for this object.
+     */
+	public int hashCode() {
+		return getName().hashCode() ^ getActions().hashCode();
+	}
+
+	private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {
+		// Write out the actions. The superclass takes care of the name
+		// call getActions to make sure actions field is initialized
+		if (actions == null)
+			getActions();
+		if (filter == null && !wildcard)
+			throw new UnsupportedOperationException("cannot serialize");
+		s.defaultWriteObject();
+	}
+
+	/**
+	 * readObject is called to restore the state of this permission from a
+	 * stream.
+	 */
+	private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+		// Read in the action, then initialize the rest
+		s.defaultReadObject();
+		action_mask = getMask(actions);
+	}
+
+    /**
+     * Returns a new <code>PermissionCollection</code> object suitable for storing
+     * <code>AdminPermission</code>s.
+     * 
+     * @return A new <code>PermissionCollection</code> object.
+     */
+    public PermissionCollection newPermissionCollection()
+    {
+        return(new AdminPermissionCollection());
+    }
+
+	/**
+	 * Stores a collection of <code>AdminPermission</code>s.
+	 */
+	private final class AdminPermissionCollection extends PermissionCollection
+	{
+		private static final long serialVersionUID = 3906372644575328048L;
+		/**
+	     * Collection of permissions.
+	     *
+	     * @serial
+	     */
+		private Hashtable permissions;
+	
+	    /**
+	     * Create an empty AdminPermissions object.
+	     *
+	     */
+	
+	    public AdminPermissionCollection()
+	    {
+	        permissions = new Hashtable();        
+	    }
+
+	    /**
+	     * Adds a permission to the <code>AdminPermission</code> objects. The key for 
+	     * the hashtable is the name
+	     *
+	     * @param permission The <code>AdminPermission</code> object to add.
+	     *
+	     * @exception IllegalArgumentException If the permission is not an
+	     * <code>AdminPermission</code> instance.
+	     *
+	     * @exception SecurityException If this <code>AdminPermissionCollection</code>
+	     * object has been marked read-only.
+	     */
+	    public void add(Permission permission)
+	    {
+	        if (! (permission instanceof AdminPermission))
+	            throw new IllegalArgumentException("invalid permission: "+
+	                                               permission);
+	        if (isReadOnly())
+	            throw new SecurityException("attempt to add a Permission to a " +
+	                                        "readonly AdminCollection");
+	        AdminPermission ap = (AdminPermission) permission;
+	    	AdminPermission existing = (AdminPermission) permissions.get(ap.getName());
+	    	if (existing != null){
+	    		int oldMask = existing.action_mask;
+	    		int newMask = ap.action_mask;
+	        
+	    		if (oldMask != newMask) {
+	    			permissions.put(existing.getName(),
+	    					new AdminPermission(existing.getName(), oldMask | newMask));
+	    		}
+	    	} else {
+	    		permissions.put(ap.getName(), ap);
+	    	}
+	    }
+	
+	
+	    /**
+	     * Determines if the specified permissions implies the permissions
+	     * expressed in <code>permission</code>.
+	     *
+	     * @param permission The Permission object to compare with the <code>AdminPermission</code>
+	     *  objects in this collection.
+	     *
+	     * @return <code>true</code> if <code>permission</code> is implied by an 
+	     * <code>AdminPermission</code> in this collection, <code>false</code> otherwise.
+	     */
+	    public boolean implies(Permission permission)
+	    {
+	        if (!(permission instanceof AdminPermission))
+	            return(false);
+	
+	        AdminPermission target = (AdminPermission) permission;
+	        
+	        //just iterate one by one
+	        Iterator permItr = permissions.values().iterator();
+	        
+	        while(permItr.hasNext())
+	        	if (((AdminPermission)permItr.next()).implies(target))
+	        		return true;
+	        return false;
+	    }
+	 
+	
+	    /**
+	     * Returns an enumeration of all <code>AdminPermission</code> objects in the
+	     * container.
+	     *
+	     * @return Enumeration of all <code>AdminPermission</code> objects.
+	     */
+	
+	    public Enumeration elements()
+	    {
+	        return(Collections.enumeration(permissions.values()));
+	    }
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/AllServiceListener.java b/src/org/osgi/framework/AllServiceListener.java
new file mode 100644
index 0000000..6de64bc
--- /dev/null
+++ b/src/org/osgi/framework/AllServiceListener.java
@@ -0,0 +1,48 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/AllServiceListener.java,v 1.5 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * A <code>ServiceEvent</code> listener.
+ *
+ * <p><code>AllServiceListener</code> is a listener interface that may be implemented by a bundle
+ * developer.
+ * <p>An <code>AllServiceListener</code> object is registered with the Framework using the
+ * <code>BundleContext.addServiceListener</code> method.
+ * <code>AllServiceListener</code> objects are called with a <code>ServiceEvent</code> object when
+ * a service is registered, modified, or is in the process of unregistering.
+ *
+ * <p><code>ServiceEvent</code> object delivery to <code>AllServiceListener</code> objects is filtered by the
+ * filter specified when the listener was registered. If the Java Runtime Environment
+ * supports permissions, then additional filtering is done.
+ * <code>ServiceEvent</code> objects are only delivered to the listener if the bundle which defines
+ * the listener object's class has the appropriate <code>ServicePermission</code> to get the service
+ * using at least one of the named classes the service was registered under.
+ * 
+ * <p>
+ * Unlike normal <code>ServiceListener</code> objects,
+ * <code>AllServiceListener</code> objects receive all ServiceEvent objects regardless of the
+ * whether the package source of the listening bundle is equal to the package source of
+ * the bundle that registered the service. This means that the listener may not be able to
+ * cast the service object to any of its corresponding service interfaces if the service
+ * object is retrieved.
+ * 
+ * @version $Revision: 1.5 $
+ * @see ServiceEvent
+ * @see ServicePermission
+ */
+
+public abstract interface AllServiceListener extends ServiceListener
+{
+	//This is a marker interface
+}
+
+
diff --git a/src/org/osgi/framework/Bundle.java b/src/org/osgi/framework/Bundle.java
new file mode 100644
index 0000000..de19d9f
--- /dev/null
+++ b/src/org/osgi/framework/Bundle.java
@@ -0,0 +1,932 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/Bundle.java,v 1.27 2005/06/21 16:37:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+/**
+ * An installed bundle in the Framework.
+ * 
+ * <p>
+ * A <code>Bundle</code> object is the access point to define the lifecycle of
+ * an installed bundle. Each bundle installed in the OSGi environment must have
+ * an associated <code>Bundle</code> object.
+ * 
+ * <p>
+ * A bundle must have a unique identity, a <code>long</code>, chosen by the
+ * Framework. This identity must not change during the lifecycle of a bundle,
+ * even when the bundle is updated. Uninstalling and then reinstalling the
+ * bundle must create a new unique identity.
+ * 
+ * <p>
+ * A bundle can be in one of six states:
+ * <ul>
+ * <li>{@link #UNINSTALLED}
+ * <li>{@link #INSTALLED}
+ * <li>{@link #RESOLVED}
+ * <li>{@link #STARTING}
+ * <li>{@link #STOPPING}
+ * <li>{@link #ACTIVE}
+ * </ul>
+ * <p>
+ * Values assigned to these states have no specified ordering; they represent
+ * bit values that may be ORed together to determine if a bundle is in one of
+ * the valid states.
+ * 
+ * <p>
+ * A bundle should only execute code when its state is one of
+ * <code>STARTING</code>,<code>ACTIVE</code>, or <code>STOPPING</code>.
+ * An <code>UNINSTALLED</code> bundle can not be set to another state; it is a
+ * zombie and can only be reached because references are kept somewhere.
+ * 
+ * <p>
+ * The Framework is the only entity that is allowed to create
+ * <code>Bundle</code> objects, and these objects are only valid within the
+ * Framework that created them.
+ * 
+ * @version $Revision: 1.27 $
+ */
+public abstract interface Bundle {
+	/**
+	 * This bundle is uninstalled and may not be used.
+	 * 
+	 * <p>
+	 * The <code>UNINSTALLED</code> state is only visible after a bundle is
+	 * uninstalled; the bundle is in an unusable state but references to the
+	 * <code>Bundle</code> object may still be available and used for
+	 * introspection.
+	 * <p>
+	 * The value of <code>UNINSTALLED</code> is 0x00000001.
+	 */
+	public static final int	UNINSTALLED	= 0x00000001;
+
+	/**
+	 * This bundle is installed but not yet resolved.
+	 * 
+	 * <p>
+	 * A bundle is in the <code>INSTALLED</code> state when it has been
+	 * installed in the Framework but cannot run.
+	 * <p>
+	 * This state is visible if the bundle's code dependencies are not resolved.
+	 * The Framework may attempt to resolve an <code>INSTALLED</code> bundle's
+	 * code dependencies and move the bundle to the <code>RESOLVED</code>
+	 * state.
+	 * <p>
+	 * The value of <code>INSTALLED</code> is 0x00000002.
+	 */
+	public static final int	INSTALLED	= 0x00000002;
+
+	/**
+	 * This bundle is resolved and is able to be started.
+	 * 
+	 * <p>
+	 * A bundle is in the <code>RESOLVED</code> state when the Framework has
+	 * successfully resolved the bundle's dependencies. These dependencies
+	 * include:
+	 * <ul>
+	 * <li>The bundle's class path from its {@link Constants#BUNDLE_CLASSPATH}
+	 * Manifest header.
+	 * <li>The bundle's package dependencies from its
+	 * {@link Constants#EXPORT_PACKAGE}and {@link Constants#IMPORT_PACKAGE}
+	 * Manifest headers.
+	 * <li>The bundle's required bundle dependencies from its
+	 * {@link Constants#REQUIRE_BUNDLE}Manifest header.
+	 * <li>A fragment bundle's host dependency from its
+	 * {@link Constants#FRAGMENT_HOST}Manifest header.
+	 * </ul>
+	 * <p>
+	 * Note that the bundle is not active yet. A bundle must be put in the
+	 * <code>RESOLVED</code> state before it can be started. The Framework may
+	 * attempt to resolve a bundle at any time. 
+	 * <p>
+	 * The value of <code>RESOLVED</code> is 0x00000004.
+	 */
+	public static final int	RESOLVED	= 0x00000004;
+
+	/**
+	 * This bundle is in the process of starting.
+	 * 
+	 * <p>
+	 * A bundle is in the <code>STARTING</code> state when the {@link #start}
+	 * method is active. A bundle must be in this state when the bundle's
+	 * {@link BundleActivator#start}is called. If this method completes without
+	 * exception, then the bundle has successfully started and must move to the
+	 * <code>ACTIVE</code> state.
+	 * <p>
+	 * The value of <code>STARTING</code> is 0x00000008.
+	 */
+	public static final int	STARTING	= 0x00000008;
+
+	/**
+	 * This bundle is in the process of stopping.
+	 * 
+	 * <p>
+	 * A bundle is in the <code>STOPPING</code> state when the {@link #stop}
+	 * method is active. A bundle must be in this state when the bundle's
+	 * {@link BundleActivator#stop}method is called. When this method completes
+	 * the bundle is stopped and must move to the <code>RESOLVED</code> state.
+	 * <p>
+	 * The value of <code>STOPPING</code> is 0x00000010.
+	 */
+	public static final int	STOPPING	= 0x00000010;
+
+	/**
+	 * This bundle is now running.
+	 * 
+	 * <p>
+	 * A bundle is in the <code>ACTIVE</code> state when it has been
+	 * successfully started.
+	 * <p>
+	 * The value of <code>ACTIVE</code> is 0x00000020.
+	 */
+	public static final int	ACTIVE		= 0x00000020;
+
+	/**
+	 * Returns this bundle's current state.
+	 * 
+	 * <p>
+	 * A bundle can be in only one state at any time.
+	 * 
+	 * @return An element of <code>UNINSTALLED</code>,<code>INSTALLED</code>,
+	 *         <code>RESOLVED</code>,<code>STARTING</code>,
+	 *         <code>STOPPING</code>,<code>ACTIVE</code>.
+	 */
+	public abstract int getState();
+
+	/**
+	 * Starts this bundle.
+	 * 
+	 * <p>
+	 * If the Framework implements the optional Start Level service and the
+	 * current start level is less than this bundle's start level, then the
+	 * Framework must persistently mark this bundle as started and delay the
+	 * starting of this bundle until the Framework's current start level becomes
+	 * equal or more than the bundle's start level.
+	 * <p>
+	 * Otherwise, the following steps are required to start a bundle:
+	 * <ol>
+	 * <li>If this bundle's state is <code>UNINSTALLED</code> then an
+	 * <code>IllegalStateException</code> is thrown.
+	 * 
+	 * <li>If this bundle's state is <code>STARTING</code> or
+	 * <code>STOPPING</code> then this method must wait for this bundle to
+	 * change state before continuing. If this does not occur in a reasonable
+	 * time, a <code>BundleException</code> is thrown to indicate this bundle
+	 * was unable to be started.
+	 * 
+	 * <li>If this bundle's state is <code>ACTIVE</code> then this method
+	 * returns immediately.
+	 * 
+	 * <li>Persistently record that this bundle has been started. When the
+	 * Framework is restarted, this bundle must be automatically started.
+	 * 
+	 * <li>If this bundle's state is not <code>RESOLVED</code>, an attempt
+	 * is made to resolve this bundle's package dependencies. If the Framework
+	 * cannot resolve this bundle, a <code>BundleException</code> is thrown.
+	 * 
+	 * <li>This bundle's state is set to <code>STARTING</code>.
+	 * 
+	 * <li>The {@link BundleActivator#start}method of this bundle's
+	 * <code>BundleActivator</code>, if one is specified, is called. If the
+	 * <code>BundleActivator</code> is invalid or throws an exception, this
+	 * bundle's state is set back to <code>RESOLVED</code>.<br>
+	 * Any services registered by the bundle must be unregistered. <br>
+	 * Any services used by the bundle must be released. <br>
+	 * Any listeners registered by the bundle must be removed. <br>
+	 * A <code>BundleException</code> is then thrown.
+	 * 
+	 * <li>If this bundle's state is <code>UNINSTALLED</code>, because the
+	 * bundle was uninstalled while the <code>BundleActivator.start</code>
+	 * method was running, a <code>BundleException</code> is thrown.
+	 * 
+	 * <li>This bundle's state is set to <code>ACTIVE</code>.
+	 * 
+	 * <li>A bundle event of type {@link BundleEvent#STARTED}is broadcast.
+	 * </ol>
+	 * 
+	 * <b>Preconditions </b>
+	 * <ul>
+	 * <li><code>getState()</code> in {<code>INSTALLED</code>}, {
+	 * <code>RESOLVED</code>}.
+	 * </ul>
+	 * <b>Postconditions, no exceptions thrown </b>
+	 * <ul>
+	 * <li>Bundle persistent state is marked as active.
+	 * <li><code>getState()</code> in {<code>ACTIVE</code>}.
+	 * <li><code>BundleActivator.start()</code> has been called and did not
+	 * throw an exception.
+	 * </ul>
+	 * <b>Postconditions, when an exception is thrown </b>
+	 * <ul>
+	 * <li>Depending on when the exception occurred, bundle persistent state is
+	 * marked as active.
+	 * <li><code>getState()</code> not in {<code>STARTING</code>}, {
+	 * <code>ACTIVE</code>}.
+	 * </ul>
+	 * 
+	 * @exception BundleException If this bundle could not be started. This
+	 *            could be because a code dependency could not be resolved or
+	 *            the specified <code>BundleActivator</code> could not be
+	 *            loaded or threw an exception.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled or this bundle tries to change its own state.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission[bundle, EXECUTE]</code>,
+	 *            and the Java Runtime Environment supports permissions.
+	 */
+	public abstract void start() throws BundleException;
+
+	/**
+	 * Stops this bundle.
+	 * 
+	 * <p>
+	 * The following steps are required to stop a bundle:
+	 * <ol>
+	 * <li>If this bundle's state is <code>UNINSTALLED</code> then an
+	 * <code>IllegalStateException</code> is thrown.
+	 * 
+	 * <li>If this bundle's state is <code>STARTING</code> or
+	 * <code>STOPPING</code> then this method must wait for this bundle to
+	 * change state before continuing. If this does not occur in a reasonable
+	 * time, a <code>BundleException</code> is thrown to indicate this bundle
+	 * was unable to be stopped.
+	 * 
+	 * <li>Persistently record that this bundle has been stopped. When the
+	 * Framework is restarted, this bundle must not be automatically started.
+	 * 
+	 * <li>If this bundle's state is not <code>ACTIVE</code> then this method
+	 * returns immediately.
+	 * 
+	 * <li>This bundle's state is set to <code>STOPPING</code>.
+	 * 
+	 * <li>The {@link BundleActivator#stop}method of this bundle's
+	 * <code>BundleActivator</code>, if one is specified, is called. If that
+	 * method throws an exception, this method must continue to stop this
+	 * bundle. A <code>BundleException</code> must be thrown after completion
+	 * of the remaining steps.
+	 * 
+	 * <li>Any services registered by this bundle must be unregistered.
+	 * <li>Any services used by this bundle must be released.
+	 * <li>Any listeners registered by this bundle must be removed.
+	 * 
+	 * <li>If this bundle's state is <code>UNINSTALLED</code>, because the
+	 * bundle was uninstalled while the <code>BundleActivator.stop</code>
+	 * method was running, a <code>BundleException</code> must be thrown.
+	 * 
+	 * <li>This bundle's state is set to <code>RESOLVED</code>.
+	 * 
+	 * <li>A bundle event of type {@link BundleEvent#STOPPED}is broadcast.
+	 * </ol>
+	 * 
+	 * <b>Preconditions </b>
+	 * <ul>
+	 * <li><code>getState()</code> in {<code>ACTIVE</code>}.
+	 * </ul>
+	 * <b>Postconditions, no exceptions thrown </b>
+	 * <ul>
+	 * <li>Bundle persistent state is marked as stopped.
+	 * <li><code>getState()</code> not in {<code>ACTIVE</code>,
+	 * <code>STOPPING</code>}.
+	 * <li><code>BundleActivator.stop</code> has been called and did not
+	 * throw an exception.
+	 * </ul>
+	 * <b>Postconditions, when an exception is thrown </b>
+	 * <ul>
+	 * <li>Bundle persistent state is marked as stopped.
+	 * </ul>
+	 * 
+	 * @exception BundleException If this bundle's <code>BundleActivator</code>
+	 *            could not be loaded or threw an exception.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled or this bundle tries to change its own state.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission[bundle, EXECUTE]</code>,
+	 *            and the Java Runtime Environment supports permissions.
+	 */
+	public abstract void stop() throws BundleException;
+
+	/**
+	 * Updates this bundle.
+	 * 
+	 * <p>
+	 * If this bundle's state is <code>ACTIVE</code>, it must be stopped
+	 * before the update and started after the update successfully completes.
+	 * 
+	 * <p>
+	 * If the bundle being updated has exported any packages, these packages
+	 * must not be updated. Instead, the previous package version must remain
+	 * exported until the <code>PackageAdmin.refreshPackages</code> method has
+	 * been has been called or the Framework is relaunched.
+	 * 
+	 * <p>
+	 * The following steps are required to update a bundle:
+	 * <ol>
+	 * <li>If this bundle's state is <code>UNINSTALLED</code> then an
+	 * <code>IllegalStateException</code> is thrown.
+	 * 
+	 * <li>If this bundle's state is <code>ACTIVE</code>,
+	 * <code>STARTING</code> or <code>STOPPING</code>, the bundle is
+	 * stopped as described in the <code>Bundle.stop</code> method. If
+	 * <code>Bundle.stop</code> throws an exception, the exception is rethrown
+	 * terminating the update.
+	 * 
+	 * <li>The download location of the new version of this bundle is
+	 * determined from either the bundle's
+	 * {@link Constants#BUNDLE_UPDATELOCATION}Manifest header (if available) or
+	 * the bundle's original location.
+	 * 
+	 * <li>The location is interpreted in an implementation dependent manner,
+	 * typically as a URL, and the new version of this bundle is obtained from
+	 * this location.
+	 * 
+	 * <li>The new version of this bundle is installed. If the Framework is
+	 * unable to install the new version of this bundle, the original version of
+	 * this bundle must be restored and a <code>BundleException</code> must be
+	 * thrown after completion of the remaining steps.
+	 * 
+	 * <li>If the bundle has declared an Bundle-RequiredExecutionEnvironment
+	 * header, then the listed execution environments must be verified against
+	 * the installed execution environments. If they do not all match, the
+	 * original version of this bundle must be restored and a
+	 * <code>BundleException</code> must be thrown after completion of the
+	 * remaining steps.
+	 * 
+	 * <li>This bundle's state is set to <code>INSTALLED</code>.
+	 * 
+	 * <li>If the new version of this bundle was successfully installed, a
+	 * bundle event of type {@link BundleEvent#UPDATED}is broadcast.
+	 * 
+	 * <li>If this bundle's state was originally <code>ACTIVE</code>, the
+	 * updated bundle is started as described in the <code>Bundle.start</code>
+	 * method. If <code>Bundle.start</code> throws an exception, a Framework
+	 * event of type {@link FrameworkEvent#ERROR}is broadcast containing the
+	 * exception.
+	 * </ol>
+	 * 
+	 * <b>Preconditions </b>
+	 * <ul>
+	 * <li><code>getState()</code> not in {<code>UNINSTALLED</code>}.
+	 * </ul>
+	 * <b>Postconditions, no exceptions thrown </b>
+	 * <ul>
+	 * <li><code>getState()</code> in {<code>INSTALLED</code>,
+	 * <code>RESOLVED</code>,<code>ACTIVE</code>}.
+	 * <li>This bundle has been updated.
+	 * </ul>
+	 * <b>Postconditions, when an exception is thrown </b>
+	 * <ul>
+	 * <li><code>getState()</code> in {<code>INSTALLED</code>,
+	 * <code>RESOLVED</code>,<code>ACTIVE</code>}.
+	 * <li>Original bundle is still used; no update occurred.
+	 * </ul>
+	 * 
+	 * @exception BundleException If the update fails.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled or this bundle tries to change its own state.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission[bundle, LIFECYCLE]</code> for both
+	 *            the current bundle and the updated bundle,
+	 *            and the Java Runtime Environment supports permissions.
+	 * @see #stop()
+	 * @see #start()
+	 */
+	public abstract void update() throws BundleException;
+
+	/**
+	 * Updates this bundle from an <code>InputStream</code>.
+	 * 
+	 * <p>
+	 * This method performs all the steps listed in <code>Bundle.update()</code>,
+	 * except the bundle must be read from the supplied <code>InputStream</code>,
+	 * rather than a <code>URL</code>.
+	 * <p>
+	 * This method must always close the <code>InputStream</code> when it is
+	 * done, even if an exception is thrown.
+	 * 
+	 * @param in The <code>InputStream</code> from which to read the new
+	 *        bundle.
+	 * @exception BundleException If the provided stream cannot be read or the
+	 *            update fails.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled or this bundle tries to change its own state.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission[bundle, LIFECYCLE]</code> for both
+	 *            the current bundle and the updated bundle,
+	 *            and the Java Runtime Environment supports permissions.
+	 * @see #update()
+	 */
+	public abstract void update(InputStream in) throws BundleException;
+
+	/**
+	 * Uninstalls this bundle.
+	 * 
+	 * <p>
+	 * This method causes the Framework to notify other bundles that this bundle
+	 * is being uninstalled, and then puts this bundle into the
+	 * <code>UNINSTALLED</code> state. The Framework must remove any resources
+	 * related to this bundle that it is able to remove.
+	 * 
+	 * <p>
+	 * If this bundle has exported any packages, the Framework must continue to
+	 * make these packages available to their importing bundles until the
+	 * <code>PackageAdmin.refreshPackages</code> method has been called or the
+	 * Framework is relaunched.
+	 * 
+	 * <p>
+	 * The following steps are required to uninstall a bundle:
+	 * <ol>
+	 * <li>If this bundle's state is <code>UNINSTALLED</code> then an
+	 * <code>IllegalStateException</code> is thrown.
+	 * 
+	 * <li>If this bundle's state is <code>ACTIVE</code>,
+	 * <code>STARTING</code> or <code>STOPPING</code>, this bundle is
+	 * stopped as described in the <code>Bundle.stop</code> method. If
+	 * <code>Bundle.stop</code> throws an exception, a Framework event of type
+	 * {@link FrameworkEvent#ERROR}is broadcast containing the exception.
+	 * 
+	 * <li>This bundle's state is set to <code>UNINSTALLED</code>.
+	 * 
+	 * <li>A bundle event of type {@link BundleEvent#UNINSTALLED}is broadcast.
+	 * 
+	 * <li>This bundle and any persistent storage area provided for this bundle
+	 * by the Framework are removed.
+	 * </ol>
+	 * 
+	 * <b>Preconditions </b>
+	 * <ul>
+	 * <li><code>getState()</code> not in {<code>UNINSTALLED</code>}.
+	 * </ul>
+	 * <b>Postconditions, no exceptions thrown </b>
+	 * <ul>
+	 * <li><code>getState()</code> in {<code>UNINSTALLED</code>}.
+	 * <li>This bundle has been uninstalled.
+	 * </ul>
+	 * <b>Postconditions, when an exception is thrown </b>
+	 * <ul>
+	 * <li><code>getState()</code> not in {<code>UNINSTALLED</code>}.
+	 * <li>This Bundle has not been uninstalled.
+	 * </ul>
+	 * 
+	 * @exception BundleException If the uninstall failed. This can occur if
+	 *            another thread is attempting to change the bundle's state and
+	 *            does not complete in a timely manner.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled or this bundle tries to change its own state.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission[bundle, LIFECYCLE]</code>,
+	 *            and the Java Runtime Environment supports permissions.
+	 * @see #stop()
+	 */
+	public abstract void uninstall() throws BundleException;
+
+	/**
+	 * Returns this bundle's Manifest headers and values. This method returns
+	 * all the Manifest headers and values from the main section of the bundle's
+	 * Manifest file; that is, all lines prior to the first blank line.
+	 * 
+	 * <p>
+	 * Manifest header names are case-insensitive. The methods of the returned
+	 * <code>Dictionary</code> object must operate on header names in a
+	 * case-insensitive manner.
+	 * 
+	 * If a Manifest header value starts with &quot;%&quot;, it must be
+	 * localized according to the default locale.
+	 * 
+	 * <p>
+	 * For example, the following Manifest headers and values are included if
+	 * they are present in the Manifest file:
+	 * 
+	 * <pre>
+	 *        Bundle-Name
+	 *        Bundle-Vendor
+	 *        Bundle-Version
+	 *        Bundle-Description
+	 *        Bundle-DocURL
+	 *        Bundle-ContactAddress
+	 * </pre>
+	 * 
+	 * <p>
+	 * This method must continue to return Manifest header information while
+	 * this bundle is in the <code>UNINSTALLED</code> state.
+	 * 
+	 * @return A <code>Dictionary</code> object containing this bundle's
+	 *         Manifest headers and values.
+	 * 
+	 * @exception java.lang.SecurityException If the caller does not have the appropriate
+	 *            <code>AdminPermission[bundle, METADATA]</code>, and the
+	 *            Java Runtime Environment supports permissions.
+	 * 
+	 * @see Constants#BUNDLE_LOCALIZATION
+	 */
+	public abstract Dictionary getHeaders();
+
+	/**
+	 * Returns this bundle's identifier. The bundle is assigned a unique
+	 * identifier by the Framework when it is installed in the OSGi environment.
+	 * 
+	 * <p>
+	 * A bundle's unique identifier has the following attributes:
+	 * <ul>
+	 * <li>Is unique and persistent.
+	 * <li>Is a <code>long</code>.
+	 * <li>Its value is not reused for another bundle, even after the bundle is
+	 * uninstalled.
+	 * <li>Does not change while the bundle remains installed.
+	 * <li>Does not change when the bundle is updated.
+	 * </ul>
+	 * 
+	 * <p>
+	 * This method must continue to return this bundle's unique identifier while
+	 * this bundle is in the <code>UNINSTALLED</code> state.
+	 * 
+	 * @return The unique identifier of this bundle.
+	 */
+	public abstract long getBundleId();
+
+	/**
+	 * Returns this bundle's location identifier.
+	 * 
+	 * <p>
+	 * The bundle location identifier is the location passed to
+	 * <code>BundleContext.installBundle</code> when a bundle is installed.
+	 * The bundle location identifier does not change while the bundle remains
+	 * installed, even if the bundle is updated.
+	 * 
+	 * <p>
+	 * This method must continue to return this bundle's location identifier
+	 * while this bundle is in the <code>UNINSTALLED</code> state.
+	 * 
+	 * @return The string representation of this bundle's location identifier.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission[bundle, METADATA]</code>,
+	 *            and the Java Runtime Environment supports permissions.
+	 */
+	public abstract String getLocation();
+
+	/**
+	 * Returns this bundle's <code>ServiceReference</code> list for all
+	 * services it has registered or <code>null</code> if this bundle has no
+	 * registered services.
+	 * 
+	 * <p>
+	 * If the Java runtime supports permissions, a <code>ServiceReference</code>
+	 * object to a service is included in the returned list only if the caller
+	 * has the <code>ServicePermission</code> to get the service using at
+	 * least one of the named classes the service was registered under.
+	 * 
+	 * <p>
+	 * The list is valid at the time of the call to this method, however, as the
+	 * Framework is a very dynamic environment, services can be modified or
+	 * unregistered at anytime.
+	 * 
+	 * @return An array of <code>ServiceReference</code> objects or
+	 *         <code>null</code>.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 * @see ServiceRegistration
+	 * @see ServiceReference
+	 * @see ServicePermission
+	 */
+	public abstract ServiceReference[] getRegisteredServices();
+
+	/**
+	 * Returns this bundle's <code>ServiceReference</code> list for all
+	 * services it is using or returns <code>null</code> if this bundle is not
+	 * using any services. A bundle is considered to be using a service if its
+	 * use count for that service is greater than zero.
+	 * 
+	 * <p>
+	 * If the Java Runtime Environment supports permissions, a
+	 * <code>ServiceReference</code> object to a service is included in the
+	 * returned list only if the caller has the <code>ServicePermission</code>
+	 * to get the service using at least one of the named classes the service
+	 * was registered under.
+	 * <p>
+	 * The list is valid at the time of the call to this method, however, as the
+	 * Framework is a very dynamic environment, services can be modified or
+	 * unregistered at anytime.
+	 * 
+	 * @return An array of <code>ServiceReference</code> objects or
+	 *         <code>null</code>.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 * @see ServiceReference
+	 * @see ServicePermission
+	 */
+	public abstract ServiceReference[] getServicesInUse();
+
+	/**
+	 * Determines if this bundle has the specified permissions.
+	 * 
+	 * <p>
+	 * If the Java Runtime Environment does not support permissions, this method
+	 * always returns <code>true</code>.
+	 * <p>
+	 * <code>permission</code> is of type <code>Object</code> to avoid
+	 * referencing the <code>java.security.Permission</code> class directly.
+	 * This is to allow the Framework to be implemented in Java environments
+	 * which do not support permissions.
+	 * 
+	 * <p>
+	 * If the Java Runtime Environment does support permissions, this bundle and
+	 * all its resources including embedded JAR files, belong to the same
+	 * <code>java.security.ProtectionDomain</code>; that is, they must share
+	 * the same set of permissions.
+	 * 
+	 * @param permission The permission to verify.
+	 * 
+	 * @return <code>true</code> if this bundle has the specified permission
+	 *         or the permissions possessed by this bundle imply the specified
+	 *         permission; <code>false</code> if this bundle does not have the
+	 *         specified permission or <code>permission</code> is not an
+	 *         <code>instanceof</code> <code>java.security.Permission</code>.
+	 * 
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 */
+	public abstract boolean hasPermission(Object permission);
+
+	/**
+	 * Find the specified resource from this bundle.
+	 * 
+	 * This bundle's class loader is called to search for the named resource. If
+	 * this bundle's state is <code>INSTALLED</code>, then only this bundle
+	 * must be searched for the specified resource. Imported packages cannot be
+	 * searched when a bundle has not been resolved. If this bundle is a
+	 * fragment bundle then <code>null</code> is returned.
+	 * 
+	 * @param name The name of the resource. See
+	 *        <code>java.lang.ClassLoader.getResource</code> for a description
+	 *        of the format of a resource name.
+	 * @return a URL to the named resource, or <code>null</code> if the
+	 *         resource could not be found or if this bundle is a fragment
+	 *         bundle or if the caller does not have the appropriate
+	 *         <code>AdminPermission[bundle, RESOURCE]</code>, and the Java
+	 *         Runtime Environment supports permissions.
+	 * 
+	 * @since 1.1
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 */
+	public abstract URL getResource(String name);
+
+	/**
+	 * Returns this bundle's Manifest headers and values localized to the
+	 * specifed locale.
+	 * 
+	 * <p>
+	 * This method performs the same function as
+	 * <code>Bundle.getHeaders()</code> except the manifest header values are
+	 * localized to the specified locale.
+	 * 
+	 * If a Manifest header value starts with &quot;%&quot;, it must be
+	 * localized according to the specified locale. If the specified locale
+	 * cannot be found, then the header values must be returned using the
+	 * default locale.
+	 * 
+	 * If <code>null</code> is specified as the locale string, the header
+	 * values must be localized using the default locale. If the empty string
+	 * (&quot;&quot;) is specified as the locale string, the header values must
+	 * not be localized and the raw (unlocalized) header values, including any
+	 * leading &quot;%&quot;, must be returned.
+	 * 
+	 * <p>
+	 * This method must continue to return Manifest header information while
+	 * this bundle is in the <code>UNINSTALLED</code> state, however the
+	 * header values must only be available in the raw and default locale
+	 * values.
+	 * 
+	 * @param locale The locale name into which the header values are to be
+	 *        localized. If the specified locale is <code>null</code> then the
+	 *        locale returned by <code>java.util.Locale.getDefault</code> is
+	 *        used. If the specified locale is the empty string, this method
+	 *        will return the raw (unlocalized) manifest headers including any
+	 *        leading &quot;%&quot;.
+	 * @return A <code>Dictionary</code> object containing this bundle's
+	 *         Manifest headers and values.
+	 * 
+	 * @exception java.lang.SecurityException If the caller does not have the appropriate
+	 *            <code>AdminPermission[bundle, METADATA]</code>, and the
+	 *            Java Runtime Environment supports permissions.
+	 * 
+	 * @see #getHeaders()
+	 * @see Constants#BUNDLE_LOCALIZATION
+	 * @since 1.3
+	 */
+	public Dictionary getHeaders(String locale);
+
+	/**
+	 * Returns the symbolic name of this bundle as specified by its
+	 * <code>Bundle-SymbolicName</code> manifest header. The name must be
+	 * unique, it is recommended to use a reverse domain name naming convention
+	 * like that used for java packages. If the bundle does not have a specified
+	 * symbolic name then <code>null</code> is returned.
+	 * 
+	 * <p>
+	 * This method must continue to return this bundle's symbolic name while
+	 * this bundle is in the <code>UNINSTALLED</code> state.
+	 * 
+	 * @return The symbolic name of this bundle.
+	 * @since 1.3
+	 */
+	public String getSymbolicName();
+
+	/**
+	 * 
+	 * Loads the specified class using this bundle's classloader.
+	 * 
+	 * <p>
+	 * If the bundle is a fragment bundle then this method must throw a
+	 * <code>ClassNotFoundException</code>.
+	 * 
+	 * <p>
+	 * If this bundle's state is <code>INSTALLED</code>, this method must
+	 * attempt to resolve the bundle before attempting to load the class.
+	 * 
+	 * <p>
+	 * If the bundle cannot be resolved, a Framework event of type
+	 * {@link FrameworkEvent#ERROR}is broadcast containing a
+	 * <code>BundleException</code> with details of the reason the bundle
+	 * could not be resolved. This method must then throw a
+	 * <code>ClassNotFoundException</code>.
+	 * 
+	 * <p>
+	 * If this bundle's state is <code>UNINSTALLED</code>, then an
+	 * <code>IllegalStateException</code> is thrown.
+	 * 
+	 * @param name The name of the class to load.
+	 * @return The Class object for the requested class.
+	 * @exception java.lang.ClassNotFoundException If no such class can be found
+	 *            or if this bundle is a fragment bundle or if the caller does
+	 *            not have the appropriate <code>AdminPermission[bundle, CLASS]</code>,
+	 *            and the Java Runtime Environment supports permissions.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 * @since 1.3
+	 */
+	public Class loadClass(String name) throws ClassNotFoundException;
+
+	/**
+	 * Find the specified resources from this bundle.
+	 * 
+	 * This bundle's class loader is called to search for the named resource. If
+	 * this bundle's state is <code>INSTALLED</code>, then only this bundle
+	 * must be searched for the specified resource. Imported packages cannot be
+	 * searched when a bundle has not been resolved. If this bundle is a
+	 * fragment bundle then <code>null</code> is returned.
+	 * 
+	 * @param name The name of the resource. See
+	 *        <code>java.lang.ClassLoader.getResources</code> for a
+	 *        description of the format of a resource name.
+	 * @return an Enumeration of URLs to the named resources, or
+	 *         <code>null</code> if the resource could not be found or if this
+	 *         bundle is a fragment bundle or if the caller does not have the appropriate
+	 *         <code>AdminPermission[bundle, RESOURCE]</code>, and the Java
+	 *         Runtime Environment supports permissions.
+	 * 
+	 * @since 1.3
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 * @throws java.io.IOException If there is an I/O error.
+	 */
+	public Enumeration getResources(String name) throws IOException;
+
+	/**
+	 * Returns an Enumeration of all the paths (<code>String</code>) objects) to 
+	 * entries within the bundle whose longest sub-path matches the supplied path 
+	 * argument. The bundle's classloader is not used to search for entries. Only 
+	 * the contents of the bundle is searched.  A specified path of &quot;/&quot; 
+	 * indicates the root of the bundle.
+	 * 
+	 * <p>
+	 * Returned paths indicating subdirectory paths end with a &quot;/&quot;.
+	 * The returned paths are all relative to the root of the bundle.
+	 * 
+	 * <p>
+	 * This method returns <code>null</code> if no entries could be found that
+	 * match the specified path or if the caller does not have the appropriate
+	 * <code>AdminPermission[bundle, RESOURCE]</code> and the Java 
+	 * Runtime Environment supports permissions.
+	 * 
+	 * @param path the path name to get the entry path for.
+	 * @return An Enumeration of the entry paths (<code>String</code> objects) 
+	 *         or <code>null</code> if an entry could not be found or if the 
+	 *         caller does not have the appropriate <code>AdminPermission[bundle, RESOURCE]</code> 
+	 *         and the Java Runtime Environment supports permissions.
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 * @since 1.3
+	 */
+	public Enumeration getEntryPaths(String path);
+
+	/**
+	 * Returns a URL to the specified entry in this bundle. The bundle's
+	 * classloader is not used to search for the specified entry. Only the
+	 * contents of the bundle is searched for the specified entry. A specified
+	 * path of &quot;/&quot; indicates the root of the bundle.
+	 * 
+	 * <p>
+	 * This method returns a URL to the specified entry, or <code>null</code>
+	 * if the entry could not be found or if the caller does not have the appropriate
+	 * <code>AdminPermission[bundle, RESOURCE]</code> and the Java Runtime
+	 * Environment supports permissions.
+	 * 
+	 * @param name The name of the entry. See
+	 *        <code>java.lang.ClassLoader.getResource</code> for a description
+	 *        of the format of a resource name.
+	 * @return A URL to the specified entry, or <code>null</code> if the entry
+	 *         could not be found or if the caller does not have the appropriate
+	 *         <code>AdminPermission[bundle, RESOURCE]</code> and the Java
+	 *         Runtime Environment supports permissions.
+	 * 
+	 * @exception java.lang.IllegalStateException If this bundle has been
+	 *            uninstalled.
+	 * @since 1.3
+	 */
+	public URL getEntry(String name);
+
+	/**
+	 * Returns the time when this bundle was last modified. A bundle is
+	 * considered to be modified when it is installed, updated or uninstalled.
+	 * 
+	 * <p>
+	 * The time value is the number of milliseconds since January 1, 1970,
+	 * 00:00:00 GMT.
+	 * 
+	 * @return The time when this bundle was last modified.
+	 * @since 1.3
+	 */
+	public long getLastModified();
+
+	/**
+	 * Returns entries in this bundle and its attached fragments. The bundle's
+	 * classloader is not used to search for entries. Only the contents of the
+	 * bundle and its attached fragments are searched for the specified
+	 * entries.
+	 * 
+	 * If this bundle's state is <code>INSTALLED</code>, this method must
+	 * attempt to resolve the bundle before attempting to find entries.<p>
+	 * 
+	 * This method is intended to be used to obtain configuration, setup,
+	 * localization and other information from this bundle. This method takes
+	 * into account that the &quot;contents&quot; of this bundle can be extended
+	 * with fragments. This &quot;bundle space&quot; is not a namespace with
+	 * unique members; the same entry name can be present multiple times. This
+	 * method therefore returns an enumeration of URL objects. These URLs can
+	 * come from different JARs but have the same path name. This method can
+	 * either return only entries in the specified path or recurse into
+	 * subdirectories returning entries in the directory tree beginning at the
+	 * specified path. Fragments can be attached after this bundle is resolved,
+	 * possibly changing the set of URLs returned by this method. If this bundle
+	 * is not resolved, only the entries in the JAR file of this bundle are
+	 * returned.
+	 * <p>
+	 * Examples:
+	 * <pre>
+	 * // List all XML files in the OSGI-INF directory and below
+	 * Enumeration	e	= b.findEntries(&quot;OSGI-INF&quot;, &quot;*.xml&quot;, true);
+	 * 
+	 * // Find a specific localization file
+	 * Enumeration e = b.findEntries(&quot;OSGI-INF/l10n&quot;, 
+	 *    &quot;bundle_nl_DU.properties&quot;, false);
+	 * if (e.hasMoreElements())
+	 * 	return (URL) e.nextElement();
+	 * </pre>
+	 * 
+	 * @param path The path name in which to look. A specified path of
+	 *        &quot;/&quot; indicates the root of the bundle. Path is relative
+	 *        to the root of the bundle and must not be null.
+	 * @param filePattern The file name pattern for selecting entries in the
+	 *        specified path. The pattern is only matched against the last
+	 *        element of the entry path and it supports substring matching, as
+	 *        specified in the Filter specification, using the wildcard
+	 *        character (&quot;*&quot;). If null is specified, this is
+	 *        equivalent to &quot;*&quot; and matches all files.
+	 * @param recurse If <code>true</code>, recurse into subdirectories.
+	 *        Otherwise only return entries from the given directory.
+	 * @return An enumeration of URL objects for each matching entry, or
+	 *         <code>null</code> if an entry could not be found or if the
+	 *         caller does not have the appropriate
+	 *         <code>AdminPermission[bundle, RESOURCE]</code>, and the Java
+	 *         Runtime Environment supports permissions. The URLs are sorted
+	 *         such that entries from this bundle are returned first followed by
+	 *         the entries from attached fragments in ascending bundle id order.
+	 *         If this bundle is a fragment, then only matching entries in this
+	 *         fragment are returned.
+	 * @since 1.3
+	 */
+	public Enumeration findEntries(String path, String filePattern,
+			boolean recurse);
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/BundleActivator.java b/src/org/osgi/framework/BundleActivator.java
new file mode 100644
index 0000000..8be4194
--- /dev/null
+++ b/src/org/osgi/framework/BundleActivator.java
@@ -0,0 +1,82 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/BundleActivator.java,v 1.8 2005/06/21 16:22:12 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * Customizes the starting and stopping of a bundle.
+ * <p>
+ * <code>BundleActivator</code> is an interface that may be implemented when a
+ * bundle is started or stopped. The Framework can create instances of a
+ * bundle's <code>BundleActivator</code> as required. If an instance's
+ * <code>BundleActivator.start</code> method executes successfully, it is
+ * guaranteed that the same instance's <code>BundleActivator.stop</code> method
+ * will be called when the bundle is to be stopped.
+ * 
+ * <p>
+ * <code>BundleActivator</code> is specified through the <code>Bundle-Activator</code>
+ * Manifest header. A bundle can only specify a single <code>BundleActivator</code>
+ * in the Manifest file. Fragment bundles must not have a <code>BundleActivator</code>.
+ * The form of the Manifest header is:
+ * 
+ * <pre>
+ *  Bundle-Activator: &lt;i&gt;class-name&lt;/i&gt;
+ * </pre>
+ * 
+ * where <code>class-name</code> is a fully qualified Java classname.
+ * <p>
+ * The specified <code>BundleActivator</code> class must have a public constructor
+ * that takes no parameters so that a <code>BundleActivator</code> object can be
+ * created by <code>Class.newInstance()</code>.
+ * 
+ * @version $Revision: 1.8 $
+ */
+
+public abstract interface BundleActivator {
+	/**
+	 * Called when this bundle is started so the Framework can perform the
+	 * bundle-specific activities necessary to start this bundle. This method
+	 * can be used to register services or to allocate any resources that this
+	 * bundle needs.
+	 * 
+	 * <p>
+	 * This method must complete and return to its caller in a timely manner.
+	 * 
+	 * @param context The execution context of the bundle being started.
+	 * @exception java.lang.Exception If this method throws an exception, this
+	 *            bundle is marked as stopped and the Framework will remove this
+	 *            bundle's listeners, unregister all services registered by this
+	 *            bundle, and release all services used by this bundle.
+	 * @see Bundle#start
+	 */
+	public abstract void start(BundleContext context) throws Exception;
+
+	/**
+	 * Called when this bundle is stopped so the Framework can perform the
+	 * bundle-specific activities necessary to stop the bundle. In general, this
+	 * method should undo the work that the <code>BundleActivator.start</code>
+	 * method started. There should be no active threads that were started by
+	 * this bundle when this bundle returns. A stopped bundle must
+	 * not call any Framework objects.
+	 * 
+	 * <p>
+	 * This method must complete and return to its caller in a timely manner.
+	 * 
+	 * @param context The execution context of the bundle being stopped.
+	 * @exception java.lang.Exception If this method throws an exception, the
+	 *            bundle is still marked as stopped, and the Framework will
+	 *            remove the bundle's listeners, unregister all services
+	 *            registered by the bundle, and release all services used by the
+	 *            bundle.
+	 * @see Bundle#stop
+	 */
+	public abstract void stop(BundleContext context) throws Exception;
+}
+
diff --git a/src/org/osgi/framework/BundleContext.java b/src/org/osgi/framework/BundleContext.java
new file mode 100644
index 0000000..91fa559
--- /dev/null
+++ b/src/org/osgi/framework/BundleContext.java
@@ -0,0 +1,790 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/BundleContext.java,v 1.13 2005/06/21 16:22:12 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+/**
+ * A bundle's execution context within the Framework. The context is used to
+ * grant access to other methods so that this bundle can interact with the
+ * Framework.
+ * 
+ * <p>
+ * <code>BundleContext</code> methods allow a bundle to:
+ * <ul>
+ * <li>Subscribe to events published by the Framework.
+ * <li>Register service objects with the Framework service registry.
+ * <li>Retrieve <code>ServiceReferences</code> from the Framework service
+ * registry.
+ * <li>Get and release service objects for a referenced service.
+ * <li>Install new bundles in the Framework.
+ * <li>Get the list of bundles installed in the Framework.
+ * <li>Get the {@link Bundle} object for a bundle.
+ * <li>Create <code>File</code> objects for files in a persistent storage area
+ * provided for the bundle by the Framework.
+ * </ul>
+ * 
+ * <p>
+ * A <code>BundleContext</code> object will be created and provided to the bundle
+ * associated with this context when it is started using the
+ * {@link BundleActivator#start} method. The same <code>BundleContext</code>
+ * object will be passed to the bundle associated with this context when it is
+ * stopped using the {@link BundleActivator#stop} method. A <code>BundleContext</code>
+ * object is generally for the private use of its associated bundle and is not
+ * meant to be shared with other bundles in the OSGi environment.
+ * 
+ * <p>
+ * The <code>Bundle</code> object associated with a <code>BundleContext</code> object
+ * is called the <em>context bundle</em>.
+ *
+ * <p>
+ * The <code>BundleContext</code> object is only valid during the execution
+ * of its context bundle; that is, during the period from when the context
+ * bundle is in the <code>STARTING</code>, <code>STOPPING</code>, and
+ * <code>ACTIVE</code> bundle states. If the <code>BundleContext</code> object
+ * is used subsequently, an <code>IllegalStateException</code> must be thrown.
+ * The <code>BundleContext</code> object must never be reused after its
+ * context bundle is stopped.
+ * 
+ * <p>
+ * The Framework is the only entity that can create <code>BundleContext</code>
+ * objects and they are only valid within the Framework that created them.
+ * 
+ * @version $Revision: 1.13 $
+ */
+
+public abstract interface BundleContext {
+	/**
+	 * Returns the value of the specified property. If the key is not found in
+	 * the Framework properties, the system properties are then searched. The
+	 * method returns <code>null</code> if the property is not found.
+	 * 
+	 * <p>
+	 * The Framework defines the following standard property keys:
+	 * </p>
+	 * <ul>
+	 * <li>{@link Constants#FRAMEWORK_VERSION}- The OSGi Framework version.
+	 * </li>
+	 * <li>{@link Constants#FRAMEWORK_VENDOR}- The Framework implementation
+	 * vendor.</li>
+	 * <li>{@link Constants#FRAMEWORK_LANGUAGE}- The language being used. See
+	 * ISO 639 for possible values.</li>
+	 * <li>{@link Constants#FRAMEWORK_OS_NAME}- The host computer operating
+	 * system.</li>
+	 * <li>{@link Constants#FRAMEWORK_OS_VERSION}- The host computer operating
+	 * system version number.</li>
+	 * <li>{@link Constants#FRAMEWORK_PROCESSOR}- The host computer processor
+	 * name.</li>
+	 * </ul>
+	 * <p>
+	 * All bundles must have permission to read these properties.
+	 * 
+	 * <p>
+	 * Note: The last four standard properties are used by the
+	 * {@link Constants#BUNDLE_NATIVECODE} <code>Manifest</code> header's matching
+	 * algorithm for selecting native language code.
+	 * 
+	 * @param key The name of the requested property.
+	 * @return The value of the requested property, or <code>null</code> if the
+	 *         property is undefined.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>PropertyPermission</code> to read the
+	 *            property, and the Java Runtime Environment supports
+	 *            permissions.
+	 */
+	public abstract String getProperty(String key);
+
+	/**
+	 * Returns the <code>Bundle</code> object associated with this
+	 * <code>BundleContext</code>. This bundle is called the context bundle.
+	 * 
+	 * @return The <code>Bundle</code> object associated with this
+	 * 		   <code>BundleContext</code>.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract Bundle getBundle();
+
+	/**
+	 * Installs a bundle from the specified location string. A bundle is
+	 * obtained from <code>location</code> as interpreted by the Framework in an
+	 * implementation dependent manner.
+	 * <p>
+	 * Every installed bundle is uniquely identified by its location string,
+	 * typically in the form of a URL.
+	 * 
+	 * <p>
+	 * The following steps are required to install a bundle:
+	 * <ol>
+	 * <li>If a bundle containing the same location string is already
+	 * installed, the <code>Bundle</code> object for that bundle is returned.
+	 * 
+	 * <li>The bundle's content is read from the location string. If this
+	 * fails, a {@link BundleException} is thrown.
+	 * 
+	 * <li>The bundle's <code>Bundle-NativeCode</code> dependencies are resolved.
+	 * If this fails, a <code>BundleException</code> is thrown.
+	 * 
+	 * <li>The bundle's associated resources are allocated. The associated
+	 * resources minimally consist of a unique identifier and a persistent
+	 * storage area if the platform has file system support. If this step fails,
+	 * a <code>BundleException</code> is thrown.
+	 * 
+	 * <li>If the bundle has declared an Bundle-RequiredExecutionEnvironment
+	 * header, then the listed execution environments must be verified against
+	 * the installed execution environments. If they are not all present, a
+	 * <code>BundleException</code> must be thrown.
+	 * 
+	 * <li>The bundle's state is set to <code>INSTALLED</code>.
+	 * 
+	 * <li>A bundle event of type {@link BundleEvent#INSTALLED} is broadcast.
+	 * 
+	 * <li>The <code>Bundle</code> object for the newly or previously installed
+	 * bundle is returned.
+	 * </ol>
+	 * 
+	 * <b>Postconditions, no exceptions thrown </b>
+	 * <ul>
+	 * <li><code>getState()</code> in {<code>INSTALLED</code>,<code>RESOLVED</code>}.
+	 * <li>Bundle has a unique ID.
+	 * </ul>
+	 * <b>Postconditions, when an exception is thrown </b>
+	 * <ul>
+	 * <li>Bundle is not installed and no trace of the bundle exists.
+	 * </ul>
+	 * 
+	 * @param location The location identifier of the bundle to install.
+	 * @return The <code>Bundle</code> object of the installed bundle.
+	 * @exception BundleException If the installation failed.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission</code>, and the Java Runtime
+	 *            Environment supports permissions.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract Bundle installBundle(String location)
+			throws BundleException;
+
+	/**
+	 * Installs a bundle from the specified <code>InputStream</code> object.
+	 * 
+	 * <p>
+	 * This method performs all of the steps listed in
+	 * <code>BundleContext.installBundle(String location)</code>, except that the
+	 * bundle's content will be read from the <code>InputStream</code> object. The
+	 * location identifier string specified will be used as the identity of the
+	 * bundle.
+	 * 
+	 * <p>
+	 * This method must always close the <code>InputStream</code> object, even if
+	 * an exception is thrown.
+	 * 
+	 * @param location The location identifier of the bundle to install.
+	 * @param input The <code>InputStream</code> object from which this bundle
+	 *        will be read.
+	 * @return The <code>Bundle</code> object of the installed bundle.
+	 * @exception BundleException If the provided stream cannot be read or the
+	 *            installation failed.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            appropriate <code>AdminPermission</code>, and the Java Runtime
+	 *            Environment supports permissions.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * @see #installBundle(java.lang.String)
+	 */
+	public abstract Bundle installBundle(String location, InputStream input)
+			throws BundleException;
+
+	/**
+	 * Returns the bundle with the specified identifier.
+	 * 
+	 * @param id The identifier of the bundle to retrieve.
+	 * @return A <code>Bundle</code> object or <code>null</code> if the identifier
+	 *         does not match any installed bundle.
+	 */
+	public abstract Bundle getBundle(long id);
+
+	/**
+	 * Returns a list of all installed bundles.
+	 * <p>
+	 * This method returns a list of all bundles installed in the OSGi
+	 * environment at the time of the call to this method. However, since the
+	 * Framework is a very dynamic environment, bundles can be installed or
+	 * uninstalled at anytime.
+	 * 
+	 * @return An array of <code>Bundle</code> objects, one object per installed
+	 *         bundle.
+	 */
+	public abstract Bundle[] getBundles();
+
+	/**
+	 * Adds the specified <code>ServiceListener</code> object with the specified
+	 * <code>filter</code> to the context bundle's list of listeners.
+	 * See {@link Filter} for a description of the filter syntax.
+	 * <code>ServiceListener</code> objects are notified when a service has a
+	 * lifecycle state change.
+	 * 
+	 * <p>
+	 * If the context bundle's list of listeners already contains a listener
+	 * <code>l</code> such that <code>(l==listener)</code>, then this method replaces
+	 * that listener's filter (which may be <code>null</code>) with the specified
+	 * one (which may be <code>null</code>).
+	 * 
+	 * <p>
+	 * The listener is called if the filter criteria is met. To filter based
+	 * upon the class of the service, the filter should reference the
+	 * {@link Constants#OBJECTCLASS} property. If <code>filter</code> is
+	 * <code>null</code>, all services are considered to match the filter.
+	 * 
+	 * <p>
+	 * When using a <code>filter</code>, it is possible that the
+	 * <code>ServiceEvent</code>s for the complete lifecycle of a service will
+	 * not be delivered to the listener. For example, if the <code>filter</code>
+	 * only matches when the property <code>x</code> has the value <code>1</code>,
+	 * the listener will not be called if the service is registered with the
+	 * property <code>x</code> not set to the value <code>1</code>. Subsequently,
+	 * when the service is modified setting property <code>x</code> to the value
+	 * <code>1</code>, the filter will match and the listener will be called with
+	 * a <code>ServiceEvent</code> of type <code>MODIFIED</code>. Thus, the
+	 * listener will not be called with a <code>ServiceEvent</code> of type
+	 * <code>REGISTERED</code>.
+	 * 
+	 * <p>
+	 * If the Java Runtime Environment supports permissions, the
+	 * <code>ServiceListener</code> object will be notified of a service event
+	 * only if the bundle that is registering it has the
+	 * <code>ServicePermission</code> to get the service using at least one of the
+	 * named classes the service was registered under.
+	 * 
+	 * @param listener The <code>ServiceListener</code> object to be added.
+	 * @param filter The filter criteria.
+	 * 
+	 * @exception InvalidSyntaxException If <code>filter</code> contains an
+	 *            invalid filter string that cannot be parsed.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * 
+	 * @see ServiceEvent
+	 * @see ServiceListener
+	 * @see ServicePermission
+	 */
+	public abstract void addServiceListener(ServiceListener listener,
+			String filter) throws InvalidSyntaxException;
+
+	/**
+	 * Adds the specified <code>ServiceListener</code> object to the context
+	 * bundle's list of listeners.
+	 * 
+	 * <p>
+	 * This method is the same as calling
+	 * <code>BundleContext.addServiceListener(ServiceListener listener,
+	 * String filter)</code>
+	 * with <code>filter</code> set to <code>null</code>.
+	 * 
+	 * @param listener The <code>ServiceListener</code> object to be added.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * 
+	 * @see #addServiceListener(ServiceListener, String)
+	 */
+	public abstract void addServiceListener(ServiceListener listener);
+
+	/**
+	 * Removes the specified <code>ServiceListener</code> object from the context
+	 * bundle's list of listeners.
+	 * 
+	 * <p>
+	 * If <code>listener</code> is not contained in this context bundle's list of
+	 * listeners, this method does nothing.
+	 * 
+	 * @param listener The <code>ServiceListener</code> to be removed.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract void removeServiceListener(ServiceListener listener);
+
+	/**
+	 * Adds the specified <code>BundleListener</code> object to the context
+	 * bundle's list of listeners if not already present. BundleListener
+	 * objects are notified when a bundle has a lifecycle state change.
+	 * 
+	 * <p>
+	 * If the context bundle's list of listeners already contains a listener
+	 * <code>l</code> such that <code>(l==listener)</code>, this method does
+	 * nothing.
+	 * 
+	 * @param listener The <code>BundleListener</code> to be added.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * 
+	 * @see BundleEvent
+	 * @see BundleListener
+	 */
+	public abstract void addBundleListener(BundleListener listener);
+
+	/**
+	 * Removes the specified <code>BundleListener</code> object from the context
+	 * bundle's list of listeners.
+	 * 
+	 * <p>
+	 * If <code>listener</code> is not contained in the context bundle's list of
+	 * listeners, this method does nothing.
+	 * 
+	 * @param listener The <code>BundleListener</code> object to be removed.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract void removeBundleListener(BundleListener listener);
+
+	/**
+	 * Adds the specified <code>FrameworkListener</code> object to the context
+	 * bundle's list of listeners if not already present.
+	 * FrameworkListeners are notified of general Framework events.
+	 * 
+	 * <p>
+	 * If the context bundle's list of listeners already contains a listener
+	 * <code>l</code> such that <code>(l==listener)</code>, this method does
+	 * nothing.
+	 * 
+	 * @param listener The <code>FrameworkListener</code> object to be added.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * 
+	 * @see FrameworkEvent
+	 * @see FrameworkListener
+	 */
+	public abstract void addFrameworkListener(FrameworkListener listener);
+
+	/**
+	 * Removes the specified <code>FrameworkListener</code> object from the
+	 * context bundle's list of listeners.
+	 * 
+	 * <p>
+	 * If <code>listener</code> is not contained in the context bundle's list of
+	 * listeners, this method does nothing.
+	 * 
+	 * @param listener The <code>FrameworkListener</code> object to be removed.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract void removeFrameworkListener(FrameworkListener listener);
+
+	/**
+	 * Registers the specified service object with the specified properties
+	 * under the specified class names into the Framework. A
+	 * <code>ServiceRegistration</code> object is returned. The
+	 * <code>ServiceRegistration</code> object is for the private use of the
+	 * bundle registering the service and should not be shared with other
+	 * bundles. The registering bundle is defined to be the context bundle.
+	 * Other bundles can locate the service by using either the
+	 * {@link #getServiceReferences} or {@link #getServiceReference} method.
+	 * 
+	 * <p>
+	 * A bundle can register a service object that implements the
+	 * {@link ServiceFactory} interface to have more flexibility in providing
+	 * service objects to other bundles.
+	 * 
+	 * <p>
+	 * The following steps are required to register a service:
+	 * <ol>
+	 * <li>If <code>service</code> is not a <code>ServiceFactory</code>, an
+	 * <code>IllegalArgumentException</code> is thrown if <code>service</code> is
+	 * not an <code>instanceof</code> all the classes named.
+	 * <li>The Framework adds these service properties to the specified
+	 * <code>Dictionary</code> (which may be <code>null</code>): a property named
+	 * {@link Constants#SERVICE_ID} identifying the registration number of the
+	 * service and a property named {@link Constants#OBJECTCLASS} containing
+	 * all the specified classes. If any of these properties have already been
+	 * specified by the registering bundle, their values will be overwritten by
+	 * the Framework.
+	 * <li>The service is added to the Framework service registry and may now
+	 * be used by other bundles.
+	 * <li>A service event of type {@link ServiceEvent#REGISTERED} is
+	 * synchronously sent.
+	 * <li>A <code>ServiceRegistration</code> object for this registration is
+	 * returned.
+	 * </ol>
+	 * 
+	 * @param clazzes The class names under which the service can be located.
+	 *        The class names in this array will be stored in the service's
+	 *        properties under the key {@link Constants#OBJECTCLASS}.
+	 * @param service The service object or a <code>ServiceFactory</code> object.
+	 * @param properties The properties for this service. The keys in the
+	 *        properties object must all be <code>String</code> objects. See
+	 *        {@link Constants} for a list of standard service property keys.
+	 *        Changes should not be made to this object after calling this
+	 *        method. To update the service's properties the
+	 *        {@link ServiceRegistration#setProperties} method must be called.
+	 *        The set of properties may be <code>null</code> if the service has no
+	 *        properties.
+	 * 
+	 * @return A <code>ServiceRegistration</code> object for use by the bundle
+	 *         registering the service to update the service's properties or to
+	 *         unregister the service.
+	 * 
+	 * @exception java.lang.IllegalArgumentException If one of the following is
+	 *            true:
+	 *            <ul>
+	 *            <li><code>service</code> is <code>null</code>.
+	 *            <li><code>service</code> is not a <code>ServiceFactory</code>
+	 *            object and is not an instance of all the named classes in
+	 *            <code>clazzes</code>.
+	 *            <li><code>properties</code> contains case variants of the same
+	 *            key name.
+	 *            </ul>
+	 * 
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            <code>ServicePermission</code> to register the service for all
+	 *            the named classes and the Java Runtime Environment supports
+	 *            permissions.
+	 * 
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * 
+	 * @see ServiceRegistration
+	 * @see ServiceFactory
+	 */
+	public abstract ServiceRegistration registerService(String[] clazzes,
+			Object service, Dictionary properties);
+
+	/**
+	 * Registers the specified service object with the specified properties
+	 * under the specified class name with the Framework.
+	 * 
+	 * <p>
+	 * This method is otherwise identical to
+	 * {@link #registerService(java.lang.String[], java.lang.Object,
+	 * java.util.Dictionary)} and is provided as a convenience when
+	 * <code>service</code> will only be registered under a single class name.
+	 * Note that even in this case the value of the service's
+	 * {@link Constants#OBJECTCLASS} property will be an array of strings,
+	 * rather than just a single string.
+	 * 
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * @see #registerService(java.lang.String[], java.lang.Object,
+	 *      java.util.Dictionary)
+	 */
+	public abstract ServiceRegistration registerService(String clazz,
+			Object service, Dictionary properties);
+
+	/**
+	 * Returns an array of <code>ServiceReference</code> objects. The returned
+	 * array of <code>ServiceReference</code> objects contains services that
+	 * were registered under the specified class, match the specified filter
+	 * criteria, and the packages for the class names under which the services
+	 * were registered match the context bundle's packages as defined in
+	 * {@link ServiceReference#isAssignableTo(Bundle, String)}.
+	 * 
+	 * <p>
+	 * The list is valid at the time of the call to this method, however since the
+	 * Framework is a very dynamic environment, services can be modified or
+	 * unregistered at anytime.
+	 * 
+	 * <p>
+	 * <code>filter</code> is used to select the registered service whose
+	 * properties objects contain keys and values which satisfy the filter. See
+	 * {@link Filter} for a description of the filter string syntax.
+	 * 
+	 * <p>
+	 * If <code>filter</code> is <code>null</code>, all registered services are
+	 * considered to match the filter.
+	 * If <code>filter</code> cannot be parsed, an {@link InvalidSyntaxException}
+	 * will be thrown with a human readable message where the filter became
+	 * unparsable.
+	 * 
+	 * <p>
+	 * The following steps are required to select a set of 
+	 * <code>ServiceReference</code> objects:
+	 * <ol>
+	 * <li>If the filter string is not <code>null</code>, the filter string is
+	 * parsed and the set <code>ServiceReference</code> objects of registered
+	 * services that satisfy the filter is produced. If the filter string is
+	 * <code>null</code>, then all registered
+	 * services are considered to satisfy the filter.
+	 * <li>If the Java Runtime Environment supports permissions, the
+	 * set of <code>ServiceReference</code> objects produced by the
+	 * previous step is reduced by checking that the caller has
+	 * the <code>ServicePermission</code> to get at least one
+	 * of the class names under which the service was registered. If the caller
+	 * does not have the correct permission for a particular
+	 * <code>ServiceReference</code> object, then it is removed from the set.
+	 * <li>If <code>clazz</code> is not <code>null</code>, the set is further
+	 * reduced to those services that are an <code>instanceof</code> and were
+	 * registered under the specified class. The complete list of classes of
+	 * which a service is an instance and which were specified when the service
+	 * was registered is available from the service's
+	 * {@link Constants#OBJECTCLASS} property.
+	 * <li>The set is reduced one final time by cycling through each
+	 * <code>ServiceReference</code> object and calling
+	 * {@link ServiceReference#isAssignableTo(Bundle, String)} with the
+	 * context bundle and each class name under which the
+	 * <code>ServiceReference</code> object was registered. For any
+	 * given <code>ServiceReference</code> object, if any call to
+	 * {@link ServiceReference#isAssignableTo(Bundle, String)} returns
+	 * <code>false</code>, then it is removed from the set of
+	 * <code>ServiceReference</code> objects.
+	 * <li>An array of the remaining <code>ServiceReference</code> objects
+	 * is returned.
+	 * </ol>
+	 * 
+	 * @param clazz The class name with which the service was registered or
+	 *        <code>null</code> for all services.
+	 * @param filter The filter criteria.
+	 * @return An array of <code>ServiceReference</code> objects or <code>null</code>
+	 *         if no services are registered which satisfy the search.
+	 * @exception InvalidSyntaxException If <code>filter</code> contains an
+	 *            invalid filter string that cannot be parsed.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract ServiceReference[] getServiceReferences(String clazz,
+			String filter) throws InvalidSyntaxException;
+
+	/**
+	 * Returns an array of <code>ServiceReference</code> objects. The returned
+	 * array of <code>ServiceReference</code> objects contains services that
+	 * were registered under the specified class and match the
+	 * specified filter criteria.
+	 * 
+	 * <p>
+	 * The list is valid at the time of the call to this method, however since the
+	 * Framework is a very dynamic environment, services can be modified or
+	 * unregistered at anytime.
+	 * 
+	 * <p>
+	 * <code>filter</code> is used to select the registered service whose
+	 * properties objects contain keys and values which satisfy the filter. See
+	 * {@link Filter} for a description of the filter string syntax.
+	 * 
+	 * <p>
+	 * If <code>filter</code> is <code>null</code>, all registered services are
+	 * considered to match the filter.
+	 * If <code>filter</code> cannot be parsed, an {@link InvalidSyntaxException}
+	 * will be thrown with a human readable message where the filter became
+	 * unparsable.
+	 * 
+	 * <p>
+	 * The following steps are required to select a set of 
+	 * <code>ServiceReference</code> objects:
+	 * <ol>
+	 * <li>If the filter string is not <code>null</code>, the filter string is
+	 * parsed and the set <code>ServiceReference</code> objects of registered
+	 * services that satisfy the filter is produced. If the filter string is
+	 * <code>null</code>, then all registered
+	 * services are considered to satisfy the filter.
+	 * <li>If the Java Runtime Environment supports permissions, the
+	 * set of <code>ServiceReference</code> objects produced by the
+	 * previous step is reduced by checking that the caller has
+	 * the <code>ServicePermission</code> to get at least one
+	 * of the class names under which the service was registered. If the caller
+	 * does not have the correct permission for a particular
+	 * <code>ServiceReference</code> object, then it is removed from the set.
+	 * <li>If <code>clazz</code> is not <code>null</code>, the set is further
+	 * reduced to those services that are an <code>instanceof</code> and were
+	 * registered under the specified class. The complete list of classes of
+	 * which a service is an instance and which were specified when the service
+	 * was registered is available from the service's
+	 * {@link Constants#OBJECTCLASS} property.
+	 * <li>An array of the remaining <code>ServiceReference</code> objects
+	 * is returned.
+	 * </ol>
+	 * 
+	 * @param clazz The class name with which the service was registered or
+	 *        <code>null</code> for all services.
+	 * @param filter The filter criteria.
+	 * @return An array of <code>ServiceReference</code> objects or <code>null</code>
+	 *         if no services are registered which satisfy the search.
+	 * @exception InvalidSyntaxException If <code>filter</code> contains an
+	 *            invalid filter string that cannot be parsed.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract ServiceReference[] getAllServiceReferences(String clazz, 
+			String filter) throws InvalidSyntaxException;
+
+	/**
+	 * Returns a <code>ServiceReference</code> object for a service that
+	 * implements and was registered under the specified class.
+	 * 
+	 * <p>
+	 * This <code>ServiceReference</code> object is valid at the time of the call
+	 * to this method, however as the Framework is a very dynamic environment,
+	 * services can be modified or unregistered at anytime.
+	 * 
+	 * <p>
+	 * This method is the same as calling
+	 * {@link BundleContext#getServiceReferences(String, String)} with a
+	 * <code>null</code> filter string. It is provided as a convenience for when
+	 * the caller is interested in any service that implements the specified
+	 * class.
+	 * <p>
+	 * If multiple such services exist, the service with the highest ranking (as
+	 * specified in its {@link Constants#SERVICE_RANKING} property) is returned.
+	 * <p>
+	 * If there is a tie in ranking, the service with the lowest service ID (as
+	 * specified in its {@link Constants#SERVICE_ID} property); that is, the
+	 * service that was registered first is returned.
+	 * 
+	 * @param clazz The class name with which the service was registered.
+	 * @return A <code>ServiceReference</code> object, or <code>null</code> if no
+	 *         services are registered which implement the named class.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * @see #getServiceReferences(String, String)
+	 */
+	public abstract ServiceReference getServiceReference(String clazz);
+
+	/**
+	 * Returns the specified service object for a service.
+	 * <p>
+	 * A bundle's use of a service is tracked by the bundle's use count of that
+	 * service. Each time a service's service object is returned by
+	 * {@link #getService(ServiceReference)} the context bundle's use count for
+	 * that service is incremented by one. Each time the service is released by
+	 * {@link #ungetService(ServiceReference)} the context bundle's use count
+	 * for that service is decremented by one.
+	 * <p>
+	 * When a bundle's use count for a service drops to zero, the bundle should
+	 * no longer use that service.
+	 * 
+	 * <p>
+	 * This method will always return <code>null</code> when the service
+	 * associated with this <code>reference</code> has been unregistered.
+	 * 
+	 * <p>
+	 * The following steps are required to get the service object:
+	 * <ol>
+	 * <li>If the service has been unregistered, <code>null</code> is returned.
+	 * <li>The context bundle's use count for this service is incremented by
+	 * one.
+	 * <li>If the context bundle's use count for the service is currently one
+	 * and the service was registered with an object implementing the
+	 * <code>ServiceFactory</code> interface, the
+	 * {@link ServiceFactory#getService(Bundle, ServiceRegistration)} method
+	 * is called to create a service object for the context bundle. This service
+	 * object is cached by the Framework. While the context bundle's use count
+	 * for the service is greater than zero, subsequent calls to get the
+	 * services's service object for the context bundle will return the cached
+	 * service object.
+	 * <br>
+	 * If the service object returned by the <code>ServiceFactory</code> object is
+	 * not an <code>instanceof</code> all the classes named when the service was
+	 * registered or the <code>ServiceFactory</code> object throws an exception,
+	 * <code>null</code> is returned and a Framework event of type
+	 * {@link FrameworkEvent#ERROR} is broadcast.
+	 * <li>The service object for the service is returned.
+	 * </ol>
+	 * 
+	 * @param reference A reference to the service.
+	 * @return A service object for the service associated with
+	 *         <code>reference</code> or <code>null</code> if the service is not
+	 *         registered or does not implement the classes under which it was
+	 *         registered in the case of a <code>ServiceFactory</code>.
+	 * @exception java.lang.SecurityException If the caller does not have the
+	 *            <code>ServicePermission</code> to get the service using at least
+	 *            one of the named classes the service was registered under and
+	 *            the Java Runtime Environment supports permissions.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * @see #ungetService(ServiceReference)
+	 * @see ServiceFactory
+	 */
+	public abstract Object getService(ServiceReference reference);
+
+	/**
+	 * Releases the service object referenced by the specified
+	 * <code>ServiceReference</code> object. If the context bundle's use count for
+	 * the service is zero, this method returns <code>false</code>. Otherwise,
+	 * the context bundle's use count for the service is decremented by one.
+	 * 
+	 * <p>
+	 * The service's service object should no longer be used and all references
+	 * to it should be destroyed when a bundle's use count for the service drops
+	 * to zero.
+	 * 
+	 * <p>
+	 * The following steps are required to unget the service object:
+	 * <ol>
+	 * <li>If the context bundle's use count for the service is zero or the
+	 * service has been unregistered, <code>false</code> is returned.
+	 * <li>The context bundle's use count for this service is decremented by
+	 * one.
+	 * <li>If the context bundle's use count for the service is currently zero
+	 * and the service was registered with a <code>ServiceFactory</code> object,
+	 * the {@link ServiceFactory#ungetService(Bundle, ServiceRegistration, Object)}
+	 * method is called to release the service object for the context bundle.
+	 * <li><code>true</code> is returned.
+	 * </ol>
+	 * 
+	 * @param reference A reference to the service to be released.
+	 * @return <code>false</code> if the context bundle's use count for the
+	 *         service is zero or if the service has been unregistered;
+	 *         <code>true</code> otherwise.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * @see #getService
+	 * @see ServiceFactory
+	 */
+	public abstract boolean ungetService(ServiceReference reference);
+
+	/**
+	 * Creates a <code>File</code> object for a file in the persistent storage
+	 * area provided for the bundle by the Framework. This method will return
+	 * <code>null</code> if the platform does not have file system support.
+	 * 
+	 * <p>
+	 * A <code>File</code> object for the base directory of the persistent storage
+	 * area provided for the context bundle by the Framework can be obtained by
+	 * calling this method with an empty string as <code>filename</code>.
+	 * 
+	 * <p>
+	 * If the Java Runtime Environment supports permissions, the Framework will
+	 * ensure that the bundle has the <code>java.io.FilePermission</code> with
+	 * actions <code>read</code>,<code>write</code>,<code>delete</code> for all
+	 * files (recursively) in the persistent storage area provided for the
+	 * context bundle.
+	 * 
+	 * @param filename A relative name to the file to be accessed.
+	 * @return A <code>File</code> object that represents the requested file or
+	 *         <code>null</code> if the platform does not have file system
+	 *         support.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 */
+	public abstract File getDataFile(String filename);
+
+	/**
+	 * Creates a <code>Filter</code> object. This <code>Filter</code> object may be
+	 * used to match a <code>ServiceReference</code> object or a
+	 * <code>Dictionary</code> object. 
+	 * 
+	 * <p>
+	 * If the filter cannot be parsed, an {@link InvalidSyntaxException} will be
+	 * thrown with a human readable message where the filter became unparsable.
+	 * 
+	 * @param filter The filter string.
+	 * @return A <code>Filter</code> object encapsulating the filter string.
+	 * @exception InvalidSyntaxException If <code>filter</code> contains an
+	 *            invalid filter string that cannot be parsed.
+	 * @exception NullPointerException If <code>filter</code> is null.
+	 * @exception java.lang.IllegalStateException If this BundleContext is no
+	 *            longer valid.
+	 * 
+	 * @since 1.1
+	 * @see "Framework specification for a description of the filter string syntax."
+	 */
+	public abstract Filter createFilter(String filter)
+			throws InvalidSyntaxException;
+}
+
diff --git a/src/org/osgi/framework/BundleEvent.java b/src/org/osgi/framework/BundleEvent.java
new file mode 100644
index 0000000..dcd6e8e
--- /dev/null
+++ b/src/org/osgi/framework/BundleEvent.java
@@ -0,0 +1,146 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/BundleEvent.java,v 1.10 2005/05/13 20:32:54 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.EventObject;
+
+/**
+ * A Framework event describing a bundle lifecycle change.
+ * <p>
+ * <code>BundleEvent</code> objects are delivered to <code>BundleListener</code>
+ * objects when a change occurs in a bundle's lifecycle. A type code is used to
+ * identify the event type for future extendability.
+ * 
+ * <p>
+ * OSGi Alliance reserves the right to extend the set of types.
+ * 
+ * @version $Revision: 1.10 $
+ */
+
+public class BundleEvent extends EventObject {
+	static final long		serialVersionUID	= 4080640865971756012L;
+	/**
+	 * Bundle that had a change occur in its lifecycle.
+	 */
+	private Bundle			bundle;
+
+	/**
+	 * Type of bundle lifecycle change.
+	 */
+	private int				type;
+
+	/**
+	 * The bundle has been installed.
+	 * <p>
+	 * The value of <code>INSTALLED</code> is 0x00000001.
+	 * 
+	 * @see BundleContext#installBundle(String)
+	 */
+	public final static int	INSTALLED			= 0x00000001;
+
+	/**
+	 * The bundle has been started.
+	 * <p>
+	 * The value of <code>STARTED</code> is 0x00000002.
+	 * 
+	 * @see Bundle#start
+	 */
+	public final static int	STARTED				= 0x00000002;
+
+	/**
+	 * The bundle has been stopped.
+	 * <p>
+	 * The value of <code>STOPPED</code> is 0x00000004.
+	 * 
+	 * @see Bundle#stop
+	 */
+	public final static int	STOPPED				= 0x00000004;
+
+	/**
+	 * The bundle has been updated.
+	 * <p>
+	 * The value of <code>UPDATED</code> is 0x00000008.
+	 * 
+	 * @see Bundle#update()
+	 */
+	public final static int	UPDATED				= 0x00000008;
+
+	/**
+	 * The bundle has been uninstalled.
+	 * <p>
+	 * The value of <code>UNINSTALLED</code> is 0x00000010.
+	 * 
+	 * @see Bundle#uninstall
+	 */
+	public final static int	UNINSTALLED			= 0x00000010;
+
+	/**
+	 * The bundle has been resolved.
+	 * <p>
+	 * The value of <code>RESOLVED</code> is 0x00000020.
+	 * 
+	 * @see Bundle#RESOLVED
+	 * @since 1.3
+	 */
+	public final static int	RESOLVED	= 0x00000020;
+
+	/**
+	 * The bundle has been unresolved.
+	 * <p>
+	 * The value of <code>UNRESOLVED</code> is 0x00000040.
+	 * 
+	 * @see Bundle#INSTALLED
+	 * @since 1.3
+	 */
+	public final static int	UNRESOLVED	= 0x00000040;
+
+	/**
+	 * Creates a bundle event of the specified type.
+	 * 
+	 * @param type The event type.
+	 * @param bundle The bundle which had a lifecycle change.
+	 */
+
+	public BundleEvent(int type, Bundle bundle) {
+		super(bundle);
+		this.bundle = bundle;
+		this.type = type;
+	}
+
+	/**
+	 * Returns the bundle which had a lifecycle change. This bundle is the
+	 * source of the event.
+	 * 
+	 * @return The bundle that had a change occur in its lifecycle.
+	 */
+	public Bundle getBundle() {
+		return bundle;
+	}
+
+	/**
+	 * Returns the type of lifecyle event. The type values are:
+	 * <ul>
+	 * <li>{@link #INSTALLED}
+	 * <li>{@link #STARTED}
+	 * <li>{@link #STOPPED}
+	 * <li>{@link #UPDATED}
+	 * <li>{@link #UNINSTALLED}
+	 * <li>{@link #RESOLVED}
+	 * <li>{@link #UNRESOLVED}
+	 * </ul>
+	 * 
+	 * @return The type of lifecycle event.
+	 */
+
+	public int getType() {
+		return type;
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/BundleException.java b/src/org/osgi/framework/BundleException.java
new file mode 100644
index 0000000..c6cade1
--- /dev/null
+++ b/src/org/osgi/framework/BundleException.java
@@ -0,0 +1,95 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/BundleException.java,v 1.10 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * A Framework exception used to indicate that a bundle lifecycle problem
+ * occurred.
+ * 
+ * <p>
+ * <code>BundleException</code> object is created by the Framework to denote an
+ * exception condition in the lifecycle of a bundle. <code>BundleException</code>s
+ * should not be created by bundle developers.
+ * 
+ * <p>
+ * This exception is updated to conform to the general purpose exception
+ * chaining mechanism.
+ * 
+ * @version $Revision: 1.10 $
+ */
+
+public class BundleException extends Exception {
+	static final long	serialVersionUID	= 3571095144220455665L;
+	/**
+	 * Nested exception.
+	 */
+	private Throwable	cause;
+
+	/**
+	 * Creates a <code>BundleException</code> that wraps another exception.
+	 * 
+	 * @param msg The associated message.
+	 * @param cause The cause of this exception.
+	 */
+	public BundleException(String msg, Throwable cause) {
+		super(msg);
+		this.cause = cause;
+	}
+
+	/**
+	 * Creates a <code>BundleException</code> object with the specified message.
+	 * 
+	 * @param msg The message.
+	 */
+	public BundleException(String msg) {
+		super(msg);
+		this.cause = null;
+	}
+
+	/**
+	 * Returns any nested exceptions included in this exception.
+	 * 
+	 * <p>
+	 * This method predates the general purpose exception chaining mechanism.
+	 * The {@link #getCause()} method is now the preferred means of obtaining
+	 * this information.
+	 * 
+	 * @return The nested exception; <code>null</code> if there is no nested
+	 *         exception.
+	 */
+	public Throwable getNestedException() {
+		return cause;
+	}
+
+	/**
+	 * Returns the cause of this exception or <code>null</code> if no cause was
+	 * specified when this exception was created.
+	 * 
+	 * @return The cause of this exception or <code>null</code> if no cause was
+	 *         specified.
+	 * @since 1.3
+	 */
+	public Throwable getCause() {
+		return cause;
+	}
+
+	/**
+	 * The cause of this exception can only be set when constructed.
+	 * 
+	 * @throws java.lang.IllegalStateException This method will always throw an
+	 *         <code>IllegalStateException</code> since the cause of this
+	 *         exception can only be set when constructed.
+	 * @since 1.3
+	 */
+	public Throwable initCause(Throwable cause) {
+		throw new IllegalStateException();
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/BundleListener.java b/src/org/osgi/framework/BundleListener.java
new file mode 100644
index 0000000..88c38aa
--- /dev/null
+++ b/src/org/osgi/framework/BundleListener.java
@@ -0,0 +1,38 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/BundleListener.java,v 1.7 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.EventListener;
+
+/**
+ * A <code>BundleEvent</code> listener.
+ * 
+ * <p>
+ * <code>BundleListener</code> is a listener interface that may be implemented by
+ * a bundle developer.
+ * <p>
+ * A <code>BundleListener</code> object is registered with the Framework using the
+ * {@link BundleContext#addBundleListener} method. <code>BundleListener</code>s
+ * are called with a <code>BundleEvent</code> object when a bundle has been
+ * installed, resolved, started, stopped, updated, unresolved, or uninstalled.
+ * 
+ * @version $Revision: 1.7 $
+ * @see BundleEvent
+ */
+
+public abstract interface BundleListener extends EventListener {
+	/**
+	 * Receives notification that a bundle has had a lifecycle change.
+	 * 
+	 * @param event The <code>BundleEvent</code>.
+	 */
+	public abstract void bundleChanged(BundleEvent event);
+}
diff --git a/src/org/osgi/framework/BundlePermission.java b/src/org/osgi/framework/BundlePermission.java
new file mode 100644
index 0000000..62b2113
--- /dev/null
+++ b/src/org/osgi/framework/BundlePermission.java
@@ -0,0 +1,578 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/BundlePermission.java,v 1.10 2005/06/21 16:22:12 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.io.IOException;
+import java.security.*;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * A bundle's authority to require or provide a bundle or to receive or attach
+ * fragments.
+ * 
+ * <p>
+ * A bundle symbolic name defines a unique fully qualified name.
+ * <p>
+ * For example:
+ * 
+ * <pre>
+ * <code>
+ * org.osgi.example.bundle
+ * </code>
+ * </pre>
+ * 
+ * <p>
+ * <code>BundlePermission</code> has four actions: <code>PROVIDE</code>,
+ * <code>REQUIRE</code>,<code>HOST</code>, and <code>FRAGMENT</code>.
+ * The <code>PROVIDE</code> action implies the <code>REQUIRE</code> action.
+ * 
+ * @since 1.3
+ */
+
+public final class BundlePermission extends BasicPermission {
+
+	private static final long	serialVersionUID	= 3257846601685873716L;
+
+	/**
+	 * The action string <code>provide</code>.
+	 */
+	public final static String	PROVIDE				= "provide";
+
+	/**
+	 * The action string <code>require</code>.
+	 */
+	public final static String	REQUIRE				= "require";
+
+	/**
+	 * The action string <code>host</code>.
+	 */
+	public final static String	HOST				= "host";
+
+	/**
+	 * The action string <code>fragment</code>.
+	 */
+	public final static String	FRAGMENT			= "fragment";
+
+	private final static int	ACTION_PROVIDE		= 0x00000001;
+	private final static int	ACTION_REQUIRE		= 0x00000002;
+	private final static int	ACTION_HOST			= 0x00000004;
+	private final static int	ACTION_FRAGMENT		= 0x00000008;
+	private final static int	ACTION_ALL			= ACTION_PROVIDE
+															| ACTION_REQUIRE
+															| ACTION_HOST
+															| ACTION_FRAGMENT;
+	private final static int	ACTION_NONE			= 0;
+	private final static int	ACTION_ERROR		= 0x80000000;
+
+	/**
+	 * The actions mask.
+	 */
+	private transient int		action_mask			= ACTION_NONE;
+
+	/**
+	 * The actions in canonical form.
+	 * 
+	 * @serial
+	 */
+	private String				actions				= null;
+
+	/**
+	 * Defines the authority to provide and/or require and or specify a host
+	 * fragment symbolic name within the OSGi environment.
+	 * <p>
+	 * Bundle Permissions are granted over all possible versions of a bundle.
+	 * 
+	 * A bundle that needs to provide a bundle must have the appropriate
+	 * <code>BundlePermission</code> for the symbolic name; a bundle that
+	 * requires a bundle must have the appropriate <code>BundlePermssion</code>
+	 * for that symbolic name; a bundle that specifies a fragment host must have
+	 * the appropriate <code>BundlePermission</code> for that symbolic name.
+	 * 
+	 * @param symbolicName the bundle symbolic name.
+	 * @param actions <code>PROVIDE</code>,<code>REQUIRE</code>,
+	 *        <code>HOST</code>,<code>FRAGMENT</code> (canonical order).
+	 */
+
+	public BundlePermission(String symbolicName, String actions) {
+		this(symbolicName, getMask(actions));
+	}
+
+	/**
+	 * Bundle private constructor used by BundlePermissionCollection.
+	 * 
+	 * @param symbolicName the bundle symbolic name
+	 * @param mask the action mask
+	 */
+	BundlePermission(String symbolicName, int mask) {
+		super(symbolicName);
+		init(mask);
+	}
+
+	/**
+	 * Called by constructors and when deserialized.
+	 * 
+	 * @param mask
+	 */
+	private void init(int mask) {
+		if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
+			throw new IllegalArgumentException("invalid action string");
+		}
+
+		action_mask = mask;
+	}
+
+	/**
+	 * Parse action string into action mask.
+	 * 
+	 * @param actions Action string.
+	 * @return action mask.
+	 */
+	private static int getMask(String actions) {
+		boolean seencomma = false;
+
+		int mask = ACTION_NONE;
+
+		if (actions == null) {
+			return (mask);
+		}
+
+		char[] a = actions.toCharArray();
+
+		int i = a.length - 1;
+		if (i < 0)
+			return (mask);
+
+		while (i != -1) {
+			char c;
+
+			// skip whitespace
+			while ((i != -1)
+					&& ((c = a[i]) == ' ' || c == '\r' || c == '\n'
+							|| c == '\f' || c == '\t'))
+				i--;
+
+			// check for the known strings
+			int matchlen;
+
+			if (i >= 6 && (a[i - 6] == 'p' || a[i - 6] == 'P')
+					&& (a[i - 5] == 'r' || a[i - 5] == 'R')
+					&& (a[i - 4] == 'o' || a[i - 4] == 'O')
+					&& (a[i - 3] == 'v' || a[i - 3] == 'V')
+					&& (a[i - 2] == 'i' || a[i - 2] == 'I')
+					&& (a[i - 1] == 'd' || a[i - 1] == 'D')
+					&& (a[i] == 'e' || a[i] == 'E')) {
+				matchlen = 7;
+				mask |= ACTION_PROVIDE | ACTION_REQUIRE;
+			}
+			else
+				if (i >= 6 && (a[i - 6] == 'r' || a[i - 6] == 'R')
+						&& (a[i - 5] == 'e' || a[i - 5] == 'E')
+						&& (a[i - 4] == 'q' || a[i - 4] == 'Q')
+						&& (a[i - 3] == 'u' || a[i - 3] == 'U')
+						&& (a[i - 2] == 'i' || a[i - 2] == 'I')
+						&& (a[i - 1] == 'r' || a[i - 1] == 'R')
+						&& (a[i] == 'e' || a[i] == 'E')) {
+					matchlen = 7;
+					mask |= ACTION_REQUIRE;
+				}
+				else
+					if (i >= 3 && (a[i - 3] == 'h' || a[i - 3] == 'H')
+							&& (a[i - 2] == 'o' || a[i - 2] == 'O')
+							&& (a[i - 1] == 's' || a[i - 1] == 'S')
+							&& (a[i] == 't' || a[i] == 'T')) {
+						matchlen = 4;
+						mask |= ACTION_HOST;
+					}
+					else
+						if (i >= 7 && (a[i - 7] == 'f' || a[i - 7] == 'F')
+								&& (a[i - 6] == 'r' || a[i - 6] == 'R')
+								&& (a[i - 5] == 'a' || a[i - 5] == 'A')
+								&& (a[i - 4] == 'g' || a[i - 4] == 'G')
+								&& (a[i - 3] == 'm' || a[i - 3] == 'M')
+								&& (a[i - 2] == 'e' || a[i - 2] == 'E')
+								&& (a[i - 1] == 'n' || a[i - 1] == 'N')
+								&& (a[i] == 't' || a[i] == 'T')) {
+							matchlen = 8;
+							mask |= ACTION_FRAGMENT;
+						}
+						else {
+							// parse error
+							throw new IllegalArgumentException(
+									"invalid permission: " + actions);
+						}
+
+			// make sure we didn't just match the tail of a word
+			// like "ackbarfrequire". Also, skip to the comma.
+			seencomma = false;
+			while (i >= matchlen && !seencomma) {
+				switch (a[i - matchlen]) {
+					case ',' :
+						seencomma = true;
+					/* FALLTHROUGH */
+					case ' ' :
+					case '\r' :
+					case '\n' :
+					case '\f' :
+					case '\t' :
+						break;
+					default :
+						throw new IllegalArgumentException(
+								"invalid permission: " + actions);
+				}
+				i--;
+			}
+
+			// point i at the location of the comma minus one (or -1).
+			i -= matchlen;
+		}
+
+		if (seencomma) {
+			throw new IllegalArgumentException("invalid permission: " + actions);
+		}
+
+		return (mask);
+	}
+
+	/**
+	 * Determines if the specified permission is implied by this object.
+	 * 
+	 * <p>
+	 * This method checks that the symbolic name of the target is implied by the
+	 * symbolic name of this object. The list of <code>BundlePermission</code>
+	 * actions must either match or allow for the list of the target object to
+	 * imply the target <code>BundlePermission</code> action.
+	 * <p>
+	 * The permission to provide a bundle implies the permission to require the
+	 * named symbolic name.
+	 * 
+	 * <pre>
+	 *      x.y.*,&quot;provide&quot; -&gt; x.y.z,&quot;provide&quot; is true
+	 *      *,&quot;require&quot; -&gt; x.y, &quot;require&quot;      is true
+	 *      *,&quot;provide&quot; -&gt; x.y, &quot;require&quot;      is true
+	 *      x.y,&quot;provide&quot; -&gt; x.y.z, &quot;provide&quot;  is false
+	 * </pre>
+	 * 
+	 * @param p The target permission to interrogate.
+	 * @return <code>true</code> if the specified
+	 *         <code>BundlePermission</code> action is implied by this object;
+	 *         <code>false</code> otherwise.
+	 */
+
+	public boolean implies(Permission p) {
+		if (p instanceof BundlePermission) {
+			BundlePermission target = (BundlePermission) p;
+
+			return (((action_mask & target.action_mask) == target.action_mask) && super
+					.implies(p));
+		}
+
+		return (false);
+	}
+
+	/**
+	 * Returns the canonical string representation of the
+	 * <code>BundlePermission</code> actions.
+	 * 
+	 * <p>
+	 * Always returns present <code>BundlePermission</code> actions in the
+	 * following order: <code>PROVIDE</code>,<code>REQUIRE</code>,
+	 * <code>HOST</code>,<code>FRAGMENT.
+	 * @return Canonical string representation of the <code>BundlePermission</code> actions.
+	 */
+
+	public String getActions() {
+		if (actions == null) {
+			StringBuffer sb = new StringBuffer();
+			boolean comma = false;
+
+			if ((action_mask & ACTION_PROVIDE) == ACTION_PROVIDE) {
+				sb.append(PROVIDE);
+				comma = true;
+			}
+
+			if ((action_mask & ACTION_REQUIRE) == ACTION_REQUIRE) {
+				if (comma)
+					sb.append(',');
+				sb.append(REQUIRE);
+				comma = true;
+			}
+
+			if ((action_mask & ACTION_HOST) == ACTION_HOST) {
+				if (comma)
+					sb.append(',');
+				sb.append(HOST);
+			}
+
+			if ((action_mask & ACTION_FRAGMENT) == ACTION_FRAGMENT) {
+				if (comma)
+					sb.append(',');
+				sb.append(FRAGMENT);
+			}
+
+			actions = sb.toString();
+		}
+
+		return (actions);
+	}
+
+	/**
+	 * Returns a new <code>PermissionCollection</code> object suitable for
+	 * storing <code>BundlePermission</code> objects.
+	 * 
+	 * @return A new <code>PermissionCollection</code> object.
+	 */
+	public PermissionCollection newPermissionCollection() {
+		return (new BundlePermissionCollection());
+	}
+
+	/**
+	 * Determines the equality of two <code>BundlePermission</code> objects.
+	 * 
+	 * This method checks that specified bundle has the same bundle symbolic
+	 * name and <code>BundlePermission</code> actions as this
+	 * <code>BundlePermission</code> object.
+	 * 
+	 * @param obj The object to test for equality with this
+	 *        <code>BundlePermission</code> object.
+	 * @return <code>true</code> if <code>obj</code> is a
+	 *         <code>BundlePermission</code>, and has the same bundle
+	 *         symbolic name and actions as this <code>BundlePermission</code>
+	 *         object; <code>false</code> otherwise.
+	 */
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return (true);
+		}
+
+		if (!(obj instanceof BundlePermission)) {
+			return (false);
+		}
+
+		BundlePermission p = (BundlePermission) obj;
+
+		return ((action_mask == p.action_mask) && getName().equals(p.getName()));
+	}
+
+	/**
+	 * Returns the hash code value for this object.
+	 * 
+	 * @return A hash code value for this object.
+	 */
+
+	public int hashCode() {
+		return (getName().hashCode() ^ getActions().hashCode());
+	}
+
+	/**
+	 * Returns the current action mask.
+	 * <p>
+	 * Used by the BundlePermissionCollection class.
+	 * 
+	 * @return Current action mask.
+	 */
+	int getMask() {
+		return (action_mask);
+	}
+
+	/**
+	 * WriteObject is called to save the state of the
+	 * <code>BundlePermission</code> object to a stream. The actions are
+	 * serialized, and the superclass takes care of the name.
+	 */
+
+	private synchronized void writeObject(java.io.ObjectOutputStream s)
+			throws IOException {
+		// Write out the actions. The superclass takes care of the name
+		// call getActions to make sure actions field is initialized
+		if (actions == null)
+			getActions();
+		s.defaultWriteObject();
+	}
+
+	/**
+	 * readObject is called to restore the state of the BundlePermission from a
+	 * stream.
+	 */
+	private synchronized void readObject(java.io.ObjectInputStream s)
+			throws IOException, ClassNotFoundException {
+		// Read in the action, then initialize the rest
+		s.defaultReadObject();
+		init(getMask(actions));
+	}
+}
+
+/**
+ * Stores a set of <code>BundlePermission</code> permissions.
+ * 
+ * @see java.security.Permission
+ * @see java.security.Permissions
+ * @see java.security.PermissionCollection
+ */
+
+final class BundlePermissionCollection extends PermissionCollection {
+
+	/**
+	 * Comment for <code>serialVersionUID</code>
+	 */
+	private static final long	serialVersionUID	= 3258407326846433079L;
+
+	/**
+	 * Table of permissions.
+	 * 
+	 * @serial
+	 */
+	private Hashtable			permissions;
+
+	/**
+	 * Boolean saying if "*" is in the collection.
+	 * 
+	 * @serial
+	 */
+	private boolean				all_allowed;
+
+	/**
+	 * Create an empty BundlePermissions object.
+	 * 
+	 */
+
+	public BundlePermissionCollection() {
+		permissions = new Hashtable();
+		all_allowed = false;
+	}
+
+	/**
+	 * Adds a permission to the <code>BundlePermission</code> objects. The key
+	 * for the hash is the symbolic name.
+	 * 
+	 * @param permission The <code>BundlePermission</code> object to add.
+	 * 
+	 * @exception IllegalArgumentException If the permission is not a
+	 *            <code>BundlePermission</code> instance.
+	 * 
+	 * @exception SecurityException If this
+	 *            <code>BundlePermissionCollection</code> object has been
+	 *            marked read-only.
+	 */
+
+	public void add(Permission permission) {
+		if (!(permission instanceof BundlePermission))
+			throw new IllegalArgumentException("invalid permission: "
+					+ permission);
+		if (isReadOnly())
+			throw new SecurityException("attempt to add a Permission to a "
+					+ "readonly PermissionCollection");
+
+		BundlePermission bp = (BundlePermission) permission;
+		String name = bp.getName();
+
+		BundlePermission existing = (BundlePermission) permissions.get(name);
+
+		if (existing != null) {
+			int oldMask = existing.getMask();
+			int newMask = bp.getMask();
+			if (oldMask != newMask) {
+				permissions.put(name, new BundlePermission(name, oldMask
+						| newMask));
+
+			}
+		}
+		else {
+			permissions.put(name, permission);
+		}
+
+		if (!all_allowed) {
+			if (name.equals("*"))
+				all_allowed = true;
+		}
+	}
+
+	/**
+	 * Determines if the specified permissions implies the permissions expressed
+	 * in <code>permission</code>.
+	 * 
+	 * @param permission The Permission object to compare with this
+	 *        <code>BundlePermission</code> object.
+	 * 
+	 * @return <code>true</code> if <code>permission</code> is a proper
+	 *         subset of a permission in the set; <code>false</code>
+	 *         otherwise.
+	 */
+
+	public boolean implies(Permission permission) {
+		if (!(permission instanceof BundlePermission))
+			return (false);
+
+		BundlePermission bp = (BundlePermission) permission;
+		BundlePermission x;
+
+		int desired = bp.getMask();
+		int effective = 0;
+
+		// short circuit if the "*" Permission was added
+		if (all_allowed) {
+			x = (BundlePermission) permissions.get("*");
+			if (x != null) {
+				effective |= x.getMask();
+				if ((effective & desired) == desired)
+					return (true);
+			}
+		}
+
+		// strategy:
+		// Check for full match first. Then work our way up the
+		// name looking for matches on a.b.*
+
+		String name = bp.getName();
+
+		x = (BundlePermission) permissions.get(name);
+
+		if (x != null) {
+			// we have a direct hit!
+			effective |= x.getMask();
+			if ((effective & desired) == desired)
+				return (true);
+		}
+
+		// work our way up the tree...
+		int last, offset;
+
+		offset = name.length() - 1;
+
+		while ((last = name.lastIndexOf(".", offset)) != -1) {
+
+			name = name.substring(0, last + 1) + "*";
+			x = (BundlePermission) permissions.get(name);
+
+			if (x != null) {
+				effective |= x.getMask();
+				if ((effective & desired) == desired)
+					return (true);
+			}
+			offset = last - 1;
+		}
+
+		// we don't have to check for "*" as it was already checked
+		// at the top (all_allowed), so we just return false
+		return (false);
+	}
+
+	/**
+	 * Returns an enumeration of all <code>BundlePermission</code> objects in
+	 * the container.
+	 * 
+	 * @return Enumeration of all <code>BundlePermission</code> objects.
+	 */
+
+	public Enumeration elements() {
+		return (permissions.elements());
+	}
+}
diff --git a/src/org/osgi/framework/Configurable.java b/src/org/osgi/framework/Configurable.java
new file mode 100644
index 0000000..27f5ec2
--- /dev/null
+++ b/src/org/osgi/framework/Configurable.java
@@ -0,0 +1,44 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/Configurable.java,v 1.7 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * Supports a configuration object.
+ *
+ * <p><code>Configurable</code> is an interface that should be used by a bundle developer in support
+ * of a configurable service.
+ * Bundles that need to configure a service may test to determine
+ * if the service object is an <code>instanceof Configurable</code>.
+ *
+ * @version $Revision: 1.7 $
+ * @deprecated Please use the Configuration Admin
+ */
+public abstract interface Configurable
+{
+    /**
+     * Returns this service's configuration object.
+     *
+     * <p>Services implementing <code>Configurable</code> should take care when returning a
+     * service configuration object since this object is probably sensitive.
+     * <p>If the Java Runtime Environment supports permissions, it is recommended that
+     * the caller is checked for the appropriate permission before returning the configuration object.
+     * It is recommended that callers possessing the appropriate
+     * {@link AdminPermission} always be allowed to get the configuration object.
+     *
+     * @return The configuration object for this service.
+     * @exception java.lang.SecurityException If the caller does not have
+     * an appropriate permission and the Java Runtime Environment supports permissions.
+     * @deprecated Please use the Configuration Admin
+     */
+    public abstract Object getConfigurationObject();
+}
+
+
diff --git a/src/org/osgi/framework/Constants.java b/src/org/osgi/framework/Constants.java
new file mode 100644
index 0000000..d4b012f
--- /dev/null
+++ b/src/org/osgi/framework/Constants.java
@@ -0,0 +1,1015 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/Constants.java,v 1.18 2005/06/21 15:45:08 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.Dictionary;
+
+/**
+ * Defines standard names for the OSGi environment property, service property,
+ * and Manifest header attribute keys.
+ * 
+ * <p>
+ * The values associated with these keys are of type <code>java.lang.String</code>,
+ * unless otherwise indicated.
+ * 
+ * @version $Revision: 1.18 $
+ * @since 1.1
+ * @see Bundle#getHeaders()
+ * @see BundleContext#getProperty
+ * @see BundleContext#registerService(String[],Object,Dictionary)
+ */
+
+public interface Constants {
+	/**
+	 * Location identifier of the OSGi <i>system bundle </i>, which is defined
+	 * to be &quot;System Bundle&quot;.
+	 */
+	public static final String	SYSTEM_BUNDLE_LOCATION					= "System Bundle";
+
+	/**
+	 * Alias for the symbolic name of the OSGi <i>system bundle </i>. It is defined
+	 * to be &quot;system.bundle&quot;.
+	 * @since 1.3
+	 */
+	public static final String	SYSTEM_BUNDLE_SYMBOLICNAME				= "system.bundle";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Category&quot;) identifying the
+	 * bundle's category.
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_CATEGORY							= "Bundle-Category";
+
+	/**
+	 * Manifest header (named &quot;Bundle-ClassPath&quot;) identifying a list
+	 * of directories and embedded JAR files, which are bundle resources used
+	 * to extend the bundle's classpath.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_CLASSPATH						= "Bundle-ClassPath";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Copyright&quot;) identifying the
+	 * bundle's copyright information.
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_COPYRIGHT						= "Bundle-Copyright";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Description&quot;) containing a brief
+	 * description of the bundle's functionality.
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_DESCRIPTION						= "Bundle-Description";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Name&quot;) identifying the bundle's
+	 * name.
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_NAME								= "Bundle-Name";
+
+	/**
+	 * Manifest header (named &quot;Bundle-NativeCode&quot;) identifying a
+	 * number of hardware environments and the native language code libraries
+	 * that the bundle is carrying for each of these environments.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_NATIVECODE						= "Bundle-NativeCode";
+
+	/**
+	 * Manifest header (named &quot;Export-Package&quot;) identifying 
+	 * the packages that the bundle offers to the Framework for export.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	EXPORT_PACKAGE							= "Export-Package";
+
+	/**
+	 * Manifest header (named &quot;Export-Service&quot;) identifying the fully
+	 * qualified class names of the services that the bundle may register (used
+	 * for informational purposes only).
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	EXPORT_SERVICE							= "Export-Service";
+
+	/**
+	 * Manifest header (named &quot;Import-Package&quot;) identifying
+	 * the packages on which the bundle depends.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	IMPORT_PACKAGE							= "Import-Package";
+
+	/**
+	 * Manifest header (named &quot;DynamicImport-Package&quot;) identifying
+	 * the packages that the bundle may dynamically import during
+	 * execution.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @since 1.2
+	 */
+	public static final String	DYNAMICIMPORT_PACKAGE					= "DynamicImport-Package";
+
+	/**
+	 * Manifest header (named &quot;Import-Service&quot;) identifying the fully
+	 * qualified class names of the services that the bundle requires (used for
+	 * informational purposes only).
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	IMPORT_SERVICE							= "Import-Service";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Vendor&quot;) identifying the
+	 * bundle's vendor.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_VENDOR							= "Bundle-Vendor";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Version&quot;) identifying the
+	 * bundle's version.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_VERSION							= "Bundle-Version";
+
+	/**
+	 * Manifest header (named &quot;Bundle-DocURL&quot;) identifying the
+	 * bundle's documentation URL, from which further information about the
+	 * bundle may be obtained.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_DOCURL							= "Bundle-DocURL";
+
+	/**
+	 * Manifest header (named &quot;Bundle-ContactAddress&quot;) identifying the
+	 * contact address where problems with the bundle may be reported; for
+	 * example, an email address.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_CONTACTADDRESS					= "Bundle-ContactAddress";
+
+	/**
+	 * Manifest header attribute (named &quot;Bundle-Activator&quot;)
+	 * identifying the bundle's activator class.
+	 * 
+	 * <p>
+	 * If present, this header specifies the name of the bundle resource class
+	 * that implements the <code>BundleActivator</code> interface and whose
+	 * <code>start</code> and <code>stop</code> methods are called by the Framework
+	 * when the bundle is started and stopped, respectively.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_ACTIVATOR						= "Bundle-Activator";
+
+	/**
+	 * Manifest header (named &quot;Bundle-UpdateLocation&quot;) identifying the
+	 * location from which a new bundle version is obtained during a bundle
+	 * update operation.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 */
+	public static final String	BUNDLE_UPDATELOCATION					= "Bundle-UpdateLocation";
+
+	/**
+	 * Manifest header attribute (named &quot;specification-version&quot;)
+	 * identifying the version of a package specified in the Export-Package or
+	 * Import-Package manifest header.
+	 * 
+	 * <p>
+	 * The attribute value is encoded in the Export-Package or Import-Package
+	 * manifest header like:
+	 * 
+	 * <pre>
+	 *  Import-Package: org.osgi.framework ; specification-version=&quot;1.1&quot;
+	 * </pre>
+	 * @deprecated Since 1.3, this has been replaced by {@link #VERSION_ATTRIBUTE}.
+	 */
+	public static final String	PACKAGE_SPECIFICATION_VERSION			= "specification-version";
+
+	/**
+	 * Manifest header attribute (named &quot;processor&quot;) identifying the
+	 * processor required to run native bundle code specified in the
+	 * Bundle-NativeCode manifest header).
+	 * 
+	 * <p>
+	 * The attribute value is encoded in the Bundle-NativeCode manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-NativeCode: http.so ; processor=x86 ...
+	 * </pre>
+	 */
+	public static final String	BUNDLE_NATIVECODE_PROCESSOR				= "processor";
+
+	/**
+	 * Manifest header attribute (named &quot;osname&quot;) identifying the
+	 * operating system required to run native bundle code specified in the
+	 * Bundle-NativeCode manifest header).
+	 * <p>
+	 * The attribute value is encoded in the Bundle-NativeCode manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-NativeCode: http.so ; osname=Linux ...
+	 * </pre>
+	 */
+	public static final String	BUNDLE_NATIVECODE_OSNAME				= "osname";
+
+	/**
+	 * Manifest header attribute (named &quot;osversion&quot;) identifying the
+	 * operating system version required to run native bundle code specified in
+	 * the Bundle-NativeCode manifest header).
+	 * <p>
+	 * The attribute value is encoded in the Bundle-NativeCode manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-NativeCode: http.so ; osversion=&quot;2.34&quot; ...
+	 * </pre>
+	 */
+	public static final String	BUNDLE_NATIVECODE_OSVERSION				= "osversion";
+
+	/**
+	 * Manifest header attribute (named &quot;language&quot;) identifying the
+	 * language in which the native bundle code is written specified in the
+	 * Bundle-NativeCode manifest header. See ISO 639 for possible values.
+	 * <p>
+	 * The attribute value is encoded in the Bundle-NativeCode manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-NativeCode: http.so ; language=nl_be ...
+	 * </pre>
+	 */
+	public static final String	BUNDLE_NATIVECODE_LANGUAGE				= "language";
+
+	/**
+	 * Manifest header (named &quot;Bundle-RequiredExecutionEnvironment&quot;)
+	 * identifying the required execution environment for the bundle. The
+	 * service platform may run this bundle if any of the execution environments
+	 * named in this header matches one of the execution environments it
+	 * implements.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @since 1.2
+	 */
+	public static final String	BUNDLE_REQUIREDEXECUTIONENVIRONMENT		= "Bundle-RequiredExecutionEnvironment";
+
+	/*
+	 * Framework environment properties.
+	 */
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.version&quot;) identifying the Framework
+	 * version.
+	 * 
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 */
+	public static final String	FRAMEWORK_VERSION						= "org.osgi.framework.version";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.vendor&quot;) identifying the Framework
+	 * implementation vendor.
+	 * 
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 */
+	public static final String	FRAMEWORK_VENDOR						= "org.osgi.framework.vendor";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.language&quot;) identifying the Framework
+	 * implementation language (see ISO 639 for possible values).
+	 * 
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 */
+	public static final String	FRAMEWORK_LANGUAGE						= "org.osgi.framework.language";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.os.name&quot;) identifying the Framework
+	 * host-computer's operating system.
+	 * 
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 */
+	public static final String	FRAMEWORK_OS_NAME						= "org.osgi.framework.os.name";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.os.version&quot;) identifying the Framework
+	 * host-computer's operating system version number.
+	 * 
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 */
+	public static final String	FRAMEWORK_OS_VERSION					= "org.osgi.framework.os.version";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.processor&quot;) identifying the Framework
+	 * host-computer's processor name.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 */
+	public static final String	FRAMEWORK_PROCESSOR						= "org.osgi.framework.processor";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.executionenvironment&quot;) identifying
+	 * execution environments provided by the Framework.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.2
+	 */
+	public static final String	FRAMEWORK_EXECUTIONENVIRONMENT			= "org.osgi.framework.executionenvironment";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.bootdelegation&quot;) identifying
+	 * packages for which the Framework must delegate class loading to the boot class path.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	FRAMEWORK_BOOTDELEGATION			= "org.osgi.framework.bootdelegation";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.framework.system.packages&quot;) identifying
+	 * package which the system bundle must export.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	FRAMEWORK_SYSTEMPACKAGES			= "org.osgi.framework.system.packages";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.supports.framework.extension&quot;) identifying
+	 * whether the Framework supports framework extension bundles.
+	 * If the value of this property is <code>true</code>, then the Framework
+	 * supports framework extension bundles.
+	 * The default value is <code>false</code>.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	SUPPORTS_FRAMEWORK_EXTENSION			= "org.osgi.supports.framework.extension";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.supports.bootclasspath.extension&quot;) identifying
+	 * whether the Framework supports bootclasspath extension bundles.
+	 * If the value of this property is <code>true</code>, then the Framework
+	 * supports bootclasspath extension bundles.
+	 * The default value is <code>false</code>.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	SUPPORTS_BOOTCLASSPATH_EXTENSION		= "org.osgi.supports.bootclasspath.extension";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.supports.framework.fragment&quot;) identifying
+	 * whether the Framework supports fragment bundles.
+	 * If the value of this property is <code>true</code>, then the Framework
+	 * supports fragment bundles.
+	 * The default value is <code>false</code>.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	SUPPORTS_FRAMEWORK_FRAGMENT			= "org.osgi.supports.framework.fragment";
+
+	/**
+	 * Framework environment property (named
+	 * &quot;org.osgi.supports.framework.requirebundle&quot;) identifying
+	 * whether the Framework supports the <code>Require-Bundle</code> manifest header.
+	 * If the value of this property is <code>true</code>, then the Framework
+	 * supports the <code>Require-Bundle</code> manifest header.
+	 * The default value is <code>false</code>.
+	 * <p>
+	 * The value of this property may be retrieved by calling the
+	 * <code>BundleContext.getProperty</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	SUPPORTS_FRAMEWORK_REQUIREBUNDLE			= "org.osgi.supports.framework.requirebundle";
+
+	/*
+	 * Service properties.
+	 */
+
+	/**
+	 * Service property (named &quot;objectClass&quot;) identifying all of the
+	 * class names under which a service was registered in the Framework (of
+	 * type <code>java.lang.String[]</code>).
+	 * 
+	 * <p>
+	 * This property is set by the Framework when a service is registered.
+	 */
+	public static final String	OBJECTCLASS								= "objectClass";
+
+	/**
+	 * Service property (named &quot;service.id&quot;) identifying a service's
+	 * registration number (of type <code>java.lang.Long</code>).
+	 * 
+	 * <p>
+	 * The value of this property is assigned by the Framework when a service is
+	 * registered. The Framework assigns a unique value that is larger than all
+	 * previously assigned values since the Framework was started. These values
+	 * are NOT persistent across restarts of the Framework.
+	 */
+	public static final String	SERVICE_ID								= "service.id";
+
+	/**
+	 * Service property (named &quot;service.pid&quot;) identifying a service's
+	 * persistent identifier.
+	 * 
+	 * <p>
+	 * This property may be supplied in the <code>properties</code>
+	 * <code>Dictionary</code>
+	 * object passed to the <code>BundleContext.registerService</code> method.
+	 * 
+	 * <p>
+	 * A service's persistent identifier uniquely identifies the service and
+	 * persists across multiple Framework invocations.
+	 * 
+	 * <p>
+	 * By convention, every bundle has its own unique namespace, starting with
+	 * the bundle's identifier (see {@link Bundle#getBundleId}) and followed by
+	 * a dot (.). A bundle may use this as the prefix of the persistent
+	 * identifiers for the services it registers.
+	 */
+	public static final String	SERVICE_PID								= "service.pid";
+
+	/**
+	 * Service property (named &quot;service.ranking&quot;) identifying a
+	 * service's ranking number (of type <code>java.lang.Integer</code>).
+	 * 
+	 * <p>
+	 * This property may be supplied in the <code>properties
+	 * Dictionary</code>
+	 * object passed to the <code>BundleContext.registerService</code> method.
+	 * 
+	 * <p>
+	 * The service ranking is used by the Framework to determine the <i>default
+	 * </i> service to be returned from a call to the
+	 * {@link BundleContext#getServiceReference}method: If more than one
+	 * service implements the specified class, the <code>ServiceReference</code>
+	 * object with the highest ranking is returned.
+	 * 
+	 * <p>
+	 * The default ranking is zero (0). A service with a ranking of
+	 * <code>Integer.MAX_VALUE</code> is very likely to be returned as the default
+	 * service, whereas a service with a ranking of <code>Integer.MIN_VALUE</code>
+	 * is very unlikely to be returned.
+	 * 
+	 * <p>
+	 * If the supplied property value is not of type <code>java.lang.Integer</code>,
+	 * it is deemed to have a ranking value of zero.
+	 */
+	public static final String	SERVICE_RANKING							= "service.ranking";
+
+	/**
+	 * Service property (named &quot;service.vendor&quot;) identifying a
+	 * service's vendor.
+	 * 
+	 * <p>
+	 * This property may be supplied in the properties <code>Dictionary</code>
+	 * object passed to the <code>BundleContext.registerService</code> method.
+	 */
+	public static final String	SERVICE_VENDOR							= "service.vendor";
+
+	/**
+	 * Service property (named &quot;service.description&quot;) identifying a
+	 * service's description.
+	 * 
+	 * <p>
+	 * This property may be supplied in the properties <code>Dictionary</code>
+	 * object passed to the <code>BundleContext.registerService</code> method.
+	 */
+	public static final String	SERVICE_DESCRIPTION						= "service.description";
+
+	/**
+	 * Manifest header (named &quot;Bundle-SymbolicName&quot;) identifying the
+	 * bundle's symbolic name.
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	BUNDLE_SYMBOLICNAME						= "Bundle-SymbolicName";
+
+	/**
+	 * Manifest header directive (named &quot;singleton&quot;) identifying
+	 * whether a bundle is a singleton. The default value is <code>false</code>.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Bundle-SymbolicName manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-SymbolicName: com.acme.module.test; singleton:=true
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	SINGLETON_DIRECTIVE						= "singleton";
+
+	/**
+	 * Manifest header directive (named &quot;fragment-attachment&quot;)
+	 * identifying if and when a fragment may attach to a host bundle. The
+	 * default value is <code>&quot;always&quot;</code>.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Bundle-SymbolicName manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-SymbolicName: com.acme.module.test; fragment-attachment:=&quot;never&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#FRAGMENT_ATTACHMENT_ALWAYS
+	 * @see Constants#FRAGMENT_ATTACHMENT_RESOLVETIME
+	 * @see Constants#FRAGMENT_ATTACHMENT_NEVER
+	 * @since 1.3
+	 */
+	public final static String	FRAGMENT_ATTACHMENT_DIRECTIVE			= "fragment-attachment";
+
+	/**
+	 * Manifest header directive value (named &quot;always&quot;) identifying a
+	 * fragment attachment type of always. A fragment attachment type of always
+	 * indicates that fragments are allowed to attach to the host bundle at any
+	 * time (while the host is resolved or during the process of resolving the
+	 * host bundle).
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Bundle-SymbolicName manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-SymbolicName: com.acme.module.test; fragment-attachment:=&quot;always&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#FRAGMENT_ATTACHMENT_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	FRAGMENT_ATTACHMENT_ALWAYS				= "always";
+
+	/**
+	 * Manifest header directive value (named &quot;resolve-time&quot;)
+	 * identifying a fragment attachment type of resolve-time. A fragment
+	 * attachment type of resolve-time indicates that fragments are allowed to
+	 * attach to the host bundle only during the process of resolving the host
+	 * bundle.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Bundle-SymbolicName manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-SymbolicName: com.acme.module.test; fragment-attachment:=&quot;resolve-time&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#FRAGMENT_ATTACHMENT_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	FRAGMENT_ATTACHMENT_RESOLVETIME			= "resolve-time";
+
+	/**
+	 * Manifest header directive value (named &quot;never&quot;) identifying a
+	 * fragment attachment type of never. A fragment attachment type of never
+	 * indicates that no fragments are allowed to attach to the host bundle at
+	 * any time.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Bundle-SymbolicName manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Bundle-SymbolicName: com.acme.module.test; fragment-attachment:=&quot;never&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#FRAGMENT_ATTACHMENT_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	FRAGMENT_ATTACHMENT_NEVER				= "never";
+
+	/**
+	 * Manifest header (named &quot;Bundle-Localization&quot;) identifying the
+	 * base name of the bundle's localization entries.
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @see #BUNDLE_LOCALIZATION_DEFAULT_BASENAME
+	 * @since 1.3
+	 */
+	public final static String	BUNDLE_LOCALIZATION						= "Bundle-Localization";
+
+	/**
+	 * Default value for the <code>Bundle-Localization</code> manifest header.
+	 * 
+	 * @see #BUNDLE_LOCALIZATION
+	 * @since 1.3
+	 */
+	public final static String	BUNDLE_LOCALIZATION_DEFAULT_BASENAME	= "OSGI-INF/l10n/bundle";
+
+	/**
+	 * Manifest header (named &quot;Require-Bundle&quot;) identifying the
+	 * symbolic names of other bundles required by the bundle.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	REQUIRE_BUNDLE							= "Require-Bundle";
+
+	/**
+	 * Manifest header attribute (named &quot;bundle-version&quot;) identifying
+	 * a range of versions for a bundle specified in the Require-Bundle or
+	 * Fragment-Host manifest headers. The default value is <code>0.0.0</code>.
+	 * 
+	 * <p>
+	 * The attribute value is encoded in the Require-Bundle manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Require-Bundle: com.acme.module.test; bundle-version=&quot;1.1&quot;
+	 *  Require-Bundle: com.acme.module.test; bundle-version=&quot;[1.0,2.0)&quot;
+	 * </pre>
+	 * 
+	 * <p>
+	 * The bundle-version attribute value uses a mathematical interval notation
+	 * to specify a range of bundle versions. A bundle-version attribute value
+	 * specified as a single version means a version range that includes any
+	 * bundle version greater than or equal to the specified version.
+	 * 
+	 * @since 1.3
+	 */
+	public static final String	BUNDLE_VERSION_ATTRIBUTE				= "bundle-version";
+
+	/**
+	 * Manifest header (named &quot;Fragment-Host&quot;) identifying the
+	 * symbolic name of another bundle for which that the bundle is a fragment.
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	FRAGMENT_HOST							= "Fragment-Host";
+
+	/**
+	 * Manifest header directive (named &quot;multiple-hosts&quot;) identifying
+	 * if the fragment should attach to each bundle selected by the
+	 * Fragment-Host manifest header. The default value is <code>false</code>.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Fragment-Host manifest header like:
+	 * 
+	 * <pre>
+	 *  Fragment-Host: com.acme.module.test; multiple-hosts:=&quot;false&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	MULTIPLE_HOSTS_DIRECTIVE				= "multiple-hosts";
+
+	/**
+	 * Manifest header attribute (named &quot;selection-filter&quot;) is used
+	 * for selection by filtering based upon system properties.
+	 * 
+	 * <p>
+	 * The attribute value is encoded in manifest headers like:
+	 * 
+	 * <pre>
+	 *  Bundle-NativeCode: libgtk.so; selection-filter=&quot;(ws=gtk)&quot;; ...
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	SELECTION_FILTER_ATTRIBUTE				= "selection-filter";
+
+	/**
+	 * Manifest header (named &quot;Bundle-ManifestVersion&quot;) identifying
+	 * the bundle manifest version. A bundle manifest may express the version of
+	 * the syntax in which it is written by specifying a bundle manifest
+	 * version. Bundles exploiting OSGi R4, or later, syntax must specify a
+	 * bundle manifest version.
+	 * <p>
+	 * The bundle manifest version defined by OSGi R4 or, more specifically, by
+	 * V1.3 of the OSGi Framework Specification is "2".
+	 * 
+	 * <p>
+	 * The attribute value may be retrieved from the <code>Dictionary</code>
+	 * object returned by the <code>Bundle.getHeaders</code> method.
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	BUNDLE_MANIFESTVERSION					= "Bundle-ManifestVersion";
+
+	/**
+	 * Manifest header attribute (named &quot;version&quot;) identifying the
+	 * version of a package specified in the Export-Package or Import-Package
+	 * manifest header.
+	 * 
+	 * <p>
+	 * The attribute value is encoded in the Export-Package or Import-Package
+	 * manifest header like:
+	 * 
+	 * <pre>
+	 *  Import-Package: org.osgi.framework; version=&quot;1.1&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	VERSION_ATTRIBUTE						= "version";
+
+	/**
+	 * Manifest header attribute (named &quot;bundle-symbolic-name&quot;)
+	 * identifying the symbolic name of a bundle that exports a package
+	 * specified in the Import-Package manifest header.
+	 * 
+	 * <p>
+	 * The attribute value is encoded in the Import-Package manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Import-Package: org.osgi.framework; bundle-symbolic-name=&quot;com.acme.module.test&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	BUNDLE_SYMBOLICNAME_ATTRIBUTE			= "bundle-symbolic-name";
+
+	/**
+	 * Manifest header directive (named &quot;resolution&quot;) identifying the
+	 * resolution type in the Import-Package or Require-Bundle manifest header.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Import-Package or Require-Bundle
+	 * manifest header like:
+	 * 
+	 * <pre>
+	 *  Import-Package: org.osgi.framework; resolution:=&quot;optional&quot;
+	 *  Require-Bundle: com.acme.module.test; resolution:=&quot;optional&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#RESOLUTION_MANDATORY
+	 * @see Constants#RESOLUTION_OPTIONAL
+	 * @since 1.3
+	 */
+	public final static String	RESOLUTION_DIRECTIVE					= "resolution";
+
+	/**
+	 * Manifest header directive value (named &quot;mandatory&quot;) identifying
+	 * a mandatory resolution type. A mandatory resolution type indicates that
+	 * the import package or require bundle must be resolved when the bundle is
+	 * resolved. If such an import or require bundle cannot be resolved, the
+	 * module fails to resolve.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Import-Package or Require-Bundle
+	 * manifest header like:
+	 * 
+	 * <pre>
+	 *  Import-Package: org.osgi.framework; resolution:=&quot;manditory&quot;
+	 *  Require-Bundle: com.acme.module.test; resolution:=&quot;manditory&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#RESOLUTION_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	RESOLUTION_MANDATORY					= "mandatory";
+
+	/**
+	 * Manifest header directive value (named &quot;optional&quot;) identifying
+	 * an optional resolution type. An optional resolution type indicates that
+	 * the import or require bundle is optional and the bundle may be resolved
+	 * without the import or require bundle being resolved. If the import or
+	 * require bundle is not resolved when the bundle is resolved, the import or
+	 * require bundle may not be resolved before the bundle is refreshed.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Import-Package or Require-Bundle
+	 * manifest header like:
+	 * 
+	 * <pre>
+	 *  Import-Package: org.osgi.framework; resolution:=&quot;optional&quot;
+	 *  Require-Bundle: com.acme.module.test; resolution:=&quot;optional&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#RESOLUTION_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	RESOLUTION_OPTIONAL						= "optional";
+
+	/**
+	 * Manifest header directive (named &quot;uses&quot;) identifying a list of
+	 * packages that an exported package uses.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Export-Package manifest header like:
+	 * 
+	 * <pre>
+	 *  Export-Package: org.osgi.util.tracker; uses:=&quot;org.osgi.framework&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	USES_DIRECTIVE						= "uses";
+
+	/**
+	 * Manifest header directive (named &quot;include&quot;) identifying a list
+	 * of classes and/or resources of the specified package which must be
+	 * allowed to be exported in the Export-Package manifest header.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Export-Package manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Export-Package: org.osgi.framework; include:=&quot;MyStuff*&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	INCLUDE_DIRECTIVE						= "include";
+
+	/**
+	 * Manifest header directive (named &quot;exclude&quot;) identifying a list
+	 * of classes and/or resources of the specified package which must not be
+	 * allowed to be exported in the Export-Package manifest header.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Export-Package manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Export-Package: org.osgi.framework; exclude:=&quot;MyStuff*&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	EXCLUDE_DIRECTIVE						= "exclude";
+
+	/**
+	 * Manifest header directive (named &quot;mandatory&quot;) identifying names
+	 * of matching attributes which must be specified by matching Import-Package
+	 * statements in the Export-Package manifest header.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Export-Package manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Export-Package: org.osgi.framework; mandatory:=&quot;bundle-symbolic-name&quot;
+	 * </pre>
+	 * 
+	 * @since 1.3
+	 */
+	public final static String	MANDATORY_DIRECTIVE						= "mandatory";
+
+	/**
+	 * Manifest header directive (named &quot;visibility&quot;) identifying the
+	 * visibility of a reqiured bundle in the Require-Bundle manifest header.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Require-Bundle manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Require-Bundle: com.acme.module.test; visibility:=&quot;reexport&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#VISIBILITY_PRIVATE
+	 * @see Constants#VISIBILITY_REEXPORT
+	 * @since 1.3
+	 */
+	public final static String	VISIBILITY_DIRECTIVE					= "visibility";
+
+	/**
+	 * Manifest header directive value (named &quot;private&quot;) identifying a
+	 * private visibility type. A private visibility type indicates that any
+	 * packages that are exported by the required bundle are not made visible on
+	 * the export signature of the requiring bundle.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Require-Bundle manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Require-Bundle: com.acme.module.test; visibility:=&quot;private&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#VISIBILITY_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	VISIBILITY_PRIVATE						= "private";
+
+	/**
+	 * Manifest header directive value (named &quot;reexport&quot;) identifying
+	 * a reexport visibility type. A reexport visibility type indicates any
+	 * packages that are exported by the required bundle are re-exported by the
+	 * requiring bundle. Any arbitrary arbitrary matching attributes with which
+	 * they were exported by the required bundle are deleted.
+	 * 
+	 * <p>
+	 * The directive value is encoded in the Require-Bundle manifest header
+	 * like:
+	 * 
+	 * <pre>
+	 *  Require-Bundle: com.acme.module.test; visibility:=&quot;reexport&quot;
+	 * </pre>
+	 * 
+	 * @see Constants#VISIBILITY_DIRECTIVE
+	 * @since 1.3
+	 */
+	public final static String	VISIBILITY_REEXPORT						= "reexport";
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/Filter.java b/src/org/osgi/framework/Filter.java
new file mode 100644
index 0000000..262675f
--- /dev/null
+++ b/src/org/osgi/framework/Filter.java
@@ -0,0 +1,111 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/Filter.java,v 1.11 2005/05/13 20:32:56 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+package org.osgi.framework;
+
+import java.util.Dictionary;
+
+/**
+ * An RFC 1960-based Filter.
+ * <p>
+ * <code>Filter</code> objects can be created by calling
+ * {@link BundleContext#createFilter} with the chosen filter string.
+ * <p>
+ * A <code>Filter</code> object can be used numerous times to determine if the
+ * match argument matches the filter string that was used to create the
+ * <code>Filter</code> object.
+ * <p>
+ * Some 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>
+ * 
+ * @version $Revision: 1.11 $
+ * @since 1.1
+ * @see "Framework specification for a description of the filter string syntax."
+ */
+public interface Filter {
+	/**
+	 * Filter using a service's properties.
+	 * <p>
+	 * The filter is executed using the keys and values of the referenced service's
+	 * properties.  The keys are case insensitively matched with the filter.
+	 * 
+	 * @param reference The reference to the service whose properties are used
+	 *        in the match.
+	 * 
+	 * @return <code>true</code> if the service's properties match this filter;
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean match(ServiceReference reference);
+
+	/**
+	 * Filter using a <code>Dictionary</code> object. The Filter is executed using
+	 * the <code>Dictionary</code> object's keys and values.
+	 * The keys are case insensitively matched with the filter.
+	 * 
+	 * @param dictionary The <code>Dictionary</code> object whose keys are used in
+	 *        the match.
+	 * 
+	 * @return <code>true</code> if the <code>Dictionary</code> object's keys and
+	 *         values match this filter; <code>false</code> otherwise.
+	 * 
+	 * @exception IllegalArgumentException If <code>dictionary</code> contains
+	 *            case variants of the same key name.
+	 */
+	public boolean match(Dictionary dictionary);
+
+	/**
+	 * Returns this <code>Filter</code> object's filter string.
+	 * <p>
+	 * The filter string is normalized by removing whitespace which does not
+	 * affect the meaning of the filter.
+	 * 
+	 * @return Filter string.
+	 */
+	public String toString();
+
+	/**
+	 * Compares this <code>Filter</code> object to another object.
+	 * 
+	 * @param obj The object to compare against this <code>Filter</code> object.
+	 * 
+	 * @return If the other object is a <code>Filter</code> object, then returns
+	 *         <code>this.toString().equals(obj.toString()</code>;<code>false</code>
+	 *         otherwise.
+	 */
+	public boolean equals(Object obj);
+
+	/**
+	 * Returns the hashCode for this <code>Filter</code> object.
+	 * 
+	 * @return The hashCode of the filter string; that is,
+	 *         <code>this.toString().hashCode()</code>.
+	 */
+	public int hashCode();
+
+	/**
+	 * Filter with case sensitivity using a <code>Dictionary</code> object. The
+	 * Filter is executed using the <code>Dictionary</code> object's keys and
+	 * values. The keys are case sensitively matched with the filter.
+	 * 
+	 * @param dictionary The <code>Dictionary</code> object whose keys are used in
+	 *        the match.
+	 * 
+	 * @return <code>true</code> if the <code>Dictionary</code> object's keys and
+	 *         values match this filter; <code>false</code> otherwise.
+	 * 
+	 * @since 1.3
+	 */
+	public boolean matchCase(Dictionary dictionary);
+}
\ No newline at end of file
diff --git a/src/org/osgi/framework/FrameworkEvent.java b/src/org/osgi/framework/FrameworkEvent.java
new file mode 100644
index 0000000..b226dab
--- /dev/null
+++ b/src/org/osgi/framework/FrameworkEvent.java
@@ -0,0 +1,199 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/FrameworkEvent.java,v 1.9 2005/05/13 20:32:56 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.EventObject;
+
+/**
+ * A general Framework event.
+ * 
+ * <p>
+ * <code>FrameworkEvent</code> is the event class used when notifying listeners of
+ * general events occuring within the OSGI environment. A type code is used to
+ * identify the event type for future extendability.
+ * 
+ * <p>
+ * OSGi Alliance reserves the right to extend the set of event types.
+ * 
+ * @version $Revision: 1.9 $
+ */
+
+public class FrameworkEvent extends EventObject {
+	static final long		serialVersionUID	= 207051004521261705L;
+	/**
+	 * Bundle related to the event.
+	 */
+	private Bundle			bundle;
+
+	/**
+	 * Exception related to the event.
+	 */
+	private Throwable		throwable;
+
+	/**
+	 * Type of event.
+	 */
+	private int				type;
+
+	/**
+	 * The Framework has started.
+	 * 
+	 * <p>
+	 * This event is broadcast when the Framework has started after all
+	 * installed bundles that are marked to be started have been started and the
+	 * Framework has reached the intitial start level.
+	 * 
+	 * <p>
+	 * The value of <code>STARTED</code> is 0x00000001.
+	 * 
+	 * @see "<code>StartLevel</code>"
+	 */
+	public final static int	STARTED				= 0x00000001;
+
+	/**
+	 * An error has occurred.
+	 * 
+	 * <p>
+	 * There was an error associated with a bundle.
+	 * 
+	 * <p>
+	 * The value of <code>ERROR</code> is 0x00000002.
+	 */
+	public final static int	ERROR				= 0x00000002;
+
+	/**
+	 * A PackageAdmin.refreshPackage operation has completed.
+	 * 
+	 * <p>
+	 * This event is broadcast when the Framework has completed the refresh
+	 * packages operation initiated by a call to the
+	 * PackageAdmin.refreshPackages method.
+	 * 
+	 * <p>
+	 * The value of <code>PACKAGES_REFRESHED</code> is 0x00000004.
+	 * 
+	 * @since 1.2
+	 * @see "<code>PackageAdmin.refreshPackages</code>"
+	 */
+	public final static int	PACKAGES_REFRESHED	= 0x00000004;
+
+	/**
+	 * A StartLevel.setStartLevel operation has completed.
+	 * 
+	 * <p>
+	 * This event is broadcast when the Framework has completed changing the
+	 * active start level initiated by a call to the StartLevel.setStartLevel
+	 * method.
+	 * 
+	 * <p>
+	 * The value of <code>STARTLEVEL_CHANGED</code> is 0x00000008.
+	 * 
+	 * @since 1.2
+	 * @see "<code>StartLevel</code>"
+	 */
+	public final static int	STARTLEVEL_CHANGED	= 0x00000008;
+
+	/**
+	 * A warning has occurred.
+	 * 
+	 * <p>
+	 * There was a warning associated with a bundle.
+	 * 
+	 * <p>
+	 * The value of <code>WARNING</code> is 0x00000010.
+	 * 
+	 * @since 1.3
+	 */
+	public final static int	WARNING				= 0x00000010;
+
+	/**
+	 * An informational event has occurred.
+	 * 
+	 * <p>
+	 * There was an informational event associated with a bundle.
+	 * 
+	 * <p>
+	 * The value of <code>INFO</code> is 0x00000020.
+	 * 
+	 * @since 1.3
+	 */
+	public final static int	INFO				= 0x00000020;
+
+	/**
+	 * Creates a Framework event.
+	 * 
+	 * @param type The event type.
+	 * @param source The event source object. This may not be <code>null</code>.
+	 * @deprecated Since 1.2. This constructor is deprecated in favor of using
+	 *             the other constructor with the System Bundle as the event
+	 *             source.
+	 */
+	public FrameworkEvent(int type, Object source) {
+		super(source);
+		this.type = type;
+		this.bundle = null;
+		this.throwable = null;
+	}
+
+	/**
+	 * Creates a Framework event regarding the specified bundle.
+	 * 
+	 * @param type The event type.
+	 * @param bundle The event source.
+	 * @param throwable The related exception. This argument may be
+	 *        <code>null</code> if there is no related exception.
+	 */
+	public FrameworkEvent(int type, Bundle bundle, Throwable throwable) {
+		super(bundle);
+		this.type = type;
+		this.bundle = bundle;
+		this.throwable = throwable;
+	}
+
+	/**
+	 * Returns the exception related to this event.
+	 * 
+	 * @return The related exception or <code>null</code> if none.
+	 */
+	public Throwable getThrowable() {
+		return throwable;
+	}
+
+	/**
+	 * Returns the bundle associated with the event. This bundle is also the
+	 * source of the event.
+	 * 
+	 * @return The bundle associated with the event.
+	 */
+	public Bundle getBundle() {
+		return bundle;
+	}
+
+	/**
+	 * Returns the type of framework event.
+	 * <p>
+	 * The type values are:
+	 * <ul>
+	 * <li>{@link #STARTED}
+	 * <li>{@link #ERROR}
+	 * <li>{@link #WARNING}
+	 * <li>{@link #INFO}
+	 * <li>{@link #PACKAGES_REFRESHED}
+	 * <li>{@link #STARTLEVEL_CHANGED}
+	 * </ul>
+	 * 
+	 * @return The type of state change.
+	 */
+
+	public int getType() {
+		return type;
+	}
+}
diff --git a/src/org/osgi/framework/FrameworkListener.java b/src/org/osgi/framework/FrameworkListener.java
new file mode 100644
index 0000000..e97b4e4
--- /dev/null
+++ b/src/org/osgi/framework/FrameworkListener.java
@@ -0,0 +1,39 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/FrameworkListener.java,v 1.6 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.EventListener;
+
+/**
+ * A <code>FrameworkEvent</code> listener.
+ * 
+ * <p>
+ * <code>FrameworkListener</code> is a listener interface that may be implemented
+ * by a bundle developer. A <code>FrameworkListener</code> object is registered
+ * with the Framework using the {@link BundleContext#addFrameworkListener}
+ * method. <code>FrameworkListener</code> objects are called with a
+ * <code>FrameworkEvent</code> objects when the Framework starts and when
+ * asynchronous errors occur.
+ * 
+ * @version $Revision: 1.6 $
+ * @see FrameworkEvent
+ */
+
+public abstract interface FrameworkListener extends EventListener {
+
+	/**
+	 * Receives notification of a general <code>FrameworkEvent</code> object.
+	 * 
+	 * @param event The <code>FrameworkEvent</code> object.
+	 */
+	public abstract void frameworkEvent(FrameworkEvent event);
+}
+
diff --git a/src/org/osgi/framework/FrameworkUtil.java b/src/org/osgi/framework/FrameworkUtil.java
new file mode 100644
index 0000000..6a95479
--- /dev/null
+++ b/src/org/osgi/framework/FrameworkUtil.java
@@ -0,0 +1,48 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/FrameworkUtil.java,v 1.1 2005/07/14 20:32:46 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import org.apache.osgi.framework.FilterImpl;
+
+/**
+ * Framework Utility class.
+ * 
+ * <p>
+ * This class contains utility methods which access Framework functions that may
+ * be useful to bundles.
+ * 
+ * @version $Revision: 1.1 $
+ * @since 1.3
+ */
+public class FrameworkUtil {
+
+	/**
+	 * Creates a <code>Filter</code> object. This <code>Filter</code> object
+	 * may be used to match a <code>ServiceReference</code> object or a
+	 * <code>Dictionary</code> object.
+	 * 
+	 * <p>
+	 * If the filter cannot be parsed, an {@link InvalidSyntaxException} will be
+	 * thrown with a human readable message where the filter became unparsable.
+	 * 
+	 * @param filter The filter string.
+	 * @return A <code>Filter</code> object encapsulating the filter string.
+	 * @throws InvalidSyntaxException If <code>filter</code> contains an
+	 *            invalid filter string that cannot be parsed.
+	 * @throws NullPointerException If <code>filter</code> is null.
+	 * 
+	 * @see Filter
+	 */
+	public static Filter createFilter(String filter)
+			throws InvalidSyntaxException {
+		return new FilterImpl(filter);
+	}
+}
diff --git a/src/org/osgi/framework/InvalidSyntaxException.java b/src/org/osgi/framework/InvalidSyntaxException.java
new file mode 100644
index 0000000..8d1782d
--- /dev/null
+++ b/src/org/osgi/framework/InvalidSyntaxException.java
@@ -0,0 +1,107 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/InvalidSyntaxException.java,v 1.10 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * A Framework exception.
+ * 
+ * <p>
+ * An <code>InvalidSyntaxException</code> object indicates that a filter string
+ * parameter has an invalid syntax and cannot be parsed.
+ * 
+ * <p>
+ * See {@link Filter} for a description of the filter string syntax.
+ * 
+ * @version $Revision: 1.10 $
+ */
+
+public class InvalidSyntaxException extends Exception {
+	static final long	serialVersionUID	= -4295194420816491875L;
+	/**
+	 * The invalid filter string.
+	 */
+	private String		filter;
+	/**
+	 * Nested exception.
+	 */
+	private Throwable	cause;
+
+	/**
+	 * Creates an exception of type <code>InvalidSyntaxException</code>.
+	 * 
+	 * <p>
+	 * This method creates an <code>InvalidSyntaxException</code> object with the
+	 * specified message and the filter string which generated the exception.
+	 * 
+	 * @param msg The message.
+	 * @param filter The invalid filter string.
+	 */
+	public InvalidSyntaxException(String msg, String filter) {
+		super(msg);
+		this.filter = filter;
+		this.cause = null;
+	}
+
+	/**
+	 * Creates an exception of type <code>InvalidSyntaxException</code>.
+	 * 
+	 * <p>
+	 * This method creates an <code>InvalidSyntaxException</code> object with the
+	 * specified message and the filter string which generated the exception.
+	 * 
+	 * @param msg The message.
+	 * @param filter The invalid filter string.
+	 * @param cause The cause of this exception.
+	 * @since 1.3
+	 */
+	public InvalidSyntaxException(String msg, String filter, Throwable cause) {
+		super(msg);
+		this.filter = filter;
+		this.cause = cause;
+	}
+
+	/**
+	 * Returns the filter string that generated the
+	 * <code>InvalidSyntaxException</code> object.
+	 * 
+	 * @return The invalid filter string.
+	 * @see BundleContext#getServiceReferences
+	 * @see BundleContext#addServiceListener(ServiceListener,String)
+	 */
+	public String getFilter() {
+		return filter;
+	}
+
+	/**
+	 * Returns the cause of this exception or <code>null</code> if no cause was
+	 * specified when this exception was created.
+	 * 
+	 * @return The cause of this exception or <code>null</code> if no cause was
+	 *         specified.
+	 * @since 1.3
+	 */
+	public Throwable getCause() {
+		return cause;
+	}
+
+	/**
+	 * The cause of this exception can only be set when constructed.
+	 * 
+	 * @throws java.lang.IllegalStateException This method will always throw an
+	 *         <code>IllegalStateException</code> since the cause of this
+	 *         exception can only be set when constructed.
+	 * @since 1.3
+	 */
+	public Throwable initCause(Throwable cause) {
+		throw new IllegalStateException();
+	}
+}
+
diff --git a/src/org/osgi/framework/PackagePermission.java b/src/org/osgi/framework/PackagePermission.java
new file mode 100644
index 0000000..29d2980
--- /dev/null
+++ b/src/org/osgi/framework/PackagePermission.java
@@ -0,0 +1,534 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/PackagePermission.java,v 1.10 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.io.IOException;
+import java.security.*;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * A bundle's authority to import or export a package.
+ * 
+ * <p>
+ * A package is a dot-separated string that defines a fully qualified Java
+ * package.
+ * <p>
+ * For example:
+ * 
+ * <pre>
+ * <code>
+ * org.osgi.service.http
+ * </code>
+ * </pre>
+ * 
+ * <p>
+ * <code>PackagePermission</code> has two actions: <code>EXPORT</code> and
+ * <code>IMPORT</code>. The <code>EXPORT</code> action implies the <code>IMPORT</code>
+ * action.
+ * 
+ * @version $Revision: 1.10 $
+ */
+
+public final class PackagePermission extends BasicPermission {
+	static final long			serialVersionUID	= -5107705877071099135L;
+	/**
+	 * The action string <code>export</code>.
+	 */
+	public final static String	EXPORT				= "export";
+
+	/**
+	 * The action string <code>import</code>.
+	 */
+	public final static String	IMPORT				= "import";
+
+	private final static int	ACTION_EXPORT		= 0x00000001;
+	private final static int	ACTION_IMPORT		= 0x00000002;
+	private final static int	ACTION_ALL			= ACTION_EXPORT
+															| ACTION_IMPORT;
+	private final static int	ACTION_NONE			= 0;
+	private final static int	ACTION_ERROR		= 0x80000000;
+
+	/**
+	 * The actions mask.
+	 */
+	private transient int		action_mask			= ACTION_NONE;
+
+	/**
+	 * The actions in canonical form.
+	 * 
+	 * @serial
+	 */
+	private String				actions				= null;
+
+	/**
+	 * Defines the authority to import and/or export a package within the OSGi
+	 * environment.
+	 * <p>
+	 * The name is specified as a normal Java package name: a dot-separated
+	 * string. Wildcards may be used. For example:
+	 * 
+	 * <pre>
+	 * 
+	 *  org.osgi.service.http
+	 *  javax.servlet.*
+	 *  *
+	 *  
+	 * </pre>
+	 * 
+	 * <p>
+	 * Package Permissions are granted over all possible versions of a package.
+	 * 
+	 * A bundle that needs to export a package must have the appropriate
+	 * <code>PackagePermission</code> for that package; similarly, a bundle that
+	 * needs to import a package must have the appropriate
+	 * <code>PackagePermssion</code> for that package.
+	 * <p>
+	 * Permission is granted for both classes and resources.
+	 * 
+	 * @param name Package name.
+	 * @param actions <code>EXPORT</code>,<code>IMPORT</code> (canonical order).
+	 */
+
+	public PackagePermission(String name, String actions) {
+		this(name, getMask(actions));
+	}
+
+	/**
+	 * Package private constructor used by PackagePermissionCollection.
+	 * 
+	 * @param name class name
+	 * @param mask action mask
+	 */
+	PackagePermission(String name, int mask) {
+		super(name);
+		init(mask);
+	}
+
+	/**
+	 * Called by constructors and when deserialized.
+	 * 
+	 * @param mask action mask
+	 */
+	private void init(int mask) {
+		if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
+			throw new IllegalArgumentException("invalid action string");
+		}
+
+		action_mask = mask;
+	}
+
+	/**
+	 * Parse action string into action mask.
+	 * 
+	 * @param actions Action string.
+	 * @return action mask.
+	 */
+	private static int getMask(String actions) {
+		boolean seencomma = false;
+
+		int mask = ACTION_NONE;
+
+		if (actions == null) {
+			return (mask);
+		}
+
+		char[] a = actions.toCharArray();
+
+		int i = a.length - 1;
+		if (i < 0)
+			return (mask);
+
+		while (i != -1) {
+			char c;
+
+			// skip whitespace
+			while ((i != -1)
+					&& ((c = a[i]) == ' ' || c == '\r' || c == '\n'
+							|| c == '\f' || c == '\t'))
+				i--;
+
+			// check for the known strings
+			int matchlen;
+
+			if (i >= 5 && (a[i - 5] == 'i' || a[i - 5] == 'I')
+					&& (a[i - 4] == 'm' || a[i - 4] == 'M')
+					&& (a[i - 3] == 'p' || a[i - 3] == 'P')
+					&& (a[i - 2] == 'o' || a[i - 2] == 'O')
+					&& (a[i - 1] == 'r' || a[i - 1] == 'R')
+					&& (a[i] == 't' || a[i] == 'T')) {
+				matchlen = 6;
+				mask |= ACTION_IMPORT;
+
+			}
+			else
+				if (i >= 5 && (a[i - 5] == 'e' || a[i - 5] == 'E')
+						&& (a[i - 4] == 'x' || a[i - 4] == 'X')
+						&& (a[i - 3] == 'p' || a[i - 3] == 'P')
+						&& (a[i - 2] == 'o' || a[i - 2] == 'O')
+						&& (a[i - 1] == 'r' || a[i - 1] == 'R')
+						&& (a[i] == 't' || a[i] == 'T')) {
+					matchlen = 6;
+					mask |= ACTION_EXPORT | ACTION_IMPORT;
+
+				}
+				else {
+					// parse error
+					throw new IllegalArgumentException("invalid permission: "
+							+ actions);
+				}
+
+			// make sure we didn't just match the tail of a word
+			// like "ackbarfimport". Also, skip to the comma.
+			seencomma = false;
+			while (i >= matchlen && !seencomma) {
+				switch (a[i - matchlen]) {
+					case ',' :
+						seencomma = true;
+					/* FALLTHROUGH */
+					case ' ' :
+					case '\r' :
+					case '\n' :
+					case '\f' :
+					case '\t' :
+						break;
+					default :
+						throw new IllegalArgumentException(
+								"invalid permission: " + actions);
+				}
+				i--;
+			}
+
+			// point i at the location of the comma minus one (or -1).
+			i -= matchlen;
+		}
+
+		if (seencomma) {
+			throw new IllegalArgumentException("invalid permission: " + actions);
+		}
+
+		return (mask);
+	}
+
+	/**
+	 * Determines if the specified permission is implied by this object.
+	 * 
+	 * <p>
+	 * This method checks that the package name of the target is implied by the
+	 * package name of this object. The list of <code>PackagePermission</code>
+	 * actions must either match or allow for the list of the target object to
+	 * imply the target <code>PackagePermission</code> action.
+	 * <p>
+	 * The permission to export a package implies the permission to import the
+	 * named package.
+	 * 
+	 * <pre>
+	 *  x.y.*,&quot;export&quot; -&gt; x.y.z,&quot;export&quot; is true
+	 *  *,&quot;import&quot; -&gt; x.y, &quot;import&quot;      is true
+	 *  *,&quot;export&quot; -&gt; x.y, &quot;import&quot;      is true
+	 *  x.y,&quot;export&quot; -&gt; x.y.z, &quot;export&quot;  is false
+	 * </pre>
+	 * 
+	 * @param p The target permission to interrogate.
+	 * @return <code>true</code> if the specified <code>PackagePermission</code>
+	 *         action is implied by this object; <code>false</code> otherwise.
+	 */
+
+	public boolean implies(Permission p) {
+		if (p instanceof PackagePermission) {
+			PackagePermission target = (PackagePermission) p;
+
+			return (((action_mask & target.action_mask) == target.action_mask) && super
+					.implies(p));
+		}
+
+		return (false);
+	}
+
+	/**
+	 * Returns the canonical string representation of the
+	 * <code>PackagePermission</code> actions.
+	 * 
+	 * <p>
+	 * Always returns present <code>PackagePermission</code> actions in the
+	 * following order: <code>EXPORT</code>,<code>IMPORT</code>.
+	 * 
+	 * @return Canonical string representation of the <code>PackagePermission</code>
+	 *         actions.
+	 */
+
+	public String getActions() {
+		if (actions == null) {
+			StringBuffer sb = new StringBuffer();
+			boolean comma = false;
+
+			if ((action_mask & ACTION_EXPORT) == ACTION_EXPORT) {
+				sb.append(EXPORT);
+				comma = true;
+			}
+
+			if ((action_mask & ACTION_IMPORT) == ACTION_IMPORT) {
+				if (comma)
+					sb.append(',');
+				sb.append(IMPORT);
+			}
+
+			actions = sb.toString();
+		}
+
+		return (actions);
+	}
+
+	/**
+	 * Returns a new <code>PermissionCollection</code> object suitable for storing
+	 * <code>PackagePermission</code> objects.
+	 * 
+	 * @return A new <code>PermissionCollection</code> object.
+	 */
+	public PermissionCollection newPermissionCollection() {
+		return (new PackagePermissionCollection());
+	}
+
+	/**
+	 * Determines the equality of two <code>PackagePermission</code> objects.
+	 * 
+	 * This method checks that specified package has the same package name and
+	 * <code>PackagePermission</code> actions as this <code>PackagePermission</code>
+	 * object.
+	 * 
+	 * @param obj The object to test for equality with this
+	 *        <code>PackagePermission</code> object.
+	 * @return <code>true</code> if <code>obj</code> is a <code>PackagePermission</code>,
+	 *         and has the same package name and actions as this
+	 *         <code>PackagePermission</code> object; <code>false</code> otherwise.
+	 */
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return (true);
+		}
+
+		if (!(obj instanceof PackagePermission)) {
+			return (false);
+		}
+
+		PackagePermission p = (PackagePermission) obj;
+
+		return ((action_mask == p.action_mask) && getName().equals(p.getName()));
+	}
+
+	/**
+	 * Returns the hash code value for this object.
+	 * 
+	 * @return A hash code value for this object.
+	 */
+
+	public int hashCode() {
+		return (getName().hashCode() ^ getActions().hashCode());
+	}
+
+	/**
+	 * Returns the current action mask.
+	 * <p>
+	 * Used by the PackagePermissionCollection class.
+	 * 
+	 * @return Current action mask.
+	 */
+	int getMask() {
+		return (action_mask);
+	}
+
+	/**
+	 * WriteObject is called to save the state of this permission object to a
+	 * stream. The actions are serialized, and the superclass takes care of the
+	 * name.
+	 */
+
+	private synchronized void writeObject(java.io.ObjectOutputStream s)
+			throws IOException {
+		// Write out the actions. The superclass takes care of the name
+		// call getActions to make sure actions field is initialized
+		if (actions == null)
+			getActions();
+		s.defaultWriteObject();
+	}
+
+	/**
+	 * readObject is called to restore the state of this permission from a
+	 * stream.
+	 */
+	private synchronized void readObject(java.io.ObjectInputStream s)
+			throws IOException, ClassNotFoundException {
+		// Read in the action, then initialize the rest
+		s.defaultReadObject();
+		init(getMask(actions));
+	}
+}
+
+/**
+ * Stores a set of <code>PackagePermission</code> permissions.
+ * 
+ * @see java.security.Permission
+ * @see java.security.Permissions
+ * @see java.security.PermissionCollection
+ */
+
+final class PackagePermissionCollection extends PermissionCollection {
+	static final long	serialVersionUID	= -3350758995234427603L;
+	/**
+	 * Table of permissions.
+	 * 
+	 * @serial
+	 */
+	private Hashtable	permissions;
+
+	/**
+	 * Boolean saying if "*" is in the collection.
+	 * 
+	 * @serial
+	 */
+	private boolean		all_allowed;
+
+	/**
+	 * Create an empty PackagePermissions object.
+	 *  
+	 */
+
+	public PackagePermissionCollection() {
+		permissions = new Hashtable();
+		all_allowed = false;
+	}
+
+	/**
+	 * Adds a permission to the <code>PackagePermission</code> objects. The key
+	 * for the hash is the name.
+	 * 
+	 * @param permission The <code>PackagePermission</code> object to add.
+	 * 
+	 * @exception IllegalArgumentException If the permission is not a
+	 *            <code>PackagePermission</code> instance.
+	 * 
+	 * @exception SecurityException If this <code>PackagePermissionCollection</code>
+	 *            object has been marked read-only.
+	 */
+
+	public void add(Permission permission) {
+		if (!(permission instanceof PackagePermission))
+			throw new IllegalArgumentException("invalid permission: "
+					+ permission);
+		if (isReadOnly())
+			throw new SecurityException("attempt to add a Permission to a "
+					+ "readonly PermissionCollection");
+
+		PackagePermission pp = (PackagePermission) permission;
+		String name = pp.getName();
+
+		PackagePermission existing = (PackagePermission) permissions.get(name);
+
+		if (existing != null) {
+			int oldMask = existing.getMask();
+			int newMask = pp.getMask();
+			if (oldMask != newMask) {
+				permissions.put(name, new PackagePermission(name, oldMask
+						| newMask));
+
+			}
+		}
+		else {
+			permissions.put(name, permission);
+		}
+
+		if (!all_allowed) {
+			if (name.equals("*"))
+				all_allowed = true;
+		}
+	}
+
+	/**
+	 * Determines if the specified permissions implies the permissions expressed
+	 * in <code>permission</code>.
+	 * 
+	 * @param permission The Permission object to compare with this
+	 *        <code>PackagePermission</code> object.
+	 * 
+	 * @return <code>true</code> if <code>permission</code> is a proper subset of a
+	 *         permission in the set; <code>false</code> otherwise.
+	 */
+
+	public boolean implies(Permission permission) {
+		if (!(permission instanceof PackagePermission))
+			return (false);
+
+		PackagePermission pp = (PackagePermission) permission;
+		PackagePermission x;
+
+		int desired = pp.getMask();
+		int effective = 0;
+
+		// short circuit if the "*" Permission was added
+		if (all_allowed) {
+			x = (PackagePermission) permissions.get("*");
+			if (x != null) {
+				effective |= x.getMask();
+				if ((effective & desired) == desired)
+					return (true);
+			}
+		}
+
+		// strategy:
+		// Check for full match first. Then work our way up the
+		// name looking for matches on a.b.*
+
+		String name = pp.getName();
+
+		x = (PackagePermission) permissions.get(name);
+
+		if (x != null) {
+			// we have a direct hit!
+			effective |= x.getMask();
+			if ((effective & desired) == desired)
+				return (true);
+		}
+
+		// work our way up the tree...
+		int last, offset;
+
+		offset = name.length() - 1;
+
+		while ((last = name.lastIndexOf(".", offset)) != -1) {
+
+			name = name.substring(0, last + 1) + "*";
+			x = (PackagePermission) permissions.get(name);
+
+			if (x != null) {
+				effective |= x.getMask();
+				if ((effective & desired) == desired)
+					return (true);
+			}
+			offset = last - 1;
+		}
+
+		// we don't have to check for "*" as it was already checked
+		// at the top (all_allowed), so we just return false
+		return (false);
+	}
+
+	/**
+	 * Returns an enumeration of all <code>PackagePermission</code> objects in the
+	 * container.
+	 * 
+	 * @return Enumeration of all <code>PackagePermission</code> objects.
+	 */
+
+	public Enumeration elements() {
+		return (permissions.elements());
+	}
+}
+
diff --git a/src/org/osgi/framework/ServiceEvent.java b/src/org/osgi/framework/ServiceEvent.java
new file mode 100644
index 0000000..29d3650
--- /dev/null
+++ b/src/org/osgi/framework/ServiceEvent.java
@@ -0,0 +1,129 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/ServiceEvent.java,v 1.9 2005/05/13 20:32:56 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.Dictionary;
+import java.util.EventObject;
+
+/**
+ * A service lifecycle change event.
+ * <p>
+ * <code>ServiceEvent</code> objects are delivered to a <code>ServiceListener</code>
+ * objects when a change occurs in this service's lifecycle. A type code is used
+ * to identify the event type for future extendability.
+ * 
+ * <p>
+ * OSGi Alliance reserves the right to extend the set of types.
+ * 
+ * @version $Revision: 1.9 $
+ * @see ServiceListener
+ */
+
+public class ServiceEvent extends EventObject {
+	static final long			serialVersionUID	= 8792901483909409299L;
+	/**
+	 * Reference to the service that had a change occur in its lifecycle.
+	 */
+	private ServiceReference	reference;
+
+	/**
+	 * Type of service lifecycle change.
+	 */
+	private int					type;
+
+	/**
+	 * This service has been registered.
+	 * <p>
+	 * This event is synchronously delivered <strong>after </strong> the service
+	 * has been registered with the Framework.
+	 * 
+	 * <p>
+	 * The value of <code>REGISTERED</code> is 0x00000001.
+	 * 
+	 * @see BundleContext#registerService(String[],Object,Dictionary)
+	 */
+	public final static int		REGISTERED			= 0x00000001;
+
+	/**
+	 * The properties of a registered service have been modified.
+	 * <p>
+	 * This event is synchronously delivered <strong>after </strong> the service
+	 * properties have been modified.
+	 * 
+	 * <p>
+	 * The value of <code>MODIFIED</code> is 0x00000002.
+	 * 
+	 * @see ServiceRegistration#setProperties
+	 */
+	public final static int		MODIFIED			= 0x00000002;
+
+	/**
+	 * This service is in the process of being unregistered.
+	 * <p>
+	 * This event is synchronously delivered <strong>before </strong> the
+	 * service has completed unregistering.
+	 * 
+	 * <p>
+	 * If a bundle is using a service that is <code>UNREGISTERING</code>, the
+	 * bundle should release its use of the service when it receives this event.
+	 * If the bundle does not release its use of the service when it receives
+	 * this event, the Framework will automatically release the bundle's use of
+	 * the service while completing the service unregistration operation.
+	 * 
+	 * <p>
+	 * The value of UNREGISTERING is 0x00000004.
+	 * 
+	 * @see ServiceRegistration#unregister
+	 * @see BundleContext#ungetService
+	 */
+	public final static int		UNREGISTERING		= 0x00000004;
+
+	/**
+	 * Creates a new service event object.
+	 * 
+	 * @param type The event type.
+	 * @param reference A <code>ServiceReference</code> object to the service that
+	 *        had a lifecycle change.
+	 */
+	public ServiceEvent(int type, ServiceReference reference) {
+		super(reference);
+		this.reference = reference;
+		this.type = type;
+	}
+
+	/**
+	 * Returns a reference to the service that had a change occur in its
+	 * lifecycle.
+	 * <p>
+	 * This reference is the source of the event.
+	 * 
+	 * @return Reference to the service that had a lifecycle change.
+	 */
+	public ServiceReference getServiceReference() {
+		return (reference);
+	}
+
+	/**
+	 * Returns the type of event. The event type values are:
+	 * <ul>
+	 * <li>{@link #REGISTERED}
+	 * <li>{@link #MODIFIED}
+	 * <li>{@link #UNREGISTERING}
+	 * </ul>
+	 * 
+	 * @return Type of service lifecycle change.
+	 */
+
+	public int getType() {
+		return (type);
+	}
+}
+
diff --git a/src/org/osgi/framework/ServiceFactory.java b/src/org/osgi/framework/ServiceFactory.java
new file mode 100644
index 0000000..04041c3
--- /dev/null
+++ b/src/org/osgi/framework/ServiceFactory.java
@@ -0,0 +1,89 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/ServiceFactory.java,v 1.6 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * Allows services to provide customized service objects in the OSGi
+ * environment.
+ * 
+ * <p>
+ * When registering a service, a <code>ServiceFactory</code> object can be used
+ * instead of a service object, so that the bundle developer can gain control of
+ * the specific service object granted to a bundle that is using the service.
+ * 
+ * <p>
+ * When this happens, the <code>BundleContext.getService(ServiceReference)</code>
+ * method calls the <code>ServiceFactory.getService</code> method to create a
+ * service object specifically for the requesting bundle. The service object
+ * returned by the <code>ServiceFactory</code> object is cached by the Framework
+ * until the bundle releases its use of the service.
+ * 
+ * <p>
+ * When the bundle's use count for the service equals zero (including the bundle
+ * stopping or the service being unregistered), the
+ * <code>ServiceFactory.ungetService</code> method is called.
+ * 
+ * <p>
+ * <code>ServiceFactory</code> objects are only used by the Framework and are not
+ * made available to other bundles in the OSGi environment.
+ * 
+ * @version $Revision: 1.6 $
+ * @see BundleContext#getService
+ */
+
+public abstract interface ServiceFactory {
+	/**
+	 * Creates a new service object.
+	 * 
+	 * <p>
+	 * The Framework invokes this method the first time the specified
+	 * <code>bundle</code> requests a service object using the
+	 * <code>BundleContext.getService(ServiceReference)</code> method. The service
+	 * factory can then return a specific service object for each bundle.
+	 * 
+	 * <p>
+	 * The Framework caches the value returned (unless it is <code>null</code>),
+	 * and will return the same service object on any future call to
+	 * <code>BundleContext.getService</code> from the same bundle.
+	 * 
+	 * <p>
+	 * The Framework will check if the returned service object is an instance of
+	 * all the classes named when the service was registered. If not, then
+	 * <code>null</code> is returned to the bundle.
+	 * 
+	 * @param bundle The bundle using the service.
+	 * @param registration The <code>ServiceRegistration</code> object for the
+	 *        service.
+	 * @return A service object that <strong>must </strong> be an instance of
+	 *         all the classes named when the service was registered.
+	 * @see BundleContext#getService
+	 */
+	public abstract Object getService(Bundle bundle,
+			ServiceRegistration registration);
+
+	/**
+	 * Releases a service object.
+	 * 
+	 * <p>
+	 * The Framework invokes this method when a service has been released by a
+	 * bundle. The service object may then be destroyed.
+	 * 
+	 * @param bundle The bundle releasing the service.
+	 * @param registration The <code>ServiceRegistration</code> object for the
+	 *        service.
+	 * @param service The service object returned by a previous call to the
+	 *        <code>ServiceFactory.getService</code> method.
+	 * @see BundleContext#ungetService
+	 */
+	public abstract void ungetService(Bundle bundle,
+			ServiceRegistration registration, Object service);
+}
+
diff --git a/src/org/osgi/framework/ServiceListener.java b/src/org/osgi/framework/ServiceListener.java
new file mode 100644
index 0000000..699c888
--- /dev/null
+++ b/src/org/osgi/framework/ServiceListener.java
@@ -0,0 +1,51 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/ServiceListener.java,v 1.8 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.EventListener;
+
+/**
+ * A <code>ServiceEvent</code> listener.
+ *
+ * <p><code>ServiceListener</code> is a listener interface that may be implemented by a bundle
+ * developer.
+ * <p>A <code>ServiceListener</code> object is registered with the Framework using the
+ * <code>BundleContext.addServiceListener</code> method.
+ * <code>ServiceListener</code> objects are called with a <code>ServiceEvent</code> object when
+ * a service is registered, modified, or is in the process of unregistering.
+ *
+ * <p><code>ServiceEvent</code> object delivery to <code>ServiceListener</code> objects is filtered by the
+ * filter specified when the listener was registered. If the Java Runtime Environment
+ * supports permissions, then additional filtering is done.
+ * <code>ServiceEvent</code> objects are only delivered to the listener if the bundle which defines
+ * the listener object's class has the appropriate <code>ServicePermission</code> to get the service
+ * using at least one of the named classes the service was registered under.
+ *
+ * <p><code>ServiceEvent</code> object delivery to <code>ServiceListener</code> objects is
+ * further filtered according to package sources as defined in
+ * {@link ServiceReference#isAssignableTo(Bundle, String)}.
+ * 
+ * @version $Revision: 1.8 $
+ * @see ServiceEvent
+ * @see ServicePermission
+ */
+
+public abstract interface ServiceListener extends EventListener
+{
+    /**
+     * Receives notification that a service has had a lifecycle change.
+     *
+     * @param event The <code>ServiceEvent</code> object.
+     */
+    public abstract void serviceChanged(ServiceEvent event);
+}
+
+
diff --git a/src/org/osgi/framework/ServicePermission.java b/src/org/osgi/framework/ServicePermission.java
new file mode 100644
index 0000000..49242ea
--- /dev/null
+++ b/src/org/osgi/framework/ServicePermission.java
@@ -0,0 +1,502 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/ServicePermission.java,v 1.10 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.io.IOException;
+import java.security.*;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * Indicates a bundle's authority to register or get a service.
+ * <ul>
+ * <li>The <code>ServicePermission.REGISTER</code> action allows a bundle to
+ * register a service on the specified names.
+ * <li>The <code>ServicePermission.GET</code> action allows a bundle to detect a
+ * service and get it.
+ * </ul>
+ * Permission to get a service is required in order to detect events regarding
+ * the service. Untrusted bundles should not be able to detect the presence of
+ * certain services unless they have the appropriate <code>ServicePermission</code>
+ * to get the specific service.
+ * 
+ * @version $Revision: 1.10 $
+ */
+
+final public class ServicePermission extends BasicPermission {
+	static final long			serialVersionUID	= -7662148639076511574L;
+	/**
+	 * The action string <code>get</code> (Value is "get").
+	 */
+	public final static String	GET					= "get";
+	/**
+	 * The action string <code>register</code> (Value is "register").
+	 */
+	public final static String	REGISTER			= "register";
+
+	private final static int	ACTION_GET			= 0x00000001;
+	private final static int	ACTION_REGISTER		= 0x00000002;
+	private final static int	ACTION_ALL			= ACTION_GET
+															| ACTION_REGISTER;
+	private final static int	ACTION_NONE			= 0;
+	private final static int	ACTION_ERROR		= 0x80000000;
+
+	/**
+	 * The actions mask.
+	 */
+	private transient int		action_mask			= ACTION_NONE;
+
+	/**
+	 * The actions in canonical form.
+	 * 
+	 * @serial
+	 */
+	private String				actions				= null;
+
+	/**
+	 * Create a new ServicePermission.
+	 * 
+	 * <p>
+	 * The name of the service is specified as a fully qualified class name.
+	 * 
+	 * <pre>
+	 * 
+	 *  ClassName ::= &lt;class name&gt; | &lt;class name ending in &quot;.*&quot;&gt;
+	 *  
+	 * </pre>
+	 * 
+	 * Examples:
+	 * 
+	 * <pre>
+	 *     org.osgi.service.http.HttpService
+	 *     org.osgi.service.http.*
+	 *     org.osgi.service.snmp.*
+	 * </pre>
+	 * 
+	 * <p>
+	 * There are two possible actions: <code>get</code> and <code>register</code>.
+	 * The <code>get</code> permission allows the owner of this permission to
+	 * obtain a service with this name. The <code>register</code> permission
+	 * allows the bundle to register a service under that name.
+	 * 
+	 * @param name class name
+	 * @param actions <code>get</code>,<code>register</code> (canonical order)
+	 */
+
+	public ServicePermission(String name, String actions) {
+		this(name, getMask(actions));
+	}
+
+	/**
+	 * Package private constructor used by ServicePermissionCollection.
+	 * 
+	 * @param name class name
+	 * @param mask action mask
+	 */
+	ServicePermission(String name, int mask) {
+		super(name);
+
+		init(mask);
+	}
+
+	/**
+	 * Called by constructors and when deserialized.
+	 * 
+	 * @param mask action mask
+	 */
+	private void init(int mask) {
+		if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
+			throw new IllegalArgumentException("invalid action string");
+		}
+
+		action_mask = mask;
+	}
+
+	/**
+	 * Parse action string into action mask.
+	 * 
+	 * @param actions Action string.
+	 * @return action mask.
+	 */
+	private static int getMask(String actions) {
+		boolean seencomma = false;
+
+		int mask = ACTION_NONE;
+
+		if (actions == null) {
+			return mask;
+		}
+
+		char[] a = actions.toCharArray();
+
+		int i = a.length - 1;
+		if (i < 0)
+			return mask;
+
+		while (i != -1) {
+			char c;
+
+			// skip whitespace
+			while ((i != -1)
+					&& ((c = a[i]) == ' ' || c == '\r' || c == '\n'
+							|| c == '\f' || c == '\t'))
+				i--;
+
+			// check for the known strings
+			int matchlen;
+
+			if (i >= 2 && (a[i - 2] == 'g' || a[i - 2] == 'G')
+					&& (a[i - 1] == 'e' || a[i - 1] == 'E')
+					&& (a[i] == 't' || a[i] == 'T')) {
+				matchlen = 3;
+				mask |= ACTION_GET;
+
+			}
+			else
+				if (i >= 7 && (a[i - 7] == 'r' || a[i - 7] == 'R')
+						&& (a[i - 6] == 'e' || a[i - 6] == 'E')
+						&& (a[i - 5] == 'g' || a[i - 5] == 'G')
+						&& (a[i - 4] == 'i' || a[i - 4] == 'I')
+						&& (a[i - 3] == 's' || a[i - 3] == 'S')
+						&& (a[i - 2] == 't' || a[i - 2] == 'T')
+						&& (a[i - 1] == 'e' || a[i - 1] == 'E')
+						&& (a[i] == 'r' || a[i] == 'R')) {
+					matchlen = 8;
+					mask |= ACTION_REGISTER;
+
+				}
+				else {
+					// parse error
+					throw new IllegalArgumentException("invalid permission: "
+							+ actions);
+				}
+
+			// make sure we didn't just match the tail of a word
+			// like "ackbarfregister". Also, skip to the comma.
+			seencomma = false;
+			while (i >= matchlen && !seencomma) {
+				switch (a[i - matchlen]) {
+					case ',' :
+						seencomma = true;
+					/* FALLTHROUGH */
+					case ' ' :
+					case '\r' :
+					case '\n' :
+					case '\f' :
+					case '\t' :
+						break;
+					default :
+						throw new IllegalArgumentException(
+								"invalid permission: " + actions);
+				}
+				i--;
+			}
+
+			// point i at the location of the comma minus one (or -1).
+			i -= matchlen;
+		}
+
+		if (seencomma) {
+			throw new IllegalArgumentException("invalid permission: " + actions);
+		}
+
+		return mask;
+	}
+
+	/**
+	 * Determines if a <code>ServicePermission</code> object "implies" the
+	 * specified permission.
+	 * 
+	 * @param p The target permission to check.
+	 * @return <code>true</code> if the specified permission is implied by this
+	 *         object; <code>false</code> otherwise.
+	 */
+
+	public boolean implies(Permission p) {
+		if (p instanceof ServicePermission) {
+			ServicePermission target = (ServicePermission) p;
+
+			return (((action_mask & target.action_mask) == target.action_mask) && super
+					.implies(p));
+		}
+
+		return (false);
+	}
+
+	/**
+	 * Returns the canonical string representation of the actions. Always
+	 * returns present actions in the following order: <code>get</code>,
+	 * <code>register</code>.
+	 * 
+	 * @return The canonical string representation of the actions.
+	 */
+	public String getActions() {
+		if (actions == null) {
+			StringBuffer sb = new StringBuffer();
+			boolean comma = false;
+
+			if ((action_mask & ACTION_GET) == ACTION_GET) {
+				sb.append(GET);
+				comma = true;
+			}
+
+			if ((action_mask & ACTION_REGISTER) == ACTION_REGISTER) {
+				if (comma)
+					sb.append(',');
+				sb.append(REGISTER);
+			}
+
+			actions = sb.toString();
+		}
+
+		return (actions);
+	}
+
+	/**
+	 * Returns a new <code>PermissionCollection</code> object for storing
+	 * <code>ServicePermission<code> objects.
+	 *
+	 * @return A new <code>PermissionCollection</code> object suitable for storing
+	 * <code>ServicePermission</code> objects.
+	 */
+	public PermissionCollection newPermissionCollection() {
+		return (new ServicePermissionCollection());
+	}
+
+	/**
+	 * Determines the equalty of two ServicePermission objects.
+	 * 
+	 * Checks that specified object has the same class name and action as this
+	 * <code>ServicePermission</code>.
+	 * 
+	 * @param obj The object to test for equality.
+	 * @return true if obj is a <code>ServicePermission</code>, and has the same
+	 *         class name and actions as this <code>ServicePermission</code>
+	 *         object; <code>false</code> otherwise.
+	 */
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return (true);
+		}
+
+		if (!(obj instanceof ServicePermission)) {
+			return (false);
+		}
+
+		ServicePermission p = (ServicePermission) obj;
+
+		return ((action_mask == p.action_mask) && getName().equals(p.getName()));
+	}
+
+	/**
+	 * Returns the hash code value for this object.
+	 * 
+	 * @return Hash code value for this object.
+	 */
+
+	public int hashCode() {
+		return (getName().hashCode() ^ getActions().hashCode());
+	}
+
+	/**
+	 * Returns the current action mask. Used by the ServicePermissionCollection
+	 * object.
+	 * 
+	 * @return The actions mask.
+	 */
+	int getMask() {
+		return (action_mask);
+	}
+
+	/**
+	 * WriteObject is called to save the state of this permission to a stream.
+	 * The actions are serialized, and the superclass takes care of the name.
+	 */
+
+	private synchronized void writeObject(java.io.ObjectOutputStream s)
+			throws IOException {
+		// Write out the actions. The superclass takes care of the name
+		// call getActions to make sure actions field is initialized
+		if (actions == null)
+			getActions();
+		s.defaultWriteObject();
+	}
+
+	/**
+	 * readObject is called to restore the state of this permission from a
+	 * stream.
+	 */
+	private synchronized void readObject(java.io.ObjectInputStream s)
+			throws IOException, ClassNotFoundException {
+		// Read in the action, then initialize the rest
+		s.defaultReadObject();
+		init(getMask(actions));
+	}
+}
+
+/**
+ * Stores a set of ServicePermission permissions.
+ * 
+ * @see java.security.Permission
+ * @see java.security.Permissions
+ * @see java.security.PermissionCollection
+ */
+
+final class ServicePermissionCollection extends PermissionCollection {
+	static final long	serialVersionUID	= 662615640374640621L;
+	/**
+	 * Table of permissions.
+	 * 
+	 * @serial
+	 */
+	private Hashtable	permissions;
+
+	/**
+	 * Boolean saying if "*" is in the collection.
+	 * 
+	 * @serial
+	 */
+	private boolean		all_allowed;
+
+	/**
+	 * Creates an empty ServicePermissions object.
+	 *  
+	 */
+
+	public ServicePermissionCollection() {
+		permissions = new Hashtable();
+		all_allowed = false;
+	}
+
+	/**
+	 * Adds a permission to the <code>ServicePermission</code> objects using the
+	 * key for the hash as the name.
+	 * 
+	 * @param permission The Permission object to add.
+	 * 
+	 * @exception IllegalArgumentException If the permission is not a
+	 *            ServicePermission object.
+	 * 
+	 * @exception SecurityException If this <code>ServicePermissionCollection</code>
+	 *            object has been marked read-only.
+	 */
+
+	public void add(Permission permission) {
+		if (!(permission instanceof ServicePermission))
+			throw new IllegalArgumentException("invalid permission: "
+					+ permission);
+		if (isReadOnly())
+			throw new SecurityException("attempt to add a Permission to a "
+					+ "readonly PermissionCollection");
+
+		ServicePermission sp = (ServicePermission) permission;
+		String name = sp.getName();
+
+		ServicePermission existing = (ServicePermission) permissions.get(name);
+
+		if (existing != null) {
+			int oldMask = existing.getMask();
+			int newMask = sp.getMask();
+			if (oldMask != newMask) {
+				permissions.put(name, new ServicePermission(name, oldMask
+						| newMask));
+			}
+		}
+		else {
+			permissions.put(name, permission);
+		}
+
+		if (!all_allowed) {
+			if (name.equals("*"))
+				all_allowed = true;
+		}
+	}
+
+	/**
+	 * Determines if a set of permissions implies the permissions expressed in
+	 * <code>permission</code>.
+	 * 
+	 * @param permission The Permission object to compare.
+	 * 
+	 * @return <code>true</code> if <code>permission</code> is a proper subset of a
+	 *         permission in the set; <code>false</code> otherwise.
+	 */
+
+	public boolean implies(Permission permission) {
+		if (!(permission instanceof ServicePermission))
+			return (false);
+
+		ServicePermission sp = (ServicePermission) permission;
+		ServicePermission x;
+
+		int desired = sp.getMask();
+		int effective = 0;
+
+		// short circuit if the "*" Permission was added
+		if (all_allowed) {
+			x = (ServicePermission) permissions.get("*");
+			if (x != null) {
+				effective |= x.getMask();
+				if ((effective & desired) == desired)
+					return (true);
+			}
+		}
+
+		// strategy:
+		// Check for full match first. Then work our way up the
+		// name looking for matches on a.b.*
+
+		String name = sp.getName();
+
+		x = (ServicePermission) permissions.get(name);
+
+		if (x != null) {
+			// we have a direct hit!
+			effective |= x.getMask();
+			if ((effective & desired) == desired)
+				return (true);
+		}
+
+		// work our way up the tree...
+		int last, offset;
+
+		offset = name.length() - 1;
+
+		while ((last = name.lastIndexOf(".", offset)) != -1) {
+
+			name = name.substring(0, last + 1) + "*";
+			x = (ServicePermission) permissions.get(name);
+
+			if (x != null) {
+				effective |= x.getMask();
+				if ((effective & desired) == desired)
+					return (true);
+			}
+			offset = last - 1;
+		}
+
+		// we don't have to check for "*" as it was already checked
+		// at the top (all_allowed), so we just return false
+		return (false);
+	}
+
+	/**
+	 * Returns an enumeration of all the <code>ServicePermission</code> objects in
+	 * the container.
+	 * 
+	 * @return Enumeration of all the ServicePermission objects.
+	 */
+
+	public Enumeration elements() {
+		return (permissions.elements());
+	}
+}
+
diff --git a/src/org/osgi/framework/ServiceReference.java b/src/org/osgi/framework/ServiceReference.java
new file mode 100644
index 0000000..87b16e5
--- /dev/null
+++ b/src/org/osgi/framework/ServiceReference.java
@@ -0,0 +1,146 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/ServiceReference.java,v 1.11 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.Dictionary;
+
+/**
+ * A reference to a service.
+ * 
+ * <p>
+ * The Framework returns <code>ServiceReference</code> objects from the
+ * <code>BundleContext.getServiceReference</code> and
+ * <code>BundleContext.getServiceReferences</code> methods.
+ * <p>
+ * A <code>ServiceReference</code> object may be shared between bundles and can be
+ * used to examine the properties of the service and to get the service object.
+ * <p>
+ * Every service registered in the Framework has a unique
+ * <code>ServiceRegistration</code> object and may have multiple, distinct
+ * <code>ServiceReference</code> objects referring to it.
+ * <code>ServiceReference</code> objects associated with a
+ * <code>ServiceRegistration</code> object have the same <code>hashCode</code>
+ * and are considered equal (more specifically, their <code>equals()</code>
+ * method will return <code>true</code> when compared).
+ * <p>
+ * If the same service object is registered multiple times,
+ * <code>ServiceReference</code> objects associated with different
+ * <code>ServiceRegistration</code> objects are not equal.
+ * 
+ * @version $Revision: 1.11 $
+ * @see BundleContext#getServiceReference
+ * @see BundleContext#getServiceReferences
+ * @see BundleContext#getService
+ */
+
+public abstract interface ServiceReference {
+	/**
+	 * Returns the property value to which the specified property key is mapped
+	 * in the properties <code>Dictionary</code> object of the service
+	 * referenced by this <code>ServiceReference</code> object.
+	 * 
+	 * <p>
+	 * Property keys are case-insensitive.
+	 * 
+	 * <p>
+	 * This method must continue to return property values after the service has
+	 * been unregistered. This is so references to unregistered services (for
+	 * example, <code>ServiceReference</code> objects stored in the log) can
+	 * still be interrogated.
+	 * 
+	 * @param key The property key.
+	 * @return The property value to which the key is mapped; <code>null</code>
+	 *         if there is no property named after the key.
+	 */
+	public abstract Object getProperty(String key);
+
+	/**
+	 * Returns an array of the keys in the properties <code>Dictionary</code>
+	 * object of the service referenced by this <code>ServiceReference</code>
+	 * object.
+	 * 
+	 * <p>
+	 * This method will continue to return the keys after the service has been
+	 * unregistered. This is so references to unregistered services (for
+	 * example, <code>ServiceReference</code> objects stored in the log) can
+	 * still be interrogated.
+	 * 
+	 * <p>
+	 * This method is <i>case-preserving </i>; this means that every key in the
+	 * returned array must have the same case as the corresponding key in the
+	 * properties <code>Dictionary</code> that was passed to the
+	 * {@link BundleContext#registerService(String[],Object,Dictionary)}or
+	 * {@link ServiceRegistration#setProperties}methods.
+	 * 
+	 * @return An array of property keys.
+	 */
+	public abstract String[] getPropertyKeys();
+
+	/**
+	 * Returns the bundle that registered the service referenced by this
+	 * <code>ServiceReference</code> object.
+	 * 
+	 * <p>
+	 * This method must return <code>null</code> when the service has
+	 * been unregistered. This can be used to determine if the service has been
+	 * unregistered.
+	 * 
+	 * @return The bundle that registered the service referenced by this
+	 *         <code>ServiceReference</code> object; <code>null</code> if
+	 *         that service has already been unregistered.
+	 * @see BundleContext#registerService(String[],Object,Dictionary)
+	 */
+	public abstract Bundle getBundle();
+
+	/**
+	 * Returns the bundles that are using the service referenced by this
+	 * <code>ServiceReference</code> object. Specifically, this method returns
+	 * the bundles whose usage count for that service is greater than zero.
+	 * 
+	 * @return An array of bundles whose usage count for the service referenced
+	 *         by this <code>ServiceReference</code> object is greater than
+	 *         zero; <code>null</code> if no bundles are currently using that
+	 *         service.
+	 * 
+	 * @since 1.1
+	 */
+	public abstract Bundle[] getUsingBundles();
+
+	/**
+	 * Tests if the bundle that registered the service referenced by this
+	 * <code>ServiceReference</code> and the specified bundle use the same source
+	 * for the package of the specified class name.
+	 * <p>
+	 * This method performs the following checks:
+	 * <ol>
+	 * <li>Get the package name from the specified class name.</li>
+	 * <li>For the bundle that registered the service referenced by this
+	 * <code>ServiceReference</code> (registrant bundle); find the source for the
+	 * package. If no source is found then return <code>true</code> if the
+	 * registrant bundle is equal to the specified bundle; otherwise return
+	 * <code>false</code>.</li>
+	 * <li>If the package source of the registrant bundle is equal to the
+	 * package source of the specified bundle then return <code>true</code>;
+	 * otherwise return <code>false</code>.</li>
+	 * </ol>
+	 * 
+	 * @param bundle The <code>Bundle</code> object to check.
+	 * @param className The class name to check.
+	 * @return <code>true</code> if the bundle which registered the service
+	 *         referenced by this <code>ServiceReference</code> and the specified
+	 *         bundle use the same source for the package of the specified class
+	 *         name. Otherwise <code>false</code> is returned.
+	 * 
+	 * @since 1.3
+	 */
+	public abstract boolean isAssignableTo(Bundle bundle, String className);
+
+}
diff --git a/src/org/osgi/framework/ServiceRegistration.java b/src/org/osgi/framework/ServiceRegistration.java
new file mode 100644
index 0000000..5b4f026
--- /dev/null
+++ b/src/org/osgi/framework/ServiceRegistration.java
@@ -0,0 +1,105 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/ServiceRegistration.java,v 1.8 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.Dictionary;
+
+/**
+ * A registered service.
+ * 
+ * <p>
+ * The Framework returns a <code>ServiceRegistration</code> object when a
+ * <code>BundleContext.registerService</code> method invocation is successful.
+ * The <code>ServiceRegistration</code> object is for the private use of the
+ * registering bundle and should not be shared with other bundles.
+ * <p>
+ * The <code>ServiceRegistration</code> object may be used to update the
+ * properties of the service or to unregister the service.
+ * 
+ * @version $Revision: 1.8 $
+ * @see BundleContext#registerService(String[],Object,Dictionary)
+ */
+
+public abstract interface ServiceRegistration {
+	/**
+	 * Returns a <code>ServiceReference</code> object for a service being
+	 * registered.
+	 * <p>
+	 * The <code>ServiceReference</code> object may be shared with other bundles.
+	 * 
+	 * @exception java.lang.IllegalStateException If this
+	 *            <code>ServiceRegistration</code> object has already been
+	 *            unregistered.
+	 * @return <code>ServiceReference</code> object.
+	 */
+	public abstract ServiceReference getReference();
+
+	/**
+	 * Updates the properties associated with a service.
+	 * 
+	 * <p>
+	 * The {@link Constants#OBJECTCLASS} and {@link Constants#SERVICE_ID} keys
+	 * cannot be modified by this method. These values are set by the Framework
+	 * when the service is registered in the OSGi environment.
+	 * 
+	 * <p>
+	 * The following steps are required to modify service properties:
+	 * <ol>
+	 * <li>The service's properties are replaced with the provided properties.
+	 * <li>A service event of type {@link ServiceEvent#MODIFIED} is
+	 * synchronously sent.
+	 * </ol>
+	 * 
+	 * @param properties The properties for this service. See {@link Constants}
+	 *        for a list of standard service property keys. Changes should not
+	 *        be made to this object after calling this method. To update the
+	 *        service's properties this method should be called again.
+	 * 
+	 * @exception IllegalStateException If this <code>ServiceRegistration</code>
+	 *            object has already been unregistered.
+	 * 
+	 * @exception IllegalArgumentException If <code>properties</code> contains
+	 *            case variants of the same key name.
+	 */
+	public abstract void setProperties(Dictionary properties);
+
+	/**
+	 * Unregisters a service. Remove a <code>ServiceRegistration</code> object
+	 * from the Framework service registry. All <code>ServiceReference</code>
+	 * objects associated with this <code>ServiceRegistration</code> object can no
+	 * longer be used to interact with the service.
+	 * 
+	 * <p>
+	 * The following steps are required to unregister a service:
+	 * <ol>
+	 * <li>The service is removed from the Framework service registry so that
+	 * it can no longer be used. <code>ServiceReference</code> objects for the
+	 * service may no longer be used to get a service object for the service.
+	 * <li>A service event of type {@link ServiceEvent#UNREGISTERING} is
+	 * synchronously sent so that bundles using this service can release their
+	 * use of it.
+	 * <li>For each bundle whose use count for this service is greater than
+	 * zero: <br>
+	 * The bundle's use count for this service is set to zero. <br>
+	 * If the service was registered with a {@link ServiceFactory} object, the
+	 * <code>ServiceFactory.ungetService</code> method is called to release the
+	 * service object for the bundle.
+	 * </ol>
+	 * 
+	 * @exception java.lang.IllegalStateException If this
+	 *            <code>ServiceRegistration</code> object has already been
+	 *            unregistered.
+	 * @see BundleContext#ungetService
+	 * @see ServiceFactory#ungetService
+	 */
+	public abstract void unregister();
+}
+
diff --git a/src/org/osgi/framework/SynchronousBundleListener.java b/src/org/osgi/framework/SynchronousBundleListener.java
new file mode 100644
index 0000000..edbe0b3
--- /dev/null
+++ b/src/org/osgi/framework/SynchronousBundleListener.java
@@ -0,0 +1,43 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/SynchronousBundleListener.java,v 1.8 2005/05/13 20:32:54 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2001, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+/**
+ * A synchronous <code>BundleEvent</code> listener.
+ * 
+ * <p>
+ * <code>SynchronousBundleListener</code> is a listener interface that may be
+ * implemented by a bundle developer.
+ * <p>
+ * A <code>SynchronousBundleListener</code> object is registered with the
+ * Framework using the {@link BundleContext#addBundleListener} method.
+ * <code>SynchronousBundleListener</code> objects are called with a
+ * <code>BundleEvent</code> object when a bundle has been installed,
+ * resolved, started, stopped, updated, unresolved, or uninstalled.
+ * <p>
+ * Unlike normal <code>BundleListener</code> objects,
+ * <code>SynchronousBundleListener</code>s are synchronously called during bundle
+ * lifecycle processing. The bundle lifecycle processing will not proceed
+ * until all <code>SynchronousBundleListener</code>s have completed.
+ * <code>SynchronousBundleListener</code> objects will be called prior to
+ * <code>BundleListener</code> objects.
+ * <p>
+ * <code>AdminPermission</code> is required to add or remove a
+ * <code>SynchronousBundleListener</code> object.
+ * 
+ * @version $Revision: 1.8 $
+ * @since 1.1
+ * @see BundleEvent
+ */
+
+public abstract interface SynchronousBundleListener extends BundleListener {
+}
+
diff --git a/src/org/osgi/framework/Version.java b/src/org/osgi/framework/Version.java
new file mode 100644
index 0000000..93e3173
--- /dev/null
+++ b/src/org/osgi/framework/Version.java
@@ -0,0 +1,344 @@
+/*
+ * $Header: /cvshome/build/org.osgi.framework/src/org/osgi/framework/Version.java,v 1.13 2005/05/13 20:32:55 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.framework;
+
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/**
+ * Version identifier for bundles and packages.
+ * 
+ * <p>
+ * Version identifiers have four components.
+ * <ol>
+ * <li>Major version. A non-negative integer.</li>
+ * <li>Minor version. A non-negative integer.</li>
+ * <li>Micro version. A non-negative integer.</li>
+ * <li>Qualifier. A text string. See <code>Version(String)</code> for the
+ * format of the qualifier string.</li>
+ * </ol>
+ * 
+ * <p>
+ * <code>Version</code> objects are immutable.
+ * 
+ * @version $Revision: 1.13 $
+ * @since 1.3
+ */
+
+public class Version implements Comparable {
+	private final int			major;
+	private final int			minor;
+	private final int			micro;
+	private final String		qualifier;
+	private static final String	SEPARATOR		= ".";					//$NON-NLS-1$
+
+	/**
+	 * The empty version "0.0.0". Equivalent to calling
+	 * <code>new Version(0,0,0)</code>.
+	 */
+	public static final Version	emptyVersion	= new Version(0, 0, 0);
+
+	/**
+	 * Creates a version identifier from the specified numerical components.
+	 * 
+	 * <p>
+	 * The qualifier is set to the empty string.
+	 * 
+	 * @param major Major component of the version identifier.
+	 * @param minor Minor component of the version identifier.
+	 * @param micro Micro component of the version identifier.
+	 * @throws IllegalArgumentException If the numerical components are
+	 *         negative.
+	 */
+	public Version(int major, int minor, int micro) {
+		this(major, minor, micro, null);
+	}
+
+	/**
+	 * Creates a version identifier from the specifed components.
+	 * 
+	 * @param major Major component of the version identifier.
+	 * @param minor Minor component of the version identifier.
+	 * @param micro Micro component of the version identifier.
+	 * @param qualifier Qualifier component of the version identifier. If
+	 *        <code>null</code> is specified, then the qualifier will be set
+	 *        to the empty string.
+	 * @throws IllegalArgumentException If the numerical components are negative
+	 *         or the qualifier string is invalid.
+	 */
+	public Version(int major, int minor, int micro, String qualifier) {
+		if (qualifier == null) {
+			qualifier = ""; //$NON-NLS-1$
+		}
+
+		this.major = major;
+		this.minor = minor;
+		this.micro = micro;
+		this.qualifier = qualifier;
+		validate();
+	}
+
+	/**
+	 * Created a version identifier from the specified string.
+	 * 
+	 * <p>
+	 * Here is the grammar for version strings.
+	 * 
+	 * <pre>
+	 *       version ::= major('.'minor('.'micro('.'qualifier)?)?)?
+	 *       major ::= digit+
+	 *       minor ::= digit+
+	 *       micro ::= digit+
+	 *       qualifier ::= (alpha|digit|'_'|'-')+
+	 *       digit ::= [0..9]
+	 *       alpha ::= [a..zA..Z]
+	 * </pre>
+	 * There must be no whitespace in version.
+	 * 
+	 * @param version String representation of the version identifier.
+	 * @throws IllegalArgumentException If <code>version</code> is improperly
+	 *         formatted.
+	 */
+	public Version(String version) {
+		int major = 0;
+		int minor = 0;
+		int micro = 0;
+		String qualifier = ""; //$NON-NLS-1$
+
+		try {
+			StringTokenizer st = new StringTokenizer(version, SEPARATOR, true);
+			major = Integer.parseInt(st.nextToken());
+
+			if (st.hasMoreTokens()) {
+				st.nextToken(); // consume delimiter
+				minor = Integer.parseInt(st.nextToken());
+
+				if (st.hasMoreTokens()) {
+					st.nextToken(); // consume delimiter
+					micro = Integer.parseInt(st.nextToken());
+
+					if (st.hasMoreTokens()) {
+						st.nextToken(); // consume delimiter
+						qualifier = st.nextToken();
+
+						if (st.hasMoreTokens()) {
+							throw new IllegalArgumentException("invalid format"); //$NON-NLS-1$
+						}
+					}
+				}
+			}
+		}
+		catch (NoSuchElementException e) {
+			throw new IllegalArgumentException("invalid format"); //$NON-NLS-1$
+		}
+
+		this.major = major;
+		this.minor = minor;
+		this.micro = micro;
+		this.qualifier = qualifier;
+		validate();
+	}
+
+	/**
+	 * Called by the Version constructors to validate the version components.
+	 * 
+	 * @throws IllegalArgumentException If the numerical components are negative
+	 *         or the qualifier string is invalid.
+	 */
+	private void validate() {
+		if (major < 0) {
+			throw new IllegalArgumentException("negative major"); //$NON-NLS-1$
+		}
+		if (minor < 0) {
+			throw new IllegalArgumentException("negative minor"); //$NON-NLS-1$
+		}
+		if (micro < 0) {
+			throw new IllegalArgumentException("negative micro"); //$NON-NLS-1$
+		}
+		int length = qualifier.length();
+		for (int i = 0; i < length; i++) {
+			if ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".indexOf(qualifier.charAt(i)) == -1) { //$NON-NLS-1$
+				throw new IllegalArgumentException("invalid qualifier"); //$NON-NLS-1$
+			}
+		}
+	}
+
+	/**
+	 * Parses a version identifier from the specified string.
+	 * 
+	 * <p>
+	 * See <code>Version(String)</code> for the format of the version string.
+	 * 
+	 * @param version String representation of the version identifier. Leading
+	 *        and trailing whitespace will be ignored.
+	 * @return A <code>Version</code> object representing the version
+	 *         identifier. If <code>version</code> is <code>null</code> or
+	 *         the empty string then <code>emptyVersion</code> will be
+	 *         returned.
+	 * @throws IllegalArgumentException If <code>version</code> is improperly
+	 *         formatted.
+	 */
+	public static Version parseVersion(String version) {
+		if (version == null) {
+			return emptyVersion;
+		}
+
+		version = version.trim();
+		if (version.length() == 0) {
+			return emptyVersion;
+		}
+
+		return new Version(version);
+	}
+
+	/**
+	 * Returns the major component of this version identifier.
+	 * 
+	 * @return The major component.
+	 */
+	public int getMajor() {
+		return major;
+	}
+
+	/**
+	 * Returns the minor component of this version identifier.
+	 * 
+	 * @return The minor component.
+	 */
+	public int getMinor() {
+		return minor;
+	}
+
+	/**
+	 * Returns the micro component of this version identifier.
+	 * 
+	 * @return The micro component.
+	 */
+	public int getMicro() {
+		return micro;
+	}
+
+	/**
+	 * Returns the qualifier component of this version identifier.
+	 * 
+	 * @return The qualifier component.
+	 */
+	public String getQualifier() {
+		return qualifier;
+	}
+
+	/**
+	 * Returns the string representation of this version identifier.
+	 * 
+	 * <p>
+	 * The format of the version string will be <code>major.minor.micro</code>
+	 * if qualifier is the empty string or
+	 * <code>major.minor.micro.qualifier</code> otherwise.
+	 * 
+	 * @return The string representation of this version identifier.
+	 */
+	public String toString() {
+		String base = major + SEPARATOR + minor + SEPARATOR + micro;
+		if (qualifier.length() == 0) { //$NON-NLS-1$
+			return base;
+		}
+		else {
+			return base + SEPARATOR + qualifier;
+		}
+	}
+
+	/**
+	 * Returns a hash code value for the object.
+	 * 
+	 * @return An integer which is a hash code value for this object.
+	 */
+	public int hashCode() {
+		return (major << 24) + (minor << 16) + (micro << 8)
+				+ qualifier.hashCode();
+	}
+
+	/**
+	 * Compares this <code>Version</code> object to another object.
+	 * 
+	 * <p>
+	 * A version is considered to be <b>equal to </b> another version if the
+	 * major, minor and micro components are equal and the qualifier component
+	 * is equal (using <code>String.equals</code>).
+	 * 
+	 * @param object The <code>Version</code> object to be compared.
+	 * @return <code>true</code> if <code>object</code> is a
+	 *         <code>Version</code> and is equal to this object;
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean equals(Object object) {
+		if (object == this) { // quicktest
+			return true;
+		}
+
+		if (!(object instanceof Version)) {
+			return false;
+		}
+
+		Version other = (Version) object;
+		return (major == other.major) && (minor == other.minor)
+				&& (micro == other.micro) && qualifier.equals(other.qualifier);
+	}
+
+	/**
+	 * Compares this <code>Version</code> object to another object.
+	 * 
+	 * <p>
+	 * A version is considered to be <b>less than </b> another version if its
+	 * major component is less than the other version's major component, or the
+	 * major components are equal and its minor component is less than the other
+	 * version's minor component, or the major and minor components are equal
+	 * and its micro component is less than the other version's micro component,
+	 * or the major, minor and micro components are equal and it's qualifier
+	 * component is less than the other version's qualifier component (using
+	 * <code>String.compareTo</code>).
+	 * 
+	 * <p>
+	 * A version is considered to be <b>equal to</b> another version if the
+	 * major, minor and micro components are equal and the qualifier component
+	 * is equal (using <code>String.compareTo</code>).
+	 * 
+	 * @param object The <code>Version</code> object to be compared.
+	 * @return A negative integer, zero, or a positive integer if this object is
+	 *         less than, equal to, or greater than the specified
+	 *         <code>Version</code> object.
+	 * @throws ClassCastException If the specified object is not a
+	 *         <code>Version</code>.
+	 */
+	public int compareTo(Object object) {
+		if (object == this) { // quicktest
+			return 0;
+		}
+
+		Version other = (Version) object;
+
+		int result = major - other.major;
+		if (result != 0) {
+			return result;
+		}
+
+		result = minor - other.minor;
+		if (result != 0) {
+			return result;
+		}
+
+		result = micro - other.micro;
+		if (result != 0) {
+			return result;
+		}
+
+		return qualifier.compareTo(other.qualifier);
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/condpermadmin/BundleLocationCondition.java b/src/org/osgi/service/condpermadmin/BundleLocationCondition.java
new file mode 100644
index 0000000..62962dd
--- /dev/null
+++ b/src/org/osgi/service/condpermadmin/BundleLocationCondition.java
@@ -0,0 +1,49 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/BundleLocationCondition.java,v 1.9 2005/05/25 16:22:46 twatson Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+package org.osgi.service.condpermadmin;
+
+import java.io.FilePermission;
+import org.osgi.framework.Bundle;
+
+/**
+ * 
+ * Checks to see if a Bundle matches the given location pattern. Pattern matching
+ * is done using FilePermission style patterns.
+ * 
+ * @version $Revision: 1.9 $
+ */
+public class BundleLocationCondition {
+	private static final String CONDITION_TYPE = "org.osgi.service.condpermadmin.BundleLocationCondition";
+	/**
+	 * Constructs a condition that tries to match the passed Bundle's location
+	 * to the location pattern.
+	 * 
+	 * @param bundle the Bundle being evaluated.
+	 * @param info the ConditionInfo to construct the condition for.  The args of the 
+	 *        ConditionInfo specify the location to match the Bundle
+	 *        location to. Matching is done according to the patterns documented
+	 *        in FilePermission.
+	 */
+	static public Condition getCondition(Bundle bundle, ConditionInfo info) {
+		if (!CONDITION_TYPE.equals(info.getType()))
+			throw new IllegalArgumentException("ConditionInfo must be of type \"" + CONDITION_TYPE + "\"");
+		String[] args = info.getArgs();
+		if (args.length != 1)
+			throw new IllegalArgumentException("Illegal number of args: " + args.length);
+		String location = args[0];
+		FilePermission locationPat = new FilePermission(location, "read");
+		FilePermission sourcePat = new FilePermission(bundle.getLocation().toString(), "read");
+		return locationPat.implies(sourcePat) ? Condition.TRUE : Condition.FALSE;
+	}
+
+	private BundleLocationCondition() {
+		// private constructor to prevent objects of this type
+	}
+}
diff --git a/src/org/osgi/service/condpermadmin/BundleSignerCondition.java b/src/org/osgi/service/condpermadmin/BundleSignerCondition.java
new file mode 100644
index 0000000..d62f811
--- /dev/null
+++ b/src/org/osgi/service/condpermadmin/BundleSignerCondition.java
@@ -0,0 +1,66 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/BundleSignerCondition.java,v 1.4 2005/05/25 16:22:46 twatson Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.condpermadmin;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * This condition checks the signer of a bundle. Since the bundle's signer can only change
+ * when the bundle is updated, this condition is immutable.
+ * <p>
+ * The condition expressed using a single String that specifies a Distinguished Name (DN)
+ * chain to match bundle signers against. DN's are encoded using IETF RFC 2253. Usually
+ * signers use certificates that are issued by certificate authorities, which also have a
+ * corresponding DN and certificate. The certificate authorities can form a chain of trust
+ * where the last DN and certificate is known by the framework. The signer of a bundle is
+ * expressed as signers DN followed by the DN of its issuer followed by the DN of the next
+ * issuer until the DN of the root certificate authority. Each DN is separated by a semicolon.
+ * <p>
+ * A bundle can satisfy this condition if one of its signers has a DN chain that matches the
+ * DN chain used to construct this condition.
+ * Wildcards (`*') can be used to allow greater flexibility in specifying the DN chains.
+ * Wildcards can be used in place of DNs, RDNs, or the value in an RDN. If a wildcard is
+ * used for a value of an RDN, the value must be exactly "*" and will match any value for
+ * the corresponding type in that RDN.  If a wildcard is used for a RDN, it must be the
+ * first RDN and will match any number of RDNs (including zero RDNs).   
+ * 
+ * @version $Revision: 1.4 $
+ */
+public class BundleSignerCondition {
+	private static final String CONDITION_TYPE = "org.osgi.service.condpermadmin.BundleSignerCondition";
+	/**
+	 * Constructs a condition that tries to match the passed Bundle's location
+	 * to the location pattern.
+	 * 
+	 * @param bundle the Bundle being evaluated.
+	 * @param info the ConditionInfo to construct the condition for.  The args of the 
+	 *        ConditionInfo specify the chain of distinguished names pattern to match 
+	 *        against the signer of the Bundle
+	 */
+	static public Condition getCondition(Bundle bundle, ConditionInfo info) {
+/*
+		if (!CONDITION_TYPE.equals(info.getType()))
+			throw new IllegalArgumentException("ConditionInfo must be of type \"" + CONDITION_TYPE + "\"");
+		String[] args = info.getArgs();
+		if (args.length != 1)
+			throw new IllegalArgumentException("Illegal number of args: " + args.length);
+		// implementation specific code used here
+		AbstractBundle ab = (AbstractBundle) bundle;
+		return ab.getBundleData().matchDNChain(args[0]) ? Condition.TRUE : Condition.FALSE;
+*/
+        // TODO: Fix BundleSignerCondition.getCondition()
+        return null;
+	}
+
+	private BundleSignerCondition() {
+		// private constructor to prevent objects of this type
+	}
+}
diff --git a/src/org/osgi/service/condpermadmin/Condition.java b/src/org/osgi/service/condpermadmin/Condition.java
new file mode 100644
index 0000000..76dba1c
--- /dev/null
+++ b/src/org/osgi/service/condpermadmin/Condition.java
@@ -0,0 +1,97 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/Condition.java,v 1.9 2005/05/25 16:22:46 twatson Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.condpermadmin;
+
+import java.util.Dictionary;
+
+/**
+ * This interface is used to implement Conditions that are bound to Permissions
+ * using ConditionalPermissionCollection. The Permissions of the
+ * ConditionalPermissionCollection can only be used if the associated Condition
+ * is satisfied.
+ */
+public interface Condition {
+	/**
+	 * A condition object that will always evaluate to true and that is never postponed.
+	 */
+	public final static Condition TRUE = new BooleanCondition(true);
+
+	/**
+	 * A condition object that will always evaluate to false and that is never postponed.
+	 */
+	public final static Condition FALSE = new BooleanCondition(false);
+
+	/**
+	 * This method returns true if the evaluation of the Condition must be postponed
+	 * until the end of the permission check. If it returns false, it must be able
+	 * to directly answer the isSatisfied method. In other
+	 * words, isSatisfied() will return very quickly since no external sources,
+	 * such as for example users, need to be consulted.
+	 * 
+	 * @return false if evaluation is immediate, otherwise true to indicate the evaluation must be postponed.
+	 */
+	boolean isPostponed();
+
+	/**
+	 * This method returns true if the Condition is satisfied.
+	 */
+	boolean isSatisfied();
+
+	/**
+	 * This method returns true if the satisfiability may change.
+	 */
+	boolean isMutable();
+
+	/**
+	 * This method returns true if the set of Conditions are satisfied. Although
+	 * this method is not static, it should be implemented as if it were static.
+	 * All of the passed Conditions will have the same type and will correspond
+	 * to the class type of the object on which this method is invoked.
+	 *
+	 * @param conds the array of Conditions that must be satisfied
+	 * @param context a Dictionary object that implementors can use to track 
+	 * state. If this method is invoked multiple times in the same permission 
+	 * evaluation, the same Dictionary will be passed multiple times. The
+	 * SecurityManager treats this Dictionary as an opaque object simply
+	 * creates an empty dictionary and passes it to subsequent invocations
+	 * if multiple invocatios are needed.
+	 * @return true if all the Conditions are satisfied.
+	 */
+	boolean isSatisfied(Condition conds[], Dictionary context);
+
+	/**
+	 * Package internal class used to define the {@link Condition#FALSE} and 
+	 * {@link Condition#TRUE} constants.
+	 */
+	final static class BooleanCondition implements Condition {
+		boolean satisfied;
+		BooleanCondition(boolean satisfied) {
+			this.satisfied = satisfied;
+		}
+		public boolean isPostponed() {
+			return false;
+		}
+		public boolean isSatisfied() {
+			return satisfied;
+		}
+		public boolean isMutable() {
+			return false;
+		}
+		public boolean isSatisfied(Condition[] conds, Dictionary context) {
+			for(int i = 0; i < conds.length; i++) {
+				if (!conds[i].isSatisfied())
+					return false;
+			}
+			return true;
+		}
+		
+	}
+}
diff --git a/src/org/osgi/service/condpermadmin/ConditionInfo.java b/src/org/osgi/service/condpermadmin/ConditionInfo.java
new file mode 100644
index 0000000..6207c4f
--- /dev/null
+++ b/src/org/osgi/service/condpermadmin/ConditionInfo.java
@@ -0,0 +1,314 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/ConditionInfo.java,v 1.6 2005/05/13 20:33:31 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.condpermadmin;
+
+import java.util.Vector;
+
+/**
+ * Condition representation used by the Conditional Permission Admin service.
+ * 
+ * <p>
+ * This class encapsulates two pieces of information: a Condition <i>type</i>
+ * (class name), which must implement <tt>Condition</tt>, and the arguments
+ * passed to its constructor.
+ * 
+ * <p>
+ * In order for a Condition represented by a <tt>ConditionInfo</tt> to be
+ * instantiated and considered during a permission check, its Condition class
+ * must be available from the system classpath.
+ * 
+ */
+
+public class ConditionInfo {
+
+	private String type;
+
+	private String args[];
+
+	/**
+	 * Constructs a <tt>ConditionInfo</tt> from the given type and args.
+	 * 
+	 * @param type
+	 *            The fully qualified class name of the condition represented by
+	 *            this <tt>ConditionInfo</tt>. The class must implement
+	 *            <tt>Condition</tt> and must define a constructor that takes
+	 *            a <tt>Bundle</tt> and the correct number of argument
+	 *            strings.
+	 * 
+	 * @param args
+	 *            The arguments that will be passed to the constructor of the
+	 *            <tt>Conditon</tt> class identified by <tt>type</tt>.
+	 * 
+	 * @exception java.lang.NullPointerException
+	 *                if <tt>type</tt> is <tt>null</tt>.
+	 */
+	public ConditionInfo(String type, String args[]) {
+		this.type = type;
+		this.args = args;
+		if (type == null) {
+			throw new NullPointerException("type is null");
+		}
+	}
+
+	/**
+	 * Constructs a <tt>ConditionInfo</tt> object from the given encoded
+	 * <tt>ConditionInfo</tt> string.
+	 * 
+	 * @param encodedCondition
+	 *            The encoded <tt>ConditionInfo</tt>.
+	 * @see #getEncoded
+	 * @exception java.lang.IllegalArgumentException
+	 *                if <tt>encodedCondition</tt> is not properly formatted.
+	 */
+	public ConditionInfo(String encodedCondition) {
+		if (encodedCondition == null) {
+			throw new NullPointerException("missing encoded permission");
+		}
+		if (encodedCondition.length() == 0) {
+			throw new IllegalArgumentException("empty encoded permission");
+		}
+
+		try {
+			char[] encoded = encodedCondition.toCharArray();
+
+			/* the first character must be '[' */
+			if (encoded[0] != '[') {
+				throw new IllegalArgumentException(
+						"first character not open bracket");
+			}
+
+			/* type is not quoted or encoded */
+			int end = 1;
+			int begin = end;
+
+			while ((encoded[end] != ' ') && (encoded[end] != ')')) {
+				end++;
+			}
+
+			if (end == begin) {
+				throw new IllegalArgumentException("expecting type");
+			}
+
+			this.type = new String(encoded, begin, end - begin);
+
+			Vector args = new Vector();
+			/* type may be followed by name which is quoted and encoded */
+			while (encoded[end] == ' ') {
+				end++;
+
+				if (encoded[end] != '"') {
+					throw new IllegalArgumentException("expecting quoted name");
+				}
+
+				end++;
+				begin = end;
+
+				while (encoded[end] != '"') {
+					if (encoded[end] == '\\') {
+						end++;
+					}
+
+					end++;
+				}
+
+				args.add(decodeString(encoded, begin, end));
+				end++;
+			}
+			this.args = (String[]) args.toArray(new String[0]);
+			/* the final character must be ')' */
+			if ((encoded[end] != ']') || (end + 1 != encoded.length)) {
+				throw new IllegalArgumentException("last character not "
+						+ "close bracket");
+			}
+		} catch (ArrayIndexOutOfBoundsException e) {
+			throw new IllegalArgumentException("parsing terminated abruptly");
+		}
+	}
+
+	/**
+	 * Returns the string encoding of this <tt>ConditionInfo</tt> in a form
+	 * suitable for restoring this <tt>ConditionInfo</tt>.
+	 * 
+	 * <p>
+	 * The encoding format is:
+	 * 
+	 * <pre>
+	 * 
+	 *  [type &quot;arg0&quot; &quot;arg1&quot; ...]
+	 *  
+	 * </pre>
+	 * 
+	 * where <i>argX</i> are strings that are encoded for proper parsing.
+	 * Specifically, the <tt>"</tt>, <tt>\</tt>, carriage return, and
+	 * linefeed characters are escaped using <tt>\"</tt>, <tt>\\</tt>,
+	 * <tt>\r</tt>, and <tt>\n</tt>, respectively.
+	 * 
+	 * <p>
+	 * The encoded string must contain no leading or trailing whitespace
+	 * characters. A single space character must be used between type and "<i>arg0</i>"
+	 * and between all arguments.
+	 * 
+	 * @return The string encoding of this <tt>ConditionInfo</tt>.
+	 */
+	public final String getEncoded() {
+		StringBuffer output = new StringBuffer();
+		output.append('[');
+		output.append(type);
+
+		for (int i = 0; i < args.length; i++) {
+			output.append(" \"");
+			encodeString(args[i], output);
+			output.append('\"');
+		}
+
+		output.append(']');
+
+		return (output.toString());
+	}
+
+	/**
+	 * Returns the string representation of this <tt>ConditionInfo</tt>. The
+	 * string is created by calling the <tt>getEncoded</tt> method on this
+	 * <tt>ConditionInfo</tt>.
+	 * 
+	 * @return The string representation of this <tt>ConditionInfo</tt>.
+	 */
+	public String toString() {
+		return (getEncoded());
+	}
+
+	/**
+	 * Returns the fully qualified class name of the condition represented by
+	 * this <tt>ConditionInfo</tt>.
+	 * 
+	 * @return The fully qualified class name of the condition represented by
+	 *         this <tt>ConditionInfo</tt>.
+	 */
+	public final String getType() {
+		return (type);
+	}
+
+	/**
+	 * Returns arguments of this <tt>ConditionInfo</tt>.
+	 * 
+	 * @return The arguments of this <tt>ConditionInfo</tt>. have a name.
+	 */
+	public final String[] getArgs() {
+		return (args);
+	}
+
+	/**
+	 * Determines the equality of two <tt>ConditionInfo</tt> objects.
+	 * 
+	 * This method checks that specified object has the same type and args as
+	 * this <tt>ConditionInfo</tt> object.
+	 * 
+	 * @param obj
+	 *            The object to test for equality with this
+	 *            <tt>ConditionInfo</tt> object.
+	 * @return <tt>true</tt> if <tt>obj</tt> is a <tt>ConditionInfo</tt>,
+	 *         and has the same type and args as this <tt>ConditionInfo</tt>
+	 *         object; <tt>false</tt> otherwise.
+	 */
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return (true);
+		}
+
+		if (!(obj instanceof ConditionInfo)) {
+			return (false);
+		}
+
+		ConditionInfo other = (ConditionInfo) obj;
+
+		if (!type.equals(other.type) || args.length != other.args.length)
+			return false;
+
+		for (int i = 0; i < args.length; i++) {
+			if (!args[i].equals(other.args[i]))
+				return false;
+		}
+		return true;
+	}
+
+	/**
+	 * Returns the hash code value for this object.
+	 * 
+	 * @return A hash code value for this object.
+	 */
+
+	public int hashCode() {
+		int hash = type.hashCode();
+
+		for (int i = 0; i < args.length; i++) {
+			hash ^= args[i].hashCode();
+		}
+		return (hash);
+	}
+
+	/**
+	 * This escapes the quotes, backslashes, \n, and \r in the string using a
+	 * backslash and appends the newly escaped string to a StringBuffer.
+	 */
+	private static void encodeString(String str, StringBuffer output) {
+		int len = str.length();
+
+		for (int i = 0; i < len; i++) {
+			char c = str.charAt(i);
+
+			switch (c) {
+			case '"':
+			case '\\':
+				output.append('\\');
+				output.append(c);
+				break;
+			case '\r':
+				output.append("\\r");
+				break;
+			case '\n':
+				output.append("\\n");
+				break;
+			default:
+				output.append(c);
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Takes an encoded character array and decodes it into a new String.
+	 */
+	private static String decodeString(char[] str, int begin, int end) {
+		StringBuffer output = new StringBuffer(end - begin);
+
+		for (int i = begin; i < end; i++) {
+			char c = str[i];
+
+			if (c == '\\') {
+				i++;
+
+				if (i < end) {
+					c = str[i];
+
+					if (c == 'n') {
+						c = '\n';
+					} else if (c == 'r') {
+						c = '\r';
+					}
+				}
+			}
+
+			output.append(c);
+		}
+
+		return (output.toString());
+	}
+}
diff --git a/src/org/osgi/service/condpermadmin/ConditionalPermissionAdmin.java b/src/org/osgi/service/condpermadmin/ConditionalPermissionAdmin.java
new file mode 100644
index 0000000..16fd7da
--- /dev/null
+++ b/src/org/osgi/service/condpermadmin/ConditionalPermissionAdmin.java
@@ -0,0 +1,89 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/ConditionalPermissionAdmin.java,v 1.6 2005/07/14 10:47:13 pkriens Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.condpermadmin;
+
+import java.security.AccessControlContext;
+import java.util.Enumeration;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * This is a framework service that allows ConditionalPermissionInfos to be
+ * added to, retrieved from, and removed from the framework.
+ * 
+ * @version $Revision: 1.6 $
+ */
+public interface ConditionalPermissionAdmin {
+	/**
+	 * Add a new Conditional Permission Info to the repository.
+	 * 
+	 * The Conditional Permission Info will be given a unique, never reused name.
+	 * 
+	 * @param conds the Conditions that need to be satisfied to enable the
+	 *        corresponding Permissions.
+	 * @param perms the Permissions that are enable when the corresponding
+	 *        Conditions are satisfied.
+	 * @return the ConditionalPermissionInfo that for the newly added Conditions
+	 *         and Permissions.
+	 */
+	ConditionalPermissionInfo addConditionalPermissionInfo(
+			ConditionInfo conds[], PermissionInfo perms[]);
+
+	/**
+	 * Set or create a Conditional Permission Info with conditions and
+	 * permissions.
+	 * 
+	 * If the given <code>name</code> is null or not used in the repository
+	 * yet, a new Conditional Permission Info must be created, otherwise the
+	 * existing Conditional Permission Info must be reused.
+	 * 
+	 * @param name the name of this Conditional Permission Info, or
+	 *        <code>null</code>.
+	 * @param conds the Conditions that need to be satisfied to enable the
+	 *        corresponding Permissions.
+	 * @param perms the Permissions that are enable when the corresponding
+	 *        Conditions are satisfied.
+	 * @return the ConditionalPermissionInfo that for the newly added Conditions
+	 *         and Permissions.
+	 */
+	ConditionalPermissionInfo setConditionalPermissionInfo(String name,
+			ConditionInfo conds[], PermissionInfo perms[]);
+
+	/**
+	 * Returns the ConditionalPermissionInfos that are currently managed by
+	 * ConditionalPermissionAdmin. The Enumeration is made up of
+	 * ConditionalPermissionInfos. Calling ConditionalPermissionInfo.delete()
+	 * will remove the ConditionalPermissionInfo from
+	 * ConditionalPermissionAdmin.
+	 * 
+	 * @return the ConditionalPermissionInfos that are currently managed by
+	 *         ConditionalPermissionAdmin. The Enumeration is made up of
+	 *         ConditionalPermissionInfos.
+	 */
+	Enumeration getConditionalPermissionInfos();
+
+	/**
+	 * Return the the Conditional Permission Info with the given name.
+	 * 
+	 * @param name the name of the Conditional Permission Info that must be
+	 *        returned
+	 */
+	ConditionalPermissionInfo getConditionalPermissionInfo(String name);
+
+	/**
+	 * Returns the AccessControlContext that corresponds to the given signers.
+	 * 
+	 * @param signers the signers that will be checked agains
+	 *        BundleSignerCondition.
+	 * @return an AccessControlContext that has the Permissions associated with
+	 *         the signer.
+	 */
+	AccessControlContext getAccessControlContext(String signers[]);
+}
diff --git a/src/org/osgi/service/condpermadmin/ConditionalPermissionInfo.java b/src/org/osgi/service/condpermadmin/ConditionalPermissionInfo.java
new file mode 100644
index 0000000..c39e4f5
--- /dev/null
+++ b/src/org/osgi/service/condpermadmin/ConditionalPermissionInfo.java
@@ -0,0 +1,45 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.condpermadmin/src/org/osgi/service/condpermadmin/ConditionalPermissionInfo.java,v 1.7 2005/07/14 10:47:13 pkriens Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+ 
+package org.osgi.service.condpermadmin;
+
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * This interface describes a binding of a set of Conditions to a set of
+ * Permissions. Instances of this interface are obtained from the
+ * ConditionalPermissionAdmin service. This interface is also used to remove
+ * ConditionalPermissionCollections from ConditionPermissionAdmin.
+ */
+public interface ConditionalPermissionInfo {
+	/**
+	 * Returns the ConditionInfos for the Conditions that must be satisfied to
+	 * enable this ConditionalPermissionCollection.
+	 */
+	ConditionInfo[] getConditionInfos();
+
+	/**
+	 * Returns the PermissionInfos for the Permission in this
+	 * ConditionalPermissionCollection.
+	 */
+	PermissionInfo[] getPermissionInfos();
+
+	/**
+	 * Removes the ConditionalPermissionCollection from the
+	 * ConditionalPermissionAdmin.
+	 */
+	void delete();
+	
+	/**
+	 * Return the name of this Conditional Permission Info object.
+	 * 
+	 */
+	String getName();
+}
diff --git a/src/org/osgi/service/packageadmin/ExportedPackage.java b/src/org/osgi/service/packageadmin/ExportedPackage.java
new file mode 100644
index 0000000..ac6f362
--- /dev/null
+++ b/src/org/osgi/service/packageadmin/ExportedPackage.java
@@ -0,0 +1,90 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.packageadmin/src/org/osgi/service/packageadmin/ExportedPackage.java,v 1.8 2005/05/13 20:32:34 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2001, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.packageadmin;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * An exported package.
+ * 
+ * Instances implementing this interface are created by the Package Admin
+ * service.
+ * 
+ * <p>
+ * The information about an exported package provided by this object is valid
+ * only until the next time <code>PackageAdmin.refreshPackages()</code> is called.
+ * If an <code>ExportedPackage</code> object becomes stale (that is, the package
+ * it references has been updated or removed as a result of calling
+ * <code>PackageAdmin.refreshPackages()</code>), its <code>getName()</code> and
+ * <code>getSpecificationVersion()</code> continue to return their old values,
+ * <code>isRemovalPending()</code> returns <code>true</code>, and
+ * <code>getExportingBundle()</code> and <code>getImportingBundles()</code> return
+ * <code>null</code>.
+ * 
+ * @version $Revision: 1.8 $
+ */
+public interface ExportedPackage {
+	/**
+	 * Returns the name of the package associated with this
+	 * <code>ExportedPackage</code> object.
+	 * 
+	 * @return The name of this <code>ExportedPackage</code> object.
+	 */
+	public String getName();
+
+	/**
+	 * Returns the bundle exporting the package associated with this
+	 * <code>ExportedPackage</code> object.
+	 * 
+	 * @return The exporting bundle, or <code>null</code> if this
+	 *         <code>ExportedPackage</code> object has become stale.
+	 */
+	public Bundle getExportingBundle();
+
+	/**
+	 * Returns the resolved bundles that are currently importing the package
+	 * associated with this <code>ExportedPackage</code> object.
+	 * 
+	 * <p>
+	 * Bundles which require the exporting bundle associated with this
+	 * <code>ExportedPackage</code> object are considered to be importing bundles
+	 * and are included in the returned array. See
+	 * {@link RequiredBundle#getRequiringBundles()}
+	 * 
+	 * @return The array of resolved bundles currently importing the package
+	 *         associated with this <code>ExportedPackage</code> object, or
+	 *         <code>null</code> if this <code>ExportedPackage</code> object has
+	 *         become stale.
+	 */
+	public Bundle[] getImportingBundles();
+
+	/**
+	 * Returns the specification version of this <code>ExportedPackage</code>, as
+	 * specified in the exporting bundle's manifest file.
+	 * 
+	 * @return The specification version of this <code>ExportedPackage</code>
+	 *         object, or <code>null</code> if no version information is
+	 *         available.
+	 */
+	public String getSpecificationVersion();
+
+	/**
+	 * Returns <code>true</code> if the package associated with this
+	 * <code>ExportedPackage</code> object has been exported by a bundle that has
+	 * been updated or uninstalled.
+	 * 
+	 * @return <code>true</code> if the associated package is being exported by a
+	 *         bundle that has been updated or uninstalled, or if this
+	 *         <code>ExportedPackage</code> object has become stale;
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean isRemovalPending();
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/packageadmin/PackageAdmin.java b/src/org/osgi/service/packageadmin/PackageAdmin.java
new file mode 100644
index 0000000..6c5307c
--- /dev/null
+++ b/src/org/osgi/service/packageadmin/PackageAdmin.java
@@ -0,0 +1,289 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.packageadmin/src/org/osgi/service/packageadmin/PackageAdmin.java,v 1.10 2005/05/13 20:32:34 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2001, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.packageadmin;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * Framework service which allows bundle programmers to inspect the packages
+ * exported in the Framework and eagerly update or uninstall bundles.
+ * 
+ * If present, there will only be a single instance of this service registered
+ * with the Framework.
+ * 
+ * <p>
+ * The term <i>exported package </i> (and the corresponding interface
+ * {@link ExportedPackage})refers to a package that has actually been exported
+ * (as opposed to one that is available for export).
+ * 
+ * <p>
+ * The information about exported packages returned by this service is valid
+ * only until the next time {@link #refreshPackages}is called. If an
+ * <code>ExportedPackage</code> object becomes stale, (that is, the package it
+ * references has been updated or removed as a result of calling
+ * <code>PackageAdmin.refreshPackages()</code>), its <code>getName()</code> and
+ * <code>getSpecificationVersion()</code> continue to return their old values,
+ * <code>isRemovalPending()</code> returns <code>true</code>, and
+ * <code>getExportingBundle()</code> and <code>getImportingBundles()</code> return
+ * <code>null</code>.
+ * 
+ * @version $Revision: 1.10 $
+ */
+public interface PackageAdmin {
+	/**
+	 * Gets the packages exported by the specified bundle.
+	 * 
+	 * @param bundle The bundle whose exported packages are to be returned, or
+	 *        <code>null</code> if all the packages currently exported in the
+	 *        Framework are to be returned. If the specified bundle is the
+	 *        system bundle (that is, the bundle with id zero), this method
+	 *        returns all the packages on the system classpath whose name does
+	 *        not start with "java.". In an environment where the exhaustive
+	 *        list of packages on the system classpath is not known in advance,
+	 *        this method will return all currently known packages on the system
+	 *        classpath, that is, all packages on the system classpath that
+	 *        contains one or more classes that have been loaded.
+	 * 
+	 * @return The array of packages exported by the specified bundle, or
+	 *         <code>null</code> if the specified bundle has not exported any
+	 *         packages.
+	 */
+	public ExportedPackage[] getExportedPackages(Bundle bundle);
+
+	/**
+	 * Gets the <code>ExportedPackage</code> object with the specified package
+	 * name. All exported packages will be checked for the specified name. The
+	 * exported package with the highest version will be returned.
+	 * <p>
+	 * In an environment where the exhaustive list of packages on the system
+	 * classpath is not known in advance, this method attempts to see if the
+	 * named package is on the system classpath. This means that this method may
+	 * discover an <code>ExportedPackage</code> object that was not present in the
+	 * list returned by a prior call to <code>getExportedPackages()</code>.
+	 * 
+	 * @param name The name of the exported package to be returned.
+	 * 
+	 * @return The exported package with the specified name, or <code>null</code>
+	 *         if no exported packages with that name exists.
+	 */
+	public ExportedPackage getExportedPackage(String name);
+
+	/**
+	 * Forces the update (replacement) or removal of packages exported by the
+	 * specified bundles.
+	 * 
+	 * <p>
+	 * If no bundles are specified, this method will update or remove any
+	 * packages exported by any bundles that were previously updated or
+	 * uninstalled since the last call to this method. The technique by which
+	 * this is accomplished may vary among different Framework implementations.
+	 * One permissible implementation is to stop and restart the Framework.
+	 * 
+	 * <p>
+	 * This method returns to the caller immediately and then performs the
+	 * following steps in its own thread:
+	 * 
+	 * <ol>
+	 * <li>Compute a graph of bundles starting with the specified bundles. If
+	 * no bundles are specified, compute a graph of bundles starting with
+	 * previously updated or uninstalled ones. Add to the graph any bundle that
+	 * imports a package that is currently exported by a bundle in the graph.
+	 * The graph is fully constructed when there is no bundle outside the graph
+	 * that imports a package from a bundle in the graph. The graph may contain
+	 * <code>UNINSTALLED</code> bundles that are currently still exporting
+	 * packages.
+	 * 
+	 * <li>Each bundle in the graph that is in the <code>ACTIVE</code> state will
+	 * be stopped as described in the <code>Bundle.stop</code> method.
+	 * 
+	 * <li>Each bundle in the graph that is in the <code>RESOLVED</code> state is
+	 * moved to the <code>INSTALLED</code> state. The effect of this step is that
+	 * bundles in the graph are no longer <code>RESOLVED</code>.
+	 * 
+	 * <li>Each bundle in the graph that is in the <code>UNINSTALLED</code> state
+	 * is removed from the graph and is now completely removed from the
+	 * Framework.
+	 * 
+	 * <li>Each bundle in the graph that was in the <code>ACTIVE</code> state
+	 * prior to Step 2 is started as described in the <code>Bundle.start</code>
+	 * method, causing all bundles required for the restart to be resolved. It
+	 * is possible that, as a result of the previous steps, packages that were
+	 * previously exported no longer are. Therefore, some bundles may be
+	 * unresolvable until another bundle offering a compatible package for
+	 * export has been installed in the Framework.
+	 * <li>A framework event of type <code>FrameworkEvent.PACKAGES_REFRESHED</code>
+	 * is broadcast.
+	 * </ol>
+	 * 
+	 * <p>
+	 * For any exceptions that are thrown during any of these steps, a
+	 * <code>FrameworkEvent</code> of type <code>ERROR</code> is broadcast,
+	 * containing the exception. The source bundle for these events should be
+	 * the specific bundle to which the exception is related. If no specific
+	 * bundle can be associated with the exception then the System Bundle must
+	 * be used as the source bundle for the event.
+	 * 
+	 * @param bundles the bundles whose exported packages are to be updated or
+	 *        removed, or <code>null</code> for all previously updated or
+	 *        uninstalled bundles.
+	 * 
+	 * @exception SecurityException if the caller does not have the
+	 *            <code>AdminPermission</code> and the Java runtime environment
+	 *            supports permissions.
+	 */
+	public void refreshPackages(Bundle[] bundles);
+
+	/**
+	 * Get the <code>ExportedPackage</code> objects with the specified
+	 * package name. All exported packages will be checked for the specified
+	 * name.
+	 * <p>
+	 * In an environment where the exhaustive list of packages on the system
+	 * classpath is not known in advance, this method attempts to see if the
+	 * named package is on the system classpath. This means that this method may
+	 * discover an <code>ExportedPackage</code> object that was not present in the
+	 * list returned by a prior call to <code>getExportedPackages()</code>.
+	 * 
+	 * @param name The name of the exported packages to be returned.
+	 * 
+	 * @return An array of the exported packages with the specified name, or
+	 *         <code>null</code> if no exported packages with that name exists.
+	 * @since 1.2
+	 */
+	public ExportedPackage[] getExportedPackages(String name);
+
+	/**
+	 * Resolve the specified bundles. The Framework must attempt to resolve the
+	 * specified bundles that are unresolved. Additional bundles that are not
+	 * included in the specified bundles may be resolved as a result of calling
+	 * this method. A permissible implementation of this method is to attempt to
+	 * resolve all unresolved bundles installed in the framework.
+	 * 
+	 * <p>
+	 * If <code>null</code> is specified then the Framework will attempt to
+	 * resolve all unresolved bundles. This method must not cause any bundle to
+	 * be refreshed, stopped, or started. This method will not return until the
+	 * operation has completed.
+	 * 
+	 * @param bundles The bundles to resolve or <code>null</code> to resolve all
+	 *        unresolved bundles installed in the Framework.
+	 * @return <code>true</code> if all specified bundles are resolved;
+	 * @since 1.2
+	 */
+	public boolean resolveBundles(Bundle[] bundles);
+
+	/**
+	 * Returns an array of RequiredBundles with the specified symbolic name. If
+	 * the symbolic name argument is <code>null</code> then all RequiredBundles
+	 * are returned.
+	 * 
+	 * @param symbolicName The symbolic name of the RequiredBundle or
+	 *        <code>null</code> for all RequiredBundles in the Framework.
+	 * @return An array of RequiredBundles with the specified symbolic name or
+	 *         <code>null</code> if no RequiredBundles exist with that symbolic
+	 *         name.
+	 * @since 1.2
+	 */
+	public RequiredBundle[] getRequiredBundles(String symbolicName);
+
+	/**
+	 * Returns the bundles with the specified symbolic name within the specified
+	 * version range. If no bundles are installed that have the specified
+	 * symbolic name, then <code>null</code> is returned. If a version range is
+	 * specified, then only the bundles that have the specified symbolic name
+	 * and belong to the specified version range are returned. The returned
+	 * bundles are ordered by version in descending version order so that the
+	 * first element of the array contains the bundle with the highest version.
+	 * 
+	 * @see org.osgi.framework.Constants#BUNDLE_VERSION_ATTRIBUTE
+	 * @param symbolicName The symbolic name of the desired bundles.
+	 * @param versionRange The version range of the desired bundles, or
+	 *        <code>null</code> if all versions are desired.
+	 * @return An array of bundles with the specified name belonging to the
+	 *         specified version range ordered in descending version order, or
+	 *         <code>null</code> if no bundles are found.
+	 * @since 1.2
+	 */
+	public Bundle[] getBundles(String symbolicName, String versionRange);
+
+	/**
+	 * Returns an array of attached fragment bundles for the specified bundle.
+	 * If the specified bundle is a fragment then <code>null</code> is returned.
+	 * If no fragments are attached to the specified bundle then <code>null</code>
+	 * is returned.
+	 * <p>
+	 * This method does not attempt to resolve the specified bundle.  If the 
+	 * specified bundle is not resolved then <code>null</code> is returned. 
+	 * 
+	 * @param bundle The bundle whose attached fragment bundles are to be
+	 *        returned.
+	 * @return An array of fragment bundles or <code>null</code> if the bundle
+	 *         does not have any attached fragment bundles or the bundle is not
+	 *         resolved.
+	 * @since 1.2
+	 */
+	public Bundle[] getFragments(Bundle bundle);
+
+	/**
+	 * Returns an array of host bundles to which the specified fragment bundle
+	 * is attached or <code>null</code> if the specified bundle is not attached to
+	 * a host or is not a fragment bundle.
+	 * 
+	 * @param bundle The bundle whose host bundles are to be returned.
+	 * @return An array of host bundles or <code>null</code> if the bundle does
+	 *         not have any host bundles.
+	 * @since 1.2
+	 */
+	public Bundle[] getHosts(Bundle bundle);
+
+	/**
+	 * Returns the bundle for which the specified class is loaded from. The
+	 * classloader of the bundle returned must have been used to load the
+	 * specified class. If the class was not loaded by a bundle classloader then
+	 * <code>null</code> is returned.
+	 * 
+	 * @param clazz the class object to get a bundle for
+	 * @return the bundle from which the specified class is loaded or
+	 *         <code>null</code> if the class was not loaded by a bundle
+	 *         classloader
+	 * @since 1.2
+	 */
+	public Bundle getBundle(Class clazz);
+
+	/**
+	 * The bundle is a fragment bundle.
+	 * 
+	 * <p>
+	 * The value of <code>BUNDLE_TYPE_FRAGMENT</code> is 0x00000001.
+	 * 
+	 * @since 1.2
+	 */
+	public static final int	BUNDLE_TYPE_FRAGMENT	= 0x00000001;
+
+	/**
+	 * Returns the special type of the specified bundle. The bundle type values
+	 * are:
+	 * <ul>
+	 * <li>{@link #BUNDLE_TYPE_FRAGMENT}
+	 * </ul>
+	 * 
+	 * A bundle may be more than one type at a time. A type code is used to
+	 * identify the bundle type for future extendability.
+	 * 
+	 * <p>
+	 * If a bundle is not one or more of the defined types then 0x00000000 is
+	 * returned.
+	 * 
+	 * @return The special type of the bundle.
+	 * @since 1.2
+	 */
+	public int getBundleType(Bundle bundle);
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/packageadmin/RequiredBundle.java b/src/org/osgi/service/packageadmin/RequiredBundle.java
new file mode 100644
index 0000000..0da07c4
--- /dev/null
+++ b/src/org/osgi/service/packageadmin/RequiredBundle.java
@@ -0,0 +1,77 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.packageadmin/src/org/osgi/service/packageadmin/RequiredBundle.java,v 1.5 2005/05/13 20:32:34 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2004, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.packageadmin;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+
+/**
+ * A required bundle.
+ * 
+ * Instances implementing this interface are created by the Package Admin
+ * service.
+ * 
+ * <p>
+ * The information about a <code>RequiredBundle</code> provided by this object is
+ * valid only until the next time <code>PackageAdmin.refreshPackages()</code>
+ * called. If a <code>RequiredBundle</code> object becomes stale (that is, the
+ * bundle it references has been updated or removed as a result of calling
+ * <code>PackageAdmin.refreshPackages()</code>), its <code>getSymbolicName()</code>
+ * and <code>getVersion()</code> continue to return their old values,
+ * <code>isRemovalPending()</code> returns true, and <code>getBundle()</code> and
+ * <code>getRequiringBundles()</code> return <code>null</code>.
+ * 
+ * @since 1.2
+ */
+public interface RequiredBundle {
+	/**
+	 * Returns the bundle which defines this RequiredBundle.
+	 * 
+	 * @return The bundle, or <code>null</code> if this <code>RequiredBundle</code>
+	 *         object has become stale.
+	 */
+	public Bundle getBundle();
+
+	/**
+	 * Returns the resolved bundles that currently require this bundle. If this
+	 * <code>RequiredBundle</code> object is required and re-exported by another
+	 * bundle then all the requiring bundles of the re-exporting bundle are
+	 * included in the returned array.
+	 * 
+	 * @return An array of resolved bundles currently requiring this bundle, or
+	 *         <code>null</code> if this <code>RequiredBundle</code> object has
+	 *         become stale.
+	 */
+	public Bundle[] getRequiringBundles();
+
+	/**
+	 * Returns the symbolic name of the bundle.
+	 * 
+	 * @return The symbolic name of the bundle.
+	 */
+	public String getSymbolicName();
+
+	/**
+	 * Returns the version of the bundle.
+	 * 
+	 * @return The version of the bundle.
+	 */
+	public Version getVersion();
+
+	/**
+	 * Returns <code>true</code> if the bundle has been updated or uninstalled.
+	 * 
+	 * @return <code>true</code> if the bundle has been updated or uninstalled, or
+	 *         if the <code>RequiredBundle</code> object has become stale;
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean isRemovalPending();
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/permissionadmin/PermissionAdmin.java b/src/org/osgi/service/permissionadmin/PermissionAdmin.java
new file mode 100644
index 0000000..904b318
--- /dev/null
+++ b/src/org/osgi/service/permissionadmin/PermissionAdmin.java
@@ -0,0 +1,113 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.permissionadmin/src/org/osgi/service/permissionadmin/PermissionAdmin.java,v 1.7 2005/05/13 20:33:46 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2001, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.permissionadmin;
+
+/**
+ * The Permission Admin service allows management agents to manage the
+ * permissions of bundles. There is at most one Permission Admin service present
+ * in the OSGi environment.
+ * <p>
+ * Access to the Permission Admin service is protected by corresponding
+ * <code>ServicePermission</code>. In addition <code>AdminPermission</code> is
+ * required to actually set permissions.
+ * 
+ * <p>
+ * Bundle permissions are managed using a permission table. A bundle's location
+ * serves as the key into this permission table. The value of a table entry is
+ * the set of permissions (of type <code>PermissionInfo</code>) granted to the
+ * bundle named by the given location. A bundle may have an entry in the
+ * permission table prior to being installed in the Framework.
+ * 
+ * <p>
+ * The permissions specified in <code>setDefaultPermissions</code> are used as the
+ * default permissions which are granted to all bundles that do not have an
+ * entry in the permission table.
+ * 
+ * <p>
+ * Any changes to a bundle's permissions in the permission table will take
+ * effect no later than when bundle's <code>java.security.ProtectionDomain</code>
+ * is next involved in a permission check, and will be made persistent.
+ * 
+ * <p>
+ * Only permission classes on the system classpath or from an exported package
+ * are considered during a permission check. Additionally, only permission
+ * classes that are subclasses of <code>java.security.Permission</code> and define
+ * a 2-argument constructor that takes a <i>name </i> string and an <i>actions
+ * </i> string can be used.
+ * <p>
+ * Permissions implicitly granted by the Framework (for example, a bundle's
+ * permission to access its persistent storage area) cannot be changed, and are
+ * not reflected in the permissions returned by <code>getPermissions</code> and
+ * <code>getDefaultPermissions</code>.
+ * 
+ * @version $Revision: 1.7 $
+ */
+public interface PermissionAdmin {
+	/**
+	 * Gets the permissions assigned to the bundle with the specified location.
+	 * 
+	 * @param location The location of the bundle whose permissions are to be
+	 *        returned.
+	 * 
+	 * @return The permissions assigned to the bundle with the specified
+	 *         location, or <code>null</code> if that bundle has not been assigned
+	 *         any permissions.
+	 */
+	PermissionInfo[] getPermissions(String location);
+
+	/**
+	 * Assigns the specified permissions to the bundle with the specified
+	 * location.
+	 * 
+	 * @param location The location of the bundle that will be assigned the
+	 *        permissions.
+	 * @param permissions The permissions to be assigned, or <code>null</code> if
+	 *        the specified location is to be removed from the permission table.
+	 * @exception SecurityException if the caller does not have the
+	 *            <code>AdminPermission</code>.
+	 */
+	void setPermissions(String location, PermissionInfo[] permissions);
+
+	/**
+	 * Returns the bundle locations that have permissions assigned to them, that
+	 * is, bundle locations for which an entry exists in the permission table.
+	 * 
+	 * @return The locations of bundles that have been assigned any permissions,
+	 *         or <code>null</code> if the permission table is empty.
+	 */
+	String[] getLocations();
+
+	/**
+	 * Gets the default permissions.
+	 * 
+	 * <p>
+	 * These are the permissions granted to any bundle that does not have
+	 * permissions assigned to its location.
+	 * 
+	 * @return The default permissions, or <code>null</code> if no default
+	 *         permissions are set.
+	 */
+	PermissionInfo[] getDefaultPermissions();
+
+	/**
+	 * Sets the default permissions.
+	 * 
+	 * <p>
+	 * These are the permissions granted to any bundle that does not have
+	 * permissions assigned to its location.
+	 * 
+	 * @param permissions The default permissions, or <code>null</code> if the
+	 *        default permissions are to be removed from the permission table.
+	 * @exception SecurityException if the caller does not have the
+	 *            <code>AdminPermission</code>.
+	 */
+	void setDefaultPermissions(PermissionInfo[] permissions);
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/permissionadmin/PermissionInfo.java b/src/org/osgi/service/permissionadmin/PermissionInfo.java
new file mode 100644
index 0000000..3502e89
--- /dev/null
+++ b/src/org/osgi/service/permissionadmin/PermissionInfo.java
@@ -0,0 +1,360 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.permissionadmin/src/org/osgi/service/permissionadmin/PermissionInfo.java,v 1.8 2005/06/21 15:41:57 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2001, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.permissionadmin;
+
+/**
+ * Permission representation used by the Permission Admin service.
+ * 
+ * <p>
+ * This class encapsulates three pieces of information: a Permission <i>type
+ * </i> (class name), which must be a subclass of
+ * <code>java.security.Permission</code>, and the <i>name </i> and <i>actions
+ * </i> arguments passed to its constructor.
+ * 
+ * <p>
+ * In order for a permission represented by a <code>PermissionInfo</code> to be
+ * instantiated and considered during a permission check, its Permission class
+ * must be available from the system classpath or an exported package. This
+ * means that the instantiation of a permission represented by a
+ * <code>PermissionInfo</code> may be delayed until the package containing its
+ * Permission class has been exported by a bundle.
+ * 
+ * @version $Revision: 1.8 $
+ */
+public class PermissionInfo {
+	private String	type;
+	private String	name;
+	private String	actions;
+
+	/**
+	 * Constructs a <code>PermissionInfo</code> from the given type, name, and
+	 * actions.
+	 * 
+	 * @param type The fully qualified class name of the permission represented
+	 *        by this <code>PermissionInfo</code>. The class must be a subclass
+	 *        of <code>java.security.Permission</code> and must define a
+	 *        2-argument constructor that takes a <i>name </i> string and an
+	 *        <i>actions </i> string.
+	 * 
+	 * @param name The permission name that will be passed as the first argument
+	 *        to the constructor of the <code>Permission</code> class identified
+	 *        by <code>type</code>.
+	 * 
+	 * @param actions The permission actions that will be passed as the second
+	 *        argument to the constructor of the <code>Permission</code> class
+	 *        identified by <code>type</code>.
+	 * 
+	 * @exception java.lang.NullPointerException if <code>type</code> is
+	 *            <code>null</code>.
+	 * @exception java.lang.IllegalArgumentException if <code>action</code> is not
+	 *            <code>null</code> and <code>name</code> is <code>null</code>.
+	 */
+	public PermissionInfo(String type, String name, String actions) {
+		this.type = type;
+		this.name = name;
+		this.actions = actions;
+		if (type == null) {
+			throw new NullPointerException("type is null");
+		}
+		if ((name == null) && (actions != null)) {
+			throw new IllegalArgumentException("name missing");
+		}
+	}
+
+	/**
+	 * Constructs a <code>PermissionInfo</code> object from the given encoded
+	 * <code>PermissionInfo</code> string.
+	 * 
+	 * @param encodedPermission The encoded <code>PermissionInfo</code>.
+	 * @see #getEncoded
+	 * @exception java.lang.IllegalArgumentException if
+	 *            <code>encodedPermission</code> is not properly formatted.
+	 */
+	public PermissionInfo(String encodedPermission) {
+		if (encodedPermission == null) {
+			throw new NullPointerException("missing encoded permission");
+		}
+		if (encodedPermission.length() == 0) {
+			throw new IllegalArgumentException("empty encoded permission");
+		}
+		try {
+			char[] encoded = encodedPermission.toCharArray();
+			/* the first character must be '(' */
+			if (encoded[0] != '(') {
+				throw new IllegalArgumentException(
+						"first character not open parenthesis");
+			}
+			/* type is not quoted or encoded */
+			int end = 1;
+			int begin = end;
+			while ((encoded[end] != ' ') && (encoded[end] != ')')) {
+				end++;
+			}
+			if (end == begin) {
+				throw new IllegalArgumentException("expecting type");
+			}
+			this.type = new String(encoded, begin, end - begin);
+			/* type may be followed by name which is quoted and encoded */
+			// TODO Need to support multiple spaces
+			if (encoded[end] == ' ') {
+				end++;
+				if (encoded[end] != '"') {
+					throw new IllegalArgumentException("expecting quoted name");
+				}
+				end++;
+				begin = end;
+				while (encoded[end] != '"') {
+					if (encoded[end] == '\\') {
+						end++;
+					}
+					end++;
+				}
+				this.name = decodeString(encoded, begin, end);
+				end++;
+				/* name may be followed by actions which is quoted and encoded */
+				// TODO Need to support multiple spaces
+				if (encoded[end] == ' ') {
+					end++;
+					if (encoded[end] != '"') {
+						throw new IllegalArgumentException(
+								"expecting quoted actions");
+					}
+					end++;
+					begin = end;
+					while (encoded[end] != '"') {
+						if (encoded[end] == '\\') {
+							end++;
+						}
+						end++;
+					}
+					this.actions = decodeString(encoded, begin, end);
+					end++;
+				}
+			}
+			/* the final character must be ')' */
+			if ((encoded[end] != ')') || (end + 1 != encoded.length)) {
+				throw new IllegalArgumentException("last character not "
+						+ "close parenthesis");
+			}
+		}
+		catch (ArrayIndexOutOfBoundsException e) {
+			throw new IllegalArgumentException("parsing terminated abruptly");
+		}
+	}
+
+	/**
+	 * Returns the string encoding of this <code>PermissionInfo</code> in a form
+	 * suitable for restoring this <code>PermissionInfo</code>.
+	 * 
+	 * <p>
+	 * The encoded format is:
+	 * 
+	 * <pre>
+	 * (type)
+	 * </pre>
+	 * 
+	 * or
+	 * 
+	 * <pre>
+	 * (type &quot;name&quot;)
+	 * </pre>
+	 * 
+	 * or
+	 * 
+	 * <pre>
+	 * (type &quot;name&quot; &quot;actions&quot;)
+	 * </pre>
+	 * 
+	 * where <i>name</i> and <i>actions</i> are strings that are encoded for
+	 * proper parsing. Specifically, the <code>"</code>,<code>\</code>, carriage
+	 * return, and linefeed characters are escaped using <code>\"</code>,
+	 * <code>\\</code>,<code>\r</code>, and <code>\n</code>, respectively.
+	 * 
+	 * <p>
+	 * The encoded string must contain no leading or trailing whitespace
+	 * characters. A single space character must be used between <i>type</i> and 
+	 * &quot;<i>name</i>&quot; and between &quot;<i>name</i>&quot; and &quot;<i>actions</i>&quot;.
+	 * 
+	 * @return The string encoding of this <code>PermissionInfo</code>.
+	 */
+	public final String getEncoded() {
+		StringBuffer output = new StringBuffer(
+				8
+						+ type.length()
+						+ ((((name == null) ? 0 : name.length()) + ((actions == null) ? 0
+								: actions.length())) << 1));
+		output.append('(');
+		output.append(type);
+		if (name != null) {
+			output.append(" \"");
+			encodeString(name, output);
+			if (actions != null) {
+				output.append("\" \"");
+				encodeString(actions, output);
+			}
+			output.append('\"');
+		}
+		output.append(')');
+		return (output.toString());
+	}
+
+	/**
+	 * Returns the string representation of this <code>PermissionInfo</code>. The
+	 * string is created by calling the <code>getEncoded</code> method on this
+	 * <code>PermissionInfo</code>.
+	 * 
+	 * @return The string representation of this <code>PermissionInfo</code>.
+	 */
+	public String toString() {
+		return (getEncoded());
+	}
+
+	/**
+	 * Returns the fully qualified class name of the permission represented by
+	 * this <code>PermissionInfo</code>.
+	 * 
+	 * @return The fully qualified class name of the permission represented by
+	 *         this <code>PermissionInfo</code>.
+	 */
+	public final String getType() {
+		return (type);
+	}
+
+	/**
+	 * Returns the name of the permission represented by this
+	 * <code>PermissionInfo</code>.
+	 * 
+	 * @return The name of the permission represented by this
+	 *         <code>PermissionInfo</code>, or <code>null</code> if the permission
+	 *         does not have a name.
+	 */
+	public final String getName() {
+		return (name);
+	}
+
+	/**
+	 * Returns the actions of the permission represented by this
+	 * <code>PermissionInfo</code>.
+	 * 
+	 * @return The actions of the permission represented by this
+	 *         <code>PermissionInfo</code>, or <code>null</code> if the permission
+	 *         does not have any actions associated with it.
+	 */
+	public final String getActions() {
+		return (actions);
+	}
+
+	/**
+	 * Determines the equality of two <code>PermissionInfo</code> objects.
+	 * 
+	 * This method checks that specified object has the same type, name and
+	 * actions as this <code>PermissionInfo</code> object.
+	 * 
+	 * @param obj The object to test for equality with this
+	 *        <code>PermissionInfo</code> object.
+	 * @return <code>true</code> if <code>obj</code> is a <code>PermissionInfo</code>,
+	 *         and has the same type, name and actions as this
+	 *         <code>PermissionInfo</code> object; <code>false</code> otherwise.
+	 */
+	public boolean equals(Object obj) {
+		if (obj == this) {
+			return (true);
+		}
+		if (!(obj instanceof PermissionInfo)) {
+			return (false);
+		}
+		PermissionInfo other = (PermissionInfo) obj;
+		if (!type.equals(other.type) || ((name == null) ^ (other.name == null))
+				|| ((actions == null) ^ (other.actions == null))) {
+			return (false);
+		}
+		if (name != null) {
+			if (actions != null) {
+				return (name.equals(other.name) && actions
+						.equals(other.actions));
+			}
+			else {
+				return (name.equals(other.name));
+			}
+		}
+		else {
+			return (true);
+		}
+	}
+
+	/**
+	 * Returns the hash code value for this object.
+	 * 
+	 * @return A hash code value for this object.
+	 */
+	public int hashCode() {
+		int hash = type.hashCode();
+		if (name != null) {
+			hash ^= name.hashCode();
+			if (actions != null) {
+				hash ^= actions.hashCode();
+			}
+		}
+		return (hash);
+	}
+
+	/**
+	 * This escapes the quotes, backslashes, \n, and \r in the string using a
+	 * backslash and appends the newly escaped string to a StringBuffer.
+	 */
+	private static void encodeString(String str, StringBuffer output) {
+		int len = str.length();
+		for (int i = 0; i < len; i++) {
+			char c = str.charAt(i);
+			switch (c) {
+				case '"' :
+				case '\\' :
+					output.append('\\');
+					output.append(c);
+					break;
+				case '\r' :
+					output.append("\\r");
+					break;
+				case '\n' :
+					output.append("\\n");
+					break;
+				default :
+					output.append(c);
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Takes an encoded character array and decodes it into a new String.
+	 */
+	private static String decodeString(char[] str, int begin, int end) {
+		StringBuffer output = new StringBuffer(end - begin);
+		for (int i = begin; i < end; i++) {
+			char c = str[i];
+			if (c == '\\') {
+				i++;
+				if (i < end) {
+					c = str[i];
+					if (c == 'n') {
+						c = '\n';
+					}
+					else
+						if (c == 'r') {
+							c = '\r';
+						}
+				}
+			}
+			output.append(c);
+		}
+		return (output.toString());
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/startlevel/StartLevel.java b/src/org/osgi/service/startlevel/StartLevel.java
new file mode 100644
index 0000000..e2421cd
--- /dev/null
+++ b/src/org/osgi/service/startlevel/StartLevel.java
@@ -0,0 +1,228 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.startlevel/src/org/osgi/service/startlevel/StartLevel.java,v 1.6 2005/05/13 20:34:03 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.startlevel;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * The StartLevel service allows management agents to manage a start level
+ * assigned to each bundle and the active start level of the Framework. There is
+ * at most one StartLevel service present in the OSGi environment.
+ * 
+ * <p>
+ * A start level is defined to be a state of execution in which the Framework
+ * exists. StartLevel values are defined as unsigned integers with 0 (zero)
+ * being the state where the Framework is not launched. Progressively higher
+ * integral values represent progressively higher start levels. e.g. 2 is a
+ * higher start level than 1.
+ * <p>
+ * Access to the StartLevel service is protected by corresponding
+ * <code>ServicePermission</code>. In addition the <code>AdminPermission</code>
+ * that is required to actually modify start level information.
+ * <p>
+ * Start Level support in the Framework includes the ability to control the
+ * beginning start level of the Framework, to modify the active start level of
+ * the Framework and to assign a specific start level to a bundle. How the
+ * beginning start level of a Framework is specified is implementation
+ * dependent. It may be a command line argument when invoking the Framework
+ * implementation.
+ * <p>
+ * When the Framework is first started it must be at start level zero. In this
+ * state, no bundles are running. This is the initial state of the Framework
+ * before it is launched.
+ * 
+ * When the Framework is launched, the Framework will enter start level one and
+ * all bundles which are assigned to start level one and are persistently marked
+ * to be started are started as described in the <code>Bundle.start</code> method.
+ * Within a start level, bundles are started in ascending order by
+ * <code>Bundle.getBundleId</code>. The Framework will continue to increase the
+ * start level, starting bundles at each start level, until the Framework has
+ * reached a beginning start level. At this point the Framework has completed
+ * starting bundles and will then broadcast a Framework event of type
+ * <code>FrameworkEvent.STARTED</code> to announce it has completed its launch.
+ * 
+ * <p>
+ * The StartLevel service can be used by management bundles to alter the active
+ * start level of the framework.
+ * 
+ * @version $Revision: 1.6 $
+ */
+public interface StartLevel {
+	/**
+	 * Return the active start level value of the Framework.
+	 * 
+	 * If the Framework is in the process of changing the start level this
+	 * method must return the active start level if this differs from the
+	 * requested start level.
+	 * 
+	 * @return The active start level value of the Framework.
+	 */
+	public abstract int getStartLevel();
+
+	/**
+	 * Modify the active start level of the Framework.
+	 * 
+	 * <p>
+	 * The Framework will move to the requested start level. This method will
+	 * return immediately to the caller and the start level change will occur
+	 * asynchronously on another thread.
+	 * 
+	 * <p>
+	 * If the specified start level is higher than the active start level, the
+	 * Framework will continue to increase the start level until the Framework
+	 * has reached the specified start level, starting bundles at each start
+	 * level which are persistently marked to be started as described in the
+	 * <code>Bundle.start</code> method.
+	 * 
+	 * At each intermediate start level value on the way to and including the
+	 * target start level, the framework must:
+	 * <ol>
+	 * <li>Change the active start level to the intermediate start level value.
+	 * <li>Start bundles at the intermediate start level in ascending order by
+	 * <code>Bundle.getBundleId</code>.
+	 * </ol>
+	 * When this process completes after the specified start level is reached,
+	 * the Framework will broadcast a Framework event of type
+	 * <code>FrameworkEvent.STARTLEVEL_CHANGED</code> to announce it has moved to
+	 * the specified start level.
+	 * 
+	 * <p>
+	 * If the specified start level is lower than the active start level, the
+	 * Framework will continue to decrease the start level until the Framework
+	 * has reached the specified start level stopping bundles at each start
+	 * level as described in the <code>Bundle.stop</code> method except that their
+	 * persistently recorded state indicates that they must be restarted in the
+	 * future.
+	 * 
+	 * At each intermediate start level value on the way to and including the
+	 * specified start level, the framework must:
+	 * <ol>
+	 * <li>Stop bundles at the intermediate start level in descending order by
+	 * <code>Bundle.getBundleId</code>.
+	 * <li>Change the active start level to the intermediate start level value.
+	 * </ol>
+	 * When this process completes after the specified start level is reached,
+	 * the Framework will broadcast a Framework event of type
+	 * <code>FrameworkEvent.STARTLEVEL_CHANGED</code> to announce it has moved to
+	 * the specified start level.
+	 * 
+	 * <p>
+	 * If the specified start level is equal to the active start level, then no
+	 * bundles are started or stopped, however, the Framework must broadcast a
+	 * Framework event of type <code>FrameworkEvent.STARTLEVEL_CHANGED</code> to
+	 * announce it has finished moving to the specified start level. This event
+	 * may arrive before the this method return.
+	 * 
+	 * @param startlevel The requested start level for the Framework.
+	 * @throws IllegalArgumentException If the specified start level is less
+	 *         than or equal to zero.
+	 * @throws SecurityException If the caller does not have the
+	 *         <code>AdminPermission</code> and the Java runtime environment
+	 *         supports permissions.
+	 */
+	public abstract void setStartLevel(int startlevel);
+
+	/**
+	 * Return the assigned start level value for the specified Bundle.
+	 * 
+	 * @param bundle The target bundle.
+	 * @return The start level value of the specified Bundle.
+	 * @exception java.lang.IllegalArgumentException If the specified bundle has
+	 *            been uninstalled.
+	 */
+	public abstract int getBundleStartLevel(Bundle bundle);
+
+	/**
+	 * Assign a start level value to the specified Bundle.
+	 * 
+	 * <p>
+	 * The specified bundle will be assigned the specified start level. The
+	 * start level value assigned to the bundle will be persistently recorded by
+	 * the Framework.
+	 * 
+	 * If the new start level for the bundle is lower than or equal to the
+	 * active start level of the Framework, the Framework will start the
+	 * specified bundle as described in the <code>Bundle.start</code> method if
+	 * the bundle is persistently marked to be started. The actual starting of
+	 * this bundle must occur asynchronously.
+	 * 
+	 * If the new start level for the bundle is higher than the active start
+	 * level of the Framework, the Framework will stop the specified bundle as
+	 * described in the <code>Bundle.stop</code> method except that the
+	 * persistently recorded state for the bundle indicates that the bundle must
+	 * be restarted in the future. The actual stopping of this bundle must occur
+	 * asynchronously.
+	 * 
+	 * @param bundle The target bundle.
+	 * @param startlevel The new start level for the specified Bundle.
+	 * @throws IllegalArgumentException If the specified bundle has been
+	 *         uninstalled or if the specified start level is less than or equal
+	 *         to zero, or the specified bundle is the system bundle.
+	 * @throws SecurityException if the caller does not have the
+	 *         <code>AdminPermission</code> and the Java runtime environment
+	 *         supports permissions.
+	 */
+	public abstract void setBundleStartLevel(Bundle bundle, int startlevel);
+
+	/**
+	 * Return the initial start level value that is assigned to a Bundle when it
+	 * is first installed.
+	 * 
+	 * @return The initial start level value for Bundles.
+	 * @see #setInitialBundleStartLevel
+	 */
+	public abstract int getInitialBundleStartLevel();
+
+	/**
+	 * Set the initial start level value that is assigned to a Bundle when it is
+	 * first installed.
+	 * 
+	 * <p>
+	 * The initial bundle start level will be set to the specified start level.
+	 * The initial bundle start level value will be persistently recorded by the
+	 * Framework.
+	 * 
+	 * <p>
+	 * When a Bundle is installed via <code>BundleContext.installBundle</code>,
+	 * it is assigned the initial bundle start level value.
+	 * 
+	 * <p>
+	 * The default initial bundle start level value is 1 unless this method has
+	 * been called to assign a different initial bundle start level value.
+	 * 
+	 * <p>
+	 * Thie method does not change the start level values of installed bundles.
+	 * 
+	 * @param startlevel The initial start level for newly installed bundles.
+	 * @throws IllegalArgumentException If the specified start level is less
+	 *         than or equal to zero.
+	 * @throws SecurityException if the caller does not have the
+	 *         <code>AdminPermission</code> and the Java runtime environment
+	 *         supports permissions.
+	 */
+	public abstract void setInitialBundleStartLevel(int startlevel);
+
+	/**
+	 * Return the persistent state of the specified bundle.
+	 * 
+	 * <p>
+	 * This method returns the persistent state of a bundle. The persistent
+	 * state of a bundle indicates whether a bundle is persistently marked to be
+	 * started when it's start level is reached.
+	 * 
+	 * @return <code>true</code> if the bundle is persistently marked to be
+	 *         started, <code>false</code> if the bundle is not persistently
+	 *         marked to be started.
+	 * @exception java.lang.IllegalArgumentException If the specified bundle has
+	 *            been uninstalled.
+	 */
+	public abstract boolean isBundlePersistentlyStarted(Bundle bundle);
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/url/AbstractURLStreamHandlerService.java b/src/org/osgi/service/url/AbstractURLStreamHandlerService.java
new file mode 100644
index 0000000..03bfd1b
--- /dev/null
+++ b/src/org/osgi/service/url/AbstractURLStreamHandlerService.java
@@ -0,0 +1,142 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.url/src/org/osgi/service/url/AbstractURLStreamHandlerService.java,v 1.6 2005/05/13 20:32:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.url;
+
+import java.net.*;
+
+/**
+ * Abstract implementation of the <code>URLStreamHandlerService</code> interface.
+ * All the methods simply invoke the corresponding methods on
+ * <code>java.net.URLStreamHandler</code> except for <code>parseURL</code> and
+ * <code>setURL</code>, which use the <code>URLStreamHandlerSetter</code>
+ * parameter. Subclasses of this abstract class should not need to override the
+ * <code>setURL</code> and <code>parseURL(URLStreamHandlerSetter,...)</code>
+ * methods.
+ * 
+ * @version $Revision: 1.6 $
+ */
+public abstract class AbstractURLStreamHandlerService extends URLStreamHandler
+		implements URLStreamHandlerService {
+	/**
+	 * @see "java.net.URLStreamHandler.openConnection"
+	 */
+	public abstract URLConnection openConnection(URL u)
+			throws java.io.IOException;
+
+	/**
+	 * The <code>URLStreamHandlerSetter</code> object passed to the parseURL
+	 * method.
+	 */
+	protected URLStreamHandlerSetter	realHandler;
+
+	/**
+	 * Parse a URL using the <code>URLStreamHandlerSetter</code> object. This
+	 * method sets the <code>realHandler</code> field with the specified
+	 * <code>URLStreamHandlerSetter</code> object and then calls
+	 * <code>parseURL(URL,String,int,int)</code>.
+	 * 
+	 * @param realHandler The object on which the <code>setURL</code> method must
+	 *        be invoked for the specified URL.
+	 * @see "java.net.URLStreamHandler.parseURL"
+	 */
+	public void parseURL(URLStreamHandlerSetter realHandler, URL u,
+			String spec, int start, int limit) {
+		this.realHandler = realHandler;
+		parseURL(u, spec, start, limit);
+	}
+
+	/**
+	 * This method calls <code>super.toExternalForm</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.toExternalForm"
+	 */
+	public String toExternalForm(URL u) {
+		return super.toExternalForm(u);
+	}
+
+	/**
+	 * This method calls <code>super.equals(URL,URL)</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.equals(URL,URL)"
+	 */
+	public boolean equals(URL u1, URL u2) {
+		return super.equals(u1, u2);
+	}
+
+	/**
+	 * This method calls <code>super.getDefaultPort</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.getDefaultPort"
+	 */
+	public int getDefaultPort() {
+		return super.getDefaultPort();
+	}
+
+	/**
+	 * This method calls <code>super.getHostAddress</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.getHostAddress"
+	 */
+	public InetAddress getHostAddress(URL u) {
+		return super.getHostAddress(u);
+	}
+
+	/**
+	 * This method calls <code>super.hashCode(URL)</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.hashCode(URL)"
+	 */
+	public int hashCode(URL u) {
+		return super.hashCode(u);
+	}
+
+	/**
+	 * This method calls <code>super.hostsEqual</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.hostsEqual"
+	 */
+	public boolean hostsEqual(URL u1, URL u2) {
+		return super.hostsEqual(u1, u2);
+	}
+
+	/**
+	 * This method calls <code>super.sameFile</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.sameFile"
+	 */
+	public boolean sameFile(URL u1, URL u2) {
+		return super.sameFile(u1, u2);
+	}
+
+	/**
+	 * This method calls
+	 * <code>realHandler.setURL(URL,String,String,int,String,String)</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.setURL(URL,String,String,int,String,String)"
+	 * @deprecated This method is only for compatibility with handlers written
+	 *             for JDK 1.1.
+	 */
+	protected void setURL(URL u, String proto, String host, int port,
+			String file, String ref) {
+		realHandler.setURL(u, proto, host, port, file, ref);
+	}
+
+	/**
+	 * This method calls
+	 * <code>realHandler.setURL(URL,String,String,int,String,String,String,String)</code>.
+	 * 
+	 * @see "java.net.URLStreamHandler.setURL(URL,String,String,int,String,String,String,String)"
+	 */
+	protected void setURL(URL u, String proto, String host, int port,
+			String auth, String user, String path, String query, String ref) {
+		realHandler.setURL(u, proto, host, port, auth, user, path, query, ref);
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/url/URLConstants.java b/src/org/osgi/service/url/URLConstants.java
new file mode 100644
index 0000000..6915096
--- /dev/null
+++ b/src/org/osgi/service/url/URLConstants.java
@@ -0,0 +1,36 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.url/src/org/osgi/service/url/URLConstants.java,v 1.6 2005/05/13 20:32:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.url;
+
+/**
+ * Defines standard names for property keys associated with
+ * {@link URLStreamHandlerService}and <code>java.net.ContentHandler</code>
+ * services.
+ * 
+ * <p>
+ * The values associated with these keys are of type <code>java.lang.String[]</code>,
+ * unless otherwise indicated.
+ * 
+ * @version $Revision: 1.6 $
+ */
+public interface URLConstants {
+	/**
+	 * Service property naming the protocols serviced by a
+	 * URLStreamHandlerService. The property's value is an array of protocol
+	 * names.
+	 */
+	public static final String	URL_HANDLER_PROTOCOL	= "url.handler.protocol";
+	/**
+	 * Service property naming the MIME types serviced by a
+	 * java.net.ContentHandler. The property's value is an array of MIME types.
+	 */
+	public static final String	URL_CONTENT_MIMETYPE	= "url.content.mimetype";
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/url/URLStreamHandlerService.java b/src/org/osgi/service/url/URLStreamHandlerService.java
new file mode 100644
index 0000000..9dea3ff
--- /dev/null
+++ b/src/org/osgi/service/url/URLStreamHandlerService.java
@@ -0,0 +1,84 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.url/src/org/osgi/service/url/URLStreamHandlerService.java,v 1.6 2005/05/13 20:32:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.url;
+
+import java.net.*;
+
+/**
+ * Service interface with public versions of the protected
+ * <code>java.net.URLStreamHandler</code> methods.
+ * <p>
+ * The important differences between this interface and the
+ * <code>URLStreamHandler</code> class are that the <code>setURL</code> method is
+ * absent and the <code>parseURL</code> method takes a
+ * {@link URLStreamHandlerSetter}object as the first argument. Classes
+ * implementing this interface must call the <code>setURL</code> method on the
+ * <code>URLStreamHandlerSetter</code> object received in the <code>parseURL</code>
+ * method instead of <code>URLStreamHandler.setURL</code> to avoid a
+ * <code>SecurityException</code>.
+ * 
+ * @see AbstractURLStreamHandlerService
+ * 
+ * @version $Revision: 1.6 $
+ */
+public interface URLStreamHandlerService {
+	/**
+	 * @see "java.net.URLStreamHandler.openConnection"
+	 */
+	public URLConnection openConnection(URL u) throws java.io.IOException;
+
+	/**
+	 * Parse a URL. This method is called by the <code>URLStreamHandler</code>
+	 * proxy, instead of <code>java.net.URLStreamHandler.parseURL</code>, passing
+	 * a <code>URLStreamHandlerSetter</code> object.
+	 * 
+	 * @param realHandler The object on which <code>setURL</code> must be invoked
+	 *        for this URL.
+	 * @see "java.net.URLStreamHandler.parseURL"
+	 */
+	public void parseURL(URLStreamHandlerSetter realHandler, URL u,
+			String spec, int start, int limit);
+
+	/**
+	 * @see "java.net.URLStreamHandler.toExternalForm"
+	 */
+	public String toExternalForm(URL u);
+
+	/**
+	 * @see "java.net.URLStreamHandler.equals(URL, URL)"
+	 */
+	public boolean equals(URL u1, URL u2);
+
+	/**
+	 * @see "java.net.URLStreamHandler.getDefaultPort"
+	 */
+	public int getDefaultPort();
+
+	/**
+	 * @see "java.net.URLStreamHandler.getHostAddress"
+	 */
+	public InetAddress getHostAddress(URL u);
+
+	/**
+	 * @see "java.net.URLStreamHandler.hashCode(URL)"
+	 */
+	public int hashCode(URL u);
+
+	/**
+	 * @see "java.net.URLStreamHandler.hostsEqual"
+	 */
+	public boolean hostsEqual(URL u1, URL u2);
+
+	/**
+	 * @see "java.net.URLStreamHandler.sameFile"
+	 */
+	public boolean sameFile(URL u1, URL u2);
+}
\ No newline at end of file
diff --git a/src/org/osgi/service/url/URLStreamHandlerSetter.java b/src/org/osgi/service/url/URLStreamHandlerSetter.java
new file mode 100644
index 0000000..1d59988
--- /dev/null
+++ b/src/org/osgi/service/url/URLStreamHandlerSetter.java
@@ -0,0 +1,44 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.url/src/org/osgi/service/url/URLStreamHandlerSetter.java,v 1.6 2005/05/13 20:32:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2002, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.service.url;
+
+import java.net.URL;
+
+/**
+ * Interface used by <code>URLStreamHandlerService</code> objects to call the
+ * <code>setURL</code> method on the proxy <code>URLStreamHandler</code> object.
+ * 
+ * <p>
+ * Objects of this type are passed to the
+ * {@link URLStreamHandlerService#parseURL}method. Invoking the <code>setURL</code>
+ * method on the <code>URLStreamHandlerSetter</code> object will invoke the
+ * <code>setURL</code> method on the proxy <code>URLStreamHandler</code> object that
+ * is actually registered with <code>java.net.URL</code> for the protocol.
+ * 
+ * @version $Revision: 1.6 $
+ */
+public interface URLStreamHandlerSetter {
+	/**
+	 * @see "java.net.URLStreamHandler.setURL(URL,String,String,int,String,String)"
+	 * 
+	 * @deprecated This method is only for compatibility with handlers written
+	 *             for JDK 1.1.
+	 */
+	public void setURL(URL u, String protocol, String host, int port,
+			String file, String ref);
+
+	/**
+	 * @see "java.net.URLStreamHandler.setURL(URL,String,String,int,String,String,String,String)"
+	 */
+	public void setURL(URL u, String protocol, String host, int port,
+			String authority, String userInfo, String path, String query,
+			String ref);
+}
\ No newline at end of file
diff --git a/src/org/osgi/util/tracker/ServiceTracker.java b/src/org/osgi/util/tracker/ServiceTracker.java
new file mode 100644
index 0000000..19ffb44
--- /dev/null
+++ b/src/org/osgi/util/tracker/ServiceTracker.java
@@ -0,0 +1,982 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.tracker/src/org/osgi/util/tracker/ServiceTracker.java,v 1.13 2005/05/13 20:33:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.util.tracker;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+
+/**
+ * The <code>ServiceTracker</code> class simplifies using services from the
+ * Framework's service registry.
+ * <p>
+ * A <code>ServiceTracker</code> object is constructed with search criteria
+ * and a <code>ServiceTrackerCustomizer</code> object. A
+ * <code>ServiceTracker</code> object can use the
+ * <code>ServiceTrackerCustomizer</code> object to customize the service
+ * objects to be tracked. The <code>ServiceTracker</code> object can then be
+ * opened to begin tracking all services in the Framework's service registry
+ * that match the specified search criteria. The <code>ServiceTracker</code>
+ * object correctly handles all of the details of listening to
+ * <code>ServiceEvent</code> objects and getting and ungetting services.
+ * <p>
+ * The <code>getServiceReferences</code> method can be called to get
+ * references to the services being tracked. The <code>getService</code> and
+ * <code>getServices</code> methods can be called to get the service objects
+ * for the tracked service.
+ * 
+ * @version $Revision: 1.13 $
+ */
+public class ServiceTracker implements ServiceTrackerCustomizer {
+	/* set this to true to compile in debug messages */
+	static final boolean					DEBUG			= false;
+	/**
+	 * Bundle context this <code>ServiceTracker</code> object is tracking
+	 * against.
+	 */
+	protected final BundleContext			context;
+	/**
+	 * Filter specifying search criteria for the services to track.
+	 * 
+	 * @since 1.1
+	 */
+	protected final Filter					filter;
+	/**
+	 * <code>ServiceTrackerCustomizer</code> object for this tracker.
+	 */
+	private final ServiceTrackerCustomizer	customizer;
+	/**
+	 * Filter string for use when adding the ServiceListener. If this field is
+	 * set, then certain optimizations can be taken since we don't have a user
+	 * supplied filter.
+	 */
+	private final String					listenerFilter;
+	/**
+	 * Class name to be tracked. If this field is set, then we are tracking by
+	 * class name.
+	 */
+	private final String					trackClass;
+	/**
+	 * Reference to be tracked. If this field is set, then we are tracking a
+	 * single ServiceReference.
+	 */
+	private final ServiceReference			trackReference;
+	/**
+	 * Tracked services: <code>ServiceReference</code> object -> customized
+	 * Object and <code>ServiceListener</code> object
+	 */
+	private Tracked							tracked;
+	/**
+	 * Modification count. This field is initialized to zero by open, set to -1
+	 * by close and incremented by modified. This field is volatile since it is
+	 * accessed by multiple threads.
+	 */
+	private volatile int					trackingCount	= -1;
+	/**
+	 * Cached ServiceReference for getServiceReference. This field is volatile
+	 * since it is accessed by multiple threads.
+	 */
+	private volatile ServiceReference		cachedReference;
+	/**
+	 * Cached service object for getService. This field is volatile since it is
+	 * accessed by multiple threads.
+	 */
+	private volatile Object					cachedService;
+
+	/**
+	 * Create a <code>ServiceTracker</code> object on the specified
+	 * <code>ServiceReference</code> object.
+	 * 
+	 * <p>
+	 * The service referenced by the specified <code>ServiceReference</code>
+	 * object will be tracked by this <code>ServiceTracker</code> object.
+	 * 
+	 * @param context <code>BundleContext</code> object against which the
+	 *        tracking is done.
+	 * @param reference <code>ServiceReference</code> object for the service
+	 *        to be tracked.
+	 * @param customizer The customizer object to call when services are added,
+	 *        modified, or removed in this <code>ServiceTracker</code> object.
+	 *        If customizer is <code>null</code>, then this
+	 *        <code>ServiceTracker</code> object will be used as the
+	 *        <code>ServiceTrackerCustomizer</code> object and the
+	 *        <code>ServiceTracker</code> object will call the
+	 *        <code>ServiceTrackerCustomizer</code> methods on itself.
+	 */
+	public ServiceTracker(BundleContext context, ServiceReference reference,
+			ServiceTrackerCustomizer customizer) {
+		this.context = context;
+		this.trackReference = reference;
+		this.trackClass = null;
+		this.customizer = (customizer == null) ? this : customizer;
+		this.listenerFilter = "(" + Constants.SERVICE_ID + "=" + reference.getProperty(Constants.SERVICE_ID).toString() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		try {
+			this.filter = context.createFilter(listenerFilter);
+		}
+		catch (InvalidSyntaxException e) { // we could only get this exception
+			// if the ServiceReference was
+			// invalid
+			throw new IllegalArgumentException(
+					"unexpected InvalidSyntaxException: " + e.getMessage()); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Create a <code>ServiceTracker</code> object on the specified class
+	 * name.
+	 * 
+	 * <p>
+	 * Services registered under the specified class name will be tracked by
+	 * this <code>ServiceTracker</code> object.
+	 * 
+	 * @param context <code>BundleContext</code> object against which the
+	 *        tracking is done.
+	 * @param clazz Class name of the services to be tracked.
+	 * @param customizer The customizer object to call when services are added,
+	 *        modified, or removed in this <code>ServiceTracker</code> object.
+	 *        If customizer is <code>null</code>, then this
+	 *        <code>ServiceTracker</code> object will be used as the
+	 *        <code>ServiceTrackerCustomizer</code> object and the
+	 *        <code>ServiceTracker</code> object will call the
+	 *        <code>ServiceTrackerCustomizer</code> methods on itself.
+	 */
+	public ServiceTracker(BundleContext context, String clazz,
+			ServiceTrackerCustomizer customizer) {
+		this.context = context;
+		this.trackReference = null;
+		this.trackClass = clazz;
+		this.customizer = (customizer == null) ? this : customizer;
+		this.listenerFilter = "(" + Constants.OBJECTCLASS + "=" + clazz.toString() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		try {
+			this.filter = context.createFilter(listenerFilter);
+		}
+		catch (InvalidSyntaxException e) { // we could only get this exception
+			// if the clazz argument was
+			// malformed
+			throw new IllegalArgumentException(
+					"unexpected InvalidSyntaxException: " + e.getMessage()); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Create a <code>ServiceTracker</code> object on the specified
+	 * <code>Filter</code> object.
+	 * 
+	 * <p>
+	 * Services which match the specified <code>Filter</code> object will be
+	 * tracked by this <code>ServiceTracker</code> object.
+	 * 
+	 * @param context <code>BundleContext</code> object against which the
+	 *        tracking is done.
+	 * @param filter <code>Filter</code> object to select the services to be
+	 *        tracked.
+	 * @param customizer The customizer object to call when services are added,
+	 *        modified, or removed in this <code>ServiceTracker</code> object.
+	 *        If customizer is null, then this <code>ServiceTracker</code>
+	 *        object will be used as the <code>ServiceTrackerCustomizer</code>
+	 *        object and the <code>ServiceTracker</code> object will call the
+	 *        <code>ServiceTrackerCustomizer</code> methods on itself.
+	 * @since 1.1
+	 */
+	public ServiceTracker(BundleContext context, Filter filter,
+			ServiceTrackerCustomizer customizer) {
+		this.context = context;
+		this.trackReference = null;
+		this.trackClass = null;
+		this.listenerFilter = null;
+		this.filter = filter;
+		this.customizer = (customizer == null) ? this : customizer;
+		if ((context == null) || (filter == null)) { // we throw a NPE here
+			// to
+			// be consistent with the
+			// other constructors
+			throw new NullPointerException();
+		}
+	}
+
+	/**
+	 * Open this <code>ServiceTracker</code> object and begin tracking
+	 * services.
+	 * 
+	 * <p>
+	 * This method calls <code>open(false)</code>.
+	 * 
+	 * @throws java.lang.IllegalStateException if the <code>BundleContext</code>
+	 *         object with which this <code>ServiceTracker</code> object was
+	 *         created is no longer valid.
+	 * @see #open(boolean)
+	 */
+	public void open() {
+		open(false);
+	}
+
+	/**
+	 * Open this <code>ServiceTracker</code> object and begin tracking
+	 * services.
+	 * 
+	 * <p>
+	 * Services which match the search criteria specified when this
+	 * <code>ServiceTracker</code> object was created are now tracked by this
+	 * <code>ServiceTracker</code> object.
+	 * 
+	 * @param trackAllServices If <code>true</code>, then this
+	 *        <code>ServiceTracker</code> will track all matching services
+	 *        regardless of class loader accessibility. If <code>false</code>,
+	 *        then this <code>ServiceTracker</code> will only track matching
+	 *        services which are class loader accessibile to the bundle whose
+	 *        <code>BundleContext</code> is used by this
+	 *        <code>ServiceTracker</code>.
+	 * @throws java.lang.IllegalStateException if the <code>BundleContext</code>
+	 *         object with which this <code>ServiceTracker</code> object was
+	 *         created is no longer valid.
+	 * @since 1.3
+	 */
+	public synchronized void open(boolean trackAllServices) {
+		if (tracked != null) {
+			return;
+		}
+		if (DEBUG) {
+			System.out.println("ServiceTracker.open: " + filter); //$NON-NLS-1$
+		}
+		tracked = trackAllServices ? new AllTracked() : new Tracked();
+		trackingCount = 0;
+		ServiceReference[] references;
+		synchronized (tracked) {
+			try {
+				context.addServiceListener(tracked, listenerFilter);
+				if (listenerFilter == null) { // user supplied filter
+					references = getInitialReferences(trackAllServices, null,
+							filter.toString());
+				}
+				else { // constructor supplied filter
+					if (trackClass == null) {
+						references = new ServiceReference[] {trackReference};
+					}
+					else {
+						references = getInitialReferences(trackAllServices,
+								trackClass, null);
+					}
+				}
+			}
+			catch (InvalidSyntaxException e) {
+				throw new RuntimeException(
+						"unexpected InvalidSyntaxException: " + e.getMessage()); //$NON-NLS-1$
+			}
+		}
+		/* Call tracked outside of synchronized region */
+		if (references != null) {
+			int length = references.length;
+			for (int i = 0; i < length; i++) {
+				ServiceReference reference = references[i];
+				/* if the service is still registered */
+				if (reference.getBundle() != null) {
+					tracked.track(reference);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns the list of initial <code>ServiceReference</code> objects that
+	 * will be tracked by this <code>ServiceTracker</code> object.
+	 * 
+	 * @param trackAllServices If true, use getAllServiceReferences.
+	 * @param trackClass the class name with which the service was registered,
+	 *        or null for all services.
+	 * @param filterString the filter criteria or null for all services.
+	 * @return the list of initial <code>ServiceReference</code> objects.
+	 * @throws InvalidSyntaxException if the filter uses an invalid syntax.
+	 */
+	private ServiceReference[] getInitialReferences(boolean trackAllServices,
+			String trackClass, String filterString)
+			throws InvalidSyntaxException {
+		if (trackAllServices) {
+			return context.getAllServiceReferences(trackClass, filterString);
+		}
+		else {
+			return context.getServiceReferences(trackClass, filterString);
+		}
+	}
+
+	/**
+	 * Close this <code>ServiceTracker</code> object.
+	 * 
+	 * <p>
+	 * This method should be called when this <code>ServiceTracker</code>
+	 * object should end the tracking of services.
+	 */
+	public synchronized void close() {
+		if (tracked == null) {
+			return;
+		}
+		if (DEBUG) {
+			System.out.println("ServiceTracker.close: " + filter); //$NON-NLS-1$
+		}
+		tracked.close();
+		ServiceReference[] references = getServiceReferences();
+		Tracked outgoing = tracked;
+		tracked = null;
+		try {
+			context.removeServiceListener(outgoing);
+		}
+		catch (IllegalStateException e) {
+			/* In case the context was stopped. */
+		}
+		if (references != null) {
+			for (int i = 0; i < references.length; i++) {
+				outgoing.untrack(references[i]);
+			}
+		}
+		trackingCount = -1;
+		if (DEBUG) {
+			if ((cachedReference == null) && (cachedService == null)) {
+				System.out
+						.println("ServiceTracker.close[cached cleared]: " + filter); //$NON-NLS-1$
+			}
+		}
+	}
+
+	/**
+	 * Default implementation of the
+	 * <code>ServiceTrackerCustomizer.addingService</code> method.
+	 * 
+	 * <p>
+	 * This method is only called when this <code>ServiceTracker</code> object
+	 * has been constructed with a <code>null ServiceTrackerCustomizer</code>
+	 * argument.
+	 * 
+	 * The default implementation returns the result of calling
+	 * <code>getService</code>, on the <code>BundleContext</code> object
+	 * with which this <code>ServiceTracker</code> object was created, passing
+	 * the specified <code>ServiceReference</code> object.
+	 * <p>
+	 * This method can be overridden in a subclass to customize the service
+	 * object to be tracked for the service being added. In that case, take care
+	 * not to rely on the default implementation of removedService that will
+	 * unget the service.
+	 * 
+	 * @param reference Reference to service being added to this
+	 *        <code>ServiceTracker</code> object.
+	 * @return The service object to be tracked for the service added to this
+	 *         <code>ServiceTracker</code> object.
+	 * @see ServiceTrackerCustomizer
+	 */
+	public Object addingService(ServiceReference reference) {
+		return context.getService(reference);
+	}
+
+	/**
+	 * Default implementation of the
+	 * <code>ServiceTrackerCustomizer.modifiedService</code> method.
+	 * 
+	 * <p>
+	 * This method is only called when this <code>ServiceTracker</code> object
+	 * has been constructed with a <code>null ServiceTrackerCustomizer</code>
+	 * argument.
+	 * 
+	 * The default implementation does nothing.
+	 * 
+	 * @param reference Reference to modified service.
+	 * @param service The service object for the modified service.
+	 * @see ServiceTrackerCustomizer
+	 */
+	public void modifiedService(ServiceReference reference, Object service) {
+	}
+
+	/**
+	 * Default implementation of the
+	 * <code>ServiceTrackerCustomizer.removedService</code> method.
+	 * 
+	 * <p>
+	 * This method is only called when this <code>ServiceTracker</code> object
+	 * has been constructed with a <code>null ServiceTrackerCustomizer</code>
+	 * argument.
+	 * 
+	 * The default implementation calls <code>ungetService</code>, on the
+	 * <code>BundleContext</code> object with which this
+	 * <code>ServiceTracker</code> object was created, passing the specified
+	 * <code>ServiceReference</code> object.
+	 * <p>
+	 * This method can be overridden in a subclass. If the default
+	 * implementation of <code>addingService</code> method was used, this
+	 * method must unget the service.
+	 * 
+	 * @param reference Reference to removed service.
+	 * @param service The service object for the removed service.
+	 * @see ServiceTrackerCustomizer
+	 */
+	public void removedService(ServiceReference reference, Object service) {
+		context.ungetService(reference);
+	}
+
+	/**
+	 * Wait for at least one service to be tracked by this
+	 * <code>ServiceTracker</code> object.
+	 * <p>
+	 * It is strongly recommended that <code>waitForService</code> is not used
+	 * during the calling of the <code>BundleActivator</code> methods.
+	 * <code>BundleActivator</code> methods are expected to complete in a
+	 * short period of time.
+	 * 
+	 * @param timeout time interval in milliseconds to wait. If zero, the method
+	 *        will wait indefinately.
+	 * @return Returns the result of <code>getService()</code>.
+	 * @throws IllegalArgumentException If the value of timeout is negative.
+	 */
+	public Object waitForService(long timeout) throws InterruptedException {
+		if (timeout < 0) {
+			throw new IllegalArgumentException("timeout value is negative"); //$NON-NLS-1$
+		}
+		Object object = getService();
+		while (object == null) {
+			Tracked tracked = this.tracked; /*
+											 * use local var since we are not
+											 * synchronized
+											 */
+			if (tracked == null) /* if ServiceTracker is not open */
+			{
+				return null;
+			}
+			synchronized (tracked) {
+				if (tracked.size() == 0) {
+					tracked.wait(timeout);
+				}
+			}
+			object = getService();
+			if (timeout > 0) {
+				return object;
+			}
+		}
+		return object;
+	}
+
+	/**
+	 * Return an array of <code>ServiceReference</code> objects for all
+	 * services being tracked by this <code>ServiceTracker</code> object.
+	 * 
+	 * @return Array of <code>ServiceReference</code> objects or
+	 *         <code>null</code> if no service are being tracked.
+	 */
+	public ServiceReference[] getServiceReferences() {
+		Tracked tracked = this.tracked; /*
+										 * use local var since we are not
+										 * synchronized
+										 */
+		if (tracked == null) /* if ServiceTracker is not open */
+		{
+			return null;
+		}
+		synchronized (tracked) {
+			int length = tracked.size();
+			if (length == 0) {
+				return null;
+			}
+			ServiceReference[] references = new ServiceReference[length];
+			Enumeration keys = tracked.keys();
+			for (int i = 0; i < length; i++) {
+				references[i] = (ServiceReference) keys.nextElement();
+			}
+			return references;
+		}
+	}
+
+	/**
+	 * Returns a <code>ServiceReference</code> object for one of the services
+	 * being tracked by this <code>ServiceTracker</code> object.
+	 * 
+	 * <p>
+	 * If multiple services are being tracked, the service with the highest
+	 * ranking (as specified in its <code>service.ranking</code> property) is
+	 * returned.
+	 * 
+	 * <p>
+	 * If there is a tie in ranking, the service with the lowest service ID (as
+	 * specified in its <code>service.id</code> property); that is, the
+	 * service that was registered first is returned.
+	 * <p>
+	 * This is the same algorithm used by
+	 * <code>BundleContext.getServiceReference</code>.
+	 * 
+	 * @return <code>ServiceReference</code> object or <code>null</code> if
+	 *         no service is being tracked.
+	 * @since 1.1
+	 */
+	public ServiceReference getServiceReference() {
+		ServiceReference reference = cachedReference;
+		if (reference != null) {
+			if (DEBUG) {
+				System.out
+						.println("ServiceTracker.getServiceReference[cached]: " + filter); //$NON-NLS-1$
+			}
+			return reference;
+		}
+		if (DEBUG) {
+			System.out.println("ServiceTracker.getServiceReference: " + filter); //$NON-NLS-1$
+		}
+		ServiceReference[] references = getServiceReferences();
+		int length = (references == null) ? 0 : references.length;
+		if (length == 0) /* if no service is being tracked */
+		{
+			return null;
+		}
+		int index = 0;
+		if (length > 1) /* if more than one service, select highest ranking */
+		{
+			int rankings[] = new int[length];
+			int count = 0;
+			int maxRanking = Integer.MIN_VALUE;
+			for (int i = 0; i < length; i++) {
+				Object property = references[i]
+						.getProperty(Constants.SERVICE_RANKING);
+				int ranking = (property instanceof Integer) ? ((Integer) property)
+						.intValue()
+						: 0;
+				rankings[i] = ranking;
+				if (ranking > maxRanking) {
+					index = i;
+					maxRanking = ranking;
+					count = 1;
+				}
+				else {
+					if (ranking == maxRanking) {
+						count++;
+					}
+				}
+			}
+			if (count > 1) /* if still more than one service, select lowest id */
+			{
+				long minId = Long.MAX_VALUE;
+				for (int i = 0; i < length; i++) {
+					if (rankings[i] == maxRanking) {
+						long id = ((Long) (references[i]
+								.getProperty(Constants.SERVICE_ID)))
+								.longValue();
+						if (id < minId) {
+							index = i;
+							minId = id;
+						}
+					}
+				}
+			}
+		}
+		return cachedReference = references[index];
+	}
+
+	/**
+	 * Returns the service object for the specified
+	 * <code>ServiceReference</code> object if the referenced service is being
+	 * tracked by this <code>ServiceTracker</code> object.
+	 * 
+	 * @param reference Reference to the desired service.
+	 * @return Service object or <code>null</code> if the service referenced
+	 *         by the specified <code>ServiceReference</code> object is not
+	 *         being tracked.
+	 */
+	public Object getService(ServiceReference reference) {
+		Tracked tracked = this.tracked; /*
+										 * use local var since we are not
+										 * synchronized
+										 */
+		if (tracked == null) /* if ServiceTracker is not open */
+		{
+			return null;
+		}
+		synchronized (tracked) {
+			return tracked.get(reference);
+		}
+	}
+
+	/**
+	 * Return an array of service objects for all services being tracked by this
+	 * <code>ServiceTracker</code> object.
+	 * 
+	 * @return Array of service objects or <code>null</code> if no service are
+	 *         being tracked.
+	 */
+	public Object[] getServices() {
+		Tracked tracked = this.tracked; /*
+										 * use local var since we are not
+										 * synchronized
+										 */
+		if (tracked == null) /* if ServiceTracker is not open */
+		{
+			return null;
+		}
+		synchronized (tracked) {
+			ServiceReference[] references = getServiceReferences();
+			int length = (references == null) ? 0 : references.length;
+			if (length == 0) {
+				return null;
+			}
+			Object[] objects = new Object[length];
+			for (int i = 0; i < length; i++) {
+				objects[i] = getService(references[i]);
+			}
+			return objects;
+		}
+	}
+
+	/**
+	 * Returns a service object for one of the services being tracked by this
+	 * <code>ServiceTracker</code> object.
+	 * 
+	 * <p>
+	 * If any services are being tracked, this method returns the result of
+	 * calling <code>getService(getServiceReference())</code>.
+	 * 
+	 * @return Service object or <code>null</code> if no service is being
+	 *         tracked.
+	 */
+	public Object getService() {
+		Object service = cachedService;
+		if (service != null) {
+			if (DEBUG) {
+				System.out
+						.println("ServiceTracker.getService[cached]: " + filter); //$NON-NLS-1$
+			}
+			return service;
+		}
+		if (DEBUG) {
+			System.out.println("ServiceTracker.getService: " + filter); //$NON-NLS-1$
+		}
+		ServiceReference reference = getServiceReference();
+		if (reference == null) {
+			return null;
+		}
+		return cachedService = getService(reference);
+	}
+
+	/**
+	 * Remove a service from this <code>ServiceTracker</code> object.
+	 * 
+	 * The specified service will be removed from this
+	 * <code>ServiceTracker</code> object. If the specified service was being
+	 * tracked then the <code>ServiceTrackerCustomizer.removedService</code>
+	 * method will be called for that service.
+	 * 
+	 * @param reference Reference to the service to be removed.
+	 */
+	public void remove(ServiceReference reference) {
+		Tracked tracked = this.tracked; /*
+										 * use local var since we are not
+										 * synchronized
+										 */
+		if (tracked == null) /* if ServiceTracker is not open */
+		{
+			return;
+		}
+		tracked.untrack(reference);
+	}
+
+	/**
+	 * Return the number of services being tracked by this
+	 * <code>ServiceTracker</code> object.
+	 * 
+	 * @return Number of services being tracked.
+	 */
+	public int size() {
+		Tracked tracked = this.tracked; /*
+										 * use local var since we are not
+										 * synchronized
+										 */
+		if (tracked == null) /* if ServiceTracker is not open */
+		{
+			return 0;
+		}
+		return tracked.size();
+	}
+
+	/**
+	 * Returns the tracking count for this <code>ServiceTracker</code> object.
+	 * 
+	 * The tracking count is initialized to 0 when this
+	 * <code>ServiceTracker</code> object is opened. Every time a service is
+	 * added or removed from this <code>ServiceTracker</code> object the
+	 * tracking count is incremented.
+	 * 
+	 * <p>
+	 * The tracking count can be used to determine if this
+	 * <code>ServiceTracker</code> object has added or removed a service by
+	 * comparing a tracking count value previously collected with the current
+	 * tracking count value. If the value has not changed, then no service has
+	 * been added or removed from this <code>ServiceTracker</code> object
+	 * since the previous tracking count was collected.
+	 * 
+	 * @since 1.2
+	 * @return The tracking count for this <code>ServiceTracker</code> object
+	 *         or -1 if this <code>ServiceTracker</code> object is not open.
+	 */
+	public int getTrackingCount() {
+		return trackingCount;
+	}
+
+	/**
+	 * Called by the Tracked object whenever the set of tracked services is
+	 * modified. Increments the tracking count and clears the cache.
+	 */
+	/*
+	 * This method must not be synchronized since it is called by Tracked while
+	 * Tracked is synchronized. We don't want synchronization interactions
+	 * between the ServiceListener thread and the user thread.
+	 */
+	private void modified() {
+		trackingCount++; /* increment modification count */
+		cachedReference = null; /* clear cached value */
+		cachedService = null; /* clear cached value */
+		if (DEBUG) {
+			System.out.println("ServiceTracker.modified: " + filter); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Inner class to track services. If a <code>ServiceTracker</code> object
+	 * is reused (closed then reopened), then a new Tracked object is used. This
+	 * class is a hashtable mapping <code>ServiceReference</code> object ->
+	 * customized Object. This class is the <code>ServiceListener</code>
+	 * object for the tracker. This class is used to synchronize access to the
+	 * tracked services. This is not a public class. It is only for use by the
+	 * implementation of the <code>ServiceTracker</code> class.
+	 * 
+	 */
+	class Tracked extends Hashtable implements ServiceListener {
+		static final long			serialVersionUID	= -7420065199791006079L;
+		/**
+		 * List of ServiceReferences in the process of being added.
+		 */
+		private ArrayList			adding;
+		/**
+		 * true if the tracked object is closed. This field is volatile because
+		 * it is set by one thread and read by another.
+		 */
+		private volatile boolean	closed;
+
+		/**
+		 * Tracked constructor.
+		 */
+		protected Tracked() {
+			super();
+			closed = false;
+			adding = new ArrayList(6);
+		}
+
+		/**
+		 * Called by the owning <code>ServiceTracker</code> object when it is
+		 * closed.
+		 */
+		protected void close() {
+			closed = true;
+		}
+
+		/**
+		 * <code>ServiceListener</code> method for the
+		 * <code>ServiceTracker</code> class. This method must NOT be
+		 * synchronized to avoid deadlock potential.
+		 * 
+		 * @param event <code>ServiceEvent</code> object from the framework.
+		 */
+		public void serviceChanged(ServiceEvent event) {
+			/*
+			 * Check if we had a delayed call (which could happen when we
+			 * close).
+			 */
+			if (closed) {
+				return;
+			}
+			ServiceReference reference = event.getServiceReference();
+			switch (event.getType()) {
+				case ServiceEvent.REGISTERED :
+				case ServiceEvent.MODIFIED :
+					if (listenerFilter != null) { // constructor supplied
+						// filter
+						track(reference);
+						/*
+						 * If the customizer throws an unchecked exception, it
+						 * is safe to let it propagate
+						 */
+					}
+					else { // user supplied filter
+						if (filter.match(reference)) {
+							track(reference);
+							/*
+							 * If the customizer throws an unchecked exception,
+							 * it is safe to let it propagate
+							 */
+						}
+						else {
+							untrack(reference);
+							/*
+							 * If the customizer throws an unchecked exception,
+							 * it is safe to let it propagate
+							 */
+						}
+					}
+					break;
+				case ServiceEvent.UNREGISTERING :
+					untrack(reference);
+					/*
+					 * If the customizer throws an unchecked exception, it is
+					 * safe to let it propagate
+					 */
+					break;
+			}
+		}
+
+		/**
+		 * Begin to track the referenced service.
+		 * 
+		 * @param reference Reference to a service to be tracked.
+		 */
+		protected void track(ServiceReference reference) {
+			Object object;
+			synchronized (this) {
+				object = this.get(reference);
+			}
+			if (object != null) /* we are already tracking the service */
+			{
+				if (DEBUG) {
+					System.out
+							.println("ServiceTracker.Tracked.track[modified]: " + reference); //$NON-NLS-1$
+				}
+				/* Call customizer outside of synchronized region */
+				customizer.modifiedService(reference, object);
+				/*
+				 * If the customizer throws an unchecked exception, it is safe
+				 * to let it propagate
+				 */
+				return;
+			}
+			synchronized (this) {
+				if (adding.contains(reference)) /*
+												 * if this service is already in
+												 * the process of being added.
+												 */
+				{
+					if (DEBUG) {
+						System.out
+								.println("ServiceTracker.Tracked.track[already adding]: " + reference); //$NON-NLS-1$
+					}
+					return;
+				}
+				adding.add(reference); /* mark this service is being added */
+			}
+			if (DEBUG) {
+				System.out
+						.println("ServiceTracker.Tracked.track[adding]: " + reference); //$NON-NLS-1$
+			}
+			boolean becameUntracked = false;
+			/* Call customizer outside of synchronized region */
+			try {
+				object = customizer.addingService(reference);
+				/*
+				 * If the customizer throws an unchecked exception, it will
+				 * propagate after the finally
+				 */
+			}
+			finally {
+				synchronized (this) {
+					if (adding.remove(reference)) /*
+													 * if the service was not
+													 * untracked during the
+													 * customizer callback
+													 */
+					{
+						if (object != null) {
+							this.put(reference, object);
+							modified(); /* increment modification count */
+							notifyAll();
+						}
+					}
+					else {
+						becameUntracked = true;
+					}
+				}
+			}
+			/*
+			 * The service became untracked during the customizer callback.
+			 */
+			if (becameUntracked) {
+				if (DEBUG) {
+					System.out
+							.println("ServiceTracker.Tracked.track[removed]: " + reference); //$NON-NLS-1$
+				}
+				/* Call customizer outside of synchronized region */
+				customizer.removedService(reference, object);
+				/*
+				 * If the customizer throws an unchecked exception, it is safe
+				 * to let it propagate
+				 */
+			}
+		}
+
+		/**
+		 * Discontinue tracking the referenced service.
+		 * 
+		 * @param reference Reference to the tracked service.
+		 */
+		protected void untrack(ServiceReference reference) {
+			Object object;
+			synchronized (this) {
+				if (adding.remove(reference)) /*
+												 * if the service is in the
+												 * process of being added
+												 */
+				{
+					if (DEBUG) {
+						System.out
+								.println("ServiceTracker.Tracked.untrack[being added]: " + reference); //$NON-NLS-1$
+					}
+					return; /*
+							 * in case the service is untracked while in the
+							 * process of adding
+							 */
+				}
+				object = this.remove(reference); /*
+													 * must remove from tracker
+													 * before calling customizer
+													 * callback
+													 */
+				if (object == null) /* are we actually tracking the service */
+				{
+					return;
+				}
+				modified(); /* increment modification count */
+			}
+			if (DEBUG) {
+				System.out
+						.println("ServiceTracker.Tracked.untrack[removed]: " + reference); //$NON-NLS-1$
+			}
+			/* Call customizer outside of synchronized region */
+			customizer.removedService(reference, object);
+			/*
+			 * If the customizer throws an unchecked exception, it is safe to
+			 * let it propagate
+			 */
+		}
+	}
+
+	/**
+	 * Subclass of Tracked which implements the AllServiceListener interface.
+	 * This class is used by the ServiceTracker if isAllServiceTracker returns
+	 * true.
+	 * 
+	 * @since 1.3
+	 */
+	class AllTracked extends Tracked implements AllServiceListener {
+		static final long	serialVersionUID	= 4050764875305137716L;
+
+		/**
+		 * Tracked constructor.
+		 */
+		protected AllTracked() {
+			super();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/org/osgi/util/tracker/ServiceTrackerCustomizer.java b/src/org/osgi/util/tracker/ServiceTrackerCustomizer.java
new file mode 100644
index 0000000..be4459e
--- /dev/null
+++ b/src/org/osgi/util/tracker/ServiceTrackerCustomizer.java
@@ -0,0 +1,84 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.tracker/src/org/osgi/util/tracker/ServiceTrackerCustomizer.java,v 1.7 2005/05/13 20:33:35 hargrave Exp $
+ * 
+ * Copyright (c) OSGi Alliance (2000, 2005). All Rights Reserved.
+ * 
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this 
+ * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html.
+ */
+
+package org.osgi.util.tracker;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The <code>ServiceTrackerCustomizer</code> interface allows a
+ * <code>ServiceTracker</code> object to customize the service objects that are
+ * tracked. The <code>ServiceTrackerCustomizer</code> object is called when a
+ * service is being added to the <code>ServiceTracker</code> object. The
+ * <code>ServiceTrackerCustomizer</code> can then return an object for the tracked
+ * service. The <code>ServiceTrackerCustomizer</code> object is also called when a
+ * tracked service is modified or has been removed from the
+ * <code>ServiceTracker</code> object.
+ * 
+ * <p>
+ * The methods in this interface may be called as the result of a
+ * <code>ServiceEvent</code> being received by a <code>ServiceTracker</code> object.
+ * Since <code>ServiceEvent</code> s are synchronously delivered by the Framework,
+ * it is highly recommended that implementations of these methods do not
+ * register (<code>BundleContext.registerService</code>), modify (
+ * <code>ServiceRegistration.setProperties</code>) or unregister (
+ * <code>ServiceRegistration.unregister</code>) a service while being
+ * synchronized on any object.
+ * 
+ * @version $Revision: 1.7 $
+ */
+public interface ServiceTrackerCustomizer {
+	/**
+	 * A service is being added to the <code>ServiceTracker</code> object.
+	 * 
+	 * <p>
+	 * This method is called before a service which matched the search
+	 * parameters of the <code>ServiceTracker</code> object is added to it. This
+	 * method should return the service object to be tracked for this
+	 * <code>ServiceReference</code> object. The returned service object is stored
+	 * in the <code>ServiceTracker</code> object and is available from the
+	 * <code>getService</code> and <code>getServices</code> methods.
+	 * 
+	 * @param reference Reference to service being added to the
+	 *        <code>ServiceTracker</code> object.
+	 * @return The service object to be tracked for the
+	 *         <code>ServiceReference</code> object or <code>null</code> if the
+	 *         <code>ServiceReference</code> object should not be tracked.
+	 */
+	public abstract Object addingService(ServiceReference reference);
+
+	/**
+	 * A service tracked by the <code>ServiceTracker</code> object has been
+	 * modified.
+	 * 
+	 * <p>
+	 * This method is called when a service being tracked by the
+	 * <code>ServiceTracker</code> object has had it properties modified.
+	 * 
+	 * @param reference Reference to service that has been modified.
+	 * @param service The service object for the modified service.
+	 */
+	public abstract void modifiedService(ServiceReference reference,
+			Object service);
+
+	/**
+	 * A service tracked by the <code>ServiceTracker</code> object has been
+	 * removed.
+	 * 
+	 * <p>
+	 * This method is called after a service is no longer being tracked by the
+	 * <code>ServiceTracker</code> object.
+	 * 
+	 * @param reference Reference to service that has been removed.
+	 * @param service The service object for the removed service.
+	 */
+	public abstract void removedService(ServiceReference reference,
+			Object service);
+}
\ No newline at end of file
diff --git a/src/org/ungoverned/osgi/service/shell/CdCommand.java b/src/org/ungoverned/osgi/service/shell/CdCommand.java
new file mode 100644
index 0000000..227ac3d
--- /dev/null
+++ b/src/org/ungoverned/osgi/service/shell/CdCommand.java
@@ -0,0 +1,49 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.ungoverned.osgi.service.shell;
+
+/**
+ * This interface defines the <tt>cd</tt> command service interface for the
+ * Felix shell service. The <tt>cd</tt> command does not really change the
+ * directory of the shell, rather it maintains a base URL for
+ * simplifying URL entry.
+ * <p>
+ * For example, if the base URL is <tt>http://www.foo.com/<tt> and you
+ * try to install a bundle <tt>foo.jar</tt>, the actual URL will be
+ * expanded to <tt>http://www.foo.com/foo.jar</tt>. Any bundles wishing
+ * to retrieve or set the current directory of the shell can use this
+ * service interface.
+**/
+public interface CdCommand extends Command
+{
+    /**
+     * Property used to configure the base URL.
+    **/
+    public static final String BASE_URL_PROPERTY = "felix.shell.baseurl";
+
+    /**
+     * Returns the current <i>directory</i> of the shell service.
+     * @return the current shell directory.
+    **/
+    public String getBaseURL();
+
+    /**
+     * Sets the current <i>directory</i> of the shell service.
+     * @param s the new value for the base URL.
+    **/
+    public void setBaseURL(String s);
+}
diff --git a/src/org/ungoverned/osgi/service/shell/Command.java b/src/org/ungoverned/osgi/service/shell/Command.java
new file mode 100644
index 0000000..16ee2f6
--- /dev/null
+++ b/src/org/ungoverned/osgi/service/shell/Command.java
@@ -0,0 +1,68 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.ungoverned.osgi.service.shell;
+
+import java.io.PrintStream;
+
+/**
+ * This interface is used to define commands for the Felix shell
+ * service. Any bundle wishing to create commands for the
+ * shell service simply needs to create a service object that
+ * implements this interface and then register it with the OSGi
+ * framework. The shell service automatically includes any
+ * registered command services in its list of available commands.
+**/
+public interface Command
+{
+    /**
+     * Returns the name of the command that is implemented by the
+     * interface. The command name should not contain whitespace
+     * and should also be unique.
+     * @return the name of the command.
+    **/
+    public String getName();
+
+    /**
+     * Returns the usage string for the command. The usage string is
+     * a short string that illustrates how to use the command on the
+     * command line. This information is used when generating command
+     * help information. An example usage string for the <tt>install</tt>
+     * command is:
+     * <pre>
+     *     install <URL> [<URL> ...]
+     * </pre>
+     * @return the usage string for the command.
+    **/
+    public String getUsage();
+
+    /**
+     * Returns a short description of the command; this description
+     * should be as short as possible. This information is used when
+     * generating the command help information.
+     * @return a short description of the command.
+    **/
+    public String getShortDescription();
+
+    /**
+     * Executes the command using the supplied command line, output
+     * print stream, and error print stream.
+     * @param line the complete command line, including the command name.
+     * @param out the print stream to use for standard output.
+     * @param err the print stream to use for standard error.
+    **/
+    public void execute(String line, PrintStream out, PrintStream err);
+}
diff --git a/src/org/ungoverned/osgi/service/shell/ShellService.java b/src/org/ungoverned/osgi/service/shell/ShellService.java
new file mode 100644
index 0000000..41b954e
--- /dev/null
+++ b/src/org/ungoverned/osgi/service/shell/ShellService.java
@@ -0,0 +1,95 @@
+/*
+ *   Copyright 2005 The Apache Software Foundation
+ *
+ *   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.ungoverned.osgi.service.shell;
+
+import java.io.PrintStream;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This interface defines the Felix shell service. The shell service
+ * is an extensible, user interface neutral shell for controlling and
+ * interacting with the framework. In general, the shell service assumes that
+ * it is operating in a command line fashion, i.e., it receives a
+ * complete command line, parses it, and executes the corresponding
+ * command, but graphical interfaces are also possible.
+ * <p>
+ * All commands in the shell service are actually implemented as OSGi
+ * services; these services implement the <tt>Command</tt> service
+ * interface. Any bundle can implement custom commands by creating
+ * command services and registering them with the OSGi framework.
+**/
+public interface ShellService
+{
+    /**
+     * Returns an array of command names available in the shell service.
+     * @return an array of available command names or an empty array.
+    **/
+    public String[] getCommands();
+
+    /**
+     * Returns the usage string associated with the specified command name.
+     * @param name the name of the target command.
+     * @return the usage string of the specified command or null.
+    **/
+    public String getCommandUsage(String name);
+
+    /**
+     * Returns the description associated with the specified command name.
+     * @param name the name of the target command.
+     * @return the description of the specified command or null.
+    **/
+    public String getCommandDescription(String name);
+
+    /**
+     * Returns the service reference associated with the specified
+     * command name.
+     * @param name the name of the target command.
+     * @return the description of the specified command or null.
+    **/
+    public ServiceReference getCommandReference(String name);
+
+    /**
+     *
+     * This method executes the supplied command line using the
+     * supplied output and error print stream. The assumption of
+     * this method is that a command line will be typed by the user
+     * (or perhaps constructed by a GUI) and passed into it for
+     * execution. The command line is interpretted in a very
+     * simplistic fashion; it takes the leading string of characters
+     * terminated by a space character (not including it) and
+     * assumes that this leading token is the command name. For an
+     * example, consider the following command line:
+     * </p>
+     * <pre>
+     *     update 3 http://www.foo.com/bar.jar
+     * </pre>
+     * <p>
+     * This is interpretted as an <tt>update</tt> command; as a
+     * result, the entire command line (include command name) is
+     * passed into the <tt>execute()</tt> method of the command
+     * service with the name <tt>update</tt> if one exists. If the
+     * corresponding command service is not found, then an error
+     * message is printed to the error print stream.
+     * @param commandLine the command line to execute.
+     * @param out the standard output print stream.
+     * @param err the standard error print stream.
+    **/
+    public void executeCommand(
+        String commandLine, PrintStream out, PrintStream err)
+        throws Exception;
+}
\ No newline at end of file