Skip to content

Commit

Permalink
Parse @see tags and include them as quickLinks. (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaspj10 committed Sep 13, 2024
1 parent 8be0a76 commit 1c9b7a6
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ private JsonObject getElement(FrankElement frankElement) throws JsonException {
}
List<FrankAttribute> 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()) {
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -62,10 +61,13 @@ public class FrankElement implements Comparable<FrankElement> {
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 href=[\"'](.*?)[\"']>(.*?)<\\/a>");

private static Logger log = LogUtil.getLogger(FrankElement.class);

private static final Comparator<FrankElement> COMPARATOR =
Expand Down Expand Up @@ -108,19 +110,22 @@ public class FrankElement implements Comparable<FrankElement> {
private @Getter String meaningOfParameters;
private @Getter List<ParsedJavaDocTag> specificParameters = new ArrayList<>();
private @Getter List<Forward> forwards = new ArrayList<>();
private @Getter List<QuickLink> quickLinks = new ArrayList<>();
private @Getter List<ParsedJavaDocTag> tags = new ArrayList<>();

private @Getter List<FrankLabel> 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);
}

Expand Down Expand Up @@ -197,73 +202,107 @@ void handleAncestorMethod(FrankClass ancestorClass) {
}
}

private void handlePossibleParameters(FrankClass clazz) {
private List<ParsedJavaDocTag> 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<Forward> 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<Forward> 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");

if (description.isEmpty()) {
description = null;
}

this.forwards.add(new Forward(name, description));
return new Forward(name, description);
}

private List<QuickLink> 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<String, Long> tagCounts = tags.stream().collect(Collectors.groupingBy(ParsedJavaDocTag::getName, Collectors.counting()));
List<String> duplicates = tagCounts.entrySet().stream()
private List<ParsedJavaDocTag> parseTagJavadocTags(FrankClass clazz) {
List<ParsedJavaDocTag> 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<ParsedJavaDocTag> 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<ParsedJavaDocTag> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public static Collection<Object[]> 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"},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.frankframework.frankdoc.testtarget.see;

/**
* Documentation of the class.
*
* @see <a href="https://exampleone.com">exampleone.com</a>
* @see <a href='https://exampletwo.com'>exampletwo.com</a>
* @see See
* @see See#setAttribute
* @see "Ignore this"
*/
public class See {

public void setAttribute(String attribute) {
}

}
Loading

0 comments on commit 1c9b7a6

Please sign in to comment.