summaryrefslogtreecommitdiffstats
path: root/jack-server/src/com
diff options
context:
space:
mode:
authorJean-Marie Henaff <jmhenaff@google.com>2015-03-27 18:41:36 +0100
committerJean-Marie Henaff <jmhenaff@google.com>2015-03-30 15:36:18 +0200
commit00d8ad6749739964c35f37740fc87bb05afc47ae (patch)
tree8cbbed83972f90ed17ed10df096dd38d01759914 /jack-server/src/com
parent091516b153ad020a1b92afa33ae417598e78c3e6 (diff)
downloadtoolchain_jack-00d8ad6749739964c35f37740fc87bb05afc47ae.zip
toolchain_jack-00d8ad6749739964c35f37740fc87bb05afc47ae.tar.gz
toolchain_jack-00d8ad6749739964c35f37740fc87bb05afc47ae.tar.bz2
Split Jack server as a separate project
Change-Id: I1b593fda9ba89298d8729684778f8992689d8b32
Diffstat (limited to 'jack-server/src/com')
-rw-r--r--jack-server/src/com/android/jack/server/JackSimpleServer.java704
-rw-r--r--jack-server/src/com/android/jack/server/Server.java438
-rw-r--r--jack-server/src/com/android/jack/server/ServerExitStatus.java30
-rw-r--r--jack-server/src/com/android/jack/server/ServerTask.java32
-rw-r--r--jack-server/src/com/android/jack/server/ServerTaskInsideVm.java95
-rw-r--r--jack-server/src/com/android/jack/server/ServerTaskSpawningVM.java152
6 files changed, 1451 insertions, 0 deletions
diff --git a/jack-server/src/com/android/jack/server/JackSimpleServer.java b/jack-server/src/com/android/jack/server/JackSimpleServer.java
new file mode 100644
index 0000000..4f27b4a
--- /dev/null
+++ b/jack-server/src/com/android/jack/server/JackSimpleServer.java
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.google.common.base.Joiner;
+
+import com.android.sched.util.config.cli.TokenIterator;
+import com.android.sched.util.file.OutputStreamFile;
+import com.android.sched.util.findbugs.SuppressFBWarnings;
+import com.android.sched.util.location.NoLocation;
+import com.android.sched.util.log.tracer.probe.MemoryBytesProbe;
+import com.android.sched.util.log.tracer.probe.TimeNanosProbe;
+
+import org.simpleframework.http.Path;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.Status;
+import org.simpleframework.http.core.Container;
+import org.simpleframework.http.core.ContainerSocketProcessor;
+import org.simpleframework.http.parse.PathParser;
+import org.simpleframework.transport.connect.Connection;
+import org.simpleframework.transport.connect.SocketConnection;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.management.CompilationMXBean;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryPoolMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.management.RuntimeMXBean;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Server controlling the number of Jack compilations that are executed simultaneously.
+ */
+public class JackSimpleServer {
+ private static int port;
+
+ @Nonnull
+ private static final ServerTask serviceTest = new ServerTask() {
+ @Nonnull
+ private final Random rnd = new Random();
+
+ @Override
+ public int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir,
+ @Nonnull TokenIterator args) {
+ String cmd = null;
+ try {
+ args.hasNext();
+ cmd = args.next();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ out.println("Pre-test stdout for '" + workingDir.getPath() + "' from " + port);
+ err.println("Pre-test stderr for '" + cmd + "' from " + port);
+
+ try {
+ Thread.sleep(rnd.nextInt(3000));
+ } catch (InterruptedException e) {
+ // Doesn't matter
+ }
+
+ out.println("Post-test stdout for '" + workingDir.getPath() + "' from " + port);
+ err.println("Post-test stderr for '" + cmd + "' from " + port);
+
+ return rnd.nextInt(30);
+ }
+ };
+
+ @Nonnull
+ private static ServerTask service = new ServerTaskInsideVm();
+
+ @Nonnull
+ private static Logger logger = Logger.getLogger(JackSimpleServer.class.getSimpleName());
+
+ private static final int CMD_IDX_CMD = 0;
+ private static final int CMD_IDX_OUT = 1;
+ private static final int CMD_IDX_ERR = 2;
+ private static final int CMD_IDX_CLI = 3;
+ private static final int CMD_IDX_END = 4;
+
+ private static final int CLI_IDX_PORT = 0;
+ private static final int CLI_IDX_COUNT = 1;
+ private static final int CLI_IDX_MAX = 2;
+ private static final int CLI_IDX_TIEMOUT = 3;
+ private static final int CLI_IDX_END = 4;
+
+ @CheckForNull
+ private static Connection connection;
+ @CheckForNull
+ private static Timer timer;
+ @Nonnull
+ private static Lock lock = new ReentrantLock();
+
+ private static int timeout;
+
+ private static int currentLocal = 0;
+ private static long totalLocal = 0;
+ private static int maxLocal = 0;
+
+ private static int currentForward = 0;
+ private static long totalForward = 0;
+ private static int maxForward = 0;
+
+ public static void main(String[] args) {
+ if (args.length != CLI_IDX_END) {
+ logger.log(Level.SEVERE,
+ "Usage: <port-nb> <server-count> <max-compile> <timeout-s>");
+ abort();
+ }
+
+ port = 0;
+ try {
+ port = Integer.parseInt(args[CLI_IDX_PORT]);
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse port number '" + args[CLI_IDX_PORT] + "'");
+ abort();
+ }
+
+ logger = Logger.getLogger(JackSimpleServer.class.getSimpleName() + "." + port);
+
+ int count = 0;
+ try {
+ count = Integer.parseInt(args[CLI_IDX_COUNT]);
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse server count '" + args[CLI_IDX_COUNT] + "'");
+ abort();
+ }
+
+ int nbInstance = 0;
+ try {
+ nbInstance = Integer.parseInt(args[CLI_IDX_MAX]);
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse instance count '" + args[CLI_IDX_MAX] + "'");
+ abort();
+ }
+
+ try {
+ timeout = Integer.parseInt(args[CLI_IDX_TIEMOUT]) * 1000;
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse timeout '" + args[CLI_IDX_TIEMOUT] + "'");
+ abort();
+ }
+
+ InetSocketAddress socket = new InetSocketAddress("127.0.0.1", port);
+
+ logger.log(Level.INFO, "Starting simple server on " + socket);
+ try {
+ JackRouter router = new JackRouter();
+ router.addContainer(new PathParser("/jack"), new JackRun());
+ router.addContainer(new PathParser("/gc"), new JackGc());
+ router.addContainer(new PathParser("/stat"), new JackStat());
+
+ ContainerSocketProcessor processor =
+ new ContainerSocketProcessor(router, nbInstance);
+ connection = new SocketConnection(processor);
+ assert connection != null;
+ connection.connect(socket);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Problem during connection ", e);
+ abort();
+ }
+ }
+
+ private static class JackRouter implements Container {
+ @Nonnull
+ private final Map<String, Container> registry = new HashMap<String, Container>();
+ @Nonnull
+ private final Container primary;
+
+ public JackRouter() {
+ primary = new Container() {
+ @Override
+ public void handle(@Nonnull Request request, @Nonnull Response response) {
+ logger.log(Level.INFO, "Unknown request for '" + request.getPath().getPath() + "'");
+ response.setStatus(Status.NOT_FOUND);
+ }
+ };
+ }
+
+ public JackRouter(@Nonnull Container primary) {
+ this.primary = primary;
+ }
+
+ public void addContainer(@Nonnull Path path, @Nonnull Container container) {
+ registry.put(path.getPath(), container);
+ }
+
+ @Override
+ public void handle(@Nonnull Request request, @Nonnull Response response) {
+ String normalizedPath = request.getPath().getPath();
+
+ logger.log(Level.INFO, "Route request from '" + normalizedPath + "'");
+
+ Container container = registry.get(normalizedPath);
+ if (container != null) {
+ container.handle(request, response);
+ } else {
+ primary.handle(request, response);
+ }
+ }
+ }
+
+ private static class JackRun implements Container {
+ @Override
+ public void handle(@Nonnull Request request, @Nonnull Response response) {
+ try {
+ String line;
+
+ try {
+ line = request.getContent();
+ } catch (IOException e1) {
+ logger.log(Level.SEVERE, "Command read command");
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ if (line == null) {
+ logger.log(Level.SEVERE, "Command error: nothing to read");
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ String[] command = line.split(" ");
+
+ if (!command[CMD_IDX_CMD].equals("+")) {
+ logger.log(Level.SEVERE, "Command error '" + line + "'");
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ logger.log(Level.INFO, "Read command '" + line + "'");
+
+ PrintStream out = null;
+ PrintStream err = null;
+
+ long id;
+
+ lock.lock();
+ try {
+ id = totalLocal;
+ totalLocal++;
+ if (currentLocal == 0) {
+ cancelTimer();
+ }
+ currentLocal++;
+ logger.log(Level.INFO, "Number of concurrent compilations: " + currentLocal);
+ if (currentLocal > maxLocal) {
+ maxLocal = currentLocal;
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ try {
+ if (command.length != CMD_IDX_END) {
+ logger.log(Level.SEVERE, "Command format error '" + line + "'");
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ logger.log(Level.INFO, "Open standard output '" + command[CMD_IDX_OUT] + "'");
+ try {
+ out = new OutputStreamFile(command[CMD_IDX_OUT]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ logger.log(Level.INFO, "Open standard error '" + command[CMD_IDX_ERR] + "'");
+ try {
+ err = new OutputStreamFile(command[CMD_IDX_ERR]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ logger.log(Level.INFO, "Parse command line");
+ TokenIterator args = new TokenIterator(new NoLocation(), "@" + command[CMD_IDX_CLI]);
+ args.allowFileReferenceInFile();
+ if (!args.hasNext()) {
+ logger.log(Level.SEVERE, "Cli format error");
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ String workingDir;
+ try {
+ workingDir = args.next();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Cli format error");
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+
+ int code = -1;
+ long start = System.currentTimeMillis();
+ try {
+ logger.log(Level.INFO, "Run Compilation #" + id);
+ start = System.currentTimeMillis();
+ code = service.run(out, err, new File(workingDir), args);
+ } finally {
+ long stop = System.currentTimeMillis();
+ logger.log(Level.INFO, "Compilation #" + id + " return exit code " + code);
+ logger.log(Level.INFO, "Compilation #" + id + " run in " + (stop - start) + " ms");
+
+ response.setStatus(Status.OK);
+
+ PrintStream printer;
+ try {
+ printer = response.getPrintStream();
+ printer.println(code);
+ printer.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Problem to send exit code for compilation #" + id);
+ response.setStatus(Status.BAD_REQUEST);
+ return;
+ }
+ }
+ } finally {
+ if (out != null) {
+ out.close();
+ } else {
+ unblock(command[CMD_IDX_OUT]);
+ }
+
+ if (err != null) {
+ err.close();
+ } else {
+ unblock(command[CMD_IDX_ERR]);
+ }
+
+ lock.lock();
+ try {
+ currentLocal--;
+ if (currentLocal == 0) {
+ startTimer();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ } finally {
+ try {
+ response.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Exception during close: ", e);
+ }
+ }
+
+ return;
+ }
+ }
+
+ private static class JackStat implements Container {
+ @Override
+ public void handle(@Nonnull Request request, @Nonnull Response response) {
+ try {
+ response.setStatus(Status.OK);
+ PrintStream printer = response.getPrintStream();
+
+ long time = System.currentTimeMillis();
+ Date date = new Date(time);
+ printer.println("date: " + time + " [" + date + "]");
+
+ try {
+ lock.lock();
+ try {
+ printer.println("server.compilation: " + totalLocal);
+ printer.println("server.compilation.max: " + maxLocal);
+ printer.println("server.compilation.current: " + currentLocal);
+ printer.println("server.forward: " + totalForward);
+ printer.println("server.forward.max: " + maxForward);
+ printer.println("server.forward.current: " + currentForward);
+ } finally {
+ lock.unlock();
+ }
+
+ OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
+ printer.println("os.arch: " + os.getArch());
+ printer.println("os.proc.nb: " + Integer.valueOf(os.getAvailableProcessors()));
+ printer.println("os.name: " + os.getName());
+ printer.println("os.version: " + os.getVersion());
+
+ RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
+ printer.println("vm.name: " + runtime.getVmName());
+ printer.println("vm.vendor: " + runtime.getVmVendor());
+ printer.println("vm.version: " + runtime.getVmVersion());
+ printer.println("vm_options: "
+ + Joiner.on(' ').skipNulls().join(runtime.getInputArguments()));
+ printer.println("vm.memory.max: " + formatQuatity(Runtime.getRuntime().maxMemory()));
+ printer.println("vm.memory.free: " + formatQuatity(Runtime.getRuntime().freeMemory()));
+ printer.println("vm.memory.total: " + formatQuatity(Runtime.getRuntime().totalMemory()));
+
+ try {
+ CompilationMXBean compilation = ManagementFactory.getCompilationMXBean();
+ printer.println("vm.jit.time: "
+ + formatDuration(compilation.getTotalCompilationTime(), TimeUnit.MILLISECONDS));
+ } catch (UnsupportedOperationException e) {
+ // Do the best
+ }
+
+ for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+ String suffix = "vm.collector." + tranformString(gc.getName()) + ".";
+ printer.println(suffix + "time: "
+ + formatDuration(gc.getCollectionTime(), TimeUnit.MILLISECONDS));
+ printer.println(suffix + "count: " + gc.getCollectionCount());
+ }
+
+ for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
+ String suffix = "vm.pool." + tranformString(pool.getName()) + ".";
+ printer.println(suffix + "type: " + pool.getType().name());
+
+ printMemoryUsage(printer, suffix + "collection.", pool.getCollectionUsage());
+ try {
+ printer.println(suffix + "collection.threshold: "
+ + formatQuatity(pool.getCollectionUsageThreshold()));
+ } catch (UnsupportedOperationException e) {
+ // Best effort
+ }
+
+ try {
+ printer.println(suffix + "collection.threshold.count: "
+ + pool.getCollectionUsageThresholdCount());
+ } catch (UnsupportedOperationException e) {
+ // Best effort
+ }
+
+ printMemoryUsage(printer, suffix + "peak.", pool.getPeakUsage());
+
+ printMemoryUsage(printer, suffix + "usage.", pool.getUsage());
+ try {
+ printer.println(suffix + "usage.threshold: "
+ + formatQuatity(pool.getUsageThreshold()));
+ } catch (UnsupportedOperationException e) {
+ // Best effort
+ }
+ try {
+ printer.println(suffix + "usage.threshold.count: " + pool.getUsageThresholdCount());
+ } catch (UnsupportedOperationException e) {
+ // Best effort
+ }
+ }
+
+ try {
+ printer.println("host.name: " + InetAddress.getLocalHost().getHostName());
+ } catch (UnknownHostException e1) {
+ // Best effort
+ }
+
+ Method method;
+ try {
+ method = os.getClass().getMethod("getCommittedVirtualMemorySize");
+ method.setAccessible(true);
+ printer.println("os.memory.virtual.committed: "
+ + formatQuatity(((Long) method.invoke(os)).longValue()));
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getTotalPhysicalMemorySize");
+ method.setAccessible(true);
+ printer.println("os.memory.physical.total: "
+ + formatQuatity(((Long) method.invoke(os)).longValue()));
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getFreePhysicalMemorySize");
+ method.setAccessible(true);
+ printer.println("os.memory.physical.free: "
+ + formatQuatity(((Long) method.invoke(os)).longValue()));
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getTotalSwapSpaceSize");
+ method.setAccessible(true);
+ printer.println("os.memory.swap.total: "
+ + formatQuatity(((Long) method.invoke(os)).longValue()));
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getFreeSwapSpaceSize");
+ method.setAccessible(true);
+ printer.println("os.memory.swap.free: "
+ + formatQuatity(((Long) method.invoke(os)).longValue()));
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getOpenFileDescriptorCount");
+ method.setAccessible(true);
+ printer.println("os.fd.open: " + ((Long) method.invoke(os)).longValue());
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+
+ try {
+ method = os.getClass().getMethod("getProcessCpuLoad");
+ method.setAccessible(true);
+ printer.println("os.process.cpu.load: " + ((Double) method.invoke(os)).doubleValue());
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getProcessCpuTime");
+ method.setAccessible(true);
+ printer.println("os.process.cpu.time: "
+ + formatDuration(((Long) method.invoke(os)).longValue(), TimeUnit.NANOSECONDS));
+ } catch (Throwable t) {
+ // Best effort
+ }
+
+ try {
+ method = os.getClass().getMethod("getSystemCpuLoad");
+ method.setAccessible(true);
+ printer.println("os.system.cpu.load: " + ((Double) method.invoke(os)).doubleValue());
+ } catch (Throwable t) {
+ // Best effort
+ }
+ } catch (Throwable e) {
+ logger.log(Level.SEVERE, "Unexpected exception: ", e);
+ }
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Exception during IO: ", e);
+ } finally {
+ try {
+ response.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Exception during close: ", e);
+ }
+ }
+ }
+ }
+
+ static void printMemoryUsage(@Nonnull PrintStream printer, @Nonnull String suffix,
+ @CheckForNull MemoryUsage usage) {
+ if (usage != null) {
+ printer.println(suffix + "commited: " + formatQuatity(usage.getCommitted()));
+ printer.println(suffix + "itnit: " + formatQuatity(usage.getInit()));
+ printer.println(suffix + "max: " + formatQuatity(usage.getMax()));
+ printer.println(suffix + "used: " + formatQuatity(usage.getUsed()));
+ }
+ }
+
+ @Nonnull
+ static String formatDuration(@Nonnull long duration, @Nonnull TimeUnit unit) {
+ String str = Long.toString(duration);
+
+ str += " [";
+ str += TimeNanosProbe.formatDuration(unit.toNanos(duration));
+ str += "]";
+
+ return str;
+ }
+
+ @Nonnull
+ static String formatQuatity(@Nonnull long quantity) {
+ String str = Long.toString(quantity);
+
+ str += " [";
+ str += MemoryBytesProbe.formatBytes(quantity);
+ str += "]";
+
+ return str;
+ }
+
+ @Nonnull
+ static String tranformString(@Nonnull String string) {
+ return string.toLowerCase().replace(' ', '-');
+ }
+
+ @SuppressFBWarnings("DM_GC")
+ private static class JackGc implements Container {
+ @Override
+ public void handle(@Nonnull Request request, @Nonnull Response response) {
+ logger.log(Level.INFO, "Force GC");
+ System.gc();
+ response.setStatus(Status.OK);
+ try {
+ response.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Exception during close: ", e);
+ }
+ }
+ }
+
+ private static void abort() {
+ logger.log(Level.SEVERE, "Abort sever");
+ System.exit(1);
+ }
+
+ private static void unblock(@Nonnull String name) {
+ logger.log(Level.INFO, "Trying to unblock '" + name + "'");
+ PrintStream out = null;
+ try {
+ out = new OutputStreamFile(name).getPrintStream();
+ } catch (IOException e) {
+ // Best effort
+ }
+
+ if (out != null) {
+ out.close();
+ }
+ }
+
+ private static void startTimer() {
+ lock.lock();
+ try {
+ if (timer != null) {
+ cancelTimer();
+ }
+
+ logger.log(Level.INFO, "Start timer");
+
+ timer = new Timer("jack-server-timeout");
+ assert timer != null;
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ cancelTimer();
+
+ Connection conn = connection;
+ assert conn != null;
+
+ logger.log(Level.INFO, "Shutdowning server");
+ logger.log(Level.INFO, "# max of concurrent compilations: " + maxLocal);
+ logger.log(Level.INFO, "# total of compilations: " + totalLocal);
+ logger.log(Level.INFO, "# max of concurrent forward compilations: " + maxForward);
+ logger.log(Level.INFO, "# total of forward compilations: " + totalForward);
+ try {
+ conn.close();
+ logger.log(Level.INFO, "Done");
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Cannot shutdown the server: ", e);
+ }
+ }
+ }, timeout);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private static void cancelTimer() {
+ lock.lock();
+ try {
+ if (timer != null) {
+ logger.log(Level.INFO, "Cancel timer");
+
+ timer.cancel();
+ timer.purge();
+ timer = null;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/jack-server/src/com/android/jack/server/Server.java b/jack-server/src/com/android/jack/server/Server.java
new file mode 100644
index 0000000..8ab3153
--- /dev/null
+++ b/jack-server/src/com/android/jack/server/Server.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.sched.util.config.cli.TokenIterator;
+import com.android.sched.util.file.AbstractStreamFile;
+import com.android.sched.util.file.OutputStreamFile;
+import com.android.sched.util.location.FileLocation;
+import com.android.sched.util.location.NoLocation;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Server controlling the number of Jack compilations that are executed simultaneously.
+ */
+public class Server {
+ @Nonnull
+ private static ServerTask serviceTest = new ServerTask() {
+ @Nonnull
+ private final Random rnd = new Random();
+
+ @Override
+ public int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir,
+ @Nonnull TokenIterator args) {
+ String cmd = null;
+ try {
+ args.hasNext();
+ cmd = args.next();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ out.println("Pre-test stdout for '" + workingDir.getPath() + "'");
+ err.println("Pre-test stderr for '" + cmd + "'");
+
+ try {
+ Thread.sleep(rnd.nextInt(3000));
+ } catch (InterruptedException e) {
+ // Doesn't matter
+ }
+
+ out.println("Post-test stdout for '" + workingDir.getPath() + "'");
+ err.println("Post-test stderr for '" + cmd + "'");
+
+ return rnd.nextInt(30);
+ }
+ };
+
+ @Nonnull
+ private static ServerTask service = new ServerTaskInsideVm();
+
+ @Nonnull
+ private static Logger logger = Logger.getLogger(Server.class.getSimpleName());
+
+ private static final int CMD_IDX_CMD = 0;
+ private static final int CMD_IDX_OUT = 1;
+ private static final int CMD_IDX_ERR = 2;
+ private static final int CMD_IDX_EXIT = 3;
+ private static final int CMD_IDX_CLI = 4;
+ private static final int CMD_IDX_END = 5;
+
+ private static final int CLI_IDX_MAX = 0;
+ private static final int CLI_IDX_TIEMOUT = 1;
+ private static final int CLI_IDX_FIFO = 2;
+ private static final int CLI_IDX_LOCK = 3;
+ private static final int CLI_IDX_END = 4;
+
+ @CheckForNull
+ private static File fifo;
+ @CheckForNull
+ private static File lock;
+ @CheckForNull
+ private static BufferedReader in;
+
+ private static int timeout;
+
+ @Nonnull
+ private static AtomicInteger nbMax = new AtomicInteger(0);
+ @Nonnull
+ private static AtomicLong nbCurrent = new AtomicLong(0);
+
+ public static void main(String[] args) throws InterruptedException {
+ if (args.length != CLI_IDX_END) {
+ logger.log(Level.SEVERE, "Usage: <max-compile> <timeout-s> <path-fifo> <path-lock>");
+ abort();
+ }
+
+ int nbInstance = 0;
+ try {
+ nbInstance = Integer.parseInt(args[CLI_IDX_MAX]);
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse instance count '" + args[CLI_IDX_MAX] + "'");
+ abort();
+ }
+
+ try {
+ timeout = Integer.parseInt(args[CLI_IDX_TIEMOUT]) * 1000;
+ } catch (NumberFormatException e) {
+ logger.log(Level.SEVERE, "Cannot parse timeout '" + args[CLI_IDX_TIEMOUT] + "'");
+ abort();
+ }
+
+ fifo = new File(args[CLI_IDX_FIFO]);
+ lock = new File(args[CLI_IDX_LOCK]);
+
+
+ try {
+ AbstractStreamFile.check(fifo, new FileLocation(fifo));
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ abort();
+ }
+
+ Runtime.getRuntime().addShutdownHook(new Thread(){
+ @Override
+ public void run() {
+ cancelTimer();
+ shutdownFifo();
+
+ if (!lock.delete()) {
+ logger.log(Level.SEVERE, "Can not remove lock file '" + lock.getPath() + "'");
+ }
+ }
+ });
+
+ startFifo();
+ startTimer();
+ try {
+ in = new BufferedReader(new FileReader(fifo));
+ } catch (FileNotFoundException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ abort();
+ }
+
+ assert fifo != null;
+ logger.log(Level.INFO, "Starting server on '" + fifo.getPath() + "'");
+ ExecutorService executor = Executors.newFixedThreadPool(nbInstance);
+ for (int i = 0; i < nbInstance; i++) {
+ logger.log(Level.FINE, "Launching task #" + i);
+ executor.execute(new Task());
+ }
+
+ executor.shutdown();
+ executor.awaitTermination(1, TimeUnit.HOURS);
+
+ logger.log(Level.INFO, "Shutdown server");
+ logger.log(Level.INFO, "# service runs " + nbCurrent.get());
+ }
+
+ /**
+ * {@link Runnable} task launched by the server that will be in charge to call {@link ServerTask}
+ * implementation that will launch a Jack compilation either in the same VM, either in a spawn VM.
+ */
+ public static class Task implements Runnable {
+ @Override
+ public void run() {
+ while (true) {
+ String line;
+
+ try {
+ line = getLine();
+ logger.log(Level.FINE, "Read command '" + line + "'");
+ } catch (IOException e) {
+ logger.log(Level.FINE, "Shutdown task");
+ return;
+ }
+
+ String[] command = line.split(" ");
+
+ if (command[CMD_IDX_CMD].equals("=")) {
+ continue;
+ }
+
+ if (command[CMD_IDX_CMD].equals("-")) {
+ cancelTimer();
+ shutdownFifo();
+ continue;
+ }
+
+ if (!command[CMD_IDX_CMD].equals("+")) {
+ logger.log(Level.SEVERE, "Command error '" + line + "'");
+ continue;
+ }
+
+ PrintStream out = null;
+ PrintStream err = null;
+ PrintStream exit = null;
+
+ try {
+ if (command.length != CMD_IDX_END) {
+ logger.log(Level.SEVERE, "Command format error '" + line + "'");
+ continue;
+ }
+
+ logger.log(Level.FINE, "Open standard output '" + command[CMD_IDX_OUT] + "'");
+ try {
+ out = new OutputStreamFile(command[CMD_IDX_OUT]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ continue;
+ }
+
+ logger.log(Level.FINE, "Open standard error '" + command[CMD_IDX_ERR] + "'");
+ try {
+ err = new OutputStreamFile(command[CMD_IDX_ERR]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ continue;
+ }
+
+ logger.log(Level.FINE, "Open exit fifo '" + command[CMD_IDX_EXIT] + "'");
+ try {
+ exit = new OutputStreamFile(command[CMD_IDX_EXIT]).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ continue;
+ }
+
+ logger.log(Level.FINE, "Parse command line");
+ TokenIterator args = new TokenIterator(new NoLocation(), "@" + command[CMD_IDX_CLI]);
+ args.allowFileReferenceInFile();
+ if (!args.hasNext()) {
+ logger.log(Level.SEVERE, "Cli format error");
+ continue;
+ }
+
+ String workingDir;
+ try {
+ workingDir = args.next();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Cli format error");
+ continue;
+ }
+
+ if (nbMax.getAndIncrement() == 0) {
+ cancelTimer();
+ }
+
+ int code = -1;
+ try {
+ logger.log(Level.FINE, "Run service");
+ nbCurrent.incrementAndGet();
+ code = service.run(out, err, new File(workingDir), args);
+ } finally {
+ if (nbMax.decrementAndGet() == 0) {
+ startTimer();
+ }
+
+ logger.log(Level.FINE, "Write exit code '" + code + "'");
+ assert exit != null;
+ exit.println(code);
+ }
+ } finally {
+ if (out != null) {
+ out.close();
+ } else {
+ unblock(command[CMD_IDX_OUT]);
+ }
+
+ if (err != null) {
+ err.close();
+ } else {
+ unblock(command[CMD_IDX_ERR]);
+ }
+
+ if (exit != null) {
+ exit.close();
+ } else {
+ unblock(command[CMD_IDX_EXIT]);
+ }
+ }
+ }
+ }
+ }
+
+ @Nonnull
+ private static Object lockRead = new Object();
+
+ private static volatile boolean stop = false;
+
+ @Nonnull
+ public static String getLine() throws IOException {
+ synchronized (lockRead) {
+ if (in == null) {
+ throw new IOException();
+ }
+
+ assert in != null;
+ String str = in.readLine();
+ while (str == null) {
+ try {
+ in.close();
+ } catch (IOException e1) {
+ // Best effort
+ }
+ in = null;
+
+ if (stop) {
+ throw new IOException();
+ }
+
+ in = new BufferedReader(new FileReader(fifo));
+ assert in != null;
+ str = in.readLine();
+ }
+
+ return str;
+ }
+ }
+
+ private static void startFifo() {
+ logger.log(Level.FINE, "Start FIFO");
+ }
+
+ private static void shutdownFifo() {
+ logger.log(Level.FINE, "Shutdown FIFO");
+
+ stop = true;
+
+ Unblocker unblocker = new Unblocker();
+ unblocker.setName("Unblocker");
+ unblocker.setDaemon(true);
+ unblocker.start();
+ }
+
+ private static class Unblocker extends Thread {
+ @Override
+ public void run() {
+ PrintStream out = null;
+
+ while (true) {
+ try {
+ out = new PrintStream(fifo);
+ } catch (FileNotFoundException e) {
+ // Best effort
+ }
+
+ if (out != null) {
+ out.close();
+ }
+
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ // Best effort here
+ }
+ }
+ }
+ }
+
+ private static void abort() {
+ System.exit(1);
+ }
+
+ @CheckForNull
+ private static Timer timer;
+
+ @Nonnull
+ private static Object lockTimer = new Object();
+
+ private static void startTimer() {
+ synchronized (lockTimer) {
+ if (timer != null) {
+ cancelTimer();
+ }
+
+ logger.log(Level.FINE, "Start timer");
+
+ timer = new Timer("jack-server-timeout");
+ assert timer != null;
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ shutdownFifo();
+ cancelTimer();
+ }
+ }, timeout);
+ }
+ }
+
+ private static void cancelTimer() {
+ synchronized (lockTimer) {
+ if (timer != null) {
+ logger.log(Level.FINE, "Cancel timer");
+
+ timer.cancel();
+ timer.purge();
+ timer = null;
+ }
+ }
+ }
+
+ private static void unblock(@Nonnull String name) {
+ logger.log(Level.FINE, "Trying to unblock '" + name + "'");
+ PrintStream out = null;
+ try {
+ out = new OutputStreamFile(name).getPrintStream();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getMessage(), e);
+ }
+
+ if (out != null) {
+ out.close();
+ }
+ }
+}
diff --git a/jack-server/src/com/android/jack/server/ServerExitStatus.java b/jack-server/src/com/android/jack/server/ServerExitStatus.java
new file mode 100644
index 0000000..6f37ab2
--- /dev/null
+++ b/jack-server/src/com/android/jack/server/ServerExitStatus.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.jack.ExitStatus;
+
+/**
+ * Server exit status.
+ */
+public class ServerExitStatus extends ExitStatus {
+ /**
+ * Jack jar not found.
+ */
+ public static final int FAILURE_JACK_JAR_NOT_FOUND = -1;
+
+}
diff --git a/jack-server/src/com/android/jack/server/ServerTask.java b/jack-server/src/com/android/jack/server/ServerTask.java
new file mode 100644
index 0000000..b89f76c
--- /dev/null
+++ b/jack-server/src/com/android/jack/server/ServerTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.sched.util.config.cli.TokenIterator;
+
+import java.io.File;
+import java.io.PrintStream;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Interface of tasks that will be launched by the Jack server.
+ */
+public interface ServerTask {
+ int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir,
+ @Nonnull TokenIterator args);
+} \ No newline at end of file
diff --git a/jack-server/src/com/android/jack/server/ServerTaskInsideVm.java b/jack-server/src/com/android/jack/server/ServerTaskInsideVm.java
new file mode 100644
index 0000000..7331c9e
--- /dev/null
+++ b/jack-server/src/com/android/jack/server/ServerTaskInsideVm.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.jack.CommandLine;
+import com.android.jack.Main;
+import com.android.jack.Options;
+import com.android.sched.util.config.cli.TokenIterator;
+import com.android.sched.util.file.CannotReadException;
+import com.android.sched.util.file.NoSuchFileException;
+import com.android.sched.util.file.NotDirectoryException;
+import com.android.sched.util.file.NotFileOrDirectoryException;
+import com.android.sched.util.file.WrongPermissionException;
+
+import org.kohsuke.args4j.CmdLineException;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.annotation.Nonnull;
+
+/**
+ * {@link ServerTask} implementation that launches compilations with Jack into the running VM.
+ */
+public class ServerTaskInsideVm extends CommandLine implements ServerTask {
+
+ @Override
+ public int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File pwd,
+ @Nonnull TokenIterator args) {
+ List<String> list = new ArrayList<String>();
+ Options options;
+
+ try {
+ args.withFileRelativeTo(pwd);
+ } catch (NotDirectoryException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (WrongPermissionException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (NoSuchFileException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ }
+
+ try {
+ while (args.hasNext()) {
+ list.add(args.next());
+ }
+ options = Main.parseCommandLine(list);
+ options.setWorkingDirectory(pwd);
+ options.setStandardError(err);
+ options.setStandardOutput(out);
+ } catch (CmdLineException e) {
+ if (e.getMessage() != null) {
+ err.println(e.getMessage());
+ }
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (NoSuchElementException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (WrongPermissionException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (NoSuchFileException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (NotFileOrDirectoryException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ } catch (CannotReadException e) {
+ err.println(e.getMessage());
+ return ServerExitStatus.FAILURE_USAGE;
+ }
+
+ return runJack(err, options);
+ }
+}
diff --git a/jack-server/src/com/android/jack/server/ServerTaskSpawningVM.java b/jack-server/src/com/android/jack/server/ServerTaskSpawningVM.java
new file mode 100644
index 0000000..a8ef6ab
--- /dev/null
+++ b/jack-server/src/com/android/jack/server/ServerTaskSpawningVM.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 com.android.jack.server;
+
+import com.android.jack.util.ExecuteFile;
+import com.android.sched.util.config.cli.TokenIterator;
+import com.android.sched.util.file.CannotReadException;
+import com.android.sched.util.file.InputStreamFile;
+import com.android.sched.util.file.NoSuchFileException;
+import com.android.sched.util.file.NotFileOrDirectoryException;
+import com.android.sched.util.file.WrongPermissionException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.Nonnull;
+
+/**
+ * {@link ServerTask} implementation that launches compilations with Jack into a spawned VM.
+ */
+public class ServerTaskSpawningVM implements ServerTask {
+
+ private static class MissingEnvException extends Exception {
+ private static final long serialVersionUID = 1L;
+ @Nonnull
+ private final String missingVariable;
+
+ public MissingEnvException(@Nonnull String missingVariable) {
+ this.missingVariable = missingVariable;
+ }
+
+ @Nonnull
+ @Override
+ public String getMessage() {
+ return "Environment variable '" + missingVariable + "' is undefined";
+ }
+ }
+ @Nonnull
+ private static Logger logger = Logger.getLogger(ServerTaskSpawningVM.class.getSimpleName());
+
+ @Override
+ public int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File pwd,
+ @Nonnull TokenIterator args) {
+ List<String> commandLineArgs;
+ try {
+ commandLineArgs = buildArgs(args);
+ } catch (NoSuchFileException e) {
+ return ServerExitStatus.FAILURE_JACK_JAR_NOT_FOUND;
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Failed to build command line", e);
+ return ServerExitStatus.FAILURE_USAGE;
+ }
+
+ ExecuteFile exec = new ExecuteFile(commandLineArgs.toArray(new String[commandLineArgs.size()]));
+ exec.setErr(err);
+ exec.setOut(out);
+
+ try {
+ exec.setWorkingDir(pwd, /* create */false);
+ } catch (IOException e) {
+ // It means that pwd is not a directory
+ return ServerExitStatus.FAILURE_USAGE;
+ }
+
+ try {
+ return exec.run();
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Failed to run command " + commandLineArgs, e);
+
+ return ServerExitStatus.FAILURE_UNKNOWN;
+ }
+ }
+
+ @Nonnull
+ private List<String> buildArgs(@Nonnull TokenIterator args)
+ throws NoSuchElementException,
+ WrongPermissionException,
+ NoSuchFileException,
+ NotFileOrDirectoryException,
+ CannotReadException, MissingEnvException {
+
+ List<String> commandLineArgs = new ArrayList<String>();
+ String vmCommand = System.getenv("JACK_VM_COMMAND");
+ if (vmCommand == null) {
+ throw new MissingEnvException("JACK_VM_COMMAND");
+ }
+ String jackJarPath = System.getenv("JACK_JAR");
+ if (jackJarPath == null) {
+ throw new MissingEnvException("JACK_JAR");
+ }
+ new InputStreamFile(jackJarPath);
+
+ StreamTokenizer iter = getCommandLineTokenizer(vmCommand);
+ try {
+ while (iter.nextToken() != StreamTokenizer.TT_EOF) {
+ commandLineArgs.add(iter.sval);
+ }
+ } catch (IOException e) {
+ throw new AssertionError();
+ }
+ commandLineArgs.add("-jar");
+ commandLineArgs.add(jackJarPath);
+
+ while (args.hasNext()) {
+ commandLineArgs.add(args.next());
+ }
+
+ return commandLineArgs;
+ }
+
+ @Nonnull
+ private static StreamTokenizer getCommandLineTokenizer(@Nonnull String command) {
+ StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(command));
+
+ tokenizer.resetSyntax();
+ tokenizer.wordChars(0, 255);
+ tokenizer.whitespaceChars(' ', ' ');
+ tokenizer.whitespaceChars('\t', '\t');
+ tokenizer.whitespaceChars('\n', '\n');
+ tokenizer.whitespaceChars('\r', '\r');
+ tokenizer.quoteChar('\'');
+ tokenizer.quoteChar('\"');
+ tokenizer.eolIsSignificant(false);
+ tokenizer.slashSlashComments(false);
+ tokenizer.slashStarComments(false);
+ tokenizer.lowerCaseMode(false);
+
+ return tokenizer;
+ }
+}