Add a JLine based shell
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1735995 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/DEPENDENCIES b/gogo/jline/DEPENDENCIES
new file mode 100644
index 0000000..664b8ac
--- /dev/null
+++ b/gogo/jline/DEPENDENCIES
@@ -0,0 +1,20 @@
+Apache Felix Gogo Shell
+Copyright 2011 The Apache Software Foundation
+
+This software was developed at the Apache Software Foundation
+(http://www.apache.org) and may have dependencies on other
+Apache software licensed under Apache License 2.0.
+
+I. Included Third-Party Software
+
+None.
+
+II. Used Third-Party 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/gogo/jline/LICENSE b/gogo/jline/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/gogo/jline/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/gogo/jline/NOTICE b/gogo/jline/NOTICE
new file mode 100644
index 0000000..4007bd3
--- /dev/null
+++ b/gogo/jline/NOTICE
@@ -0,0 +1,6 @@
+Apache Felix Gogo Shell
+Copyright 2011 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
diff --git a/gogo/jline/doc/changelog.txt b/gogo/jline/doc/changelog.txt
new file mode 100644
index 0000000..0f7c8dc
--- /dev/null
+++ b/gogo/jline/doc/changelog.txt
@@ -0,0 +1,67 @@
+Changes from 0.8.0 to 0.10.0
+----------------------------
+
+** Improvement
+ * Added gosh_profile work around for issue with OSGi R4.3 API
+ ambiguity.
+
+Changes from 0.6.1 to 0.8.0
+---------------------------
+
+** Bug
+ * [FELIX-2651] - [Gogo] MOTD formatting is broken under Windows
+
+** Improvement
+ * [FELIX-2661] - [Gogo] It should be easier to start Gogo shell
+ non-interactively
+
+** New Feature
+ * [FELIX-2767] - gogo telnet IP address
+
+Changes from 0.6.0 to 0.6.1
+---------------------------
+
+** Bug
+ * [FELIX-2446] - [Gogo] The bundle context command is not used with a
+ scope in gosh_profile
+ * [FELIX-2477] - [gogo] shell procedural commands don't inherit closure
+ arguments
+
+** Improvement
+ * [FELIX-2445] - [Gogo] Default gosh_profile should be updated to use
+ system bundle to load java.lang.System
+ * [FELIX-2543] - [Gogo] Should avoid using System.getProperty() to get
+ configuration properties
+
+Gogo Shell 0.6.0
+----------------
+
+** Bug
+ * [FELIX-1473] - [gogo] The syntax does not provide a way to call methods
+ on a string
+ * [FELIX-1474] - [gogo] result of commands is implicitly written to pipe
+ * [FELIX-1493] - [gogo] automatic expansion of $args in Closure stops
+ direct access to $args list
+ * [FELIX-2337] - [gogo] no way to access array[] elements produced by
+ assignment
+ * [FELIX-2375] - [gogo] when supplied args can't be coerced, the error
+ message prints the arg values, rather than their types
+ * [FELIX-2380] - [gogo] lock contention in piped writer when reader
+ doesn't read all input
+
+** Improvement
+ * [FELIX-1487] - Support for commands on multiple lines
+ * [FELIX-2328] - [gogo] tidy-up runtime to remove optional code etc
+ * [FELIX-2339] - [gogo] add support for running scripts
+ * [FELIX-2342] - [gogo] remove old felix command adaptor
+
+** New Feature
+ * [FELIX-2363] - [Gogo] Add annotations for creating commands with
+ optional and out-of-order arguments
+
+** Task
+ * [FELIX-1670] - [gogo] launcher bundle not required
+ * [FELIX-1889] - Gogo should depend on the official OSGi jars
+ * [FELIX-2334] - [Gogo] Use org.apache.felix as Maven groupId
+ * [FELIX-2367] - [Gogo] Use org.apache.felix namespace to avoid any
+ perceived legal issues
diff --git a/gogo/jline/pom.xml b/gogo/jline/pom.xml
new file mode 100644
index 0000000..ecb67b1
--- /dev/null
+++ b/gogo/jline/pom.xml
@@ -0,0 +1,111 @@
+<!--
+ 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">
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>gogo-parent</artifactId>
+ <version>0.6.0</version>
+ <relativePath>../gogo-parent/pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Gogo JLine Shell</name>
+ <artifactId>org.apache.felix.gogo.jline</artifactId>
+ <version>0.11.0-SNAPSHOT</version>
+ <properties>
+ <!-- Skip because of NPE -->
+ <animal.sniffer.skip>true</animal.sniffer.skip>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.2.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.apache.felix</groupId>
+ <artifactId>org.apache.felix.gogo.runtime</artifactId>
+ <version>0.16.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>jline</groupId>
+ <artifactId>jline</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-core</artifactId>
+ <version>1.0.0</version>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ </Export-Package>
+ <Import-Package>
+ org.apache.felix.service.command; status="provisional",
+ *
+ </Import-Package>
+ <Private-Package>
+ org.apache.felix.gogo.shell,
+ org.apache.felix.gogo.options
+ </Private-Package>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+ <Bundle-Activator>org.apache.felix.gogo.jline.Activator</Bundle-Activator>
+ <Include-Resource>{maven-resources},META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_removeheaders>Private-Package,Ignore-Package,Include-Resource</_removeheaders>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <param>src/main/resources/motd</param>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
new file mode 100644
index 0000000..ca19dd2
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.jline.telnet.Telnet;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Activator implements BundleActivator {
+ private BundleContext context;
+ private ServiceTracker commandProcessorTracker;
+ private Set<ServiceRegistration> regs;
+
+ private ExecutorService executor;
+
+ public Activator() {
+ regs = new HashSet<ServiceRegistration>();
+ }
+
+ public void start(BundleContext context) throws Exception {
+ this.context = context;
+ this.commandProcessorTracker = createCommandProcessorTracker();
+ this.commandProcessorTracker.open();
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ Iterator<ServiceRegistration> iterator = regs.iterator();
+ while (iterator.hasNext()) {
+ ServiceRegistration reg = iterator.next();
+ reg.unregister();
+ iterator.remove();
+ }
+
+ stopShell();
+
+ this.commandProcessorTracker.close();
+ }
+
+ private ServiceTracker createCommandProcessorTracker() {
+ return new ServiceTracker(context, CommandProcessor.class.getName(), null) {
+ @Override
+ public Object addingService(ServiceReference reference) {
+ CommandProcessor processor = (CommandProcessor) super.addingService(reference);
+ startShell(context, processor);
+ return processor;
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service) {
+ stopShell();
+ super.removedService(reference, service);
+ }
+ };
+ }
+
+ private void startShell(final BundleContext context, CommandProcessor processor) {
+ Dictionary<String, Object> dict = new Hashtable<String, Object>();
+ dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
+
+ // register converters
+ regs.add(context.registerService(Converter.class.getName(), new Converters(context.getBundle(0).getBundleContext()), null));
+
+ // register commands
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Builtin.functions);
+ regs.add(context.registerService(Builtin.class.getName(), new Builtin(), dict));
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Procedural.functions);
+ regs.add(context.registerService(Procedural.class.getName(), new Procedural(), dict));
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Posix.functions);
+ regs.add(context.registerService(Posix.class.getName(), new Posix(), dict));
+
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Telnet.functions);
+ regs.add(context.registerService(Telnet.class.getName(), new Telnet(processor), dict));
+
+ Shell shell = new Shell(new ShellContext(), processor, null);
+ dict.put(CommandProcessor.COMMAND_FUNCTION, Shell.functions);
+ regs.add(context.registerService(Shell.class.getName(), shell, dict));
+
+ // start shell on a separate thread...
+ executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+ public Thread newThread(Runnable runnable) {
+ return new Thread(runnable, "Gogo shell");
+ }
+ });
+ executor.submit(new StartShellJob(context, processor));
+ }
+
+ private void stopShell() {
+ if (executor != null && !(executor.isShutdown() || executor.isTerminated())) {
+ executor.shutdownNow();
+
+ try {
+ if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+ System.err.println("!!! FAILED TO STOP EXECUTOR !!!");
+ }
+ } catch (InterruptedException e) {
+ // Restore administration...
+ Thread.currentThread().interrupt();
+ }
+ executor = null;
+ }
+ }
+
+ private static class StartShellJob implements Runnable {
+ private final BundleContext context;
+ private final CommandProcessor processor;
+
+ public StartShellJob(BundleContext context, CommandProcessor processor) {
+ this.context = context;
+ this.processor = processor;
+ }
+
+ public void run() {
+ CommandSession session = processor.createSession(System.in, System.out, System.err);
+ try {
+ // wait for gosh command to be registered
+ for (int i = 0; (i < 100) && session.get("gogo:gosh") == null; ++i) {
+ TimeUnit.MILLISECONDS.sleep(10);
+ }
+
+ String args = context.getProperty("gosh.args");
+ args = (args == null) ? "" : args;
+ session.execute("gosh --login " + args);
+ } catch (Exception e) {
+ Object loc = session.get(".location");
+ if (null == loc || !loc.toString().contains(":")) {
+ loc = "gogo";
+ }
+
+ System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ session.close();
+ }
+ }
+ }
+
+ private class ShellContext implements Context {
+ public String getProperty(String name) {
+ return context.getProperty(name);
+ }
+
+ public void exit() throws Exception {
+ context.getBundle(0).stop();
+ }
+ }
+}
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
new file mode 100644
index 0000000..a4fc3ef
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
@@ -0,0 +1,498 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.jline.builtins.Options;
+
+/**
+ * gosh built-in commands.
+ */
+public class Builtin {
+
+ static final String[] functions = {"format", "getopt", "new", "set", "tac", "type"};
+
+ private static final String[] packages = {"java.lang", "java.io", "java.net",
+ "java.util"};
+ private final static Set<String> KEYWORDS = new HashSet<String>(
+ Arrays.asList(new String[]{"abstract", "continue", "for", "new", "switch",
+ "assert", "default", "goto", "package", "synchronized", "boolean", "do",
+ "if", "private", "this", "break", "double", "implements", "protected",
+ "throw", "byte", "else", "import", "public", "throws", "case", "enum",
+ "instanceof", "return", "transient", "catch", "extends", "int", "short",
+ "try", "char", "final", "interface", "static", "void", "class",
+ "finally", "long", "strictfp", "volatile", "const", "float", "native",
+ "super", "while"}));
+
+ @SuppressWarnings("unchecked")
+ static Set<String> getCommands(CommandSession session) {
+ return (Set<String>) session.get(".commands");
+ }
+
+ public CharSequence format(CommandSession session) {
+ return format(session, session.get("_")); // last result
+ }
+
+ public CharSequence format(CommandSession session, Object arg) {
+ CharSequence result = session.format(arg, Converter.INSPECT);
+ System.out.println(result);
+ return result;
+ }
+
+ /**
+ * script access to Options.
+ */
+ public Options getopt(List<Object> spec, Object[] args) {
+ String[] optSpec = new String[spec.size()];
+ for (int i = 0; i < optSpec.length; ++i) {
+ optSpec[i] = spec.get(i).toString();
+ }
+ return Options.compile(optSpec).parse(args);
+ }
+
+ // FIXME: the "new" command should be provided by runtime,
+ // so it can leverage same argument coercion mechanism, used to invoke methods.
+ public Object _new(CommandSession session, Object name, Object[] argv) throws Exception {
+ Class<?> clazz;
+
+ if (name instanceof Class<?>) {
+ clazz = (Class<?>) name;
+ } else {
+ clazz = loadClass(name.toString());
+ }
+
+ for (Constructor<?> c : clazz.getConstructors()) {
+ Class<?>[] types = c.getParameterTypes();
+ if (types.length != argv.length) {
+ continue;
+ }
+
+ boolean match = true;
+
+ Object[] transformed = argv.clone();
+ for (int i = 0; i < transformed.length; ++i) {
+ try {
+ transformed[i] = session.convert(types[i], transformed[i]);
+ } catch (IllegalArgumentException e) {
+ match = false;
+ break;
+ }
+ }
+
+ if (!match) {
+ continue;
+ }
+
+ try {
+ return c.newInstance(transformed);
+ } catch (InvocationTargetException ite) {
+ Throwable cause = ite.getCause();
+ if (cause instanceof Exception) {
+ throw (Exception) cause;
+ }
+ throw ite;
+ }
+ }
+
+ throw new IllegalArgumentException("can't coerce " + Arrays.asList(argv)
+ + " to any of " + Arrays.asList(clazz.getConstructors()));
+ }
+
+ private Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (!name.contains(".")) {
+ for (String p : packages) {
+ String pkg = p + "." + name;
+ try {
+ return Class.forName(pkg);
+ } catch (ClassNotFoundException e) {
+ }
+ }
+ }
+ return Class.forName(name);
+ }
+
+ public void set(CommandSession session, String[] argv) throws Exception {
+ final String[] usage = {
+ "set - show session variables",
+ "Usage: set [OPTIONS] [PREFIX]",
+ " -? --help show help",
+ " -a --all show all variables, including those starting with .",
+ " -x set xtrace option",
+ " +x unset xtrace option",
+ "If PREFIX given, then only show variable(s) starting with PREFIX"};
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return;
+ }
+
+ List<String> args = opt.args();
+ String prefix = (args.isEmpty() ? "" : args.get(0));
+
+ if (opt.isSet("x")) {
+ session.put("echo", true);
+ } else if ("+x".equals(prefix)) {
+ session.put("echo", null);
+ } else {
+ boolean all = opt.isSet("all");
+ for (String key : new TreeSet<String>(Shell.getVariables(session))) {
+ if (!key.startsWith(prefix))
+ continue;
+
+ if (key.startsWith(".") && !(all || prefix.length() > 0))
+ continue;
+
+ Object target = session.get(key);
+ String type = null;
+ String value = null;
+
+ if (target != null) {
+ Class<? extends Object> clazz = target.getClass();
+ type = clazz.getSimpleName();
+ value = target.toString();
+ }
+
+ String trunc = value == null || value.length() < 55 ? "" : "...";
+ System.out.println(String.format("%-15.15s %-15s %.45s%s", type, key,
+ value, trunc));
+ }
+ }
+ }
+
+ /*
+ * the following methods depend on the internals of the runtime implementation.
+ * ideally, they should be available via some API.
+ */
+
+ public Object tac(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {
+ "tac - capture stdin as String or List and optionally write to file.",
+ "Usage: tac [-al] [FILE]",
+ " -a --append append to FILE",
+ " -l --list return List<String>",
+ " -? --help show help"};
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return null;
+ }
+
+ List<String> args = opt.args();
+ BufferedWriter fw = null;
+
+ if (args.size() == 1) {
+ String path = args.get(0);
+ File file = new File(Posix._pwd(session), path);
+ fw = new BufferedWriter(new FileWriter(file, opt.isSet("append")));
+ }
+
+ StringWriter sw = new StringWriter();
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+
+ ArrayList<String> list = null;
+
+ if (opt.isSet("list")) {
+ list = new ArrayList<String>();
+ }
+
+ boolean first = true;
+ String s;
+
+ while ((s = rdr.readLine()) != null) {
+ if (list != null) {
+ list.add(s);
+ } else {
+ if (!first) {
+ sw.write(' ');
+ }
+ first = false;
+ sw.write(s);
+ }
+
+ if (fw != null) {
+ fw.write(s);
+ fw.newLine();
+ }
+ }
+
+ if (fw != null) {
+ fw.close();
+ }
+
+ return list != null ? list : sw.toString();
+ }
+
+ // FIXME: expose API in runtime so type command doesn't have to duplicate the runtime
+ // command search strategy.
+ public boolean type(CommandSession session, String[] argv) throws Exception {
+ final String[] usage = {"type - show command type",
+ "Usage: type [OPTIONS] [name[:]]",
+ " -a --all show all matches",
+ " -? --help show help",
+ " -q --quiet don't print anything, just return status",
+ " -s --scope=NAME list all commands in named scope",
+ " -t --types show full java type names"};
+
+ Options opt = Options.compile(usage).parse(argv);
+ List<String> args = opt.args();
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return true;
+ }
+
+ boolean all = opt.isSet("all");
+
+ String optScope = null;
+ if (opt.isSet("scope")) {
+ optScope = opt.get("scope");
+ }
+
+ if (args.size() == 1) {
+ String arg = args.get(0);
+ if (arg.endsWith(":")) {
+ optScope = args.remove(0);
+ }
+ }
+
+ if (optScope != null || (args.isEmpty() && all)) {
+ Set<String> snames = new TreeSet<String>();
+
+ for (String sname : (getCommands(session))) {
+ if ((optScope == null) || sname.startsWith(optScope)) {
+ snames.add(sname);
+ }
+ }
+
+ for (String sname : snames) {
+ System.out.println(sname);
+ }
+
+ return true;
+ }
+
+ if (args.size() == 0) {
+ Map<String, Integer> scopes = new TreeMap<String, Integer>();
+
+ for (String sname : getCommands(session)) {
+ int colon = sname.indexOf(':');
+ String scope = sname.substring(0, colon);
+ Integer count = scopes.get(scope);
+ if (count == null) {
+ count = 0;
+ }
+ scopes.put(scope, ++count);
+ }
+
+ for (Entry<String, Integer> entry : scopes.entrySet()) {
+ System.out.println(entry.getKey() + ":" + entry.getValue());
+ }
+
+ return true;
+ }
+
+ final String name = args.get(0).toLowerCase();
+
+ final int colon = name.indexOf(':');
+ final String MAIN = "_main"; // FIXME: must match Reflective.java
+
+ StringBuilder buf = new StringBuilder();
+ Set<String> cmds = new LinkedHashSet<String>();
+
+ // get all commands
+ if ((colon != -1) || (session.get(name) != null)) {
+ cmds.add(name);
+ } else if (session.get(MAIN) != null) {
+ cmds.add(MAIN);
+ } else {
+ String path = session.get("SCOPE") != null ? session.get("SCOPE").toString()
+ : "*";
+
+ for (String s : path.split(":")) {
+ if (s.equals("*")) {
+ for (String sname : getCommands(session)) {
+ if (sname.endsWith(":" + name)) {
+ cmds.add(sname);
+ if (!all) {
+ break;
+ }
+ }
+ }
+ } else {
+ String sname = s + ":" + name;
+ if (session.get(sname) != null) {
+ cmds.add(sname);
+ if (!all) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for (String key : cmds) {
+ Object target = session.get(key);
+ if (target == null) {
+ continue;
+ }
+
+ CharSequence source = getClosureSource(session, key);
+
+ if (source != null) {
+ buf.append(name);
+ buf.append(" is function {");
+ buf.append(source);
+ buf.append("}");
+ continue;
+ }
+
+ for (Method m : getMethods(session, key)) {
+ StringBuilder params = new StringBuilder();
+
+ for (Class<?> type : m.getParameterTypes()) {
+ if (params.length() > 0) {
+ params.append(", ");
+ }
+ params.append(type.getSimpleName());
+ }
+
+ String rtype = m.getReturnType().getSimpleName();
+
+ if (buf.length() > 0) {
+ buf.append("\n");
+ }
+
+ if (opt.isSet("types")) {
+ String cname = m.getDeclaringClass().getName();
+ buf.append(String.format("%s %s.%s(%s)", rtype, cname, m.getName(),
+ params));
+ } else {
+ buf.append(String.format("%s is %s %s(%s)", name, rtype, key, params));
+ }
+ }
+ }
+
+ if (buf.length() > 0) {
+ if (!opt.isSet("quiet")) {
+ System.out.println(buf);
+ }
+ return true;
+ }
+
+ if (!opt.isSet("quiet")) {
+ System.err.println("type: " + name + " not found.");
+ }
+
+ return false;
+ }
+
+ private boolean isClosure(Object target) {
+ return target.getClass().getSimpleName().equals("Closure");
+ }
+
+ private boolean isCommand(Object target) {
+ return target.getClass().getSimpleName().equals("CommandProxy");
+ }
+
+ private CharSequence getClosureSource(CommandSession session, String name)
+ throws Exception {
+ Object target = session.get(name);
+
+ if (target == null) {
+ return null;
+ }
+
+ if (!isClosure(target)) {
+ return null;
+ }
+
+ Field sourceField = target.getClass().getDeclaredField("source");
+ sourceField.setAccessible(true);
+ return (CharSequence) sourceField.get(target);
+ }
+
+ private List<Method> getMethods(CommandSession session, String scmd) throws Exception {
+ final int colon = scmd.indexOf(':');
+ final String function = colon == -1 ? scmd : scmd.substring(colon + 1);
+ final String name = KEYWORDS.contains(function) ? ("_" + function) : function;
+ final String get = "get" + function;
+ final String is = "is" + function;
+ final String set = "set" + function;
+ final String MAIN = "_main"; // FIXME: must match Reflective.java
+
+ Object target = session.get(scmd);
+ if (target == null) {
+ return null;
+ }
+
+ if (isClosure(target)) {
+ return null;
+ }
+
+ if (isCommand(target)) {
+ Method method = target.getClass().getMethod("getTarget", (Class[]) null);
+ method.setAccessible(true);
+ target = method.invoke(target, (Object[]) null);
+ }
+
+ ArrayList<Method> list = new ArrayList<Method>();
+ Class<?> tc = (target instanceof Class<?>) ? (Class<?>) target
+ : target.getClass();
+ Method[] methods = tc.getMethods();
+
+ for (Method m : methods) {
+ String mname = m.getName().toLowerCase();
+
+ if (mname.equals(name) || mname.equals(get) || mname.equals(set)
+ || mname.equals(is) || mname.equals(MAIN)) {
+ list.add(m);
+ }
+ }
+
+ return list;
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
new file mode 100644
index 0000000..7d67d45
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Formatter;
+
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.startlevel.StartLevel;
+
+public class Converters implements Converter {
+ private final BundleContext context;
+
+ public Converters(BundleContext context) {
+ this.context = context;
+ }
+
+ private CharSequence print(Bundle bundle) {
+ // [ ID ] [STATE ] [ SL ] symname
+ StartLevel sl = null;
+ ServiceReference ref = context.getServiceReference(StartLevel.class.getName());
+ if (ref != null) {
+ sl = (StartLevel) context.getService(ref);
+ }
+
+ if (sl == null) {
+ return String.format("%5d|%-11s|%s (%s)", bundle.getBundleId(),
+ getState(bundle), bundle.getSymbolicName(), bundle.getVersion());
+ }
+
+ int level = sl.getBundleStartLevel(bundle);
+ context.ungetService(ref);
+
+ return String.format("%5d|%-11s|%5d|%s (%s)", bundle.getBundleId(),
+ getState(bundle), level, bundle.getSymbolicName(), bundle.getVersion());
+ }
+
+ private CharSequence print(ServiceReference ref) {
+ StringBuilder sb = new StringBuilder();
+ Formatter f = new Formatter(sb);
+
+ String spid = "";
+ Object pid = ref.getProperty("service.pid");
+ if (pid != null) {
+ spid = pid.toString();
+ }
+
+ f.format("%06d %3s %-40s %s", ref.getProperty("service.id"),
+ ref.getBundle().getBundleId(),
+ getShortNames((String[]) ref.getProperty("objectclass")), spid);
+ return sb;
+ }
+
+ private CharSequence getShortNames(String[] list) {
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ for (String s : list) {
+ sb.append(del + getShortName(s));
+ del = " | ";
+ }
+ return sb;
+ }
+
+ private CharSequence getShortName(String name) {
+ int n = name.lastIndexOf('.');
+ if (n < 0) {
+ n = 0;
+ } else {
+ n++;
+ }
+ return name.subSequence(n, name.length());
+ }
+
+ private String getState(Bundle bundle) {
+ switch (bundle.getState()) {
+ case Bundle.ACTIVE:
+ return "Active";
+
+ case Bundle.INSTALLED:
+ return "Installed";
+
+ case Bundle.RESOLVED:
+ return "Resolved";
+
+ case Bundle.STARTING:
+ return "Starting";
+
+ case Bundle.STOPPING:
+ return "Stopping";
+
+ case Bundle.UNINSTALLED:
+ return "Uninstalled ";
+ }
+ return null;
+ }
+
+ public Bundle bundle(Bundle i) {
+ return i;
+ }
+
+ public Object convert(Class<?> desiredType, final Object in) throws Exception {
+ if (desiredType == Bundle.class) {
+ return convertBundle(in);
+ }
+
+ if (desiredType == ServiceReference.class) {
+ return convertServiceReference(in);
+ }
+
+ if (desiredType == Class.class) {
+ try {
+ return Class.forName(in.toString());
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ if (desiredType.isAssignableFrom(String.class) && in instanceof InputStream) {
+ return read(((InputStream) in));
+ }
+
+ if (in instanceof Function && desiredType.isInterface()
+ && desiredType.getDeclaredMethods().length == 1) {
+ return Proxy.newProxyInstance(desiredType.getClassLoader(),
+ new Class[]{desiredType}, new InvocationHandler() {
+ Function command = ((Function) in);
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ return command.execute(null, Arrays.asList(args));
+ }
+ });
+ }
+
+ return null;
+ }
+
+ private Object convertServiceReference(Object in) throws InvalidSyntaxException {
+ String s = in.toString();
+ if (s.startsWith("(") && s.endsWith(")")) {
+ ServiceReference refs[] = context.getServiceReferences(null, String.format(
+ "(|(service.id=%s)(service.pid=%s))", in, in));
+ if (refs != null && refs.length > 0) {
+ return refs[0];
+ }
+ }
+
+ ServiceReference refs[] = context.getServiceReferences(null, String.format(
+ "(|(service.id=%s)(service.pid=%s))", in, in));
+ if (refs != null && refs.length > 0) {
+ return refs[0];
+ }
+ return null;
+ }
+
+ private Object convertBundle(Object in) {
+ String s = in.toString();
+ try {
+ long id = Long.parseLong(s);
+ return context.getBundle(id);
+ } catch (NumberFormatException nfe) {
+ // Ignore
+ }
+
+ Bundle bundles[] = context.getBundles();
+ for (Bundle b : bundles) {
+ if (b.getLocation().equals(s)) {
+ return b;
+ }
+
+ if (b.getSymbolicName().equals(s)) {
+ return b;
+ }
+ }
+
+ return null;
+ }
+
+ public CharSequence format(Object target, int level, Converter converter)
+ throws IOException {
+ if (level == INSPECT && target instanceof InputStream) {
+ return read(((InputStream) target));
+ }
+ if (level == LINE && target instanceof Bundle) {
+ return print((Bundle) target);
+ }
+ if (level == LINE && target instanceof ServiceReference) {
+ return print((ServiceReference) target);
+ }
+ if (level == PART && target instanceof Bundle) {
+ return ((Bundle) target).getSymbolicName();
+ }
+ if (level == PART && target instanceof ServiceReference) {
+ return getShortNames((String[]) ((ServiceReference) target).getProperty("objectclass"));
+ }
+ return null;
+ }
+
+ private CharSequence read(InputStream in) throws IOException {
+ int c;
+ StringBuffer sb = new StringBuffer();
+ while ((c = in.read()) > 0) {
+ if (c >= 32 && c <= 0x7F || c == '\n' || c == '\r') {
+ sb.append((char) c);
+ } else {
+ String s = Integer.toHexString(c).toUpperCase();
+ sb.append("\\");
+ if (s.length() < 1) {
+ sb.append(0);
+ }
+ sb.append(s);
+ }
+ }
+ return sb;
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
new file mode 100644
index 0000000..1d91d46
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
+import org.apache.felix.gogo.runtime.EOFError;
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Parser.Statement;
+import org.apache.felix.gogo.runtime.SyntaxError;
+import org.apache.felix.gogo.runtime.Token;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReader.RegionType;
+import org.jline.reader.impl.DefaultHighlighter;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+import org.jline.utils.WCWidth;
+
+public class Highlighter extends DefaultHighlighter {
+
+ private final CommandSession session;
+
+ public Highlighter(CommandSession session) {
+ this.session = session;
+ }
+
+ public AttributedString highlight(LineReader reader, String buffer) {
+ try {
+ Program program = null;
+ List<Token> tokens = null;
+ List<Statement> statements = null;
+ String repaired = buffer + " ";
+ while (program == null) {
+ try {
+ org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(repaired);
+ program = parser.program();
+ tokens = parser.tokens();
+ statements = parser.statements();
+ } catch (EOFError e) {
+ repaired = repaired + e.repair();
+ }
+ }
+
+ int underlineStart = -1;
+ int underlineEnd = -1;
+ int negativeStart = -1;
+ int negativeEnd = -1;
+ String search = reader.getSearchTerm();
+ if (search != null && search.length() > 0) {
+ underlineStart = buffer.indexOf(search);
+ if (underlineStart >= 0) {
+ underlineEnd = underlineStart + search.length() - 1;
+ }
+ }
+ if (reader.getRegionActive() != RegionType.NONE) {
+ negativeStart = reader.getRegionMark();
+ negativeEnd = reader.getBuffer().cursor();
+ if (negativeStart > negativeEnd) {
+ int x = negativeEnd;
+ negativeEnd = negativeStart;
+ negativeStart = x;
+ }
+ if (reader.getRegionActive() == RegionType.LINE) {
+ while (negativeStart > 0 && reader.getBuffer().atChar(negativeStart - 1) != '\n') {
+ negativeStart--;
+ }
+ while (negativeEnd < reader.getBuffer().length() - 1 && reader.getBuffer().atChar(negativeEnd + 1) != '\n') {
+ negativeEnd++;
+ }
+ }
+ }
+
+ Type[] types = new Type[repaired.length()];
+
+ Arrays.fill(types, Type.Unknown);
+
+ int cur = 0;
+ for (Token token : tokens) {
+ // We're on the repair side, so exit now
+ if (token.start() >= buffer.length()) {
+ break;
+ }
+ if (token.start() > cur) {
+// ansi.a(buffer.substring(cur, token.start()));
+ cur = token.start();
+ }
+ // Find corresponding statement
+ Statement statement = null;
+ for (int i = statements.size() - 1; i >= 0; i--) {
+ Statement s = statements.get(i);
+ if (s.start() <= cur && cur < s.start() + s.length()) {
+ statement = s;
+ break;
+ }
+ }
+
+ // Reserved tokens
+ Type type = Type.Unknown;
+ if (Token.eq(token, "{")
+ || Token.eq(token, "}")
+ || Token.eq(token, "(")
+ || Token.eq(token, ")")
+ || Token.eq(token, "[")
+ || Token.eq(token, "]")
+ || Token.eq(token, "|")
+ || Token.eq(token, ";")
+ || Token.eq(token, "=")) {
+ type = Type.Reserved;
+ } else if (token.charAt(0) == '\'' || token.charAt(0) == '"') {
+ type = Type.String;
+ } else if (token.toString().matches("^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$")) {
+ type = Type.Number;
+ } else if (token.charAt(0) == '$') {
+ type = Type.Variable;
+ } else if (((Set) session.get(CommandSessionImpl.CONSTANTS)).contains(token.toString())
+ || Token.eq(token, "null") || Token.eq(token, "false") || Token.eq(token, "true")) {
+ type = Type.Constant;
+ } else {
+ boolean isFirst = statement != null && statement.tokens().size() > 0
+ && token == statement.tokens().get(0);
+ boolean isThirdWithNext = statement != null && statement.tokens().size() > 3
+ && token == statement.tokens().get(2);
+ boolean isAssign = statement != null && statement.tokens().size() > 1
+ && Token.eq(statement.tokens().get(1), "=");
+ if (isFirst && isAssign) {
+ type = Type.VariableName;
+ }
+ if (isFirst && !isAssign || isAssign && isThirdWithNext) {
+ Object v = session.get(Shell.resolve(session, token.toString()));
+ type = (v instanceof Function) ? Type.Function : Type.BadFunction;
+ }
+ }
+ Arrays.fill(types, token.start(), Math.min(token.start() + token.length(), types.length), type);
+ /*
+ String valid;
+ if (token.start() + token.length() <= buffer.length()) {
+ valid = token.toString();
+ } else {
+ valid = token.subSequence(0, buffer.length() - token.start()).toString();
+ }
+ switch (type) {
+ case Reserved:
+ ansi.fg(Color.MAGENTA).a(valid).fg(Color.DEFAULT);
+ break;
+ case String:
+ case Number:
+ case Constant:
+ ansi.fg(Color.GREEN).a(valid).fg(Color.DEFAULT);
+ break;
+ case Variable:
+ case VariableName:
+ ansi.fg(Color.CYAN).a(valid).fg(Color.DEFAULT);
+ break;
+ case Function:
+ ansi.fg(Color.BLUE).a(valid).fg(Color.DEFAULT);
+ break;
+ case BadFunction:
+ ansi.fg(Color.RED).a(valid).fg(Color.DEFAULT);
+ break;
+ default:
+ ansi.a(valid);
+ break;
+ }
+ */
+ cur = Math.min(token.start() + token.length(), buffer.length());
+ }
+
+ if (buffer.length() < repaired.length()) {
+ Arrays.fill(types, buffer.length(), repaired.length(), Type.Repair);
+ }
+
+ AttributedStringBuilder sb = new AttributedStringBuilder();
+ Type prevType = Type.Unknown;
+ for (int i = 0; i < repaired.length(); i++) {
+ if (i == underlineStart) {
+ sb.style(sb.style().underline());
+ }
+ if (i == negativeStart) {
+ sb.style(sb.style().inverse());
+ }
+ if (types[i] != prevType) {
+ prevType = types[i];
+ switch (prevType) {
+ case Reserved:
+ sb.style(sb.style().foreground(AttributedStyle.MAGENTA));
+ break;
+ case String:
+ case Number:
+ case Constant:
+ sb.style(sb.style().foreground(AttributedStyle.GREEN));
+ break;
+ case Variable:
+ case VariableName:
+ sb.style(sb.style().foreground(AttributedStyle.CYAN));
+ break;
+ case Function:
+ sb.style(sb.style().foreground(AttributedStyle.BLUE));
+ break;
+ case BadFunction:
+ sb.style(sb.style().foreground(AttributedStyle.RED));
+ break;
+ case Repair:
+ sb.style(sb.style().foreground(AttributedStyle.BLACK + AttributedStyle.BRIGHT));
+ break;
+ default:
+ sb.style(sb.style().foregroundDefault());
+ break;
+ }
+ }
+ char c = repaired.charAt(i);
+ if (c == '\t' || c == '\n') {
+ sb.append(c);
+ } else if (c < 32) {
+ sb.style(sb.style().inverseNeg())
+ .append('^')
+ .append((char) (c + '@'))
+ .style(sb.style().inverseNeg());
+ } else {
+ int w = WCWidth.wcwidth(c);
+ if (w > 0) {
+ sb.append(c);
+ }
+ }
+ if (i == underlineEnd) {
+ sb.style(sb.style().underlineOff());
+ }
+ if (i == negativeEnd) {
+ sb.style(sb.style().inverseOff());
+ }
+ }
+
+ return sb.toAttributedString();
+ } catch (SyntaxError e) {
+ return super.highlight(reader, buffer);
+ }
+ }
+
+ enum Type {
+ Reserved,
+ String,
+ Number,
+ Variable,
+ VariableName,
+ Function,
+ BadFunction,
+ Value,
+ Constant,
+ Unknown,
+ Repair
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java
new file mode 100644
index 0000000..498e12e
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.jline.builtins.Completers.DirectoriesCompleter;
+import org.jline.builtins.Completers.FilesCompleter;
+import org.jline.builtins.Options;
+import org.jline.reader.Candidate;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+import org.jline.reader.Widget;
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Terminal;
+import org.jline.utils.InfoCmp.Capability;
+
+public class JLineCommands {
+
+ public static final String[] functions = {
+ "keymap", "setopt", "unsetopt", "complete", "history",
+ "less", "watch", "nano", "widget", "tmux",
+ "__files", "__directories", "__usage_completion"
+ };
+
+ private final CommandProcessor processor;
+
+ private final org.jline.builtins.Commands commands = new org.jline.builtins.Commands();
+
+ public JLineCommands(CommandProcessor processor) {
+ this.processor = processor;
+ }
+
+ public void tmux(final CommandSession session, String[] argv) throws Exception {
+ commands.tmux(Shell.getTerminal(session),
+ System.out, System.err,
+ () -> session.get(".tmux"),
+ t -> session.put(".tmux", t),
+ c -> startShell(session, c), argv);
+ }
+
+ private void startShell(CommandSession session, Terminal terminal) {
+ new Thread(() -> runShell(session, terminal), terminal.getName() + " shell").start();
+ }
+
+ private void runShell(CommandSession session, Terminal terminal) {
+ InputStream in = terminal.input();
+ PrintStream out = new PrintStream(terminal.output());
+ CommandSession newSession = processor.createSession(in, out, out);
+ newSession.put(Shell.VAR_TERMINAL, terminal);
+ newSession.put(".tmux", session.get(".tmux"));
+ Context context = new Context() {
+ public String getProperty(String name) {
+ return System.getProperty(name);
+ }
+ public void exit() throws Exception {
+ terminal.close();
+ }
+ };
+ try {
+ new Shell(context, processor, terminal).gosh(newSession, new String[]{"--login"});
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ terminal.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void nano(final CommandSession session, String[] argv) throws Exception {
+ commands.nano(Shell.getTerminal(session), System.out, System.err, Shell.cwd(session), argv);
+ }
+
+ public void watch(final CommandSession session, String[] argv) throws IOException, InterruptedException {
+ final String[] usage = {
+ "watch - watches & refreshes the output of a command",
+ "Usage: watch [OPTIONS] COMMAND",
+ " -? --help Show help",
+ " -n --interval Interval between executions of the command in seconds",
+ " -a --append The output should be appended but not clear the console"
+ };
+ final Options opt = Options.compile(usage).parse(argv);
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return;
+ }
+ List<String> args = opt.args();
+ if (args.isEmpty()) {
+ System.err.println("Argument expected");
+ return;
+ }
+ ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+ final Terminal terminal = Shell.getTerminal(session);
+ final CommandProcessor processor = Shell.getProcessor(session);
+ try {
+ int interval = 1;
+ if (opt.isSet("interval")) {
+ interval = opt.getNumber("interval");
+ if (interval < 1) {
+ interval = 1;
+ }
+ }
+ final String cmd = String.join(" ", args);
+ Runnable task = () -> {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream is = new ByteArrayInputStream(new byte[0]);
+ PrintStream os = new PrintStream(baos);
+ if (opt.isSet("append") || !terminal.puts(Capability.clear_screen)) {
+ terminal.writer().println();
+ }
+ try {
+ CommandSession ns = processor.createSession(is, os, os);
+ Set<String> vars = Shell.getCommands(session);
+ for (String n : vars) {
+ ns.put(n, session.get(n));
+ }
+ ns.execute(cmd);
+ } catch (Throwable t) {
+ t.printStackTrace(os);
+ }
+ os.flush();
+ terminal.writer().print(baos.toString());
+ terminal.writer().flush();
+ };
+ executorService.scheduleAtFixedRate(task, 0, interval, TimeUnit.SECONDS);
+ Attributes attr = terminal.enterRawMode();
+ terminal.reader().read();
+ terminal.setAttributes(attr);
+ } finally {
+ executorService.shutdownNow();
+ }
+ }
+
+ public void less(CommandSession session, String[] argv) throws IOException, InterruptedException {
+ commands.less(Shell.getTerminal(session), System.out, System.err, Shell.cwd(session), argv);
+ }
+
+ public void history(CommandSession session, String[] argv) throws IOException {
+ commands.history(Shell.getReader(session), System.out, System.err, argv);
+ }
+
+ public void complete(CommandSession session, String[] argv) {
+ commands.complete(Shell.getReader(session), System.out, System.err, Shell.getCompletions(session), argv);
+ }
+
+ public void widget(final CommandSession session, String[] argv) throws Exception {
+ java.util.function.Function<String, Widget> creator = func -> () -> {
+ try {
+ session.execute(func);
+ } catch (Exception e) {
+ // TODO: log exception ?
+ return false;
+ }
+ return true;
+ };
+ commands.widget(Shell.getReader(session), System.out, System.err, creator, argv);
+ }
+
+ public void keymap(CommandSession session, String[] argv) {
+ commands.keymap(Shell.getReader(session), System.out, System.err, argv);
+ }
+
+ public void setopt(CommandSession session, String[] argv) {
+ commands.setopt(Shell.getReader(session), System.out, System.err, argv);
+ }
+
+ public void unsetopt(CommandSession session, String[] argv) {
+ commands.unsetopt(Shell.getReader(session), System.out, System.err, argv);
+ }
+
+ public List<Candidate> __files(CommandSession session) {
+ ParsedLine line = Shell.getParsedLine(session);
+ LineReader reader = Shell.getReader(session);
+ List<Candidate> candidates = new ArrayList<>();
+ new FilesCompleter(new File(Shell.cwd(session))).complete(reader, line, candidates);
+ return candidates;
+ }
+
+ public List<Candidate> __directories(CommandSession session) {
+ ParsedLine line = Shell.getParsedLine(session);
+ LineReader reader = Shell.getReader(session);
+ List<Candidate> candidates = new ArrayList<>();
+ new DirectoriesCompleter(new File(Shell.cwd(session))).complete(reader, line, candidates);
+ return candidates;
+ }
+
+ public void __usage_completion(CommandSession session, String command) throws Exception {
+ Object func = session.get(command.contains(":") ? command : "*:" + command);
+ if (func instanceof Function) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ByteArrayOutputStream baes = new ByteArrayOutputStream();
+ CommandSession ts = ((CommandSessionImpl) session).processor().createSession(bais, new PrintStream(baos), new PrintStream(baes));
+ ts.execute(command + " --help");
+
+ String regex = "(?x)\\s*" + "(?:-([^-]))?" + // 1: short-opt-1
+ "(?:,?\\s*-(\\w))?" + // 2: short-opt-2
+ "(?:,?\\s*--(\\w[\\w-]*)(=\\w+)?)?" + // 3: long-opt-1 and 4:arg-1
+ "(?:,?\\s*--(\\w[\\w-]*))?" + // 5: long-opt-2
+ ".*?(?:\\(default=(.*)\\))?\\s*" + // 6: default
+ "(.*)"; // 7: description
+ Pattern pattern = Pattern.compile(regex);
+ for (String l : baes.toString().split("\n")) {
+ Matcher matcher = pattern.matcher(l);
+ if (matcher.matches()) {
+ List<String> args = new ArrayList<>();
+ if (matcher.group(1) != null) {
+ args.add("--short-option");
+ args.add(matcher.group(1));
+ }
+ if (matcher.group(3) != null) {
+ args.add("--long-option");
+ args.add(matcher.group(1));
+ }
+ if (matcher.group(4) != null) {
+ args.add("--argument");
+ args.add("");
+ }
+ if (matcher.group(7) != null) {
+ args.add("--description");
+ args.add(matcher.group(7));
+ }
+ complete(session, args.toArray(new String[args.size()]));
+ }
+ }
+ }
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java
new file mode 100644
index 0000000..93b31ef
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Completers.CompletionData;
+import org.jline.builtins.Completers.CompletionEnvironment;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+
+public class JLineCompletionEnvironment implements CompletionEnvironment {
+
+ private final CommandSession session;
+
+ public JLineCompletionEnvironment(CommandSession session) {
+ this.session = session;
+ }
+
+ public Map<String, List<CompletionData>> getCompletions() {
+ return Shell.getCompletions(session);
+ }
+
+ public Set<String> getCommands() {
+ return Shell.getCommands(session);
+ }
+
+ public String resolveCommand(String command) {
+ return Shell.resolve(session, command);
+ }
+
+ public String commandName(String command) {
+ int idx = command.indexOf(':');
+ return idx >= 0 ? command.substring(idx + 1) : command;
+ }
+
+ public Object evaluate(LineReader reader, ParsedLine line, String func) throws Exception {
+ session.put(Shell.VAR_COMMAND_LINE, line);
+ return session.execute(func);
+ }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
new file mode 100644
index 0000000..8d78f6a
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.jline.ssh.Ssh;
+import org.apache.felix.gogo.jline.telnet.Telnet;
+import org.apache.felix.gogo.runtime.CommandProcessorImpl;
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.apache.felix.service.command.CommandSession;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+
+public class Main {
+
+ public static void main(String[] args) throws IOException {
+ InputStream sin = System.in;
+ PrintStream sout = System.out;
+ PrintStream serr = System.err;
+
+ try (Terminal terminal = TerminalBuilder.builder()
+ .name("gogo")
+ .type(System.getenv("TERM"))
+ .system(true)
+ .streams(sin, sout)
+ .build()) {
+ ThreadIOImpl tio = new ThreadIOImpl();
+ tio.start();
+ try {
+ CommandProcessorImpl processor = new CommandProcessorImpl(tio);
+ Context context = new MyContext();
+ Shell shell = new Shell(context, processor, terminal);
+ processor.addCommand("gogo", processor, "addCommand");
+ processor.addCommand("gogo", processor, "removeCommand");
+ processor.addCommand("gogo", processor, "eval");
+ register(processor, new Builtin(), Builtin.functions);
+ register(processor, new Procedural(), Procedural.functions);
+ register(processor, new Posix(), Posix.functions);
+ register(processor, shell, Shell.functions);
+ register(processor, new JLineCommands(processor), JLineCommands.functions);
+ try {
+ register(processor, new Telnet(processor), Telnet.functions);
+ } catch (Throwable t) {
+ // ignore
+ }
+ try {
+ register(processor, new Ssh(processor), Ssh.functions);
+ } catch (Throwable t) {
+ // ignore
+ }
+ PrintStream pout = new PrintStream(terminal.output());
+ CommandSession session = processor.createSession(terminal.input(), pout, pout);
+ session.put(Shell.VAR_CONTEXT, context);
+ session.put(Shell.VAR_TERMINAL, terminal);
+ try {
+ String[] argv = new String[args.length + 1];
+ argv[0] = "--login";
+ System.arraycopy(args, 0, argv, 1, args.length);
+ shell.gosh(session, argv);
+ } catch (Exception e) {
+ Object loc = session.get(".location");
+ if (null == loc || !loc.toString().contains(":")) {
+ loc = "gogo";
+ }
+
+ System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ session.close();
+ }
+ } finally {
+ tio.stop();
+ }
+ }
+ }
+
+ static void register(CommandProcessorImpl processor, Object target, String[] functions) {
+ for (String function : functions) {
+ processor.addCommand("gogo", target, function);
+ }
+ }
+
+ private static class MyContext implements Context {
+
+ public String getProperty(String name) {
+ return System.getProperty(name);
+ }
+
+ public void exit() throws Exception {
+ System.exit(0);
+ }
+ }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
new file mode 100644
index 0000000..22f0cdc
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Token;
+import org.jline.reader.ParsedLine;
+
+public class ParsedLineImpl implements ParsedLine {
+
+ private final Program program;
+ private final String source;
+ private final int cursor;
+ private final List<String> tokens;
+ private final int wordIndex;
+ private final int wordCursor;
+
+ public ParsedLineImpl(Program program, Token line, int cursor, List<Token> tokens) {
+ this.program = program;
+ this.source = line.toString();
+ this.cursor = cursor - line.start();
+ this.tokens = new ArrayList<String>();
+ for (Token token : tokens) {
+ this.tokens.add(token.toString());
+ }
+ int wi = tokens.size();
+ int wc = 0;
+ if (cursor >= 0) {
+ for (int i = 0; i < tokens.size(); i++) {
+ Token t = tokens.get(i);
+ if (t.start() > cursor) {
+ wi = i;
+ wc = 0;
+ this.tokens.add(i, "");
+ break;
+ }
+ if (t.start() + t.length() >= cursor) {
+ wi = i;
+ wc = cursor - t.start();
+ break;
+ }
+ }
+ }
+ if (wi == tokens.size()) {
+ this.tokens.add("");
+ }
+ wordIndex = wi;
+ wordCursor = wc;
+ }
+
+ public String word() {
+ return tokens.get(wordIndex());
+ }
+
+ public int wordCursor() {
+ return wordCursor;
+ }
+
+ public int wordIndex() {
+ return wordIndex;
+ }
+
+ public List<String> words() {
+ return tokens;
+ }
+
+ public String line() {
+ return source;
+ }
+
+ public int cursor() {
+ return cursor;
+ }
+
+ public Program program() {
+ return program;
+ }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java
new file mode 100644
index 0000000..4864252
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.EOFError;
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Parser.Statement;
+import org.apache.felix.gogo.runtime.SyntaxError;
+import org.apache.felix.gogo.runtime.Token;
+import org.jline.reader.ParsedLine;
+
+public class Parser implements org.jline.reader.Parser {
+
+ public ParsedLine parse(String line, int cursor) throws org.jline.reader.SyntaxError {
+ try {
+ return doParse(line, cursor);
+ } catch (EOFError e) {
+ throw new org.jline.reader.EOFError(e.line(), e.column(), e.getMessage(), e.missing());
+ } catch (SyntaxError e) {
+ throw new org.jline.reader.SyntaxError(e.line(), e.column(), e.getMessage());
+ }
+ }
+
+ private ParsedLine doParse(CharSequence line, int cursor) throws SyntaxError {
+ org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(line);
+ Program program = parser.program();
+ List<Statement> statements = parser.statements();
+ // Find corresponding statement
+ Statement statement = null;
+ for (int i = statements.size() - 1; i >= 0; i--) {
+ Statement s = statements.get(i);
+ if (s.start() <= cursor) {
+ boolean isOk = true;
+ // check if there are only spaces after the previous statement
+ if (s.start() + s.length() < cursor) {
+ for (int j = s.start() + s.length(); isOk && j < cursor; j++) {
+ isOk = Character.isWhitespace(line.charAt(j));
+ }
+ }
+ statement = s;
+ break;
+ }
+ }
+ if (statement != null) {
+ return new ParsedLineImpl(program, statement, cursor, statement.tokens());
+ } else {
+ // TODO:
+ return new ParsedLineImpl(program, program, cursor, Collections.<Token>singletonList(program));
+ }
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
new file mode 100644
index 0000000..05c4908
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
@@ -0,0 +1,816 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Options;
+
+/**
+ * Posix-like utilities.
+ *
+ * @see <a href="http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html">
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html</a>
+ */
+public class Posix {
+ static final String[] functions = {"cat", "echo", "grep", "sort", "sleep", "cd", "pwd", "ls"};
+
+ static final String CWD = "_cwd";
+
+ public static File _pwd(CommandSession session) {
+ try {
+ File cwd = (File) session.get(CWD);
+ if (cwd == null) {
+ cwd = new File(".").getCanonicalFile();
+ session.put(CWD, cwd);
+ }
+ return cwd;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void sort(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {
+ "sort - writes sorted standard input to standard output.",
+ "Usage: sort [OPTIONS] [FILES]",
+ " -? --help show help",
+ " -f --ignore-case fold lower case to upper case characters",
+ " -r --reverse reverse the result of comparisons",
+ " -u --unique output only the first of an equal run",
+ " -t --field-separator=SEP use SEP instead of non-blank to blank transition",
+ " -b --ignore-leading-blanks ignore leading blancks",
+ " --numeric-sort compare according to string numerical value",
+ " -k --key=KEY fields to use for sorting separated by whitespaces"};
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return;
+ }
+
+ List<String> args = opt.args();
+
+ List<String> lines = new ArrayList<String>();
+ if (!args.isEmpty()) {
+ for (String filename : args) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ Shell.cwd(session).resolve(filename).toURL().openStream()));
+ try {
+ read(reader, lines);
+ } finally {
+ reader.close();
+ }
+ }
+ } else {
+ BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
+ read(r, lines);
+ }
+
+ String separator = opt.get("field-separator");
+ boolean caseInsensitive = opt.isSet("ignore-case");
+ boolean reverse = opt.isSet("reverse");
+ boolean ignoreBlanks = opt.isSet("ignore-leading-blanks");
+ boolean numeric = opt.isSet("numeric-sort");
+ boolean unique = opt.isSet("unique");
+ List<String> sortFields = opt.getList("key");
+
+ char sep = (separator == null || separator.length() == 0) ? '\0' : separator.charAt(0);
+ Collections.sort(lines, new SortComparator(caseInsensitive, reverse, ignoreBlanks, numeric, sep, sortFields));
+ String last = null;
+ for (String s : lines) {
+ if (!unique || last == null || !s.equals(last)) {
+ System.out.println(s);
+ }
+ last = s;
+ }
+ }
+
+ protected static void read(BufferedReader r, List<String> lines) throws IOException {
+ for (String s = r.readLine(); s != null; s = r.readLine()) {
+ lines.add(s);
+ }
+ }
+
+ public static List<String> parseSubstring(String value) {
+ List<String> pieces = new ArrayList<String>();
+ StringBuilder ss = new StringBuilder();
+ // int kind = SIMPLE; // assume until proven otherwise
+ boolean wasStar = false; // indicates last piece was a star
+ boolean leftstar = false; // track if the initial piece is a star
+ boolean rightstar = false; // track if the final piece is a star
+
+ int idx = 0;
+
+ // We assume (sub)strings can contain leading and trailing blanks
+ boolean escaped = false;
+ loop:
+ for (; ; ) {
+ if (idx >= value.length()) {
+ if (wasStar) {
+ // insert last piece as "" to handle trailing star
+ rightstar = true;
+ } else {
+ pieces.add(ss.toString());
+ // accumulate the last piece
+ // note that in the case of
+ // (cn=); this might be
+ // the string "" (!=null)
+ }
+ ss.setLength(0);
+ break loop;
+ }
+
+ // Read the next character and account for escapes.
+ char c = value.charAt(idx++);
+ if (!escaped && ((c == '(') || (c == ')'))) {
+ throw new IllegalArgumentException(
+ "Illegal value: " + value);
+ } else if (!escaped && (c == '*')) {
+ if (wasStar) {
+ // encountered two successive stars;
+ // I assume this is illegal
+ throw new IllegalArgumentException("Invalid filter string: " + value);
+ }
+ if (ss.length() > 0) {
+ pieces.add(ss.toString()); // accumulate the pieces
+ // between '*' occurrences
+ }
+ ss.setLength(0);
+ // if this is a leading star, then track it
+ if (pieces.size() == 0) {
+ leftstar = true;
+ }
+ wasStar = true;
+ } else if (!escaped && (c == '\\')) {
+ escaped = true;
+ } else {
+ escaped = false;
+ wasStar = false;
+ ss.append(c);
+ }
+ }
+ if (leftstar || rightstar || pieces.size() > 1) {
+ // insert leading and/or trailing "" to anchor ends
+ if (rightstar) {
+ pieces.add("");
+ }
+ if (leftstar) {
+ pieces.add(0, "");
+ }
+ }
+ return pieces;
+ }
+
+ public static boolean compareSubstring(List<String> pieces, String s) {
+ // Walk the pieces to match the string
+ // There are implicit stars between each piece,
+ // and the first and last pieces might be "" to anchor the match.
+ // assert (pieces.length > 1)
+ // minimal case is <string>*<string>
+
+ boolean result = true;
+ int len = pieces.size();
+
+ int index = 0;
+
+ loop:
+ for (int i = 0; i < len; i++) {
+ String piece = pieces.get(i);
+
+ // If this is the first piece, then make sure the
+ // string starts with it.
+ if (i == 0) {
+ if (!s.startsWith(piece)) {
+ result = false;
+ break loop;
+ }
+ }
+
+ // If this is the last piece, then make sure the
+ // string ends with it.
+ if (i == len - 1) {
+ if (s.endsWith(piece)) {
+ result = true;
+ } else {
+ result = false;
+ }
+ break loop;
+ }
+
+ // If this is neither the first or last piece, then
+ // make sure the string contains it.
+ if ((i > 0) && (i < (len - 1))) {
+ index = s.indexOf(piece, index);
+ if (index < 0) {
+ result = false;
+ break loop;
+ }
+ }
+
+ // Move string index beyond the matching piece.
+ index += piece.length();
+ }
+
+ return result;
+ }
+
+ private static void cat(final BufferedReader reader, boolean displayLineNumbers) throws IOException {
+ String line;
+ int lineno = 1;
+ try {
+ while ((line = reader.readLine()) != null) {
+ if (displayLineNumbers) {
+ System.out.print(String.format("%6d ", lineno++));
+ }
+ System.out.println(line);
+ }
+ } finally {
+ reader.close();
+ }
+ }
+
+ private static <T> void addAll(List<? super T> list, T[] array) {
+ if (array != null) {
+ Collections.addAll(list, array);
+ }
+ }
+
+ public File pwd(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {
+ "pwd - get current directory",
+ "Usage: pwd [OPTIONS]",
+ " -? --help show help"
+ };
+ Options opt = Options.compile(usage).parse(argv);
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return null;
+ }
+ if (!opt.args().isEmpty()) {
+ System.err.println("usage: pwd");
+ return null;
+ }
+ File cwd = (File) session.get(CWD);
+ if (cwd == null) {
+ cwd = new File(".").getCanonicalFile();
+ session.put(CWD, cwd);
+ }
+ return cwd;
+ }
+
+ public File cd(CommandSession session, String[] argv)
+ throws IOException {
+ final String[] usage = {
+ "cd - get current directory",
+ "Usage: cd [OPTIONS] DIRECTORY",
+ " -? --help show help"
+ };
+ Options opt = Options.compile(usage).parse(argv);
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return null;
+ }
+ if (opt.args().size() != 1) {
+ System.err.println("usage: cd DIRECTORY");
+ return null;
+ }
+ File cwd = pwd(session, new String[0]);
+ String dir = opt.args().get(0);
+
+ URI curUri = cwd.toURI();
+ URI newUri = curUri.resolve(dir);
+
+ cwd = new File(newUri);
+ if (!cwd.exists()) {
+ throw new IOException("Directory does not exist");
+ } else if (!cwd.isDirectory()) {
+ throw new IOException("Target is not a directory");
+ }
+ session.put(CWD, cwd.getCanonicalFile());
+ return cwd;
+ }
+
+ public Collection<File> ls(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {
+ "ls - list files",
+ "Usage: ls [OPTIONS] PATTERNS...",
+ " -? --help show help"
+ };
+ Options opt = Options.compile(usage).parse(argv);
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return null;
+ }
+ if (opt.args().isEmpty()) {
+ opt.args().add("*");
+ }
+ opt.args().remove(0);
+ List<File> files = new ArrayList<File>();
+ for (String pattern : opt.args()) {
+ pattern = ((pattern == null) || (pattern.length() == 0)) ? "." : pattern;
+ pattern = ((pattern.charAt(0) != File.separatorChar) && (pattern.charAt(0) != '.'))
+ ? "./" + pattern : pattern;
+ int idx = pattern.lastIndexOf(File.separatorChar);
+ String parent = (idx < 0) ? "." : pattern.substring(0, idx + 1);
+ String target = (idx < 0) ? pattern : pattern.substring(idx + 1);
+
+ File actualParent = ((parent.charAt(0) == File.separatorChar)
+ ? new File(parent)
+ : new File(pwd(session, new String[0]), parent)).getCanonicalFile();
+
+ idx = target.indexOf(File.separatorChar, idx);
+ boolean isWildcarded = (target.indexOf('*', idx) >= 0);
+ if (isWildcarded) {
+ if (!actualParent.exists()) {
+ throw new IOException("File does not exist");
+ }
+ final List<String> pieces = parseSubstring(target);
+ addAll(files, actualParent.listFiles(new FileFilter() {
+ public boolean accept(File pathname) {
+ return compareSubstring(pieces, pathname.getName());
+ }
+ }));
+ } else {
+ File actualTarget = new File(actualParent, target).getCanonicalFile();
+ if (!actualTarget.exists()) {
+ throw new IOException("File does not exist");
+ }
+ if (actualTarget.isDirectory()) {
+ addAll(files, actualTarget.listFiles());
+ } else {
+ files.add(actualTarget);
+ }
+ }
+ }
+ return files;
+ }
+
+ public void cat(CommandSession session, String[] argv) throws Exception {
+ final String[] usage = {
+ "cat - concatenate and print FILES",
+ "Usage: cat [OPTIONS] [FILES]",
+ " -? --help show help",
+ " -n number the output lines, starting at 1"
+ };
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return;
+ }
+
+ List<String> args = opt.args();
+ if (args.isEmpty()) {
+ args = Collections.singletonList("-");
+ }
+
+ URI cwd = Shell.cwd(session);
+ for (String arg : args) {
+ InputStream is;
+ if ("-".equals(arg)) {
+ is = System.in;
+
+ } else {
+ is = cwd.resolve(arg).toURL().openStream();
+ }
+ cat(new BufferedReader(new InputStreamReader(is)), opt.isSet("n"));
+ }
+ }
+
+ public void echo(Object[] argv) {
+ final String[] usage = {
+ "echo - echoes or prints ARGUMENT to standard output",
+ "Usage: echo [OPTIONS] [ARGUMENTS]",
+ " -? --help show help",
+ " -n no trailing new line"
+ };
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return;
+ }
+
+ List<String> args = opt.args();
+ StringBuilder buf = new StringBuilder();
+ if (args != null) {
+ for (String arg : args) {
+ if (buf.length() > 0)
+ buf.append(' ');
+ buf.append(arg);
+ }
+ }
+ if (opt.isSet("n")) {
+ System.out.print(buf);
+ } else {
+ System.out.println(buf);
+ }
+ }
+
+ public boolean grep(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {
+ "grep - search for PATTERN in each FILE or standard input.",
+ "Usage: grep [OPTIONS] PATTERN [FILES]",
+ " -? --help show help",
+ " -i --ignore-case ignore case distinctions",
+ " -n --line-number prefix each line with line number within its input file",
+ " -q --quiet, --silent suppress all normal output",
+ " -v --invert-match select non-matching lines"};
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return true;
+ }
+
+ List<String> args = opt.args();
+
+ if (args.isEmpty()) {
+ throw opt.usageError("no pattern supplied.");
+ }
+
+ String regex = args.remove(0);
+ if (opt.isSet("ignore-case")) {
+ regex = "(?i)" + regex;
+ }
+
+ if (args.isEmpty()) {
+ args.add(null);
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ if (args.size() > 1) {
+ buf.append("%1$s:");
+ }
+
+ if (opt.isSet("line-number")) {
+ buf.append("%2$s:");
+ }
+
+ buf.append("%3$s");
+ String format = buf.toString();
+
+ Pattern pattern = Pattern.compile(regex);
+ boolean status = true;
+ boolean match = false;
+
+ for (String arg : args) {
+ InputStream in = null;
+
+ try {
+ URI cwd = Shell.cwd(session);
+ in = (arg == null) ? System.in : cwd.resolve(arg).toURL().openStream();
+
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
+ int line = 0;
+ String s;
+ while ((s = rdr.readLine()) != null) {
+ line++;
+ Matcher matcher = pattern.matcher(s);
+ if (!(matcher.find() ^ !opt.isSet("invert-match"))) {
+ match = true;
+ if (opt.isSet("quiet"))
+ break;
+
+ System.out.println(String.format(format, arg, line, s));
+ }
+ }
+
+ if (match && opt.isSet("quiet")) {
+ break;
+ }
+ } catch (IOException e) {
+ System.err.println("grep: " + e.getMessage());
+ status = false;
+ } finally {
+ if (arg != null && in != null) {
+ in.close();
+ }
+ }
+ }
+
+ return match && status;
+ }
+
+ public void sleep(String[] argv) throws InterruptedException {
+ final String[] usage = {
+ "sleep - suspend execution for an interval of time",
+ "Usage: sleep seconds",
+ " -? --help show help"};
+
+ Options opt = Options.compile(usage).parse(argv);
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ return;
+ }
+
+ List<String> args = opt.args();
+ if (args.size() != 1) {
+ System.err.println("usage: sleep seconds");
+ } else {
+ int s = Integer.parseInt(args.get(0));
+ Thread.sleep(s * 1000);
+ }
+ }
+
+ public static class SortComparator implements Comparator<String> {
+
+ private static Pattern fpPattern;
+
+ static {
+ final String Digits = "(\\p{Digit}+)";
+ final String HexDigits = "(\\p{XDigit}+)";
+ final String Exp = "[eE][+-]?" + Digits;
+ final String fpRegex = "([\\x00-\\x20]*[+-]?(NaN|Infinity|(((" + Digits + "(\\.)?(" + Digits + "?)(" + Exp + ")?)|(\\.(" + Digits + ")(" + Exp + ")?)|(((0[xX]" + HexDigits + "(\\.)?)|(0[xX]" + HexDigits + "?(\\.)" + HexDigits + "))[pP][+-]?" + Digits + "))" + "[fFdD]?))[\\x00-\\x20]*)(.*)";
+ fpPattern = Pattern.compile(fpRegex);
+ }
+
+ private boolean caseInsensitive;
+ private boolean reverse;
+ private boolean ignoreBlanks;
+ private boolean numeric;
+ private char separator;
+ private List<Key> sortKeys;
+
+ public SortComparator(boolean caseInsensitive,
+ boolean reverse,
+ boolean ignoreBlanks,
+ boolean numeric,
+ char separator,
+ List<String> sortFields) {
+ this.caseInsensitive = caseInsensitive;
+ this.reverse = reverse;
+ this.separator = separator;
+ this.ignoreBlanks = ignoreBlanks;
+ this.numeric = numeric;
+ if (sortFields == null || sortFields.size() == 0) {
+ sortFields = new ArrayList<String>();
+ sortFields.add("1");
+ }
+ sortKeys = new ArrayList<Key>();
+ for (String f : sortFields) {
+ sortKeys.add(new Key(f));
+ }
+ }
+
+ public int compare(String o1, String o2) {
+ int res = 0;
+
+ List<Integer> fi1 = getFieldIndexes(o1);
+ List<Integer> fi2 = getFieldIndexes(o2);
+ for (Key key : sortKeys) {
+ int[] k1 = getSortKey(o1, fi1, key);
+ int[] k2 = getSortKey(o2, fi2, key);
+ if (key.numeric) {
+ Double d1 = getDouble(o1, k1[0], k1[1]);
+ Double d2 = getDouble(o2, k2[0], k2[1]);
+ res = d1.compareTo(d2);
+ } else {
+ res = compareRegion(o1, k1[0], k1[1], o2, k2[0], k2[1], key.caseInsensitive);
+ }
+ if (res != 0) {
+ if (key.reverse) {
+ res = -res;
+ }
+ break;
+ }
+ }
+ return res;
+ }
+
+ protected Double getDouble(String s, int start, int end) {
+ Matcher m = fpPattern.matcher(s.substring(start, end));
+ m.find();
+ return new Double(s.substring(0, m.end(1)));
+ }
+
+ protected int compareRegion(String s1, int start1, int end1, String s2, int start2, int end2, boolean caseInsensitive) {
+ int n1 = end1, n2 = end2;
+ for (int i1 = start1, i2 = start2; i1 < end1 && i2 < n2; i1++, i2++) {
+ char c1 = s1.charAt(i1);
+ char c2 = s2.charAt(i2);
+ if (c1 != c2) {
+ if (caseInsensitive) {
+ c1 = Character.toUpperCase(c1);
+ c2 = Character.toUpperCase(c2);
+ if (c1 != c2) {
+ c1 = Character.toLowerCase(c1);
+ c2 = Character.toLowerCase(c2);
+ if (c1 != c2) {
+ return c1 - c2;
+ }
+ }
+ } else {
+ return c1 - c2;
+ }
+ }
+ }
+ return n1 - n2;
+ }
+
+ protected int[] getSortKey(String str, List<Integer> fields, Key key) {
+ int start;
+ int end;
+ if (key.startField * 2 <= fields.size()) {
+ start = fields.get((key.startField - 1) * 2);
+ if (key.ignoreBlanksStart) {
+ while (start < fields.get((key.startField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(start))) {
+ start++;
+ }
+ }
+ if (key.startChar > 0) {
+ start = Math.min(start + key.startChar - 1, fields.get((key.startField - 1) * 2 + 1));
+ }
+ } else {
+ start = 0;
+ }
+ if (key.endField > 0 && key.endField * 2 <= fields.size()) {
+ end = fields.get((key.endField - 1) * 2);
+ if (key.ignoreBlanksEnd) {
+ while (end < fields.get((key.endField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(end))) {
+ end++;
+ }
+ }
+ if (key.endChar > 0) {
+ end = Math.min(end + key.endChar - 1, fields.get((key.endField - 1) * 2 + 1));
+ }
+ } else {
+ end = str.length();
+ }
+ return new int[]{start, end};
+ }
+
+ protected List<Integer> getFieldIndexes(String o) {
+ List<Integer> fields = new ArrayList<Integer>();
+ if (o.length() > 0) {
+ if (separator == '\0') {
+ int i = 0;
+ fields.add(0);
+ for (int idx = 1; idx < o.length(); idx++) {
+ if (Character.isWhitespace(o.charAt(idx)) && !Character.isWhitespace(o.charAt(idx - 1))) {
+ fields.add(idx - 1);
+ fields.add(idx);
+ }
+ }
+ fields.add(o.length() - 1);
+ } else {
+ int last = -1;
+ for (int idx = o.indexOf(separator); idx >= 0; idx = o.indexOf(separator, idx + 1)) {
+ if (last >= 0) {
+ fields.add(last);
+ fields.add(idx - 1);
+ } else if (idx > 0) {
+ fields.add(0);
+ fields.add(idx - 1);
+ }
+ last = idx + 1;
+ }
+ if (last < o.length()) {
+ fields.add(last < 0 ? 0 : last);
+ fields.add(o.length() - 1);
+ }
+ }
+ }
+ return fields;
+ }
+
+ public class Key {
+ int startField;
+ int startChar;
+ int endField;
+ int endChar;
+ boolean ignoreBlanksStart;
+ boolean ignoreBlanksEnd;
+ boolean caseInsensitive;
+ boolean reverse;
+ boolean numeric;
+
+ public Key(String str) {
+ boolean modifiers = false;
+ boolean startPart = true;
+ boolean inField = true;
+ boolean inChar = false;
+ for (char c : str.toCharArray()) {
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (!inField && !inChar) {
+ throw new IllegalArgumentException("Bad field syntax: " + str);
+ }
+ if (startPart) {
+ if (inChar) {
+ startChar = startChar * 10 + (c - '0');
+ } else {
+ startField = startField * 10 + (c - '0');
+ }
+ } else {
+ if (inChar) {
+ endChar = endChar * 10 + (c - '0');
+ } else {
+ endField = endField * 10 + (c - '0');
+ }
+ }
+ break;
+ case '.':
+ if (!inField) {
+ throw new IllegalArgumentException("Bad field syntax: " + str);
+ }
+ inField = false;
+ inChar = true;
+ break;
+ case 'n':
+ inField = false;
+ inChar = false;
+ modifiers = true;
+ numeric = true;
+ break;
+ case 'f':
+ inField = false;
+ inChar = false;
+ modifiers = true;
+ caseInsensitive = true;
+ break;
+ case 'r':
+ inField = false;
+ inChar = false;
+ modifiers = true;
+ reverse = true;
+ break;
+ case 'b':
+ inField = false;
+ inChar = false;
+ modifiers = true;
+ if (startPart) {
+ ignoreBlanksStart = true;
+ } else {
+ ignoreBlanksEnd = true;
+ }
+ break;
+ case ',':
+ inField = true;
+ inChar = false;
+ startPart = false;
+ break;
+ default:
+ throw new IllegalArgumentException("Bad field syntax: " + str);
+ }
+ }
+ if (!modifiers) {
+ ignoreBlanksStart = ignoreBlanksEnd = SortComparator.this.ignoreBlanks;
+ reverse = SortComparator.this.reverse;
+ caseInsensitive = SortComparator.this.caseInsensitive;
+ numeric = SortComparator.this.numeric;
+ }
+ if (startField < 1) {
+ throw new IllegalArgumentException("Bad field syntax: " + str);
+ }
+ }
+ }
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
new file mode 100644
index 0000000..0311f1b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+
+public class Procedural {
+ static final String[] functions = {"each", "if", "not", "throw", "try", "until",
+ "while"};
+
+ public List<Object> each(CommandSession session, Collection<Object> list,
+ Function closure) throws Exception {
+ List<Object> args = new ArrayList<Object>();
+ List<Object> results = new ArrayList<Object>();
+ args.add(null);
+
+ for (Object x : list) {
+ checkInterrupt();
+ args.set(0, x);
+ results.add(closure.execute(session, args));
+ }
+
+ return results;
+ }
+
+ public Object _if(CommandSession session, Function[] fns) throws Exception {
+ int length = fns.length;
+ if (length < 2) {
+ throw new IllegalArgumentException(
+ "Usage: if {condition} {if-action} ... {else-action}");
+ }
+
+ for (int i = 0; i < length; ++i) {
+ if (i == length - 1 || isTrue(fns[i++].execute(session, null))) {
+ return fns[i].execute(session, null);
+ }
+ }
+
+ return null;
+ }
+
+ public boolean not(CommandSession session, Function condition) throws Exception {
+ if (null == condition) {
+ return true;
+ }
+
+ return !isTrue(condition.execute(session, null));
+ }
+
+ // Reflective.coerce() prefers to construct a new Throwable(String)
+ // than to call this method directly.
+ public void _throw(String message) {
+ throw new IllegalArgumentException(message);
+ }
+
+ public void _throw(Exception e) throws Exception {
+ throw e;
+ }
+
+ public void _throw(CommandSession session) throws Throwable {
+ Object exception = session.get("exception");
+ if (exception instanceof Throwable)
+ throw (Throwable) exception;
+ else
+ throw new IllegalArgumentException("exception not set or not Throwable.");
+ }
+
+ public Object _try(CommandSession session, Function func) throws Exception {
+ try {
+ return func.execute(session, null);
+ } catch (Exception e) {
+ session.put("exception", e);
+ return null;
+ }
+ }
+
+ public Object _try(CommandSession session, Function func, Function error)
+ throws Exception {
+ try {
+ return func.execute(session, null);
+ } catch (Exception e) {
+ session.put("exception", e);
+ return error.execute(session, null);
+ }
+ }
+
+ public void _while(CommandSession session, Function condition, Function ifTrue)
+ throws Exception {
+ while (isTrue(condition.execute(session, null))) {
+ ifTrue.execute(session, null);
+ }
+ }
+
+ public void until(CommandSession session, Function condition, Function ifTrue)
+ throws Exception {
+ while (!isTrue(condition.execute(session, null))) {
+ ifTrue.execute(session, null);
+ }
+ }
+
+ private boolean isTrue(Object result) throws InterruptedException {
+ checkInterrupt();
+
+ if (result == null)
+ return false;
+
+ if (result instanceof Boolean)
+ return ((Boolean) result).booleanValue();
+
+ if (result instanceof Number) {
+ if (0 == ((Number) result).intValue())
+ return false;
+ }
+
+ if ("".equals(result))
+ return false;
+
+ if ("0".equals(result))
+ return false;
+
+ return true;
+ }
+
+ private void checkInterrupt() throws InterruptedException {
+ if (Thread.currentThread().isInterrupted())
+ throw new InterruptedException("loop interrupted");
+ }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
new file mode 100644
index 0000000..d92fed6
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.felix.gogo.runtime.Closure;
+import org.apache.felix.gogo.runtime.CommandProxy;
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
+import org.apache.felix.gogo.runtime.Expander;
+import org.apache.felix.gogo.runtime.Reflective;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Descriptor;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.command.Parameter;
+import org.jline.builtins.Completers.CompletionData;
+import org.jline.builtins.Options;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.ParsedLine;
+import org.jline.reader.UserInterruptException;
+import org.jline.reader.impl.history.history.FileHistory;
+import org.jline.terminal.Terminal;
+
+public class Shell {
+
+ public static final String VAR_COMPLETIONS = ".completions";
+ public static final String VAR_COMMAND_LINE = ".commandLine";
+ public static final String VAR_READER = ".reader";
+ public static final String VAR_SESSION = ".session";
+ public static final String VAR_PROCESSOR = ".processor";
+ public static final String VAR_TERMINAL = ".terminal";
+ public static final String VAR_EXCEPTION = "exception";
+ public static final String VAR_LOCATION = ".location";
+ public static final String VAR_PROMPT = "prompt";
+ public static final String VAR_RPROMPT = "rprompt";
+ public static final String VAR_SCOPE = "SCOPE";
+ public static final String VAR_CONTEXT = org.apache.felix.gogo.runtime.activator.Activator.CONTEXT;
+
+ static final String[] functions = {"gosh", "sh", "source", "help"};
+
+ private final URI baseURI;
+ private final String profile;
+ private final Context context;
+ private final CommandProcessor processor;
+
+ public Shell(Context context, CommandProcessor processor, Terminal terminal) {
+ this(context, processor, terminal, null);
+ }
+
+ public Shell(Context context, CommandProcessor processor, Terminal terminal, String profile) {
+ this.context = context;
+ this.processor = processor;
+ String baseDir = context.getProperty("gosh.home");
+ baseDir = (baseDir == null) ? context.getProperty("user.dir") : baseDir;
+ this.baseURI = new File(baseDir).toURI();
+ this.profile = profile != null ? profile : "gosh_profile";
+ }
+
+ public static Terminal getTerminal(CommandSession session) {
+ return (Terminal) session.get(VAR_TERMINAL);
+ }
+
+ public static LineReader getReader(CommandSession session) {
+ return (LineReader) session.get(VAR_READER);
+ }
+
+ public static CommandProcessor getProcessor(CommandSession session) {
+ return (CommandProcessor) session.get(VAR_PROCESSOR);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Map<String, List<CompletionData>> getCompletions(CommandSession session) {
+ return (Map) session.get(VAR_COMPLETIONS);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Set<String> getCommands(CommandSession session) {
+ return (Set<String>) session.get(CommandSessionImpl.COMMANDS);
+ }
+
+ public static ParsedLine getParsedLine(CommandSession session) {
+ return (ParsedLine) session.get(VAR_COMMAND_LINE);
+ }
+
+ public static String getPrompt(CommandSession session) {
+ return expand(session, VAR_PROMPT, "gl! ");
+ }
+
+ public static String getRPrompt(CommandSession session) {
+ return expand(session, VAR_RPROMPT, null);
+ }
+
+ public static String expand(CommandSession session, String name, String def) {
+ Object prompt = session.get(name);
+ if (prompt != null) {
+ try {
+ Object o = Expander.expand(
+ prompt.toString(),
+ new Closure((CommandSessionImpl) session, null, null));
+ if (o != null) {
+ return o.toString();
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ return def;
+ }
+
+ public static String resolve(CommandSession session, String command) {
+ String resolved = command;
+ if (command.indexOf(':') < 0) {
+ Set<String> commands = getCommands(session);
+ Object path = session.get(VAR_SCOPE);
+ String scopePath = (null == path ? "*" : path.toString());
+ for (String scope : scopePath.split(":")) {
+ for (String entry : commands) {
+ if ("*".equals(scope) && entry.endsWith(":" + command)
+ || entry.equals(scope + ":" + command)) {
+ resolved = entry;
+ break;
+ }
+ }
+ }
+ }
+ return resolved;
+ }
+
+ public static CharSequence readScript(URI script) throws Exception {
+ URLConnection conn = script.toURL().openConnection();
+ int length = conn.getContentLength();
+
+ if (length == -1) {
+ System.err.println("eek! unknown Contentlength for: " + script);
+ length = 10240;
+ }
+
+ InputStream in = conn.getInputStream();
+ CharBuffer cbuf = CharBuffer.allocate(length);
+ Reader reader = new InputStreamReader(in);
+ reader.read(cbuf);
+ in.close();
+ cbuf.rewind();
+
+ return cbuf;
+ }
+
+ @SuppressWarnings("unchecked")
+ static Set<String> getVariables(CommandSession session) {
+ return (Set<String>) session.get(".variables");
+ }
+
+ static URI cwd(CommandSession session) {
+ return Posix._pwd(session).toURI(); // _cwd is set by felixcommands:cd
+ }
+
+ private static <T extends Annotation> T findAnnotation(Annotation[] anns,
+ Class<T> clazz) {
+ for (int i = 0; (anns != null) && (i < anns.length); i++) {
+ if (clazz.isInstance(anns[i])) {
+ return clazz.cast(anns[i]);
+ }
+ }
+ return null;
+ }
+
+ public Object gosh(final CommandSession session, String[] argv) throws Exception {
+ final String[] usage = {
+ "gosh - execute script with arguments in a new session",
+ " args are available as session variables $1..$9 and $args.",
+ "Usage: gosh [OPTIONS] [script-file [args..]]",
+ " -c --command pass all remaining args to sub-shell",
+ " --nointeractive don't start interactive session",
+ " --login login shell (same session, reads etc/gosh_profile)",
+ " -s --noshutdown don't shutdown framework when script completes",
+ " -x --xtrace echo commands before execution",
+ " -? --help show help",
+ "If no script-file, an interactive shell is started, type $D to exit."};
+
+ Options opt = Options.compile(usage).setOptionsFirst(true).parse(argv);
+ List<String> args = opt.args();
+
+ boolean login = opt.isSet("login");
+ boolean interactive = !opt.isSet("nointeractive");
+
+ if (opt.isSet("help")) {
+ opt.usage(System.err);
+ if (login && !opt.isSet("noshutdown")) {
+ shutdown();
+ }
+ return null;
+ }
+
+ if (opt.isSet("command") && args.isEmpty()) {
+ throw opt.usageError("option --command requires argument(s)");
+ }
+
+ CommandSession newSession = (login ? session : processor.createSession(
+ session.getKeyboard(), session.getConsole(), System.err));
+
+ if (opt.isSet("xtrace")) {
+ newSession.put("echo", true);
+ }
+
+ // export variables starting with upper-case to newSession
+ for (String key : getVariables(session)) {
+ if (key.matches("[.]?[A-Z].*")) {
+ newSession.put(key, session.get(key));
+ }
+ }
+
+ Terminal terminal = getTerminal(session);
+ newSession.put(Shell.VAR_CONTEXT, context);
+ newSession.put(Shell.VAR_TERMINAL, terminal);
+ newSession.put(Shell.VAR_PROCESSOR, processor);
+ newSession.put(Shell.VAR_SESSION, session);
+ newSession.put("#TERM", (Function) (s, arguments) -> terminal.getType());
+ newSession.put("#COLUMNS", (Function) (s, arguments) -> terminal.getWidth());
+ newSession.put("#LINES", (Function) (s, arguments) -> terminal.getHeight());
+
+ LineReader reader = null;
+ if (args.isEmpty() && interactive) {
+ reader = LineReaderBuilder.builder()
+ .terminal(terminal)
+ .variables(((CommandSessionImpl) newSession).getVariables())
+ .completer(new org.jline.builtins.Completers.Completer(new JLineCompletionEnvironment(newSession)))
+ .highlighter(new Highlighter(session))
+ .history(new FileHistory(new File(System.getProperty("user.home"), ".gogo.history")))
+ .parser(new Parser())
+ .build();
+ newSession.put(Shell.VAR_READER, reader);
+ newSession.put(Shell.VAR_COMPLETIONS, new HashMap());
+ }
+
+ if (login || interactive) {
+ URI uri = baseURI.resolve("etc/" + profile);
+ if (!new File(uri).exists()) {
+ URL url = getClass().getResource("/ext/" + profile);
+ if (url == null) {
+ url = getClass().getResource("/" + profile);
+ }
+ uri = (url == null) ? null : url.toURI();
+ }
+ if (uri != null) {
+ source(newSession, uri.toString());
+ }
+ }
+
+ Object result = null;
+
+ if (args.isEmpty()) {
+ if (interactive) {
+ while (true) {
+ try {
+ reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null);
+ ParsedLine parsedLine = reader.getParsedLine();
+ if (parsedLine == null) {
+ throw new EndOfFileException();
+ }
+ try {
+ result = session.execute(((ParsedLineImpl) parsedLine).program());
+ session.put("_", result); // set $_ to last result
+
+ if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
+ System.out.println(session.format(result, Converter.INSPECT));
+ }
+ } catch (Exception e) {
+ session.put(Shell.VAR_EXCEPTION, e);
+ Object loc = session.get(Shell.VAR_LOCATION);
+
+ if (null == loc || !loc.toString().contains(":")) {
+ loc = "gogo";
+ }
+ System.out.println(loc + ": " + e.getClass().getSimpleName() + ": "
+ + e.getMessage());
+ }
+
+ } catch (UserInterruptException e) {
+ // continue;
+ } catch (EndOfFileException e) {
+ try {
+ reader.getHistory().flush();
+ } catch (IOException e1) {
+ e.addSuppressed(e1);
+ }
+ break;
+ }
+ }
+ }
+ } else {
+ CharSequence program;
+
+ if (opt.isSet("command")) {
+ StringBuilder buf = new StringBuilder();
+ for (String arg : args) {
+ if (buf.length() > 0) {
+ buf.append(' ');
+ }
+ buf.append(arg);
+ }
+ program = buf;
+ } else {
+ URI script = cwd(session).resolve(args.remove(0));
+
+ // set script arguments
+ newSession.put("0", script);
+ newSession.put("args", args);
+
+ for (int i = 0; i < args.size(); ++i) {
+ newSession.put(String.valueOf(i + 1), args.get(i));
+ }
+
+ program = readScript(script);
+ }
+
+ result = newSession.execute(program);
+ }
+
+ if (login && interactive && !opt.isSet("noshutdown")) {
+ System.out.println("gosh: stopping framework");
+ shutdown();
+ }
+
+ return result;
+ }
+
+ public Object sh(final CommandSession session, String[] argv) throws Exception {
+ return gosh(session, argv);
+ }
+
+ private void shutdown() throws Exception {
+ context.exit();
+ }
+
+ public Object source(CommandSession session, String script) throws Exception {
+ URI uri = cwd(session).resolve(script);
+ session.put("0", uri);
+ try {
+ return session.execute(readScript(uri));
+ } finally {
+ session.put("0", null); // API doesn't support remove
+ }
+ }
+
+ private Map<String, List<Method>> getReflectionCommands(CommandSession session) {
+ Map<String, List<Method>> commands = new TreeMap<String, List<Method>>();
+ Set<String> names = getCommands(session);
+ for (String name : names) {
+ Function function = (Function) session.get(name);
+ if (function instanceof CommandProxy) {
+ Object target = ((CommandProxy) function).getTarget();
+ List<Method> methods = new ArrayList<>();
+ String func = name.substring(name.indexOf(':') + 1).toLowerCase();
+ List<String> funcs = new ArrayList<>();
+ funcs.add("is" + func);
+ funcs.add("get" + func);
+ funcs.add("set" + func);
+ if (Reflective.KEYWORDS.contains(func)) {
+ funcs.add("_" + func);
+ } else {
+ funcs.add(func);
+ }
+ for (Method method : target.getClass().getMethods()) {
+ if (funcs.contains(method.getName().toLowerCase())) {
+ methods.add(method);
+ }
+ }
+ commands.put(name, methods);
+ ((CommandProxy) function).ungetTarget();
+ }
+ }
+ return commands;
+ }
+
+ @Descriptor("displays available commands")
+ public void help(CommandSession session) {
+ Map<String, List<Method>> commands = getReflectionCommands(session);
+ for (String name : commands.keySet()) {
+ System.out.println(name);
+ }
+ }
+
+ @Descriptor("displays information about a specific command")
+ public void help(CommandSession session, @Descriptor("target command") String name) {
+ Map<String, List<Method>> commands = getReflectionCommands(session);
+
+ List<Method> methods = null;
+
+ // If the specified command doesn't have a scope, then
+ // search for matching methods by ignoring the scope.
+ int scopeIdx = name.indexOf(':');
+ if (scopeIdx < 0) {
+ for (Entry<String, List<Method>> entry : commands.entrySet()) {
+ String k = entry.getKey().substring(entry.getKey().indexOf(':') + 1);
+ if (name.equals(k)) {
+ name = entry.getKey();
+ methods = entry.getValue();
+ break;
+ }
+ }
+ }
+ // Otherwise directly look up matching methods.
+ else {
+ methods = commands.get(name);
+ }
+
+ if ((methods != null) && (methods.size() > 0)) {
+ for (Method m : methods) {
+ Descriptor d = m.getAnnotation(Descriptor.class);
+ if (d == null) {
+ System.out.println("\n" + m.getName());
+ } else {
+ System.out.println("\n" + m.getName() + " - " + d.value());
+ }
+
+ System.out.println(" scope: " + name.substring(0, name.indexOf(':')));
+
+ // Get flags and options.
+ Class<?>[] paramTypes = m.getParameterTypes();
+ Map<String, Parameter> flags = new TreeMap<>();
+ Map<String, String> flagDescs = new TreeMap<>();
+ Map<String, Parameter> options = new TreeMap<>();
+ Map<String, String> optionDescs = new TreeMap<>();
+ List<String> params = new ArrayList<String>();
+ Annotation[][] anns = m.getParameterAnnotations();
+ for (int paramIdx = 0; paramIdx < anns.length; paramIdx++) {
+ Class<?> paramType = m.getParameterTypes()[paramIdx];
+ if (paramType == CommandSession.class) {
+ /* Do not bother the user with a CommandSession. */
+ continue;
+ }
+ Parameter p = findAnnotation(anns[paramIdx], Parameter.class);
+ d = findAnnotation(anns[paramIdx], Descriptor.class);
+ if (p != null) {
+ if (p.presentValue().equals(Parameter.UNSPECIFIED)) {
+ options.put(p.names()[0], p);
+ if (d != null) {
+ optionDescs.put(p.names()[0], d.value());
+ }
+ } else {
+ flags.put(p.names()[0], p);
+ if (d != null) {
+ flagDescs.put(p.names()[0], d.value());
+ }
+ }
+ } else if (d != null) {
+ params.add(paramTypes[paramIdx].getSimpleName());
+ params.add(d.value());
+ } else {
+ params.add(paramTypes[paramIdx].getSimpleName());
+ params.add("");
+ }
+ }
+
+ // Print flags and options.
+ if (flags.size() > 0) {
+ System.out.println(" flags:");
+ for (Entry<String, Parameter> entry : flags.entrySet()) {
+ // Print all aliases.
+ String[] names = entry.getValue().names();
+ System.out.print(" " + names[0]);
+ for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++) {
+ System.out.print(", " + names[aliasIdx]);
+ }
+ System.out.println(" " + flagDescs.get(entry.getKey()));
+ }
+ }
+ if (options.size() > 0) {
+ System.out.println(" options:");
+ for (Entry<String, Parameter> entry : options.entrySet()) {
+ // Print all aliases.
+ String[] names = entry.getValue().names();
+ System.out.print(" " + names[0]);
+ for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++) {
+ System.out.print(", " + names[aliasIdx]);
+ }
+ System.out.println(" "
+ + optionDescs.get(entry.getKey())
+ + ((entry.getValue().absentValue() == null) ? ""
+ : " [optional]"));
+ }
+ }
+ if (params.size() > 0) {
+ System.out.println(" parameters:");
+ for (Iterator<String> it = params.iterator(); it.hasNext(); ) {
+ System.out.println(" " + it.next() + " " + it.next());
+ }
+ }
+ }
+ }
+ }
+
+ public interface Context {
+ String getProperty(String name);
+
+ void exit() throws Exception;
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java
new file mode 100644
index 0000000..a679a60
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+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.OutputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
+
+public class ShellCommand implements Command, Runnable, SessionAware {
+
+ public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";
+ public static final String EXEC_INIT_SCRIPT = "karaf.exec.init.script";
+
+ private static final Logger LOGGER = Logger.getLogger(ShellCommand.class.getName());
+
+ private String command;
+ private InputStream in;
+ private OutputStream out;
+ private OutputStream err;
+ private ExitCallback callback;
+ private ServerSession session;
+ private CommandProcessor processor;
+ private Environment env;
+
+ public ShellCommand(CommandProcessor processor, String command) {
+ this.processor = processor;
+ this.command = command;
+ }
+
+ public void setInputStream(InputStream in) {
+ this.in = in;
+ }
+
+ public void setOutputStream(OutputStream out) {
+ this.out = out;
+ }
+
+ public void setErrorStream(OutputStream err) {
+ this.err = err;
+ }
+
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ public void setSession(ServerSession session) {
+ this.session = session;
+ }
+
+ public void start(final Environment env) throws IOException {
+ this.env = env;
+ new Thread(this).start();
+ }
+
+ public void run() {
+ int exitStatus = 0;
+ try {
+ final CommandSession session = processor.createSession(in, new PrintStream(out), new PrintStream(err));
+ for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
+ session.put(e.getKey(), e.getValue());
+ }
+ try {
+ String scriptFileName = System.getProperty(EXEC_INIT_SCRIPT);
+ if (scriptFileName == null) {
+ scriptFileName = System.getProperty(SHELL_INIT_SCRIPT);
+ }
+ executeScript(scriptFileName, session);
+ session.execute(command);
+ } catch (Throwable t) {
+ exitStatus = 1;
+ t.printStackTrace();
+ }
+ } catch (Exception e) {
+ exitStatus = 1;
+ LOGGER.log(Level.SEVERE, "Unable to start shell", e);
+ } finally {
+ ShellFactoryImpl.close(in, out, err);
+ callback.onExit(exitStatus);
+ }
+ }
+
+ public void destroy() {
+ }
+
+ private void executeScript(String scriptFileName, CommandSession session) {
+ 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.log(Level.FINE, "Error in initialization script", e);
+ } finally {
+ if (r != null) {
+ try {
+ r.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java
new file mode 100644
index 0000000..d0a0a45
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+
+public class ShellCommandFactory implements CommandFactory {
+
+ private CommandProcessor processor;
+
+ public ShellCommandFactory(CommandProcessor processor) {
+ this.processor = processor;
+ }
+
+ public Command createCommand(String command) {
+ return new ShellCommand(processor, command);
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
new file mode 100644
index 0000000..ad0cc8d
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Map;
+
+import org.apache.felix.gogo.jline.Shell;
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.channel.PtyMode;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.Signal;
+import org.apache.sshd.server.SignalListener;
+import org.apache.sshd.server.session.ServerSession;
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Attributes.ControlChar;
+import org.jline.terminal.Attributes.InputFlag;
+import org.jline.terminal.Attributes.LocalFlag;
+import org.jline.terminal.Attributes.OutputFlag;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+
+/**
+ * SSHD {@link org.apache.sshd.server.Command} factory which provides access to
+ * Shell.
+ */
+public class ShellFactoryImpl implements Factory<Command> {
+ private final CommandProcessor processor;
+
+ public ShellFactoryImpl(CommandProcessor processor) {
+ this.processor = processor;
+ }
+
+ private static void flush(OutputStream... streams) {
+ for (OutputStream s : streams) {
+ try {
+ s.flush();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ static void close(Closeable... closeables) {
+ for (Closeable c : closeables) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ public Command create() {
+ return new ShellImpl();
+ }
+
+ public class ShellImpl implements Command, SessionAware {
+ private InputStream in;
+
+ private OutputStream out;
+
+ private OutputStream err;
+
+ private ExitCallback callback;
+
+ private ServerSession session;
+
+ private boolean closed;
+
+ public void setInputStream(final InputStream in) {
+ this.in = in;
+ }
+
+ public void setOutputStream(final OutputStream out) {
+ this.out = out;
+ }
+
+ public void setErrorStream(final OutputStream err) {
+ this.err = err;
+ }
+
+ public void setExitCallback(ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ public void setSession(ServerSession session) {
+ this.session = session;
+ }
+
+ public void start(final Environment env) throws IOException {
+ try {
+ new Thread() {
+ public void run() {
+ try {
+ ShellImpl.this.run(env);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }.start();
+ } catch (Exception e) {
+ throw (IOException) new IOException("Unable to start shell").initCause(e);
+ }
+ }
+
+ public void run(Environment env) throws Exception {
+ try {
+ Terminal terminal = TerminalBuilder.builder()
+ .name("gogo")
+ .type(env.getEnv().get("TERM"))
+ .system(false)
+ .streams(in, out)
+ .build();
+ terminal.setSize(new Size(Integer.parseInt(env.getEnv().get("COLUMNS")),
+ Integer.parseInt(env.getEnv().get("LINES"))));
+ Attributes attr = terminal.getAttributes();
+ for (Map.Entry<PtyMode, Integer> e : env.getPtyModes().entrySet()) {
+ switch (e.getKey()) {
+ case VINTR:
+ attr.setControlChar(ControlChar.VINTR, e.getValue());
+ break;
+ case VQUIT:
+ attr.setControlChar(ControlChar.VQUIT, e.getValue());
+ break;
+ case VERASE:
+ attr.setControlChar(ControlChar.VERASE, e.getValue());
+ break;
+ case VKILL:
+ attr.setControlChar(ControlChar.VKILL, e.getValue());
+ break;
+ case VEOF:
+ attr.setControlChar(ControlChar.VEOF, e.getValue());
+ break;
+ case VEOL:
+ attr.setControlChar(ControlChar.VEOL, e.getValue());
+ break;
+ case VEOL2:
+ attr.setControlChar(ControlChar.VEOL2, e.getValue());
+ break;
+ case VSTART:
+ attr.setControlChar(ControlChar.VSTART, e.getValue());
+ break;
+ case VSTOP:
+ attr.setControlChar(ControlChar.VSTOP, e.getValue());
+ break;
+ case VSUSP:
+ attr.setControlChar(ControlChar.VSUSP, e.getValue());
+ break;
+ case VDSUSP:
+ attr.setControlChar(ControlChar.VDSUSP, e.getValue());
+ break;
+ case VREPRINT:
+ attr.setControlChar(ControlChar.VREPRINT, e.getValue());
+ break;
+ case VWERASE:
+ attr.setControlChar(ControlChar.VWERASE, e.getValue());
+ break;
+ case VLNEXT:
+ attr.setControlChar(ControlChar.VLNEXT, e.getValue());
+ break;
+ /*
+ case VFLUSH:
+ attr.setControlChar(ControlChar.VMIN, e.getValue());
+ break;
+ case VSWTCH:
+ attr.setControlChar(ControlChar.VTIME, e.getValue());
+ break;
+ */
+ case VSTATUS:
+ attr.setControlChar(ControlChar.VSTATUS, e.getValue());
+ break;
+ case VDISCARD:
+ attr.setControlChar(ControlChar.VDISCARD, e.getValue());
+ break;
+ case ECHO:
+ attr.setLocalFlag(LocalFlag.ECHO, e.getValue() != 0);
+ break;
+ case ICANON:
+ attr.setLocalFlag(LocalFlag.ICANON, e.getValue() != 0);
+ break;
+ case ISIG:
+ attr.setLocalFlag(LocalFlag.ISIG, e.getValue() != 0);
+ break;
+ case ICRNL:
+ attr.setInputFlag(InputFlag.ICRNL, e.getValue() != 0);
+ break;
+ case INLCR:
+ attr.setInputFlag(InputFlag.INLCR, e.getValue() != 0);
+ break;
+ case IGNCR:
+ attr.setInputFlag(InputFlag.IGNCR, e.getValue() != 0);
+ break;
+ case OCRNL:
+ attr.setOutputFlag(OutputFlag.OCRNL, e.getValue() != 0);
+ break;
+ case ONLCR:
+ attr.setOutputFlag(OutputFlag.ONLCR, e.getValue() != 0);
+ break;
+ case ONLRET:
+ attr.setOutputFlag(OutputFlag.ONLRET, e.getValue() != 0);
+ break;
+ case OPOST:
+ attr.setOutputFlag(OutputFlag.OPOST, e.getValue() != 0);
+ break;
+ }
+ }
+ terminal.setAttributes(attr);
+ PrintStream pout = new PrintStream(terminal.output());
+ final CommandSession session = processor.createSession(terminal.input(), pout, pout);
+ for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
+ session.put(e.getKey(), e.getValue());
+ }
+ env.addSignalListener(new SignalListener() {
+ @Override
+ public void signal(Signal signal) {
+ terminal.setSize(new Size(Integer.parseInt(env.getEnv().get("COLUMNS")),
+ Integer.parseInt(env.getEnv().get("LINES"))));
+ terminal.raise(Terminal.Signal.WINCH);
+ }
+ }, Signal.WINCH);
+ Context context = new Context() {
+ @Override
+ public String getProperty(String name) {
+ return System.getProperty(name);
+ }
+
+ @Override
+ public void exit() throws Exception {
+ destroy();
+ }
+ };
+ new Shell(context, processor, terminal).gosh(session, new String[]{"--login"});
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ public void destroy() {
+ if (!closed) {
+ closed = true;
+ ShellFactoryImpl.flush(out, err);
+ ShellFactoryImpl.close(in, out, err);
+ callback.onExit(0);
+ }
+ }
+
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/Ssh.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/Ssh.java
new file mode 100644
index 0000000..72e7ac1
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/Ssh.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.ServerBuilder;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.jline.builtins.Options;
+
+public class Ssh {
+
+ public static final String[] functions = {"sshd"};
+
+ private static final int defaultPort = 2022;
+
+ private final CommandProcessor processor;
+ private SshServer server;
+ private Object context;
+ private int port;
+ private String ip;
+
+ public Ssh(CommandProcessor processor) {
+ this.processor = processor;
+ }
+
+ public void sshd(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {"sshd - start an ssh server",
+ "Usage: sshd [-i ip] [-p port] start | stop | status",
+ " -i --ip=INTERFACE listen interface (default=127.0.0.1)",
+ " -p --port=PORT listen port (default=" + defaultPort + ")",
+ " -? --help show help"};
+
+ Options opt = Options.compile(usage).parse(argv);
+ List<String> args = opt.args();
+
+ if (opt.isSet("help") || args.isEmpty()) {
+ opt.usage(System.err);
+ return;
+ }
+
+ String command = args.get(0);
+
+ if ("start".equals(command)) {
+ if (server != null) {
+ throw new IllegalStateException("sshd is already running on port " + port);
+ }
+ ip = opt.get("ip");
+ port = opt.getNumber("port");
+ context = session.get(org.apache.felix.gogo.runtime.activator.Activator.CONTEXT);
+ start();
+ status();
+ } else if ("stop".equals(command)) {
+ if (server == null) {
+ throw new IllegalStateException("sshd is not running.");
+ }
+ stop();
+ } else if ("status".equals(command)) {
+ status();
+ } else {
+ throw opt.usageError("bad command: " + command);
+ }
+
+ }
+
+ private void status() {
+ if (server != null) {
+ System.out.println("sshd is running on " + ip + ":" + port);
+ } else {
+ System.out.println("sshd is not running.");
+ }
+ }
+
+ private void start() throws IOException {
+ server = ServerBuilder.builder().build();
+ server.setPort(port);
+ server.setHost(ip);
+ server.setShellFactory(new ShellFactoryImpl(processor));
+ server.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(new ShellCommandFactory(processor)).build());
+ server.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(
+ new SftpSubsystemFactory.Builder().build()
+ ));
+ server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
+ server.start();
+ }
+
+ private void stop() throws IOException {
+ server.stop();
+ }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/BootException.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/BootException.java
new file mode 100644
index 0000000..a29a824
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/BootException.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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+/**
+ * Class that implements a BootException.<br>
+ * This exception will flag a broken boot process,
+ * which expresses startup failure and unavailabilty
+ * of telnet service for the container application.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public class BootException extends Exception {
+
+ /**
+ * Constructor method for a BootException.<br>
+ *
+ * @param msg String that contains an understandable failure message.
+ */
+ public BootException(String msg) {
+ super(msg);
+ }//constructor
+
+}//class BootException
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Connection.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Connection.java
new file mode 100644
index 0000000..b5f90d3
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Connection.java
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class that implements a connection with this telnet daemon.<br>
+ * It is derived from java.lang.Thread, which reflects the architecture
+ * constraint of one thread per connection. This might seem a waste of
+ * resources, but as a matter of fact sharing threads would require a
+ * far more complex imlementation, due to the fact that telnet is not a
+ * stateless protocol (i.e. alive throughout a session of multiple requests
+ * and responses).<br>
+ * Each Connection instance is created by the listeners ConnectionManager
+ * instance, making it part of a threadgroup and passing in an associated
+ * ConnectionData instance, that holds vital information about the connection.
+ * Be sure to take a look at their documention.<br>
+ * <p/>
+ * Once the thread has started and is running, it will get a login
+ * shell instance from the ShellManager and run passing its own reference.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see ConnectionManager
+ * @see ConnectionData
+ */
+public abstract class Connection
+ extends Thread {
+
+ private static final Logger LOG = Logger.getLogger(Connection.class.getName());
+ private static int number; //unique number for a thread in the thread group
+ private boolean dead;
+ private List<ConnectionListener> listeners;
+
+ //Associations
+ private ConnectionData connectionData; //associated information
+
+ /**
+ * Constructs a TelnetConnection by invoking its parent constructor
+ * and setting of various members.<br>
+ * Subsequently instantiates the whole i/o subsystem, negotiating
+ * telnet protocol level options etc.<br>
+ *
+ * @param tcg ThreadGroup that this instance is running in.
+ * @param cd ConnectionData instance containing all vital information
+ * of this connection.
+ * @see ConnectionData
+ */
+ public Connection(ThreadGroup tcg, ConnectionData cd) {
+ super(tcg, ("Connection" + (++number)));
+
+ connectionData = cd;
+ //init the connection listeners for events
+ //(there should actually be only one or two)
+ listeners = new CopyOnWriteArrayList<ConnectionListener>();
+ dead = false;
+ }//constructor
+
+ /**
+ * Method overloaded to implement following behaviour:
+ * <ol>
+ * <li> On first entry, retrieve an instance of the configured
+ * login shell from the ShellManager and run it.
+ * <li> Handle a shell switch or close down disgracefully when
+ * problems (i.e. unhandled unchecked exceptions) occur in the
+ * running shell.
+ * </ol>
+ */
+ public void run() {
+ try {
+ doRun();
+
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "run()", ex); //Handle properly
+ } finally {
+ //call close if not dead already
+ if (!dead) {
+ close();
+ }
+ }
+ LOG.log(Level.FINE, "run():: Returning from " + this.toString());
+ }//run
+
+ protected abstract void doRun() throws Exception;
+
+ protected abstract void doClose() throws Exception;
+
+ /**
+ * Method to access the associated connection data.
+ *
+ * @return ConnectionData associated with the Connection instance.
+ * @see ConnectionData
+ */
+ public ConnectionData getConnectionData() {
+ return connectionData;
+ }//getConnectionData
+
+ /**
+ * Closes the connection and its underlying i/o and network
+ * resources.<br>
+ */
+ public synchronized void close() {
+ if (dead) {
+ return;
+ } else {
+ try {
+ //connection dead
+ dead = true;
+ //close i/o
+ doClose();
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "close()", ex);
+ //handle
+ }
+ try {
+ //close socket
+ connectionData.getSocket().close();
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "close()", ex);
+ //handle
+ }
+ try {
+ //register closed connection in ConnectionManager
+ connectionData.getManager().registerClosedConnection(this);
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "close()", ex);
+ //handle
+ }
+ try {
+ //try to interrupt it
+ interrupt();
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "close()", ex);
+ //handle
+ }
+
+
+ LOG.log(Level.FINE, "Closed " + this.toString() + " and inactive.");
+ }
+ }//close
+
+ /**
+ * Returns if a connection has been closed.<br>
+ *
+ * @return the state of the connection.
+ */
+ public boolean isActive() {
+ return !dead;
+ }//isClosed
+
+ /****** Event handling ****************/
+
+ /**
+ * Method that registers a ConnectionListener with the
+ * Connection instance.
+ *
+ * @param cl ConnectionListener to be registered.
+ * @see ConnectionListener
+ */
+ public void addConnectionListener(ConnectionListener cl) {
+ listeners.add(cl);
+ }//addConnectionListener
+
+ /**
+ * Method that removes a ConnectionListener from the
+ * Connection instance.
+ *
+ * @param cl ConnectionListener to be removed.
+ * @see ConnectionListener
+ */
+ public void removeConnectionListener(ConnectionListener cl) {
+ listeners.remove(cl);
+ }//removeConnectionListener
+
+
+ /**
+ * Method called by the io subsystem to pass on a
+ * "low-level" event. It will be properly delegated to
+ * all registered listeners.
+ *
+ * @param ce ConnectionEvent to be processed.
+ * @see ConnectionEvent
+ */
+ public void processConnectionEvent(ConnectionEvent ce) {
+ for (ConnectionListener cl : listeners) {
+ switch (ce.getType()) {
+ case CONNECTION_IDLE:
+ cl.connectionIdle(ce);
+ break;
+ case CONNECTION_TIMEDOUT:
+ cl.connectionTimedOut(ce);
+ break;
+ case CONNECTION_LOGOUTREQUEST:
+ cl.connectionLogoutRequest(ce);
+ break;
+ case CONNECTION_BREAK:
+ cl.connectionSentBreak(ce);
+ break;
+ case CONNECTION_TERMINAL_GEOMETRY_CHANGED:
+ cl.connectionTerminalGeometryChanged(ce);
+ }
+ }
+ }//processConnectionEvent
+
+}//class Connection
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionData.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionData.java
new file mode 100644
index 0000000..ca7288b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionData.java
@@ -0,0 +1,449 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * An utility class that is used to store and allow retrieval
+ * of all data associated with a connection.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see Connection
+ */
+public class ConnectionData {
+
+ //Associations
+ private ConnectionManager connectionManager; //the connection's ConnectionManager
+ private Socket socket; //the connection's socket
+ private InetAddress address; //the connection's IP Address Object
+ private Map<String, String> environment; //the environment
+
+ //Members
+ private String hostName; //cache for the hostname
+ private String hostAddress; //cache for the host ip
+ private int port; //port of the connection
+ private Locale locale; //locale of the connection
+ private long lastActivity; //timestamp for the last activity
+ private boolean warned; //warned flag
+ private String negotiatedTerminalType; //negotiated TerminalType as String
+ private int[] terminalGeometry; //negotiated terminal geometry
+ private boolean terminalGeometryChanged = true; //flag for changes in the terminal geometry
+ private String loginShell; //the login shell
+ private boolean lineMode = false;
+
+ /**
+ * Constructs a ConnectionData instance storing vital
+ * information about a connection.
+ *
+ * @param sock Socket of the inbound connection.
+ */
+ public ConnectionData(Socket sock, ConnectionManager cm) {
+ socket = sock;
+ connectionManager = cm;
+ address = sock.getInetAddress();
+ setHostName();
+ setHostAddress();
+ setLocale();
+ port = sock.getPort();
+ //this will set a default geometry and terminal type for the terminal
+ terminalGeometry = new int[2];
+ terminalGeometry[0] = 80; //width
+ terminalGeometry[1] = 25; //height
+ negotiatedTerminalType = "default";
+ environment = new HashMap<String, String>(20);
+ //this will stamp the first activity for validity :)
+ activity();
+ }//ConnectionData
+
+
+ /**
+ * Returns a reference to the ConnectionManager the
+ * connection is associated with.
+ *
+ * @return Reference to the associated ConnectionManager.
+ * @see ConnectionManager
+ */
+ public ConnectionManager getManager() {
+ return connectionManager;
+ }//getManager
+
+ /**
+ * Returns a reference to the socket the Connection
+ * is associated with.
+ *
+ * @return Reference to the associated Socket.
+ * @see java.net.Socket
+ */
+ public Socket getSocket() {
+ return socket;
+ }//getSocket
+
+ /**
+ * Returns the remote port to which the socket is connected.
+ *
+ * @return String that contains the remote port number to which the socket is connected.
+ */
+ public int getPort() {
+ return port;
+ }//getPort
+
+ /**
+ * Returns the fully qualified host name for the connection's IP address.<br>
+ * The name is cached on creation for performance reasons. Subsequent calls
+ * will not result in resolve queries.
+ *
+ * @return String that contains the fully qualified host name for this address.
+ */
+ public String getHostName() {
+ return hostName;
+ }//getHostName
+
+ /**
+ * Returns the IP address of the connection.
+ *
+ * @return String that contains the connection's IP address.<br>
+ * The format "%d.%d.%d.%d" is well known, where %d goes from zero to 255.
+ */
+ public String getHostAddress() {
+ return hostAddress;
+ }//getHostAddress
+
+ /**
+ * Returns the InetAddress object associated with the connection.
+ *
+ * @return InetAddress associated with the connection.
+ */
+ public InetAddress getInetAddress() {
+ return address;
+ }//getInetAddress
+
+ /**
+ * Returns the Locale object associated with the connection
+ * by carrying out a simple domain match. <br>
+ * This can either be effective, if your users are really
+ * home in the country they are connecting from,
+ * or ineffective if they are on the move getting connected
+ * from anywhere in the world.<br>
+ * <br>
+ * Yet this gives the chance of capturing a default locale
+ * and starting from some point. On application context
+ * this can be by far better handled, so be aware that
+ * it makes sense to spend some thoughts on that thing when you
+ * build your application.
+ *
+ * @return the Locale object "guessed" for the connection based
+ * on its host name.
+ */
+ public Locale getLocale() {
+ return locale;
+ }//getLocale
+
+
+ /**
+ * Returns a timestamp of the last activity that happened on
+ * the associated connection.
+ *
+ * @return the timestamp as a long representing the difference,
+ * measured in milliseconds, between the current time and
+ * midnight, January 1, 1970 UTC.
+ */
+ public long getLastActivity() {
+ return lastActivity;
+ }//getLastActivity
+
+
+ /**
+ * Sets a new timestamp to the actual time in millis
+ * retrieved from the System. This will remove an idle warning
+ * flag if it has been set. Note that you can use this behaviour
+ * to implement your own complex idle timespan policies within
+ * the context of your application.<br>
+ * The check frequency of the ConnectionManager should just be set
+ * according to the lowest time to warning and time to disconnect
+ * requirements.
+ */
+ public void activity() {
+ warned = false;
+ lastActivity = System.currentTimeMillis();
+ }//setLastActivity
+
+ /**
+ * Returns the state of the idle warning flag, which
+ * will be true if a warning has been issued, and false
+ * if not.
+ *
+ * @return the state of the idle warning flag.
+ */
+ public boolean isWarned() {
+ return warned;
+ }//isWarned
+
+ /**
+ * Sets the state of the idle warning flag.<br>
+ * Note that this method will also update the
+ * the timestamp if the idle warning flag is removed,
+ * which means its kind of a second way to achieve the
+ * same thing as with the activity method.
+ *
+ * @param bool true if a warning is to be issued,
+ * false if to be removed.
+ * @see #activity()
+ */
+ public void setWarned(boolean bool) {
+ warned = bool;
+ if (!bool) {
+ lastActivity = System.currentTimeMillis();
+ }
+ }//setWarned
+
+ /**
+ * Sets the terminal geometry data.<br>
+ * <em>This method should not be called explicitly
+ * by the application (i.e. the its here for the io subsystem).</em><br>
+ * A call will set the terminal geometry changed flag.
+ *
+ * @param width of the terminal in columns.
+ * @param height of the terminal in rows.
+ */
+ public void setTerminalGeometry(int width, int height) {
+ terminalGeometry[0] = width;
+ terminalGeometry[1] = height;
+ terminalGeometryChanged = true;
+ }//setTerminalGeometry
+
+ /**
+ * Returns the terminal geometry in an array of two integers.
+ * <ul>
+ * <li>index 0: Width in columns.
+ * <li>index 1: Height in rows.
+ * </ul>
+ * A call will reset the terminal geometry changed flag.
+ *
+ * @return integer array containing width and height.
+ */
+ public int[] getTerminalGeometry() {
+ //we toggle the flag because the change should now be known
+ if (terminalGeometryChanged) terminalGeometryChanged = false;
+ return terminalGeometry;
+ }//getTerminalGeometry
+
+ /**
+ * Returns the width of the terminal in columns for convenience.
+ *
+ * @return the number of columns.
+ */
+ public int getTerminalColumns() {
+ return terminalGeometry[0];
+ }//getTerminalColumns
+
+ /**
+ * Returns the height of the terminal in rows for convenience.
+ *
+ * @return the number of rows.
+ */
+ public int getTerminalRows() {
+ return terminalGeometry[1];
+ }//getTerminalRows
+
+ /**
+ * Returns the state of the terminal geometry changed flag,
+ * which will be true if it has been set, and false
+ * if not.
+ *
+ * @return the state of the terminal geometry changed flag.
+ */
+ public boolean isTerminalGeometryChanged() {
+ return terminalGeometryChanged;
+ }//isTerminalGeometryChanged
+
+ /**
+ * Returns the terminal type that has been negotiated
+ * between the telnet client and the telnet server, in
+ * of a String.<br>
+ *
+ * @return the negotiated terminal type as String.
+ */
+ public String getNegotiatedTerminalType() {
+ return negotiatedTerminalType;
+ }//getNegotiatedTerminalType
+
+ /**
+ * Sets the terminal type that has been negotiated
+ * between telnet client and telnet server, in form of
+ * a String.<br>
+ * <p/>
+ * <em>This method should not be called explicitly
+ * by the application (i.e. the its here for the io subsystem).</em><br>
+ *
+ * @param termtype the negotiated terminal type as String.
+ */
+ public void setNegotiatedTerminalType(String termtype) {
+ negotiatedTerminalType = termtype;
+ }//setNegotiatedTerminalType
+
+ /**
+ * Returns the hashmap for storing and
+ * retrieving environment variables to be passed
+ * between shells.
+ *
+ * @return a <tt>HashMap</tt> instance.
+ */
+ public Map<String, String> getEnvironment() {
+ return environment;
+ }//getEnvironment
+
+ /**
+ * Returns the login shell name.
+ *
+ * @return the shell name as string.
+ */
+ public String getLoginShell() {
+ return loginShell;
+ }//getLoginShell
+
+ /**
+ * Sets the login shell name.
+ *
+ * @param s the shell name as string.
+ */
+ public void setLoginShell(String s) {
+ loginShell = s;
+ }//setLoginShell
+
+ /**
+ * Tests if in line mode.
+ *
+ * @return true if in line mode, false otherwise
+ */
+ public boolean isLineMode() {
+ return lineMode;
+ }//isLineMode
+
+ /**
+ * Sets the line mode flag for the connection.
+ * Note that the setting will only be used at
+ * startup at the moment.
+ *
+ * @param b true if to be initialized in linemode,
+ * false otherwise.
+ */
+ public void setLineMode(boolean b) {
+ lineMode = b;
+ }//setLineMode
+
+ /**
+ * Mutator for HostName cache
+ */
+ private void setHostName() {
+ hostName = address.getHostName();
+ }//setHostName
+
+ /**
+ * Mutator for HostAddress cache
+ */
+ private void setHostAddress() {
+ hostAddress = address.getHostAddress();
+ }//setHostAddress
+
+ /**
+ * Mutator for Locale
+ * Sets a Locale derived from the hostname,
+ * or the default which is Locale.ENGLISH if something
+ * goes wrong.
+ * The localhost represents a problem for example :)
+ */
+ private void setLocale() {
+ String country = getHostName();
+ try {
+ country = country.substring(country.lastIndexOf(".") + 1);
+ if (country.equals("at")) {
+ locale = new Locale("de", "AT");
+ } else if (country.equals("de")) {
+ locale = new Locale("de", "DE");
+ } else if (country.equals("mx")) {
+ locale = new Locale("es", "MX");
+ } else if (country.equals("es")) {
+ locale = new Locale("es", "ES");
+ } else if (country.equals("it")) {
+ locale = Locale.ITALY;
+ } else if (country.equals("fr")) {
+ locale = Locale.FRANCE;
+ } else if (country.equals("uk")) {
+ locale = new Locale("en", "GB");
+ } else if (country.equals("arpa")) {
+ locale = Locale.US;
+ } else if (country.equals("com")) {
+ locale = Locale.US;
+ } else if (country.equals("edu")) {
+ locale = Locale.US;
+ } else if (country.equals("gov")) {
+ locale = Locale.US;
+ } else if (country.equals("org")) {
+ locale = Locale.US;
+ } else if (country.equals("mil")) {
+ locale = Locale.US;
+ } else {
+ //default to english
+ locale = Locale.ENGLISH;
+ }
+ } catch (Exception ex) {
+ //default to english
+ locale = Locale.ENGLISH;
+ }
+ }//setLocale
+
+}//class ConnectionData
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionEvent.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionEvent.java
new file mode 100644
index 0000000..3a75a4d
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionEvent.java
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+/**
+ * Class implmenting a ConnectionEvent.<br>
+ * These events are used to communicate things that are
+ * supposed to be handled within the application context.
+ * These events are processed by the Connection instance
+ * calling upon its registered listeners.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see Connection
+ * @see ConnectionListener
+ */
+public class ConnectionEvent {
+
+ private final Connection source;
+ private final Type type;
+ /**
+ * Constructs a new instance of a ConnectionEvent
+ * with a given source (Connection) and a given type.
+ *
+ * @param source Connection that represents the source of this event.
+ * @param type int that contains one of the defined event types.
+ */
+ public ConnectionEvent(Connection source, Type type) {
+ this.type = type;
+ this.source = source;
+ }//constructor
+
+ /**
+ * Accessor method returning the source of the
+ * ConnectionEvent instance.
+ *
+ * @return Connection representing the source.
+ */
+ public Connection getSource() {
+ return source;
+ }//getSource
+
+ /**
+ * Method that helps identifying the type.
+ *
+ * @return Event type.
+ */
+ public Type getType() {
+ return type;
+ }//getType
+
+ public enum Type {
+ /**
+ * Defines the connection idle event type.<br>
+ * It occurs if a connection has been idle exceeding
+ * the configured time to warning.
+ */
+ CONNECTION_IDLE,
+
+ /**
+ * Defines the connection timed out event type.<br>
+ * It occurs if a connection has been idle exceeding
+ * the configured time to warning and the configured time
+ * to timedout.
+ */
+ CONNECTION_TIMEDOUT,
+
+ /**
+ * Defines the connection requested logout event type.<br>
+ * It occurs if a connection requested disgraceful logout by
+ * sending a <Ctrl>-<D> key combination.
+ */
+ CONNECTION_LOGOUTREQUEST,
+
+ /**
+ * Defines the connection sent break event type.<br>
+ * It occurs when the connection sent a NVT BREAK.
+ */
+ CONNECTION_BREAK,
+
+ /**
+ * Defines the connection geometry event type.
+ * It occurs when the connection sent a NAWS.
+ */
+ CONNECTION_TERMINAL_GEOMETRY_CHANGED;
+ }
+
+
+}//class ConnectionEvent
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionFilter.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionFilter.java
new file mode 100644
index 0000000..e948166
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionFilter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.net.InetAddress;
+
+/**
+ * Interface defining a generic IP level connection
+ * filter.<br>
+ * Due to the fact that this task depends heavily on
+ * application context, I chose a very generic way
+ * of applying IP level connection filtering.
+ * <br><br>
+ * Implementations should consider following issues:
+ * <ul>
+ * <li>performance
+ * <li>administration (maybe via an admin shell)
+ * <li>logging denials
+ * </ul>
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public interface ConnectionFilter {
+
+ /**
+ * Tests if a given ip address is allowed to connect.
+ *
+ * @param ip the address to be tested.
+ * @return true if allowed to connect, false otherwise.
+ */
+ boolean isAllowed(InetAddress ip);
+
+}//interface ConnectionFilter
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionListener.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionListener.java
new file mode 100644
index 0000000..38ed90b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionListener.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+
+/**
+ * Interface to be implemented if a class wants to
+ * qualify as a ConnectionListener.<br>
+ * Note that a Shell is per contract also forced to
+ * implement this interface.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see ConnectionEvent
+ */
+public interface ConnectionListener {
+
+ /**
+ * Called when a CONNECTION_IDLE event occured.
+ *
+ * @param ce ConnectionEvent instance.
+ * @see ConnectionEvent.Type#CONNECTION_IDLE
+ */
+ void connectionIdle(ConnectionEvent ce);
+
+ /**
+ * Called when a CONNECTION_TIMEDOUT event occured.
+ *
+ * @param ce ConnectionEvent instance.
+ * @see ConnectionEvent.Type#CONNECTION_TIMEDOUT
+ */
+ void connectionTimedOut(ConnectionEvent ce);
+
+ /**
+ * Called when a CONNECTION_LOGOUTREQUEST occured.
+ *
+ * @param ce ConnectionEvent instance.
+ * @see ConnectionEvent.Type#CONNECTION_LOGOUTREQUEST
+ */
+ void connectionLogoutRequest(ConnectionEvent ce);
+
+ /**
+ * Called when a CONNECTION_BREAK event occured.
+ *
+ * @param ce ConnectionEvent instance.
+ * @see ConnectionEvent.Type#CONNECTION_BREAK
+ */
+ void connectionSentBreak(ConnectionEvent ce);
+
+ /**
+ * Called when a CONNECTION_TERMINAL_GEOMETRY_CHANGED event occured.
+ *
+ * @param ce ConnectionEvent instance.
+ * @see ConnectionEvent.Type#CONNECTION_TERMINAL_GEOMETRY_CHANGED
+ */
+ void connectionTerminalGeometryChanged(ConnectionEvent ce);
+
+}//interface ConnectionListener
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionManager.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionManager.java
new file mode 100644
index 0000000..d169a49
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionManager.java
@@ -0,0 +1,393 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class that takes care for active and queued connection.
+ * Housekeeping is done also for connections that were just broken
+ * off, or exceeded their timeout.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public abstract class ConnectionManager implements Runnable {
+
+ private static Logger LOG = Logger.getLogger(ConnectionManager.class.getName());
+ private final List<Connection> openConnections;
+ private Thread thread;
+ private ThreadGroup threadGroup; //ThreadGroup all connections run in
+ private Stack<Connection> closedConnections;
+ private ConnectionFilter connectionFilter; //reference to the connection filter
+ private int maxConnections; //maximum allowed connections stored from the properties
+ private int warningTimeout; //time to idle warning
+ private int disconnectTimeout; //time to idle diconnection
+ private int housekeepingInterval; //interval for managing cleanups
+ private String loginShell;
+ private boolean lineMode = false;
+ private boolean stopping = false;
+
+ public ConnectionManager() {
+ threadGroup = new ThreadGroup(toString() + "Connections");
+ closedConnections = new Stack<Connection>();
+ openConnections = Collections.synchronizedList(new ArrayList<Connection>(100));
+ }
+
+ public ConnectionManager(int con, int timew, int timedis, int hoke, ConnectionFilter filter, String lsh, boolean lm) {
+ this();
+ connectionFilter = filter;
+ loginShell = lsh;
+ lineMode = lm;
+ maxConnections = con;
+ warningTimeout = timew;
+ disconnectTimeout = timedis;
+ housekeepingInterval = hoke;
+ }//constructor
+
+ /**
+ * Gets the active ConnectionFilter instance or
+ * returns null if no filter is set.
+ *
+ * @return the managers ConnectionFilter.
+ */
+ public ConnectionFilter getConnectionFilter() {
+ return connectionFilter;
+ }//getConnectionFilter
+
+ /**
+ * Set a connection filter for this
+ * ConnectionManager instance. The filter is used to handle
+ * IP level allow/deny of incoming connections.
+ *
+ * @param filter ConnectionFilter instance.
+ */
+ public void setConnectionFilter(ConnectionFilter filter) {
+ connectionFilter = filter;
+ }//setConnectionFilter
+
+ /**
+ * Returns the number of open connections.
+ * @return the number of open connections as <tt>int</tt>.
+ */
+ public int openConnectionCount() {
+ return openConnections.size();
+ }//openConnectionCount
+
+ /**
+ * Returns the {@link Connection} at the given index.
+ * @param idx
+ * @return
+ */
+ public Connection getConnection(int idx) {
+ synchronized (openConnections) {
+ return openConnections.get(idx);
+ }
+ }//getConnection
+
+ /**
+ * Get all {@link Connection} instances with the given
+ * <tt>InetAddress</tt>.
+ *
+ * @return all {@link Connection} instances with the given
+ * <tt>InetAddress</tt>.
+ */
+ public Connection[] getConnectionsByAdddress(InetAddress addr) {
+ ArrayList<Connection> l = new ArrayList<Connection>();
+ synchronized (openConnections) {
+ for (Connection connection : openConnections) {
+ if (connection.getConnectionData().getInetAddress().equals(addr)) {
+ l.add(connection);
+ }
+ }
+ }
+ Connection[] conns = new Connection[l.size()];
+ return l.toArray(conns);
+ }//getConnectionsByAddress
+
+ /**
+ * Starts this <tt>ConnectionManager</tt>.
+ */
+ public void start() {
+ thread = new Thread(this);
+ thread.start();
+ }//start
+
+ /**
+ * Stops this <tt>ConnectionManager</tt>.
+ */
+ public void stop() {
+ LOG.log(Level.FINE, "stop()::" + this.toString());
+ stopping = true;
+ //wait for thread to die
+ try {
+ if (thread != null) {
+ thread.join();
+ }
+ } catch (InterruptedException iex) {
+ LOG.log(Level.SEVERE, "stop()", iex);
+ }
+ synchronized (openConnections) {
+ for (Connection tc : openConnections) {
+ try {
+ //maybe write a disgrace to the socket?
+ tc.close();
+ } catch (Exception exc) {
+ LOG.log(Level.SEVERE, "stop()", exc);
+ }
+ }
+ openConnections.clear();
+ }
+ LOG.log(Level.FINE, "stop():: Stopped " + this.toString());
+ }//stop
+
+ /**
+ * Method that that tries to connect an incoming request.
+ * Properly queueing.
+ *
+ * @param insock Socket thats representing the incoming connection.
+ */
+ public void makeConnection(Socket insock) {
+ LOG.log(Level.FINE, "makeConnection()::" + insock.toString());
+ if (connectionFilter == null || connectionFilter.isAllowed(insock.getInetAddress())) {
+ //we create the connection data object at this point to
+ //store certain information there.
+ ConnectionData newCD = new ConnectionData(insock, this);
+ newCD.setLoginShell(loginShell);
+ newCD.setLineMode(lineMode);
+ if (openConnections.size() < maxConnections) {
+ //create a new Connection instance
+ Connection con = createConnection(threadGroup, newCD);
+ //log the newly created connection
+ Object[] args = {openConnections.size() + 1};
+ LOG.info(MessageFormat.format("connection #{0,number,integer} made.", args));
+ //register it for being managed
+ synchronized (openConnections) {
+ openConnections.add(con);
+ }
+ //start it
+ con.start();
+ }
+ } else {
+ LOG.info("makeConnection():: Active Filter blocked incoming connection.");
+ try {
+ insock.close();
+ } catch (IOException ex) {
+ //do nothing or log.
+ }
+ }
+ }//makeConnection
+
+ protected abstract Connection createConnection(ThreadGroup threadGroup, ConnectionData newCD);
+
+ /**
+ * Periodically does following work:
+ * <ul>
+ * <li> cleaning up died connections.
+ * <li> checking managed connections if they are working properly.
+ * <li> checking the open connections.
+ * </ul>
+ */
+ public void run() {
+ //housekeep connections
+ try {
+ do {
+ //clean up and close all broken connections
+ //cleanupBroken();
+ //clean up closed connections
+ cleanupClosed();
+ //check all active connections
+ checkOpenConnections();
+ //sleep interval
+ Thread.sleep(housekeepingInterval);
+ } while (!stopping);
+
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, "run()", e);
+ }
+ LOG.log(Level.FINE, "run():: Ran out " + this.toString());
+ }//run
+
+ /*
+ private void cleanupBroken() {
+ //cleanup loop
+ while (!m_BrokenConnections.isEmpty()) {
+ Connection nextOne = (Connection) m_BrokenConnections.pop();
+ log.info("cleanupBroken():: Closing broken connection " + nextOne.toString());
+ //fire logoff event for shell site cleanup , beware could hog the daemon thread
+ nextOne.processConnectionEvent(new ConnectionEvent(nextOne, ConnectionEvent.CONNECTION_BROKEN));
+ //close the connection, will be automatically registered as closed
+ nextOne.close();
+ }
+ }//cleanupBroken
+ */
+ private void cleanupClosed() {
+ if (stopping) {
+ return;
+ }
+ //cleanup loop
+ while (!closedConnections.isEmpty()) {
+ Connection nextOne = closedConnections.pop();
+ LOG.info("cleanupClosed():: Removing closed connection " + nextOne.toString());
+ synchronized (openConnections) {
+ openConnections.remove(nextOne);
+ }
+ }
+ }//cleanupBroken
+
+ private void checkOpenConnections() {
+ if (stopping) {
+ return;
+ }
+ //do routine checks on active connections
+ synchronized (openConnections) {
+ for (Connection conn : openConnections) {
+ ConnectionData cd = conn.getConnectionData();
+ //check if it is dead and remove it.
+ if (!conn.isActive()) {
+ registerClosedConnection(conn);
+ continue;
+ }
+ /* Timeouts check */
+ //first we caculate the inactivity time
+ long inactivity = System.currentTimeMillis() - cd.getLastActivity();
+ //now we check for warning and disconnection
+ if (inactivity > warningTimeout) {
+ //..and for disconnect
+ if (inactivity > (disconnectTimeout + warningTimeout)) {
+ //this connection needs to be disconnected :)
+ LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded total timeout.");
+ //fire logoff event for shell site cleanup , beware could hog the daemon thread
+ conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_TIMEDOUT));
+ //conn.close();
+ } else {
+ //this connection needs to be warned :)
+ if (!cd.isWarned()) {
+ LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded warning timeout.");
+ cd.setWarned(true);
+ //warning event is fired but beware this could hog the daemon thread!!
+ conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_IDLE));
+ }
+ }
+ }
+ }
+ /* end Timeouts check */
+ }
+ }//checkConnections
+
+ public void registerClosedConnection(Connection con) {
+ if (stopping) {
+ return;
+ }
+ if (!closedConnections.contains(con)) {
+ LOG.log(Level.FINE, "registerClosedConnection()::" + con.toString());
+ closedConnections.push(con);
+ }
+ }//unregister
+
+ public int getDisconnectTimeout() {
+ return disconnectTimeout;
+ }
+
+ public void setDisconnectTimeout(int disconnectTimeout) {
+ this.disconnectTimeout = disconnectTimeout;
+ }
+
+ public int getHousekeepingInterval() {
+ return housekeepingInterval;
+ }
+
+ public void setHousekeepingInterval(int housekeepingInterval) {
+ this.housekeepingInterval = housekeepingInterval;
+ }
+
+ public boolean isLineMode() {
+ return lineMode;
+ }
+
+ public void setLineMode(boolean lineMode) {
+ this.lineMode = lineMode;
+ }
+
+ public String getLoginShell() {
+ return loginShell;
+ }
+
+ public void setLoginShell(String loginShell) {
+ this.loginShell = loginShell;
+ }
+
+ public int getMaxConnections() {
+ return maxConnections;
+ }
+
+ public void setMaxConnections(int maxConnections) {
+ this.maxConnections = maxConnections;
+ }
+
+ public int getWarningTimeout() {
+ return warningTimeout;
+ }
+
+ public void setWarningTimeout(int warningTimeout) {
+ this.warningTimeout = warningTimeout;
+ }
+
+}//class ConnectionManager
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/PortListener.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/PortListener.java
new file mode 100644
index 0000000..1682db0
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/PortListener.java
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.text.MessageFormat;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class that implements a <tt>PortListener</tt>.<br>
+ * If available, it accepts incoming connections and passes them
+ * to an associated <tt>ConnectionManager</tt>.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see ConnectionManager
+ */
+public class PortListener
+ implements Runnable {
+
+ private static final Logger LOG = Logger.getLogger(PortListener.class.getName());
+ private static final String logmsg =
+ "Listening to Port {0,number,integer} with a connectivity queue size of {1,number,integer}.";
+ private String name;
+ private int port; //port number running on
+ private int floodProtection; //flooding protection
+ private ServerSocket serverSocket = null; //server socket
+ private Thread thread;
+ private ConnectionManager connectionManager; //connection management thread
+ private boolean stopping = false;
+ private boolean available; //Flag for availability
+
+ /**
+ * Constructs a PortListener instance.<br>
+ *
+ * @param port int that specifies the port number of the server socket.
+ * @param floodprot that specifies the server socket queue size.
+ */
+ public PortListener(String name, int port, int floodprot) {
+ this.name = name;
+ available = false;
+ this.port = port;
+ floodProtection = floodprot;
+ }//constructor
+
+ /**
+ * Returns the name of this <tt>PortListener</tt>.
+ *
+ * @return the name as <tt>String</tt>.
+ */
+ public String getName() {
+ return name;
+ }//getName
+
+ /**
+ * Tests if this <tt>PortListener</tt> is available.
+ *
+ * @return true if available, false otherwise.
+ */
+ public boolean isAvailable() {
+ return available;
+ }//isAvailable
+
+ /**
+ * Sets the availability flag of this <tt>PortListener</tt>.
+ *
+ * @param b true if to be available, false otherwise.
+ */
+ public void setAvailable(boolean b) {
+ available = b;
+ }//setAvailable
+
+ /**
+ * Starts this <tt>PortListener</tt>.
+ */
+ public void start() {
+ LOG.log(Level.FINE, "start()");
+ thread = new Thread(this);
+ thread.start();
+ available = true;
+ }//start
+
+ /**
+ * Stops this <tt>PortListener</tt>, and returns
+ * when everything was stopped successfully.
+ */
+ public void stop() {
+ LOG.log(Level.FINE, "stop()::" + this.toString());
+ //flag stop
+ stopping = true;
+ available = false;
+ //take down all connections
+ connectionManager.stop();
+
+ //close server socket
+ try {
+ serverSocket.close();
+ } catch (IOException ex) {
+ LOG.log(Level.SEVERE, "stop()", ex);
+ }
+
+ //wait for thread to die
+ try {
+ thread.join();
+ } catch (InterruptedException iex) {
+ LOG.log(Level.SEVERE, "stop()", iex);
+ }
+
+ LOG.info("stop()::Stopped " + this.toString());
+ }//stop
+
+ /**
+ * Listen constantly to a server socket and handles incoming connections
+ * through the associated {a:link ConnectionManager}.
+ *
+ * @see ConnectionManager
+ */
+ public void run() {
+ try {
+ /*
+ A server socket is opened with a connectivity queue of a size specified
+ in int floodProtection. Concurrent login handling under normal circumstances
+ should be handled properly, but denial of service attacks via massive parallel
+ program logins should be prevented with this.
+ */
+ serverSocket = new ServerSocket(port, floodProtection);
+
+ //log entry
+ LOG.info(MessageFormat.format(logmsg, port, floodProtection));
+
+ do {
+ try {
+ Socket s = serverSocket.accept();
+ if (available) {
+ connectionManager.makeConnection(s);
+ } else {
+ //just shut down the socket
+ s.close();
+ }
+ } catch (SocketException ex) {
+ if (stopping) {
+ //server socket was closed blocked in accept
+ LOG.log(Level.FINE, "run(): ServerSocket closed by stop()");
+ } else {
+ LOG.log(Level.SEVERE, "run()", ex);
+ }
+ }
+ } while (!stopping);
+
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "run()", e);
+ }
+ LOG.log(Level.FINE, "run(): returning.");
+ }//run
+
+ /**
+ * Returns reference to ConnectionManager instance associated
+ * with the PortListener.
+ *
+ * @return the associated ConnectionManager.
+ */
+ public ConnectionManager getConnectionManager() {
+ return connectionManager;
+ }//getConnectionManager
+
+ public void setConnectionManager(ConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ }
+
+}//class PortListener
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
new file mode 100644
index 0000000..7bba28b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+import org.apache.felix.gogo.jline.Shell;
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Options;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.Terminal.Signal;
+import org.jline.terminal.TerminalBuilder;
+
+/*
+ * a very simple Telnet server.
+ * real remote access should be via ssh.
+ */
+public class Telnet {
+ public static final String[] functions = {"telnetd"};
+
+ private static final int defaultPort = 2019;
+ private final CommandProcessor processor;
+ private PortListener portListener;
+ private int port;
+ private String ip;
+
+ public Telnet(CommandProcessor procesor) {
+ this.processor = procesor;
+ }
+
+ public void telnetd(CommandSession session, String[] argv) throws IOException {
+ final String[] usage = {"telnetd - start simple telnet server",
+ "Usage: telnetd [-i ip] [-p port] start | stop | status",
+ " -i --ip=INTERFACE listen interface (default=127.0.0.1)",
+ " -p --port=PORT listen port (default=" + defaultPort + ")",
+ " -? --help show help"};
+
+ Options opt = Options.compile(usage).parse(argv);
+ List<String> args = opt.args();
+
+ if (opt.isSet("help") || args.isEmpty()) {
+ opt.usage(System.err);
+ return;
+ }
+
+ String command = args.get(0);
+
+ if ("start".equals(command)) {
+ if (portListener != null) {
+ throw new IllegalStateException("telnetd is already running on port " + port);
+ }
+ ip = opt.get("ip");
+ port = opt.getNumber("port");
+ start(session);
+ status();
+ } else if ("stop".equals(command)) {
+ if (portListener == null) {
+ throw new IllegalStateException("telnetd is not running.");
+ }
+ stop();
+ } else if ("status".equals(command)) {
+ status();
+ } else {
+ throw opt.usageError("bad command: " + command);
+ }
+ }
+
+ private void status() {
+ if (portListener != null) {
+ System.out.println("telnetd is running on " + ip + ":" + port);
+ } else {
+ System.out.println("telnetd is not running.");
+ }
+ }
+
+ private void start(CommandSession session) throws IOException {
+ ConnectionManager connectionManager = new ConnectionManager(1000, 5 * 60 * 1000, 5 * 60 * 1000, 60 * 1000, null, null, false) {
+ @Override
+ protected Connection createConnection(ThreadGroup threadGroup, ConnectionData newCD) {
+ return new Connection(threadGroup, newCD) {
+ TelnetIO telnetIO;
+
+ @Override
+ protected void doRun() throws Exception {
+ telnetIO = new TelnetIO();
+ telnetIO.setConnection(this);
+ telnetIO.initIO();
+
+ InputStream in = new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return telnetIO.read();
+ }
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int r = read();
+ if (r >= 0) {
+ b[off] = (byte) r;
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ };
+ PrintStream out = new PrintStream(new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ telnetIO.write(b);
+ }
+ @Override
+ public void flush() throws IOException {
+ telnetIO.flush();
+ }
+ });
+ Terminal terminal = TerminalBuilder.builder()
+ .type(getConnectionData().getNegotiatedTerminalType().toLowerCase())
+ .streams(in, out)
+ .system(false)
+ .name("telnet")
+ .build();
+ terminal.setSize(new Size(getConnectionData().getTerminalColumns(), getConnectionData().getTerminalRows()));
+ terminal.setAttributes(Shell.getTerminal(session).getAttributes());
+ addConnectionListener(new ConnectionListener() {
+ @Override
+ public void connectionIdle(ConnectionEvent ce) {
+ }
+
+ @Override
+ public void connectionTimedOut(ConnectionEvent ce) {
+ }
+
+ @Override
+ public void connectionLogoutRequest(ConnectionEvent ce) {
+ }
+
+ @Override
+ public void connectionSentBreak(ConnectionEvent ce) {
+ }
+
+ @Override
+ public void connectionTerminalGeometryChanged(ConnectionEvent ce) {
+ terminal.setSize(new Size(getConnectionData().getTerminalColumns(), getConnectionData().getTerminalRows()));
+ terminal.raise(Signal.WINCH);
+ }
+ });
+ PrintStream pout = new PrintStream(terminal.output());
+ CommandSession session = processor.createSession(terminal.input(), pout, pout);
+ Context context = new Context() {
+ @Override
+ public String getProperty(String name) {
+ return System.getProperty(name);
+ }
+ @Override
+ public void exit() throws Exception {
+ close();
+ }
+ };
+ new Shell(context, processor, terminal).gosh(session, new String[]{"--login"});
+ }
+
+ @Override
+ protected void doClose() throws Exception {
+ telnetIO.closeOutput();
+ telnetIO.closeInput();
+ }
+ };
+ }
+ };
+ portListener = new PortListener("gogo", port, 10);
+ portListener.setConnectionManager(connectionManager);
+ portListener.start();
+ }
+
+ private void stop() throws IOException {
+ portListener.stop();
+ portListener = null;
+ }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/TelnetIO.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/TelnetIO.java
new file mode 100644
index 0000000..540db98
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/TelnetIO.java
@@ -0,0 +1,1548 @@
+/*
+ * 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.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.felix.gogo.jline.telnet.ConnectionEvent.Type;
+
+/**
+ * Class that represents the TelnetIO implementation. It contains
+ * an inner IACHandler class to handle the telnet protocol level
+ * communication.
+ * <p/>
+ * Although supposed to work full-duplex, we only process the telnet protocol
+ * layer communication in case of reading requests from the higher levels.
+ * This is the only way to meet the one thread per connection requirement.
+ * </p>
+ * <p/>
+ * The output is done via byte-oriented streams, definately suitable for the
+ * telnet protocol. The format of the output is UTF-8 (Unicode), which is a
+ * standard and supported by any telnet client, including the ones included
+ * in Microsoft OS's.
+ * </p>
+ * <em>Notes:</em>
+ * <ul>
+ * <li>The underlying output is buffered, to ensure that all bytes written
+ * are send, the flush() method has to be called.
+ * <li>This low-level routines ensure nice multithreading behaviour on I/O.
+ * Neither large outputs, nor input sequences excuted by the connection thread
+ * can hog the system.
+ * </ul>
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public class TelnetIO {
+
+ /**
+ * Interpret As Command
+ */
+ protected static final int IAC = 255;
+ /**
+ * Go Ahead <BR> Newer Telnets do not make use of this option
+ * that allows a specific communication mode.
+ */
+ protected static final int GA = 249;
+ /**
+ * Negotiation: Will do option
+ */
+ protected static final int WILL = 251;
+ /**
+ * Negotiation: Wont do option
+ */
+ protected static final int WONT = 252;
+ /**
+ * Negotiation: Do option
+ */
+ protected static final int DO = 253;
+ /**
+ * Negotiation: Dont do option
+ */
+ protected static final int DONT = 254;
+ /**
+ * Marks start of a subnegotiation.
+ */
+ protected static final int SB = 250;
+ /**
+ * Marks end of subnegotiation.
+ */
+ protected static final int SE = 240;
+ /**
+ * No operation
+ */
+ protected static final int NOP = 241;
+ /**
+ * Data mark its the data part of a SYNCH which helps to clean up the buffers between
+ * Telnet Server <-> Telnet Client. <BR>
+ * It should work like this we send a TCP urgent package and <IAC> <DM> the receiver
+ * should get the urgent package (SYNCH) and just discard everything until he receives
+ * our <IAC> <DM>.<BR>
+ * <EM>Remark</EM>:
+ * <OL>
+ * <LI>can we send a TCP urgent package?
+ * <LI>can we make use of the thing at all?
+ * </OL>
+ */
+ protected static final int DM = 242;
+ /**
+ * Break
+ */
+ protected static final int BRK = 243;
+ /**
+ * Interrupt Process
+ */
+ protected static final int IP = 244;
+ /**
+ * Abort Output
+ */
+ protected static final int AO = 245;
+
+ /**** Implementation of OutputStream ****************************************************/
+ /**
+ * Are You There
+ */
+ protected static final int AYT = 246;
+ /**
+ * Erase Char
+ */
+ protected static final int EC = 247;
+ /**
+ * Erase Line
+ */
+ protected static final int EL = 248;
+ /**
+ * Telnet Option: ECHO
+ */
+ protected static final int ECHO = 1;
+ /**
+ * Telnet Option: SUPress Go Ahead<br>
+ * This will be negotiated, all new telnet protocol implementations are
+ * recommended to do this.
+ */
+ protected static final int SUPGA = 3;
+ /**
+ * Telnet Option: Negotiate About Window Size<br>
+ * <ul>
+ * <li>Server request is IAC DO NAWS
+ * <li>Client response contains subnegotiation with data (columns, rows).
+ * </ul>
+ */
+ protected static final int NAWS = 31;
+ /**
+ * Telnet Option: Terminal TYPE <br>
+ * <ul>
+ * <li>Server request contains subnegotiation SEND
+ * <li>Client response contains subnegotiation with data IS,terminal type string
+ * </ul>
+ */
+ protected static final int TTYPE = 24;
+ /**
+ * TTYPE subnegotiation: IS
+ */
+ protected static final int IS = 0;
+ /**
+ * TTYPE subnegotiation: SEND
+ */
+ protected static final int SEND = 1;
+
+ /**** End implementation of OutputStream ***********************************************/
+
+
+ /**** Implementation of InputStream ****************************************************/
+ /**
+ * Telnet Option: Logout<br>
+ * This allows nice goodbye to time-outed or unwanted clients.
+ */
+ protected static final int LOGOUT = 18;
+ /**
+ * Telnet Option: Linemode
+ * <p/>
+ * The infamous line mode option.
+ */
+ protected static final int LINEMODE = 34;
+ protected static final int LM_MODE = 1;
+ protected static final int LM_EDIT = 1;
+ protected static final int LM_TRAPSIG = 2;
+
+ /**** Implementation of InputStream ****************************************************/
+
+
+ /****
+ * Following methods implement init/request/answer procedures for telnet
+ * protocol level communication.
+ */
+ protected static final int LM_MODEACK = 4;
+ protected static final int LM_FORWARDMASK = 2;
+ protected static final int LM_SLC = 3;
+ protected static final int LM_SLC_NOSUPPORT = 0;
+ protected static final int LM_SLC_DEFAULT = 3;
+
+
+ /**** End telnet protocol level communication methods *******************************/
+ protected static final int LM_SLC_VALUE = 2;
+
+
+ /** Constants declaration ***********************************************/
+
+//Telnet Protocoll Constants
+ protected static final int LM_SLC_CANTCHANGE = 1;
+ protected static final int LM_SLC_LEVELBITS = 3;
+ protected static final int LM_SLC_ACK = 128;
+ protected static final int LM_SLC_FLUSHIN = 64;
+ protected static final int LM_SLC_FLUSHOUT = 32;
+ protected static final int LM_SLC_SYNCH = 1;
+ protected static final int LM_SLC_BRK = 2;
+ protected static final int LM_SLC_IP = 3;
+ protected static final int LM_SLC_AO = 4;
+ protected static final int LM_SLC_AYT = 5;
+ protected static final int LM_SLC_EOR = 6;
+
+ /**
+ * The following implement the NVT (network virtual terminal) which offers the concept
+ * of a simple "printer". They are the basical meanings of control possibilities
+ * on a standard telnet implementation.
+ */
+ protected static final int LM_SLC_ABORT = 7;
+ protected static final int LM_SLC_EOF = 8;
+ protected static final int LM_SLC_SUSP = 9;
+ /**
+ * Telnet Option: Environment
+ */
+ protected static final int NEWENV = 39;
+ protected static final int NE_INFO = 2;
+
+ /**
+ * The following are constants for supported options,
+ * which can be negotiated based upon the telnet protocol
+ * specification.
+ */
+ protected static final int NE_VAR = 0;
+ protected static final int NE_VALUE = 1;
+
+ /**
+ * The following options are options for which we also support subnegotiation
+ * based upon the telnet protocol specification.
+ */
+ protected static final int NE_ESC = 2;
+ protected static final int NE_USERVAR = 3;
+ protected static final int NE_VAR_OK = 2;
+ protected static final int NE_VAR_DEFINED = 1;
+ protected static final int NE_VAR_DEFINED_EMPTY = 0;
+ protected static final int NE_VAR_UNDEFINED = -1;
+ protected static final int NE_IN_ERROR = -2;
+ protected static final int NE_IN_END = -3;
+ protected static final int NE_VAR_NAME_MAXLENGTH = 50;
+ protected static final int NE_VAR_VALUE_MAXLENGTH = 1000;
+ /**
+ * Unused
+ */
+ protected static final int EXT_ASCII = 17; //Defines Extended ASCII
+ protected static final int SEND_LOC = 23; //Defines Send Location
+ protected static final int AUTHENTICATION = 37; //Defines Authentication
+ protected static final int ENCRYPT = 38; //Defines Encryption
+ private static final Logger LOG = Logger.getLogger(TelnetIO.class.getName());
+ /**
+ * Window Size Constants
+ */
+ private static final int SMALLEST_BELIEVABLE_WIDTH = 20;
+ private static final int SMALLEST_BELIEVABLE_HEIGHT = 6;
+ private static final int DEFAULT_WIDTH = 80;
+ private static final int DEFAULT_HEIGHT = 25;
+ private Connection connection; //a reference to the connection this instance works for
+ private ConnectionData connectionData; //holds all important information of the connection
+ private DataOutputStream out; //the byte oriented outputstream
+ private DataInputStream in; //the byte oriented input stream
+ //Aggregations
+ private IACHandler iacHandler; //holds a reference to the aggregated IACHandler
+ //Members
+ private InetAddress localAddress; //address of the host the telnetd is running on
+ private boolean noIac = false; //describes if IAC was found and if its just processed
+ private boolean initializing;
+ private boolean crFlag;
+ /**
+ * Creates a TelnetIO object for the given connection.<br>
+ * Input- and OutputStreams are properly set and the primary telnet
+ * protocol initialization is carried out by the inner IACHandler class.<BR>
+ */
+ public TelnetIO() {
+ }//constructor
+
+ public void initIO() throws IOException {
+ //we make an instance of our inner class
+ iacHandler = new IACHandler();
+ //we setup underlying byte oriented streams
+ in = new DataInputStream(connectionData.getSocket().getInputStream());
+ out = new DataOutputStream(new BufferedOutputStream(connectionData.getSocket().getOutputStream()));
+
+ //we save the local address (necessary?)
+ localAddress = connectionData.getSocket().getLocalAddress();
+ crFlag = false;
+ //bootstrap telnet communication
+ initTelnetCommunication();
+ }//initIO
+
+ public void setConnection(Connection con) {
+ connection = con;
+ connectionData = connection.getConnectionData();
+ }//setConnection
+
+ /**
+ * Method to output a byte. Ensures that CR(\r) is never send
+ * alone,but CRLF(\r\n), which is a rule of the telnet protocol.
+ *
+ * @param b Byte to be written.
+ */
+ public void write(byte b) throws IOException {
+ //ensure CRLF(\r\n) is written for LF(\n) to adhere
+ //to the telnet protocol.
+ if (!crFlag && b == 10) {
+ out.write(13);
+ }
+
+ out.write(b);
+
+ if (b == 13) {
+ crFlag = true;
+ } else {
+ crFlag = false;
+ }
+ }//write(byte)
+
+ /**
+ * Method to output an int.
+ *
+ * @param i Integer to be written.
+ */
+ public void write(int i)
+ throws IOException {
+ write((byte) i);
+ }//write(int)
+
+ /**
+ * Method to write an array of bytes.
+ *
+ * @param sequence byte[] to be written.
+ */
+ public void write(byte[] sequence) throws IOException {
+ for (byte b : sequence) {
+ write(b);
+ }
+ }//write(byte[])
+
+ /**
+ * Method to output an array of int' s.
+ *
+ * @param sequence int [] to write
+ */
+ public void write(int[] sequence) throws IOException {
+ for (int i : sequence) {
+ write((byte) i);
+ }
+ }//write(int[])
+
+ /**
+ * Method to write a char.
+ *
+ * @param ch char to be written.
+ */
+ public void write(char ch) throws IOException {
+ write((byte) ch);
+ }//write(char)
+
+ /**
+ * Method to output a string.
+ *
+ * @param str String to be written.
+ */
+ public void write(String str) throws IOException {
+ write(str.getBytes());
+ }//write(String)
+
+ /**
+ * Method to flush all buffered output.
+ */
+ public void flush() throws IOException {
+ out.flush();
+ }//flush
+
+ /**
+ * Method to close the underlying output stream to free system resources.<br>
+ * Most likely only to be called by the ConnectionManager upon clean up of
+ * connections that ended or died.
+ */
+ public void closeOutput() {
+
+ try {
+ //sends telnetprotocol logout acknowledgement
+ write(IAC);
+ write(DO);
+ write(LOGOUT);
+ //and now close underlying outputstream
+
+ out.close();
+ } catch (IOException ex) {
+ LOG.log(Level.SEVERE, "closeOutput()", ex);
+ //handle?
+ }
+ }//close
+
+ private void rawWrite(int i) throws IOException {
+ out.write(i);
+ }//rawWrite
+
+ /**
+ * Method to read a byte from the InputStream.
+ * Invokes the IACHandler upon IAC (Byte=255).
+ *
+ * @return int read from stream.
+ */
+ public int read() throws IOException {
+ int c = rawread();
+ //if (c == 255) {
+ noIac = false;
+ while ((c == 255) && (!noIac)) {
+ /**
+ * Read next, and invoke
+ * the IACHandler he is taking care of the rest. Or at least he should :)
+ */
+ c = rawread();
+ if (c != 255) {
+ iacHandler.handleC(c);
+ c = rawread();
+ } else {
+ noIac = true;
+ }
+ }
+ return stripCRSeq(c);
+ }//read
+
+ /**
+ * Method to close the underlying inputstream to free system resources.<br>
+ * Most likely only to be called by the ConnectionManager upon clean up of
+ * connections that ended or died.
+ */
+ public void closeInput() {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //handle?
+ }
+ }//closeInput
+
+ /**
+ * This method reads an unsigned 16bit Integer from the stream,
+ * its here for getting the NAWS Data Values for height and width.
+ */
+ private int read16int() throws IOException {
+ int c = in.readUnsignedShort();
+ return c;
+ }//read16int
+
+ /**
+ * The following options are options which might be of interest, but are not
+ * yet implemented or in use.
+ */
+
+ /**
+ * Method to read a raw byte from the InputStream.<br>
+ * Telnet protocol layer communication is filtered and processed here.
+ *
+ * @return int read from stream.
+ */
+ private int rawread() throws IOException {
+ int b = 0;
+
+ //try {
+ b = in.readUnsignedByte();
+ connectionData.activity();
+ return b;
+ }//rawread
+
+ /**
+ * Checks for the telnet protocol specified CR followed by NULL or LF<BR>
+ * Subsequently reads for the next byte and forwards
+ * only a ENTER represented by LF internally.
+ */
+ private int stripCRSeq(int input) throws IOException {
+ if (input == 13) {
+ rawread();
+ return 10;
+ }
+ return input;
+ }//stripCRSeq
+
+ /**
+ * Method that initializes the telnet communication layer.
+ */
+ private void initTelnetCommunication() {
+
+ initializing = true;
+ try {
+ //start out, some clients just wait
+ if (connectionData.isLineMode()) {
+ iacHandler.doLineModeInit();
+ LOG.log(Level.FINE, "Line mode initialized.");
+ } else {
+ iacHandler.doCharacterModeInit();
+ LOG.log(Level.FINE, "Character mode initialized.");
+ }
+ //open for a defined timeout so we read incoming negotiation
+ connectionData.getSocket().setSoTimeout(1000);
+ read();
+
+ } catch (Exception e) {
+ //handle properly
+ //log.error("initTelnetCommunication()",e);
+ } finally {
+ //this is important, dont ask me why :)
+ try {
+ connectionData.getSocket().setSoTimeout(0);
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "initTelnetCommunication()", ex);
+ }
+ }
+ initializing = false;
+ }//initTelnetCommunication
+
+ /**
+ * Method that represents the answer to the
+ * AreYouThere question of the telnet protocol specification
+ * <p/>
+ * Output of the String [HostAdress:Yes]
+ */
+ private void IamHere() {
+ try {
+ write("[" + localAddress.toString() + ":Yes]");
+ flush();
+ } catch (Exception ex) {
+ LOG.log(Level.SEVERE, "IamHere()", ex);
+ }
+ }//IamHere
+
+ /**
+ * Network virtual terminal break.
+ */
+ private void nvtBreak() {
+ connection.processConnectionEvent(new ConnectionEvent(connection, ConnectionEvent.Type.CONNECTION_BREAK));
+ }//nvtBreak
+
+ /**
+ * Method that checks reported terminal sizes and sets the
+ * asserted values in the ConnectionData instance associated with
+ * the connection.
+ *
+ * @param width Integer that represents the Window width in chars
+ * @param height Integer that represents the Window height in chars
+ */
+ private void setTerminalGeometry(int width, int height) {
+ if (width < SMALLEST_BELIEVABLE_WIDTH) {
+ width = DEFAULT_WIDTH;
+ }
+ if (height < SMALLEST_BELIEVABLE_HEIGHT) {
+ height = DEFAULT_HEIGHT;
+ }
+ //DEBUG: write("[New Window Size " + window_width + "x" + window_height + "]");
+ connectionData.setTerminalGeometry(width, height);
+ connection.processConnectionEvent(new ConnectionEvent(connection,
+ Type.CONNECTION_TERMINAL_GEOMETRY_CHANGED));
+ }//setTerminalGeometry
+
+ public void setEcho(boolean b) {
+ }//setEcho
+
+ /**
+ * An inner class for handling incoming option negotiations implementing the <B>telnet protocol</B>
+ * specification based upon following Standards and RFCs:
+ * <OL>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc854.txt">854 Telnet Protocol Specification</A>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc855.txt">855 Telnet Option Specifications</A>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc857.txt">857 Telnet Echo Option</A>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc858.txt">858 Telnet Supress Go Ahead Option</A>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc727.txt">727 Telnet Logout Option</A>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc1073.txt">1073 Telnet Window Size Option</A>
+ * <LI><A HREF="ftp://ds.internic.net/rfc/rfc1091.txt">1091 Telnet Terminal-Type Option</A>
+ * </OL>
+ * <p/>
+ * Furthermore there are some more, which helped to solve problems, or might be important
+ * for future enhancements:<BR>
+ * <A HREF="ftp://ds.internic.net/rfc/rfc1143.txt">1143 The Q Method of Implementing Option Negotiation</A><BR>
+ * <A HREF="ftp://ds.internic.net/rfc/rfc1416.txt">1416 Telnet Authentication Option</A><BR>
+ * <p/>
+ * After an intense study of the available material (mainly cryptical written RFCs,
+ * a telnet client implementation for the macintosh based upon NCSA telnet, and a server side
+ * implementation called key, a mud-like system completely written in Java) I realized
+ * the problems we are facing regarding to the telnet protocol:
+ * <OL>
+ * <LI> a minimal spread of invented options, which means there are a lot of invented options,
+ * but rarely they made it through to become a standard.
+ * <LI> Dependency on a special type of implementation is dangerous in our case.
+ * We are no kind of host that offers the user to run several processes at once,
+ * a BBS is intended to be a single process the user is interacting with.
+ * <LI> The <B>LAMER</B> has to be expected to log in with the standard Microsoft telnet
+ * implementation. This means forget every nice feature and most of the almost-standards.
+ * <p/>
+ * </OL>
+ * <BR>
+ *
+ * @author Dieter Wimberger
+ * @version 1.1 16/06/1998
+ * <p/>
+ * <p/>
+ * <B>To-Do</B>:<UL>
+ * <LI>UNIX conform new style TTYPE negotiation. Setting a list and selecting from it...
+ * </UL>
+ */
+ class IACHandler {
+
+ /**
+ * Telnet readin buffer
+ * Here its implemented guys. Open your eyes upon this solution.
+ * The others take a one byte solution :)
+ */
+ private int[] buffer = new int[2];
+
+ /**
+ * DO_ECHO or not
+ */
+ private boolean DO_ECHO = false;
+
+ /**
+ * DO_SUPGA or not
+ */
+ private boolean DO_SUPGA = false;
+
+ /**
+ * DO_NAWS or not
+ */
+ private boolean DO_NAWS = false;
+
+ /**
+ * DO_TTYPE or not
+ */
+ private boolean DO_TTYPE = false;
+
+ /**
+ * DO_LINEMODE or not
+ */
+ private boolean DO_LINEMODE = false;
+
+ /**
+ * DO_NEWENV or not
+ */
+ private boolean DO_NEWENV = false;
+
+ /**
+ * Are we waiting for a DO reply?
+ */
+ private boolean WAIT_DO_REPLY_SUPGA = false;
+ private boolean WAIT_DO_REPLY_ECHO = false;
+ private boolean WAIT_DO_REPLY_NAWS = false;
+ private boolean WAIT_DO_REPLY_TTYPE = false;
+ private boolean WAIT_DO_REPLY_LINEMODE = false;
+ private boolean WAIT_LM_MODE_ACK = false;
+ private boolean WAIT_LM_DO_REPLY_FORWARDMASK = false;
+ private boolean WAIT_DO_REPLY_NEWENV = false;
+ private boolean WAIT_NE_SEND_REPLY = false;
+
+ /**
+ * Are we waiting for a WILL reply?
+ */
+ private boolean WAIT_WILL_REPLY_SUPGA = false;
+ private boolean WAIT_WILL_REPLY_ECHO = false;
+ private boolean WAIT_WILL_REPLY_NAWS = false;
+ private boolean WAIT_WILL_REPLY_TTYPE = false;
+
+
+ public void doCharacterModeInit() throws IOException {
+ sendCommand(WILL, ECHO, true);
+ sendCommand(DONT, ECHO, true); //necessary for some clients
+ sendCommand(DO, NAWS, true);
+ sendCommand(WILL, SUPGA, true);
+ sendCommand(DO, SUPGA, true);
+ sendCommand(DO, TTYPE, true);
+ sendCommand(DO, NEWENV, true); //environment variables
+ }//doCharacterModeInit
+
+ public void doLineModeInit() throws IOException {
+ sendCommand(DO, NAWS, true);
+ sendCommand(WILL, SUPGA, true);
+ sendCommand(DO, SUPGA, true);
+ sendCommand(DO, TTYPE, true);
+ sendCommand(DO, LINEMODE, true);
+ sendCommand(DO, NEWENV, true);
+ }//doLineModeInit
+
+
+ /**
+ * Method to handle a IAC that came in over the line.
+ *
+ * @param i (int)ed byte that followed the IAC
+ */
+ public void handleC(int i) throws IOException {
+ buffer[0] = i;
+ if (!parseTWO(buffer)) {
+ buffer[1] = rawread();
+ parse(buffer);
+ }
+ buffer[0] = 0;
+ buffer[1] = 0;
+ }//handleC
+
+ /**
+ * Method that parses for options with two characters.
+ *
+ * @param buf int [] that represents the first byte that followed the IAC first.
+ * @return true when it was a two byte command (IAC OPTIONBYTE)
+ */
+ private boolean parseTWO(int[] buf) {
+ switch (buf[0]) {
+ case IAC:
+ //doubled IAC to escape 255 is handled within the
+ //read method.
+ break;
+ case AYT:
+ IamHere();
+ break;
+ case AO:
+ case IP:
+ case EL:
+ case EC:
+ case NOP:
+ break;
+ case BRK:
+ nvtBreak();
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }//parseTWO
+
+ /**
+ * Method that parses further on for options.
+ *
+ * @param buf that represents the first two bytes that followed the IAC.
+ */
+ private void parse(int[] buf) throws IOException {
+ switch (buf[0]) {
+ /* First switch on the Negotiation Option */
+ case WILL:
+ if (supported(buf[1]) && isEnabled(buf[1])) {
+ ;// do nothing
+ } else {
+ if (waitDOreply(buf[1]) && supported(buf[1])) {
+ enable(buf[1]);
+ setWait(DO, buf[1], false);
+ } else {
+ if (supported(buf[1])) {
+ sendCommand(DO, buf[1], false);
+ enable(buf[1]);
+ } else {
+ sendCommand(DONT, buf[1], false);
+ }
+ }
+ }
+ break;
+ case WONT:
+ if (waitDOreply(buf[1]) && supported(buf[1])) {
+ setWait(DO, buf[1], false);
+ } else {
+ if (supported(buf[1]) && isEnabled(buf[1])) {
+ // eanable() Method disables an Option that is already enabled
+ enable(buf[1]);
+ }
+ }
+ break;
+ case DO:
+ if (supported(buf[1]) && isEnabled(buf[1])) {
+ ; // do nothing
+ } else {
+ if (waitWILLreply(buf[1]) && supported(buf[1])) {
+ enable(buf[1]);
+ setWait(WILL, buf[1], false);
+ } else {
+ if (supported(buf[1])) {
+ sendCommand(WILL, buf[1], false);
+ enable(buf[1]);
+ } else {
+ sendCommand(WONT, buf[1], false);
+ }
+ }
+ }
+ break;
+ case DONT:
+ if (waitWILLreply(buf[1]) && supported(buf[1])) {
+ setWait(WILL, buf[1], false);
+ } else {
+ if (supported(buf[1]) && isEnabled(buf[1])) {
+ // enable() Method disables an Option that is already enabled
+ enable(buf[1]);
+ }
+ }
+ break;
+
+ /* Now about other two byte IACs */
+ case DM: //How do I implement a SYNCH signal?
+ break;
+ case SB: //handle subnegotiations
+ if ((supported(buf[1])) && (isEnabled(buf[1]))) {
+ switch (buf[1]) {
+ case NAWS:
+ handleNAWS();
+ break;
+ case TTYPE:
+ handleTTYPE();
+ break;
+ case LINEMODE:
+ handleLINEMODE();
+ break;
+ case NEWENV:
+ handleNEWENV();
+ break;
+ default:
+ ;
+ }
+ } else {
+ //do nothing
+ }
+ break;
+ default:
+ ;
+ }//switch
+ }//parse
+
+ /**
+ * Method that reads a NawsSubnegotiation that ends up with a IAC SE
+ * If the measurements are unbelieveable it switches to the defaults.
+ */
+ private void handleNAWS() throws IOException {
+ int width = read16int();
+ if (width == 255) {
+ width = read16int(); //handle doubled 255 value;
+ }
+ int height = read16int();
+ if (height == 255) {
+ height = read16int(); //handle doubled 255 value;
+ }
+ skipToSE();
+ setTerminalGeometry(width, height);
+ }//handleNAWS
+
+ /**
+ * Method that reads a TTYPE Subnegotiation String that ends up with a IAC SE
+ * If no Terminal is valid, we switch to the dumb "none" terminal.
+ */
+ private void handleTTYPE() throws IOException {
+ String tmpstr = "";
+ // The next read should be 0 which is IS by the protocol
+ // specs. hmmm?
+ rawread(); //that should be the is :)
+ tmpstr = readIACSETerminatedString(40);
+ LOG.log(Level.FINE, "Reported terminal name " + tmpstr);
+ connectionData.setNegotiatedTerminalType(tmpstr);
+ }//handleTTYPE
+
+ /**
+ * Method that handles LINEMODE subnegotiation.
+ */
+ public void handleLINEMODE() throws IOException {
+ int c = rawread();
+ switch (c) {
+ case LM_MODE:
+ handleLMMode();
+ break;
+ case LM_SLC:
+ handleLMSLC();
+ break;
+ case WONT:
+ case WILL:
+ handleLMForwardMask(c);
+ break;
+ default:
+ //skip to (including) SE
+ skipToSE();
+ }
+ }//handleLINEMODE
+
+ public void handleLMMode() throws IOException {
+ //we sent the default which no client might deny
+ //so we only wait the ACK
+ if (WAIT_LM_MODE_ACK) {
+ int mask = rawread();
+ if (mask != (LM_EDIT | LM_TRAPSIG | LM_MODEACK)) {
+ LOG.log(Level.FINE, "Client violates linemodeack sent: " + mask);
+ }
+ WAIT_LM_MODE_ACK = false;
+ }
+ skipToSE();
+ }//handleLMMode
+
+ public void handleLMSLC() throws IOException {
+ int[] triple = new int[3];
+ if (!readTriple(triple)) return;
+
+ //SLC will be initiated by the client
+ //case 1. client requests set
+ //LINEMODE SLC 0 SLC_DEFAULT 0
+ if ((triple[0] == 0) && (triple[1] == LM_SLC_DEFAULT) && (triple[2] == 0)) {
+ skipToSE();
+ //reply with SLC xxx SLC_DEFAULT 0
+ rawWrite(IAC);
+ rawWrite(SB);
+ rawWrite(LINEMODE);
+ rawWrite(LM_SLC);
+ //triples defaults for all
+ for (int i = 1; i < 12; i++) {
+ rawWrite(i);
+ rawWrite(LM_SLC_DEFAULT);
+ rawWrite(0);
+ }
+ rawWrite(IAC);
+ rawWrite(SE);
+ flush();
+ } else {
+
+ //case 2: just acknowledge anything we get from the client
+ rawWrite(IAC);
+ rawWrite(SB);
+ rawWrite(LINEMODE);
+ rawWrite(LM_SLC);
+ rawWrite(triple[0]);
+ rawWrite(triple[1] | LM_SLC_ACK);
+ rawWrite(triple[2]);
+ while (readTriple(triple)) {
+ rawWrite(triple[0]);
+ rawWrite(triple[1] | LM_SLC_ACK);
+ rawWrite(triple[2]);
+ }
+ rawWrite(IAC);
+ rawWrite(SE);
+ flush();
+ }
+ }//handleLMSLC
+
+ public void handleLMForwardMask(int WHAT) throws IOException {
+ switch (WHAT) {
+ case WONT:
+ if (WAIT_LM_DO_REPLY_FORWARDMASK) {
+ WAIT_LM_DO_REPLY_FORWARDMASK = false;
+ }
+ break;
+ }
+ skipToSE();
+ }//handleLMForward
+
+ public void handleNEWENV() throws IOException {
+ LOG.log(Level.FINE, "handleNEWENV()");
+ int c = rawread();
+ switch (c) {
+ case IS:
+ handleNEIs();
+ break;
+ case NE_INFO:
+ handleNEInfo();
+ break;
+ default:
+ //skip to (including) SE
+ skipToSE();
+ }
+ }//handleNEWENV
+
+ /*
+ The characters following a "type" up to the next "type" or VALUE specify the
+ variable name.
+
+ If a "type" is not followed by a VALUE
+ (e.g., by another VAR, USERVAR, or IAC SE) then that variable is
+ undefined.
+ */
+ private int readNEVariableName(StringBuffer sbuf) throws IOException {
+ LOG.log(Level.FINE, "readNEVariableName()");
+ int i = -1;
+ do {
+ i = rawread();
+ if (i == -1) {
+ return NE_IN_ERROR;
+ } else if (i == IAC) {
+ i = rawread();
+ if (i == IAC) {
+ //duplicated IAC
+ sbuf.append((char) i);
+ } else if (i == SE) {
+ return NE_IN_END;
+ } else {
+ //Error should have been duplicated
+ return NE_IN_ERROR;
+ }
+ } else if (i == NE_ESC) {
+ i = rawread();
+ if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) {
+ sbuf.append((char) i);
+ } else {
+ return NE_IN_ERROR;
+ }
+ } else if (i == NE_VAR || i == NE_USERVAR) {
+ return NE_VAR_UNDEFINED;
+ } else if (i == NE_VALUE) {
+ return NE_VAR_DEFINED;
+ } else {
+ //check maximum length to prevent overflow
+ if (sbuf.length() >= NE_VAR_NAME_MAXLENGTH) {
+ //TODO: Log Overflow
+ return NE_IN_ERROR;
+ } else {
+ sbuf.append((char) i);
+ }
+ }
+ } while (true);
+ }//readNEVariableName
+
+
+ /*
+ The characters following a VALUE up to the next
+ "type" specify the value of the variable.
+ If a VALUE is immediately
+ followed by a "type" or IAC, then the variable is defined, but has
+ no value.
+ If an IAC is contained between the IS and the IAC SE,
+ it must be sent as IAC IAC.
+ */
+ private int readNEVariableValue(StringBuffer sbuf) throws IOException {
+ LOG.log(Level.FINE, "readNEVariableValue()");
+ //check conditions for first character after VALUE
+ int i = rawread();
+ if (i == -1) {
+ return NE_IN_ERROR;
+ } else if (i == IAC) {
+ i = rawread();
+ if (i == IAC) {
+ //Double IAC
+ return NE_VAR_DEFINED_EMPTY;
+ } else if (i == SE) {
+ return NE_IN_END;
+ } else {
+ //according to rule IAC has to be duplicated
+ return NE_IN_ERROR;
+ }
+ } else if (i == NE_VAR || i == NE_USERVAR) {
+ return NE_VAR_DEFINED_EMPTY;
+ } else if (i == NE_ESC) {
+ //escaped value
+ i = rawread();
+ if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) {
+ sbuf.append((char) i);
+ } else {
+ return NE_IN_ERROR;
+ }
+ } else {
+ //character
+ sbuf.append((char) i);
+ }
+ //loop until end of value (IAC SE or TYPE)
+ do {
+ i = rawread();
+ if (i == -1) {
+ return NE_IN_ERROR;
+ } else if (i == IAC) {
+ i = rawread();
+ if (i == IAC) {
+ //duplicated IAC
+ sbuf.append((char) i);
+ } else if (i == SE) {
+ return NE_IN_END;
+ } else {
+ //Error should have been duplicated
+ return NE_IN_ERROR;
+ }
+ } else if (i == NE_ESC) {
+ i = rawread();
+ if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) {
+ sbuf.append((char) i);
+ } else {
+ return NE_IN_ERROR;
+ }
+ } else if (i == NE_VAR || i == NE_USERVAR) {
+ return NE_VAR_OK;
+ } else {
+ //check maximum length to prevent overflow
+ if (sbuf.length() > NE_VAR_VALUE_MAXLENGTH) {
+ //TODO: LOG Overflow
+ return NE_IN_ERROR;
+ } else {
+ sbuf.append((char) i);
+ }
+ }
+ } while (true);
+ }//readNEVariableValue
+
+
+ public void readNEVariables() throws IOException {
+ LOG.log(Level.FINE, "readNEVariables()");
+ StringBuffer sbuf = new StringBuffer(50);
+ int i = rawread();
+ if (i == IAC) {
+ //invalid or empty response
+ skipToSE();
+ LOG.log(Level.FINE, "readNEVariables()::INVALID VARIABLE");
+ return;
+ }
+ boolean cont = true;
+ if (i == NE_VAR || i == NE_USERVAR) {
+ do {
+ switch (readNEVariableName(sbuf)) {
+ case NE_IN_ERROR:
+ LOG.log(Level.FINE, "readNEVariables()::NE_IN_ERROR");
+ return;
+ case NE_IN_END:
+ LOG.log(Level.FINE, "readNEVariables()::NE_IN_END");
+ return;
+ case NE_VAR_DEFINED:
+ LOG.log(Level.FINE, "readNEVariables()::NE_VAR_DEFINED");
+ String str = sbuf.toString();
+ sbuf.delete(0, sbuf.length());
+ switch (readNEVariableValue(sbuf)) {
+ case NE_IN_ERROR:
+ LOG.log(Level.FINE, "readNEVariables()::NE_IN_ERROR");
+ return;
+ case NE_IN_END:
+ LOG.log(Level.FINE, "readNEVariables()::NE_IN_END");
+ return;
+ case NE_VAR_DEFINED_EMPTY:
+ LOG.log(Level.FINE, "readNEVariables()::NE_VAR_DEFINED_EMPTY");
+ break;
+ case NE_VAR_OK:
+ //add variable
+ LOG.log(Level.FINE, "readNEVariables()::NE_VAR_OK:VAR=" + str + " VAL=" + sbuf.toString());
+ TelnetIO.this.connectionData.getEnvironment().put(str, sbuf.toString());
+ sbuf.delete(0, sbuf.length());
+ break;
+ }
+ break;
+ case NE_VAR_UNDEFINED:
+ LOG.log(Level.FINE, "readNEVariables()::NE_VAR_UNDEFINED");
+ break;
+ }
+ } while (cont);
+ }
+ }//readVariables
+
+ public void handleNEIs() throws IOException {
+ LOG.log(Level.FINE, "handleNEIs()");
+ if (isEnabled(NEWENV)) {
+ readNEVariables();
+ }
+ }//handleNEIs
+
+ public void handleNEInfo() throws IOException {
+ LOG.log(Level.FINE, "handleNEInfo()");
+ if (isEnabled(NEWENV)) {
+ readNEVariables();
+ }
+ }//handleNEInfo
+
+ /**
+ * Method that sends a TTYPE Subnegotiation Request.
+ * IAC SB TERMINAL-TYPE SEND
+ */
+ public void getTTYPE() throws IOException {
+ if (isEnabled(TTYPE)) {
+ rawWrite(IAC);
+ rawWrite(SB);
+ rawWrite(TTYPE);
+ rawWrite(SEND);
+ rawWrite(IAC);
+ rawWrite(SE);
+ flush();
+ }
+ }//getTTYPE
+
+ /**
+ * Method that sends a LINEMODE MODE Subnegotiation request.
+ * IAC LINEMODE MODE MASK SE
+ */
+ public void negotiateLineMode() throws IOException {
+ if (isEnabled(LINEMODE)) {
+ rawWrite(IAC);
+ rawWrite(SB);
+ rawWrite(LINEMODE);
+ rawWrite(LM_MODE);
+ rawWrite(LM_EDIT | LM_TRAPSIG);
+ rawWrite(IAC);
+ rawWrite(SE);
+ WAIT_LM_MODE_ACK = true;
+
+ //dont forwardmask
+ rawWrite(IAC);
+ rawWrite(SB);
+ rawWrite(LINEMODE);
+ rawWrite(DONT);
+ rawWrite(LM_FORWARDMASK);
+ rawWrite(IAC);
+ rawWrite(SE);
+ WAIT_LM_DO_REPLY_FORWARDMASK = true;
+ flush();
+ }
+ }//negotiateLineMode
+
+ /**
+ * Method that sends a NEW-ENVIRON SEND subnegotiation request
+ * for default variables and user variables.
+ * IAC SB NEW-ENVIRON SEND VAR USERVAR IAC SE
+ */
+ private void negotiateEnvironment() throws IOException {
+ //log.debug("negotiateEnvironment()");
+ if (isEnabled(NEWENV)) {
+ rawWrite(IAC);
+ rawWrite(SB);
+ rawWrite(NEWENV);
+ rawWrite(SEND);
+ rawWrite(NE_VAR);
+ rawWrite(NE_USERVAR);
+ rawWrite(IAC);
+ rawWrite(SE);
+ WAIT_NE_SEND_REPLY = true;
+ flush();
+ }
+ }//negotiateEnvironment
+
+ /**
+ * Method that skips a subnegotiation response.
+ */
+ private void skipToSE() throws IOException {
+ while (rawread() != SE) ;
+ }//skipSubnegotiation
+
+ private boolean readTriple(int[] triple) throws IOException {
+ triple[0] = rawread();
+ triple[1] = rawread();
+ if ((triple[0] == IAC) && (triple[1] == SE)) {
+ return false;
+ } else {
+ triple[2] = rawread();
+ return true;
+ }
+ }//readTriple
+
+ /**
+ * Method that reads a subnegotiation String,
+ * one of those that end with a IAC SE combination.
+ * A maximum length is observed to prevent overflow.
+ *
+ * @return IAC SE terminated String
+ */
+ private String readIACSETerminatedString(int maxlength) throws IOException {
+ int where = 0;
+ char[] cbuf = new char[maxlength];
+ char b = ' ';
+ boolean cont = true;
+
+ do {
+ int i;
+ i = rawread();
+ switch (i) {
+ case IAC:
+ i = rawread();
+ if (i == SE) {
+ cont = false;
+ }
+ break;
+ case -1:
+ return (new String("default"));
+ default:
+ }
+ if (cont) {
+ b = (char) i;
+ //Fix for overflow wimpi (10/06/2004)
+ if (b == '\n' || b == '\r' || where == maxlength) {
+ cont = false;
+ } else {
+ cbuf[where++] = b;
+ }
+ }
+ } while (cont);
+
+ return (new String(cbuf, 0, where));
+ }//readIACSETerminatedString
+
+ /**
+ * Method that informs internally about the supported Negotiation Options
+ *
+ * @param i int that represents requested the Option
+ * @return Boolean that represents support status
+ */
+ private boolean supported(int i) {
+ switch (i) {
+ case SUPGA:
+ case ECHO:
+ case NAWS:
+ case TTYPE:
+ case NEWENV:
+ return true;
+ case LINEMODE:
+ return connectionData.isLineMode();
+ default:
+ return false;
+ }
+ }//supported
+
+ /**
+ * Method that sends a Telnet IAC String with TelnetIO.write(byte b) method.
+ *
+ * @param i int that represents requested Command Type (DO,DONT,WILL,WONT)
+ * @param j int that represents the Option itself (e.g. ECHO, NAWS)
+ */
+ private void sendCommand(int i, int j, boolean westarted) throws IOException {
+ rawWrite(IAC);
+ rawWrite(i);
+ rawWrite(j);
+ // we started with DO OPTION and now wait for reply
+ if ((i == DO) && westarted) setWait(DO, j, true);
+ // we started with WILL OPTION and now wait for reply
+ if ((i == WILL) && westarted) setWait(WILL, j, true);
+ flush();
+ }//sendCommand
+
+ /**
+ * Method enables or disables a supported Option
+ *
+ * @param i int that represents the Option
+ */
+ private void enable(int i) throws IOException {
+ switch (i) {
+ case SUPGA:
+ if (DO_SUPGA) {
+ DO_SUPGA = false;
+ } else {
+ DO_SUPGA = true;
+ }
+ break;
+ case ECHO:
+ if (DO_ECHO) {
+ DO_ECHO = false;
+ } else {
+ DO_ECHO = true;
+ }
+ break;
+ case NAWS:
+ if (DO_NAWS) {
+ DO_NAWS = false;
+ } else {
+ DO_NAWS = true;
+ }
+ break;
+ case TTYPE:
+ if (DO_TTYPE) {
+ DO_TTYPE = false;
+ } else {
+ DO_TTYPE = true;
+ getTTYPE();
+ }
+ break;
+ case LINEMODE:
+ if (DO_LINEMODE) {
+ DO_LINEMODE = false;
+ //set false in connection data, so the application knows.
+ connectionData.setLineMode(false);
+ } else {
+ DO_LINEMODE = true;
+ negotiateLineMode();
+ }
+ break;
+ case NEWENV:
+ if (DO_NEWENV) {
+ DO_NEWENV = false;
+ } else {
+ DO_NEWENV = true;
+ negotiateEnvironment();
+ }
+ break;
+ }
+ }//enable
+
+ /**
+ * Method that informs internally about the status of the supported
+ * Negotiation Options.
+ *
+ * @param i int that represents requested the Option
+ * @return Boolean that represents the enabled status
+ */
+ private boolean isEnabled(int i) {
+ switch (i) {
+ case SUPGA:
+ return DO_SUPGA;
+ case ECHO:
+ return DO_ECHO;
+ case NAWS:
+ return DO_NAWS;
+ case TTYPE:
+ return DO_TTYPE;
+ case LINEMODE:
+ return DO_LINEMODE;
+ case NEWENV:
+ return DO_NEWENV;
+ default:
+ return false;
+ }
+ }//isEnabled
+
+ /**
+ * Method that informs internally about the WILL wait status
+ * of an option.
+ *
+ * @param i that represents requested the Option
+ * @return Boolean that represents WILL wait status of the Option
+ */
+ private boolean waitWILLreply(int i) {
+ switch (i) {
+ case SUPGA:
+ return WAIT_WILL_REPLY_SUPGA;
+ case ECHO:
+ return WAIT_WILL_REPLY_ECHO;
+ case NAWS:
+ return WAIT_WILL_REPLY_NAWS;
+ case TTYPE:
+ return WAIT_WILL_REPLY_TTYPE;
+ default:
+ return false;
+ }
+ }//waitWILLreply
+
+ /**
+ * Method that informs internally about the DO wait status
+ * of an option.
+ *
+ * @param i Integer that represents requested the Option
+ * @return Boolean that represents DO wait status of the Option
+ */
+ private boolean waitDOreply(int i) {
+ switch (i) {
+ case SUPGA:
+ return WAIT_DO_REPLY_SUPGA;
+ case ECHO:
+ return WAIT_DO_REPLY_ECHO;
+ case NAWS:
+ return WAIT_DO_REPLY_NAWS;
+ case TTYPE:
+ return WAIT_DO_REPLY_TTYPE;
+ case LINEMODE:
+ return WAIT_DO_REPLY_LINEMODE;
+ case NEWENV:
+ return WAIT_DO_REPLY_NEWENV;
+ default:
+ return false;
+ }
+ }//waitDOreply
+
+ /**
+ * Method that mutates the wait status of an option in
+ * negotiation. We need the wait status to keep track of
+ * negotiation in process. So we cant miss if we started out
+ * or the other and so on.
+ *
+ * @param WHAT Integer values of DO or WILL
+ * @param OPTION Integer that represents the Option
+ * @param WAIT Boolean that represents the status of wait that should be set
+ */
+ private void setWait(int WHAT, int OPTION, boolean WAIT) {
+ switch (WHAT) {
+ case DO:
+ switch (OPTION) {
+ case SUPGA:
+ WAIT_DO_REPLY_SUPGA = WAIT;
+ break;
+ case ECHO:
+ WAIT_DO_REPLY_ECHO = WAIT;
+ break;
+ case NAWS:
+ WAIT_DO_REPLY_NAWS = WAIT;
+ break;
+ case TTYPE:
+ WAIT_DO_REPLY_TTYPE = WAIT;
+ break;
+ case LINEMODE:
+ WAIT_DO_REPLY_LINEMODE = WAIT;
+ break;
+ case NEWENV:
+ WAIT_DO_REPLY_NEWENV = WAIT;
+ break;
+ }
+ break;
+ case WILL:
+ switch (OPTION) {
+ case SUPGA:
+ WAIT_WILL_REPLY_SUPGA = WAIT;
+ break;
+ case ECHO:
+ WAIT_WILL_REPLY_ECHO = WAIT;
+ break;
+ case NAWS:
+ WAIT_WILL_REPLY_NAWS = WAIT;
+ break;
+ case TTYPE:
+ WAIT_WILL_REPLY_TTYPE = WAIT;
+ break;
+ }
+ break;
+ }
+ }//setWait
+
+ }//inner class IACHandler
+
+ /** end Constants declaration **************************************************/
+
+}//class TelnetIO
diff --git a/gogo/jline/src/main/resources/gosh_profile b/gogo/jline/src/main/resources/gosh_profile
new file mode 100644
index 0000000..1a5822c
--- /dev/null
+++ b/gogo/jline/src/main/resources/gosh_profile
@@ -0,0 +1,247 @@
+#
+# 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.
+#
+# default gosh_profile
+# only read if etc/gosh_profile doesn't exist relative to the System property
+# gosh.home or failing that the current directory.
+
+# catch all exceptions from this script to avoid it aborting startup
+try {
+
+ # ensure gogo commands are found first
+ SCOPE = gogo:*
+
+ # add methods on BundleContext object as commands
+ addcommand context ${.context}
+
+ # add methods on System object as commands
+ #addcommand system (((${.context} getClass) getClassLoader) loadClass java.lang.System)
+
+ # alias to print full stack trace
+ e = { $exception printStackTrace }
+
+ ## disable console auto-formatting of each result
+ # you will then need to explicitly use the 'format' command
+ # to print the result of commands that don't write to stdout.
+ #.Gogo.format = false
+
+ ## disable printing the formatted result of a command into pipelines
+ #.Format.Pipe = false
+
+ # set prompt
+ prompt = 'g! '
+ \#rprompt = { (new java.text.SimpleDateFormat "HH:mm:ss") format (new Date) }
+
+
+ __option_not_present = {
+ res = true
+ opts = ([ $args ] get 0)
+ each $opts {
+ arg = $it
+ each ($.commandLine words) {
+ if { ($it toString) contentEquals ($arg toString) } {
+ res = false
+ }
+ }
+ }
+ $res
+ }
+
+ __load_class = {
+ (($.reader class) classLoader) loadClass $1
+ }
+
+ __as_list = {
+ (__load_class java.util.Arrays) asList $1
+ }
+
+ __set_unset_arguments = {
+ is_setopt = (($.commandLine words) get 0) equals "setopt"
+ enums = ((__load_class 'org.jline.ConsoleReader$Option') enumConstants)
+ candidates = new ArrayList
+ each (__as_list $enums) {
+ name = ((($it name) toLowerCase) replace '_' '-')
+ is_set = ($.reader isSet $it)
+ neg = %(( if(is_setopt, is_set, not(is_set)) ))
+ if { $neg } {
+ name = "no-${name}"
+ }
+ if { not { (($.commandLine words) subList 1 ($.commandLine wordIndex)) contains $name } } {
+ $candidates add (new org.jline.Candidate $name $name (if { $neg } { "unset" } { "set" }) null null null true)
+ }
+ }
+ $candidates
+ }
+
+ setopt pad-prompts
+ setopt group
+
+ complete -c gogo:complete -e
+ complete -c gogo:complete -d "Edit command specific completions"
+ complete -c gogo:complete -s c -l command --description "Command to add completion to" -n '__option_not_present -c --command' -a '$.commands'
+ complete -c gogo:complete -s s -l short-option --description "Posix-style option to complete" -n '__option_not_present -s --short-option'
+ complete -c gogo:complete -s l -l long-option --description "GNU-style option to complete" -n '__option_not_present -l --long-option'
+ complete -c gogo:complete -s a -l arguments --description "A list of possible arguments" -n '__option_not_present -a --argument'
+ complete -c gogo:complete -s d -l description --description "Description of this completions" -n '__option_not_present -d --description'
+ complete -c gogo:complete -s h -l help --description "Display help and exit" -n '__option_not_present -h --help'
+ complete -c gogo:complete -s n -l condition --description "The completion should only be used if the specified command has a zero exit status" -n '__option_not_present -n --condition'
+ complete -c gogo:complete -s e -l erase --description "Remove completion" -n '__option_not_present -e --erase'
+
+ complete -c gogo:history -e
+ complete -c gogo:history -d "Show and manipulate command history"
+ complete -c gogo:history -l clear --description "Clear history" -n '__option_not_present --clear'
+ complete -c gogo:history -l save --description "Save history" -n '__option_not_present --save'
+
+ complete -c gogo:setopt -e
+ complete -c gogo:setopt -d "Set or view set shell options"
+ complete -c gogo:setopt -a '__set_unset_arguments'
+
+ complete -c gogo:unsetopt -e
+ complete -c gogo:unsetopt -d "Unset or view unset shell options"
+ complete -c gogo:unsetopt -a '__set_unset_arguments'
+
+ complete -c gogo:cat -e
+ complete -c gogo:cat -d "Concatenate and print files"
+ complete -c gogo:cat -s n "Number the output lines, starting at 1"
+ complete -c gogo:cat -a '__files'
+
+ complete -c gogo:pwd -e
+ complete -c gogo:pwd -d "Get current directory"
+
+ complete -c gogo:ls -e
+ complete -c gogo:ls -d "List files"
+
+ complete -c gogo:cd -e
+ complete -c gogo:cd -d "Change current directory"
+ complete -c gogo:cd -a '__directories'
+
+ complete -c gogo:sleep -e
+ complete -c gogo:sleep -d "Pause execution for the specified amount of time"
+
+ complete -c gogo:echo -e
+ complete -c gogo:echo -d "Write arguments to the standard output"
+ complete -c gogo:echo -s n -d "No trailing new line"
+
+ complete -c gogo:grep -e
+ complete -c gogo:grep -d "File pattern searcher"
+ # TODO
+
+ complete -c gogo:sort -e
+ complete -c gogo:sort -d "Sort lines of text files"
+ # TODO
+
+ complete -c gogo:gosh -e
+ complete -c gogo:gosh -d "Execute script with arguments in a new session"
+ # TODO
+
+ complete -c gogo:sh -e
+ complete -c gogo:sh -d "Execute script with arguments in a new session"
+ # TODO
+
+ complete -c gogo:source -e
+ complete -c gogo:source -d "Execute script with arguments"
+ # TODO
+
+ # TODO: format getopt new set tac type addcommand removeCommand eval
+
+ complete -c gogo:each -e
+ complete -c gogo:each -d "Loop and execute script on the specified elements"
+
+ complete -c gogo:if -e
+ complete -c gogo:if -d "Conditionaly execute a script"
+
+ complete -c gogo:not -e
+ complete -c gogo:not -d "Negates the result of a script"
+
+ complete -c gogo:throw -e
+ complete -c gogo:throw -d "Throws an exception"
+
+ complete -c gogo:try -e
+ complete -c gogo:try -d "Try executing a script and catch any exception"
+
+ complete -c gogo:until -e
+ complete -c gogo:until -d "Loop and execute script until a condition is satisfied"
+
+ complete -c gogo:while -e
+ complete -c gogo:while -d "Loop and execute script while a condition is satisfied"
+
+ complete -c gogo:less -e
+ complete -c gogo:less -d "File pager"
+ complete -c gogo:less -s e -l quit-at-eof --description "Exit on second EOF"
+ complete -c gogo:less -s E -l QUIT-AT-EOF --description "Exit on EOF"
+ complete -c gogo:less -s q -l quiet -l silent --description "Silent mode"
+ complete -c gogo:less -s Q -l QUIET -l SILENT --description "Completely silent"
+ complete -c gogo:less -s S -l chop-long-lines --description "Do not fold long lines"
+ complete -c gogo:less -s i -l ignore-case --description "Search ignores lowercase case"
+ complete -c gogo:less -s I -l IGNORE-CASE --description "Search ignores all case"
+ complete -c gogo:less -s x -l tabs --description "Set tab stops"
+ complete -c gogo:less -s N -l LINE-NUMBERS --description "Display line number for each line"
+ complete -c gogo:less -a '__files'
+
+ complete -c gogo:nano -e
+ complete -c gogo:nano -d "File editor"
+ complete -c gogo:nano -a '__files'
+
+ complete -c gogo:keymap -e
+ complete -c gogo:keymap -d "Manipulate keymaps"
+ complete -c gogo:keymap -s N --description "Create a new keymap" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s d --description "Delete existing keymaps and reset to default state" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s D --description "Delete named keymaps" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s l --description "List existing keymap names" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s r --description "Unbind specified in-strings" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s s --description "Bind each in-string to each out-string" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s A --description "Create alias to keymap" -n '__option_not_present -N -d -D -l -r -s -A'
+ complete -c gogo:keymap -s e --description "Select emacs keymap and bind it to main" -n '__option_not_present -e -a -v -M'
+ complete -c gogo:keymap -s v --description "Select viins keymap and bind it to main" -n '__option_not_present -e -a -v -M'
+ complete -c gogo:keymap -s a --description "Select vicmd keymap" -n '__option_not_present -e -a -v -M'
+ complete -c gogo:keymap -s M --description "Specify keymap to select" -n '__option_not_present -e -a -v -M' -a '(keymap -l | tac) split " "'
+ complete -c gogo:keymap -s R --description "Interpret in-strings as ranges"
+ complete -c gogo:keymap -s p --description "List bindings which have given key sequence as a a prefix"
+ complete -c gogo:keymap -s L --description "Output in form of keymap commands"
+
+ complete -c gogo:widget -e
+ complete -c gogo:widget -d "Manipulate widgets"
+ complete -c gogo:widget -s N --description "Create a new widget" -n '__option_not_present -N -A -D -U -l'
+ complete -c gogo:widget -s A --description "Create alias to widget" -n '__option_not_present -N -A -D -U -l'
+ complete -c gogo:widget -s D --description "Delete widgets" -n '__option_not_present -N -A -D -U -l'
+ complete -c gogo:widget -s U --description "Push characters to the stack" -n '__option_not_present -N -A -D -U -l'
+ complete -c gogo:widget -s l --description "List user-defined widgets" -n '__option_not_present -N -A -D -U -l'
+ complete -c gogo:widget -s a --description "With -l, list all widgets" -n '__option_not_present -l'
+
+ complete -c gogo:telnetd -e
+ complete -c gogo:telnetd -d "Telnet daemon"
+ complete -c gogo:telnetd -s i -l ip --description "Listening IP interface" -n '__option_not_present -i --ip'
+ complete -c gogo:telnetd -s p -l port --description "Listening IP port" -n '__option_not_present -p --port'
+ complete -c gogo:telnetd -a '[start stop status]'
+
+ complete -c gogo:sshd -e
+ complete -c gogo:sshd -d "SSH daemon"
+ complete -c gogo:sshd -s i -l ip --description "Listening IP interface" -n '__option_not_present -i --ip'
+ complete -c gogo:sshd -s p -l port --description "Listening IP port" -n '__option_not_present -p --port'
+ complete -c gogo:sshd -a '[start stop status]'
+
+ complete -c gogo:tmux -e
+ complete -c gogo:tmux -d "Terminal multiplexer"
+
+ # print welcome message
+ cat ($0 resolve motd)
+} {
+ echo "$0: ERROR: $exception"
+}
+
+# end
diff --git a/gogo/jline/src/main/resources/motd b/gogo/jline/src/main/resources/motd
new file mode 100644
index 0000000..01954f9
--- /dev/null
+++ b/gogo/jline/src/main/resources/motd
@@ -0,0 +1,3 @@
+____________________________
+Welcome to Apache Felix Gogo
+
diff --git a/gogo/pom.xml b/gogo/pom.xml
index d814dbe..39a32d0 100644
--- a/gogo/pom.xml
+++ b/gogo/pom.xml
@@ -40,6 +40,7 @@
<modules>
<module>gogo-parent</module>
<module>runtime</module>
+ <module>jline</module>
<module>shell</module>
<module>command</module>
</modules>