Skip to content

Commit 14fbfbf

Browse files
committed
update to junit5 to accomodate new tests
1 parent efb2ea7 commit 14fbfbf

File tree

110 files changed

+2902
-1735
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+2902
-1735
lines changed

pom.xml

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,16 @@
7373
<properties>
7474
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
7575
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
76-
<maven.compiler.source>1.8</maven.compiler.source>
77-
<maven.compiler.target>1.8</maven.compiler.target>
76+
<maven.compiler.source>21</maven.compiler.source>
77+
<maven.compiler.target>21</maven.compiler.target>
7878
<maven.site.deploy.skip>true</maven.site.deploy.skip>
7979
<slf4j.version>2.0.17</slf4j.version>
80+
<jakarta.annotation-api.version>3.0.0</jakarta.annotation-api.version>
81+
<commons-text.version>1.14.0</commons-text.version>
82+
<commons-lang3.version>3.19.0</commons-lang3.version>
83+
<commons-csv.version>1.14.1</commons-csv.version>
84+
<junit-jupiter.version>5.12.2</junit-jupiter.version>
85+
<assertj-core.version>3.27.6</assertj-core.version>
8086
</properties>
8187

8288
<dependencies>
@@ -91,6 +97,26 @@
9197
<version>2.14.0</version>
9298
<optional>true</optional>
9399
</dependency>
100+
<dependency>
101+
<groupId>jakarta.annotation</groupId>
102+
<artifactId>jakarta.annotation-api</artifactId>
103+
<version>${jakarta.annotation-api.version}</version>
104+
</dependency>
105+
<dependency>
106+
<groupId>org.apache.commons</groupId>
107+
<artifactId>commons-lang3</artifactId>
108+
<version>${commons-lang3.version}</version>
109+
</dependency>
110+
<dependency>
111+
<groupId>org.apache.commons</groupId>
112+
<artifactId>commons-text</artifactId>
113+
<version>${commons-text.version}</version>
114+
</dependency>
115+
<dependency>
116+
<groupId>org.apache.commons</groupId>
117+
<artifactId>commons-csv</artifactId>
118+
<version>${commons-csv.version}</version>
119+
</dependency>
94120
<dependency>
95121
<groupId>com.google.inject</groupId>
96122
<artifactId>guice</artifactId>
@@ -110,9 +136,15 @@
110136
<scope>test</scope>
111137
</dependency>
112138
<dependency>
113-
<groupId>junit</groupId>
114-
<artifactId>junit</artifactId>
115-
<version>4.13.2</version>
139+
<groupId>org.junit.jupiter</groupId>
140+
<artifactId>junit-jupiter</artifactId>
141+
<version>${junit-jupiter.version}</version>
142+
<scope>test</scope>
143+
</dependency>
144+
<dependency>
145+
<groupId>org.assertj</groupId>
146+
<artifactId>assertj-core</artifactId>
147+
<version>${assertj-core.version}</version>
116148
<scope>test</scope>
117149
</dependency>
118150
<dependency>
@@ -293,6 +325,15 @@
293325
<groupId>org.eluder.coveralls</groupId>
294326
<artifactId>coveralls-maven-plugin</artifactId>
295327
</plugin>
328+
<plugin>
329+
<groupId>org.apache.maven.plugins</groupId>
330+
<artifactId>maven-compiler-plugin</artifactId>
331+
<configuration>
332+
<source>21</source>
333+
<target>21</target>
334+
<compilerArgs>--enable-preview</compilerArgs>
335+
</configuration>
336+
</plugin>
296337
</plugins>
297338
</build>
298339

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.github.joschi.jadconfig.documentation;
2+
3+
import java.util.Collection;
4+
import java.util.List;
5+
import java.util.ServiceLoader;
6+
7+
public class ConfigurationBeansSPI {
8+
public static List<Object> loadConfigurationBeans() {
9+
return ServiceLoader.load(DocumentedBeansService.class).stream()
10+
.map(ServiceLoader.Provider::get)
11+
.map(DocumentedBeansService::getDocumentedConfigurationBeans)
12+
.flatMap(Collection::stream)
13+
.distinct()
14+
.toList();
15+
}
16+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package com.github.joschi.jadconfig.documentation;
2+
3+
import com.github.joschi.jadconfig.Parameter;
4+
import com.github.joschi.jadconfig.ReflectionUtils;
5+
import com.github.joschi.jadconfig.documentation.printers.ConfigFileDocsPrinter;
6+
import com.github.joschi.jadconfig.documentation.printers.ConfigurationSection;
7+
import com.github.joschi.jadconfig.documentation.printers.CsvDocsPrinter;
8+
import com.github.joschi.jadconfig.documentation.printers.DocsPrinter;
9+
import jakarta.annotation.Nonnull;
10+
import org.apache.commons.lang3.ClassUtils;
11+
12+
import java.io.FileWriter;
13+
import java.io.IOException;
14+
import java.lang.reflect.Field;
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.*;
17+
import java.util.function.Supplier;
18+
import java.util.stream.Collectors;
19+
20+
public class ConfigurationDocsGenerator {
21+
/**
22+
* This class is linked from the datanode pom.xml and generates conf.example and csv documentation.
23+
*/
24+
static void main(String[] args) throws IOException {
25+
final ConfigurationDocsGenerator generator = new ConfigurationDocsGenerator();
26+
generator.generateDocumentation(parseDocumentationFormat(args), ConfigurationBeansSPI::loadConfigurationBeans);
27+
}
28+
29+
@Nonnull
30+
private static DocumentationFormat parseDocumentationFormat(String[] args) {
31+
if (args.length != 2) {
32+
throw new IllegalArgumentException("This command needs two arguments - a format and file path. For example" + "csv ${project.build.directory}/configuration-documentation.csv");
33+
}
34+
final String format = args[0].toLowerCase(Locale.ROOT).trim();
35+
final String file = args[1];
36+
return new DocumentationFormat(format, file);
37+
}
38+
39+
public void generateDocumentation(DocumentationFormat format, Supplier<List<Object>> configurationBeans) throws IOException {
40+
final List<ConfigurationSection> sections = detectConfigurationSections(configurationBeans);
41+
try (final DocsPrinter writer = createWriter(format)) {
42+
writer.write(sections);
43+
}
44+
}
45+
46+
private DocsPrinter createWriter(DocumentationFormat format) throws IOException {
47+
final FileWriter fileWriter = new FileWriter(format.outputFile(), StandardCharsets.UTF_8);
48+
// TODO: if the format list expands, introduce DI and factories for printers
49+
return switch (format.format()) {
50+
case "csv" -> new CsvDocsPrinter(fileWriter);
51+
case "conf" -> new ConfigFileDocsPrinter(fileWriter);
52+
default -> throw new IllegalArgumentException("Unsupported format " + format.format());
53+
};
54+
}
55+
56+
/**
57+
* Collects all configuration options from all available configuration beans.
58+
*/
59+
private List<ConfigurationSection> detectConfigurationSections(Supplier<List<Object>> configurationBeans) {
60+
return configurationBeans.get()
61+
.stream()
62+
.map(ConfigurationDocsGenerator::beanToConfigSections)
63+
.sorted(Comparator.comparing(ConfigurationSection::hasPriority).reversed())
64+
.toList();
65+
}
66+
67+
private static ConfigurationSection beanToConfigSections(Object configurationBean) {
68+
69+
String sectionHeading = null;
70+
String sectionDescription = null;
71+
if (configurationBean.getClass().isAnnotationPresent(DocumentationSection.class)) {
72+
final DocumentationSection documentationSection = configurationBean.getClass().getAnnotation(DocumentationSection.class);
73+
sectionHeading = documentationSection.heading();
74+
sectionDescription = documentationSection.description();
75+
}
76+
77+
final List<ConfigurationEntryWithSection> entries = Arrays.stream(configurationBean.getClass().getDeclaredFields())
78+
.filter(f -> f.isAnnotationPresent(Parameter.class))
79+
.filter(ConfigurationDocsGenerator::isPublicFacing)
80+
.map(f -> toConfigurationEntry(f, configurationBean))
81+
.toList();
82+
83+
final List<ConfigurationEntry> entriesWithoutSection = getEntriesWithoutSection(entries);
84+
final List<ConfigurationSection> sortedSections = sectionsFromEntries(entries);
85+
86+
return new ConfigurationSection(sectionHeading, sectionDescription, sortedSections, entriesWithoutSection);
87+
}
88+
89+
@Nonnull
90+
private static List<ConfigurationSection> sectionsFromEntries(List<ConfigurationEntryWithSection> entries) {
91+
final Collection<ConfigurationSection> sections = entries.stream()
92+
.filter(ConfigurationEntryWithSection::hasSection)
93+
.collect(Collectors.groupingBy(ConfigurationEntryWithSection::sectionHeading, Collectors.collectingAndThen(Collectors.toList(), list -> new ConfigurationSection(list.iterator().next().sectionHeading(), list.iterator().next().sectionDescription(), Collections.emptyList(), list.stream().map(ConfigurationEntryWithSection::entry).collect(Collectors.toList()))))).values();
94+
return sections.stream()
95+
.sorted(Comparator.comparing(ConfigurationSection::hasPriority, Comparator.reverseOrder())).toList();
96+
}
97+
98+
@Nonnull
99+
private static List<ConfigurationEntry> getEntriesWithoutSection(List<ConfigurationEntryWithSection> entries) {
100+
return entries.stream()
101+
.filter(e -> !e.hasSection())
102+
.map(ConfigurationEntryWithSection::entry)
103+
.sorted(Comparator.comparing(ConfigurationEntry::hasPriority, Comparator.reverseOrder()))
104+
.collect(Collectors.toList());
105+
}
106+
107+
/**
108+
* There are some configuration options not intended for general usage, mainly just for system packages configuration.
109+
*
110+
* @see Documentation#visible()
111+
*/
112+
private static boolean isPublicFacing(Field f) {
113+
return !f.isAnnotationPresent(Documentation.class) || f.getAnnotation(Documentation.class).visible();
114+
}
115+
116+
private static ConfigurationEntryWithSection toConfigurationEntry(Field f, Object instance) {
117+
final String documentation = Optional.ofNullable(f.getAnnotation(Documentation.class)).map(Documentation::value).orElse(null);
118+
final Parameter parameter = f.getAnnotation(Parameter.class);
119+
final String propertyName = parameter.value();
120+
final Object defaultValue = getDefaultValue(f, instance);
121+
final String type = getType(f);
122+
final boolean required = parameter.required();
123+
124+
final DocumentationSection documentationSection = f.getAnnotation(DocumentationSection.class);
125+
String sectionHeading = null;
126+
String sectionDescription = null;
127+
if (documentationSection != null) {
128+
sectionHeading = documentationSection.heading();
129+
sectionDescription = documentationSection.description();
130+
}
131+
final ConfigurationEntry entry = new ConfigurationEntry(instance.getClass(), f.getName(), type, propertyName, defaultValue, required, documentation);
132+
return new ConfigurationEntryWithSection(entry, sectionHeading, sectionDescription);
133+
}
134+
135+
private static Object getDefaultValue(Field f, Object instance) {
136+
try {
137+
return ReflectionUtils.getFieldValue(instance, f);
138+
} catch (IllegalAccessException e) {
139+
throw new RuntimeException(e);
140+
}
141+
}
142+
143+
private static String getType(Field f) {
144+
if (f.getType().isPrimitive()) { // unify primitive types and wrappers, e.g. int -> Integer
145+
return ClassUtils.primitiveToWrapper(f.getType()).getSimpleName();
146+
} else {
147+
return f.getType().getSimpleName();
148+
}
149+
}
150+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.github.joschi.jadconfig.documentation;
2+
3+
import jakarta.annotation.Nullable;
4+
5+
/**
6+
* @param configurationBean Class that defines this configuration entry
7+
* @param fieldName java field name
8+
* @param type Java type name, e.g. String or Integer.
9+
* @param configName configuration property name, as written in the config file
10+
* @param defaultValue default value declared in the java field, null if not defined
11+
* @param required if the configuration property is mandatory (needs default or entry in the config file)
12+
* @param documentation textual documentation of this configuration propery
13+
*/
14+
public record ConfigurationEntry(
15+
Class<?> configurationBean,
16+
String fieldName,
17+
String type,
18+
String configName,
19+
@Nullable Object defaultValue,
20+
boolean required,
21+
String documentation
22+
) {
23+
24+
public boolean hasPriority() {
25+
return required && defaultValue == null;
26+
}
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.joschi.jadconfig.documentation;
2+
3+
public record ConfigurationEntryWithSection(ConfigurationEntry entry, String sectionHeading, String sectionDescription) {
4+
public boolean hasSection() {
5+
return sectionHeading != null && !sectionHeading.isBlank();
6+
}
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.github.joschi.jadconfig.documentation;
2+
3+
public record DocumentationFormat(String format, String outputFile) {
4+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.joschi.jadconfig.documentation;
2+
3+
import java.util.List;
4+
5+
public interface DocumentedBeansService {
6+
List<Object> getDocumentedConfigurationBeans();
7+
}

0 commit comments

Comments
 (0)