FELIX-2625 First shot at creating a Gogo Shell Plugin for the Web Console
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1238456 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole-plugins/gogo/pom.xml b/webconsole-plugins/gogo/pom.xml
new file mode 100644
index 0000000..30f9a5e
--- /dev/null
+++ b/webconsole-plugins/gogo/pom.xml
@@ -0,0 +1,180 @@
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>2.1</version>
+ <relativePath>../../../pom/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.felix.webconsole.plugins.gogo</artifactId>
+ <packaging>bundle</packaging>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Apache Felix Web Gogo Shell Plugin</name>
+ <description>
+ This is a plugin for the Apache Felix OSGi web console that provides access to Gogo shell commands.
+ </description>
+
+ <properties>
+ <dollar>$</dollar>
+ </properties>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/webconsole-plugins/gogo</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/webconsole-plugins/gogo</developerConnection>
+ <url>http://svn.apache.org/viewvc/felix/trunk/webconsole-plugins/gogo</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <!-- translate UTF-8 encoded properties files to ISO-8859-1 -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>native2ascii-maven-plugin</artifactId>
+ <version>1.0-beta-1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>native2ascii</goal>
+ </goals>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ <version>1.7.2</version>
+ <!-- As QDox is trying to inspect/load the classes
+ we have to add a slf4j implementation to the
+ class path of the plugin - we usually use
+ a static field for the logger and during class
+ loading this field requires an slf4j implementation!
+ -->
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.5.2</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <id>generate-scr-scrdescriptor</id>
+ <goals>
+ <goal>scr</goal>
+ </goals>
+ <configuration>
+ <properties>
+ <service.vendor>The Apache Software Foundation</service.vendor>
+ </properties>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.6</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>
+ ${project.artifactId}
+ </Bundle-SymbolicName>
+ <Bundle-Activator>
+ org.apache.felix.webconsole.plugins.gogo.impl.Activator
+ </Bundle-Activator>
+ <Embed-Dependency>
+ jline;inline=true
+ </Embed-Dependency>
+ <Bundle-NativeCode>
+ META-INF/native/windows32/jansi.dll;osname=Win32;processor=x86,
+ META-INF/native/windows64/jansi.dll;osname=Win32;processor=x86-64,
+ META-INF/native/linux32/libjansi.so;osname=Linux;processor=x86,
+ META-INF/native/linux64/libjansi.so;osname=Linux;processor=x86-64,
+ META-INF/native/osx/libjansi.jnilib;osname=MacOSX,
+ *
+ </Bundle-NativeCode>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.gogo.runtime</artifactId>
+ <version>0.6.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.5.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.webconsole</artifactId>
+ <version>3.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sonatype.jline</groupId>
+ <artifactId>jline</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <version>1.6.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES b/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES
new file mode 100644
index 0000000..1ebfb84
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/appended-resources/META-INF/DEPENDENCIES
@@ -0,0 +1,23 @@
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software from https://github.com/jline/jline2
+Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
+All rights reserved.
+Licensed under the BSD License
+
+
+II. Used Software
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2009).
+Licensed under the Apache License 2.0.
+
+
+III. License Summary
+- Apache License 2.0
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java
new file mode 100644
index 0000000..b8de725
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Activator.java
@@ -0,0 +1,35 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import org.fusesource.jansi.AnsiConsole;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+ public void start(BundleContext context) throws Exception {
+ AnsiConsole.systemInstall();
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ AnsiConsole.systemUninstall();
+ }
+
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/AggregateCompleter.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/AggregateCompleter.java
new file mode 100644
index 0000000..b8ee42e
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/AggregateCompleter.java
@@ -0,0 +1,89 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Collection;
+
+import jline.console.completer.Completer;
+
+/**
+ * Completer which contains multipule completers and aggregates them together.
+ */
+public class AggregateCompleter implements Completer
+{
+ private final Collection<Completer> completers;
+
+ public AggregateCompleter(final Collection<Completer> completers) {
+ assert completers != null;
+ this.completers = completers;
+ }
+
+ public int complete(final String buffer, final int cursor, final List candidates) {
+ // buffer could be null
+ assert candidates != null;
+
+ List<Completion> completions = new ArrayList<Completion>(completers.size());
+
+ // Run each completer, saving its completion results
+ int max = -1;
+ for (Completer completer : completers) {
+ Completion completion = new Completion(candidates);
+ completion.complete(completer, buffer, cursor);
+
+ // Compute the max cursor position
+ max = Math.max(max, completion.cursor);
+
+ completions.add(completion);
+ }
+
+ // Append candiates from completions which have the same cursor position as max
+ for (Completion completion : completions) {
+ if (completion.cursor == max) {
+ // noinspection unchecked
+ candidates.addAll(completion.candidates);
+ }
+ }
+
+ return max;
+ }
+
+ private class Completion
+ {
+ public final List<CharSequence> candidates;
+
+ public int cursor;
+
+ public Completion(final List candidates) {
+ assert candidates != null;
+
+ // noinspection unchecked
+ this.candidates = new LinkedList<CharSequence>(candidates);
+ }
+
+ public void complete(final Completer completer, final String buffer, final int cursor) {
+ assert completer != null;
+
+ this.cursor = completer.complete(buffer, cursor, candidates);
+ }
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CloseShellException.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CloseShellException.java
new file mode 100644
index 0000000..f3540a2
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CloseShellException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+
+@SuppressWarnings("serial")
+public class CloseShellException extends Exception {
+
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandSessionHolder.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandSessionHolder.java
new file mode 100644
index 0000000..0c04196
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandSessionHolder.java
@@ -0,0 +1,38 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import org.apache.felix.service.command.CommandSession;
+
+public class CommandSessionHolder {
+
+ private static final ThreadLocal<CommandSession> session = new ThreadLocal<CommandSession>();
+
+ public static CommandSession getSession() {
+ return session.get();
+ }
+
+ public static void setSession(CommandSession commandSession) {
+ session.set(commandSession);
+ }
+
+ public static void unset() {
+ session.remove();
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandsCompleter.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandsCompleter.java
new file mode 100644
index 0000000..0a55a35
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/CommandsCompleter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import jline.console.completer.Completer;
+
+import org.apache.felix.service.command.CommandSession;
+
+public class CommandsCompleter implements Completer {
+
+ private CommandSession session;
+ private final List<Completer> completers = new ArrayList<Completer>();
+ private final Set<String> commands = new HashSet<String>();
+
+ public CommandsCompleter(CommandSession session) {
+ this.session = session;
+ }
+
+
+ public int complete(String buffer, int cursor, List<CharSequence> candidates) {
+ checkData();
+ int res = new AggregateCompleter(completers).complete(buffer, cursor, candidates);
+// TODO: Collections.sort(candidates);
+ return res;
+ }
+
+ protected synchronized void checkData() {
+ // Copy the set to avoid concurrent modification exceptions
+ // TODO: fix that in gogo instead
+ Set<String> names = new HashSet<String>((Set<String>) session.get(".commands" /* CommandSessionImpl.COMMANDS */));
+ if (!names.equals(commands)) {
+ commands.clear();
+ completers.clear();
+
+ // get command aliases
+ Set<String> aliases = this.getAliases();
+ completers.add(new StringsCompleter(aliases));
+
+ // add argument completers for each command
+ for (String command : names) {
+ commands.add(command);
+ }
+ }
+ }
+
+ /**
+ * Get the aliases defined in the console session.
+ *
+ * @return the aliases set
+ */
+ private Set<String> getAliases() {
+ Set<String> vars = (Set<String>) session.get(null);
+ Set<String> aliases = new HashSet<String>();
+ for (String var : vars) {
+ Object content = session.get(var);
+ if (content != null && content.getClass().getName().equals("org.apache.felix.gogo.runtime.Closure")) {
+ aliases.add(var);
+ }
+ }
+ return aliases;
+ }
+
+}
+
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java
new file mode 100644
index 0000000..c9f37e2
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Console.java
@@ -0,0 +1,442 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jline.Terminal;
+import jline.UnsupportedTerminal;
+import jline.console.ConsoleReader;
+import jline.console.completer.Completer;
+import jline.console.history.PersistentHistory;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.fusesource.jansi.Ansi;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Console implements Runnable {
+
+ public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";
+
+ public static final String PROMPT = "PROMPT";
+
+ public static final String DEFAULT_PROMPT = "\u001B[1m${USER}\u001B[0m@${APPLICATION}> ";
+
+ public static final String PRINT_STACK_TRACES = "karaf.printStackTraces";
+
+ public static final String LAST_EXCEPTION = "karaf.lastException";
+
+ public static final String IGNORE_INTERRUPTS = "karaf.ignoreInterrupts";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Console.class);
+
+ protected CommandSession session;
+
+ private ConsoleReader reader;
+
+ private BlockingQueue<Integer> queue;
+
+ private boolean interrupt;
+
+ private Thread pipe;
+
+ volatile private boolean running;
+
+ volatile private boolean eof;
+
+ private Runnable closeCallback;
+
+ private Terminal terminal;
+
+ private InputStream consoleInput;
+
+ private InputStream in;
+
+ private PrintStream out;
+
+ private PrintStream err;
+
+ private Thread thread;
+
+ public Console(CommandProcessor processor, InputStream in, PrintStream out, PrintStream err, Terminal term,
+ Runnable closeCallback) throws Exception {
+ this.in = in;
+ this.out = out;
+ this.err = err;
+ this.queue = new ArrayBlockingQueue<Integer>(1024);
+ this.terminal = term == null ? new UnsupportedTerminal() : term;
+ this.consoleInput = new ConsoleInputStream();
+ this.session = processor.createSession(this.consoleInput, this.out, this.err);
+ this.session.put("SCOPE", "shell:osgi:*");
+ this.closeCallback = closeCallback;
+
+ reader = new ConsoleReader(this.consoleInput, new PrintWriter(this.out), getClass().getResourceAsStream(
+ "keybinding.properties"), this.terminal);
+
+ session.put(".jline.history", reader.getHistory());
+ Completer completer = createCompleter();
+ if (completer != null) {
+ reader.addCompleter(completer);
+ }
+ if (Boolean.getBoolean("jline.nobell")) {
+ reader.setBellEnabled(false);
+ }
+ pipe = new Thread(new Pipe());
+ pipe.setName("gogo shell pipe thread");
+ pipe.setDaemon(true);
+ }
+
+ public CommandSession getSession() {
+ return session;
+ }
+
+ public void close() {
+ // System.err.println("Closing");
+ if (reader.getHistory() instanceof PersistentHistory) {
+ try {
+ ((PersistentHistory) reader.getHistory()).flush();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ running = false;
+ CommandSessionHolder.unset();
+ pipe.interrupt();
+ }
+
+ public void run() {
+ ThreadLocal<CommandSessionHolder> consoleState = new ThreadLocal<CommandSessionHolder>();
+ thread = Thread.currentThread();
+ CommandSessionHolder.setSession(session);
+ running = true;
+ pipe.start();
+ welcome();
+ setSessionProperties();
+ String scriptFileName = System.getProperty(SHELL_INIT_SCRIPT);
+ if (scriptFileName != null) {
+ Reader r = null;
+ try {
+ File scriptFile = new File(scriptFileName);
+ r = new InputStreamReader(new FileInputStream(scriptFile));
+ CharArrayWriter w = new CharArrayWriter();
+ int n;
+ char[] buf = new char[8192];
+ while ((n = r.read(buf)) > 0) {
+ w.write(buf, 0, n);
+ }
+ session.execute(new String(w.toCharArray()));
+ } catch (Exception e) {
+ LOGGER.debug("Error in initialization script", e);
+ System.err.println("Error in initialization script: " + e.getMessage());
+ } finally {
+ if (r != null) {
+ try {
+ r.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+ while (running) {
+ try {
+ String command = null;
+ boolean loop = true;
+ boolean first = true;
+ while (loop) {
+ checkInterrupt();
+ String line = reader.readLine(first ? getPrompt() : "> ");
+ if (line == null) {
+ break;
+ }
+ if (command == null) {
+ command = line;
+ } else {
+ command += " " + line;
+ }
+ if (reader.getHistory().size() == 0) {
+ reader.getHistory().add(command);
+ } else {
+ reader.getHistory().replace(command);
+ }
+ try {
+ new Parser(command, 0).program();
+ loop = false;
+ } catch (Exception e) {
+ loop = true;
+ first = false;
+ }
+ }
+ if (command == null) {
+ break;
+ }
+ // session.getConsole().println("Executing: " + line);
+ Object result = session.execute(command);
+ if (result != null) {
+ session.getConsole().println(session.format(result, Converter.INSPECT));
+ }
+ } catch (InterruptedIOException e) {
+ // System.err.println("^C");
+ // TODO: interrupt current thread
+ } catch (CloseShellException e) {
+ break;
+ } catch (Exception t) {
+ try {
+ LOGGER.info("Exception caught while executing command", t);
+ session.put(LAST_EXCEPTION, t);
+ session.getConsole().print(Ansi.ansi().fg(Ansi.Color.RED).toString());
+ session.getConsole().println(
+ "Error executing command: "
+ + (t.getMessage() != null ? t.getMessage() : t.getClass().getName()));
+ session.getConsole().print(Ansi.ansi().fg(Ansi.Color.DEFAULT).toString());
+ } catch (Exception ignore) {
+ // ignore
+ }
+ }
+ }
+ close();
+ // System.err.println("Exiting console...");
+ if (closeCallback != null) {
+ closeCallback.run();
+ }
+ }
+
+ protected boolean getBoolean(String name) {
+ Object s = session.get(name);
+ if (s == null) {
+ s = System.getProperty(name);
+ }
+ if (s == null) {
+ return false;
+ }
+ if (s instanceof Boolean) {
+ return (Boolean) s;
+ }
+ return Boolean.parseBoolean(s.toString());
+ }
+
+ protected void welcome() {
+ Properties props = loadBrandingProperties();
+ String welcome = props.getProperty("welcome");
+ if (welcome != null && welcome.length() > 0) {
+ session.getConsole().println(welcome);
+ }
+ }
+
+ protected void setSessionProperties() {
+ Properties props = loadBrandingProperties();
+ for (Map.Entry<Object, Object> entry : props.entrySet()) {
+ String key = (String) entry.getKey();
+ if (key.startsWith("session.")) {
+ session.put(key.substring("session.".length()), entry.getValue());
+ }
+ }
+ }
+
+ protected Completer createCompleter() {
+ return new CommandsCompleter(session);
+ }
+
+ protected Properties loadBrandingProperties() {
+ Properties props = new Properties();
+ loadProps(props, "org/apache/karaf/shell/console/branding.properties");
+ loadProps(props, "org/apache/karaf/branding/branding.properties");
+ return props;
+ }
+
+ protected void loadProps(Properties props, String resource) {
+ InputStream is = null;
+ try {
+ is = getClass().getClassLoader().getResourceAsStream(resource);
+ if (is != null) {
+ props.load(is);
+ }
+ } catch (IOException e) {
+ // ignore
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ protected String getPrompt() {
+ try {
+ String prompt;
+ try {
+ Object p = session.get(PROMPT);
+ if (p != null) {
+ prompt = p.toString();
+ } else {
+ Properties properties = loadBrandingProperties();
+ if (properties.getProperty("prompt") != null) {
+ prompt = properties.getProperty("prompt");
+ // we put the PROMPT in ConsoleSession to avoid to read
+ // the properties file each time.
+ session.put(PROMPT, prompt);
+ } else {
+ prompt = DEFAULT_PROMPT;
+ }
+ }
+ } catch (Throwable t) {
+ prompt = DEFAULT_PROMPT;
+ }
+ Matcher matcher = Pattern.compile("\\$\\{([^}]+)\\}").matcher(prompt);
+ while (matcher.find()) {
+ Object rep = session.get(matcher.group(1));
+ if (rep != null) {
+ prompt = prompt.replace(matcher.group(0), rep.toString());
+ matcher.reset(prompt);
+ }
+ }
+ return prompt;
+ } catch (Throwable t) {
+ return "$ ";
+ }
+ }
+
+ private void checkInterrupt() throws IOException {
+ if (Thread.interrupted() || interrupt) {
+ interrupt = false;
+ throw new InterruptedIOException("Keyboard interruption");
+ }
+ }
+
+ private void interrupt() {
+ interrupt = true;
+ thread.interrupt();
+ }
+
+ private class ConsoleInputStream extends InputStream {
+ private int read(boolean wait) throws IOException {
+ if (!running) {
+ return -1;
+ }
+ checkInterrupt();
+ if (eof && queue.isEmpty()) {
+ return -1;
+ }
+ Integer i;
+ if (wait) {
+ try {
+ i = queue.take();
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException();
+ }
+ checkInterrupt();
+ } else {
+ i = queue.poll();
+ }
+ if (i == null) {
+ return -1;
+ }
+ return i;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return read(true);
+ }
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ int nb = 1;
+ int i = read(true);
+ if (i < 0) {
+ return -1;
+ }
+ b[off++] = (byte) i;
+ while (nb < len) {
+ i = read(false);
+ if (i < 0) {
+ return nb;
+ }
+ b[off++] = (byte) i;
+ nb++;
+ }
+ return nb;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return queue.size();
+ }
+ }
+
+ private class Pipe implements Runnable {
+ public void run() {
+ try {
+ while (running) {
+ try {
+ int c = terminal.readCharacter(in);
+ if (c == -1) {
+ return;
+ } else if (c == 4 && !getBoolean(IGNORE_INTERRUPTS)) {
+ err.println("^D");
+ } else if (c == 3 && !getBoolean(IGNORE_INTERRUPTS)) {
+ err.println("^C");
+ reader.getCursorBuffer().clear();
+ interrupt();
+ }
+ queue.put(c);
+ } catch (Throwable t) {
+ return;
+ }
+ }
+ } finally {
+ eof = true;
+ try {
+ queue.put(-1);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java
new file mode 100644
index 0000000..323bf49
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/GogoPlugin.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+/**
+ * Based on http://antony.lesuisse.org/software/ajaxterm/
+ * Public Domain License
+ */
+
+package org.apache.felix.webconsole.plugins.gogo.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The <code>GogoPlugin</code>
+ */
+@Component
+@Service(Servlet.class)
+public class GogoPlugin extends SimpleWebConsolePlugin {
+
+ /** Pseudo class version ID to keep the IDE quite. */
+ private static final long serialVersionUID = 1L;
+
+ @Property(name=WebConsoleConstants.PLUGIN_LABEL)
+ public static final String LABEL = "gogo";
+
+ @Property(name=WebConsoleConstants.PLUGIN_TITLE)
+ public static final String TITLE = "Gogo";
+
+ public static final int TERM_WIDTH = 120;
+ public static final int TERM_HEIGHT = 39;
+
+ @Reference
+ private CommandProcessor commandProcessor;
+
+ public GogoPlugin() {
+ super(LABEL, TITLE, null);
+ }
+
+ @Override
+ @Activate
+ public void activate(BundleContext bundleContext) {
+ super.activate(bundleContext);
+ }
+
+ @Override
+ @Deactivate
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
+ {
+ PrintWriter pw = response.getWriter();
+
+ String appRoot = request.getContextPath() + request.getServletPath();
+ pw.println( "<link href=\"" + appRoot + "/gogo/res/ui/gogo.css\" rel=\"stylesheet\" type=\"text/css\" />" );
+ pw.println( "<script src=\"" + appRoot + "/gogo/res/ui/gogo.js\" type=\"text/javascript\"></script>" );
+ pw.println( "<div id='console'><div id='term'></div></div>" );
+ pw.println( "<script type=\"text/javascript\"><!--" );
+ pw.println( "window.onload = function() { gogo.Terminal(document.getElementById(\"term\"), " + TERM_WIDTH + ", " + TERM_HEIGHT + "); }" );
+ pw.println( "--></script>" );
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ String encoding = request.getHeader("Accept-Encoding");
+ boolean supportsGzip = (encoding != null && encoding.toLowerCase().indexOf("gzip") > -1);
+ SessionTerminal st = getSessionTerminal(request);
+ String str = request.getParameter("k");
+ String f = request.getParameter("f");
+ String dump = st.handle(str, f != null && f.length() > 0);
+ if (dump != null) {
+ if (supportsGzip) {
+ response.setHeader("Content-Encoding", "gzip");
+ response.setHeader("Content-Type", "text/html");
+ try {
+ GZIPOutputStream gzos = new GZIPOutputStream(response.getOutputStream());
+ gzos.write(dump.getBytes());
+ gzos.close();
+ } catch (IOException ie) {
+ // handle the error here
+ ie.printStackTrace();
+ }
+ } else {
+ response.getOutputStream().write(dump.getBytes());
+ }
+ }
+ }
+
+ private SessionTerminal getSessionTerminal(final HttpServletRequest request) throws IOException{
+ final Object terminal = request.getSession(true).getAttribute("terminal");
+ if (terminal instanceof SessionTerminal) {
+ final SessionTerminal st = (SessionTerminal) terminal;
+ if (!st.isClosed()) {
+ return st;
+ }
+ }
+
+ final SessionTerminal st = new SessionTerminal(request.getRemoteUser());
+ request.getSession().setAttribute("terminal", st);
+ return st;
+ }
+
+ public class SessionTerminal implements Runnable {
+
+ private Terminal terminal;
+ private Console console;
+ private PipedOutputStream in;
+ private PipedInputStream out;
+ private boolean closed;
+
+ public SessionTerminal(final String user) throws IOException {
+ try {
+ this.terminal = new Terminal(TERM_WIDTH, TERM_HEIGHT);
+ terminal.write("\u001b\u005B20\u0068"); // set newline mode on
+
+ in = new PipedOutputStream();
+ out = new PipedInputStream();
+ PrintStream pipedOut = new PrintStream(new PipedOutputStream(out), true);
+
+ console = new Console(commandProcessor,
+ new PipedInputStream(in),
+ pipedOut,
+ pipedOut,
+ new WebTerminal(TERM_WIDTH, TERM_HEIGHT),
+ null);
+ CommandSession session = console.getSession();
+ session.put("APPLICATION", System.getProperty("karaf.name", "root"));
+ session.put("USER", user);
+ session.put("COLUMNS", Integer.toString(TERM_WIDTH));
+ session.put("LINES", Integer.toString(TERM_HEIGHT));
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw e;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw (IOException) new IOException().initCause(e);
+ }
+ new Thread(console).start();
+ new Thread(this).start();
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public String handle(String str, boolean forceDump) throws IOException {
+ try {
+ if (str != null && str.length() > 0) {
+ String d = terminal.pipe(str);
+ for (byte b : d.getBytes()) {
+ in.write(b);
+ }
+ in.flush();
+ }
+ } catch (IOException e) {
+ closed = true;
+ throw e;
+ }
+ try {
+ return terminal.dump(10, forceDump);
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException(e.toString());
+ }
+ }
+
+ public void run() {
+ try {
+ for (;;) {
+ byte[] buf = new byte[8192];
+ int l = out.read(buf);
+ InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(buf, 0, l));
+ StringBuilder sb = new StringBuilder();
+ for (;;) {
+ int c = r.read();
+ if (c == -1) {
+ break;
+ }
+ sb.append((char) c);
+ }
+ if (sb.length() > 0) {
+ terminal.write(sb.toString());
+ }
+ String s = terminal.read();
+ if (s != null && s.length() > 0) {
+ for (byte b : s.getBytes()) {
+ in.write(b);
+ }
+ }
+ }
+ } catch (IOException e) {
+ closed = true;
+ e.printStackTrace();
+ }
+ }
+
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/NameScoping.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/NameScoping.java
new file mode 100644
index 0000000..3d4b025
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/NameScoping.java
@@ -0,0 +1,73 @@
+/**
+ *
+ * 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.webconsole.plugins.gogo.impl;
+
+import org.apache.felix.service.command.CommandSession;
+
+
+/**
+ * A helper class for name scoping
+ */
+public class NameScoping {
+
+ public static final String MULTI_SCOPE_MODE_KEY = "MULTI_SCOPE_MODE";
+
+ /**
+ * Returns the name of the command which can omit the global scope prefix if the command starts with the
+ * same prefix as the current application
+ */
+ public static String getCommandNameWithoutGlobalPrefix(CommandSession session, String key) {
+ if (!isMultiScopeMode(session)) {
+ String globalScope = (String) session.get("APPLICATION");
+ if (globalScope != null) {
+ String prefix = globalScope + ":";
+ if (key.startsWith(prefix)) {
+ // TODO we may only want to do this for single-scope mode when outside of OSGi?
+ // so we may want to also check for a isMultiScope mode == false
+ return key.substring(prefix.length());
+ }
+ }
+ }
+ return key;
+ }
+
+ /**
+ * Returns true if the given scope is the global scope so that it can be hidden from help messages
+ */
+ public static boolean isGlobalScope(CommandSession session, String scope) {
+ if (!isMultiScopeMode(session)) {
+ String globalScope = (String) session.get("APPLICATION");
+ if (globalScope != null) {
+ return scope.equals(globalScope);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if we are in multi-scope mode (the default) or if we are in single scope mode which means we
+ * avoid prefixing commands with their scope
+ */
+ public static boolean isMultiScopeMode(CommandSession session) {
+ Object value = session.get(MULTI_SCOPE_MODE_KEY);
+ if (value != null && value.equals("false")) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Parser.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Parser.java
new file mode 100644
index 0000000..d2df531
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Parser.java
@@ -0,0 +1,473 @@
+/*
+ * 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.
+ */
+// DWB14: parser loops if // comment at start of program
+// DWB15: allow program to have trailing ';'
+package org.apache.felix.webconsole.plugins.gogo.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Parser
+{
+ int current = 0;
+ String text;
+ boolean escaped;
+ static final String SPECIAL = "<;|{[\"'$`(=";
+
+ List<List<List<String>>> program;
+ List<List<String>> statements;
+ List<String> statement;
+ int cursor;
+ int start = -1;
+ int c0;
+ int c1;
+ int c2;
+ int c3;
+
+ public Parser(String text, int cursor)
+ {
+ this.text = text;
+ this.cursor = cursor;
+ }
+
+ void ws()
+ {
+ // derek: BUGFIX: loop if comment at beginning of input
+ //while (!eof() && Character.isWhitespace(peek())) {
+ while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0))
+ {
+ if (current != 0 || !escaped && Character.isWhitespace(peek()))
+ {
+ current++;
+ }
+ if (peek() == '/' && current < text.length() - 2
+ && text.charAt(current + 1) == '/')
+ {
+ comment();
+ }
+ if (current == 0)
+ {
+ break;
+ }
+ }
+ }
+
+ private void comment()
+ {
+ while (!eof() && peek() != '\n' && peek() != '\r')
+ {
+ next();
+ }
+ }
+
+ boolean eof()
+ {
+ return current >= text.length();
+ }
+
+ char peek()
+ {
+ return peek(false);
+ }
+
+ char peek(boolean increment)
+ {
+ escaped = false;
+ if (eof())
+ {
+ return 0;
+ }
+
+ int last = current;
+ char c = text.charAt(current++);
+
+ if (c == '\\')
+ {
+ escaped = true;
+ if (eof())
+ {
+ throw new RuntimeException("Eof found after \\"); // derek
+ }
+
+ c = text.charAt(current++);
+
+ switch (c)
+ {
+ case 't':
+ c = '\t';
+ break;
+ case '\r':
+ case '\n':
+ c = ' ';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 'u':
+ c = unicode();
+ current += 4;
+ break;
+ default:
+ // We just take the next character literally
+ // but have the escaped flag set, important for {},[] etc
+ }
+ }
+ if (cursor > last && cursor <= current)
+ {
+ c0 = program != null ? program.size() : 0;
+ c1 = statements != null ? statements.size() : 0;
+ c2 = statement != null ? statement.size() : 0;
+ c3 = (start >= 0) ? current - start : 0;
+ }
+ if (!increment)
+ {
+ current = last;
+ }
+ return c;
+ }
+
+ public List<List<List<String>>> program()
+ {
+ program = new ArrayList<List<List<String>>>();
+ ws();
+ if (!eof())
+ {
+ program.add(pipeline());
+ while (peek() == ';')
+ {
+ current++;
+ List<List<String>> pipeline = pipeline();
+ program.add(pipeline);
+ }
+ }
+ if (!eof())
+ {
+ throw new RuntimeException("Program has trailing text: " + context(current));
+ }
+
+ List<List<List<String>>> p = program;
+ program = null;
+ return p;
+ }
+
+ CharSequence context(int around)
+ {
+ return text.subSequence(Math.max(0, current - 20), Math.min(text.length(),
+ current + 4));
+ }
+
+ public List<List<String>> pipeline()
+ {
+ statements = new ArrayList<List<String>>();
+ statements.add(statement());
+ while (peek() == '|')
+ {
+ current++;
+ ws();
+ if (!eof())
+ {
+ statements.add(statement());
+ }
+ else
+ {
+ statements.add(new ArrayList<String>());
+ break;
+ }
+ }
+ List<List<String>> s = statements;
+ statements = null;
+ return s;
+ }
+
+ public List<String> statement()
+ {
+ statement = new ArrayList<String>();
+ statement.add(value());
+ while (!eof())
+ {
+ ws();
+ if (peek() == '|' || peek() == ';')
+ {
+ break;
+ }
+
+ if (!eof())
+ {
+ statement.add(messy());
+ }
+ }
+ List<String> s = statement;
+ statement = null;
+ return s;
+ }
+
+ public String messy()
+ {
+ start = current;
+ char c = peek();
+ if (c > 0 && SPECIAL.indexOf(c) < 0)
+ {
+ current++;
+ try {
+ while (!eof())
+ {
+ c = peek();
+ if (!escaped && (c == ';' || c == '|' || Character.isWhitespace(c)))
+ {
+ break;
+ }
+ next();
+ }
+ return text.substring(start, current);
+ } finally {
+ start = -1;
+ }
+ }
+ else
+ {
+ return value();
+ }
+ }
+
+ String value()
+ {
+ ws();
+
+ start = current;
+ try {
+ char c = next();
+ if (!escaped)
+ {
+ switch (c)
+ {
+ case '{':
+ return text.substring(start, find('}', '{'));
+ case '(':
+ return text.substring(start, find(')', '('));
+ case '[':
+ return text.substring(start, find(']', '['));
+ case '<':
+ return text.substring(start, find('>', '<'));
+ case '=':
+ return text.substring(start, current);
+ case '"':
+ case '\'':
+ quote(c);
+ break;
+ }
+ }
+
+ // Some identifier or number
+ while (!eof())
+ {
+ c = peek();
+ if (!escaped)
+ {
+ if (Character.isWhitespace(c) || c == ';' || c == '|' || c == '=')
+ {
+ break;
+ }
+ else if (c == '{')
+ {
+ next();
+ find('}', '{');
+ }
+ else if (c == '(')
+ {
+ next();
+ find(')', '(');
+ }
+ else if (c == '<')
+ {
+ next();
+ find('>', '<');
+ }
+ else if (c == '[')
+ {
+ next();
+ find(']', '[');
+ }
+ else if (c == '\'' || c == '"')
+ {
+ next();
+ quote(c);
+ next();
+ }
+ else
+ {
+ next();
+ }
+ }
+ else
+ {
+ next();
+ }
+ }
+ return text.substring(start, current);
+ } finally {
+ start = -1;
+ }
+ }
+
+ boolean escaped()
+ {
+ return escaped;
+ }
+
+ char next()
+ {
+ return peek(true);
+ }
+
+ char unicode()
+ {
+ if (current + 4 > text.length())
+ {
+ throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..."
+ + context(current) + "...");
+ }
+
+ String s = text.subSequence(current, current + 4).toString();
+ int n = Integer.parseInt(s, 16);
+ return (char) n;
+ }
+
+ int find(char target, char deeper)
+ {
+ int start = current;
+ int level = 1;
+
+ while (level != 0)
+ {
+ if (eof())
+ {
+ throw new RuntimeException("Eof found in the middle of a compound for '"
+ + target + deeper + "', begins at " + context(start));
+ }
+
+ char c = next();
+ if (!escaped)
+ {
+ if (c == target)
+ {
+ level--;
+ }
+ else
+ {
+ if (c == deeper)
+ {
+ level++;
+ }
+ else
+ {
+ if (c == '"')
+ {
+ quote('"');
+ }
+ else
+ {
+ if (c == '\'')
+ {
+ quote('\'');
+ }
+ else
+ {
+ if (c == '`')
+ {
+ quote('`');
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return current;
+ }
+
+ int quote(char which)
+ {
+ while (!eof() && (peek() != which || escaped))
+ {
+ next();
+ }
+
+ return current++;
+ }
+
+ CharSequence findVar()
+ {
+ int start = current;
+ char c = peek();
+
+ if (c == '{')
+ {
+ next();
+ int end = find('}', '{');
+ return text.subSequence(start, end);
+ }
+ if (c == '(')
+ {
+ next();
+ int end = find(')', '(');
+ return text.subSequence(start, end);
+ }
+
+ if (Character.isJavaIdentifierPart(c))
+ {
+ while (c == '$')
+ {
+ c = next();
+ }
+ while (!eof() && (Character.isJavaIdentifierPart(c) || c == '.') && c != '$')
+ {
+ next();
+ c = peek();
+ }
+ return text.subSequence(start, current);
+ }
+ throw new IllegalArgumentException(
+ "Reference to variable does not match syntax of a variable: "
+ + context(start));
+ }
+
+ public String toString()
+ {
+ return "..." + context(current) + "...";
+ }
+
+ public String unescape()
+ {
+ StringBuilder sb = new StringBuilder();
+ while (!eof())
+ {
+ sb.append(next());
+ }
+ return sb.toString();
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/StringsCompleter.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/StringsCompleter.java
new file mode 100644
index 0000000..c551db2
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/StringsCompleter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import jline.console.completer.Completer;
+
+/**
+ * Completer for a set of strings.
+ */
+public class StringsCompleter implements Completer {
+
+ private final SortedSet<String> strings = new TreeSet<String>();
+
+ private final boolean caseSensitive;
+
+ public StringsCompleter() {
+ this(true);
+ }
+
+ public StringsCompleter(final boolean caseSensitive) {
+ this.caseSensitive = caseSensitive;
+ }
+
+ public StringsCompleter(final Collection<String> strings) {
+ this();
+ assert strings != null;
+ getStrings().addAll(strings);
+ }
+
+ public StringsCompleter(final String[] strings, boolean caseSensitive) {
+ this(Arrays.asList(strings), caseSensitive);
+ }
+
+ public StringsCompleter(final Collection<String> strings, boolean caseSensitive) {
+ this(caseSensitive);
+ assert strings != null;
+ getStrings().addAll(strings);
+ }
+
+ public StringsCompleter(final String[] strings) {
+ this(Arrays.asList(strings));
+ }
+
+ public SortedSet<String> getStrings() {
+ return strings;
+ }
+
+ public int complete(String buffer, final int cursor, final List candidates) {
+ // buffer could be null
+ assert candidates != null;
+
+ if (buffer == null) {
+ buffer = "";
+ }
+ if (!caseSensitive) {
+ buffer = buffer.toLowerCase();
+ }
+
+ // KARAF-421, use getStrings() instead strings field.
+ SortedSet<String> matches = getStrings().tailSet(buffer);
+
+ for (String match : matches) {
+ String s = caseSensitive ? match : match.toLowerCase();
+ if (!s.startsWith(buffer)) {
+ break;
+ }
+
+ // noinspection unchecked
+ candidates.add(match);
+ }
+
+ if (candidates.size() == 1) {
+ // noinspection unchecked
+ candidates.set(0, candidates.get(0) + " ");
+ }
+
+ return candidates.isEmpty() ? -1 : 0;
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Terminal.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Terminal.java
new file mode 100644
index 0000000..27fc0a8
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/Terminal.java
@@ -0,0 +1,1504 @@
+/*
+ * 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.
+ */
+
+/**
+ * Based on http://antony.lesuisse.org/software/ajaxterm/
+ * Public Domain License
+ */
+
+/**
+ * See http://www.ecma-international.org/publications/standards/Ecma-048.htm
+ * and http://vt100.net/docs/vt510-rm/
+ */
+
+package org.apache.felix.webconsole.plugins.gogo.impl;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class Terminal {
+
+ enum State {
+ None,
+ Esc,
+ Str,
+ Csi,
+ }
+
+ private int width;
+ private int height;
+ private int attr;
+ private boolean eol;
+ private int cx;
+ private int cy;
+ private int[] screen;
+ private int[] screen2;
+ private State vt100_parse_state = State.None;
+ private int vt100_parse_len;
+ private int vt100_lastchar;
+ private int vt100_parse_func;
+ private String vt100_parse_param;
+ private boolean vt100_mode_autowrap;
+ private boolean vt100_mode_insert;
+ private boolean vt100_charset_is_single_shift;
+ private boolean vt100_charset_is_graphical;
+ private boolean vt100_mode_lfnewline;
+ private boolean vt100_mode_origin;
+ private boolean vt100_mode_inverse;
+ private boolean vt100_mode_cursorkey;
+ private boolean vt100_mode_cursor;
+ private boolean vt100_mode_alt_screen;
+ private boolean vt100_mode_backspace;
+ private boolean vt100_mode_column_switch;
+ private boolean vt100_keyfilter_escape;
+ private int[] vt100_charset_graph = new int[] {
+ 0x25ca, 0x2026, 0x2022, 0x3f,
+ 0xb6, 0x3f, 0xb0, 0xb1,
+ 0x3f, 0x3f, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0xaf,
+ 0x2014, 0x2014, 0x2014, 0x5f,
+ 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x7c, 0x2264, 0x2265, 0xb6,
+ 0x2260, 0xa3, 0xb7, 0x7f
+ };
+ private int vt100_charset_g_sel;
+ private int[] vt100_charset_g = { 0, 0 };
+ private Map<String, Object> vt100_saved;
+ private Map<String, Object> vt100_saved2;
+ private int vt100_saved_cx;
+ private int vt100_saved_cy;
+ private String vt100_out;
+
+ private int scroll_area_y0;
+ private int scroll_area_y1;
+
+ private List<Integer> tab_stops;
+
+ private int utf8_char;
+ private int utf8_units_count;
+ private int utf8_units_received;
+
+ private AtomicBoolean dirty = new AtomicBoolean(true);
+
+ public Terminal() {
+ this(80, 24);
+ }
+
+ public Terminal(int width, int height) {
+ this.width = width;
+ this.height = height;
+ reset_hard();
+ }
+
+ private void reset_hard() {
+ // Attribute mask: 0x0XFB0000
+ // X: Bit 0 - Underlined
+ // Bit 1 - Negative
+ // Bit 2 - Concealed
+ // F: Foreground
+ // B: Background
+ attr = 0x00fe0000;
+ // UTF-8 decoder
+ utf8_units_count = 0;
+ utf8_units_received = 0;
+ utf8_char = 0;
+ // Key filter
+ vt100_keyfilter_escape = false;
+ // Last char
+ vt100_lastchar = 0;
+ // Control sequences
+ vt100_parse_len = 0;
+ vt100_parse_state = State.None;
+ vt100_parse_func = 0;
+ vt100_parse_param = "";
+ // Buffers
+ vt100_out = "";
+ // Invoke other resets
+ reset_screen();
+ reset_soft();
+ }
+
+ private void reset_soft() {
+ // Attribute mask: 0x0XFB0000
+ // X: Bit 0 - Underlined
+ // Bit 1 - Negative
+ // Bit 2 - Concealed
+ // F: Foreground
+ // B: Background
+ attr = 0x00fe0000;
+ // Scroll parameters
+ scroll_area_y0 = 0;
+ scroll_area_y1 = height;
+ // Character sets
+ vt100_charset_is_single_shift = false;
+ vt100_charset_is_graphical = false;
+ vt100_charset_g_sel = 0;
+ vt100_charset_g = new int[] { 0, 0 };
+ // Modes
+ vt100_mode_insert = false;
+ vt100_mode_lfnewline = false;
+ vt100_mode_cursorkey = false;
+ vt100_mode_column_switch = false;
+ vt100_mode_inverse = false;
+ vt100_mode_origin = false;
+ vt100_mode_autowrap = true;
+ vt100_mode_cursor = true;
+ vt100_mode_alt_screen = false;
+ vt100_mode_backspace = false;
+ // Init DECSC state
+ esc_DECSC();
+ vt100_saved2 = vt100_saved;
+ esc_DECSC();
+ }
+
+ private void reset_screen() {
+ // Screen
+ screen = new int[width * height];
+ Arrays.fill(screen, attr | 0x0020);
+ screen2 = new int[width * height];
+ Arrays.fill(screen2, attr | 0x0020);
+ // Scroll parameters
+ scroll_area_y0 = 0;
+ scroll_area_y1 = height;
+ // Cursor position
+ cx = 0;
+ cy = 0;
+ // Tab stops
+ tab_stops = new ArrayList<Integer>();
+ for (int i = 7; i < width; i += 8) {
+ tab_stops.add(i);
+ }
+ }
+
+ //
+ // UTF-8 functions
+ //
+
+ private String utf8_decode(String d) {
+ StringBuilder o = new StringBuilder();
+ for (char c : d.toCharArray()) {
+ if (utf8_units_count != utf8_units_received) {
+ utf8_units_received++;
+ if ((c & 0xc0) == 0x80) {
+ utf8_char = (utf8_char << 6) | (c & 0x3f);
+ if (utf8_units_count == utf8_units_received) {
+ if (utf8_char < 0x10000) {
+ o.append((char) utf8_char);
+ }
+ utf8_units_count = utf8_units_received = 0;
+ }
+ } else {
+ o.append('?');
+ while (utf8_units_received-- > 0) {
+ o.append('?');
+ }
+ utf8_units_count = 0;
+ }
+ } else {
+ if ((c & 0x80) == 0x00) {
+ o.append(c);
+ } else if ((c & 0xe0) == 0xc0) {
+ utf8_units_count = 1;
+ utf8_char = c & 0x1f;
+ } else if ((c & 0xf0) == 0xe0) {
+ utf8_units_count = 2;
+ utf8_char = c & 0x0f;
+ } else if ((c & 0xf8) == 0xf0) {
+ utf8_units_count = 3;
+ utf8_char = c & 0x07;
+ } else {
+ o.append('?');
+ }
+
+ }
+ }
+ return o.toString();
+ }
+
+ private int utf8_charwidth(int c) {
+ if (c >= 0x2e80) {
+ return 2;
+ }
+ return 1;
+ }
+
+ //
+ // Low-level terminal functions
+ //
+
+ private int[] peek(int y0, int x0, int y1, int x1) {
+ int from = width * y0 + x0;
+ int to = width * (y1 - 1) + x1;
+ int newLength = to - from;
+ if (newLength < 0)
+ throw new IllegalArgumentException(from + " > " + to);
+ int[] copy = new int[newLength];
+ System.arraycopy(screen, from, copy, 0,
+ Math.min(screen.length - from, newLength));
+ return copy;
+ }
+
+ private void poke(int y, int x, int[] s) {
+ System.arraycopy(s, 0, screen, width * y + x, s.length);
+ setDirty();
+ }
+
+ private void fill(int y0, int x0, int y1, int x1, int c) {
+ int d0 = width * y0 + x0;
+ int d1 = width * (y1 - 1) + x1;
+ if (d0 <= d1) {
+ Arrays.fill(screen, width * y0 + x0, width * (y1 - 1) + x1, c);
+ setDirty();
+ }
+ }
+
+ private void clear(int y0, int x0, int y1, int x1) {
+ fill(y0, x0, y1, x1, attr | 0x20);
+ }
+
+ //
+ // Scrolling functions
+ //
+
+ private void scroll_area_up(int y0, int y1) {
+ scroll_area_up(y0, y1, 1);
+ }
+
+ private void scroll_area_up(int y0, int y1, int n) {
+ n = Math.min(y1 - y0, n);
+ poke(y0, 0, peek(y0 + n, 0, y1, width));
+ clear(y1-n, 0, y1, width);
+ }
+
+ private void scroll_area_down(int y0, int y1) {
+ scroll_area_down(y0, y1, 1);
+ }
+
+ private void scroll_area_down(int y0, int y1, int n) {
+ n = Math.min(y1 - y0, n);
+ poke(y0 + n, 0, peek(y0, 0, y1-n, width));
+ clear(y0, 0, y0 + n, width);
+ }
+
+ private void scroll_area_set(int y0, int y1) {
+ y0 = Math.max(0, Math.min(height - 1, y0));
+ y1 = Math.max(1, Math.min(height, y1));
+ if (y1 > y0) {
+ scroll_area_y0 = y0;
+ scroll_area_y1 = y1;
+ }
+ }
+
+ private void scroll_line_right(int y, int x) {
+ scroll_line_right(y, x, 1);
+ }
+
+ private void scroll_line_right(int y, int x, int n) {
+ if (x < width) {
+ n = Math.min(width - cx, n);
+ poke(y, x + n, peek(y, x, y + 1, width - n));
+ clear(y, x, y + 1, x + n);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void scroll_line_left(int y, int x) {
+ scroll_line_left(y, x, 1);
+ }
+
+ private void scroll_line_left(int y, int x, int n) {
+ if (x < width) {
+ n = Math.min(width - cx, n);
+ poke(y, x, peek(y, x + n, y + 1, width));
+ clear(y, width - n, y + 1, width);
+ }
+ }
+
+ //
+ // Cursor functions
+ //
+
+ private int[] cursor_line_width(int next_char) {
+ int wx = utf8_charwidth(next_char);
+ int lx = 0;
+ for (int x = 0; x < Math.min(cx, width); x++) {
+ int c = peek(cy, x, cy + 1, x + 1)[0] & 0xffff;
+ wx += utf8_charwidth(c);
+ lx += 1;
+ }
+ return new int[] { wx, lx };
+ }
+
+ private void cursor_up() {
+ cursor_up(1);
+ }
+
+ private void cursor_up(int n) {
+ cy = Math.max(scroll_area_y0, cy - n);
+ setDirty();
+ }
+
+ private void cursor_down() {
+ cursor_down(1);
+ }
+
+ private void cursor_down(int n) {
+ cy = Math.min(scroll_area_y1 - 1, cy + n);
+ setDirty();
+ }
+
+ @SuppressWarnings("unused")
+ private void cursor_left() {
+ cursor_left(1);
+ }
+
+ private void cursor_left(int n) {
+ eol = false;
+ cx = Math.max(0, cx - n);
+ setDirty();
+ }
+
+ private void cursor_right() {
+ cursor_right(1);
+ }
+
+ private void cursor_right(int n) {
+ eol = cx + n >= width;
+ cx = Math.min(width - 1, cx + n);
+ setDirty();
+ }
+
+ private void cursor_set_x(int x) {
+ eol = false;
+ cx = Math.max(0, x);
+ setDirty();
+ }
+
+ private void cursor_set_y(int y) {
+ cy = Math.max(0, Math.min(height - 1, y));
+ setDirty();
+ }
+
+ private void cursor_set(int y, int x) {
+ cursor_set_x(x);
+ cursor_set_y(y);
+ }
+
+ //
+ // Dumb terminal
+ //
+
+ private void ctrl_BS() {
+ int dy = (cx - 1) / width;
+ cursor_set(Math.max(scroll_area_y0, cy + dy), (cx - 1) % width);
+ }
+
+ private void ctrl_HT() {
+ ctrl_HT(1);
+ }
+
+ private void ctrl_HT(int n) {
+ if (n > 0 && cx >= width) {
+ return;
+ }
+ if (n <= 0 && cx == 0) {
+ return;
+ }
+ int ts = -1;
+ for (int i = 0; i < tab_stops.size(); i++) {
+ if (cx >= tab_stops.get(i)) {
+ ts = i;
+ }
+ }
+ ts += n;
+ if (ts < tab_stops.size() && ts >= 0) {
+ cursor_set_x(tab_stops.get(ts));
+ } else {
+ cursor_set_x(width - 1);
+ }
+ }
+
+ private void ctrl_LF() {
+ if (vt100_mode_lfnewline) {
+ ctrl_CR();
+ }
+ if (cy == scroll_area_y1 - 1) {
+ scroll_area_up(scroll_area_y0, scroll_area_y1);
+ } else {
+ cursor_down();
+ }
+ }
+
+ private void ctrl_CR() {
+ cursor_set_x(0);
+ }
+
+ private boolean dumb_write(int c) {
+ if (c < 32) {
+ if (c == 8) {
+ ctrl_BS();
+ } else if (c == 9) {
+ ctrl_HT();
+ } else if (c >= 10 && c <= 12) {
+ ctrl_LF();
+ } else if (c == 13) {
+ ctrl_CR();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void dumb_echo(int c) {
+ if (eol) {
+ if (vt100_mode_autowrap) {
+ ctrl_CR();
+ ctrl_LF();
+ } else {
+ cx = cursor_line_width(c)[1] - 1;
+ }
+ }
+ if (vt100_mode_insert) {
+ scroll_line_right(cy, cx);
+ }
+ if (vt100_charset_is_single_shift) {
+ vt100_charset_is_single_shift = false;
+ } else if (vt100_charset_is_graphical && ((c & 0xffe0) == 0x0060)) {
+ c = vt100_charset_graph[c - 0x60];
+ }
+ poke(cy, cx, new int[] { attr | c });
+ cursor_right();
+ }
+
+ //
+ // VT100
+ //
+
+ private void vt100_charset_update() {
+ vt100_charset_is_graphical = (vt100_charset_g[vt100_charset_g_sel] == 2);
+ }
+
+ private void vt100_charset_set(int g) {
+ // Invoke active character set
+ vt100_charset_g_sel = g;
+ vt100_charset_update();
+ }
+
+ private void vt100_charset_select(int g, int charset) {
+ // Select charset
+ vt100_charset_g[g] = charset;
+ vt100_charset_update();
+ }
+
+ private void vt100_setmode(String p, boolean state) {
+ // Set VT100 mode
+ String[] ps = vt100_parse_params(p, new String[0]);
+ for (String m : ps) {
+ // 1 : GATM: Guarded area transfer
+ // 2 : KAM: Keyboard action
+ // 3 : CRM: Control representation
+ if ("4".equals(m)) {
+ // Insertion replacement mode
+ vt100_mode_insert = state;
+ // 5 : SRTM: Status reporting transfer
+ // 7 : VEM: Vertical editing
+ // 10 : HEM: Horizontal editing
+ // 11 : PUM: Positioning nit
+ // 12 : SRM: Send/receive
+ // 13 : FEAM: Format effector action
+ // 14 : FETM: Format effector transfer
+ // 15 : MATM: Multiple area transfer
+ // 16 : TTM: Transfer termination
+ // 17 : SATM: Selected area transfer
+ // 18 : TSM: Tabulation stop
+ // 19 : EBM: Editing boundary
+ } else if ("20".equals(m)) {
+ // LNM: Line feed/new line
+ vt100_mode_lfnewline = state;
+ } else if ("?1".equals(m)) {
+ // DECCKM: Cursor keys
+ vt100_mode_cursorkey = state;
+ // ?2 : DECANM: ANSI
+ } else if ("?3".equals(m)) {
+ // DECCOLM: Column
+ if (vt100_mode_column_switch) {
+ if (state) {
+ width = 132;
+ } else {
+ width = 80;
+ }
+ reset_screen();
+ }
+ // ?4 : DECSCLM: Scrolling
+ } else if ("?5".equals(m)) {
+ // DECSCNM: Screen
+ vt100_mode_inverse = state;
+ } else if ("?6".equals(m)) {
+ // DECOM: Origin
+ vt100_mode_origin = state;
+ if (state) {
+ cursor_set(scroll_area_y0, 0);
+ } else {
+ cursor_set(0, 0);
+ }
+ } else if ("?7".equals(m)) {
+ // DECAWM: Autowrap
+ vt100_mode_autowrap = state;
+ // ?8 : DECARM: Autorepeat
+ // ?9 : Interlacing
+ // ?18 : DECPFF: Print form feed
+ // ?19 : DECPEX: Printer extent
+ } else if ("?25".equals(m)) {
+ // DECTCEM: Text cursor enable
+ vt100_mode_cursor = state;
+ // ?34 : DECRLM: Cursor direction, right to left
+ // ?35 : DECHEBM: Hebrew keyboard mapping
+ // ?36 : DECHEM: Hebrew encoding mode
+ } else if ("?40".equals(m)) {
+ // Column switch control
+ vt100_mode_column_switch = state;
+ // ?42 : DECNRCM: National replacement character set
+ } else if ("?47".equals(m)) {
+ // Alternate screen mode
+ if ((state && !vt100_mode_alt_screen) || (!state && vt100_mode_alt_screen)) {
+ int[] s = screen; screen = screen2; screen2 = s;
+ Map<String, Object> map = vt100_saved; vt100_saved = vt100_saved2; vt100_saved2 = map;
+ }
+ vt100_mode_alt_screen = state;
+ // ?57 : DECNAKB: Greek keyboard mapping
+ } else if ("?67".equals(m)) {
+ // DECBKM: Backarrow key
+ vt100_mode_backspace = state;
+ }
+ // ?98 : DECARSM: auto-resize
+ // ?101 : DECCANSM: Conceal answerback message
+ // ?109 : DECCAPSLK: caps lock
+ }
+ }
+
+ private void ctrl_SO() {
+ vt100_charset_set(1);
+ }
+
+ private void ctrl_SI() {
+ vt100_charset_set(0);
+ }
+
+ private void esc_CSI() {
+ vt100_parse_reset(State.Csi);
+ }
+
+ private void esc_DECALN() {
+ fill(0, 0, height, width, 0x00fe0045);
+ }
+
+ private void esc_G0_0() {
+ vt100_charset_select(0, 0);
+ }
+ private void esc_G0_1() {
+ vt100_charset_select(0, 1);
+ }
+ private void esc_G0_2() {
+ vt100_charset_select(0, 2);
+ }
+ private void esc_G0_3() {
+ vt100_charset_select(0, 3);
+ }
+ private void esc_G0_4() {
+ vt100_charset_select(0, 4);
+ }
+
+ private void esc_G1_0() {
+ vt100_charset_select(1, 0);
+ }
+ private void esc_G1_1() {
+ vt100_charset_select(1, 1);
+ }
+ private void esc_G1_2() {
+ vt100_charset_select(1, 2);
+ }
+ private void esc_G1_3() {
+ vt100_charset_select(1, 3);
+ }
+ private void esc_G1_4() {
+ vt100_charset_select(1, 4);
+ }
+
+ private void esc_DECSC() {
+ vt100_saved = new HashMap<String, Object>();
+ vt100_saved.put("cx", cx);
+ vt100_saved.put("cy", cy);
+ vt100_saved.put("attr", attr);
+ vt100_saved.put("vt100_charset_g_sel", vt100_charset_g_sel);
+ vt100_saved.put("vt100_charset_g", vt100_charset_g);
+ vt100_saved.put("vt100_mode_autowrap", vt100_mode_autowrap);
+ vt100_saved.put("vt100_mode_origin", vt100_mode_origin);
+ }
+
+ private void esc_DECRC() {
+ cx = (Integer) vt100_saved.get("cx");
+ cy = (Integer) vt100_saved.get("cy");
+ attr = (Integer) vt100_saved.get("attr");
+ vt100_charset_g_sel = (Integer) vt100_saved.get("vt100_charset_g_sel");
+ vt100_charset_g = (int[]) vt100_saved.get("vt100_charset_g");
+ vt100_charset_update();
+ vt100_mode_autowrap = (Boolean) vt100_saved.get("vt100_mode_autowrap");
+ vt100_mode_origin = (Boolean) vt100_saved.get("vt100_mode_origin");
+ }
+
+ private void esc_IND() {
+ ctrl_LF();
+ }
+
+ private void esc_NEL() {
+ ctrl_CR();
+ ctrl_LF();
+ }
+
+ private void esc_HTS() {
+ csi_CTC("0");
+ }
+
+ private void esc_RI() {
+ if (cy == scroll_area_y0) {
+ scroll_area_down(scroll_area_y0, scroll_area_y1);
+ } else {
+ cursor_up();
+ }
+ }
+
+ private void esc_SS2() {
+ vt100_charset_is_single_shift = true;
+ }
+
+ private void esc_SS3() {
+ vt100_charset_is_single_shift = true;
+ }
+
+ private void esc_DCS() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_SOS() {
+ vt100_parse_reset(State.Str);
+ }
+
+ @SuppressWarnings("unused")
+ private void esc_DECID() {
+ csi_DA("0");
+ }
+
+ private void esc_ST() {
+ }
+
+ private void esc_OSC() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_PM() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_APC() {
+ vt100_parse_reset(State.Str);
+ }
+
+ private void esc_RIS() {
+ reset_hard();
+ }
+
+ private void csi_ICH(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_line_right(cy, cx, ps[0]);
+ }
+
+ private void csi_CUU(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_up(Math.max(1, ps[0]));
+ }
+
+ private void csi_CUD(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_down(Math.max(1, ps[0]));
+ }
+
+ private void csi_CUF(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_right(Math.max(1, ps[0]));
+ }
+
+ private void csi_CUB(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_left(Math.max(1, ps[0]));
+ }
+
+ private void csi_CNL(String p) {
+ csi_CUD(p);
+ ctrl_CR();
+ }
+
+ private void csi_CPL(String p) {
+ csi_CUU(p);
+ ctrl_CR();
+ }
+
+ private void csi_CHA(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_set_x(ps[0] - 1);
+ }
+
+ private void csi_CUP(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1, 1 });
+ if (vt100_mode_origin) {
+ cursor_set(scroll_area_y0 + ps[0] - 1, ps[1] - 1);
+ } else {
+ cursor_set(ps[0] - 1, ps[1] - 1);
+ }
+ }
+
+ private void csi_CHT(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ ctrl_HT(Math.max(1, ps[0]));
+ }
+
+ private void csi_ED(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ clear(cy, cx, height, width);
+ } else if ("1".equals(ps[0])) {
+ clear(0, 0, cy + 1, cx + 1);
+ } else if ("2".equals(ps[0])) {
+ clear(0, 0, height, width);
+ }
+ }
+
+ private void csi_EL(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ clear(cy, cx, cy + 1, width);
+ } else if ("1".equals(ps[0])) {
+ clear(cy, 0, cy + 1, cx + 1);
+ } else if ("2".equals(ps[0])) {
+ clear(cy, 0, cy + 1, width);
+ }
+ }
+
+ private void csi_IL(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ if (cy >= scroll_area_y0 && cy < scroll_area_y1) {
+ scroll_area_down(cy, scroll_area_y1, Math.max(1, ps[0]));
+ }
+ }
+
+ private void csi_DL(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ if (cy >= scroll_area_y0 && cy < scroll_area_y1) {
+ scroll_area_up(cy, scroll_area_y1, Math.max(1, ps[0]));
+ }
+ }
+
+ private void csi_DCH(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_line_left(cy, cx, Math.max(1, ps[0]));
+ }
+
+ private void csi_SU(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_area_up(scroll_area_y0, scroll_area_y1, Math.max(1, ps[0]));
+ }
+
+ private void csi_SD(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ scroll_area_down(scroll_area_y0, scroll_area_y1, Math.max(1, ps[0]));
+ }
+
+ private void csi_CTC(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ for (String m : ps) {
+ if ("0".equals(m)) {
+ if (tab_stops.indexOf(cx) < 0) {
+ tab_stops.add(cx);
+ Collections.sort(tab_stops);
+ }
+ } else if ("2".equals(m)) {
+ tab_stops.remove(Integer.valueOf(cx));
+ } else if ("5".equals(m)) {
+ tab_stops = new ArrayList<Integer>();
+ }
+ }
+ }
+
+ private void csi_ECH(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ int n = Math.min(width - cx, Math.max(1, ps[0]));
+ clear(cy, cx, cy + 1, cx + n);
+ }
+
+ private void csi_CBT(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ ctrl_HT(1 - Math.max(1, ps[0]));
+ }
+
+ private void csi_HPA(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_set_x(ps[0] - 1);
+ }
+
+ private void csi_HPR(String p) {
+ csi_CUF(p);
+ }
+
+ private void csi_REP(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ if (vt100_lastchar < 32) {
+ return;
+ }
+ int n = Math.min(2000, Math.max(1, ps[0]));
+ while (n-- > 0) {
+ dumb_echo(vt100_lastchar);
+ }
+ vt100_lastchar = 0;
+ }
+
+ private void csi_DA(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ vt100_out = "\u001b[?1;2c";
+ } else if (">0".equals(ps[0]) || ">".equals(ps[0])) {
+ vt100_out = "\u001b[>0;184;0c";
+ }
+ }
+
+ private void csi_VPA(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1 });
+ cursor_set_y(ps[0] - 1);
+ }
+
+ private void csi_VPR(String p) {
+ csi_CUD(p);
+ }
+
+ private void csi_HVP(String p) {
+ csi_CUP(p);
+ }
+
+ private void csi_TBC(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("0".equals(ps[0])) {
+ csi_CTC("2");
+ } else if ("3".equals(ps[0])) {
+ csi_CTC("5");
+ }
+ }
+
+ private void csi_SM(String p) {
+ vt100_setmode(p, true);
+ }
+
+ private void csi_RM(String p) {
+ vt100_setmode(p, false);
+ }
+
+ private void csi_SGR(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 0 });
+ for (int m : ps) {
+ if (m == 0) {
+ attr = 0x00fe0000;
+ } else if (m == 1) {
+ attr |= 0x08000000;
+ } else if (m == 4) {
+ attr |= 0x01000000;
+ } else if (m == 7) {
+ attr |= 0x02000000;
+ } else if (m == 8) {
+ attr |= 0x04000000;
+ } else if (m == 24) {
+ attr &= 0x7eff0000;
+ } else if (m == 27) {
+ attr &= 0x7dff0000;
+ } else if (m == 28) {
+ attr &= 0x7bff0000;
+ } else if (m >= 30 && m <= 37) {
+ attr = (attr & 0x7f0f0000) | ((m - 30) << 20);
+ } else if (m == 39) {
+ attr = (attr & 0x7f0f0000) | 0x00f00000;
+ } else if (m >= 40 && m <= 47) {
+ attr = (attr & 0x7ff00000) | ((m - 40) << 16);
+ } else if (m == 49) {
+ attr = (attr & 0x7ff00000) | 0x000e0000;
+ }
+ }
+ }
+
+ private void csi_DSR(String p) {
+ String[] ps = vt100_parse_params(p, new String[] { "0" });
+ if ("5".equals(ps[0])) {
+ vt100_out = "\u001b[0n";
+ } else if ("6".equals(ps[0])) {
+ vt100_out = "\u001b[" + (cy + 1) + ";" + (cx + 1) + "R";
+ } else if ("7".equals(ps[0])) {
+ vt100_out = "gogo-term";
+ } else if ("8".equals(ps[0])) {
+ vt100_out = "1.0-SNAPSHOT";
+ } else if ("?6".equals(ps[0])) {
+ vt100_out = "\u001b[" + (cy + 1) + ";" + (cx + 1) + ";0R";
+ } else if ("?15".equals(ps[0])) {
+ vt100_out = "\u001b[?13n";
+ } else if ("?25".equals(ps[0])) {
+ vt100_out = "\u001b[?20n";
+ } else if ("?26".equals(ps[0])) {
+ vt100_out = "\u001b[?27;1n";
+ } else if ("?53".equals(ps[0])) {
+ vt100_out = "\u001b[?53n";
+ }
+ // ?75 : Data Integrity report
+ // ?62 : Macro Space report
+ // ?63 : Memory Checksum report
+ }
+
+ private void csi_DECSTBM(String p) {
+ int[] ps = vt100_parse_params(p, new int[] { 1, height });
+ scroll_area_set(ps[0] - 1, ps[1]);
+ if (vt100_mode_origin) {
+ cursor_set(scroll_area_y0, 0);
+ } else {
+ cursor_set(0, 0);
+ }
+ }
+
+ private void csi_SCP(@SuppressWarnings("unused") String p) {
+ vt100_saved_cx = cx;
+ vt100_saved_cy = cy;
+ }
+
+ private void csi_RCP(@SuppressWarnings("unused") String p) {
+ cx = vt100_saved_cx;
+ cy = vt100_saved_cy;
+ }
+
+ private void csi_DECREQTPARM(String p) {
+ String[] ps = vt100_parse_params(p, new String[0]);
+ if ("0".equals(ps[0])) {
+ vt100_out = "\u001b[2;1;1;112;112;1;0x";
+ } else if ("1".equals(ps[0])) {
+ vt100_out = "\u001b[3;1;1;112;112;1;0x";
+ }
+ }
+
+ private void csi_DECSTR(@SuppressWarnings("unused") String p) {
+ reset_soft();
+ }
+
+ //
+ // VT100 parser
+ //
+
+ private String[] vt100_parse_params(String p, String[] defaults) {
+ String prefix = "";
+ if (p.length() > 0) {
+ if (p.charAt(0) >= '<' && p.charAt(0) <= '?') {
+ prefix = "" + p.charAt(0);
+ p = p.substring(1);
+ }
+ }
+ String[] ps = p.split(";");
+ int n = Math.max(ps.length, defaults.length);
+ String[] values = new String[n];
+ for (int i = 0; i < n; i++) {
+ String value = null;
+ if (i < ps.length && ps[i].length() > 0) {
+ value = prefix + ps[i];
+ }
+ if (value == null && i < defaults.length) {
+ value = defaults[i];
+ }
+ if (value == null) {
+ value = "";
+ }
+ values[i] = value;
+ }
+ return values;
+ }
+
+ private int[] vt100_parse_params(String p, int[] defaults) {
+ String prefix = "";
+ p = p == null ? "" : p;
+ if (p.length() > 0) {
+ if (p.charAt(0) >= '<' && p.charAt(0) <= '?') {
+ prefix = p.substring(0, 1);
+ p = p.substring(1);
+ }
+ }
+ String[] ps = p.split(";");
+ int n = Math.max(ps.length, defaults.length);
+ int[] values = new int[n];
+ for (int i = 0; i < n; i++) {
+ Integer value = null;
+ if (i < ps.length) {
+ String v = prefix + ps[i];
+ try {
+ value = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ }
+ }
+ if (value == null && i < defaults.length) {
+ value = defaults[i];
+ }
+ if (value == null) {
+ value = 0;
+ }
+ values[i] = value;
+ }
+ return values;
+ }
+
+ private void vt100_parse_reset() {
+ vt100_parse_reset(State.None);
+ }
+
+ private void vt100_parse_reset(State state) {
+ vt100_parse_state = state;
+ vt100_parse_len = 0;
+ vt100_parse_func = 0;
+ vt100_parse_param = "";
+ }
+
+ private void vt100_parse_process() {
+ if (vt100_parse_state == State.Esc) {
+ switch (vt100_parse_func) {
+ case 0x0036: /* DECBI */ break;
+ case 0x0037: esc_DECSC(); break;
+ case 0x0038: esc_DECRC(); break;
+ case 0x0042: /* BPH */ break;
+ case 0x0043: /* NBH */ break;
+ case 0x0044: esc_IND(); break;
+ case 0x0045: esc_NEL(); break;
+ case 0x0046: /* SSA */ esc_NEL(); break;
+ case 0x0048: esc_HTS(); break;
+ case 0x0049: /* HTJ */ break;
+ case 0x004A: /* VTS */ break;
+ case 0x004B: /* PLD */ break;
+ case 0x004C: /* PLU */ break;
+ case 0x004D: esc_RI(); break;
+ case 0x004E: esc_SS2(); break;
+ case 0x004F: esc_SS3(); break;
+ case 0x0050: esc_DCS(); break;
+ case 0x0051: /* PU1 */ break;
+ case 0x0052: /* PU2 */ break;
+ case 0x0053: /* STS */ break;
+ case 0x0054: /* CCH */ break;
+ case 0x0055: /* MW */ break;
+ case 0x0056: /* SPA */ break;
+ case 0x0057: /* ESA */ break;
+ case 0x0058: esc_SOS(); break;
+ case 0x005A: /* SCI */ break;
+ case 0x005B: esc_CSI(); break;
+ case 0x005C: esc_ST(); break;
+ case 0x005D: esc_OSC(); break;
+ case 0x005E: esc_PM(); break;
+ case 0x005F: esc_APC(); break;
+ case 0x0060: /* DMI */ break;
+ case 0x0061: /* INT */ break;
+ case 0x0062: /* EMI */ break;
+ case 0x0063: esc_RIS(); break;
+ case 0x0064: /* CMD */ break;
+ case 0x006C: /* RM */ break;
+ case 0x006E: /* LS2 */ break;
+ case 0x006F: /* LS3 */ break;
+ case 0x007C: /* LS3R */ break;
+ case 0x007D: /* LS2R */ break;
+ case 0x007E: /* LS1R */ break;
+ case 0x2338: esc_DECALN(); break;
+ case 0x2841: esc_G0_0(); break;
+ case 0x2842: esc_G0_1(); break;
+ case 0x2830: esc_G0_2(); break;
+ case 0x2831: esc_G0_3(); break;
+ case 0x2832: esc_G0_4(); break;
+ case 0x2930: esc_G1_2(); break;
+ case 0x2931: esc_G1_3(); break;
+ case 0x2932: esc_G1_4(); break;
+ case 0x2941: esc_G1_0(); break;
+ case 0x2942: esc_G1_1(); break;
+ }
+ if (vt100_parse_state == State.Esc) {
+ vt100_parse_reset();
+ }
+ } else {
+ switch (vt100_parse_func) {
+ case 0x0040: csi_ICH(vt100_parse_param); break;
+ case 0x0041: csi_CUU(vt100_parse_param); break;
+ case 0x0042: csi_CUD(vt100_parse_param); break;
+ case 0x0043: csi_CUF(vt100_parse_param); break;
+ case 0x0044: csi_CUB(vt100_parse_param); break;
+ case 0x0045: csi_CNL(vt100_parse_param); break;
+ case 0x0046: csi_CPL(vt100_parse_param); break;
+ case 0x0047: csi_CHA(vt100_parse_param); break;
+ case 0x0048: csi_CUP(vt100_parse_param); break;
+ case 0x0049: csi_CHT(vt100_parse_param); break;
+ case 0x004A: csi_ED(vt100_parse_param); break;
+ case 0x004B: csi_EL(vt100_parse_param); break;
+ case 0x004C: csi_IL(vt100_parse_param); break;
+ case 0x004D: csi_DL(vt100_parse_param); break;
+ case 0x004E: /* EF */ break;
+ case 0x004F: /* EA */ break;
+ case 0x0050: csi_DCH(vt100_parse_param); break;
+ case 0x0051: /* SEE */ break;
+ case 0x0052: /* CPR */ break;
+ case 0x0053: csi_SU(vt100_parse_param); break;
+ case 0x0054: csi_SD(vt100_parse_param); break;
+ case 0x0055: /* NP */ break;
+ case 0x0056: /* PP */ break;
+ case 0x0057: csi_CTC(vt100_parse_param); break;
+ case 0x0058: csi_ECH(vt100_parse_param); break;
+ case 0x0059: /* CVT */ break;
+ case 0x005A: csi_CBT(vt100_parse_param); break;
+ case 0x005B: /* SRS */ break;
+ case 0x005C: /* PTX */ break;
+ case 0x005D: /* SDS */ break;
+ case 0x005E: /* SIMD */ break;
+ case 0x0060: csi_HPA(vt100_parse_param); break;
+ case 0x0061: csi_HPR(vt100_parse_param); break;
+ case 0x0062: csi_REP(vt100_parse_param); break;
+ case 0x0063: csi_DA(vt100_parse_param); break;
+ case 0x0064: csi_VPA(vt100_parse_param); break;
+ case 0x0065: csi_VPR(vt100_parse_param); break;
+ case 0x0066: csi_HVP(vt100_parse_param); break;
+ case 0x0067: csi_TBC(vt100_parse_param); break;
+ case 0x0068: csi_SM(vt100_parse_param); break;
+ case 0x0069: /* MC */ break;
+ case 0x006A: /* HPB */ break;
+ case 0x006B: /* VPB */ break;
+ case 0x006C: csi_RM(vt100_parse_param); break;
+ case 0x006D: csi_SGR(vt100_parse_param); break;
+ case 0x006E: csi_DSR(vt100_parse_param); break;
+ case 0x006F: /* DAQ */ break;
+ case 0x0072: csi_DECSTBM(vt100_parse_param); break;
+ case 0x0073: csi_SCP(vt100_parse_param); break;
+ case 0x0075: csi_RCP(vt100_parse_param); break;
+ case 0x0078: csi_DECREQTPARM(vt100_parse_param); break;
+ case 0x2040: /* SL */ break;
+ case 0x2041: /* SR */ break;
+ case 0x2042: /* GSM */ break;
+ case 0x2043: /* GSS */ break;
+ case 0x2044: /* FNT */ break;
+ case 0x2045: /* TSS */ break;
+ case 0x2046: /* JFY */ break;
+ case 0x2047: /* SPI */ break;
+ case 0x2048: /* QUAD */ break;
+ case 0x2049: /* SSU */ break;
+ case 0x204A: /* PFS */ break;
+ case 0x204B: /* SHS */ break;
+ case 0x204C: /* SVS */ break;
+ case 0x204D: /* IGS */ break;
+ case 0x204E: /* deprecated: HTSA */ break;
+ case 0x204F: /* IDCS */ break;
+ case 0x2050: /* PPA */ break;
+ case 0x2051: /* PPR */ break;
+ case 0x2052: /* PPB */ break;
+ case 0x2053: /* SPD */ break;
+ case 0x2054: /* DTA */ break;
+ case 0x2055: /* SLH */ break;
+ case 0x2056: /* SLL */ break;
+ case 0x2057: /* FNK */ break;
+ case 0x2058: /* SPQR */ break;
+ case 0x2059: /* SEF */ break;
+ case 0x205A: /* PEC */ break;
+ case 0x205B: /* SSW */ break;
+ case 0x205C: /* SACS */ break;
+ case 0x205D: /* SAPV */ break;
+ case 0x205E: /* STAB */ break;
+ case 0x205F: /* GCC */ break;
+ case 0x2060: /* TAPE */ break;
+ case 0x2061: /* TALE */ break;
+ case 0x2062: /* TAC */ break;
+ case 0x2063: /* TCC */ break;
+ case 0x2064: /* TSR */ break;
+ case 0x2065: /* SCO */ break;
+ case 0x2066: /* SRCS */ break;
+ case 0x2067: /* SCS */ break;
+ case 0x2068: /* SLS */ break;
+ case 0x2069: /* SPH */ break;
+ case 0x206A: /* SPL */ break;
+ case 0x206B: /* SCP */ break;
+ case 0x2170: csi_DECSTR(vt100_parse_param); break;
+ case 0x2472: /* DECCARA */ break;
+ case 0x2477: /* DECRQPSR */ break;
+ }
+ if (vt100_parse_state == State.Csi) {
+ vt100_parse_reset();
+ }
+ }
+ }
+
+ private boolean vt100_write(int c) {
+ if (c < 32) {
+ if (c == 27) {
+ vt100_parse_reset(State.Esc);
+ return true;
+ } else if (c == 14) {
+ ctrl_SO();
+ } else if (c == 15) {
+ ctrl_SI();
+ }
+ } else if ((c & 0xffe0) == 0x0080) {
+ vt100_parse_reset(State.Esc);
+ vt100_parse_func = (char)(c - 0x0040);
+ vt100_parse_process();
+ return true;
+ }
+ if (vt100_parse_state != State.None) {
+ if (vt100_parse_state == State.Str) {
+ if (c >= 32) {
+ return true;
+ }
+ vt100_parse_reset();
+ } else {
+ if (c < 32) {
+ if (c == 24 || c == 26) {
+ vt100_parse_reset();
+ return true;
+ }
+ } else {
+ vt100_parse_len += 1;
+ if (vt100_parse_len > 32) {
+ vt100_parse_reset();
+ } else {
+ int msb = c & 0xf0;
+ if (msb == 0x20) {
+ vt100_parse_func <<= 8;
+ vt100_parse_func += (char) c;
+ } else if (msb == 0x30 && vt100_parse_state == State.Csi) {
+ vt100_parse_param += new String(new char[] { (char) c } );
+ } else {
+ vt100_parse_func <<= 8;
+ vt100_parse_func += (char) c;
+ vt100_parse_process();
+ }
+ return true;
+ }
+ }
+ }
+ }
+ vt100_lastchar = c;
+ return false;
+ }
+
+ //
+ // Dirty
+ //
+
+ private synchronized void setDirty() {
+ dirty.set(true);
+ notifyAll();
+ }
+
+ //
+ // External interface
+ //
+
+ public synchronized boolean setSize(int w, int h) {
+ if (w < 2 || w > 256 || h < 2 || h > 256) {
+ return false;
+ }
+ this.width = w;
+ this.height = h;
+ reset_screen();
+ return true;
+ }
+
+ public synchronized String read() {
+ String d = vt100_out;
+ vt100_out = "";
+ return d;
+ }
+
+ public synchronized String pipe(String d) {
+ String o = "";
+ for (char c : d.toCharArray()) {
+ if (vt100_keyfilter_escape) {
+ vt100_keyfilter_escape = false;
+ if (vt100_mode_cursorkey) {
+ switch (c) {
+ case '~': o += "~"; break;
+ case 'A': o += "\u001bOA"; break;
+ case 'B': o += "\u001bOB"; break;
+ case 'C': o += "\u001bOC"; break;
+ case 'D': o += "\u001bOD"; break;
+ case 'F': o += "\u001bOF"; break;
+ case 'H': o += "\u001bOH"; break;
+ case '1': o += "\u001b[5~"; break;
+ case '2': o += "\u001b[6~"; break;
+ case '3': o += "\u001b[2~"; break;
+ case '4': o += "\u001b[3~"; break;
+ case 'a': o += "\u001bOP"; break;
+ case 'b': o += "\u001bOQ"; break;
+ case 'c': o += "\u001bOR"; break;
+ case 'd': o += "\u001bOS"; break;
+ case 'e': o += "\u001b[15~"; break;
+ case 'f': o += "\u001b[17~"; break;
+ case 'g': o += "\u001b[18~"; break;
+ case 'h': o += "\u001b[19~"; break;
+ case 'i': o += "\u001b[20~"; break;
+ case 'j': o += "\u001b[21~"; break;
+ case 'k': o += "\u001b[23~"; break;
+ case 'l': o += "\u001b[24~"; break;
+ }
+ } else {
+ switch (c) {
+ case '~': o += "~"; break;
+ case 'A': o += "\u001b[A"; break;
+ case 'B': o += "\u001b[B"; break;
+ case 'C': o += "\u001b[C"; break;
+ case 'D': o += "\u001b[D"; break;
+ case 'F': o += "\u001b[F"; break;
+ case 'H': o += "\u001b[H"; break;
+ case '1': o += "\u001b[5~"; break;
+ case '2': o += "\u001b[6~"; break;
+ case '3': o += "\u001b[2~"; break;
+ case '4': o += "\u001b[3~"; break;
+ case 'a': o += "\u001bOP"; break;
+ case 'b': o += "\u001bOQ"; break;
+ case 'c': o += "\u001bOR"; break;
+ case 'd': o += "\u001bOS"; break;
+ case 'e': o += "\u001b[15~"; break;
+ case 'f': o += "\u001b[17~"; break;
+ case 'g': o += "\u001b[18~"; break;
+ case 'h': o += "\u001b[19~"; break;
+ case 'i': o += "\u001b[20~"; break;
+ case 'j': o += "\u001b[21~"; break;
+ case 'k': o += "\u001b[23~"; break;
+ case 'l': o += "\u001b[24~"; break;
+ }
+ }
+ } else if (c == '~') {
+ vt100_keyfilter_escape = true;
+ } else if (c == 127) {
+ if (vt100_mode_backspace) {
+ o += (char) 8;
+ } else {
+ o += (char) 127;
+ }
+ } else {
+ o += c;
+ if (vt100_mode_lfnewline && c == 13) {
+ o += (char) 10;
+ }
+ }
+ }
+ return o;
+ }
+
+ public synchronized boolean write(String d) {
+ d = utf8_decode(d);
+ for (int c : d.toCharArray()) {
+ if (vt100_write(c)) {
+ continue;
+ }
+ if (dumb_write(c)) {
+ continue;
+ }
+ if (c <= 0xffff) {
+ dumb_echo(c);
+ }
+ }
+ return true;
+ }
+
+ public synchronized String dump(long timeout, boolean forceDump) throws InterruptedException {
+ if (!dirty.get() && timeout > 0) {
+ wait(timeout);
+ }
+ if (dirty.compareAndSet(true, false) || forceDump) {
+ StringBuilder sb = new StringBuilder();
+ int prev_attr = -1;
+ int cx = Math.min(this.cx, width - 1);
+ int cy = this.cy;
+ sb.append("<div><pre class='term'>");
+ for (int y = 0; y < height; y++) {
+ int wx = 0;
+ for (int x = 0; x < width; x++) {
+ int d = screen[y * width + x];
+ int c = d & 0xffff;
+ int a = d >> 16;
+ if (cy == y && cx == x && vt100_mode_cursor) {
+ a = a & 0xfff0 | 0x000c;
+ }
+ if (a != prev_attr) {
+ if (prev_attr != -1) {
+ sb.append("</span>");
+ }
+ int bg = a & 0x000f;
+ int fg = (a & 0x00f0) >> 4;
+ boolean inv = (a & 0x0200) != 0;
+ boolean inv2 = vt100_mode_inverse;
+ if (inv && !inv2 || inv2 && !inv) {
+ int i = fg; fg = bg; bg = i;
+ }
+ if ((a & 0x0400) != 0) {
+ fg = 0x0c;
+ }
+ String ul;
+ if ((a & 0x0100) != 0) {
+ ul = " ul";
+ } else {
+ ul = "";
+ }
+ String b;
+ if ((a & 0x0800) != 0) {
+ b = " b";
+ } else {
+ b = "";
+ }
+ sb.append("<span class='f").append(fg).append(" b").append(bg).append(ul).append(b).append("'>");
+ prev_attr = a;
+ }
+ switch (c) {
+ case '&': sb.append("&"); break;
+ case '<': sb.append("<"); break;
+ case '>': sb.append(">"); break;
+ default:
+ wx += utf8_charwidth(c);
+ if (wx <= width) {
+ sb.append((char) c);
+ }
+ break;
+ }
+ }
+ sb.append("\n");
+ }
+ sb.append("</span></pre></div>");
+ return sb.toString();
+ }
+ return null;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ sb.append((char) (screen[y * width + x] & 0xffff));
+ }
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+}
diff --git a/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/WebTerminal.java b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/WebTerminal.java
new file mode 100644
index 0000000..b47553e
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/java/org/apache/felix/webconsole/plugins/gogo/impl/WebTerminal.java
@@ -0,0 +1,46 @@
+/*
+ * 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.webconsole.plugins.gogo.impl;
+
+import jline.TerminalSupport;
+
+public class WebTerminal extends TerminalSupport {
+
+ private int width;
+ private int height;
+
+ public WebTerminal(int width, int height) {
+ super(true);
+ this.width = width;
+ this.height = height;
+ }
+
+ public void init() throws Exception {
+ }
+
+ public void restore() throws Exception {
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+}
diff --git a/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.css b/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.css
new file mode 100644
index 0000000..f693de7
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.css
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+/**
+ * Based on http://antony.lesuisse.org/software/ajaxterm/
+ * Public Domain License
+ */
+
+div#console {
+ font-size: 12px;
+ margin: 12px;
+}
+
+div#term {
+ display: inline-block;
+}
+
+pre.stat {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background-color: black;
+ border-top: 1px solid black;
+ color: white;
+}
+pre.stat span {
+ padding: 0px;
+}
+pre.stat .on {
+ background-color: #080;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.stat .off {
+ background-color: #888;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.term {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background:#000;
+ border-top: 1px solid white;
+ color: #eee;
+}
+pre.term span.f0 { color: #000000; }
+pre.term span.f1 { color: #c00006; }
+pre.term span.f2 { color: #1bc806; }
+pre.term span.f3 { color: #c3c609; }
+pre.term span.f4 { color: #0000c2; }
+pre.term span.f5 { color: #bf00c2; }
+pre.term span.f6 { color: #19c4c2; }
+pre.term span.f7 { color: #f2f2f2; }
+pre.term span.f12 { color: transparent; }
+pre.term span.f14 { color: #000000; }
+pre.term span.f15 { color: #bbbbbb; }
+pre.term span.b0 { background-color: #000000; }
+pre.term span.b1 { background-color: #cc2300; }
+pre.term span.b2 { background-color: #00cc00; }
+pre.term span.b3 { background-color: #cccc00; }
+pre.term span.b4 { background-color: #0e2acc; }
+pre.term span.b5 { background-color: #cc34cc; }
+pre.term span.b6 { background-color: #00cccc; }
+pre.term span.b7 { background-color: #f5f5f5; }
+pre.term span.b12 { background-color: #555555; }
+pre.term span.b14 { background-color: transparent; }
+pre.term span.b15 { background-color: #ffffff; }
+pre.term span.ul { text-decoration: underline; }
+pre.term span.b { font-weight:900; }
+
diff --git a/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js b/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js
new file mode 100644
index 0000000..77044a7
--- /dev/null
+++ b/webconsole-plugins/gogo/src/main/resources/res/ui/gogo.js
@@ -0,0 +1,245 @@
+//
+// 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.
+//
+
+//
+// Based on http://antony.lesuisse.org/software/ajaxterm/
+// Public Domain License
+//
+
+gogo = { };
+
+gogo.Terminal_ctor = function(div, width, height) {
+
+ var query0 = "w=" + width + "&h=" + height;
+ var query1 = query0 + "&k=";
+ var buf = "";
+ var timeout;
+ var error_timeout;
+ var keybuf = [];
+ var sending = 0;
+ var rmax = 1;
+ var force = 1;
+
+ var dstat = document.createElement('pre');
+ var sled = document.createElement('span');
+ var sdebug = document.createElement('span');
+ var dterm = document.createElement('div');
+
+ function debug(s) {
+ sdebug.innerHTML = s;
+ }
+
+ function error() {
+ sled.className = 'off';
+ debug("Connection lost timeout ts:" + ((new Date).getTime()));
+ }
+
+ function update() {
+ if (sending == 0) {
+ sending = 1;
+ sled.className = 'on';
+ var r = new XMLHttpRequest();
+ var send = "";
+ while (keybuf.length > 0) {
+ send += keybuf.pop();
+ }
+ var query = query1 + send;
+ if (force) {
+ query = query + "&f=1";
+ force = 0;
+ }
+ r.open("POST", "gogo", true);
+ r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ r.onreadystatechange = function () {
+ if (r.readyState == 4) {
+ if (r.status == 200) {
+ window.clearTimeout(error_timeout);
+ if (r.responseText.length > 0) {
+ dterm.innerHTML = r.responseText;
+ rmax = 100;
+ } else {
+ rmax *= 2;
+ if (rmax > 2000)
+ rmax = 2000;
+ }
+ sending=0;
+ sled.className = 'off';
+ timeout = window.setTimeout(update, rmax);
+ } else {
+ debug("Connection error status:" + r.status);
+ }
+ }
+ }
+ error_timeout = window.setTimeout(error, 5000);
+ r.send(query);
+ }
+ }
+
+ function queue(s) {
+ keybuf.unshift(s);
+ if (sending == 0) {
+ window.clearTimeout(timeout);
+ timeout = window.setTimeout(update, 1);
+ }
+ }
+
+ function keypress(ev, fromkeydown) {
+ // Translate to standard keycodes
+ if (!ev)
+ ev = window.event;
+ var kc;
+ if (ev.keyCode)
+ kc = ev.keyCode;
+ if (!fromkeydown && ev.which)
+ kc = ev.which;
+ if (ev.ctrlKey) {
+ if (kc >= 0 && kc <= 32)
+ kc = kc;
+ else if (kc >= 65 && kc <= 90)
+ kc -= 64;
+ else if (kc >= 97 && kc <= 122)
+ kc -= 96;
+ else {
+ switch (kc) {
+ case 54: kc=30; break; // Ctrl-^
+ case 109: kc=31; break; // Ctrl-_
+ case 219: kc=27; break; // Ctrl-[
+ case 220: kc=28; break; // Ctrl-\
+ case 221: kc=29; break; // Ctrl-]
+ default: return true;
+ }
+ }
+ } else if (fromkeydown) {
+ switch(kc) {
+ case 8: break; // Backspace
+ case 9: break; // Tab
+ case 27: break; // ESC
+ case 33: kc = 63276; break; // PgUp
+ case 34: kc = 63277; break; // PgDn
+ case 35: kc = 63275; break; // End
+ case 36: kc = 63273; break; // Home
+ case 37: kc = 63234; break; // Left
+ case 38: kc = 63232; break; // Up
+ case 39: kc = 63235; break; // Right
+ case 40: kc = 63233; break; // Down
+ case 45: kc = 63302; break; // Ins
+ case 46: kc = 63272; break; // Del
+ case 112: kc = 63236; break; // F1
+ case 113: kc = 63237; break; // F2
+ case 114: kc = 63238; break; // F3
+ case 115: kc = 63239; break; // F4
+ case 116: kc = 63240; break; // F5
+ case 117: kc = 63241; break; // F6
+ case 118: kc = 63242; break; // F7
+ case 119: kc = 63243; break; // F8
+ case 120: kc = 63244; break; // F9
+ case 121: kc = 63245; break; // F10
+ case 122: kc = 63246; break; // F11
+ case 123: kc = 63247; break; // F12
+ default: return true;
+ }
+ }
+
+ var k = "";
+ // Build character
+ switch (kc) {
+ case 126: k = "~~"; break;
+ case 63232: k = "~A"; break; // Up
+ case 63233: k = "~B"; break; // Down
+ case 63234: k = "~D"; break; // Left
+ case 63235: k = "~C"; break; // Right
+ case 63276: k = "~1"; break; // PgUp
+ case 63277: k = "~2"; break; // PgDn
+ case 63273: k = "~H"; break; // Home
+ case 63275: k = "~F"; break; // End
+ case 63302: k = "~3"; break; // Ins
+ case 63272: k = "~4"; break; // Del
+ case 63236: k = "~a"; break; // F1
+ case 63237: k = "~b"; break; // F2
+ case 63238: k = "~c"; break; // F3
+ case 63239: k = "~d"; break; // F4
+ case 63240: k = "~e"; break; // F5
+ case 63241: k = "~f"; break; // F6
+ case 63242: k = "~g"; break; // F7
+ case 63243: k = "~h"; break; // F8
+ case 63244: k = "~i"; break; // F9
+ case 63245: k = "~j"; break; // F10
+ case 63246: k = "~k"; break; // F11
+ case 63247: k = "~l"; break; // F12
+ default: k = String.fromCharCode(kc); break;
+ }
+
+// debug("fromkeydown=" + fromkeydown + ", ev.keyCode=" + ev.keyCode + ", " +
+// "ev.which=" + ev.which + ", ev.ctrlKey=" + ev.ctrlKey + ", " +
+// "kc=" + kc + ", k=" + k);
+
+ queue(encodeURIComponent(k));
+
+ ev.cancelBubble = true;
+ if (ev.stopPropagation) ev.stopPropagation();
+ if (ev.preventDefault) ev.preventDefault();
+
+ return true;
+ }
+
+ function keydown(ev) {
+ if (!ev)
+ ev = window.event;
+ o = { 9:1, 8:1, 27:1, 33:1, 34:1, 35:1, 36:1, 37:1, 38:1, 39:1, 40:1, 45:1, 46:1, 112:1,
+ 113:1, 114:1, 115:1, 116:1, 117:1, 118:1, 119:1, 120:1, 121:1, 122:1, 123:1 };
+ if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
+ keypress(ev, true);
+ }
+ }
+
+ function init() {
+ if (typeof(XMLHttpRequest) == "undefined") {
+ XMLHttpRequest = function() {
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
+ catch(e) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
+ catch(e) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); }
+ catch(e) {}
+ try { return new ActiveXObject("Microsoft.XMLHTTP"); }
+ catch(e) {}
+ throw new Error("This browser does not support XMLHttpRequest.");
+ };
+ }
+ sled.appendChild(document.createTextNode('\xb7'));
+ sled.className = 'off';
+ dstat.appendChild(sled);
+ dstat.appendChild(document.createTextNode(' '));
+ dstat.appendChild(sdebug);
+ dstat.className = 'stat';
+ div.appendChild(dstat);
+ var d = document.createElement('div');
+ d.appendChild(dterm);
+ div.appendChild(d);
+ document.onkeypress = keypress;
+ document.onkeydown = keydown;
+ timeout = window.setTimeout(update, 100);
+ }
+
+ init();
+
+}
+
+gogo.Terminal = function(div, width, height) {
+ return new this.Terminal_ctor(div, width, height);
+}
+