Initial source commit.


git-svn-id: https://svn.apache.org/repos/asf/incubator/oscar/trunk@233031 13f79535-47bb-0310-9956-ffa450edef68
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"