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 javax.lang.model.SourceVersion;
22 import javax.lang.model.element.AnnotationMirror;
23 import javax.lang.model.element.AnnotationValue;
24 import javax.lang.model.element.Element;
25 import javax.lang.model.element.ElementKind;
26 import javax.lang.model.element.ExecutableElement;
27 import javax.lang.model.element.ModuleElement;
28 import javax.lang.model.element.PackageElement;
29 import javax.lang.model.element.TypeElement;
30 import javax.lang.model.element.VariableElement;
31 import javax.lang.model.type.DeclaredType;
32 import javax.lang.model.type.PrimitiveType;
33 import javax.lang.model.type.TypeMirror;
34 import javax.lang.model.util.ElementFilter;
35 import javax.lang.model.util.SimpleElementVisitor14;
36 import javax.lang.model.util.SimpleTypeVisitor14;
37 import javax.tools.Diagnostic;
38
39 import java.io.IOException;
40 import java.io.PrintWriter;
41 import java.io.Writer;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.Files;
44 import java.nio.file.InvalidPathException;
45 import java.nio.file.Path;
46 import java.nio.file.Paths;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.LinkedHashMap;
52 import java.util.List;
53 import java.util.Locale;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Optional;
57 import java.util.Properties;
58 import java.util.Set;
59
60 import com.sun.source.doctree.DeprecatedTree;
61 import com.sun.source.doctree.DocCommentTree;
62 import com.sun.source.doctree.DocTree;
63 import com.sun.source.doctree.EntityTree;
64 import com.sun.source.doctree.LinkTree;
65 import com.sun.source.doctree.LiteralTree;
66 import com.sun.source.doctree.ReferenceTree;
67 import com.sun.source.doctree.SinceTree;
68 import com.sun.source.doctree.SystemPropertyTree;
69 import com.sun.source.doctree.TextTree;
70 import com.sun.source.doctree.UnknownBlockTagTree;
71 import com.sun.source.doctree.ValueTree;
72 import com.sun.source.tree.ExpressionTree;
73 import com.sun.source.tree.IdentifierTree;
74 import com.sun.source.tree.MemberSelectTree;
75 import com.sun.source.tree.VariableTree;
76 import com.sun.source.util.DocTreePath;
77 import com.sun.source.util.DocTrees;
78 import com.sun.source.util.SimpleDocTreeVisitor;
79 import jdk.javadoc.doclet.Doclet;
80 import jdk.javadoc.doclet.DocletEnvironment;
81 import jdk.javadoc.doclet.Reporter;
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 public class ConfigurationCollectorDoclet implements Doclet {
98
99
100
101
102 private static final String MAVEN_CONFIG_ANNOTATION = "org.apache.maven.api.annotations.Config";
103
104 private static final MethodReference METHOD_REFERENCE_SESSION_CONFIGURATION =
105 new MethodReference("org.eclipse.aether.RepositorySystemSession", "getConfigProperties", List.of());
106 private static final MethodReference METHOD_REFERENCE_SYSTEM_PROPERTY =
107 new MethodReference("java.lang.System", "getProperty", List.of("java.lang.String", "java.lang.String"));
108
109 private Reporter reporter;
110
111 private Path output;
112
113 private enum Mode {
114 RESOLVER,
115 MAVEN
116 }
117
118 private record ConfigurationEntry(
119 String key,
120 String description,
121 String defaultValue,
122 String fqName,
123 String since,
124 String source,
125 String type,
126 boolean supportsRepoIdSuffix,
127
128 String deprecated) {
129
130 public ConfigurationEntry {
131 Objects.requireNonNull(key);
132 Objects.requireNonNull(description);
133 }
134 }
135
136
137
138
139
140 private Mode mode = Mode.RESOLVER;
141
142 private DocTrees docTrees;
143
144 @Override
145 public void init(Locale locale, Reporter reporter) {
146 this.reporter = reporter;
147 }
148
149 @Override
150 public String getName() {
151 return "ConfigurationCollector";
152 }
153
154 @Override
155 public Set<? extends Option> getSupportedOptions() {
156 return Set.of(
157 new SingleArgumentOption(
158 List.of("--output", "-o"),
159 "The intermediate properties file to write discovered keys to",
160 "<file>",
161 arg -> {
162 try {
163 output = Paths.get(arg);
164 } catch (InvalidPathException e) {
165 throw new IllegalArgumentException("Invalid output file path: " + arg, e);
166 }
167 }),
168 new SingleArgumentOption(
169 List.of("--mode", "-m"), "The scanning mode, either 'resolver' or 'maven'", "<mode>", arg -> {
170 try {
171 mode = Mode.valueOf(arg.toUpperCase(Locale.ROOT));
172 } catch (IllegalArgumentException e) {
173 throw new IllegalArgumentException(
174 "Invalid mode: " + arg + ". Must be one of (case-insensitive): "
175 + String.join(
176 ", ",
177 Arrays.stream(Mode.values())
178 .map(Enum::name)
179 .toArray(String[]::new)));
180 }
181 }));
182 }
183
184 @Override
185 public SourceVersion getSupportedSourceVersion() {
186 return SourceVersion.latest();
187 }
188
189 @Override
190 public boolean run(DocletEnvironment environment) {
191 try {
192 return doRun(environment);
193 } catch (RuntimeException e) {
194
195
196 reportError("Error running ConfigurationCollectorDoclet", e);
197 return false;
198 }
199 }
200
201 private boolean doRun(DocletEnvironment environment) {
202 if (output == null) {
203 reportError("Missing required --output option");
204 return false;
205 }
206 docTrees = environment.getDocTrees();
207 List<ConfigurationEntry> configurationEntries = new ArrayList<>();
208
209 Set<TypeElement> types = ElementFilter.typesIn(environment.getIncludedElements());
210 for (TypeElement type : types) {
211 for (VariableElement field : ElementFilter.fieldsIn(type.getEnclosedElements())) {
212
213
214 if (field.getConstantValue() == null) {
215 continue;
216 }
217 DocCommentTree docComment = docTrees.getDocCommentTree(field);
218 if (docComment == null) {
219
220 continue;
221 }
222 DocTreePath rootPath = new DocTreePath(docTrees.getPath(field), docComment);
223 try {
224 ConfigurationEntry entry;
225 switch (mode) {
226 case MAVEN:
227 entry = processMavenField(rootPath, field);
228 break;
229 case RESOLVER:
230 entry = processResolverField(rootPath, field);
231 break;
232 default:
233 throw new IllegalStateException("Unknown mode: " + mode);
234 }
235 if (entry != null) {
236 configurationEntries.add(entry);
237 }
238 } catch (DocTreePathAwareRuntimeException e) {
239 reportError(e.getDocTreePath(), e.getMessage());
240 } catch (IllegalArgumentException e) {
241 reportError(rootPath, e.getMessage());
242 } catch (RuntimeException e) {
243
244 reportError(rootPath, e);
245 }
246 }
247 }
248
249 try {
250 writeProperties(configurationEntries);
251 } catch (IOException e) {
252 reportError("Failed to write properties file: " + e.getMessage());
253 return false;
254 }
255 return true;
256 }
257
258
259
260
261
262
263
264 private void reportError(DocTreePath path, Throwable throwable) {
265 reportError(path, throwable.getMessage());
266 reportError(throwable);
267 }
268
269 private void reportError(Throwable throwable) {
270
271 PrintWriter pw = reporter.getDiagnosticWriter();
272 if (pw == null) {
273 pw = new PrintWriter(System.err);
274 }
275 throwable.printStackTrace(pw);
276 }
277
278
279
280
281
282
283
284 private void reportError(DocTreePath path, String message) {
285 if (path != null) {
286 reporter.print(Diagnostic.Kind.ERROR, path, message);
287 } else {
288 reportError(message);
289 }
290 }
291
292
293
294
295
296
297
298 private void reportError(String message, Throwable throwable) {
299 reporter.print(Diagnostic.Kind.ERROR, message);
300 reportError(throwable);
301 }
302
303
304
305
306
307
308 private void reportError(String message) {
309 reporter.print(Diagnostic.Kind.ERROR, message);
310 }
311
312
313
314
315
316
317
318 private ConfigurationEntry processResolverField(DocTreePath path, VariableElement field) {
319 Objects.requireNonNull(path);
320 Objects.requireNonNull(field);
321 Map<String, UnknownBlockTagTree> blockTags = collectBlockTags(path.getDocComment());
322 if (!blockTags.containsKey("configurationSource")) {
323 return null;
324 }
325 return new ConfigurationEntry(
326 String.valueOf(field.getConstantValue()),
327 getFullBodyContent(path),
328 resolveDefaultValue(path, blockTags).orElse(""),
329 getFullyQualifiedName(field),
330 getSince(path).orElse(""),
331 getConfigurationSource(path, blockTags).orElse(""),
332 getConfigurationType(path, blockTags),
333 isSupportsRepoIdSuffix(path, blockTags),
334 getDeprecated(path, field).orElse(""));
335 }
336
337 private Optional<String> getDeprecated(DocTreePath path, Element element) {
338 Objects.requireNonNull(path, "path must not be null");
339 Objects.requireNonNull(element, "field must not be null");
340
341
342 if (element.getAnnotation(Deprecated.class) == null) {
343
344 return getDeprecated(element.getEnclosingElement());
345 }
346 Optional<? extends DocTree> deprecatedTag = path.getDocComment().getBlockTags().stream()
347 .filter(t -> com.sun.source.doctree.DocTree.Kind.DEPRECATED == t.getKind())
348 .findFirst();
349 if (deprecatedTag.isPresent()) {
350 return Optional.of(renderContent(DocTreePath.getPath(path, deprecatedTag.get()), RenderMode.HTML, true));
351 }
352 return Optional.of("");
353 }
354
355 private Optional<String> getDeprecated(Element element) {
356 if (element == null) {
357 return Optional.empty();
358 }
359 DocCommentTree docCommentTree = docTrees.getDocCommentTree(element);
360 if (docCommentTree == null) {
361 if (element.getAnnotation(Deprecated.class) != null) {
362 return Optional.of("");
363 }
364
365 return getDeprecated(element.getEnclosingElement());
366 } else {
367 return getDeprecated(new DocTreePath(docTrees.getPath(element), docCommentTree), element);
368 }
369 }
370
371 private boolean isSupportsRepoIdSuffix(DocTreePath path, Map<String, UnknownBlockTagTree> blockTags) {
372 UnknownBlockTagTree repoIdTag = blockTags.get("configurationRepoIdSuffix");
373 if (repoIdTag != null) {
374 String content = renderContent(DocTreePath.getPath(path, repoIdTag), RenderMode.PLAIN, true);
375 return "yes".equalsIgnoreCase(content) || "true".equalsIgnoreCase(content);
376 }
377 return false;
378 }
379
380
381
382
383
384
385
386
387
388 private ConfigurationEntry processMavenField(DocTreePath path, VariableElement field) {
389 AnnotationMirror config = getAnnotation(field, MAVEN_CONFIG_ANNOTATION);
390 if (config == null) {
391 return null;
392 }
393
394 String source = "USER_PROPERTIES";
395 String defaultValue = "";
396 String configurationType = "java.lang.String";
397 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> attribute :
398 config.getElementValues().entrySet()) {
399 String name = attribute.getKey().getSimpleName().toString();
400 Object value = attribute.getValue().getValue();
401 switch (name) {
402 case "source":
403 source = value instanceof VariableElement variableElement
404 ? variableElement.getSimpleName().toString()
405 : String.valueOf(value);
406 break;
407 case "defaultValue":
408 defaultValue = String.valueOf(value);
409 break;
410 case "type":
411 configurationType = String.valueOf(value);
412 break;
413 default:
414 break;
415 }
416 }
417
418 source = source.toLowerCase(Locale.ROOT);
419 switch (source) {
420 case "model":
421 source = "Model properties";
422 break;
423 case "user_properties":
424 source = "User properties";
425 break;
426 case "system_properties":
427 source = "System properties";
428 break;
429 default:
430 break;
431 }
432
433 if (configurationType.startsWith("java.lang.")) {
434 configurationType = configurationType.substring("java.lang.".length());
435 } else if (configurationType.startsWith("java.util.")) {
436 configurationType = configurationType.substring("java.util.".length());
437 }
438 return new ConfigurationEntry(
439 String.valueOf(field.getConstantValue()),
440 path.getDocComment() != null ? getFullBodyContent(path) : "",
441 Objects.toString(defaultValue, ""),
442 getFullyQualifiedName(field),
443 getSince(path).orElse(""),
444 source,
445 configurationType,
446 false,
447 getDeprecated(path, field).orElse(""));
448 }
449
450 private AnnotationMirror getAnnotation(Element element, String fqName) {
451 for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
452 Element annotationElement = annotation.getAnnotationType().asElement();
453 if (annotationElement instanceof TypeElement
454 && ((TypeElement) annotationElement).getQualifiedName().contentEquals(fqName)) {
455 return annotation;
456 }
457 }
458 return null;
459 }
460
461 private void writeProperties(List<ConfigurationEntry> configurationEntries) throws IOException {
462 Properties properties = new Properties();
463 properties.setProperty("keys.count", String.valueOf(configurationEntries.size()));
464 for (int i = 0; i < configurationEntries.size(); i++) {
465 ConfigurationEntry entry = configurationEntries.get(i);
466 writeEntry(properties, entry, "keys." + i + ".");
467 }
468 if (output.getParent() != null) {
469 Files.createDirectories(output.getParent());
470 }
471 try (Writer writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) {
472 properties.store(writer, "Generated by ConfigurationCollectorDoclet - DO NOT EDIT");
473 }
474 }
475
476 private void writeEntry(Properties properties, ConfigurationEntry entry, String prefix) {
477 properties.setProperty(prefix + "key", entry.key());
478 properties.setProperty(prefix + "defaultValue", entry.defaultValue());
479 properties.setProperty(prefix + "fqName", entry.fqName());
480 properties.setProperty(prefix + "description", entry.description());
481 properties.setProperty(prefix + "since", entry.since());
482 properties.setProperty(prefix + "configurationSource", entry.source());
483 properties.setProperty(prefix + "configurationType", entry.type());
484 properties.setProperty(prefix + "supportRepoIdSuffix", toYesNo(entry.supportsRepoIdSuffix()));
485 properties.setProperty(prefix + "deprecated", entry.deprecated());
486 }
487
488
489
490 private Map<String, UnknownBlockTagTree> collectBlockTags(DocCommentTree docComment) {
491 Map<String, UnknownBlockTagTree> result = new LinkedHashMap<>();
492 if (docComment == null) {
493 return result;
494 }
495 for (DocTree tag : docComment.getBlockTags()) {
496 if (tag instanceof UnknownBlockTagTree unknownBlockTree) {
497 result.put(unknownBlockTree.getTagName(), unknownBlockTree);
498 }
499 }
500 return result;
501 }
502
503 private String getFullBodyContent(DocTreePath path) {
504 return renderContent(path, RenderMode.HTML, true, path.getDocComment().getFullBody());
505 }
506
507 private Optional<String> resolveDefaultValue(DocTreePath path, Map<String, UnknownBlockTagTree> blockTags) {
508 UnknownBlockTagTree defaultValueTag = blockTags.get("configurationDefaultValue");
509 if (defaultValueTag == null) {
510 return Optional.empty();
511 }
512 DocTreePath defaultValuePath = DocTreePath.getPath(path, defaultValueTag);
513 for (DocTree tree : defaultValueTag.getContent()) {
514 if (tree instanceof LinkTree link) {
515 String signature = link.getReference().getSignature();
516 DocTreePath linkTreePath = DocTreePath.getPath(path, tree);
517
518
519 VariableElement referenced = resolveReferencedField(linkTreePath, link);
520 String value = referenced != null ? lookupConstant(referenced) : null;
521 if (value == null) {
522
523
524 DocTreePath linkRefPath = DocTreePath.getPath(linkTreePath, link.getReference());
525 throw new DocTreePathAwareRuntimeException(
526 linkRefPath != null ? linkRefPath : linkTreePath,
527 "Could not resolve link to determine default value: " + signature);
528 }
529 return Optional.ofNullable(value);
530 }
531 }
532
533
534 return Optional.of(renderContent(defaultValuePath, RenderMode.PLAIN, true));
535 }
536
537
538
539
540
541
542 private VariableElement resolveReferencedField(DocTreePath path, LinkTree link) {
543 DocTreePath refPath = DocTreePath.getPath(path, link.getReference());
544 if (refPath == null) {
545 return null;
546 }
547 Element element = docTrees.getElement(refPath);
548 return element instanceof VariableElement variableElement ? variableElement : null;
549 }
550
551 private String lookupConstant(VariableElement field) {
552 if (field.getConstantValue() != null) {
553 Object value = field.getConstantValue();
554 if (value instanceof String) {
555 return "\"" + value + "\"";
556 } else {
557 return String.valueOf(field.getConstantValue());
558 }
559 }
560
561 if (field.getKind() == ElementKind.ENUM_CONSTANT) {
562 return field.getSimpleName().toString();
563 }
564
565
566 return resolveEnumReference(field);
567 }
568
569
570
571
572
573
574 private String resolveEnumReference(VariableElement field) {
575 if (!(docTrees.getTree(field) instanceof VariableTree variableTree)) {
576 return null;
577 }
578 ExpressionTree initializer = variableTree.getInitializer();
579 String enumConstant = null;
580 if (initializer instanceof MemberSelectTree memberSelectTree) {
581
582 enumConstant = memberSelectTree.getIdentifier().toString();
583 } else if (initializer instanceof IdentifierTree identifierTree) {
584
585 enumConstant = identifierTree.getName().toString();
586 }
587 if (enumConstant == null) {
588 return null;
589 }
590 return enumConstant;
591 }
592
593 private Optional<LinkTree> getFirstLinkInBlockTag(UnknownBlockTagTree tag) {
594 for (DocTree tree : tag.getContent()) {
595 if (tree instanceof LinkTree link) {
596 return Optional.of(link);
597 }
598 }
599 return Optional.empty();
600 }
601
602
603
604
605
606
607
608 private String getType(DocTreePath path, LinkTree link) {
609 String signature = link.getReference().getSignature();
610 if (signature.contains("#")) {
611
612 DocTreePath linkRefPath = DocTreePath.getPath(path, link.getReference());
613 throw new DocTreePathAwareRuntimeException(
614 linkRefPath != null ? linkRefPath : path,
615 "Expected a class link, but got a member reference: " + signature);
616 }
617
618
619 return resolveReferencedType(path, link.getReference())
620 .map(t -> t.getQualifiedName().toString())
621 .orElse(signature);
622 }
623
624
625
626
627
628
629 private Optional<TypeElement> resolveReferencedType(DocTreePath path, ReferenceTree reference) {
630
631 DocTreePath refPath = DocTreePath.getPath(path, reference);
632 if (refPath == null) {
633 return Optional.empty();
634 }
635 Element element = docTrees.getElement(refPath);
636 return element instanceof TypeElement typeElement ? Optional.of(typeElement) : Optional.empty();
637 }
638
639 enum RenderMode {
640
641 PLAIN,
642
643 HTML
644 }
645
646 private String renderContent(DocTreePath docTreePath, RenderMode mode, boolean trim) {
647 return renderContent(docTreePath, mode, trim, null);
648 }
649
650
651
652
653
654
655
656
657
658
659
660
661 private String renderContent(
662 DocTreePath docTreePath, RenderMode mode, boolean trim, Collection<? extends DocTree> docTreesToRender) {
663 Objects.requireNonNull(docTreePath, "docTreePath must not be null");
664 StringBuilder sb = new StringBuilder();
665 SimpleDocTreeVisitor<String, Void> visitor = new SimpleDocTreeVisitor<String, Void>() {
666 @Override
667 public String visitText(TextTree node, Void p) {
668 return escape(mode, node.getBody());
669 }
670
671 @Override
672 public String visitLink(LinkTree node, Void p) {
673 String ref = node.getReference() != null ? node.getReference().getSignature() : "";
674 String label = renderContent(DocTreePath.getPath(docTreePath, node.getReference()), mode, false);
675 String text = label == null || label.isEmpty() ? ref : label;
676 return node.getKind() == DocTree.Kind.LINK_PLAIN ? escape(mode, text) : renderAsCode(text);
677 }
678
679 @Override
680 public String visitLiteral(LiteralTree node, Void p) {
681 if (node.getKind() == DocTree.Kind.CODE) {
682 return renderAsCode(node.getBody().getBody());
683 } else {
684 return escape(mode, node.getBody().getBody());
685 }
686 }
687
688 @Override
689 public String visitSystemProperty(SystemPropertyTree node, Void p) {
690 return renderAsCode(node.getPropertyName().toString());
691 }
692
693 private String renderAsCode(String text) {
694 if (mode == RenderMode.HTML) {
695 return "<code>" + escape(mode, text) + "</code>";
696 } else {
697 return escape(mode, text);
698 }
699 }
700
701 @Override
702 public String visitValue(ValueTree node, Void p) {
703 if (node.getReference() != null) {
704 DocTreePath refPath = DocTreePath.getPath(docTreePath, node.getReference());
705 if (refPath != null) {
706 Element element = docTrees.getElement(refPath);
707 if (element instanceof VariableElement ve) {
708 String value = lookupConstant(ve);
709 if (value != null) {
710 return renderAsCode(value);
711 }
712 }
713 }
714 }
715
716 String ref = node.getReference() != null ? node.getReference().getSignature() : "";
717 return renderAsCode(ref);
718 }
719
720 @Override
721 public String visitEntity(EntityTree node, Void p) {
722 return "&" + node.getName() + ";";
723 }
724
725 @Override
726 public String visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
727 StringBuilder sb = new StringBuilder();
728 node.getContent().forEach(child -> sb.append(child.accept(this, p)));
729 return sb.toString();
730 }
731
732 @Override
733 public String visitSince(SinceTree node, Void p) {
734 return escape(mode, node.getBody().toString());
735 }
736
737 @Override
738 public String visitDeprecated(DeprecatedTree node, Void p) {
739 StringBuilder sb = new StringBuilder();
740 node.getBody().forEach(child -> sb.append(child.accept(this, p)));
741 return sb.toString();
742 }
743
744 @Override
745 protected String defaultAction(DocTree node, Void p) {
746
747
748
749 return node.toString();
750 }
751 };
752 if (docTreesToRender == null) {
753 docTreesToRender = Collections.singleton(docTreePath.getLeaf());
754 }
755 for (DocTree docTreeToRender : docTreesToRender) {
756 sb.append(docTreeToRender.accept(visitor, null));
757 }
758
759 if (trim) {
760
761
762
763 return sb.toString().trim().replaceAll("\\s+", " ");
764 } else {
765 return sb.toString();
766 }
767 }
768
769 private static String escape(RenderMode mode, String text) {
770 if (mode == RenderMode.HTML) {
771 return text.replace("&", "&").replace("<", "<").replace(">", ">");
772 } else {
773 return text;
774 }
775 }
776
777 private Optional<String> getSince(DocTreePath path) {
778 String since = getSinceTag(path);
779 if (since == null && path.getTreePath().getParentPath() != null) {
780
781 return getSince(docTrees.getElement(path.getTreePath().getParentPath()));
782 }
783 return Optional.ofNullable(since);
784 }
785
786 private Optional<String> getSince(Element element) {
787 if (element == null) {
788 return Optional.empty();
789 }
790 DocCommentTree docComment = docTrees.getDocCommentTree(element);
791 if (docComment != null) {
792 DocTreePath path = new DocTreePath(docTrees.getPath(element), docComment);
793 Optional<String> since = getSince(path);
794 if (since.isPresent()) {
795 return since;
796 }
797 }
798
799 return getSince(element.getEnclosingElement());
800 }
801
802 private String getSinceTag(DocTreePath path) {
803 if (path == null) {
804
805 return null;
806 }
807 for (DocTree tag : path.getDocComment().getBlockTags()) {
808 if (tag instanceof SinceTree) {
809 return renderContent(DocTreePath.getPath(path, tag), RenderMode.PLAIN, true);
810 }
811 }
812 return null;
813 }
814
815 private String getConfigurationType(DocTreePath path, Map<String, UnknownBlockTagTree> blockTags) {
816 UnknownBlockTagTree typeTag = blockTags.get("configurationType");
817 if (typeTag == null) {
818 throw new IllegalStateException("Missing block tag @configurationType");
819 }
820 DocTreePath configurationTypePath = DocTreePath.getPath(path, typeTag);
821 LinkTree linkTree = getFirstLinkInBlockTag(typeTag)
822 .orElseThrow(() -> new DocTreePathAwareRuntimeException(
823 configurationTypePath, "No valid {@link ...} reference found in @" + typeTag.getTagName()));
824
825 String type = getType(configurationTypePath, linkTree);
826 String javaLangPackage = "java.lang.";
827 if (type.startsWith(javaLangPackage)) {
828 type = type.substring(javaLangPackage.length());
829 }
830 return type;
831 }
832
833 private Optional<String> getConfigurationSource(DocTreePath path, Map<String, UnknownBlockTagTree> blockTags) {
834 UnknownBlockTagTree configurationSourceTag = blockTags.get("configurationSource");
835 if (configurationSourceTag == null) {
836 return Optional.empty();
837 }
838 DocTreePath configurationSourcePath = DocTreePath.getPath(path, configurationSourceTag);
839 LinkTree linkTree = getFirstLinkInBlockTag(configurationSourceTag)
840 .orElseThrow(() -> new DocTreePathAwareRuntimeException(
841 configurationSourcePath,
842 "No valid {@link ...} reference found in @" + configurationSourceTag.getTagName()));
843
844
845
846 MethodReference methodReference = getReferencedMethod(configurationSourcePath, linkTree);
847 if (methodReference.equals(METHOD_REFERENCE_SESSION_CONFIGURATION)) {
848 return Optional.of("Session Configuration");
849 } else if (methodReference.equals(METHOD_REFERENCE_SYSTEM_PROPERTY)) {
850 return Optional.of("Java System Properties");
851 } else {
852 reporter.print(
853 Diagnostic.Kind.WARNING,
854 path,
855 "Unknown configuration source: " + linkTree.getReference().getSignature()
856 + ", using raw signature as source");
857 return Optional.of(linkTree.getReference().getSignature());
858 }
859 }
860
861
862
863
864
865
866
867
868
869
870 protected record MethodReference(
871 String fullyQualifiedClassName, String methodName, List<String> fullyQualifiedParameterTypes) {}
872
873 private MethodReference getReferencedMethod(DocTreePath path, LinkTree link) {
874 ExecutableElement ee = getReferencedExecutableElement(path, link);
875 String fullyQualifiedClassName =
876 ((TypeElement) ee.getEnclosingElement()).getQualifiedName().toString();
877 String methodName = ee.getSimpleName().toString();
878 List<String> parameterTypes = ee.getParameters().stream()
879 .map(p -> getFullyQualifiedName(p.asType()))
880 .toList();
881 return new MethodReference(fullyQualifiedClassName, methodName, parameterTypes);
882 }
883
884 static String getFullyQualifiedName(Element e) {
885 return new SimpleElementVisitor14<String, Void>() {
886 @Override
887 public String visitModule(ModuleElement e, Void p) {
888 return e.getQualifiedName().toString();
889 }
890
891 @Override
892 public String visitPackage(PackageElement e, Void p) {
893 return e.getQualifiedName().toString();
894 }
895
896 @Override
897 public String visitType(TypeElement e, Void p) {
898 return e.getQualifiedName().toString();
899 }
900
901 @Override
902 protected String defaultAction(Element e, Void p) {
903 return visit(e.getEnclosingElement()) + "." + e.getSimpleName();
904 }
905 }.visit(e);
906 }
907
908 static String getFullyQualifiedName(TypeMirror e) {
909 return new SimpleTypeVisitor14<String, Void>() {
910 @Override
911 public String visitDeclared(DeclaredType t, Void p) {
912 Element e = t.asElement();
913 if (e instanceof TypeElement typeElement) {
914 return typeElement.getQualifiedName().toString();
915 }
916 return super.visitDeclared(t, p);
917 }
918
919 @Override
920 public String visitPrimitive(PrimitiveType t, Void p) {
921 return t.toString();
922 }
923
924 @Override
925 protected String defaultAction(TypeMirror e, Void p) {
926 return e.toString();
927 }
928 }.visit(e);
929 }
930
931 private ExecutableElement getReferencedExecutableElement(DocTreePath path, LinkTree link) {
932 DocTreePath linkRefPath = DocTreePath.getPath(path, link.getReference());
933 if (linkRefPath == null) {
934 throw new DocTreePathAwareRuntimeException(
935 path,
936 "Could not resolve link reference: " + link.getReference().getSignature());
937 }
938 Element element = docTrees.getElement(linkRefPath);
939 if (element instanceof ExecutableElement ee) {
940 return ee;
941 } else {
942 throw new DocTreePathAwareRuntimeException(
943 linkRefPath, "Expected an executable element, but got: " + element);
944 }
945 }
946
947 private static String toYesNo(boolean value) {
948 return value ? "Yes" : "No";
949 }
950
951
952
953
954 private static final class SingleArgumentOption implements Option {
955 private final List<String> names;
956 private final String description;
957 private final String parameters;
958 private final java.util.function.Consumer<String> processor;
959
960 SingleArgumentOption(
961 List<String> names,
962 String description,
963 String parameters,
964 java.util.function.Consumer<String> processor) {
965 this.names = names;
966 this.description = description;
967 this.parameters = parameters;
968 this.processor = processor;
969 }
970
971 @Override
972 public int getArgumentCount() {
973 return 1;
974 }
975
976 @Override
977 public String getDescription() {
978 return description;
979 }
980
981 @Override
982 public Kind getKind() {
983 return Kind.STANDARD;
984 }
985
986 @Override
987 public List<String> getNames() {
988 return names;
989 }
990
991 @Override
992 public String getParameters() {
993 return parameters;
994 }
995
996 @Override
997 public boolean process(String option, List<String> arguments) {
998 processor.accept(arguments.get(0));
999
1000
1001 return true;
1002 }
1003 }
1004 }