Better support for streams
Better ls implementation
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736001 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/IoUtils.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/IoUtils.java
new file mode 100644
index 0000000..9f14b96
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/IoUtils.java
@@ -0,0 +1,105 @@
+/*
+ * 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.nio.file.LinkOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.sshd.common.util.OsUtils;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class IoUtils {
+
+ public static final LinkOption[] EMPTY_LINK_OPTIONS = new LinkOption[0];
+
+ public static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(".bat", ".exe", ".cmd"));
+
+ private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
+
+ /**
+ * Private Constructor
+ */
+ private IoUtils() {
+ throw new UnsupportedOperationException("No instance allowed");
+ }
+
+ public static LinkOption[] getLinkOptions(boolean followLinks) {
+ if (followLinks) {
+ return EMPTY_LINK_OPTIONS;
+ } else { // return a clone that modifications to the array will not affect others
+ return NO_FOLLOW_OPTIONS.clone();
+ }
+ }
+
+ /**
+ * @param fileName The file name to be evaluated - ignored if {@code null}/empty
+ * @return {@code true} if the file ends in one of the {@link #WINDOWS_EXECUTABLE_EXTENSIONS}
+ */
+ public static boolean isWindowsExecutable(String fileName) {
+ if ((fileName == null) || (fileName.length() <= 0)) {
+ return false;
+ }
+ for (String suffix : WINDOWS_EXECUTABLE_EXTENSIONS) {
+ if (fileName.endsWith(suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param f The {@link File} to be checked
+ * @return A {@link Set} of {@link PosixFilePermission}s based on whether
+ * the file is readable/writable/executable. If so, then <U>all</U> the
+ * relevant permissions are set (i.e., owner, group and others)
+ */
+ public static Set<PosixFilePermission> getPermissionsFromFile(File f) {
+ Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
+ if (f.canRead()) {
+ perms.add(PosixFilePermission.OWNER_READ);
+ perms.add(PosixFilePermission.GROUP_READ);
+ perms.add(PosixFilePermission.OTHERS_READ);
+ }
+
+ if (f.canWrite()) {
+ perms.add(PosixFilePermission.OWNER_WRITE);
+ perms.add(PosixFilePermission.GROUP_WRITE);
+ perms.add(PosixFilePermission.OTHERS_WRITE);
+ }
+
+ if (f.canExecute() || (OsUtils.isWin32() && isWindowsExecutable(f.getName()))) {
+ perms.add(PosixFilePermission.OWNER_EXECUTE);
+ perms.add(PosixFilePermission.GROUP_EXECUTE);
+ perms.add(PosixFilePermission.OTHERS_EXECUTE);
+ }
+
+ return perms;
+ }
+
+}
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
index 294011e..47784cd 100644
--- 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
@@ -22,6 +22,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
@@ -78,7 +79,7 @@
private void runShell(CommandSession session, Terminal terminal) {
InputStream in = terminal.input();
- PrintStream out = new PrintStream(terminal.output());
+ OutputStream out = terminal.output();
CommandSession newSession = processor.createSession(in, out, out);
newSession.put(Shell.VAR_TERMINAL, terminal);
newSession.put(".tmux", session.get(".tmux"));
@@ -139,8 +140,8 @@
final String cmd = String.join(" ", args);
Runnable task = () -> {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- InputStream is = new ByteArrayInputStream(new byte[0]);
PrintStream os = new PrintStream(baos);
+ InputStream is = new ByteArrayInputStream(new byte[0]);
if (opt.isSet("append") || !terminal.puts(Capability.clear_screen)) {
terminal.writer().println();
}
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
index 8d78f6a..53fc716 100644
--- 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
@@ -68,8 +68,7 @@
} catch (Throwable t) {
// ignore
}
- PrintStream pout = new PrintStream(terminal.output());
- CommandSession session = processor.createSession(terminal.input(), pout, pout);
+ CommandSession session = processor.createSession(terminal.input(), terminal.output(), terminal.output());
session.put(Shell.VAR_CONTEXT, context);
session.put(Shell.VAR_TERMINAL, terminal);
try {
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
index 4b90b7e..e40c2ef 100644
--- 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
@@ -19,22 +19,54 @@
package org.apache.felix.gogo.jline;
import java.io.BufferedReader;
-import java.io.File;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.IntBinaryOperator;
+import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.felix.gogo.runtime.Pipe;
import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Less.Source;
+import org.jline.builtins.Less.StdInSource;
+import org.jline.builtins.Less.URLSource;
import org.jline.builtins.Options;
+import org.jline.reader.LineReader.Option;
+import org.jline.terminal.Terminal;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
/**
* Posix-like utilities.
@@ -45,7 +77,57 @@
public class Posix {
static final String[] functions = {"cat", "echo", "grep", "sort", "sleep", "cd", "pwd", "ls"};
- public static void sort(CommandSession session, String[] argv) throws IOException {
+ public void _main(CommandSession session, String[] argv) {
+ if (argv == null || argv.length < 1) {
+ throw new IllegalArgumentException();
+ }
+ try {
+ argv = expand(session, argv);
+ switch (argv[0]) {
+ case "cat":
+ cat(session, argv);
+ break;
+ case "echo":
+ echo(session, argv);
+ break;
+ case "grep":
+ grep(session, argv);
+ break;
+ case "sort":
+ sort(session, argv);
+ break;
+ case "sleep":
+ sleep(session, argv);
+ break;
+ case "cd":
+ cd(session, argv);
+ break;
+ case "pwd":
+ pwd(session, argv);
+ break;
+ case "ls":
+ ls(session, argv);
+ break;
+ }
+ } catch (IllegalArgumentException e) {
+ System.err.println(e.getMessage());
+ Pipe.error(2);
+ } catch (HelpException e) {
+ System.err.println(e.getMessage());
+ Pipe.error(0);
+ } catch (Exception e) {
+ System.err.println(argv[0] + ": " + e.getMessage());
+ Pipe.error(1);
+ }
+ }
+
+ protected static class HelpException extends Exception {
+ public HelpException(String message) {
+ super(message);
+ }
+ }
+
+ protected void sort(CommandSession session, String[] argv) throws Exception {
final String[] usage = {
"sort - writes sorted standard input to standard output.",
"Usage: sort [OPTIONS] [FILES]",
@@ -58,24 +140,16 @@
" --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;
- }
+ Options opt = parseOptions(session, usage, argv);
List<String> args = opt.args();
- List<String> lines = new ArrayList<String>();
+ List<String> lines = new ArrayList<>();
if (!args.isEmpty()) {
for (String filename : args) {
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- session.currentDir().toUri().resolve(filename).toURL().openStream()));
- try {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ session.currentDir().toUri().resolve(filename).toURL().openStream()))) {
read(reader, lines);
- } finally {
- reader.close();
}
}
} else {
@@ -102,272 +176,362 @@
}
}
- 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();
- }
- }
-
- public Path pwd(CommandSession session, String[] argv) throws IOException {
+ protected void pwd(CommandSession session, String[] argv) throws Exception {
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;
- }
+ Options opt = parseOptions(session, usage, argv);
if (!opt.args().isEmpty()) {
- System.err.println("usage: pwd");
- return null;
+ throw new IllegalArgumentException("usage: pwd");
}
- return session.currentDir();
+ System.out.println(session.currentDir());
}
- public void cd(CommandSession session, String[] argv) throws IOException {
+ protected void cd(CommandSession session, String[] argv) throws Exception {
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;
- }
+ Options opt = parseOptions(session, usage, argv);
if (opt.args().size() != 1) {
- System.err.println("usage: cd DIRECTORY");
- return;
+ throw new IllegalArgumentException("usage: cd DIRECTORY");
}
Path cwd = session.currentDir();
cwd = cwd.resolve(opt.args().get(0)).toAbsolutePath();
if (!Files.exists(cwd)) {
- throw new IOException("Directory does not exist");
+ throw new IOException("no such file or directory: " + opt.args().get(0));
} else if (!Files.isDirectory(cwd)) {
- throw new IOException("Target is not a directory");
+ throw new IOException("not a directory: " + opt.args().get(0));
}
session.currentDir(cwd);
}
- public Collection<Path> ls(CommandSession session, String[] argv) throws IOException {
+ protected void ls(CommandSession session, String[] argv) throws Exception {
final String[] usage = {
"ls - list files",
- "Usage: ls [OPTIONS] PATTERNS...",
- " -? --help show help"
+ "Usage: ls [OPTIONS] [PATTERNS...]",
+ " -? --help show help",
+ " -a list entries starting with .",
+ " -F append file type indicators",
+ " -m comma separated",
+ " -l long listing",
+ " -S sort by size",
+ " -f output is not sorted",
+ " -r reverse sort order",
+ " -t sort by modification time",
+ " -x sort horizontally",
+ " -L list referenced file for links",
+ " -h print sizes in human readable form"
};
- Options opt = Options.compile(usage).parse(argv);
- if (opt.isSet("help")) {
- opt.usage(System.err);
- return null;
- }
- if (opt.args().isEmpty()) {
- opt.args().add("*");
- }
- List<Path> files = new ArrayList<>();
- 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);
+ Options opt = parseOptions(session, usage, argv);
+ Map<String, String> colors = getColorMap(session, "LS", "dr=34:ex=31:sl=35:ot=34;43");
- Path actualParent = session.currentDir().resolve(parent).normalize();
+ class PathEntry implements Comparable<PathEntry> {
+ final Path abs;
+ final Path path;
+ final Map<String, Object> attributes;
- idx = target.indexOf(File.separatorChar, idx);
- boolean isWildcarded = (target.indexOf('*', idx) >= 0);
- if (isWildcarded) {
- if (!Files.exists(actualParent)) {
- throw new IOException("File does not exist");
+ public PathEntry(Path abs, Path root) {
+ this.abs = abs;
+ this.path = abs.startsWith(root) ? root.relativize(abs) : abs;
+ this.attributes = readAttributes(abs);
+ }
+
+ @Override
+ public int compareTo(PathEntry o) {
+ int c = doCompare(o);
+ return opt.isSet("r") ? -c : c;
+ }
+
+ private int doCompare(PathEntry o) {
+ if (opt.isSet("f")) {
+ return -1;
}
- final List<String> pieces = parseSubstring(target);
- Files.list(actualParent)
- .filter(p -> compareSubstring(pieces, p.getFileName().toString()))
- .map(actualParent::relativize)
- .forEach(files::add);
- } else {
- Path actualTarget = actualParent.resolve(target);
- if (!Files.exists(actualTarget)) {
- throw new IOException("File does not exist");
+ if (opt.isSet("S")) {
+ long s0 = attributes.get("size") != null ? ((Number) attributes.get("size")).longValue() : 0L;
+ long s1 = o.attributes.get("size") != null ? ((Number) o.attributes.get("size")).longValue() : 0L;
+ return s0 > s1 ? -1 : s0 < s1 ? 1 : path.toString().compareTo(o.path.toString());
}
- if (Files.isDirectory(actualTarget)) {
- Files.list(actualTarget)
- .map(actualTarget::relativize)
- .forEach(files::add);
+ if (opt.isSet("t")) {
+ long t0 = attributes.get("lastModifiedTime") != null ? ((FileTime) attributes.get("lastModifiedTime")).toMillis() : 0L;
+ long t1 = o.attributes.get("lastModifiedTime") != null ? ((FileTime) o.attributes.get("lastModifiedTime")).toMillis() : 0L;
+ return t0 > t1 ? -1 : t0 < t1 ? 1 : path.toString().compareTo(o.path.toString());
+ }
+ return path.toString().compareTo(o.path.toString());
+ }
+
+ boolean isNotDirectory() {
+ return is("isRegularFile") || is("isSymbolicLink") || is("isOther");
+ }
+
+ boolean isDirectory() {
+ return is("isDirectory");
+ }
+
+ private boolean is(String attr) {
+ Object d = attributes.get(attr);
+ return d instanceof Boolean && (Boolean) d;
+ }
+
+ String display() {
+ String type;
+ String suffix;
+ String link = "";
+ if (is("isDirectory")) {
+ type = "dr";
+ suffix = "/";
+ } else if (is("isExecutable")) {
+ type = "ex";
+ suffix = "*";
+ } else if (is("isSymbolicLink")) {
+ type = "sl";
+ suffix = "@";
+ try {
+ Path l = Files.readSymbolicLink(abs);
+ link = " -> " + l.toString();
+ } catch (IOException e) {
+ // ignore
+ }
+ } else if (is("isRegularFile")) {
+ type = "rg";
+ suffix = "";
+ } else if (is("isOther")) {
+ type = "ot";
+ suffix = "";
} else {
- files.add(actualTarget);
+ type = "";
+ suffix = "";
+ }
+ String col = colors.get(type);
+ boolean addSuffix = opt.isSet("F");
+ if (col != null && !col.isEmpty()) {
+ return "\033[" + col + "m" + path.toString() + "\033[m" + (addSuffix ? suffix : "") + link;
+ } else {
+ return path.toString() + (addSuffix ? suffix : "") + link;
}
}
+
+ String longDisplay() {
+ String username;
+ if (attributes.containsKey("owner")) {
+ username = Objects.toString(attributes.get("owner"), null);
+ } else {
+ username = "owner";
+ }
+ if (username.length() > 8) {
+ username = username.substring(0, 8);
+ } else {
+ for (int i = username.length(); i < 8; i++) {
+ username = username + " ";
+ }
+ }
+ String group;
+ if (attributes.containsKey("group")) {
+ group = Objects.toString(attributes.get("group"), null);
+ } else {
+ group = "group";
+ }
+ if (group.length() > 8) {
+ group = group.substring(0, 8);
+ } else {
+ for (int i = group.length(); i < 8; i++) {
+ group = group + " ";
+ }
+ }
+ Number length = (Number) attributes.get("size");
+ if (length == null) {
+ length = 0L;
+ }
+ String lengthString;
+ if (opt.isSet("h")) {
+ double l = length.longValue();
+ String unit = "B";
+ if (l >= 1000) {
+ l /= 1024;
+ unit = "K";
+ if (l >= 1000) {
+ l /= 1024;
+ unit = "M";
+ if (l >= 1000) {
+ l /= 1024;
+ unit = "T";
+ }
+ }
+ }
+ if (l < 10 && length.longValue() > 1000) {
+ lengthString = String.format("%.1f", l) + unit;
+ } else {
+ lengthString = String.format("%3.0f", l) + unit;
+ }
+ } else {
+ lengthString = String.format("%1$8s", length);
+ }
+ @SuppressWarnings("unchecked")
+ Set<PosixFilePermission> perms = (Set<PosixFilePermission>) attributes.get("permissions");
+ if (perms == null) {
+ perms = EnumSet.noneOf(PosixFilePermission.class);
+ }
+ // TODO: all fields should be padded to align
+ return (is("isDirectory") ? "d" : (is("isSymbolicLink") ? "l" : (is("isOther") ? "o" : "-")))
+ + PosixFilePermissions.toString(perms) + " "
+ + String.format("%3s", (attributes.containsKey("nlink") ? attributes.get("nlink").toString() : "1"))
+ + " " + username + " " + group + " " + lengthString + " "
+ + toString((FileTime) attributes.get("lastModifiedTime"))
+ + " " + display();
+ }
+
+ protected String toString(FileTime time) {
+ long millis = (time != null) ? time.toMillis() : -1L;
+ if (millis < 0L) {
+ return "------------";
+ }
+ ZonedDateTime dt = Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault());
+ // Less than six months
+ if (System.currentTimeMillis() - millis < 183L * 24L * 60L * 60L * 1000L) {
+ return DateTimeFormatter.ofPattern("MMM ppd HH:mm").format(dt);
+ }
+ // Older than six months
+ else {
+ return DateTimeFormatter.ofPattern("MMM ppd yyyy").format(dt);
+ }
+ }
+
+ protected Map<String, Object> readAttributes(Path path) {
+ Map<String, Object> attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (String view : path.getFileSystem().supportedFileAttributeViews()) {
+ try {
+ Map<String, Object> ta = Files.readAttributes(path, view + ":*",
+ IoUtils.getLinkOptions(opt.isSet("L")));
+ ta.entrySet().forEach(e -> attrs.putIfAbsent(e.getKey(), e.getValue()));
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ attrs.computeIfAbsent("isExecutable", s -> Files.isExecutable(path));
+ attrs.computeIfAbsent("permissions", s -> IoUtils.getPermissionsFromFile(path.toFile()));
+ return attrs;
+ }
}
- return files;
+
+ Path currentDir = session.currentDir();
+ // Listing
+ List<Path> expanded = new ArrayList<>();
+ if (opt.args().isEmpty()) {
+ expanded.add(currentDir);
+ } else {
+ opt.args().forEach(s -> expanded.add(currentDir.resolve(s)));
+ }
+ boolean listAll = opt.isSet("a");
+ Predicate<Path> filter = p -> listAll || !p.getFileName().toString().startsWith(".");
+ List<PathEntry> all = expanded.stream()
+ .filter(filter)
+ .map(p -> new PathEntry(p, currentDir))
+ .sorted()
+ .collect(Collectors.toList());
+ // Print files first
+ List<PathEntry> files = all.stream()
+ .filter(PathEntry::isNotDirectory)
+ .collect(Collectors.toList());
+ PrintStream out = System.out;
+ Consumer<Stream<PathEntry>> display = s -> {
+ // Comma separated list
+ if (opt.isSet("m")) {
+ out.println(s.map(PathEntry::display).collect(Collectors.joining(", ")));
+ }
+ // Long listing
+ else if (opt.isSet("l")) {
+ s.map(PathEntry::longDisplay).forEach(out::println);
+ }
+ // Column listing
+ else {
+ toColumn(session, out, s.map(PathEntry::display), opt.isSet("x"));
+ }
+ };
+ boolean space = false;
+ if (!files.isEmpty()) {
+ display.accept(files.stream());
+ space = true;
+ }
+ // Print directories
+ List<PathEntry> directories = all.stream()
+ .filter(PathEntry::isDirectory)
+ .collect(Collectors.toList());
+ for (PathEntry entry : directories) {
+ if (space) {
+ out.println();
+ }
+ space = true;
+ Path path = currentDir.resolve(entry.path);
+ if (expanded.size() > 1) {
+ out.println(currentDir.relativize(path).toString() + ":");
+ }
+ display.accept(Stream.concat(Arrays.asList(".", "..").stream().map(path::resolve), Files.list(path))
+ .filter(filter)
+ .map(p -> new PathEntry(p, path))
+ .sorted()
+ );
+ }
}
- public void cat(CommandSession session, String[] argv) throws Exception {
+ private void toColumn(CommandSession session, PrintStream out, Stream<String> ansi, boolean horizontal) {
+ Terminal terminal = Shell.getTerminal(session);
+ int width = terminal.getWidth();
+ List<AttributedString> strings = ansi.map(AttributedString::fromAnsi).collect(Collectors.toList());
+ if (!strings.isEmpty()) {
+ int max = strings.stream().mapToInt(AttributedString::columnLength).max().getAsInt();
+ int c = Math.max(1, width / max);
+ while (c > 1 && c * max + (c - 1) >= width) {
+ c--;
+ }
+ int columns = c;
+ int lines = (strings.size() + columns - 1) / columns;
+ IntBinaryOperator index;
+ if (horizontal) {
+ index = (i, j) -> i * columns + j;
+ } else {
+ index = (i, j) -> j * lines + i;
+ }
+ AttributedStringBuilder sb = new AttributedStringBuilder();
+ for (int i = 0; i < lines; i++) {
+ for (int j = 0; j < columns; j++) {
+ int idx = index.applyAsInt(i, j);
+ if (idx < strings.size()) {
+ AttributedString str = strings.get(idx);
+ boolean hasRightItem = j < columns - 1 && index.applyAsInt(i, j + 1) < strings.size();
+ sb.append(str);
+ if (hasRightItem) {
+ for (int k = 0; k <= max - str.length(); k++) {
+ sb.append(' ');
+ }
+ }
+ }
+ }
+ sb.append('\n');
+ }
+ out.print(sb.toAnsi());
+ }
+ }
+
+ protected 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;
- }
-
+ Options opt = parseOptions(session, usage, argv);
List<String> args = opt.args();
if (args.isEmpty()) {
args = Collections.singletonList("-");
}
-
Path cwd = session.currentDir();
for (String arg : args) {
InputStream is;
if ("-".equals(arg)) {
is = System.in;
-
} else {
is = cwd.toUri().resolve(arg).toURL().openStream();
}
@@ -375,21 +539,14 @@
}
}
- public void echo(Object[] argv) {
+ protected void echo(CommandSession session, Object[] argv) throws Exception {
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;
- }
-
+ Options opt = parseOptions(session, usage, argv);
List<String> args = opt.args();
StringBuilder buf = new StringBuilder();
if (args != null) {
@@ -406,115 +563,195 @@
}
}
- public boolean grep(CommandSession session, String[] argv) throws IOException {
+ protected void grep(CommandSession session, String[] argv) throws Exception {
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;
- }
-
+ " -? --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",
+ " -w --word-regexp Select only whole words",
+ " -x --line-regexp Select only whole lines",
+ " -c --count Only print a count of matching lines per file",
+ " --color=WHEN Use markers to distinguish the matching string, may be `always', `never' or `auto'",
+ " -B --before-context=NUM Print NUM lines of leading context before matching lines",
+ " -A --after-context=NUM Print NUM lines of trailing context after matching lines",
+ " -C --context=NUM Print NUM lines of output context"
+ };
+ Options opt = parseOptions(session, usage, argv);
List<String> args = opt.args();
-
if (args.isEmpty()) {
- throw opt.usageError("no pattern supplied.");
+ throw new IllegalArgumentException("no pattern supplied");
}
String regex = args.remove(0);
+ String regexp = regex;
+ if (opt.isSet("word-regexp")) {
+ regexp = "\\b" + regexp + "\\b";
+ }
+ if (opt.isSet("line-regexp")) {
+ regexp = "^" + regexp + "$";
+ } else {
+ regexp = ".*" + regexp + ".*";
+ }
+ Pattern p;
+ Pattern p2;
if (opt.isSet("ignore-case")) {
- regex = "(?i)" + regex;
+ p = Pattern.compile(regexp, Pattern.CASE_INSENSITIVE);
+ p2 = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+ } else {
+ p = Pattern.compile(regexp);
+ p2 = Pattern.compile(regex);
}
-
- if (args.isEmpty()) {
- args.add(null);
+ int after = opt.isSet("after-context") ? opt.getNumber("after-context") : -1;
+ int before = opt.isSet("before-context") ? opt.getNumber("before-context") : -1;
+ int context = opt.isSet("context") ? opt.getNumber("context") : 0;
+ if (after < 0) {
+ after = context;
}
-
- StringBuilder buf = new StringBuilder();
-
- if (args.size() > 1) {
- buf.append("%1$s:");
+ if (before < 0) {
+ before = context;
}
+ List<String> lines = new ArrayList<String>();
+ boolean invertMatch = opt.isSet("invert-match");
+ boolean lineNumber = opt.isSet("line-number");
+ boolean count = opt.isSet("count");
+ String color = opt.isSet("color") ? opt.get("color") : "auto";
- if (opt.isSet("line-number")) {
- buf.append("%2$s:");
+ List<Source> sources = new ArrayList<>();
+ if (opt.args().isEmpty()) {
+ opt.args().add("-");
}
-
- 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 {
- Path cwd = session.currentDir();
- in = (arg == null) ? System.in : cwd.resolve(arg).toUri().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();
- }
+ for (String arg : opt.args()) {
+ if ("-".equals(arg)) {
+ sources.add(new StdInSource());
+ } else {
+ sources.add(new URLSource(session.currentDir().resolve(arg).toUri().toURL(), arg));
}
}
-
- return match && status;
+ boolean match = false;
+ for (Source source : sources) {
+ boolean firstPrint = true;
+ int nb = 0;
+ int lineno = 1;
+ String line;
+ int lineMatch = 0;
+ try (BufferedReader r = new BufferedReader(new InputStreamReader(source.read()))) {
+ while ((line = r.readLine()) != null) {
+ if (line.length() == 1 && line.charAt(0) == '\n') {
+ break;
+ }
+ if (p.matcher(line).matches() ^ invertMatch) {
+ AttributedStringBuilder sbl = new AttributedStringBuilder();
+ sbl.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.BLACK + AttributedStyle.BRIGHT));
+ if (!count && sources.size() > 1) {
+ sbl.append(source.getName());
+ sbl.append(":");
+ }
+ if (!count && lineNumber) {
+ sbl.append(String.format("%6d ", lineno));
+ }
+ sbl.style(AttributedStyle.DEFAULT);
+ Matcher matcher2 = p2.matcher(line);
+ AttributedString aLine = AttributedString.fromAnsi(line);
+ AttributedStyle style = AttributedStyle.DEFAULT;
+ if (!invertMatch && !color.equalsIgnoreCase("never")) {
+ style = style.bold().foreground(AttributedStyle.RED);
+ }
+ int cur = 0;
+ while (matcher2.find()) {
+ int index = matcher2.start(0);
+ AttributedString prefix = aLine.subSequence(cur, index);
+ sbl.append(prefix);
+ cur = matcher2.end();
+ sbl.append(aLine.subSequence(index, cur), style);
+ nb++;
+ }
+ sbl.append(aLine.subSequence(cur, aLine.length()));
+ lines.add(sbl.toAnsi(Shell.getTerminal(session)));
+ lineMatch = lines.size();
+ } else {
+ if (lineMatch != 0 & lineMatch + after + before <= lines.size()) {
+ if (!count) {
+ if (!firstPrint && before + after > 0) {
+ System.out.println("--");
+ } else {
+ firstPrint = false;
+ }
+ for (int i = 0; i < lineMatch + after; i++) {
+ System.out.println(lines.get(i));
+ }
+ }
+ while (lines.size() > before) {
+ lines.remove(0);
+ }
+ lineMatch = 0;
+ }
+ lines.add(line);
+ while (lineMatch == 0 && lines.size() > before) {
+ lines.remove(0);
+ }
+ }
+ lineno++;
+ }
+ if (!count && lineMatch > 0) {
+ if (!firstPrint && before + after > 0) {
+ System.out.println("--");
+ } else {
+ firstPrint = false;
+ }
+ for (int i = 0; i < lineMatch + after && i < lines.size(); i++) {
+ System.out.println(lines.get(i));
+ }
+ }
+ if (count) {
+ System.out.println(nb);
+ }
+ match |= nb > 0;
+ }
+ }
+ Pipe.error(match ? 0 : 1);
}
- public void sleep(String[] argv) throws InterruptedException {
+ protected void sleep(CommandSession session, String[] argv) throws Exception {
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;
- }
-
+ Options opt = parseOptions(session, usage, argv);
List<String> args = opt.args();
if (args.size() != 1) {
- System.err.println("usage: sleep seconds");
+ throw new IllegalArgumentException("usage: sleep seconds");
} else {
int s = Integer.parseInt(args.get(0));
Thread.sleep(s * 1000);
}
}
+ protected static void read(BufferedReader r, List<String> lines) throws IOException {
+ for (String s = r.readLine(); s != null; s = r.readLine()) {
+ lines.add(s);
+ }
+ }
+
+ 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();
+ }
+ }
+
public static class SortComparator implements Comparator<String> {
private static Pattern fpPattern;
@@ -546,7 +783,7 @@
this.ignoreBlanks = ignoreBlanks;
this.numeric = numeric;
if (sortFields == null || sortFields.size() == 0) {
- sortFields = new ArrayList<String>();
+ sortFields = new ArrayList<>();
sortFields.add("1");
}
sortKeys = new ArrayList<Key>();
@@ -643,7 +880,7 @@
}
protected List<Integer> getFieldIndexes(String o) {
- List<Integer> fields = new ArrayList<Integer>();
+ List<Integer> fields = new ArrayList<>();
if (o.length() > 0) {
if (separator == '\0') {
int i = 0;
@@ -778,4 +1015,100 @@
}
}
+ private String[] expand(CommandSession session, String[] argv) throws IOException {
+ String reserved = "(?<!\\\\)[*(|<\\[?]";
+ List<String> params = new ArrayList<>();
+ for (String arg : argv) {
+ if (arg.matches(".*" + reserved + ".*")) {
+ String org = arg;
+ List<String> expanded = new ArrayList<>();
+ Path currentDir = session.currentDir();
+ Path dir;
+ String pfx = arg.replaceFirst(reserved + ".*", "");
+ String prefix;
+ if (pfx.indexOf('/') >= 0) {
+ pfx = pfx.substring(0, pfx.lastIndexOf('/'));
+ arg = arg.substring(pfx.length() + 1);
+ dir = currentDir.resolve(pfx).normalize();
+ prefix = pfx + "/";
+ } else {
+ dir = currentDir;
+ prefix = "";
+ }
+ PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:" + arg);
+ Files.walkFileTree(dir,
+ EnumSet.of(FileVisitOption.FOLLOW_LINKS),
+ Integer.MAX_VALUE,
+ new FileVisitor<Path>() {
+ @Override
+ public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {
+ if (file.equals(dir)) {
+ return FileVisitResult.CONTINUE;
+ }
+ if (Files.isHidden(file)) {
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ Path r = dir.relativize(file);
+ if (matcher.matches(r)) {
+ expanded.add(prefix + r.toString());
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ if (!Files.isHidden(file)) {
+ Path r = dir.relativize(file);
+ if (matcher.matches(r)) {
+ expanded.add(prefix + r.toString());
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+ return FileVisitResult.CONTINUE;
+ }
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ Collections.sort(expanded);
+ if (expanded.isEmpty()) {
+ throw new IOException("no matches found: " + org);
+ }
+ params.addAll(expanded);
+ } else {
+ params.add(arg);
+ }
+ }
+ return params.toArray(new String[params.size()]);
+ }
+
+ private Map<String, String> getColorMap(CommandSession session, String name, String def) {
+ Object obj = session.get(name + "_COLORS");
+ String str = obj != null ? obj.toString() : null;
+ if (str == null || !str.matches("[a-z]{2}=[0-9]+(;[0-9]+)*(:[a-z]{2}=[0-9]+(;[0-9]+)*)*")) {
+ str = def;
+ }
+ return Arrays.stream(str.split(":"))
+ .collect(Collectors.toMap(s -> s.substring(0, s.indexOf('=')),
+ s -> s.substring(s.indexOf('=') + 1)));
+ }
+
+ private Options parseOptions(CommandSession session, String[] usage, Object[] argv) throws Exception {
+ Options opt = Options.compile(usage, s -> get(session, s)).parse(argv, true);
+ if (opt.isSet("help")) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ opt.usage(new PrintStream(baos));
+ throw new HelpException(baos.toString());
+ }
+ return opt;
+ }
+
+ private String get(CommandSession session, String name) {
+ Object o = session.get(name);
+ return o != null ? o.toString() : null;
+ }
+
}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
index 77febe8..d5a9a3c 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -68,6 +68,7 @@
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_RESULT = "_";
public static final String VAR_LOCATION = ".location";
public static final String VAR_PROMPT = "prompt";
public static final String VAR_RPROMPT = "rprompt";
@@ -229,8 +230,7 @@
throw opt.usageError("option --command requires argument(s)");
}
- CommandSession newSession = (login ? session : processor.createSession(
- session.getKeyboard(), session.getConsole(), System.err));
+ CommandSession newSession = (login ? session : processor.createSession(session));
if (opt.isSet("xtrace")) {
newSession.put("echo", true);
@@ -251,7 +251,7 @@
newSession.put("#TERM", (Function) (s, arguments) -> terminal.getType());
newSession.put("#COLUMNS", (Function) (s, arguments) -> terminal.getWidth());
newSession.put("#LINES", (Function) (s, arguments) -> terminal.getHeight());
- newSession.put("#CWD", (Function) (s, arguments) -> s.currentDir().toString());
+ newSession.put("#PWD", (Function) (s, arguments) -> s.currentDir().toString());
LineReader reader = null;
if (args.isEmpty() && interactive) {
@@ -294,7 +294,7 @@
}
try {
result = session.execute(((ParsedLineImpl) parsedLine).program());
- session.put("_", result); // set $_ to last result
+ session.put(Shell.VAR_RESULT, result); // set $_ to last result
if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
System.out.println(session.format(result, Converter.INSPECT));
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
index 7e1cc89..2a63c11 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Closure.java
@@ -31,6 +31,8 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import org.apache.felix.gogo.runtime.Parser.Array;
import org.apache.felix.gogo.runtime.Parser.Executable;
@@ -39,6 +41,7 @@
import org.apache.felix.gogo.runtime.Parser.Program;
import org.apache.felix.gogo.runtime.Parser.Sequence;
import org.apache.felix.gogo.runtime.Parser.Statement;
+import org.apache.felix.gogo.runtime.Pipe.Result;
import org.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Function;
@@ -46,9 +49,10 @@
{
public static final String LOCATION = ".location";
+ public static final String PIPE_EXCEPTION = "pipe-exception";
private static final String DEFAULT_LOCK = ".defaultLock";
- private static final ThreadLocal<String> location = new ThreadLocal<String>();
+ private static final ThreadLocal<String> location = new ThreadLocal<>();
private final CommandSessionImpl session;
private final Closure parent;
@@ -178,51 +182,45 @@
}
}
- Pipe last = null;
+ Result last = null;
Operator operator = null;
for (Iterator<Executable> iterator = program.tokens().iterator(); iterator.hasNext();)
{
- if (operator != null) {
- if (Token.eq("&&", operator)) {
- if (!isSuccess(last)) {
- continue;
- }
- }
- else if (Token.eq("||", operator)) {
- if (isSuccess(last)) {
- continue;
- }
- }
- }
+ Operator prevOperator = operator;
Executable executable = iterator.next();
if (iterator.hasNext()) {
operator = (Operator) iterator.next();
} else {
operator = null;
}
-
- if (operator != null && Token.eq("&", operator)) {
- // TODO: need to start in background
+ if (prevOperator != null) {
+ if (Token.eq("&&", prevOperator)) {
+ if (!last.isSuccess()) {
+ continue;
+ }
+ }
+ else if (Token.eq("||", prevOperator)) {
+ if (last.isSuccess()) {
+ continue;
+ }
+ }
}
- Channel[] mark = Pipe.mark();
Channel[] streams;
boolean[] toclose = new boolean[10];
- if (mark == null) {
- streams = new Channel[10];
- streams[0] = Channels.newChannel(session.in);
- streams[1] = Channels.newChannel(session.out);
- streams[2] = Channels.newChannel(session.err);
+ if (Pipe.getCurrentPipe() != null) {
+ streams = Pipe.getCurrentPipe().streams.clone();
} else {
- streams = mark.clone();
+ streams = new Channel[10];
+ System.arraycopy(session.channels, 0, streams, 0, 3);
}
- List<Pipe> pipes = new ArrayList<Pipe>();
+ List<Pipe> pipes = new ArrayList<>();
if (executable instanceof Pipeline) {
Pipeline pipeline = (Pipeline) executable;
List<Executable> exec = pipeline.tokens();
for (int i = 0; i < exec.size(); i++) {
- Executable ex = exec.get(i);
+ Statement ex = (Statement) exec.get(i);
Operator op = i < exec.size() - 1 ? (Operator) exec.get(++i) : null;
Channel[] nstreams;
boolean[] ntoclose;
@@ -253,57 +251,53 @@
pipes.add(new Pipe(this, ex, nstreams, ntoclose));
}
} else {
- pipes.add(new Pipe(this, executable, streams, toclose));
+ pipes.add(new Pipe(this, (Statement) executable, streams, toclose));
}
- // Don't start a thread if we have a single pipe
- if (pipes.size() == 1)
- {
- pipes.get(0).run();
- }
- else {
- // Start threads
+ // Start pipe in background
+ if (operator != null && Token.eq("&", operator)) {
+
for (Pipe pipe : pipes) {
- pipe.start();
+ session().getExecutor().submit(pipe);
}
- // Wait for them
- try {
- for (Pipe pipe : pipes) {
- pipe.join();
- }
- } catch (InterruptedException e) {
- for (Pipe pipe : pipes) {
- pipe.interrupt();
- }
- throw e;
- }
- }
- for (int i = 0; i < pipes.size() - 1; i++)
- {
- Pipe pipe = pipes.get(i);
- if (pipe.exception != null)
- {
- // can't throw exception, as result is defined by last pipe
- session.put("pipe-exception", pipe.exception);
- }
- }
- last = pipes.get(pipes.size() - 1);
+ last = new Result((Object) null);
- boolean errReturn = true;
- if (last.exit != 0 && errReturn)
- {
- throw last.exception;
+ }
+ // Start in foreground and wait for results
+ else {
+ List<Future<Result>> results = session().getExecutor().invokeAll(pipes);
+
+ // Get pipe exceptions
+ Exception pipeException = null;
+ for (int i = 0; i < results.size() - 1; i++) {
+ Future<Result> future = results.get(i);
+ Throwable e;
+ try {
+ Result r = future.get();
+ e = r.exception;
+ } catch (ExecutionException ee) {
+ e = ee.getCause();
+ }
+ if (e != null) {
+ if (pipeException == null) {
+ pipeException = new Exception("Exception caught during pipe execution");
+ }
+ pipeException.addSuppressed(e);
+ }
+ }
+ session.put(PIPE_EXCEPTION, pipeException);
+
+ last = results.get(results.size() - 1).get();
+ if (last.exception != null) {
+ throw last.exception;
+ }
}
}
return last == null ? null : last.result;
}
- private boolean isSuccess(Pipe pipe) {
- return pipe.exit == 0;
- }
-
private Object eval(Object v)
{
String s = v.toString();
@@ -368,7 +362,7 @@
}
else if (executable instanceof Sequence)
{
- return new Closure(session, this, ((Sequence) executable).program()).execute(new ArrayList<Object>());
+ return new Closure(session, this, ((Sequence) executable).program()).execute(new ArrayList<>());
}
else
{
@@ -393,7 +387,7 @@
{
// set -x execution trace
xtrace = "+" + statement;
- session.err.println(xtrace);
+ session.perr.println(xtrace);
}
List<Token> tokens = statement.tokens();
@@ -402,7 +396,7 @@
return null;
}
- List<Object> values = new ArrayList<Object>();
+ List<Object> values = new ArrayList<>();
errTok = tokens.get(0);
if ((tokens.size() > 3) && Token.eq("=", tokens.get(1)))
@@ -496,7 +490,7 @@
if (!trace2.equals(trace1))
{
- session.err.println("+" + trace2);
+ session.perr.println("+" + trace2);
}
}
}
@@ -567,7 +561,7 @@
if (dot)
{
Object target = cmd;
- ArrayList<Object> args = new ArrayList<Object>();
+ ArrayList<Object> args = new ArrayList<>();
values.remove(0);
for (Object arg : values)
@@ -621,7 +615,7 @@
if (list != null)
{
- List<Object> olist = new ArrayList<Object>();
+ List<Object> olist = new ArrayList<>();
for (Token t : list)
{
Object oval = eval(t);
@@ -638,7 +632,7 @@
}
else
{
- Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
+ Map<Object, Object> omap = new LinkedHashMap<>();
for (Entry<Token, Token> e : map.entrySet())
{
Token key = e.getKey();
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
index c8fcd3a..8f07108 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandProcessorImpl.java
@@ -19,8 +19,10 @@
package org.apache.felix.gogo.runtime;
import java.io.InputStream;
-import java.io.PrintStream;
+import java.io.OutputStream;
import java.lang.reflect.Method;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -42,12 +44,12 @@
public class CommandProcessorImpl implements CommandProcessor
{
- protected final Set<Converter> converters = new CopyOnWriteArraySet<Converter>();
- protected final Set<CommandSessionListener> listeners = new CopyOnWriteArraySet<CommandSessionListener>();
- protected final ConcurrentMap<String, Map<Object, Integer>> commands = new ConcurrentHashMap<String, Map<Object, Integer>>();
- protected final Map<String, Object> constants = new ConcurrentHashMap<String, Object>();
+ protected final Set<Converter> converters = new CopyOnWriteArraySet<>();
+ protected final Set<CommandSessionListener> listeners = new CopyOnWriteArraySet<>();
+ protected final ConcurrentMap<String, Map<Object, Integer>> commands = new ConcurrentHashMap<>();
+ protected final Map<String, Object> constants = new ConcurrentHashMap<>();
protected final ThreadIO threadIO;
- protected final WeakHashMap<CommandSession, Object> sessions = new WeakHashMap<CommandSession, Object>();
+ protected final WeakHashMap<CommandSession, Object> sessions = new WeakHashMap<>();
protected boolean stopped;
public CommandProcessorImpl(ThreadIO tio)
@@ -55,7 +57,37 @@
threadIO = tio;
}
- public CommandSession createSession(InputStream in, PrintStream out, PrintStream err)
+ @Override
+ public CommandSessionImpl createSession(CommandSession parent) {
+ synchronized (sessions) {
+ if (stopped)
+ {
+ throw new IllegalStateException("CommandProcessor has been stopped");
+ }
+ if (!sessions.containsKey(parent) || !(parent instanceof CommandSessionImpl)) {
+ throw new IllegalArgumentException();
+ }
+ CommandSessionImpl session = new CommandSessionImpl(this, (CommandSessionImpl) parent);
+ sessions.put(session, null);
+ return session;
+ }
+ }
+
+ public CommandSessionImpl createSession(ReadableByteChannel in, WritableByteChannel out, WritableByteChannel err)
+ {
+ synchronized (sessions)
+ {
+ if (stopped)
+ {
+ throw new IllegalStateException("CommandProcessor has been stopped");
+ }
+ CommandSessionImpl session = new CommandSessionImpl(this, in, out, err);
+ sessions.put(session, null);
+ return session;
+ }
+ }
+
+ public CommandSessionImpl createSession(InputStream in, OutputStream out, OutputStream err)
{
synchronized (sessions)
{
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
index cc38c8d..f914a23 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
@@ -22,9 +22,14 @@
package org.apache.felix.gogo.runtime;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.nio.channels.Channel;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -37,6 +42,8 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.CommandSession;
@@ -52,23 +59,62 @@
public static final String CONSTANTS = ".constants";
private static final String COLUMN = "%-20s %s\n";
+ // Streams and channels
protected InputStream in;
- protected PrintStream out;
- PrintStream err;
+ protected OutputStream out;
+ protected PrintStream pout;
+ protected OutputStream err;
+ protected PrintStream perr;
+ protected Channel[] channels;
private final CommandProcessorImpl processor;
- protected final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<String, Object>();
+ protected final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<>();
private volatile boolean closed;
+ private final ExecutorService executor;
+
private Path currentDir;
- protected CommandSessionImpl(CommandProcessorImpl shell, InputStream in, PrintStream out, PrintStream err)
+ protected CommandSessionImpl(CommandProcessorImpl shell, CommandSessionImpl parent)
{
+ this.currentDir = parent.currentDir;
+ this.executor = Executors.newCachedThreadPool();
this.processor = shell;
+ this.channels = parent.channels;
+ this.in = parent.in;
+ this.out = parent.out;
+ this.err = parent.err;
+ this.pout = parent.pout;
+ this.perr = parent.perr;
+ }
+
+ protected CommandSessionImpl(CommandProcessorImpl shell, ReadableByteChannel in, WritableByteChannel out, WritableByteChannel err)
+ {
+ this.currentDir = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
+ this.executor = Executors.newCachedThreadPool();
+ this.processor = shell;
+ this.channels = new Channel[] { in, out, err };
+ this.in = Channels.newInputStream(in);
+ this.out = Channels.newOutputStream(out);
+ this.err = out == err ? this.out : Channels.newOutputStream(err);
+ this.pout = new PrintStream(this.out, true);
+ this.perr = out == err ? pout : new PrintStream(this.err, true);
+ }
+
+ protected CommandSessionImpl(CommandProcessorImpl shell, InputStream in, OutputStream out, OutputStream err)
+ {
+ this.currentDir = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
+ this.executor = Executors.newCachedThreadPool();
+ this.processor = shell;
+ ReadableByteChannel inCh = Channels.newChannel(in);
+ WritableByteChannel outCh = Channels.newChannel(out);
+ WritableByteChannel errCh = out == err ? outCh : Channels.newChannel(err);
+ this.channels = new Channel[] { inCh, outCh, errCh };
this.in = in;
this.out = out;
this.err = err;
- this.currentDir = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
+ this.pout = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out, true);
+ this.perr = out == err ? pout : err instanceof PrintStream ? (PrintStream) err : new PrintStream(err, true);
}
ThreadIO threadIO()
@@ -98,11 +144,16 @@
{
if (!this.closed)
{
- this.processor.closeSession(this);
this.closed = true;
+ this.processor.closeSession(this);
+ executor.shutdownNow();
}
}
+ ExecutorService getExecutor() {
+ return executor;
+ }
+
public Object execute(CharSequence commandline) throws Exception
{
assert processor != null;
@@ -199,7 +250,7 @@
public PrintStream getConsole()
{
- return out;
+ return pout;
}
@SuppressWarnings("unchecked")
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
index de317fc..fdf8fc1 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Parser.java
@@ -205,7 +205,7 @@
int start = tz.index - 1;
while (true)
{
- Executable ex;
+ Statement ex;
Token t = next();
if (t == null)
{
@@ -260,7 +260,7 @@
{
if (pipes == null)
{
- pipes = new ArrayList<Executable>();
+ pipes = new ArrayList<>();
}
pipes.add(ex);
pipes.add(new Operator(t));
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
index 6b01470..e92b329 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Pipe.java
@@ -33,46 +33,81 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.apache.felix.gogo.runtime.Parser.Executable;
import org.apache.felix.gogo.runtime.Parser.Statement;
+import org.apache.felix.gogo.runtime.Pipe.Result;
import org.apache.felix.service.command.Converter;
-public class Pipe extends Thread
+public class Pipe implements Callable<Result>
{
- static final ThreadLocal<Channel[]> tStreams = new ThreadLocal<Channel[]>();
+ private static final ThreadLocal<Pipe> CURRENT = new ThreadLocal<>();
- public static Channel[] mark() {
- return tStreams.get();
+ public static class Result {
+ public final Object result;
+ public final Exception exception;
+ public final int error;
+
+ public Result(Object result) {
+ this.result = result;
+ this.exception = null;
+ this.error = 0;
+ }
+
+ public Result(Exception exception) {
+ this.result = null;
+ this.exception = exception;
+ this.error = 1;
+ }
+
+ public Result(int error) {
+ this.result = null;
+ this.exception = null;
+ this.error = error;
+ }
+
+ public boolean isSuccess() {
+ return exception == null && error == 0;
+ }
}
- public static void reset(Channel[] streams) {
- tStreams.set(streams);
+ public static Pipe getCurrentPipe() {
+ return CURRENT.get();
+ }
+
+ public static void error(int error) {
+ Pipe current = getCurrentPipe();
+ if (current != null) {
+ current.error = error;
+ }
+ }
+
+ private static Pipe setCurrentPipe(Pipe pipe) {
+ Pipe previous = CURRENT.get();
+ CURRENT.set(pipe);
+ return previous;
}
final Closure closure;
- final Executable executable;
+ final Statement statement;
final Channel[] streams;
final boolean[] toclose;
- Object result;
- Exception exception;
- int exit = 0;
+ int error;
- public Pipe(Closure closure, Executable executable, Channel[] streams, boolean[] toclose)
+ public Pipe(Closure closure, Statement statement, Channel[] streams, boolean[] toclose)
{
- super("pipe-" + executable);
this.closure = closure;
- this.executable = executable;
+ this.statement = statement;
this.streams = streams;
this.toclose = toclose;
}
public String toString()
{
- return "pipe<" + executable + "> out=" + streams[1];
+ return "pipe<" + statement + "> out=" + streams[1];
}
private static final int READ = 1;
@@ -148,8 +183,8 @@
}
private static class MultiChannel<T extends Channel> implements Channel {
- protected final List<T> channels = new ArrayList<T>();
- protected final List<T> toClose = new ArrayList<T>();
+ protected final List<T> channels = new ArrayList<>();
+ protected final List<T> toClose = new ArrayList<>();
protected final AtomicBoolean opened = new AtomicBoolean(true);
public void addChannel(T channel, boolean toclose) {
channels.add(channel);
@@ -200,14 +235,30 @@
}
}
- public void run()
+ @Override
+ public Result call() throws Exception {
+ Thread thread = Thread.currentThread();
+ String name = thread.getName();
+ try {
+ thread.setName("pipe-" + statement);
+ return doCall();
+ } finally {
+ thread.setName(name);
+ }
+ }
+
+ public Result doCall()
{
- InputStream in = null;
+ InputStream in;
PrintStream out = null;
PrintStream err = null;
- WritableByteChannel errChannel = (WritableByteChannel) streams[2];
- Channel[] prevStreams = tStreams.get();
+ // The errChannel will be used to print errors to the error stream
+ // Before the command is actually executed (i.e. during the initialization,
+ // including the redirection processing), it will be the original error stream.
+ // This value may be modified by redirections and the redirected error stream
+ // will be effective just before actually running the command.
+ WritableByteChannel errChannel = (WritableByteChannel) streams[2];
// TODO: not sure this is the correct way
boolean begOfPipe = !toclose[0];
@@ -215,98 +266,101 @@
try
{
- if (executable instanceof Statement) {
- Statement statement = (Statement) executable;
- List<Token> tokens = statement.redirections();
- for (int i = 0; i < tokens.size(); i++) {
- Token t = tokens.get(i);
- Matcher m;
- if ((m = Pattern.compile("(?:([0-9])?|(&)?)>(>)?").matcher(t)).matches()) {
- int fd;
- if (m.group(1) != null) {
- fd = Integer.parseInt(m.group(1));
- }
- else if (m.group(2) != null) {
- fd = -1; // both 1 and 2
- } else {
- fd = 1;
- }
- boolean append = m.group(3) != null;
- Token file = tokens.get(++i);
- Path outPath = closure.session().currentDir().resolve(file.toString());
- Set<StandardOpenOption> options = new HashSet<StandardOpenOption>();
- options.add(StandardOpenOption.WRITE);
- options.add(StandardOpenOption.CREATE);
- if (append) {
- options.add(StandardOpenOption.APPEND);
- } else {
- options.add(StandardOpenOption.TRUNCATE_EXISTING);
- }
- Channel ch = Files.newByteChannel(outPath, options);
- if (fd >= 0) {
- setStream(ch, fd, WRITE, begOfPipe, endOfPipe);
- } else {
- setStream(ch, 1, WRITE, begOfPipe, endOfPipe);
- setStream(ch, 2, WRITE, begOfPipe, endOfPipe);
- }
+ List<Token> tokens = statement.redirections();
+ for (int i = 0; i < tokens.size(); i++) {
+ Token t = tokens.get(i);
+ Matcher m;
+ if ((m = Pattern.compile("(?:([0-9])?|(&)?)>(>)?").matcher(t)).matches()) {
+ int fd;
+ if (m.group(1) != null) {
+ fd = Integer.parseInt(m.group(1));
}
- else if ((m = Pattern.compile("([0-9])?>&([0-9])").matcher(t)).matches()) {
- int fd0 = 1;
- if (m.group(1) != null) {
- fd0 = Integer.parseInt(m.group(1));
- }
- int fd1 = Integer.parseInt(m.group(2));
- if (streams[fd0] != null && toclose[fd0]) {
- streams[fd0].close();
- }
- streams[fd0] = streams[fd1];
- // TODO: this is wrong, we should keep a counter somehow so that the
- // stream is closed when both are closed
- toclose[fd0] = false;
+ else if (m.group(2) != null) {
+ fd = -1; // both 1 and 2
+ } else {
+ fd = 1;
}
- else if ((m = Pattern.compile("([0-9])?<(>)?").matcher(t)).matches()) {
- int fd = 0;
- if (m.group(1) != null) {
- fd = Integer.parseInt(m.group(1));
- }
- boolean output = m.group(2) != null;
- Token file = tokens.get(++i);
- Path inPath = closure.session().currentDir().resolve(file.toString());
- Set<StandardOpenOption> options = new HashSet<StandardOpenOption>();
- options.add(StandardOpenOption.READ);
- if (output) {
- options.add(StandardOpenOption.WRITE);
- options.add(StandardOpenOption.CREATE);
- }
- Channel ch = Files.newByteChannel(inPath, options);
- setStream(ch, fd, READ + (output ? WRITE : 0), begOfPipe, endOfPipe);
+ boolean append = m.group(3) != null;
+ Token file = tokens.get(++i);
+ Path outPath = closure.session().currentDir().resolve(file.toString());
+ Set<StandardOpenOption> options = new HashSet<>();
+ options.add(StandardOpenOption.WRITE);
+ options.add(StandardOpenOption.CREATE);
+ if (append) {
+ options.add(StandardOpenOption.APPEND);
+ } else {
+ options.add(StandardOpenOption.TRUNCATE_EXISTING);
+ }
+ Channel ch = Files.newByteChannel(outPath, options);
+ if (fd >= 0) {
+ setStream(ch, fd, WRITE, begOfPipe, endOfPipe);
+ } else {
+ setStream(ch, 1, WRITE, begOfPipe, endOfPipe);
+ setStream(ch, 2, WRITE, begOfPipe, endOfPipe);
}
}
- } else {
- new UnsupportedOperationException("what to do ?").printStackTrace();
+ else if ((m = Pattern.compile("([0-9])?>&([0-9])").matcher(t)).matches()) {
+ int fd0 = 1;
+ if (m.group(1) != null) {
+ fd0 = Integer.parseInt(m.group(1));
+ }
+ int fd1 = Integer.parseInt(m.group(2));
+ if (streams[fd0] != null && toclose[fd0]) {
+ streams[fd0].close();
+ }
+ streams[fd0] = streams[fd1];
+ // TODO: this is wrong, we should keep a counter somehow so that the
+ // stream is closed when both are closed
+ toclose[fd0] = false;
+ }
+ else if ((m = Pattern.compile("([0-9])?<(>)?").matcher(t)).matches()) {
+ int fd = 0;
+ if (m.group(1) != null) {
+ fd = Integer.parseInt(m.group(1));
+ }
+ boolean output = m.group(2) != null;
+ Token file = tokens.get(++i);
+ Path inPath = closure.session().currentDir().resolve(file.toString());
+ Set<StandardOpenOption> options = new HashSet<>();
+ options.add(StandardOpenOption.READ);
+ if (output) {
+ options.add(StandardOpenOption.WRITE);
+ options.add(StandardOpenOption.CREATE);
+ }
+ Channel ch = Files.newByteChannel(inPath, options);
+ setStream(ch, fd, READ + (output ? WRITE : 0), begOfPipe, endOfPipe);
+ }
}
- tStreams.set(streams);
-
+ // Create streams
in = Channels.newInputStream((ReadableByteChannel) streams[0]);
out = new PrintStream(Channels.newOutputStream((WritableByteChannel) streams[1]), true);
err = new PrintStream(Channels.newOutputStream((WritableByteChannel) streams[2]), true);
+ // Change the error stream to the redirected one, now that
+ // the command is about to be executed.
errChannel = (WritableByteChannel) streams[2];
closure.session().threadIO().setStreams(in, out, err);
- result = closure.execute(executable);
- // We don't print the result if toclose[1] == false, which means we're at the end of the pipe
- if (result != null && !endOfPipe && !Boolean.FALSE.equals(closure.session().get(".FormatPipe"))) {
- out.println(closure.session().format(result, Converter.INSPECT));
+ Pipe previous = setCurrentPipe(this);
+ try {
+ Object result = closure.execute(statement);
+ // If an error has been set
+ if (error != 0) {
+ return new Result(error);
+ }
+ // We don't print the result if we're at the end of the pipe
+ if (result != null && !endOfPipe && !Boolean.FALSE.equals(closure.session().get(".FormatPipe"))) {
+ out.println(closure.session().format(result, Converter.INSPECT));
+ }
+ return new Result(result);
+
+ } finally {
+ setCurrentPipe(previous);
}
}
catch (Exception e)
{
- exception = e;
- if (exit == 0) {
- exit = 1; // failure
- }
// TODO: use shell name instead of 'gogo'
// TODO: use color if not redirected
// TODO: use conversion ?
@@ -316,6 +370,7 @@
} catch (IOException ioe) {
e.addSuppressed(ioe);
}
+ return new Result(e);
}
finally
{
@@ -327,8 +382,6 @@
}
closure.session().threadIO().close();
- tStreams.set(prevStreams);
-
try
{
for (int i = 0; i < 10; i++) {
diff --git a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandProcessor.java b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandProcessor.java
index 2d77053..b602ade 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandProcessor.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/service/command/CommandProcessor.java
@@ -19,7 +19,7 @@
package org.apache.felix.service.command;
import java.io.InputStream;
-import java.io.PrintStream;
+import java.io.OutputStream;
/**
* A command shell can create and maintain a number of command sessions.
@@ -59,5 +59,7 @@
* @param err The stream used for System.err
* @return A new session.
*/
- CommandSession createSession(InputStream in, PrintStream out, PrintStream err);
+ CommandSession createSession(InputStream in, OutputStream out, OutputStream err);
+
+ CommandSession createSession(CommandSession parent);
}