1import java.util.ArrayList
2import java.util.HashMap
3import java.util.List
4import java.util.Map
5import java.util.regex.Matcher
6import java.util.regex.Pattern
7
8class DslConfigParser {
9
10private static final Pattern KEY_VALUE_PAIR_PATTERN = Pattern.compile("\s*(\w+)\s*:\s*(.*?)(?:,|\n|$)")
11private static final Pattern BLOCK_START_PATTERN = Pattern.compile("\s*(\w+)\s*\{\s*$")
12private static final Pattern BLOCK_END_PATTERN = Pattern.compile("\s*\}\s*$")
13
14private static final Map<String, Class<?>> VALID_TYPES = [
15"string": String.class,
16"integer": Integer.class,
17"boolean": Boolean.class,
18"list": List.class
19]
20
21
22
23
24
25
26
27Map<String, Object> parse(String dslContent) {
28if (dslContent == null || dslContent.trim().isEmpty()) {
29return [:]
30}
31
32if (countOccurrences(dslContent, '{') != countOccurrences(dslContent, '}')) {
33throw new IllegalArgumentException("Mismatched braces in DSL content.")
34}
35
36
37
38return parseBlock(dslContent, 0)
39}
40
41
42
43
44
45
46
47
48private Map<String, Object> parseBlock(String content, int startIndex) {
49Map<String, Object> blockData = new HashMap<>()
50int currentIndex = startIndex
51
52while (currentIndex < content.length()) {
53Matcher blockStartMatcher = BLOCK_START_PATTERN.matcher(content)
54blockStartMatcher.region(currentIndex, content.length())
55if (blockStartMatcher.lookingAt()) {
56String blockName = blockStartMatcher.group(1)
57int blockContentStart = blockStartMatcher.end()
58int blockEndIndex = findMatchingBrace(content, blockContentStart)
59if (blockEndIndex == -1) {
60throw new IllegalArgumentException("Unclosed block starting at index ${currentIndex}.")
61}
62String subBlockContent = content.substring(blockContentStart, blockEndIndex)
63blockData.put(blockName, parseBlock(subBlockContent, 0))
64currentIndex = blockEndIndex + 1
65} else {
66Matcher keyValueMatcher = KEY_VALUE_PAIR_PATTERN.matcher(content)
67keyValueMatcher.region(currentIndex, content.length())
68if (keyValueMatcher.lookingAt()) {
69String key = keyValueMatcher.group(1)
70String valueStr = keyValueMatcher.group(2).trim()
71
72blockData.put(key, parseValue(valueStr))
73currentIndex = keyValueMatcher.end()
74} else if (BLOCK_END_PATTERN.matcher(content).region(currentIndex, content.length()).lookingAt()) {
75
76break
77} else if (Character.isWhitespace(content.charAt(currentIndex))) {
78
79currentIndex++
80} else {
81throw new IllegalArgumentException("Unexpected token at index ${currentIndex}: ${content.substring(currentIndex, Math.min(currentIndex + 10, content.length()))}")
82}
83}
84}
85return blockData
86}
87
88
89
90
91
92
93private Object parseValue(String valueStr) {
94if (valueStr.equalsIgnoreCase("true")) return Boolean.TRUE
95if (valueStr.equalsIgnoreCase("false")) return Boolean.FALSE
96try {
97return Integer.parseInt(valueStr)
98} catch (NumberFormatException e) {
99
100if (valueStr.startsWith("'") && valueStr.endsWith("'")) {
101return valueStr.substring(1, valueStr.length() - 1)
102}
103if (valueStr.startsWith("\"") && valueStr.endsWith("\"")) {
104return valueStr.substring(1, valueStr.length() - 1)
105}
106return valueStr
107}
108}
109
110
111
112
113
114
115
116private int findMatchingBrace(String content, int startIndex) {
117int braceLevel = 1
118for (int i = startIndex; i < content.length(); i++) {
119char c = content.charAt(i)
120if (c == '{') {
121braceLevel++
122} else if (c == '}') {
123braceLevel--
124if (braceLevel == 0) {
125return i
126}
127}
128}
129return -1
130}
131
132
133
134
135
136
137
138private int countOccurrences(String text, char charToCount) {
139int count = 0
140for (int i = 0; i < text.length(); i++) {
141if (text.charAt(i) == charToCount) {
142count++
143}
144}
145return count
146}
147
148
149
150
151
152
153
154void validate(Map<String, Object> config, Map<String, Object> schema) {
155schema.each {
156schemaName, schemaDefinition ->
157if (schemaDefinition instanceof Map) {
158if (!config.containsKey(schemaName)) {
159throw new IllegalArgumentException("Missing required block: ${schemaName}")
160}
161Object configValue = config.get(schemaName)
162if (!(configValue instanceof Map)) {
163throw new IllegalArgumentException("Expected block '${schemaName}' to be a map, but found ${configValue.class.simpleName}.")
164}
165validate(configValue as Map<String, Object>, schemaDefinition as Map<String, Object>)
166} else if (schemaDefinition instanceof String) {
167if (!config.containsKey(schemaName)) {
168throw new IllegalArgumentException("Missing required field: ${schemaName}")
169}
170Object configValue = config.get(schemaName)
171Class<?> expectedType = VALID_TYPES.get(schemaDefinition)
172if (expectedType == null) {
173throw new IllegalArgumentException("Unknown type in schema: ${schemaDefinition}")
174}
175if (!expectedType.isInstance(configValue)) {
176throw new IllegalArgumentException("Field '${schemaName}' expected type ${schemaDefinition}, but found ${configValue.class.simpleName}.")
177}
178} else {
179throw new IllegalArgumentException("Invalid schema definition for '${schemaName}'. Must be String (type) or Map (nested schema).")
180}
181}
182}
183
184
185
186
187static void main(String[] args) {
188DslConfigParser parser = new DslConfigParser()
189
190String dsl1 = """
191server {
192host: 'localhost',
193port: 8080,
194sslEnabled: false
195}
196database {
197type: 'postgresql',
198connectionString: "jdbc:postgresql:
199poolSize: 10
200}
201"""
202
203Map<String, Object> schema1 = [
204"server": [
205"host": "string",
206"port": "integer",
207"sslEnabled": "boolean"
208],
209"database": [
210"type": "string",
211"connectionString": "string",
212"poolSize": "integer"
213]
214]
215
216try {
217Map<String, Object> parsedConfig1 = parser.parse(dsl1)
218println "Parsed Config 1: ${parsedConfig1}"
219parser.validate(parsedConfig1, schema1)
220println "Validation successful for Config 1."
221} catch (IllegalArgumentException e) {
222println "Error processing Config 1: ${e.getMessage()}"
223}
224
225println "\n---\n\n"
226
227String dsl2 = """
228logging {
229level: 'INFO',
230file: "app.log"
231}
232users: [
233name: 'admin',
234id: 123
235]
236"""
237
238Map<String, Object> schema2 = [
239"logging": [
240"level": "string",
241"file": "string"
242],
243"users": "list"
244]
245
246try {
247Map<String, Object> parsedConfig2 = parser.parse(dsl2)
248println "Parsed Config 2: ${parsedConfig2}"
249parser.validate(parsedConfig2, schema2)
250println "Validation successful for Config 2."
251} catch (IllegalArgumentException e) {
252println "Error processing Config 2: ${e.getMessage()}"
253}
254
255println "\n---\n\n"
256
257String dsl3 = """
258settings {
259timeout: 30
260}
261features {
262featureA: true
263}
264"""
265
266Map<String, Object> schema3 = [
267"settings": [
268"timeout": "integer"
269],
270"features": [
271"featureA": "boolean",
272"featureB": "boolean"
273]
274]
275
276try {
277Map<String, Object> parsedConfig3 = parser.parse(dsl3)
278println "Parsed Config 3: ${parsedConfig3}"
279parser.validate(parsedConfig3, schema3)
280println "Validation successful for Config 3."
281} catch (IllegalArgumentException e) {
282println "Error processing Config 3: ${e.getMessage()}"
283}
284}
285}