Skip to content

Commit 1e57827

Browse files
committed
Add name index for TDNode for large number of chilrens; Improve CliArg
1 parent 49fd4b6 commit 1e57827

File tree

5 files changed

+72
-36
lines changed

5 files changed

+72
-36
lines changed

CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
package org.jsonex.cliarg;
22

3+
import lombok.Data;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.SneakyThrows;
6+
import lombok.extern.slf4j.Slf4j;
37
import org.jsonex.core.util.BeanConvertContext;
48
import org.jsonex.core.util.ClassUtil;
9+
import static org.jsonex.core.util.LangUtil.doIf;
10+
import static org.jsonex.core.util.LangUtil.doIfNotNull;
11+
import static org.jsonex.core.util.ListUtil.isIn;
512
import org.jsonex.jsoncoder.JSONCoder;
613
import org.jsonex.jsoncoder.JSONCoderOption;
714
import org.jsonex.treedoc.TDNode.Type;
815
import org.jsonex.treedoc.json.TDJSONOption;
9-
import lombok.Data;
10-
import lombok.RequiredArgsConstructor;
11-
import lombok.SneakyThrows;
12-
import lombok.extern.slf4j.Slf4j;
1316

14-
import java.util.*;
15-
16-
import static org.jsonex.core.util.LangUtil.doIf;
17-
import static org.jsonex.core.util.ListUtil.isIn;
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.LinkedHashMap;
20+
import java.util.LinkedHashSet;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Set;
1824

1925
/**
2026
* 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) {
106112
}
107113
Param param = spec.indexedParams.get(paramIndex++);
108114
missingParams.remove(param.name);
109-
param.property.set(target, parseValue(param, arg));
115+
doIfNotNull(parseValue(param, arg), v -> param.property.set(target, v));
110116
}
111117

112118
private Object parseValue(Param param, String value) {
@@ -124,7 +130,8 @@ private Object parseValue(Param param, String value) {
124130
? JSONCoder.decode(value, cls, opt)
125131
: JSONCoder.decodeTo(value, param.getProperty().get(target), opt.setMergeArray(true));
126132
} catch (Exception e) {
127-
log.error("Error parsing parameter:" + param.name, e);
133+
errorMessages.put(param.name, value + ";" + e.toString());
134+
// log.error("Error parsing parameter:" + param.name, e);
128135
}
129136
return null;
130137
}
@@ -133,9 +140,9 @@ private Object parseValue(Param param, String value) {
133140

134141
public String getErrorsAsString() {
135142
StringBuilder sb = new StringBuilder();
136-
doIf(!missingParams.isEmpty(), () -> sb.append("\nMissing required arguments:" + missingParams));
137-
doIf(!extraArgs.isEmpty(), () -> sb.append("\nUnexpected arguments:" + extraArgs));
138-
doIf(!errorMessages.isEmpty(), () -> sb.append("\nError parsing following arguments:" + errorMessages));
143+
doIf(!missingParams.isEmpty(), () -> sb.append("\nMissing required arguments:").append(missingParams));
144+
doIf(!extraArgs.isEmpty(), () -> sb.append("\nUnexpected arguments:").append(extraArgs));
145+
doIf(!errorMessages.isEmpty(), () -> sb.append("\nError parsing following arguments:").append(errorMessages));
139146
return sb.toString();
140147
}
141148
}

CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@
2020
import static org.jsonex.core.util.ListUtil.setAt;
2121

2222
/**
23+
* <pre>
2324
* CLI specification based on annotated java bean of `cls`. Following annotations will be processed:
2425
*
2526
* Class level:
2627
* {@link Name}: Name of the command. (Optional) Default to the class simple name
2728
* {@link Summary}: Summary of the command (Optional)
2829
* {@link Description}: Description of the command (Optional)
2930
* {@link Examples}: Array of string representation of samples usages (Optional)
30-
*
3131
* For field level annotations, please refer to class {@link Param}
3232
*
33+
* </pre>
34+
*
3335
* @param <T>
3436
*/
3537
@Data

CliArg/src/main/java/org/jsonex/cliarg/Param.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,28 @@
1111
import static org.jsonex.core.util.StringUtil.noNull;
1212

1313
/**
14-
* Represent an command line parameter, it can be either argument or option
15-
* If index is not null indicate it's argument
16-
* Argument default to required unless explicitly specified.
17-
* required argument can't follow non-required argument which index less than it
18-
* If index is null indicates it's an option, option default to not required, unless specified
19-
*
20-
* For option of Boolean type, it will be mapped as flag, that means the value of the option can be omitted.
2114
*
15+
* <pre>
16+
* Represent a command line parameter, it can be either argument or option
17+
* If index is not null indicate it is an argument.
18+
* Argument default to required unless explicitly specified.
19+
* Required argument can't follow non-required argument which index less than it
20+
* If index is null indicates it's an option, option default to not required, unless specified
21+
* For option of Boolean type, it will be mapped as flag, that means the value of the option can be omitted.
22+
2223
* For Param of complex type or array/list, the value can be specified as JSON(ex) string, the top level "{" or "[",
2324
* can be, omitted. The quote for key and value can be omitted.
24-
*
25-
* For array parameters, it also possible to specify the values as separate options. The values will be merged
26-
*
25+
26+
* For array parameters, it is also possible to specify the values as separate options. The values will be merged
27+
2728
* Following Annotation will be processed for each parameter:
28-
*
2929
* {@link Name} Name of the parameter, optional, default to field name
3030
* {@link ShortName} The optional short name
3131
* {@link Description} The optional description
3232
* {@link Index} Indicate this an indexed parameter
3333
* {@link Required} Indicate if this field is required. all the index fields are required unless explicitly indicated.
3434
* All the non-index fields are not required unless explicitly indicated.
35+
* </pre>
3536
*/
3637
@Data
3738
public class Param {

CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ public static class Arg1 {
4949

5050
@Test
5151
public void testParse() {
52-
CLISpec spec = new CLISpec(Arg1.class);
52+
CLISpec<Arg1> spec = new CLISpec<>(Arg1.class);
5353
assertMatchesSnapshot("spec", spec);
5454

5555
log.info("spec:\n" + spec.printUsage());
5656
assertMatchesSnapshot("usage", spec.printUsage());
5757

5858
String[] args = { "abc", "10", "name:n1,x:1,y:2", "-o", "VAL2", "--optInt", "100",
5959
"--arrayArg", "str1,str2,'It\\'s escapted'", "--arrayArg", "array as separate option"};
60-
CLIParser parser = spec.parse(args, 0);
60+
CLIParser<Arg1> parser = spec.parse(args, 0);
6161
log.info("parsedValue:\n" + parser.target);
6262
assertMatchesSnapshot("parserTarget", parser.target);
6363

treedoc/src/main/java/org/jsonex/treedoc/TDNode.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,31 @@
1515
import lombok.experimental.Accessors;
1616
import org.jsonex.core.charsource.Bookmark;
1717
import org.jsonex.core.type.Lazy;
18+
import static org.jsonex.core.util.LangUtil.orElse;
19+
import static org.jsonex.core.util.LangUtil.safe;
1820
import org.jsonex.core.util.ListUtil;
21+
import static org.jsonex.core.util.ListUtil.last;
22+
import static org.jsonex.core.util.ListUtil.listOf;
23+
import static org.jsonex.core.util.ListUtil.map;
1924
import org.jsonex.core.util.StringUtil;
2025
import org.jsonex.treedoc.TDPath.Part;
2126

2227
import java.util.ArrayList;
2328
import java.util.Collections;
29+
import java.util.HashMap;
30+
import java.util.HashSet;
2431
import java.util.List;
32+
import java.util.Map;
2533
import java.util.Objects;
34+
import java.util.Set;
2635
import java.util.function.Consumer;
2736

28-
import static org.jsonex.core.util.LangUtil.orElse;
29-
import static org.jsonex.core.util.LangUtil.safe;
30-
import static org.jsonex.core.util.ListUtil.*;
31-
3237
/** A Node in TreeDoc */
3338
@RequiredArgsConstructor
3439
// @Getter @Setter
3540
@Accessors(chain = true)
3641
public class TDNode {
42+
public final static int SIZE_TO_INIT_NAME_INDEX = 64;
3743
public final static String ID_KEY = "$id";
3844
public final static String REF_KEY = "$ref";
3945

@@ -58,6 +64,7 @@ public enum Type { MAP, ARRAY, SIMPLE }
5864
transient private boolean deduped;
5965
transient private final Lazy<Integer> hash = new Lazy<>();
6066
transient private final Lazy<String> str = new Lazy<>();
67+
transient private Map<String, Integer> nameIndex; // Will only initialize when size is big enough
6168

6269
public TDNode(TDNode parent, String key) { this.doc = parent.doc; this.parent = parent; this.key = key; }
6370
public TDNode(TreeDoc doc, String key) { this.doc = doc; this.key = key; }
@@ -101,9 +108,20 @@ public TDNode addChild(TDNode node) {
101108
if (node.key == null) // Assume it's array element
102109
node.key = "" + getChildrenSize();
103110
children.add(node);
111+
if (children.size() > SIZE_TO_INIT_NAME_INDEX && nameIndex == null)
112+
initNameIndex();
104113
return touch();
105114
}
106115

116+
private void initNameIndex() {
117+
nameIndex = new HashMap<>();
118+
for (int i = 0; i< children.size(); i++) {
119+
TDNode child = children.get(i);
120+
if (child.key != null)
121+
nameIndex.put(child.key, i);
122+
}
123+
}
124+
107125
public void swapWith(TDNode to) {
108126
if (this.parent == null || to.parent == null)
109127
throw new IllegalArgumentException("Can't swap root node");
@@ -129,8 +147,12 @@ public TDNode getChild(String name) {
129147
return idx < 0 ? null : children.get(idx);
130148
}
131149

132-
int indexOf(TDNode node) { return ListUtil.indexOf(children, n -> n == node); }
133-
int indexOf(String name) { return ListUtil.indexOf(children, n -> n.getKey().equals(name)); }
150+
int indexOf(TDNode node) {
151+
return nameIndex != null ? indexOf(node.key) : ListUtil.indexOf(children, n -> n == node);
152+
}
153+
int indexOf(String name) {
154+
return nameIndex != null ? orElse(nameIndex.get(name), -1) : ListUtil.indexOf(children, n -> n.getKey().equals(name));
155+
}
134156
int index() { return parent == null ? 0 : parent.indexOf(this); }
135157

136158
public Object getChildValue(String name) {
@@ -206,8 +228,8 @@ public List<String> getPath() {
206228
public boolean isLeaf() { return getChildrenSize() == 0; }
207229

208230
private TDNode touch() {
209-
hash.clear();;
210-
str.clear();;
231+
hash.clear();
232+
str.clear();
211233
if (parent != null)
212234
parent.touch();
213235
return this;
@@ -285,15 +307,19 @@ public List<String> getChildrenKeys() {
285307
if (this.type == Type.SIMPLE || children == null)
286308
return result;
287309
// Add the key column
310+
Set<String> keySet = new HashSet<>();
288311
result.add(COLUMN_KEY);
312+
keySet.add(COLUMN_KEY);
289313
boolean hasValue = false;
290314
for (TDNode c : children) {
291315
if (c.value != null)
292316
hasValue = true;
293317
if (c.children != null)
294318
for (TDNode cc : c.getChildren())
295-
if (!result.contains(cc.key))
319+
if (!keySet.contains(cc.key)) {
296320
result.add(cc.key);
321+
keySet.add(cc.key);
322+
}
297323
}
298324
if (hasValue)
299325
result.add(1, COLUMN_VALUE);

0 commit comments

Comments
 (0)