1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.tools;
20
21 import java.io.BufferedWriter;
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.io.StringWriter;
25 import java.io.UncheckedIOException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.TreeMap;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import java.util.spi.ToolProvider;
37
38 import org.apache.velocity.VelocityContext;
39 import org.apache.velocity.app.VelocityEngine;
40 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
41 import org.jboss.forge.roaster.Roaster;
42 import org.jboss.forge.roaster.model.JavaDocCapable;
43 import org.jboss.forge.roaster.model.JavaDocTag;
44 import org.jboss.forge.roaster.model.JavaType;
45 import org.jboss.forge.roaster.model.source.FieldSource;
46 import org.jboss.forge.roaster.model.source.JavaClassSource;
47
48 public class CollectConfiguration {
49 public static void main(String[] args) throws Exception {
50 Path start = Paths.get(args.length > 0 ? args[0] : ".");
51 Path output = Paths.get(args.length > 1 ? args[1] : "output");
52
53 TreeMap<String, ConfigurationKey> discoveredKeys = new TreeMap<>();
54 Files.walk(start)
55 .map(Path::toAbsolutePath)
56 .filter(p -> p.getFileName().toString().endsWith(".java"))
57 .filter(p -> p.toString().contains("/src/main/java/"))
58 .forEach(p -> {
59 JavaType<?> type = parse(p);
60 if (type instanceof JavaClassSource javaClassSource) {
61 javaClassSource.getFields().stream()
62 .filter(CollectConfiguration::hasConfigurationSource)
63 .forEach(f -> {
64 Map<String, String> constants = extractConstants(Paths.get(p.toString()
65 .replace("/src/main/java/", "/target/classes/")
66 .replace(".java", ".class")));
67
68 String name = f.getName();
69 String key = constants.get(name);
70 String fqName = f.getOrigin().getCanonicalName() + "." + name;
71 String configurationType = getConfigurationType(f);
72 String defValue = getTag(f, "@configurationDefaultValue");
73 if (defValue != null && defValue.startsWith("{@link #") && defValue.endsWith("}")) {
74
75 String lookupValue =
76 constants.get(defValue.substring(8, defValue.length() - 1));
77 if (lookupValue == null) {
78
79
80
81 throw new IllegalArgumentException(
82 "Could not look up " + defValue + " for configuration " + fqName);
83 }
84 defValue = lookupValue;
85 }
86 if ("java.lang.Long".equals(configurationType)
87 && (defValue.endsWith("l") || defValue.endsWith("L"))) {
88 defValue = defValue.substring(0, defValue.length() - 1);
89 }
90 discoveredKeys.put(
91 key,
92 new ConfigurationKey(
93 key,
94 defValue,
95 fqName,
96 f.getJavaDoc().getText(),
97 nvl(getSince(f), ""),
98 getConfigurationSource(f),
99 configurationType,
100 toBoolean(getTag(f, "@configurationRepoIdSuffix"))));
101 });
102 }
103 });
104
105 VelocityEngine velocityEngine = new VelocityEngine();
106 Properties properties = new Properties();
107 properties.setProperty("resource.loaders", "classpath");
108 properties.setProperty("resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
109 velocityEngine.init(properties);
110
111 VelocityContext context = new VelocityContext();
112 context.put("keys", discoveredKeys.values());
113
114 try (BufferedWriter fileWriter = Files.newBufferedWriter(output)) {
115 velocityEngine.getTemplate("page.vm").merge(context, fileWriter);
116 }
117 }
118
119 private static JavaType<?> parse(Path path) {
120 try {
121 return Roaster.parse(path.toFile());
122 } catch (IOException e) {
123 throw new UncheckedIOException(e);
124 }
125 }
126
127 private static boolean toBoolean(String value) {
128 return ("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value));
129 }
130
131
132
133
134 public static class ConfigurationKey {
135 private final String key;
136 private final String defaultValue;
137 private final String fqName;
138 private final String description;
139 private final String since;
140 private final String configurationSource;
141 private final String configurationType;
142 private final boolean supportRepoIdSuffix;
143
144 @SuppressWarnings("checkstyle:parameternumber")
145 public ConfigurationKey(
146 String key,
147 String defaultValue,
148 String fqName,
149 String description,
150 String since,
151 String configurationSource,
152 String configurationType,
153 boolean supportRepoIdSuffix) {
154 this.key = key;
155 this.defaultValue = defaultValue;
156 this.fqName = fqName;
157 this.description = description;
158 this.since = since;
159 this.configurationSource = configurationSource;
160 this.configurationType = configurationType;
161 this.supportRepoIdSuffix = supportRepoIdSuffix;
162 }
163
164 public String getKey() {
165 return key;
166 }
167
168 public String getDefaultValue() {
169 return defaultValue;
170 }
171
172 public String getFqName() {
173 return fqName;
174 }
175
176 public String getDescription() {
177 return description;
178 }
179
180 public String getSince() {
181 return since;
182 }
183
184 public String getConfigurationSource() {
185 return configurationSource;
186 }
187
188 public String getConfigurationType() {
189 return configurationType;
190 }
191
192 public boolean isSupportRepoIdSuffix() {
193 return supportRepoIdSuffix;
194 }
195 }
196
197 private static String nvl(String string, String def) {
198 return string == null ? def : string;
199 }
200
201 private static boolean hasConfigurationSource(JavaDocCapable<?> javaDocCapable) {
202 return getTag(javaDocCapable, "@configurationSource") != null;
203 }
204
205 private static String getConfigurationType(JavaDocCapable<?> javaDocCapable) {
206 String type = getTag(javaDocCapable, "@configurationType");
207 if (type != null) {
208 if (type.startsWith("{@link ") && type.endsWith("}")) {
209 type = type.substring(7, type.length() - 1);
210 }
211 }
212 return nvl(type, "n/a");
213 }
214
215 private static String getConfigurationSource(JavaDocCapable<?> javaDocCapable) {
216 String source = getTag(javaDocCapable, "@configurationSource");
217 if ("{@link RepositorySystemSession#getConfigProperties()}".equals(source)) {
218 return "Session Configuration";
219 } else if ("{@link System#getProperty(String,String)}".equals(source)) {
220 return "Java System Properties";
221 } else {
222 return source;
223 }
224 }
225
226 private static String getSince(JavaDocCapable<?> javaDocCapable) {
227 List<JavaDocTag> tags;
228 if (javaDocCapable != null) {
229 if (javaDocCapable instanceof FieldSource<?> fieldSource) {
230 tags = fieldSource.getJavaDoc().getTags("@since");
231 if (tags.isEmpty()) {
232 return getSince(fieldSource.getOrigin());
233 } else {
234 return tags.get(0).getValue();
235 }
236 } else if (javaDocCapable instanceof JavaClassSource classSource) {
237 tags = classSource.getJavaDoc().getTags("@since");
238 if (!tags.isEmpty()) {
239 return tags.get(0).getValue();
240 }
241 }
242 }
243 return null;
244 }
245
246 private static String getTag(JavaDocCapable<?> javaDocCapable, String tagName) {
247 List<JavaDocTag> tags;
248 if (javaDocCapable != null) {
249 if (javaDocCapable instanceof FieldSource<?> fieldSource) {
250 tags = fieldSource.getJavaDoc().getTags(tagName);
251 if (tags.isEmpty()) {
252 return getTag(fieldSource.getOrigin(), tagName);
253 } else {
254 return tags.get(0).getValue();
255 }
256 }
257 }
258 return null;
259 }
260
261 private static final Pattern CONSTANT_PATTERN = Pattern.compile(".*static final.* ([A-Z_]+) = (.*);");
262
263 private static final ToolProvider JAVAP = ToolProvider.findFirst("javap").orElseThrow();
264
265
266
267
268
269
270
271
272
273 private static Map<String, String> extractConstants(Path file) {
274 StringWriter out = new StringWriter();
275 JAVAP.run(new PrintWriter(out), new PrintWriter(System.err), "-constants", file.toString());
276 Map<String, String> result = new HashMap<>();
277 out.getBuffer().toString().lines().forEach(l -> {
278 Matcher matcher = CONSTANT_PATTERN.matcher(l);
279 if (matcher.matches()) {
280 result.put(matcher.group(1), matcher.group(2));
281 }
282 });
283 return result;
284 }
285 }