Fix OSGi issues

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736061 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/pom.xml b/gogo/jline/pom.xml
index e1e4f75..991ca55 100644
--- a/gogo/jline/pom.xml
+++ b/gogo/jline/pom.xml
@@ -37,13 +37,13 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
-            <version>4.2.0</version>
+            <version>4.3.1</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.compendium</artifactId>
-            <version>4.0.0</version>
+            <version>4.3.1</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
index 82d0496..a731fcd 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
@@ -18,73 +18,69 @@
  */
 package org.apache.felix.gogo.jline;
 
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 
 import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.jline.SingleServiceTracker.SingleServiceListener;
+import org.apache.felix.gogo.runtime.Token;
+import org.apache.felix.gogo.runtime.Tokenizer;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Converter;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
-import org.osgi.util.tracker.ServiceTracker;
 
-public class Activator implements BundleActivator {
+public class Activator implements BundleActivator, SingleServiceListener {
+    private final Set<ServiceRegistration<?>> regs = new HashSet<>();
     private BundleContext context;
-    private ServiceTracker commandProcessorTracker;
-    private Set<ServiceRegistration> regs;
-
-    private ExecutorService executor;
+    private SingleServiceTracker<CommandProcessor> commandProcessorTracker;
+    private Thread thread;
 
     public Activator() {
-        regs = new HashSet<>();
     }
 
     public void start(BundleContext context) throws Exception {
         this.context = context;
-        this.commandProcessorTracker = createCommandProcessorTracker();
+        this.commandProcessorTracker = new SingleServiceTracker<>(context, CommandProcessor.class, this);
         this.commandProcessorTracker.open();
     }
 
     public void stop(BundleContext context) throws Exception {
-        Iterator<ServiceRegistration> iterator = regs.iterator();
+        Iterator<ServiceRegistration<?>> iterator = regs.iterator();
         while (iterator.hasNext()) {
             ServiceRegistration reg = iterator.next();
             reg.unregister();
             iterator.remove();
         }
-
-        stopShell();
-
         this.commandProcessorTracker.close();
     }
 
-    private ServiceTracker createCommandProcessorTracker() {
-        return new ServiceTracker(context, CommandProcessor.class.getName(), null) {
-            @Override
-            public Object addingService(ServiceReference reference) {
-                CommandProcessor processor = (CommandProcessor) super.addingService(reference);
-                startShell(context, processor);
-                return processor;
-            }
-
-            @Override
-            public void removedService(ServiceReference reference, Object service) {
-                stopShell();
-                super.removedService(reference, service);
-            }
-        };
+    @Override
+    public void serviceFound() {
+        startShell(context, commandProcessorTracker.getService());
     }
 
-    private void startShell(final BundleContext context, CommandProcessor processor) {
+    @Override
+    public void serviceLost() {
+        stopShell();
+    }
+
+    @Override
+    public void serviceReplaced() {
+        serviceLost();
+        serviceFound();
+    }
+
+    private void startShell(BundleContext context, CommandProcessor processor) {
         Dictionary<String, Object> dict = new Hashtable<>();
         dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
 
@@ -107,57 +103,58 @@
         regs.add(context.registerService(Shell.class.getName(), shell, dict));
 
         // start shell on a separate thread...
-        executor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Gogo shell"));
-        executor.submit(new StartShellJob(context, processor));
+        thread = new Thread(() -> doStartShell(processor, shell), "Gogo shell");
+        thread.start();
     }
 
     private void stopShell() {
-        if (executor != null && !(executor.isShutdown() || executor.isTerminated())) {
-            executor.shutdownNow();
-
+        if (thread != null && thread.isAlive()) {
+            thread.interrupt();
             try {
-                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+                thread.join(5000);
+                if (thread.isAlive()) {
                     System.err.println("!!! FAILED TO STOP EXECUTOR !!!");
                 }
             } catch (InterruptedException e) {
                 // Restore administration...
                 Thread.currentThread().interrupt();
             }
-            executor = null;
+        }
+        while (!regs.isEmpty()) {
+            ServiceRegistration<?> reg = regs.iterator().next();
+            regs.remove(reg);
+            reg.unregister();
         }
     }
 
-    private static class StartShellJob implements Runnable {
-        private final BundleContext context;
-        private final CommandProcessor processor;
-
-        public StartShellJob(BundleContext context, CommandProcessor processor) {
-            this.context = context;
-            this.processor = processor;
-        }
-
-        public void run() {
-            CommandSession session = processor.createSession(System.in, System.out, System.err);
+    private void doStartShell(CommandProcessor processor, Shell shell) {
+        String errorMessage = "gogo: unable to create console";
+        try (Terminal terminal = TerminalBuilder.terminal();
+             CommandSession session = processor.createSession(terminal.input(), terminal.output(), terminal.output())) {
+            session.put(Shell.VAR_TERMINAL, terminal);
             try {
-                // wait for gosh command to be registered
-                for (int i = 0; (i < 100) && session.get("gogo:gosh") == null; ++i) {
-                    TimeUnit.MILLISECONDS.sleep(10);
+                List<String> args = new ArrayList<>();
+                args.add("--login");
+                String argstr = shell.getContext().getProperty("gosh.args");
+                if (argstr != null) {
+                    Tokenizer tokenizer = new Tokenizer(argstr);
+                    Token token;
+                    while ((token = tokenizer.next()) != null) {
+                        args.add(token.toString());
+                    }
                 }
-
-                String args = context.getProperty("gosh.args");
-                args = (args == null) ? "" : args;
-                session.execute("gosh --login " + args);
+                shell.gosh(session, args.toArray(new String[args.size()]));
             } catch (Exception e) {
                 Object loc = session.get(".location");
                 if (null == loc || !loc.toString().contains(":")) {
                     loc = "gogo";
                 }
-
-                System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
-                e.printStackTrace();
-            } finally {
-                session.close();
+                errorMessage = loc.toString();
+                throw e;
             }
+        } catch (Exception e) {
+            System.err.println(errorMessage + e.getClass().getSimpleName() + ": " + e.getMessage());
+            e.printStackTrace();
         }
     }
 
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
index 7d67d45..439b10d 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
@@ -164,14 +164,14 @@
     private Object convertServiceReference(Object in) throws InvalidSyntaxException {
         String s = in.toString();
         if (s.startsWith("(") && s.endsWith(")")) {
-            ServiceReference refs[] = context.getServiceReferences(null, String.format(
+            ServiceReference refs[] = context.getServiceReferences((String) null, String.format(
                     "(|(service.id=%s)(service.pid=%s))", in, in));
             if (refs != null && refs.length > 0) {
                 return refs[0];
             }
         }
 
-        ServiceReference refs[] = context.getServiceReferences(null, String.format(
+        ServiceReference refs[] = context.getServiceReferences((String) null, String.format(
                 "(|(service.id=%s)(service.pid=%s))", in, in));
         if (refs != null && refs.length > 0) {
             return refs[0];
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
index cf91985..9d29ccf 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -101,6 +101,10 @@
         this.profile = profile != null ? profile : "gosh_profile";
     }
 
+    public Context getContext() {
+        return context;
+    }
+
     public static Terminal getTerminal(CommandSession session) {
         return (Terminal) session.get(VAR_TERMINAL);
     }
@@ -316,96 +320,7 @@
 
         if (args.isEmpty()) {
             if (interactive) {
-                AtomicBoolean reading = new AtomicBoolean();
-                newSession.setJobListener((job, previous, current) -> {
-                    if (previous == Status.Background || current == Status.Background
-                            || previous == Status.Suspended || current == Status.Suspended) {
-                        int w = terminal.getWidth();
-                        String status = current.name().toLowerCase();
-                        StringBuilder sb = new StringBuilder();
-                        for (int i = 0; i < w - 1; i++) {
-                            sb.append(' ');
-                        }
-                        sb.append('\r');
-                        sb.append("[").append(job.id()).append("]  ");
-                        sb.append(status);
-                        for (int i = status.length(); i < "background".length(); i++) {
-                            sb.append(' ');
-                        }
-                        sb.append("  ").append(job.command()).append("\n");
-                        terminal.writer().write(sb.toString());
-                        terminal.flush();
-                        if (reading.get()) {
-                            ((LineReaderImpl) reader).redrawLine();
-                            ((LineReaderImpl) reader).redisplay();
-                        }
-                    }
-                });
-                SignalHandler intHandler = terminal.handle(Signal.INT, s -> {
-                    Job current = newSession.foregroundJob();
-                    if (current != null) {
-                        current.interrupt();
-                    }
-                });
-                SignalHandler suspHandler = terminal.handle(Signal.TSTP, s -> {
-                    Job current = newSession.foregroundJob();
-                    if (current != null) {
-                        current.suspend();
-                    }
-                });
-                try {
-                    while (true) {
-                        try {
-                            reading.set(true);
-                            try {
-                                reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null);
-                            } finally {
-                                reading.set(false);
-                            }
-                            ParsedLine parsedLine = reader.getParsedLine();
-                            if (parsedLine == null) {
-                                throw new EndOfFileException();
-                            }
-                            try {
-                                result = session.execute(((ParsedLineImpl) parsedLine).program());
-                                session.put(Shell.VAR_RESULT, result); // set $_ to last result
-
-                                if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
-                                    System.out.println(session.format(result, Converter.INSPECT));
-                                }
-                            } catch (Exception e) {
-                                session.put(Shell.VAR_EXCEPTION, e);
-                            }
-
-                            while (true) {
-                                Job job = session.foregroundJob();
-                                if (job != null) {
-                                    //noinspection SynchronizationOnLocalVariableOrMethodParameter
-                                    synchronized (job) {
-                                        if (job.status() == Status.Foreground) {
-                                            job.wait();
-                                        }
-                                    }
-                                } else {
-                                    break;
-                                }
-                            }
-
-                        } catch (UserInterruptException e) {
-                            // continue;
-                        } catch (EndOfFileException e) {
-                            try {
-                                reader.getHistory().flush();
-                            } catch (IOException e1) {
-                                e.addSuppressed(e1);
-                            }
-                            break;
-                        }
-                    }
-                } finally {
-                    terminal.handle(Signal.INT, intHandler);
-                    terminal.handle(Signal.TSTP, suspHandler);
-                }
+                result = runShell(session, newSession, terminal, reader);
             }
         } else {
             CharSequence program;
@@ -433,7 +348,7 @@
                 program = readScript(script);
             }
 
-                result = newSession.execute(program);
+            result = newSession.execute(program);
         }
 
         if (login && interactive && !opt.isSet("noshutdown")) {
@@ -444,6 +359,110 @@
         return result;
     }
 
+    private Object runShell(final CommandSession session, CommandSession newSession, Terminal terminal,
+                            LineReader reader) throws InterruptedException {
+        AtomicBoolean reading = new AtomicBoolean();
+        newSession.setJobListener((job, previous, current) -> {
+            if (previous == Status.Background || current == Status.Background
+                    || previous == Status.Suspended || current == Status.Suspended) {
+                int width = terminal.getWidth();
+                String status = current.name().toLowerCase();
+                terminal.writer().write(getStatusLine(job, width, status));
+                terminal.flush();
+                if (reading.get()) {
+                    ((LineReaderImpl) reader).redrawLine();
+                    ((LineReaderImpl) reader).redisplay();
+                }
+            }
+        });
+        SignalHandler intHandler = terminal.handle(Signal.INT, s -> {
+            Job current = newSession.foregroundJob();
+            if (current != null) {
+                current.interrupt();
+            }
+        });
+        SignalHandler suspHandler = terminal.handle(Signal.TSTP, s -> {
+            Job current = newSession.foregroundJob();
+            if (current != null) {
+                current.suspend();
+            }
+        });
+        Object result = null;
+        try {
+            while (true) {
+                try {
+                    reading.set(true);
+                    try {
+                        reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null);
+                    } finally {
+                        reading.set(false);
+                    }
+                    ParsedLine parsedLine = reader.getParsedLine();
+                    if (parsedLine == null) {
+                        throw new EndOfFileException();
+                    }
+                    try {
+                        result = session.execute(((ParsedLineImpl) parsedLine).program());
+                        session.put(Shell.VAR_RESULT, result); // set $_ to last result
+
+                        if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
+                            System.out.println(session.format(result, Converter.INSPECT));
+                        }
+                    } catch (Exception e) {
+                        session.put(Shell.VAR_EXCEPTION, e);
+                    }
+
+                    //waitJobCompletion(session);
+
+                } catch (UserInterruptException e) {
+                    // continue;
+                } catch (EndOfFileException e) {
+                    try {
+                        reader.getHistory().flush();
+                    } catch (IOException e1) {
+                        e.addSuppressed(e1);
+                    }
+                    break;
+                }
+            }
+        } finally {
+            terminal.handle(Signal.INT, intHandler);
+            terminal.handle(Signal.TSTP, suspHandler);
+        }
+        return result;
+    }
+
+    private void waitJobCompletion(final CommandSession session) throws InterruptedException {
+        while (true) {
+            Job job = session.foregroundJob();
+            if (job != null) {
+                //noinspection SynchronizationOnLocalVariableOrMethodParameter
+                synchronized (job) {
+                    if (job.status() == Status.Foreground) {
+                        job.wait();
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+    }
+
+    private String getStatusLine(Job job, int width, String status) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < width - 1; i++) {
+            sb.append(' ');
+        }
+        sb.append('\r');
+        sb.append("[").append(job.id()).append("]  ");
+        sb.append(status);
+        for (int i = status.length(); i < "background".length(); i++) {
+            sb.append(' ');
+        }
+        sb.append("  ").append(job.command()).append("\n");
+        return sb.toString();
+    }
+
     @Descriptor("start a new shell")
     public Object sh(final CommandSession session, String[] argv) throws Exception {
         return gosh(session, argv);
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/SingleServiceTracker.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/SingleServiceTracker.java
new file mode 100644
index 0000000..826c78a
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/SingleServiceTracker.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.apache.felix.gogo.jline;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+//This is from aries util
+public final class SingleServiceTracker<T> implements ServiceListener {
+
+    public static interface SingleServiceListener {
+        public void serviceFound();
+
+        public void serviceLost();
+
+        public void serviceReplaced();
+    }
+
+    private final BundleContext ctx;
+    private final String className;
+    private final AtomicReference<T> service = new AtomicReference<T>();
+    private final AtomicReference<ServiceReference> ref = new AtomicReference<ServiceReference>();
+    private final AtomicBoolean open = new AtomicBoolean(false);
+    private final SingleServiceListener serviceListener;
+    private final String filterString;
+    private final Filter filter;
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, SingleServiceListener sl) throws InvalidSyntaxException {
+        this(context, clazz, null, sl);
+    }
+
+    public SingleServiceTracker(BundleContext context, Class<T> clazz, String filterString, SingleServiceListener sl) throws InvalidSyntaxException {
+        this(context, clazz.getName(), filterString, sl);
+    }
+
+    public SingleServiceTracker(BundleContext context, String className, String filterString, SingleServiceListener sl) throws InvalidSyntaxException {
+        this.ctx = context;
+        this.className = className;
+        this.serviceListener = sl;
+        if (filterString == null || filterString.isEmpty()) {
+            this.filterString = null;
+            this.filter = null;
+        } else {
+            this.filterString = filterString;
+            this.filter = context.createFilter(filterString);
+        }
+    }
+
+    public T getService() {
+        return service.get();
+    }
+
+    public ServiceReference getServiceReference() {
+        return ref.get();
+    }
+
+    public void open() {
+        if (open.compareAndSet(false, true)) {
+            try {
+                String filterString = '(' + Constants.OBJECTCLASS + '=' + className + ')';
+                if (filter != null) filterString = "(&" + filterString + filter + ')';
+                ctx.addServiceListener(this, filterString);
+                findMatchingReference(null);
+            } catch (InvalidSyntaxException e) {
+                // this can never happen. (famous last words :)
+            }
+        }
+    }
+
+    public void serviceChanged(ServiceEvent event) {
+        if (open.get()) {
+            if (event.getType() == ServiceEvent.UNREGISTERING) {
+                ServiceReference deadRef = event.getServiceReference();
+                if (deadRef.equals(ref.get())) {
+                    findMatchingReference(deadRef);
+                }
+            } else if (event.getType() == ServiceEvent.REGISTERED && ref.get() == null) {
+                findMatchingReference(null);
+            }
+        }
+    }
+
+    private void findMatchingReference(ServiceReference original) {
+        try {
+            boolean clear = true;
+            ServiceReference[] refs = ctx.getServiceReferences(className, filterString);
+            if (refs != null && refs.length > 0) {
+                if (refs.length > 1) {
+                    Arrays.sort(refs);
+                }
+                @SuppressWarnings("unchecked")
+                T service = (T) ctx.getService(refs[0]);
+                if (service != null) {
+                    clear = false;
+
+                    // We do the unget out of the lock so we don't exit this class while holding a lock.
+                    if (!!!update(original, refs[0], service)) {
+                        ctx.ungetService(refs[0]);
+                    }
+                }
+            } else if (original == null) {
+                clear = false;
+            }
+
+            if (clear) {
+                update(original, null, null);
+            }
+        } catch (InvalidSyntaxException e) {
+            // this can never happen. (famous last words :)
+        }
+    }
+
+    private boolean update(ServiceReference deadRef, ServiceReference newRef, T service) {
+        boolean result = false;
+        int foundLostReplaced = -1;
+
+        // Make sure we don't try to get a lock on null
+        Object lock;
+
+        // we have to choose our lock.
+        if (newRef != null) lock = newRef;
+        else if (deadRef != null) lock = deadRef;
+        else lock = this;
+
+        // This lock is here to ensure that no two threads can set the ref and service
+        // at the same time.
+        synchronized (lock) {
+            if (open.get()) {
+                result = this.ref.compareAndSet(deadRef, newRef);
+                if (result) {
+                    this.service.set(service);
+
+                    if (deadRef == null && newRef != null) foundLostReplaced = 0;
+                    if (deadRef != null && newRef == null) foundLostReplaced = 1;
+                    if (deadRef != null && newRef != null) foundLostReplaced = 2;
+                }
+            }
+        }
+
+        if (serviceListener != null) {
+            if (foundLostReplaced == 0) serviceListener.serviceFound();
+            else if (foundLostReplaced == 1) serviceListener.serviceLost();
+            else if (foundLostReplaced == 2) serviceListener.serviceReplaced();
+        }
+
+        return result;
+    }
+
+    public void close() {
+        if (open.compareAndSet(true, false)) {
+            ctx.removeServiceListener(this);
+
+            ServiceReference deadRef;
+            synchronized (this) {
+                deadRef = ref.getAndSet(null);
+                service.set(null);
+            }
+            if (deadRef != null) {
+                serviceListener.serviceLost();
+                ctx.ungetService(deadRef);
+            }
+        }
+    }
+}
\ No newline at end of file