Refactoring checkstyle daemon into a more general purpose buck daemon.

Change-Id: I999541e8894f07061141b3a62414e491369f8d08
diff --git a/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckDaemon.java b/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckDaemon.java
new file mode 100644
index 0000000..2c79b5f
--- /dev/null
+++ b/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckDaemon.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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.onosproject.buckdaemon;
+
+import com.google.common.io.ByteStreams;
+import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
+import org.onosproject.checkstyle.CheckstyleRunner;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static java.nio.file.StandardOpenOption.*;
+
+/**
+ * Buck daemon process.
+ */
+public final class BuckDaemon {
+
+    private static long POLLING_INTERVAL = 1000; //ms
+
+    private final Map<String, BuckTask> tasks = new HashMap<>();
+    private final String portLock;
+    private final String buckPid;
+
+    // Public construction forbidden
+    private BuckDaemon(String[] args) {
+        portLock = args[0];
+        buckPid = args[1];
+    }
+
+    /**
+     * Main entry point for the daemon.
+     *
+     * @param args command-line arguments
+     */
+    public static void main(String[] args)
+            throws CheckstyleException, IOException {
+        BuckDaemon daemon = new BuckDaemon(args);
+        daemon.registerTasks();
+        daemon.startServer();
+    }
+
+    /**
+     * Registers re-entrant tasks by their task name.
+     */
+    private void registerTasks() {
+        tasks.put("checkstyle", new CheckstyleRunner(System.getProperty("checkstyle.config"),
+                                                     System.getProperty("checkstyle.suppressions")));
+        // tasks.put("swagger", new SwaggerGenerator());
+    }
+
+    /**
+     * Monitors another PID and exit when that process exits.
+     */
+    private void watchProcess(String pid) {
+        if (pid == null || pid.equals("0")) {
+            return;
+        }
+        Timer timer = new Timer(true); // start as a daemon, so we don't hang shutdown
+        timer.scheduleAtFixedRate(new TimerTask() {
+            private String cmd = "kill -s 0 " + pid;
+
+            @Override
+            public void run() {
+                try {
+                    Process p = Runtime.getRuntime().exec(cmd);
+                    p.waitFor();
+                    if (p.exitValue() != 0) {
+                        System.err.println("shutting down...");
+                        System.exit(0);
+                    }
+                } catch (IOException | InterruptedException e) {
+                    //no-op
+                    e.printStackTrace();
+                }
+            }
+        }, POLLING_INTERVAL, POLLING_INTERVAL);
+    }
+
+    /**
+     * Initiates a server.
+     */
+    private void startServer() throws IOException, CheckstyleException {
+        // Use a file lock to ensure only one copy of the daemon runs
+        Path portLockPath = Paths.get(portLock);
+        FileChannel channel = FileChannel.open(portLockPath, WRITE, CREATE);
+        FileLock lock = channel.tryLock();
+        if (lock == null) {
+            System.out.println("Server is already running");
+            System.exit(1);
+        } //else, hold the lock until the JVM exits
+
+        // Start the server and bind it to a random port
+        ServerSocket server = new ServerSocket(0);
+
+        // Monitor the parent buck process
+        watchProcess(buckPid);
+
+        // Set up hook to clean up after ourselves
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            try {
+                channel.truncate(0);
+                channel.close();
+                System.err.println("tear down...");
+                Files.delete(portLockPath);
+            } catch (IOException e) {
+                //no-op: shutting down
+                e.printStackTrace();
+            }
+        }));
+
+        // Write the bound port to the port file
+        int port = server.getLocalPort();
+        channel.truncate(0);
+        channel.write(ByteBuffer.wrap(Integer.toString(port).getBytes()));
+
+        // Instantiate a Checkstyle runner and executor; serve until exit...
+        ExecutorService executor = Executors.newCachedThreadPool();
+        while (true) {
+            try {
+                executor.submit(new BuckTaskRunner(server.accept()));
+            } catch (Exception e) {
+                e.printStackTrace();
+                //no-op
+            }
+        }
+    }
+
+    /**
+     * Runnable capable of invoking the appropriate Buck task with input
+     * consumed form the specified socket and output produced back to that
+     * socket.
+     */
+    private class BuckTaskRunner implements Runnable {
+
+        private final Socket socket;
+
+        public BuckTaskRunner(Socket socket) {
+            this.socket = socket;
+        }
+
+        @Override
+        public void run() {
+            try {
+                BuckTaskContext context = new BuckTaskContext(socket.getInputStream());
+                String taskName = context.taskName();
+                if (!taskName.isEmpty()) {
+                    BuckTask task = tasks.get(taskName);
+                    if (task != null) {
+                        System.out.println(String.format("Executing task '%s'", taskName));
+                        task.execute(context);
+                        for (String line : context.output()) {
+                            output(socket, line);
+                        }
+                    } else {
+                        String message = String.format("No task named '%s'", taskName);
+                        System.out.print(message);
+                        output(socket, message);
+                    }
+                }
+                socket.getOutputStream().flush();
+                socket.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        private void output(Socket socket, String line) throws IOException {
+            socket.getOutputStream().write((line + "\n").getBytes());
+        }
+    }
+}
diff --git a/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckTask.java b/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckTask.java
new file mode 100644
index 0000000..fb6a8dc
--- /dev/null
+++ b/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckTask.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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.onosproject.buckdaemon;
+
+/**
+ * Abstraction of a Buck task that can be spawned by the Buck daemon
+ */
+public interface BuckTask {
+
+    /**
+     * Executes the task, consuming the specified input and producing output.
+     *
+     * @param context context for the tast operation
+     */
+    void execute(BuckTaskContext context);
+
+}
diff --git a/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckTaskContext.java b/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckTaskContext.java
new file mode 100644
index 0000000..dc5b753
--- /dev/null
+++ b/tools/build/conf/src/main/java/org/onosproject/buckdaemon/BuckTaskContext.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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.onosproject.buckdaemon;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Context for executing a single Buck task.
+ */
+public class BuckTaskContext {
+
+    private final String taskName;
+    private final ImmutableList<String> input;
+    private final List<String> output = new ArrayList<>();
+
+    BuckTaskContext(InputStream inputString) throws IOException {
+        String[] split = new String(ByteStreams.toByteArray(inputString)).split("\n");
+        checkArgument(split.length >= 1, "Request must contain at least task type");
+        this.taskName = split[0];
+        ImmutableList.Builder<String> builder = ImmutableList.builder();
+        for (int i = 1; i < split.length; i++) {
+            builder.add(split[i]);
+        }
+        input = builder.build();
+    }
+
+    /**
+     * Returns the symbolic task name.
+     */
+    public String taskName() {
+        return taskName;
+    }
+
+    /**
+     * Returns the input data a list of strings.
+     *
+     * @return input data
+     */
+    public List<String> input() {
+        return ImmutableList.copyOf(input);
+    }
+
+    /**
+     * Returns the output data a list of strings.
+     *
+     * @return output data
+     */
+    List<String> output() {
+        return ImmutableList.copyOf(output);
+    }
+
+    /**
+     * Adds a line to the output data.
+     *
+     * @param line line of output data
+     */
+    public void output(String line) {
+        output.add(line);
+    }
+
+}