Improve parameter expansion
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736016 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java
new file mode 100644
index 0000000..f61717c
--- /dev/null
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/ArgList.java
@@ -0,0 +1,69 @@
+/*
+ * 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.runtime;
+
+import java.util.AbstractList;
+import java.util.List;
+
+/**
+ * List that overrides toString() for implicit $args expansion.
+ * Also checks for index out of bounds, so that $1 evaluates to null
+ * rather than throwing IndexOutOfBoundsException.
+ * e.g. x = { a$args }; x 1 2 => a1 2 and not a[1, 2]
+ */
+public class ArgList extends AbstractList<Object>
+{
+ private List<Object> list;
+
+ public ArgList(List<Object> args)
+ {
+ this.list = args;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+ for (Object o : list)
+ {
+ if (buf.length() > 0)
+ buf.append(' ');
+ buf.append(o);
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public Object get(int index)
+ {
+ return index < list.size() ? list.get(index) : null;
+ }
+
+ @Override
+ public Object remove(int index)
+ {
+ return list.remove(index);
+ }
+
+ @Override
+ public int size()
+ {
+ return list.size();
+ }
+}
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 2b121cc..b4f0e5a 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
@@ -23,7 +23,6 @@
import java.io.PipedOutputStream;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
-import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -392,9 +391,9 @@
// return v;
// }
- if (parms == v && parms != null)
+ if (v instanceof ArgList)
{
- values.addAll(parms); // explode $args array
+ values.addAll((ArgList) v); // explode $args array
}
else
{
@@ -672,51 +671,4 @@
"([^\\\\{}(\\[])[\\s\n]*\n", "$1;").replaceAll("[ \\\\\t\n]+", " ");
}
- /**
- * List that overrides toString() for implicit $args expansion.
- * Also checks for index out of bounds, so that $1 evaluates to null
- * rather than throwing IndexOutOfBoundsException.
- * e.g. x = { a$args }; x 1 2 => a1 2 and not a[1, 2]
- */
- class ArgList extends AbstractList<Object>
- {
- private List<Object> list;
-
- public ArgList(List<Object> args)
- {
- this.list = args;
- }
-
- @Override
- public String toString()
- {
- StringBuilder buf = new StringBuilder();
- for (Object o : list)
- {
- if (buf.length() > 0)
- buf.append(' ');
- buf.append(o);
- }
- return buf.toString();
- }
-
- @Override
- public Object get(int index)
- {
- return index < list.size() ? list.get(index) : null;
- }
-
- @Override
- public Object remove(int index)
- {
- return list.remove(index);
- }
-
- @Override
- public int size()
- {
- return list.size();
- }
- }
-
}
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 3243429..e12a2d0 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
@@ -134,7 +134,7 @@
return Collections.unmodifiableSet(commands.keySet());
}
- Function getCommand(String name, final Object path)
+ protected Function getCommand(String name, final Object path)
{
int colon = name.indexOf(':');
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
index 08a233f..b248562 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Expander.java
@@ -18,6 +18,15 @@
*/
package org.apache.felix.gogo.runtime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
+
@SuppressWarnings("fallthrough")
public class Expander extends BaseTokenizer
{
@@ -50,6 +59,7 @@
return expand(word, evaluate, inQuote);
}
+
private Object expand() throws Exception
{
final String special = "%$\\\"'";
@@ -112,17 +122,34 @@
case '"':
skipQuote();
value = text.subSequence(start, index - 1);
+ getch();
Object expand = expand(value, evaluate, true);
- if (eot() && buf.length() == 0 && value == expand)
+ if (eot() && buf.length() == 0)
{
- // FELIX-2468 avoid returning CharSequence implementation
- return value.toString();
+ if (expand instanceof ArgList) {
+ return new ArgList((ArgList) expand).stream().map(String::valueOf).collect(Collectors.toList());
+ } else if (expand instanceof Collection) {
+ return ((Collection) expand).stream().map(String::valueOf).collect(Collectors.joining(" "));
+ } else if (expand != null) {
+ return expand.toString();
+ } else {
+ return "";
+ }
}
- if (null != expand)
- {
+ if (expand instanceof Collection) {
+ boolean first = true;
+ for (Object o : ((Collection) expand)) {
+ if (!first) {
+ buf.append(" ");
+ }
+ first = false;
+ buf.append(o);
+ }
+ }
+ else if (expand != null) {
buf.append(expand.toString());
}
- break;
+ continue; // has already read next char
case '\'':
if (!inQuote)
@@ -240,7 +267,11 @@
private Object expandVar() throws Exception
{
assert '$' == ch;
- Object val;
+
+ Object val = null;
+
+ short sLine = line;
+ short sCol = column;
if (getch() != '{')
{
@@ -273,6 +304,455 @@
}
else
{
+ getch();
+
+ boolean flagk = false;
+ boolean flagv = false;
+ boolean flagP = false;
+ boolean flagC = false;
+ boolean flagL = false;
+ boolean flagU = false;
+ boolean flagExpand = false;
+ if (ch == '(') {
+ getch();
+ while (ch != EOT && ch != ')') {
+ switch (ch) {
+ case 'P':
+ flagP = true;
+ break;
+ case '@':
+ flagExpand = true;
+ break;
+ case 'k':
+ flagk = true;
+ break;
+ case 'v':
+ flagv = true;
+ break;
+ case 'C':
+ flagC = true;
+ flagL = false;
+ flagU = false;
+ break;
+ case 'L':
+ flagC = false;
+ flagL = true;
+ flagU = false;
+ break;
+ case 'U':
+ flagC = false;
+ flagL = false;
+ flagU = true;
+ break;
+ default:
+ throw new SyntaxError(line, column, "unsupported flag: " + ch);
+ }
+ getch();
+ }
+ getch();
+ }
+
+ if (ch == '+') {
+ getch();
+ val = getAndEvaluateName();
+ }
+ else {
+ boolean computeLength = false;
+ boolean wordSplit = false;
+ if (ch == '#') {
+ computeLength = true;
+ getch();
+ }
+ if (ch == '=') {
+ wordSplit = true;
+ getch();
+ }
+
+ Object val1 = getName('}');
+
+ if (ch == '}' || ch == '[') {
+ val = val1 instanceof Token ? evaluate.get(expand((Token) val1).toString()) : val1;
+ }
+ else {
+ int start = index - 1;
+ while (ch != EOT && ch != '}' && ":-+=?#%/^|*?".indexOf(ch) >= 0) {
+ getch();
+ }
+ Token op = text.subSequence(start, index - 1);
+ if (Token.eq("-", op) || Token.eq(":-", op)) {
+ val1 = val1 instanceof Token ? evaluate.get(expand((Token) val1).toString()) : val1;
+ Object val2 = getValue();
+ val = val1 == null ? val2 : val1;
+ }
+ else if (Token.eq("+", op) || Token.eq(":+", op)) {
+ val1 = val1 instanceof Token ? evaluate.get(expand((Token) val1).toString()) : val1;
+ Object val2 = getValue();
+ val = val1 != null ? val2 : null;
+ }
+ else if (Token.eq("=", op) || Token.eq(":=", op) || Token.eq("::=", op)) {
+ if (!(val1 instanceof Token)) {
+ throw new SyntaxError(line, column, "not an identifier");
+ }
+ String name = expand((Token) val1).toString();
+ val1 = evaluate.get(name);
+ val = getValue();
+ if (Token.eq("::=", op) || val1 == null) {
+ evaluate.put(name, val);
+ }
+ }
+ else if (Token.eq("?", op) || Token.eq(":?", op)) {
+ String name;
+ if (val1 instanceof Token) {
+ name = expand((Token) val1).toString();
+ val = evaluate.get(name);
+ } else {
+ name = "";
+ val = val1;
+ }
+ if (val == null || val.toString().length() == 0) {
+ throw new IllegalArgumentException(name + ": parameter not set");
+ }
+ }
+ else if (Token.eq("#", op) || Token.eq("##", op) || Token.eq("%", op) || Token.eq("%%", op)) {
+ val1 = val1 instanceof Token ? evaluate.get(expand((Token) val1).toString()) : val1;
+ Object val2 = getValue();
+ if (val2 != null) {
+ String p = toRegexPattern(val2.toString(), op.length() == 1);
+ String m = op.charAt(0) == '#' ? "^" + p : p + "$";
+ if (val1 instanceof Map) {
+ val1 = toList((Map) val1, flagk, flagv);
+ }
+ if (val1 instanceof Collection) {
+ List<String> l = new ArrayList<>();
+ for (Object o : ((Collection) val1)) {
+ l.add(o.toString().replaceFirst(m, ""));
+ }
+ val = l;
+ } else if (val1 != null) {
+ val = val1.toString().replaceFirst(m, "");
+ }
+ } else {
+ val = val1;
+ }
+ }
+ }
+ if (computeLength) {
+ if (val instanceof Collection) {
+ val = ((Collection) val).size();
+ }
+ else if (val instanceof Map) {
+ val = ((Map) val).size();
+ }
+ else if (val != null) {
+ val = val.toString().length();
+ }
+ else {
+ val = 0;
+ }
+ }
+ if (wordSplit) {
+ if (val instanceof Map) {
+ List<Object> c = toList((Map) val, flagk, flagv);
+ val = new ArgList(c);
+ }
+ else if (val instanceof Collection) {
+ if (!(val instanceof ArgList)) {
+ List<Object> l = val instanceof List ? (List) val : new ArrayList<>((Collection<?>) val);
+ val = new ArgList(l);
+ }
+ }
+ else if (val != null) {
+ val = new ArgList(Arrays.asList(val.toString().split("\\s")));
+ }
+ }
+ }
+
+ while (ch == '[') {
+// Token leftParam;
+ Object left;
+// Token rightParam;
+ Object right;
+ getch();
+// if (ch == '(') {
+// int start = index;
+// findClosing();
+// leftParam = text.subSequence(start, index - 1);
+// getch();
+// } else {
+// leftParam = null;
+// }
+ if (ch == '@') {
+ left = text.subSequence(index - 1, index);
+ getch();
+ } else {
+ left = getName(']');
+ }
+ if (ch == ',') {
+ getch();
+// if (ch == '(') {
+// int start = index;
+// findClosing();
+// rightParam = text.subSequence(start, index - 1);
+// getch();
+// } else {
+// rightParam = null;
+// }
+ right = getName(']');
+ } else {
+// rightParam = null;
+ right = null;
+ }
+ if (ch != ']') {
+ throw new SyntaxError(line, column, "invalid subscript");
+ }
+ getch();
+ if (right == null) {
+ left = left instanceof Token ? expand((Token) left) : left;
+ if (val instanceof Map) {
+ if (left.toString().equals("@")) {
+ val = new ArgList(toList((Map) val, flagk, flagv));
+ }
+ else {
+ val = ((Map) val).get(left.toString());
+ }
+ }
+ else if (val instanceof List) {
+ if (left.toString().equals("@")) {
+ val = new ArgList((List) val);
+ }
+ else {
+ val = ((List) val).get(Integer.parseInt(left.toString()));
+ }
+ }
+ else {
+ if (left.toString().equals("@")) {
+ val = val.toString();
+ } else {
+ val = val.toString().charAt(Integer.parseInt(left.toString()));
+ }
+ }
+ }
+ else {
+ left = left instanceof Token ? expand((Token) left) : left;
+ right = right instanceof Token ? expand((Token) right) : right;
+ if (val instanceof Map) {
+ val = null;
+ }
+ else if (val instanceof List) {
+ val = ((List) val).subList(Integer.parseInt(left.toString()), Integer.parseInt(right.toString()));
+ }
+ else {
+ val = val.toString().substring(Integer.parseInt(left.toString()), Integer.parseInt(right.toString()));
+ }
+ }
+ }
+
+ if (ch != '}') {
+ throw new SyntaxError(sLine, sCol, "bad substitution");
+ }
+
+ if (flagP) {
+ val = val != null ? evaluate.get(val.toString()) : null;
+ }
+ if (flagC || flagL || flagU) {
+ Function<String, String> cnv;
+ if (flagC)
+ cnv = s -> s.isEmpty() ? s : s.substring(0, 1).toUpperCase() + s.substring(1);
+ else if (flagL)
+ cnv = String::toLowerCase;
+ else
+ cnv = String::toUpperCase;
+ if (val instanceof Map) {
+ val = toList((Map) val, flagk, flagv);
+ }
+ if (val instanceof Collection) {
+ List<String> list = new ArrayList<>();
+ for (Object o : ((Collection) val)) {
+ list.add(o != null ? cnv.apply(o.toString()) : null);
+ }
+ val = list;
+ } else if (val != null) {
+ val = cnv.apply(val.toString());
+ }
+ }
+
+ if (inQuote) {
+ if (val instanceof Map) {
+ val = toList((Map) val, flagk, flagv);
+ }
+ if (val instanceof Collection) {
+ List<Object> l = val instanceof List ? (List) val : new ArrayList<>((Collection) val);
+ if (flagExpand) {
+ val = new ArgList(l);
+ } else {
+ val = l;
+ }
+ }
+ }
+ else {
+ if (flagExpand && val instanceof List) {
+ val = new ArgList((List) val);
+ }
+ }
+
+ getch();
+
+ /*
+ Token pre;
+ if ("#^~=+".indexOf(ch) >= 0) {
+ pre = text.subSequence(index, index + 1);
+ getch();
+ } else {
+ pre = null;
+ }
+
+ Token name1;
+ Object val1;
+ if (ch == '$') {
+ name1 = null;
+ val1 = expandVar();
+ } else {
+ int start = index - 1;
+ while (ch != EOT && ch != '}' && (isName(ch) || ch == '\\')) {
+ getch();
+ if (ch == '{' || ch == '(' || ch == '[') {
+ findClosing();
+ }
+ }
+ if (ch == EOT) {
+ throw new EOFError(sLine, sCol, "unexpected EOT looking for matching '}'", "compound", Character.toString('}'));
+ }
+ name1 = text.subSequence(start, index - 1);
+ val1 = null;
+ }
+
+ Token op;
+ if (ch != '}') {
+ int start = index - 1;
+ while (ch != EOT && ch != '}' && ":-+=?#%/^|*?".indexOf(ch) >= 0) {
+ getch();
+ }
+ op = text.subSequence(start, index - 1);
+ } else {
+ op = null;
+ }
+
+ Token name2;
+ Object val2;
+ if (ch == '}') {
+ name2 = null;
+ val2 = null;
+ }
+ else if (ch == '$') {
+ name2 = null;
+ val2 = expandVar();
+ }
+ else {
+ int start = index - 1;
+ while (ch != EOT && ch != '}') {
+ getch();
+ if (ch == '\\') {
+ escape();
+ }
+ else if (ch == '{' || ch == '(' || ch == '[') {
+ findClosing();
+ }
+ }
+ if (ch == EOT) {
+ throw new EOFError(sLine, sCol, "unexpected EOT looking for matching '}'", "compound", Character.toString('}'));
+ }
+ name2 = text.subSequence(start, index - 1);
+ val2 = null;
+ }
+
+ if (ch != '}') {
+ throw new SyntaxError(sLine, sCol, "bad substitution");
+ }
+
+ if (pre == null && op == null) {
+ if (name1 != null) {
+ val1 = evaluate.get(expand(name1).toString());
+ }
+ }
+ else if (pre != null && Token.eq(pre, "+") && op == null) {
+ if (name1 != null) {
+ val1 = evaluate.get(expand(name1).toString()) != null;
+ } else {
+ throw new SyntaxError(sLine, sCol, "bad substitution");
+ }
+ }
+ else if (pre != null) {
+ throw new SyntaxError(sLine, sCol, "bad substitution");
+ }
+ else if (Token.eq("-", op) || Token.eq(":-", op)) {
+ if (name1 != null) {
+ val1 = evaluate.get(expand(name1).toString());
+ }
+ if (val1 == null) {
+ if (name2 != null) {
+ val2 = expand(expand(name2).toString(), evaluate, inQuote);
+ }
+ val1 = val2;
+ }
+ if (val1 instanceof Token) {
+ val1 = val1.toString();
+ }
+ }
+ else if (Token.eq("=", op) || Token.eq(":=", op) || Token.eq("::=", op)) {
+ assert name1 != null;
+ val1 = evaluate.get(expand(name1).toString());
+ if (val1 == null && name2 != null) {
+ val1 = expand(name2);
+ if (val1 instanceof Token) {
+ val1 = val1.toString();
+ }
+ evaluate.put(name1.toString(), val1);
+ }
+ }
+ else if (Token.eq("+", op) || Token.eq(":+", op)) {
+ assert name1 != null;
+ val1 = evaluate.get(expand(name1).toString());
+ if (val1 != null && name2 != null) {
+ val1 = expand(name2);
+ if (val1 instanceof Token) {
+ val1 = val1.toString();
+ }
+ }
+ }
+ else if (Token.eq("?", op) || Token.eq(":?", op)) {
+ assert name1 != null;
+ val1 = evaluate.get(expand(name1).toString());
+ if (val1 == null) {
+ if (name2 != null) {
+ val1 = expand(name2);
+ if (val1 instanceof Token) {
+ val1 = val1.toString();
+ }
+ }
+ if (val1 == null || val1.toString().length() == 0) {
+ val1 = "parameter not set";
+ }
+ throw new IllegalArgumentException(name1 + ": " + val1);
+ }
+ }
+ else {
+
+ }
+ val = val1;
+ if (flagP) {
+ val = val != null ? evaluate.get(val.toString()) : null;
+ }
+
+ /*
+ while (true) {
+ if (ch == EOT) {
+ throw new EOFError(sLine, sCol, "unexpected EOT looking for matching '}'", "compound", Character.toString('}'));
+ }
+ if (ch == '\\') {
+ escape();
+ }
+ if (ch == '{')
+ }
// ${NAME[[:]-+=?]WORD}
short sLine = line;
short sCol = column;
@@ -289,6 +769,7 @@
case '+':
case '=':
case '?':
+ case '#':
break;
default:
@@ -367,16 +848,230 @@
}
break;
+ case '#':
+ if (null != val)
+ {
+
+ }
+ break;
+
default:
throw new SyntaxError(sLine, sCol, "bad substitution: ${" + group + "}");
}
}
getch();
+ */
}
return val;
}
+ private List<Object> toList(Map<Object, Object> val1, boolean flagk, boolean flagv) {
+ List<Object> l = new ArrayList<>();
+ if (flagk && flagv) {
+ for (Map.Entry<Object, Object> entry : val1.entrySet()) {
+ l.add(entry.getKey());
+ l.add(entry.getValue());
+ }
+ } else if (flagk) {
+ l.addAll(val1.keySet());
+ } else {
+ l.addAll(val1.values());
+ }
+ return l;
+ }
+
+ private Object getAndEvaluateName() throws Exception {
+ Object r = getName('}');
+ if (r instanceof Token) {
+ return evaluate.get(expand((Token) r).toString());
+ } else {
+ return r;
+ }
+ }
+
+ private Object getName(char closing) throws Exception {
+ if (ch == '$') {
+ return expandVar();
+ } else {
+ int start = index - 1;
+ while (ch != EOT && ch != closing && isName(ch)) {
+ getch();
+ if (ch == '\\') {
+ escape();
+ }
+ else if (ch == '{') {
+ findClosing();
+ }
+ }
+ if (ch == EOT) {
+ throw new EOFError(line, column, "unexpected EOT looking for matching '}'", "compound", Character.toString('}'));
+ }
+ return text.subSequence(start, index - 1);
+ }
+ }
+
+ private Object getValue() throws Exception {
+ if (ch == '$') {
+ return expandVar();
+ } else {
+ int start = index - 1;
+ while (ch != EOT && ch != '}') {
+ if (ch == '\\') {
+ escape();
+ }
+ else if (ch == '{' || ch == '(' || ch == '[') {
+ findClosing();
+ }
+ else {
+ getch();
+ }
+ }
+ if (ch == EOT) {
+ throw new EOFError(line, column, "unexpected EOT looking for matching '}'", "compound", Character.toString('}'));
+ }
+ Token name = text.subSequence(start, index - 1);
+ return expand(name).toString();
+ }
+ }
+
+ private void findClosing() {
+ char start = ch;
+ while (getch() != EOT) {
+ if (ch == '(' || ch == '{' || ch == '[') {
+ findClosing();
+ } else if (start == '(' && ch == ')'
+ || start == '{' && ch == '}'
+ || start == '[' && ch == ']') {
+ return;
+ }
+ }
+ }
+
+ private static final char EOL = 0;
+
+ private static boolean isRegexMeta(char ch) {
+ return ".^$+{[]|()".indexOf(ch) != -1;
+ }
+
+ private static boolean isGlobMeta(char ch) {
+ return "\\*?[{".indexOf(ch) != -1;
+ }
+
+ private static char next(String str, int index) {
+ return index < str.length() ? str.charAt(index) : EOL;
+ }
+
+ private static String toRegexPattern(String str, boolean shortest) {
+ boolean inGroup = false;
+ StringBuilder sb = new StringBuilder();
+ int index = 0;
+ while (true) {
+ while (index < str.length()) {
+ char ch = str.charAt(index++);
+ switch (ch) {
+ case '*':
+ sb.append(shortest ? ".*?" : ".*");
+ break;
+ case ',':
+ if (inGroup) {
+ sb.append(")|(?:");
+ } else {
+ sb.append(',');
+ }
+ break;
+ case '?':
+ sb.append(".");
+ break;
+ case '[':
+ sb.append("[");
+ if (next(str, index) == '^') {
+ sb.append("\\^");
+ ++index;
+ } else {
+ if (next(str, index) == '!') {
+ sb.append('^');
+ ++index;
+ }
+ if (next(str, index) == '-') {
+ sb.append('-');
+ ++index;
+ }
+ }
+ boolean inLeft = false;
+ char left = 0;
+ while (index < str.length()) {
+ ch = str.charAt(index++);
+ if (ch == ']') {
+ break;
+ }
+ if (ch == '\\' || ch == '[' || ch == '&' && next(str, index) == '&') {
+ sb.append('\\');
+ }
+ sb.append(ch);
+ if (ch == '-') {
+ if (!inLeft) {
+ throw new PatternSyntaxException("Invalid range", str, index - 1);
+ }
+ if ((ch = next(str, index++)) == EOL || ch == ']') {
+ break;
+ }
+ if (ch < left) {
+ throw new PatternSyntaxException("Invalid range", str, index - 3);
+ }
+ sb.append(ch);
+ inLeft = false;
+ } else {
+ inLeft = true;
+ left = ch;
+ }
+ }
+ if (ch != ']') {
+ throw new PatternSyntaxException("Missing \']", str, index - 1);
+ }
+ sb.append("]");
+ break;
+ case '\\':
+ if (index == str.length()) {
+ throw new PatternSyntaxException("No character to escape", str, index - 1);
+ }
+ char ch2 = str.charAt(index++);
+ if (isGlobMeta(ch2) || isRegexMeta(ch2)) {
+ sb.append('\\');
+ }
+ sb.append(ch2);
+ break;
+ case '{':
+ if (inGroup) {
+ throw new PatternSyntaxException("Cannot nest groups", str, index - 1);
+ }
+ sb.append("(?:(?:");
+ inGroup = true;
+ break;
+ case '}':
+ if (inGroup) {
+ sb.append("))");
+ inGroup = false;
+ } else {
+ sb.append('}');
+ }
+ break;
+ default:
+ if (isRegexMeta(ch)) {
+ sb.append('\\');
+ }
+ sb.append(ch);
+ break;
+ }
+ }
+ if (inGroup) {
+ throw new PatternSyntaxException("Missing \'}", str, index - 1);
+ }
+ return sb.toString();
+ }
+ }
+
+
private boolean isName(char ch)
{
return Character.isJavaIdentifierPart(ch) && (ch != '$') || ('.' == ch);
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 a11f45f..492a198 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
@@ -288,7 +288,19 @@
Pipe previous = setCurrentPipe(this);
try {
- Object result = closure.execute(statement);
+ Object result;
+ // Very special case for empty statements with redirection
+ if (statement.tokens().isEmpty() && toclose[0]) {
+ ByteBuffer bb = ByteBuffer.allocate(1024);
+ while (((ReadableByteChannel) streams[0]).read(bb) >= 0 || bb.position() != 0) {
+ bb.flip();
+ ((WritableByteChannel) streams[1]).write(bb);
+ bb.compact();
+ }
+ result = null;
+ } else {
+ result = closure.execute(statement);
+ }
// If an error has been set
if (error != 0) {
return new Result(error);
diff --git a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
index 442505a..b619ee7 100644
--- a/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
+++ b/gogo/runtime/src/main/java/org/apache/felix/gogo/runtime/Reflective.java
@@ -24,8 +24,10 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.apache.felix.service.command.CommandSession;
@@ -392,7 +394,7 @@
return converted;
}
- String string = arg.toString();
+ String string = toString(arg);
if (type.isAssignableFrom(String.class))
{
@@ -431,6 +433,70 @@
return NO_MATCH;
}
+ private static String toString(Object arg)
+ {
+ if (arg instanceof Map)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ boolean first = true;
+ for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) arg).entrySet())
+ {
+ if (!first) {
+ sb.append(" ");
+ }
+ first = false;
+ writeValue(sb, entry.getKey());
+ sb.append("=");
+ writeValue(sb, entry.getValue());
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+ else if (arg instanceof Collection)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ boolean first = true;
+ for (Object o : ((Collection) arg))
+ {
+ if (!first) {
+ sb.append(" ");
+ }
+ first = false;
+ writeValue(sb, o);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+ else
+ {
+ return arg.toString();
+ }
+ }
+
+ private static void writeValue(StringBuilder sb, Object o) {
+ if (o == null || o instanceof Boolean || o instanceof Number)
+ {
+ sb.append(o);
+ }
+ else
+ {
+ String s = o.toString();
+ sb.append("\"");
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if (c == '\"' || c == '=')
+ {
+ sb.append("\\");
+ }
+ sb.append(c);
+ }
+ sb.append("\"");
+ }
+ }
+
private static Class<?> primitiveToObject(Class<?> type)
{
if (type == boolean.class)
diff --git a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
index f7b818e..9a7cc03 100644
--- a/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
+++ b/gogo/runtime/src/test/java/org/apache/felix/gogo/runtime/TestTokenizer.java
@@ -22,7 +22,9 @@
import java.io.File;
import java.io.PrintStream;
import java.net.URI;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
@@ -39,7 +41,7 @@
public class TestTokenizer
{
- private final Map<String, Object> vars = new HashMap<String, Object>();
+ private final Map<String, Object> vars = new HashMap<>();
private final Evaluate evaluate;
public TestTokenizer()
@@ -163,6 +165,31 @@
}
@Test
+ public void testSubscripts() throws Exception
+ {
+ Map<String, String> map = new LinkedHashMap<>();
+ map.put("a1", "baz");
+ map.put("a2", "bar");
+ map.put("b1", "foo");
+
+ vars.clear();
+ vars.put("key", "a1");
+ vars.put("map", map);
+
+ assertEquals("baz", expand("${map[a1]}"));
+ assertEquals("baz", expand("${map[$key]}"));
+ assertEquals("az", expand("${map[a1][1,3]}"));
+ assertEquals("AZ", expand("${(U)map[a1][1,3]}"));
+ assertEquals(map, expand("${map}"));
+ assertEquals("baz bar foo", expand("\"${map}\""));
+ assertEquals(Arrays.asList("baz", "bar", "foo"), expand("\"${map[@]}\""));
+ assertEquals(Arrays.asList("a1", "a2", "b1"), expand("\"${(k)map[@]}\""));
+ assertEquals(Arrays.asList("a1", "baz", "a2", "bar", "b1", "foo"), expand("\"${(kv)map[@]}\""));
+ assertEquals(Arrays.asList("a2", "bar"), expand("${${(kv)map[@]}[2,4]}"));
+ assertEquals(Arrays.asList("a2", "bar"), expand("${${(kv)=map}[2,4]}"));
+ }
+
+ @Test
public void testExpand() throws Exception
{
final URI home = new URI("/home/derek");
@@ -270,12 +297,22 @@
// expected
}
- assertEquals(user, expand("${USER\\\n:?}"));
+ try {
+ expand("${USER\\\n:?}");
+ }
+ catch (SyntaxError e)
+ {
+ // expected
+ }
assertEquals(user, expand("${US\\u0045R:?}"));
// bash doesn't supported nested expansions
// gogo only supports them in the ${} syntax
- assertEquals("Derek Baum", expand("${$USER}"));
+ assertEquals("Derek Baum", expand("${(P)$USER}"));
+ assertEquals("Derek Baum", expand("${${(P)USER}:-Derek Baum}"));
+ assertEquals("Derek Baum", expand("${${(P)USR}:-$derek}"));
+ assertEquals("derek", expand("${${USER}}"));
+ assertEquals("derek", expand("${${USER:-d}}"));
assertEquals("x", expand("${$USR:-x}"));
assertEquals("$" + user, expand("$$USER"));
}