From 1c9b7a6c99f7d3c56a68599590d53f004fde5ba7 Mon Sep 17 00:00:00 2001 From: Thomas P Date: Fri, 13 Sep 2024 16:09:37 +0200 Subject: [PATCH] Parse @see tags and include them as quickLinks. (#213) --- .../frankdoc/FrankDocJsonFactory.java | 46 ++++--- .../frankdoc/model/FrankDocModel.java | 4 +- .../frankdoc/model/FrankElement.java | 119 ++++++++++++------ .../frankdoc/model/QuickLink.java | 20 +++ .../frankdoc/model/RootFrankElement.java | 4 +- ...riterNewAndJsonGenerationExamplesTest.java | 1 + .../frankdoc/testtarget/see/See.java | 17 +++ .../resources/doc/examplesExpected/see.json | 87 +++++++++++++ 8 files changed, 237 insertions(+), 61 deletions(-) create mode 100644 frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/QuickLink.java create mode 100644 frank-doc-doclet/src/test/java/org/frankframework/frankdoc/testtarget/see/See.java create mode 100644 frank-doc-doclet/src/test/resources/doc/examplesExpected/see.json diff --git a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/FrankDocJsonFactory.java b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/FrankDocJsonFactory.java index d6d28c44..a3ca58b5 100644 --- a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/FrankDocJsonFactory.java +++ b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/FrankDocJsonFactory.java @@ -230,9 +230,9 @@ private JsonObject getElement(FrankElement frankElement) throws JsonException { } List nonInheritedAttributes = frankElement.getChildrenOfKind(ElementChild.JSON_NOT_INHERITED, FrankAttribute.class); if (!nonInheritedAttributes.isEmpty()) { - JsonArrayBuilder b = bf.createArrayBuilder(); - nonInheritedAttributes.forEach(nia -> b.add(nia.getName())); - result.add("nonInheritedAttributes", b.build()); + final var builder = bf.createArrayBuilder(); + nonInheritedAttributes.forEach(nia -> builder.add(nia.getName())); + result.add("nonInheritedAttributes", builder.build()); } JsonArray configChildren = getConfigChildren(frankElement); if (!configChildren.isEmpty()) { @@ -242,31 +242,43 @@ private JsonObject getElement(FrankElement frankElement) throws JsonException { result.add("parametersDescription", frankElement.getMeaningOfParameters()); } if (!frankElement.getSpecificParameters().isEmpty()) { - JsonArrayBuilder b = bf.createArrayBuilder(); - frankElement.getSpecificParameters().forEach(sp -> b.add(getParsedJavaDocTag(sp))); - result.add("parameters", b.build()); + final var builder = bf.createArrayBuilder(); + frankElement.getSpecificParameters().forEach(sp -> builder.add(getParsedJavaDocTag(sp))); + result.add("parameters", builder.build()); } if(!frankElement.getForwards().isEmpty()) { - JsonArrayBuilder b = bf.createArrayBuilder(); - frankElement.getForwards().forEach(f -> b.add(getJsonForForward(f))); - result.add("forwards", b.build()); + final var builder = bf.createArrayBuilder(); + frankElement.getForwards().forEach(f -> builder.add(getJsonForForward(f))); + result.add("forwards", builder.build()); } if (!frankElement.getTags().isEmpty()) { - JsonObjectBuilder b = bf.createObjectBuilder(); - for(ParsedJavaDocTag tag: frankElement.getTags()) { - b.add(tag.getName(), tag.getDescription()); + final var builder = bf.createObjectBuilder(); + for (ParsedJavaDocTag tag: frankElement.getTags()) { + builder.add(tag.getName(), tag.getDescription()); } - result.add("tags", b.build()); + result.add("tags", builder.build()); } if (!frankElement.getLabels().isEmpty()) { - JsonArrayBuilder b = bf.createArrayBuilder(); - for(FrankLabel lab: frankElement.getLabels()) { + final var builder = bf.createArrayBuilder(); + for (FrankLabel lab: frankElement.getLabels()) { JsonObjectBuilder ob = bf.createObjectBuilder(); ob.add("label", lab.getName()); ob.add("value", lab.getValue()); - b.add(ob.build()); + builder.add(ob.build()); } - result.add("labels", b.build()); + result.add("labels", builder.build()); + } + if (!frankElement.getQuickLinks().isEmpty()) { + final var builder = bf.createArrayBuilder(); + frankElement.getQuickLinks().forEach(quickLink -> { + final var quickLinkBuilder = bf.createObjectBuilder(); + + quickLinkBuilder.add("label", quickLink.label()); + quickLinkBuilder.add("url", quickLink.url()); + + builder.add(quickLinkBuilder); + }); + result.add("links", builder.build()); } return result.build(); } diff --git a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankDocModel.java b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankDocModel.java index 75738561..4a3dbedb 100644 --- a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankDocModel.java +++ b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankDocModel.java @@ -222,7 +222,7 @@ private abstract class FrankElementCreationStrategy { private class FrankElementCreationStrategyRoot extends FrankElementCreationStrategy{ @Override FrankElement createFromClass(FrankClass clazz) { - return new RootFrankElement(clazz, classRepository, groupFactory, labelValues); + return new RootFrankElement(clazz, labelValues); } @Override @@ -234,7 +234,7 @@ FrankElement recursiveFindOrCreate(String fullClassName) throws FrankDocExceptio private class FrankElementCreationStrategyNonRoot extends FrankElementCreationStrategy { @Override FrankElement createFromClass(FrankClass clazz) { - return new FrankElement(clazz, classRepository, groupFactory, labelValues); + return new FrankElement(clazz, labelValues); } @Override diff --git a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankElement.java b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankElement.java index 87739dcf..f15a5726 100644 --- a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankElement.java +++ b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankElement.java @@ -28,7 +28,6 @@ import org.frankframework.frankdoc.util.LogUtil; import org.frankframework.frankdoc.wrapper.FrankAnnotation; import org.frankframework.frankdoc.wrapper.FrankClass; -import org.frankframework.frankdoc.wrapper.FrankClassRepository; import org.frankframework.frankdoc.wrapper.FrankDocException; import org.frankframework.frankdoc.wrapper.FrankEnumConstant; import org.frankframework.frankdoc.wrapper.FrankMethod; @@ -45,8 +44,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -62,10 +61,13 @@ public class FrankElement implements Comparable { public static final String JAVADOC_FORWARD = "@ff.forward"; public static final String JAVADOC_FORWARD_ANNOTATION_CLASSNAME = "org.frankframework.doc.Forward"; public static final String JAVADOC_FORWARDS_ANNOTATION_CLASSNAME = "org.frankframework.doc.Forwards"; + public static final String JAVADOC_SEE = "@see"; public static final String JAVADOC_TAG = "@ff.tag"; public static final String LABEL = "org.frankframework.doc.Label"; public static final String LABEL_NAME = "name"; + private static final Pattern JAVADOC_SEE_PATTERN = Pattern.compile("(.*?)<\\/a>"); + private static Logger log = LogUtil.getLogger(FrankElement.class); private static final Comparator COMPARATOR = @@ -108,19 +110,22 @@ public class FrankElement implements Comparable { private @Getter String meaningOfParameters; private @Getter List specificParameters = new ArrayList<>(); private @Getter List forwards = new ArrayList<>(); + private @Getter List quickLinks = new ArrayList<>(); private @Getter List tags = new ArrayList<>(); private @Getter List labels = new ArrayList<>(); - FrankElement(FrankClass clazz, FrankClassRepository repository, FrankDocGroupFactory groupFactory, LabelValues labelValues) { + FrankElement(FrankClass clazz, LabelValues labelValues) { this(clazz.getName(), clazz.getSimpleName(), clazz.isAbstract()); deprecationInfo = Deprecated.getInstance().getInfo(clazz); configChildSets = new LinkedHashMap<>(); this.completeFrankElement(clazz); handleConfigChildSetterCandidates(clazz); - handlePossibleParameters(clazz); - handlePossibleForwards(clazz); - handlePossibleTags(clazz); + this.meaningOfParameters = parseParametersJavadocTag(clazz); + this.specificParameters = parseParameterJavadocTags(clazz); + this.forwards = parseForwardJavadocTags(clazz); + this.quickLinks = parseSeeJavadocTags(clazz); + this.tags = parseTagJavadocTags(clazz); handleLabels(clazz, labelValues); } @@ -197,36 +202,46 @@ void handleAncestorMethod(FrankClass ancestorClass) { } } - private void handlePossibleParameters(FrankClass clazz) { + private List parseParameterJavadocTags(FrankClass clazz) { + return parseJavadocTags(clazz, JAVADOC_PARAMETER); + } + + private String parseParametersJavadocTag(FrankClass clazz) { try { - this.meaningOfParameters = Utils.substituteJavadocTags(clazz.getJavaDocTag(JAVADOC_PARAMETERS), clazz); + return Utils.substituteJavadocTags(clazz.getJavaDocTag(JAVADOC_PARAMETERS), clazz); } catch(FrankDocException e) { log.error("Error parsing the meaning of parameters", e); } - assembleParsedJavaDocTags(clazz, JAVADOC_PARAMETER, p -> this.specificParameters.add(p)); + + return null; } - private void handlePossibleForwards(FrankClass clazz) { + private List parseForwardJavadocTags(FrankClass clazz) { // The following line can be removed when ff.forward should no longer be supported. - assembleParsedJavaDocTags(clazz, JAVADOC_FORWARD, p -> this.forwards.add(new Forward(p.getName(), p.getDescription()))); + List forwards = new ArrayList<>(parseJavadocTags(clazz, JAVADOC_FORWARD).stream() + .map(tag -> new Forward(tag.getName(), tag.getDescription())) + .toList()); // The Forwards annotation contains an array of Forward annotation (repeatable annotation). // The Forwards annotation will not exist when the class has only one Forward annotation. FrankAnnotation forwardsAnnotation = clazz.getAnnotation(JAVADOC_FORWARDS_ANNOTATION_CLASSNAME); if (forwardsAnnotation != null) { - FrankAnnotation[] forwards = (FrankAnnotation[]) forwardsAnnotation.getValue(); - Arrays.stream(forwards) - .forEach(this::addForwardAnnotation); - return; + FrankAnnotation[] forwardAnnotations = (FrankAnnotation[]) forwardsAnnotation.getValue(); + + forwards.addAll(Arrays.stream(forwardAnnotations) + .map(this::annotationToForward) + .toList()); } FrankAnnotation forwardAnnotation = clazz.getAnnotation(JAVADOC_FORWARD_ANNOTATION_CLASSNAME); if (forwardAnnotation != null) { - addForwardAnnotation(forwardAnnotation); + forwards.add(annotationToForward(forwardAnnotation)); } + + return forwards; } - private void addForwardAnnotation(FrankAnnotation annotation) { + private Forward annotationToForward(FrankAnnotation annotation) { String name = (String) annotation.getValueOf("name"); String description = (String) annotation.getValueOf("description"); @@ -234,36 +249,60 @@ private void addForwardAnnotation(FrankAnnotation annotation) { description = null; } - this.forwards.add(new Forward(name, description)); + return new Forward(name, description); + } + + private List parseSeeJavadocTags(FrankClass clazz) { + return clazz.getAllJavaDocTagsOf(JAVADOC_SEE).stream() + .map(this::tryParseQuickLink) + .filter(Objects::nonNull) + .toList(); + } + + private QuickLink tryParseQuickLink(String value) { + final var matcher = JAVADOC_SEE_PATTERN.matcher(value); + + if (!matcher.matches()) { + return null; + } + + String label = matcher.group(2); + String url = matcher.group(1); + + return new QuickLink(label, url); } - private void handlePossibleTags(FrankClass clazz) { - assembleParsedJavaDocTags(clazz, JAVADOC_TAG, p -> this.tags.add(p)); - Map tagCounts = tags.stream().collect(Collectors.groupingBy(ParsedJavaDocTag::getName, Collectors.counting())); - List duplicates = tagCounts.entrySet().stream() + private List parseTagJavadocTags(FrankClass clazz) { + List tags = parseJavadocTags(clazz, JAVADOC_TAG); + tags.stream() + .collect(Collectors.groupingBy(ParsedJavaDocTag::getName, Collectors.counting())) + .entrySet().stream() .filter(e -> e.getValue() >= 2L) .map(Entry::getKey) .sorted() - .collect(Collectors.toList()); - for (String duplicate : duplicates) { - log.error("FrankElement [{}] has multiple values for tag [{}]", fullName, duplicate); - } + .forEach(duplicate -> log.error("FrankElement [{}] has multiple values for tag [{}]", fullName, duplicate)); + + return tags; } - private void assembleParsedJavaDocTags(FrankClass clazz, String tagName, Consumer acceptor) { - for (String arguments : clazz.getAllJavaDocTagsOf(tagName)) { - ParsedJavaDocTag parsed; - try { - parsed = ParsedJavaDocTag.getInstance(arguments, s -> Utils.substituteJavadocTags(s, clazz)); - } catch (FrankDocException e) { - log.error("Error parsing a [{}] tag of class [{}]", tagName, fullName, e); - continue; - } - if (parsed.getDescription() == null) { - log.warn("FrankElement [{}] has a [{}] tag without a value: [{}]", fullName, tagName, arguments); - } - acceptor.accept(parsed); - } + private List parseJavadocTags(FrankClass clazz, String tagName) { + return clazz.getAllJavaDocTagsOf(tagName).stream() + .map(arguments -> { + ParsedJavaDocTag parsed; + try { + parsed = ParsedJavaDocTag.getInstance(arguments, s -> Utils.substituteJavadocTags(s, clazz)); + } catch (FrankDocException e) { + log.error("Error parsing a [{}] tag of class [{}]", tagName, fullName, e); + return null; + } + if (parsed.getDescription() == null) { + log.warn("FrankElement [{}] has a [{}] tag without a value: [{}]", fullName, tagName, arguments); + } + + return parsed; + }) + .filter(Objects::nonNull) + .toList(); } private void handleLabels(FrankClass clazz, LabelValues labelValues) { diff --git a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/QuickLink.java b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/QuickLink.java new file mode 100644 index 00000000..539a6cb1 --- /dev/null +++ b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/QuickLink.java @@ -0,0 +1,20 @@ +/* +Copyright 2024 WeAreFrank! + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.frankframework.frankdoc.model; + +public record QuickLink(String label, String url) { +} diff --git a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/RootFrankElement.java b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/RootFrankElement.java index 5af4cd7e..2321d3f3 100644 --- a/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/RootFrankElement.java +++ b/frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/RootFrankElement.java @@ -24,8 +24,8 @@ * part of a config child, but they have a role name that matches rules in digester-rules.xml. */ class RootFrankElement extends FrankElement { - RootFrankElement(FrankClass clazz, FrankClassRepository repository, FrankDocGroupFactory groupFactory, LabelValues labelValues) { - super(clazz, repository, groupFactory, labelValues); + RootFrankElement(FrankClass clazz, LabelValues labelValues) { + super(clazz, labelValues); } public String getRoleName() { diff --git a/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/DocWriterNewAndJsonGenerationExamplesTest.java b/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/DocWriterNewAndJsonGenerationExamplesTest.java index 24e591b7..6429c932 100644 --- a/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/DocWriterNewAndJsonGenerationExamplesTest.java +++ b/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/DocWriterNewAndJsonGenerationExamplesTest.java @@ -70,6 +70,7 @@ public static Collection data() { {XsdVersion.STRICT, AttributeTypeStrategy.ALLOW_PROPERTY_REF, "singular-test-digester-rules.xml", "org.frankframework.frankdoc.testtarget.examples.mandatory.single.Master", "mandatorySingle.xsd", "mandatorySingle.json"}, {XsdVersion.STRICT, AttributeTypeStrategy.ALLOW_PROPERTY_REF, "general-test-digester-rules.xml", "org.frankframework.frankdoc.testtarget.examples.unsafe.Unsafe", "unsafe.xsd", "unsafe.json"}, {XsdVersion.STRICT, AttributeTypeStrategy.ALLOW_PROPERTY_REF, "general-test-digester-rules.xml", "org.frankframework.frankdoc.testtarget.examples.inheritdoc.Child", "inheritdoc.xsd", "inheritdoc.json"}, + {XsdVersion.STRICT, AttributeTypeStrategy.ALLOW_PROPERTY_REF, "general-test-digester-rules.xml", "org.frankframework.frankdoc.testtarget.see.See", null, "see.json"}, // Classes in package "org.frankframework.frankdoc.testtarget.examples.simple.name.conflict.second" are also added although that package is not shown in this table. // See method getAllRequiredPackages(). {XsdVersion.STRICT, AttributeTypeStrategy.ALLOW_PROPERTY_REF, "general-test-digester-rules.xml", "org.frankframework.frankdoc.testtarget.examples.simple.name.conflict.first.Master", "nameConflictStrict.xsd", "nameConflict.json"}, diff --git a/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/testtarget/see/See.java b/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/testtarget/see/See.java new file mode 100644 index 00000000..636fc70e --- /dev/null +++ b/frank-doc-doclet/src/test/java/org/frankframework/frankdoc/testtarget/see/See.java @@ -0,0 +1,17 @@ +package org.frankframework.frankdoc.testtarget.see; + +/** + * Documentation of the class. + * + * @see exampleone.com + * @see exampletwo.com + * @see See + * @see See#setAttribute + * @see "Ignore this" + */ +public class See { + + public void setAttribute(String attribute) { + } + +} diff --git a/frank-doc-doclet/src/test/resources/doc/examplesExpected/see.json b/frank-doc-doclet/src/test/resources/doc/examplesExpected/see.json new file mode 100644 index 00000000..a2226d1d --- /dev/null +++ b/frank-doc-doclet/src/test/resources/doc/examplesExpected/see.json @@ -0,0 +1,87 @@ +{ + "metadata": { + "version": "1.2.3-SNAPSHOT" + }, + "groups": [ + { + "name": "Other", + "types": [ + "org.frankframework.frankdoc.testtarget.see.See", + "Module" + ] + } + ], + "types": [ + { + "name": "org.frankframework.frankdoc.testtarget.see.See", + "members": [ + "org.frankframework.frankdoc.testtarget.see.See" + ] + }, + { + "name": "Module", + "members": [ + "Module" + ] + } + ], + "elements": [ + { + "name": "Module", + "fullName": "Module", + "description": "Wrapper element to help split up large configuration files into smaller valid XML files. It may be used as root tag when an XML file contains multiple adapters and/or jobs. The Module element itself does not influence the behavior of Frank configurations.", + "elementNames": [ + "Module" + ] + }, + { + "name": "Object", + "fullName": "java.lang.Object", + "elementNames": [ + ], + "attributes": [ + { + "name": "active", + "description": "If defined and empty or false, then this element and all its children are ignored" + } + ] + }, + { + "name": "See", + "fullName": "org.frankframework.frankdoc.testtarget.see.See", + "description": "Documentation of the class.", + "elementNames": [ + "See" + ], + "attributes": [ + { + "name": "attribute" + }, + { + "name": "active", + "description": "If defined and empty or false, then this element and all its children are ignored" + } + ], + "children": [ + { + "multiple": true, + "roleName": "module", + "description": "Wrapper element to help split up large configuration files into smaller valid XML files. It may be used as root tag when an XML file contains multiple adapters and/or jobs. The Module element itself does not influence the behavior of Frank configurations.", + "type": "Module" + } + ], + "links": [ + { + "label": "exampleone.com", + "url": "https://exampleone.com" + }, + { + "label": "exampletwo.com", + "url": "https://exampletwo.com" + } + ] + } + ], + "enums": [ + ] +}