[FELIX-4637] Gogo can't cope without several commands with defined service.ranking

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1624391 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
index 65eebcf..6f41986 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
@@ -29,8 +29,11 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.SortedMap;
 import java.util.TreeSet;
 import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 import org.apache.felix.gogo.api.CommandSessionListener;
@@ -44,10 +47,11 @@
 {
     protected final Set<Converter> converters = new CopyOnWriteArraySet<Converter>();
     protected final Set<CommandSessionListener> listeners = new CopyOnWriteArraySet<CommandSessionListener>();
-    protected final Map<String, Object> commands = new LinkedHashMap<String, Object>();
-    protected final Map<String, Object> constants = new HashMap<String, Object>();
+    protected final ConcurrentMap<String, Map<Object, Integer>> commands = new ConcurrentHashMap<String, Map<Object, Integer>>();
+    protected final Map<String, Object> constants = new ConcurrentHashMap<String, Object>();
     protected final ThreadIO threadIO;
     protected final WeakHashMap<CommandSession, Object> sessions = new WeakHashMap<CommandSession, Object>();
+    protected boolean stopped;
 
     public CommandProcessorImpl(ThreadIO tio)
     {
@@ -58,6 +62,9 @@
     {
         synchronized (sessions)
         {
+            if (stopped) {
+                throw new IllegalStateException("CommandProcessor has been stopped");
+            }
             CommandSessionImpl session = new CommandSessionImpl(this, in, out, err);
             sessions.put(session, null);
             return session;
@@ -68,6 +75,7 @@
     {
         synchronized (sessions)
         {
+            stopped = true;
             for (CommandSession session : sessions.keySet())
             {
                 session.close();
@@ -113,9 +121,9 @@
         String cfunction = name.substring(colon);
         boolean anyScope = (colon == 1 && name.charAt(0) == '*');
 
-        Object cmd = commands.get(name);
+        Map<Object, Integer> cmdMap = commands.get(name);
 
-        if (null == cmd && anyScope)
+        if (null == cmdMap && anyScope)
         {
             String scopePath = (null == path ? "*" : path.toString());
 
@@ -123,30 +131,37 @@
             {
                 if (scope.equals("*"))
                 {
-                    synchronized (commands)
+                    for (Entry<String, Map<Object, Integer>> entry : commands.entrySet())
                     {
-                        for (Entry<String, Object> entry : commands.entrySet())
+                        if (entry.getKey().endsWith(cfunction))
                         {
-                            if (entry.getKey().endsWith(cfunction))
-                            {
-                                cmd = entry.getValue();
-                                break;
-                            }
+                            cmdMap = entry.getValue();
+                            break;
                         }
                     }
                 }
                 else
                 {
-                    cmd = commands.get(scope + cfunction);
-                }
-
-                if (cmd != null)
-                {
-                    break;
+                    cmdMap = commands.get(scope + cfunction);
+                    if (cmdMap != null)
+                    {
+                        break;
+                    }
                 }
             }
         }
 
+        Object cmd = null;
+        if (cmdMap != null && !cmdMap.isEmpty())
+        {
+            for (Entry<Object, Integer> e : cmdMap.entrySet())
+            {
+                if (cmd == null || e.getValue() > cmdMap.get(cmd))
+                {
+                    cmd = e.getKey();
+                }
+            }
+        }
         if ((null == cmd) || (cmd instanceof Function))
         {
             return (Function) cmd;
@@ -164,6 +179,11 @@
 
     public void addCommand(String scope, Object target, Class<?> functions)
     {
+        addCommand(scope, target, functions, 0);
+    }
+
+    public void addCommand(String scope, Object target, Class<?> functions, int ranking)
+    {
         if (target == null)
         {
             return;
@@ -172,7 +192,7 @@
         String[] names = getFunctions(functions);
         for (String function : names)
         {
-            addCommand(scope, target, function);
+            addCommand(scope, target, function, ranking);
         }
     }
 
@@ -188,32 +208,44 @@
 
     public void addCommand(String scope, Object target, String function)
     {
-        synchronized (commands)
+        addCommand(scope, target, function, 0);
+    }
+
+    public void addCommand(String scope, Object target, String function, int ranking)
+    {
+        String key = (scope + ":" + function).toLowerCase();
+        Map<Object, Integer> cmdMap = commands.get(key);
+        if (cmdMap == null)
         {
-            commands.put((scope + ":" + function).toLowerCase(), target);
+            commands.putIfAbsent(key, new LinkedHashMap<Object, Integer>());
+            cmdMap = commands.get(key);
         }
+        cmdMap.put(target, ranking);
     }
 
     public void removeCommand(String scope, String function)
     {
-        String func = (scope + ":" + function).toLowerCase();
-        synchronized (commands)
+        // TODO: WARNING: this method does remove all mapping for scope:function
+        String key = (scope + ":" + function).toLowerCase();
+        commands.remove(key);
+    }
+
+    public void removeCommand(String scope, String function, Object target)
+    {
+        // TODO: WARNING: this method does remove all mapping for scope:function
+        String key = (scope + ":" + function).toLowerCase();
+        Map<Object, Integer> cmdMap = commands.get(key);
+        if (cmdMap != null)
         {
-            commands.remove(func);
+            cmdMap.remove(target);
         }
     }
 
     public void removeCommand(Object target)
     {
-        synchronized (commands)
+        for (Map<Object, Integer> cmdMap : commands.values())
         {
-            for (Iterator<Object> i = commands.values().iterator(); i.hasNext();)
-            {
-                if (i.next() == target)
-                {
-                    i.remove();
-                }
-            }
+            cmdMap.remove(target);
         }
     }
 
@@ -243,14 +275,6 @@
         return functions;
     }
 
-    protected void put(String name, Object target)
-    {
-        synchronized (commands)
-        {
-            commands.put(name, target);
-        }
-    }
-
     public Object convert(Class<?> desiredType, Object in)
     {
         for (Converter c : converters)
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/activator/Activator.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/activator/Activator.java
index f7652b0..37a26a8 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/activator/Activator.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/activator/Activator.java
@@ -19,7 +19,11 @@
 package org.apache.felix.gogo.runtime.activator;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.apache.felix.gogo.runtime.CommandProcessorImpl;
 import org.apache.felix.gogo.runtime.CommandProxy;
@@ -27,6 +31,7 @@
 import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
 import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -138,32 +143,50 @@
 
         return new ServiceTracker(context, filter, null)
         {
+            private final ConcurrentMap<ServiceReference, Map<String, CommandProxy>> proxies
+                    = new ConcurrentHashMap<ServiceReference, Map<String, CommandProxy>>();
+
             @Override
             public Object addingService(ServiceReference reference)
             {
                 Object scope = reference.getProperty(CommandProcessor.COMMAND_SCOPE);
                 Object function = reference.getProperty(CommandProcessor.COMMAND_FUNCTION);
+                Object ranking = reference.getProperty(Constants.SERVICE_RANKING);
                 List<Object> commands = new ArrayList<Object>();
 
+                int rank = 0;
+                if (ranking != null)
+                {
+                    try
+                    {
+                        rank = Integer.parseInt(ranking.toString());
+                    }
+                    catch (NumberFormatException e)
+                    {
+                        // Ignore
+                    }
+                }
                 if (scope != null && function != null)
                 {
+                    Map<String, CommandProxy> proxyMap = new HashMap<String, CommandProxy>();
                     if (function.getClass().isArray())
                     {
                         for (Object f : ((Object[]) function))
                         {
-                            Function target = new CommandProxy(context, reference,
-                                f.toString());
-                            processor.addCommand(scope.toString(), target, f.toString());
+                            CommandProxy target = new CommandProxy(context, reference, f.toString());
+                            proxyMap.put(f.toString(), target);
+                            processor.addCommand(scope.toString(), target, f.toString(), rank);
                             commands.add(target);
                         }
                     }
                     else
                     {
-                        Function target = new CommandProxy(context, reference,
-                            function.toString());
-                        processor.addCommand(scope.toString(), target, function.toString());
+                        CommandProxy target = new CommandProxy(context, reference, function.toString());
+                        proxyMap.put(function.toString(), target);
+                        processor.addCommand(scope.toString(), target, function.toString(), rank);
                         commands.add(target);
                     }
+                    proxies.put(reference, proxyMap);
                     return commands;
                 }
                 return null;
@@ -177,16 +200,10 @@
 
                 if (scope != null && function != null)
                 {
-                    if (!function.getClass().isArray())
+                    Map<String, CommandProxy> proxyMap = proxies.remove(reference);
+                    for (Map.Entry<String, CommandProxy> entry : proxyMap.entrySet())
                     {
-                        processor.removeCommand(scope.toString(), function.toString());
-                    }
-                    else
-                    {
-                        for (Object func : (Object[]) function)
-                        {
-                            processor.removeCommand(scope.toString(), func.toString());
-                        }
+                        processor.removeCommand(scope.toString(), entry.getKey(), entry.getValue());
                     }
                 }