1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
package org.shedlang.compiler.config.json;
import org.shedlang.compiler.config.errors.ConfigError;
import org.shedlang.compiler.config.errors.Json5SyntaxConfigError;
import org.shedlang.compiler.config.errors.UnexpectedTypeConfigError;
import org.shedlang.compiler.sources.FileFragmentSource;
import org.zwobble.json5.parser.Json5ParseError;
import org.zwobble.json5.parser.Json5Parser;
import org.zwobble.json5.values.*;
import org.zwobble.sourcetext.SourceText;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
public class Json5ObjectWithSource {
private final Json5Object object;
private Json5ObjectWithSource(Json5Object object) {
this.object = object;
}
public static Json5ObjectWithSource parse(SourceText sourceText) {
try {
var document = Json5Parser.parse(sourceText);
if (document instanceof Json5Object object) {
return new Json5ObjectWithSource(object);
} else {
// TODO: write a test for this.
throw new UnexpectedTypeConfigError(
document.path(),
Json5Object.class,
document.getClass(),
new FileFragmentSource(document.sourceRange())
);
}
} catch (Json5ParseError error) {
throw Json5SyntaxConfigError.fromJson5ParseError(error);
}
}
public Optional<Json5ObjectWithSource> getObject(String memberName) {
return getValueOfType(Json5Object.class, memberName)
.map(object -> new Json5ObjectWithSource(object));
}
private Optional<Json5Array> getArray(String memberName) {
return getValueOfType(Json5Array.class, memberName);
}
public Optional<List<Json5ObjectWithSource>> getArrayOfObjects(String memberName) {
return getArray(memberName).map(array -> {
var objects = new ArrayList<Json5ObjectWithSource>();
var index = 0;
for (var element : array.elements()) {
var object = castJson5Value(
Json5Object.class,
element
);
objects.add(new Json5ObjectWithSource(object));
index++;
}
return objects;
});
}
public Optional<Json5String> getStringOrNone(String memberName) {
return getValueOfType(Json5String.class, memberName);
}
public Json5String getString(String memberName) {
return getStringOrNone(memberName)
.orElseThrow(() -> new ConfigError(
// TODO: escape member name
MessageFormat.format("{0} is missing {1}", object.path(), memberName),
new FileFragmentSource(object.sourceRange())
));
}
public OptionalLong getLongOrNone(String memberName) {
var numberFinite = getValueOfType(Json5NumberFinite.class, memberName);
if (numberFinite.isEmpty()) {
return OptionalLong.empty();
}
try {
return OptionalLong.of(numberFinite.get().value().longValueExact());
} catch (ArithmeticException exception) {
// TODO: more specific error?
throw new ConfigError(
String.format(
"%s must be a 64-bit integer",
numberFinite.get().path()
),
new FileFragmentSource(numberFinite.get().sourceRange())
);
}
}
public long getLong(String memberName) {
return getLongOrNone(memberName)
.orElseThrow(() -> new ConfigError(
// TODO: escape member name
MessageFormat.format("{0} is missing {1}", object.path(), memberName),
new FileFragmentSource(object.sourceRange())
));
}
private <TValue extends Json5Value> Optional<TValue> getValueOfType(
Class<TValue> valueType,
String memberName
) {
return object.getValue(memberName)
.map(value -> castJson5Value(
valueType,
value
));
}
private static <TValue extends Json5Value> TValue castJson5Value(
Class<TValue> valueType,
Json5Value value
) {
try {
return valueType.cast(value);
} catch (ClassCastException exception) {
throw new UnexpectedTypeConfigError(
value.path(),
valueType,
value.getClass(),
new FileFragmentSource(value.sourceRange())
);
}
}
}
|