diff --git a/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java b/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java
index 783c386..27ff251 100644
--- a/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java
+++ b/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java
@@ -1,20 +1,26 @@
package org.jsonex.cliarg;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
import org.jsonex.core.util.BeanConvertContext;
import org.jsonex.core.util.ClassUtil;
+import static org.jsonex.core.util.LangUtil.doIf;
+import static org.jsonex.core.util.LangUtil.doIfNotNull;
+import static org.jsonex.core.util.ListUtil.isIn;
import org.jsonex.jsoncoder.JSONCoder;
import org.jsonex.jsoncoder.JSONCoderOption;
import org.jsonex.treedoc.TDNode.Type;
import org.jsonex.treedoc.json.TDJSONOption;
-import lombok.Data;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import java.util.*;
-
-import static org.jsonex.core.util.LangUtil.doIf;
-import static org.jsonex.core.util.ListUtil.isIn;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Parse the input command line arguments against the {@link CLISpec}. The parsed result will be stored in the target
@@ -106,7 +112,7 @@ private void parseArg(String arg) {
}
Param param = spec.indexedParams.get(paramIndex++);
missingParams.remove(param.name);
- param.property.set(target, parseValue(param, arg));
+ doIfNotNull(parseValue(param, arg), v -> param.property.set(target, v));
}
private Object parseValue(Param param, String value) {
@@ -124,7 +130,8 @@ private Object parseValue(Param param, String value) {
? JSONCoder.decode(value, cls, opt)
: JSONCoder.decodeTo(value, param.getProperty().get(target), opt.setMergeArray(true));
} catch (Exception e) {
- log.error("Error parsing parameter:" + param.name, e);
+ errorMessages.put(param.name, value + ";" + e.toString());
+ // log.error("Error parsing parameter:" + param.name, e);
}
return null;
}
@@ -133,9 +140,9 @@ private Object parseValue(Param param, String value) {
public String getErrorsAsString() {
StringBuilder sb = new StringBuilder();
- doIf(!missingParams.isEmpty(), () -> sb.append("\nMissing required arguments:" + missingParams));
- doIf(!extraArgs.isEmpty(), () -> sb.append("\nUnexpected arguments:" + extraArgs));
- doIf(!errorMessages.isEmpty(), () -> sb.append("\nError parsing following arguments:" + errorMessages));
+ doIf(!missingParams.isEmpty(), () -> sb.append("\nMissing required arguments:").append(missingParams));
+ doIf(!extraArgs.isEmpty(), () -> sb.append("\nUnexpected arguments:").append(extraArgs));
+ doIf(!errorMessages.isEmpty(), () -> sb.append("\nError parsing following arguments:").append(errorMessages));
return sb.toString();
}
}
diff --git a/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java b/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java
index ed069f8..b4155b4 100644
--- a/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java
+++ b/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java
@@ -20,6 +20,7 @@
import static org.jsonex.core.util.ListUtil.setAt;
/**
+ *
* CLI specification based on annotated java bean of `cls`. Following annotations will be processed:
*
* Class level:
@@ -27,9 +28,10 @@
* {@link Summary}: Summary of the command (Optional)
* {@link Description}: Description of the command (Optional)
* {@link Examples}: Array of string representation of samples usages (Optional)
- *
* For field level annotations, please refer to class {@link Param}
*
+ *
+ *
* @param
*/
@Data
diff --git a/CliArg/src/main/java/org/jsonex/cliarg/Param.java b/CliArg/src/main/java/org/jsonex/cliarg/Param.java
index 54abbda..6cadbbc 100644
--- a/CliArg/src/main/java/org/jsonex/cliarg/Param.java
+++ b/CliArg/src/main/java/org/jsonex/cliarg/Param.java
@@ -11,27 +11,28 @@
import static org.jsonex.core.util.StringUtil.noNull;
/**
- * Represent an command line parameter, it can be either argument or option
- * If index is not null indicate it's argument
- * Argument default to required unless explicitly specified.
- * required argument can't follow non-required argument which index less than it
- * If index is null indicates it's an option, option default to not required, unless specified
- *
- * For option of Boolean type, it will be mapped as flag, that means the value of the option can be omitted.
*
+ *
+ * Represent a command line parameter, it can be either argument or option
+ * If index is not null indicate it is an argument.
+ * Argument default to required unless explicitly specified.
+ * Required argument can't follow non-required argument which index less than it
+ * If index is null indicates it's an option, option default to not required, unless specified
+ * For option of Boolean type, it will be mapped as flag, that means the value of the option can be omitted.
+
* For Param of complex type or array/list, the value can be specified as JSON(ex) string, the top level "{" or "[",
* can be, omitted. The quote for key and value can be omitted.
- *
- * For array parameters, it also possible to specify the values as separate options. The values will be merged
- *
+
+ * For array parameters, it is also possible to specify the values as separate options. The values will be merged
+
* Following Annotation will be processed for each parameter:
- *
* {@link Name} Name of the parameter, optional, default to field name
* {@link ShortName} The optional short name
* {@link Description} The optional description
* {@link Index} Indicate this an indexed parameter
* {@link Required} Indicate if this field is required. all the index fields are required unless explicitly indicated.
* All the non-index fields are not required unless explicitly indicated.
+ *
*/
@Data
public class Param {
diff --git a/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java b/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java
index a3ed5a7..2bee19b 100644
--- a/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java
+++ b/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java
@@ -49,7 +49,7 @@ public static class Arg1 {
@Test
public void testParse() {
- CLISpec spec = new CLISpec(Arg1.class);
+ CLISpec spec = new CLISpec<>(Arg1.class);
assertMatchesSnapshot("spec", spec);
log.info("spec:\n" + spec.printUsage());
@@ -57,7 +57,7 @@ public void testParse() {
String[] args = { "abc", "10", "name:n1,x:1,y:2", "-o", "VAL2", "--optInt", "100",
"--arrayArg", "str1,str2,'It\\'s escapted'", "--arrayArg", "array as separate option"};
- CLIParser parser = spec.parse(args, 0);
+ CLIParser parser = spec.parse(args, 0);
log.info("parsedValue:\n" + parser.target);
assertMatchesSnapshot("parserTarget", parser.target);
diff --git a/treedoc/src/main/java/org/jsonex/treedoc/TDNode.java b/treedoc/src/main/java/org/jsonex/treedoc/TDNode.java
index 0a7a9ce..02dbf37 100644
--- a/treedoc/src/main/java/org/jsonex/treedoc/TDNode.java
+++ b/treedoc/src/main/java/org/jsonex/treedoc/TDNode.java
@@ -15,25 +15,31 @@
import lombok.experimental.Accessors;
import org.jsonex.core.charsource.Bookmark;
import org.jsonex.core.type.Lazy;
+import static org.jsonex.core.util.LangUtil.orElse;
+import static org.jsonex.core.util.LangUtil.safe;
import org.jsonex.core.util.ListUtil;
+import static org.jsonex.core.util.ListUtil.last;
+import static org.jsonex.core.util.ListUtil.listOf;
+import static org.jsonex.core.util.ListUtil.map;
import org.jsonex.core.util.StringUtil;
import org.jsonex.treedoc.TDPath.Part;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
-import static org.jsonex.core.util.LangUtil.orElse;
-import static org.jsonex.core.util.LangUtil.safe;
-import static org.jsonex.core.util.ListUtil.*;
-
/** A Node in TreeDoc */
@RequiredArgsConstructor
// @Getter @Setter
@Accessors(chain = true)
public class TDNode {
+ private final static int SIZE_TO_INIT_NAME_INDEX = 64;
public final static String ID_KEY = "$id";
public final static String REF_KEY = "$ref";
@@ -58,6 +64,7 @@ public enum Type { MAP, ARRAY, SIMPLE }
transient private boolean deduped;
transient private final Lazy hash = new Lazy<>();
transient private final Lazy str = new Lazy<>();
+ transient private Map nameIndex; // Will only initialize when size is big enough
public TDNode(TDNode parent, String key) { this.doc = parent.doc; this.parent = parent; this.key = key; }
public TDNode(TreeDoc doc, String key) { this.doc = doc; this.key = key; }
@@ -101,9 +108,20 @@ public TDNode addChild(TDNode node) {
if (node.key == null) // Assume it's array element
node.key = "" + getChildrenSize();
children.add(node);
+ if (children.size() > SIZE_TO_INIT_NAME_INDEX && nameIndex == null)
+ initNameIndex();
return touch();
}
+ private void initNameIndex() {
+ nameIndex = new HashMap<>();
+ for (int i = 0; i< children.size(); i++) {
+ TDNode child = children.get(i);
+ if (child.key != null)
+ nameIndex.put(child.key, i);
+ }
+ }
+
public void swapWith(TDNode to) {
if (this.parent == null || to.parent == null)
throw new IllegalArgumentException("Can't swap root node");
@@ -129,8 +147,12 @@ public TDNode getChild(String name) {
return idx < 0 ? null : children.get(idx);
}
- int indexOf(TDNode node) { return ListUtil.indexOf(children, n -> n == node); }
- int indexOf(String name) { return ListUtil.indexOf(children, n -> n.getKey().equals(name)); }
+ int indexOf(TDNode node) {
+ return nameIndex != null ? indexOf(node.key) : ListUtil.indexOf(children, n -> n == node);
+ }
+ int indexOf(String name) {
+ return nameIndex != null ? orElse(nameIndex.get(name), -1) : ListUtil.indexOf(children, n -> n.getKey().equals(name));
+ }
int index() { return parent == null ? 0 : parent.indexOf(this); }
public Object getChildValue(String name) {
@@ -206,8 +228,8 @@ public List getPath() {
public boolean isLeaf() { return getChildrenSize() == 0; }
private TDNode touch() {
- hash.clear();;
- str.clear();;
+ hash.clear();
+ str.clear();
if (parent != null)
parent.touch();
return this;
@@ -285,15 +307,19 @@ public List getChildrenKeys() {
if (this.type == Type.SIMPLE || children == null)
return result;
// Add the key column
+ Set keySet = new HashSet<>();
result.add(COLUMN_KEY);
+ keySet.add(COLUMN_KEY);
boolean hasValue = false;
for (TDNode c : children) {
if (c.value != null)
hasValue = true;
if (c.children != null)
for (TDNode cc : c.getChildren())
- if (!result.contains(cc.key))
+ if (!keySet.contains(cc.key)) {
result.add(cc.key);
+ keySet.add(cc.key);
+ }
}
if (hasValue)
result.add(1, COLUMN_VALUE);
diff --git a/treedoc/src/test/java/org/jsonex/treedoc/TDNodeTest.java b/treedoc/src/test/java/org/jsonex/treedoc/TDNodeTest.java
new file mode 100644
index 0000000..a29213e
--- /dev/null
+++ b/treedoc/src/test/java/org/jsonex/treedoc/TDNodeTest.java
@@ -0,0 +1,22 @@
+package org.jsonex.treedoc;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+import java.util.List;
+
+public class TDNodeTest {
+ @Test
+ public void test() {
+ TDNode node = new TDNode().setType(TDNode.Type.ARRAY);
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < 1000000; i++) {
+ node.createChild("name_" + i).setType(TDNode.Type.MAP).createChild("name_" + i + "_1").setValue("value_" + i + "_1");
+ }
+ List keys = node.getChildrenKeys();
+ long time = System.currentTimeMillis() - start;
+ System.out.println(time);
+ assertTrue(time < 2000);
+// Assert.assertEquals("", keys.toString());
+ }
+}