Skip to content

Commit 6c70e7a

Browse files
authored
3.x: Fix sitegen config substitutions (#1065) (#1115)
1 parent 37f0ea9 commit 6c70e7a

File tree

11 files changed

+414
-105
lines changed

11 files changed

+414
-105
lines changed

maven-plugins/sitegen-maven-plugin/src/main/java/io/helidon/build/maven/sitegen/Config.java

Lines changed: 149 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,10 +21,15 @@
2121
import java.io.UncheckedIOException;
2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
24+
import java.util.ArrayDeque;
25+
import java.util.ArrayList;
26+
import java.util.Deque;
2427
import java.util.List;
28+
import java.util.ListIterator;
2529
import java.util.Map;
2630
import java.util.Optional;
27-
import java.util.Set;
31+
import java.util.TreeMap;
32+
import java.util.function.BiConsumer;
2833
import java.util.function.Function;
2934
import java.util.stream.Collectors;
3035

@@ -123,7 +128,7 @@ public Optional<Config> asOptional() {
123128
}
124129

125130
/**
126-
* Get the value as string.
131+
* Get the value as a string.
127132
*
128133
* @return optional
129134
*/
@@ -132,7 +137,7 @@ public Optional<String> asString() {
132137
}
133138

134139
/**
135-
* Get the value as boolean.
140+
* Get the value as a boolean.
136141
*
137142
* @return optional
138143
*/
@@ -166,7 +171,7 @@ public <T> Optional<T> as(Function<Object, T> mapper) {
166171
* @return optional
167172
*/
168173
public <T> Optional<T> as(Class<T> type) {
169-
return as(o -> cast(o, type));
174+
return as(o -> convert(o, type));
170175
}
171176

172177
/**
@@ -175,87 +180,99 @@ public <T> Optional<T> as(Class<T> type) {
175180
* @return optional
176181
*/
177182
public Optional<List<Config>> asNodeList() {
178-
return asList(e -> new Config(e, this));
183+
if (value == null) {
184+
return Optional.empty();
185+
}
186+
if (value instanceof List) {
187+
return Optional.of(((List<?>) value).stream()
188+
.map(e -> new Config(e, this))
189+
.collect(Collectors.toList()));
190+
}
191+
throw new MappingException(value.getClass(), List.class);
179192
}
180193

181194
/**
182-
* Map the value to a list of object.
195+
* Get the value to a list.
183196
*
184197
* @return optional
185198
*/
186-
public Optional<List<Object>> asList() {
199+
public Optional<List<String>> asList() {
187200
return asList(Function.identity());
188201
}
189202

190203
/**
191-
* Map the value to a list of a given type.
204+
* Get the value as a list.
192205
*
193-
* @param type requested type
194-
* @param <T> requested type
206+
* @param type value type
207+
* @param <T> value type
195208
* @return optional
196209
*/
197210
public <T> Optional<List<T>> asList(Class<T> type) {
198-
return asList(e -> cast(e, type));
211+
return asList(e -> convert(e, type));
199212
}
200213

201214
/**
202-
* Map the value to a list using a mapping function.
215+
* Get the value as a list.
203216
*
204-
* @param mapper mapping function
205-
* @param <T> requested type
217+
* @param mapper value mapper
218+
* @param <T> value type
206219
* @return optional
207220
*/
208-
public <T> Optional<List<T>> asList(Function<Object, T> mapper) {
221+
public <T> Optional<List<T>> asList(Function<String, T> mapper) {
209222
if (value == null) {
210223
return Optional.empty();
211224
}
212225
if (value instanceof List) {
213-
return Optional.of(((List<?>) value).stream()
214-
.map(mapper)
215-
.collect(Collectors.toList()));
226+
List<T> values = new ArrayList<>();
227+
traverse((prefix, entry) -> {
228+
T value = mapper.apply(entry.getValue());
229+
values.add(value);
230+
});
231+
return Optional.of(values);
216232
}
217233
throw new MappingException(value.getClass(), List.class);
218234
}
219235

220236
/**
221-
* Map the value to a map using a mapping function.
237+
* Get the value as a map.
222238
*
223-
* @param mapper mapping function
224-
* @param <T> requested type
225239
* @return optional
226240
*/
227-
public <T> Optional<Map<String, T>> asMap(Function<Object, T> mapper) {
228-
if (value == null) {
229-
return Optional.empty();
230-
}
231-
if (value instanceof Map) {
232-
return Optional.of(((Map<?, ?>) value)
233-
.entrySet()
234-
.stream()
235-
.map(e -> Map.entry(e.getKey().toString(), mapper.apply(e.getValue())))
236-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
237-
}
238-
throw new MappingException(value.getClass(), Map.class);
241+
public Optional<Map<String, String>> asMap() {
242+
return asMap(Function.identity());
239243
}
240244

241245
/**
242-
* Map the value to a map of object.
246+
* Get the value as a map.
243247
*
248+
* @param type value type
249+
* @param <T> value type
244250
* @return optional
245251
*/
246-
public Optional<Map<String, Object>> asMap() {
247-
return asMap(Function.identity());
252+
public <T> Optional<Map<String, T>> asMap(Class<T> type) {
253+
return asMap(e -> convert(e, type));
248254
}
249255

250256
/**
251-
* Map the value to a map of a given type.
257+
* Get the value as a map.
252258
*
253-
* @param type requested type
254-
* @param <T> requested type
259+
* @param mapper value mapper
260+
* @param <T> value type
255261
* @return optional
256262
*/
257-
public <T> Optional<Map<String, T>> asMap(Class<T> type) {
258-
return asMap(e -> cast(e, type));
263+
public <T> Optional<Map<String, T>> asMap(Function<String, T> mapper) {
264+
if (value == null) {
265+
return Optional.empty();
266+
}
267+
if (value instanceof Map) {
268+
Map<String, T> values = new TreeMap<>();
269+
traverse((prefix, entry) -> {
270+
String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
271+
values.put(key, mapper.apply(entry.getValue()));
272+
});
273+
return Optional.of(values);
274+
}
275+
throw new MappingException(value.getClass(), Map.class);
259276
}
260277

261278
/**
@@ -308,31 +325,98 @@ public static Config create(Reader reader, Map<String, String> properties) {
308325
return create((Object) yaml.loadAs(reader, Object.class), properties);
309326
}
310327

311-
private static final Set<Class<?>> PRIMITIVE_BOXED =
312-
Set.of(
313-
Boolean.class,
314-
Character.class,
315-
Byte.class,
316-
Short.class,
317-
Integer.class,
318-
Long.class,
319-
Float.class,
320-
Double.class
321-
);
322-
323-
324-
private <T> T cast(Object obj, Class<T> type) {
325-
if (obj != null) {
326-
if (type.equals(String.class)
327-
|| obj.getClass().isPrimitive()
328-
|| PRIMITIVE_BOXED.contains(obj.getClass())) {
329-
return type.cast(substitutions.resolve(String.valueOf(obj)));
328+
private void traverse(BiConsumer<String, Map.Entry<String, String>> visitLeaf) {
329+
Deque<String> path = new ArrayDeque<>();
330+
traverse((k, v) -> {
331+
if (k != null) {
332+
path.addLast(substitutions.resolve(k));
330333
}
331-
if (type.isInstance(obj)) {
332-
return type.cast(obj);
334+
}, (k, v) -> {
335+
if (!path.isEmpty()) {
336+
path.removeLast();
337+
}
338+
}, (k, v) -> {
339+
String prefix = String.join(".", path);
340+
visitLeaf.accept(prefix, Map.entry(substitutions.resolve(k), substitutions.resolve(v)));
341+
});
342+
}
343+
344+
private void traverse(BiConsumer<String, Object> visitNode,
345+
BiConsumer<String, Object> postVisitNode,
346+
BiConsumer<String, String> visitLeaf) {
347+
348+
Deque<Object> parents = new ArrayDeque<>();
349+
Deque<String> keys = new ArrayDeque<>();
350+
Deque<Object> stack = new ArrayDeque<>();
351+
stack.push(value);
352+
while (!stack.isEmpty()) {
353+
Object parent = parents.peek();
354+
String key = keys.peek();
355+
Object node = stack.peek();
356+
if (parent == node) {
357+
postVisitNode.accept(key, node);
358+
stack.pop();
359+
if (!stack.isEmpty()) {
360+
parents.pop();
361+
keys.pop();
362+
}
363+
} else if (node instanceof Map) {
364+
List<? extends Map.Entry<?, ?>> entries = List.copyOf(((Map<?, ?>) node).entrySet());
365+
ListIterator<? extends Map.Entry<?, ?>> it = entries.listIterator(entries.size());
366+
while (it.hasPrevious()) {
367+
Map.Entry<?, ?> previous = it.previous();
368+
stack.push(previous.getValue());
369+
keys.push(previous.getKey().toString());
370+
}
371+
parents.push(node);
372+
visitNode.accept(key, node);
373+
} else if (node instanceof List) {
374+
List<?> list = (List<?>) node;
375+
ListIterator<?> it = list.listIterator(list.size());
376+
while (it.hasPrevious()) {
377+
keys.push(String.valueOf(it.previousIndex()));
378+
stack.push(it.previous());
379+
}
380+
parents.push(node);
381+
visitNode.accept(key, node);
382+
} else {
383+
visitLeaf.accept(key, String.valueOf(node));
384+
stack.pop();
385+
keys.pop();
333386
}
334-
throw new MappingException(obj.getClass(), type);
335387
}
336-
return null;
388+
}
389+
390+
@SuppressWarnings("unchecked")
391+
private <T> T convert(Object obj, Class<T> type) {
392+
String value = substitutions.resolve(String.valueOf(obj));
393+
if (type.equals(String.class)) {
394+
return (T) value;
395+
}
396+
if (type.equals(Boolean.class) || type.equals(boolean.class)) {
397+
return (T) Boolean.valueOf(value);
398+
}
399+
if (type.equals(Character.class) || type.equals(char.class)) {
400+
return (T) Character.valueOf(value.charAt(0));
401+
}
402+
if (type.equals(Byte.class) || type.equals(byte.class)) {
403+
return (T) Byte.valueOf(value);
404+
}
405+
if (type.equals(Short.class) || type.equals(short.class)) {
406+
return (T) Short.valueOf(value);
407+
}
408+
if (type.equals(Integer.class) || type.equals(int.class)) {
409+
return (T) Integer.valueOf(value);
410+
}
411+
if (type.equals(Long.class) || type.equals(long.class)) {
412+
return (T) Long.valueOf(value);
413+
}
414+
if (type.equals(Float.class) || type.equals(float.class)) {
415+
return (T) Float.valueOf(value);
416+
}
417+
if (type.equals(Double.class) || type.equals(double.class)) {
418+
return (T) Double.valueOf(value);
419+
}
420+
throw new MappingException(obj.getClass(), type);
337421
}
338422
}

maven-plugins/sitegen-maven-plugin/src/main/java/io/helidon/build/maven/sitegen/VuetifyBackend.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2018, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -382,15 +382,13 @@ public Builder releases(String... releases) {
382382
* @return this builder
383383
*/
384384
public Builder config(Config config) {
385-
theme.putAll(config.get("theme")
386-
.asMap(String.class)
387-
.orElseGet(Map::of));
385+
theme.putAll(config.get("theme").asMap().orElseGet(Map::of));
386+
388387
home = config.get("homePage")
389-
.asString()
390-
.orElse(null);
391-
releases.addAll(config.get("releases")
392-
.asList(String.class)
393-
.orElseGet(List::of));
388+
.asString()
389+
.orElse(null);
390+
releases.addAll(config.get("releases").asList().orElseGet(List::of));
391+
394392
nav = config.get("navigation")
395393
.asOptional()
396394
.map(Nav::create)

maven-plugins/sitegen-maven-plugin/src/main/java/io/helidon/build/maven/sitegen/asciidoctor/AsciidocEngine.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2018, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -295,17 +295,9 @@ public Builder imagesDir(String imagesDir) {
295295
* @return this builder
296296
*/
297297
public Builder config(Config config) {
298-
libraries.addAll(config.get("libraries")
299-
.asList(String.class)
300-
.orElseGet(List::of));
301-
302-
attributes.putAll(config.get("attributes")
303-
.asMap()
304-
.orElseGet(Map::of));
305-
306-
imagesDir = config.get("images-dir")
307-
.asString()
308-
.orElse(null);
298+
libraries.addAll(config.get("libraries").asList().orElseGet(List::of));
299+
attributes.putAll(config.get("attributes").asMap().orElseGet(Map::of));
300+
imagesDir = config.get("images-dir").asString().orElse(null);
309301
return this;
310302
}
311303

maven-plugins/sitegen-maven-plugin/src/main/java/io/helidon/build/maven/sitegen/freemarker/FreemarkerEngine.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2018, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -215,12 +215,8 @@ public Builder model(Map<String, String> model) {
215215
* @return this builder
216216
*/
217217
public Builder config(Config config) {
218-
directives.putAll(config.get("directives")
219-
.asMap(String.class)
220-
.orElseGet(Map::of));
221-
model.putAll(config.get("model")
222-
.asMap(String.class)
223-
.orElseGet(Map::of));
218+
directives.putAll(config.get("directives").asMap().orElseGet(Map::of));
219+
model.putAll(config.get("model").asMap().orElseGet(Map::of));
224220
return this;
225221
}
226222

0 commit comments

Comments
 (0)