Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,12 @@ public Map<String, JavadocWrapper> getMethodsJavadocByIdentifier() {
return methodsJavadocByIdentifier;
}

public Optional<String> getSummary() {
return Optional.ofNullable(javadocWrapper).flatMap(JavadocWrapper::getSummary);
}

public Optional<String> getDescription() {
if(javadocWrapper != null) {
return javadocWrapper.getDescription();
}
return Optional.empty();
return Optional.ofNullable(javadocWrapper).flatMap(JavadocWrapper::getDescription);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.github.kbuntrock.javadoc;

import com.github.javaparser.javadoc.description.JavadocDescription;
import com.github.javaparser.javadoc.description.JavadocDescriptionElement;
import com.github.javaparser.javadoc.description.JavadocInlineTag;
import com.github.javaparser.javadoc.description.JavadocSnippet;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class JavadocElementParser {

public static final Pattern hrefLinkRegex = Pattern.compile("href=\"(.*?)\"");

public static Optional<String> getSummary(JavadocDescription description, String endOfLineReplacement) {
return processJavadocElements(description, endOfLineReplacement, elements -> elements
.filter(onlyTagsOfType(JavadocTag.SUMMARY))
.map(JavadocElementParser::toTagContent));
}

public static Optional<String> getDescription(JavadocDescription description, String endOfLineReplacement1) {
return processJavadocElements(description, endOfLineReplacement1, elements -> elements
.filter(JavadocElementParser::onlySnippetsAndFormattingTags)
.map(JavadocElementParser::formatDescriptionElement));
}

private static Optional<String> processJavadocElements(
JavadocDescription description,
String endOfLineReplacement,
Function<Stream<JavadocDescriptionElement>, Stream<String>> elementProcessor) {
return Optional.ofNullable(description)
.map(JavadocDescription::getElements)
.map(Collection::stream)
.map(elementProcessor)
.map(s -> s.collect(Collectors.joining()))
.map(s -> s.replaceAll("\r\n", "\n"))
.map(s -> removeNewlinesIfActivated(s, endOfLineReplacement))
.filter(text -> !text.isEmpty())
.map(String::trim);
}

private static String formatDescriptionElement(JavadocDescriptionElement javadocElement) {
if(javadocElement instanceof JavadocInlineTag) {
JavadocInlineTag tag = (JavadocInlineTag) javadocElement;
switch(JavadocTag.fromString(tag.getName())) {
case CODE:
return toInlineCodeMarkdown(tag);
case SEE:
return toEmphasizedMarkdown(tag);
case LINK:
return toHrefMarkdown(tag);
default:
return tag.getContent();
}
}

return javadocElement.toText();
}

private static String toInlineCodeMarkdown(JavadocInlineTag tag) {
return "`" + tag.getContent().trim() + "`";
}

private static String toEmphasizedMarkdown(JavadocInlineTag tag) {
return "*" + tag.getContent().trim() + "*";
}

private static String toHrefMarkdown(JavadocInlineTag tag) {
Matcher m = hrefLinkRegex.matcher(tag.getContent());
String url = null;
if(m.find()) {
url = m.group(1);
}
return String.format("[%s](%s)", url, url);
}

private static Predicate<JavadocDescriptionElement> onlyTagsOfType(JavadocTag tag) {
return e -> (e instanceof JavadocInlineTag && tag.toString().toLowerCase().equals(((JavadocInlineTag) e).getName()));
}

private static boolean onlySnippets(JavadocDescriptionElement e) {
return e instanceof JavadocSnippet;
}

private static String toTagContent(JavadocDescriptionElement t) {
return ((JavadocInlineTag) t).getContent().trim();
}

private static boolean onlySnippetsAndFormattingTags(JavadocDescriptionElement e) {
return onlySnippets(e) || (
e instanceof JavadocInlineTag &&
JavadocTag.isFormattingTag(((JavadocInlineTag) e).getName()));
}

private static String removeNewlinesIfActivated(String text, String endOfLineReplacement) {
return endOfLineReplacement != null
? text.replaceAll("\\r\\n", endOfLineReplacement).replaceAll("\\n", endOfLineReplacement)
: text;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public JavadocParser(final List<File> filesToScan, final JavadocConfiguration ja
charset = Charset.forName(javadocConfiguration.getEncoding());
} else {
logger.warn("Encoding " + javadocConfiguration.getEncoding() + " is not supported. UTF-8 will be used instead.");
logger.warn("Supported encoding on this JVM are : " + Charset.availableCharsets().keySet().stream()
.collect(Collectors.joining(", ")));
logger.warn("Supported encoding on this JVM are : " + String.join(", ", Charset.availableCharsets().keySet()));
}
parserConfiguration.setCharacterEncoding(charset);
debugScan = javadocConfiguration.isDebugScan();
Expand Down Expand Up @@ -85,10 +84,12 @@ private void printDebug() {
logger.debug("-------- PRINT JAVADOC SCAN RESULTS ----------");
for(final ClassDocumentation classDocumentation : javadocMap.values()) {
logger.debug("Class documentation for : " + classDocumentation.getCompleteName());
logger.debug("Summary : " + classDocumentation.getSummary());
logger.debug("Description : " + classDocumentation.getDescription());
if(!classDocumentation.getMethodsJavadocByIdentifier().isEmpty()) {
for(final Entry<String, JavadocWrapper> entry : classDocumentation.getMethodsJavadocByIdentifier().entrySet()) {
logger.debug("Method doc for : " + entry.getKey());
logger.debug("Summary : " + entry.getValue().getSummary());
logger.debug("Description : " + entry.getValue().getDescription());
entry.getValue().printParameters();
entry.getValue().printReturn();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.kbuntrock.javadoc;

import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

public enum JavadocTag {
// Tags with extractable information (content will be stripped from the general description)
SUMMARY,
AUTHOR,

// Formatting tags (text content should not be stripped)
CODE,
LINK,
SEE;

@Override
public String toString() {
return this.name().toLowerCase(Locale.ROOT);
}

public static JavadocTag fromString(String tagName) {
return JavadocTag.valueOf(tagName.toUpperCase(Locale.ROOT));
}

private static final HashSet<JavadocTag> formattingTags = new HashSet<>();

static {
formattingTags.add(CODE);
formattingTags.add(LINK);
formattingTags.add(SEE);
}

public static boolean isFormattingTag(String tagName) {
try {
JavadocTag tag = fromString(tagName);
return isFormattingTag(tag);
} catch (IllegalArgumentException e) {
return false;
}
}

public static boolean isFormattingTag(JavadocTag tag) {
return formattingTags.contains(tag);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,12 @@ public Optional<JavadocBlockTag> getReturnBlockTag() {
return Optional.empty();
}

public Optional<String> getSummary() {
return JavadocElementParser.getSummary(javadoc.getDescription(), endOfLineReplacement);
}

public Optional<String> getDescription() {
if(javadoc.getDescription() != null) {
String desc = javadoc.getDescription().toText();
if(endOfLineReplacement != null) {
desc = desc.replaceAll("\\r\\n", endOfLineReplacement).replaceAll("\\n", endOfLineReplacement);
}
if(!desc.isEmpty()) {
return Optional.of(desc);
}
}
return Optional.empty();
return JavadocElementParser.getDescription(javadoc.getDescription(), endOfLineReplacement);
}

public boolean isInheritTagFound() {
Expand All @@ -94,15 +89,14 @@ public void printParameters() {
Logger.INSTANCE.getLogger().debug(entry.getKey() + " : " + entry.getValue().getContent().toText());
}
}

}

public void printReturn() {
if(blockTagsByType != null) {
final Optional<JavadocBlockTag> returnTag = getReturnBlockTag();
if(returnTag.isPresent()) {
Logger.INSTANCE.getLogger().debug("Return : " + returnTag.get().getContent().toText());
}
returnTag.ifPresent(javadocBlockTag ->
Logger.INSTANCE.getLogger().debug("Return : " + javadocBlockTag.getContent().toText()));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ public void write(final File file, final TagLibrary tagLibrary) throws IOExcepti
specification.setTags(tagLibrary.getSortedTags().stream()
.map(tag -> {
if(tagLibrary.hasJavadocMap() && tag.getDescription() == null) {
ClassDocumentation classDocumentation = tagLibrary.getJavadocMap().get(tag.getClazz().getCanonicalName());
ClassDocumentation classDocumentation = tagLibrary.getJavadocMap()
.computeIfAbsent(tag.getClazz().getCanonicalName(),
k -> new ClassDocumentation(tag.getClazz().getCanonicalName(), tag.getClazz().getSimpleName()));
// Even if there is no declared class documentation, we may enhance it with javadoc on interface and/or abstract classes
if(classDocumentation == null) {
classDocumentation = new ClassDocumentation(tag.getClazz().getCanonicalName(), tag.getClazz().getSimpleName());
Expand All @@ -147,16 +149,15 @@ public void write(final File file, final TagLibrary tagLibrary) throws IOExcepti
"Class documentation found for tag " + tag.getClazz().getSimpleName() + " ? " + (classDocumentation != null));

classDocumentation.inheritanceEnhancement(tag.getClazz(), ClassDocumentation.EnhancementType.METHODS, tagLibrary.getJavadocMap());
final Optional<String> description = classDocumentation.getDescription();
if(description.isPresent()) {
return new TagElement(tag.computeConfiguredName(apiConfiguration), description.get());
}
return new TagElement(
tag.computeConfiguredName(apiConfiguration),
classDocumentation.getDescription().orElse(null));
}

return new TagElement(tag.computeConfiguredName(apiConfiguration), tag.getDescription());
}).collect(Collectors.toList()));

// Write the "paths" section (all url / http verbs combinaison scanned)
// Write the "paths" section (all url / http verbs combination scanned)
specification.setPaths(createPaths(tagLibrary));

final Map<String, Object> schemaSection = createSchemaSection(tagLibrary);
Expand Down Expand Up @@ -226,8 +227,8 @@ private Map<String, Map<String, Operation>> createPaths(final TagLibrary tagLibr
final String computedTagName = tag.computeConfiguredName(apiConfiguration);
operation.getTags().add(computedTagName);
operation.setOperationId(ObjectsUtils.nonNullElse(endpoint.getOperationAnnotationInfo().getOperationId(),
apiConfiguration.getOperationIdHelper().toOperationId(tag.getName(), computedTagName, endpoint.getName()))
);
apiConfiguration.getOperationIdHelper().toOperationId(tag.getName(), computedTagName, endpoint.getName()))
);
if(apiConfiguration.isLoopbackOperationName()) {
operation.setLoopbackOperationName(endpoint.getName());
}
Expand All @@ -248,12 +249,16 @@ private Map<String, Map<String, Operation>> createPaths(final TagLibrary tagLibr
operation.setDescription(endpoint.getOperationAnnotationInfo().getDescription());
} else {
if(methodJavadoc != null) {
operation.setDescription(methodJavadoc.getJavadoc().getDescription().toText());
operation.setDescription(methodJavadoc.getDescription().orElse(null));
}
}

if(endpoint.getOperationAnnotationInfo().getSummary() != null) {
operation.setSummary(endpoint.getOperationAnnotationInfo().getSummary());
} else {
if(methodJavadoc != null) {
operation.setSummary(methodJavadoc.getSummary().orElse(null));
}
}

// Warning on paths
Expand All @@ -273,7 +278,8 @@ private Map<String, Map<String, Operation>> createPaths(final TagLibrary tagLibr

// All parameters which are not in the body
for(final ParameterObject parameter : endpoint.getParameters().stream()
.filter(x -> ParameterLocation.BODY != x.getLocation() && ParameterLocation.BODY_PART != x.getLocation()).collect(Collectors.toList())) {
.filter(x -> ParameterLocation.BODY != x.getLocation() && ParameterLocation.BODY_PART != x.getLocation())
.collect(Collectors.toList())) {
final ParameterElement parameterElement = new ParameterElement();
parameterElement.setName(parameter.getName());
parameterElement.setIn(parameter.getLocation().toString().toLowerCase(Locale.ENGLISH));
Expand All @@ -290,8 +296,8 @@ private Map<String, Map<String, Operation>> createPaths(final TagLibrary tagLibr
}
parameterElement.setSchema(schema);


if(parameter.getJavaClass() == Object.class && parameter.isAllowEmptyValue() && parameter.getLocation() == ParameterLocation.QUERY) {
if(parameter.getJavaClass() == Object.class && parameter.isAllowEmptyValue()
&& parameter.getLocation() == ParameterLocation.QUERY) {
parameterElement.setSchema(null);
}

Expand Down Expand Up @@ -321,6 +327,9 @@ private Map<String, Map<String, Operation>> createPaths(final TagLibrary tagLibr
if(javadocParamWrapper != null) {
final Optional<String> desc = javadocParamWrapper.getDescription();
parameterElement.setDescription(desc.get());

final Optional<String> summary = javadocParamWrapper.getSummary();
parameterElement.setSummary(summary.orElse(null));
}
}
}
Expand All @@ -342,7 +351,7 @@ private Map<String, Map<String, Operation>> createPaths(final TagLibrary tagLibr

final ParameterObject body = bodies.get(0);
final Content requestBodyContent = isFormData(bodies)
? Content.fromMultipartBodies(bodies, tagLibrary)
? Content.fromMultipartBodies(bodies, tagLibrary)
: Content.fromDataObject(body, tagLibrary);

if(body.getFormats() != null) {
Expand Down Expand Up @@ -468,10 +477,11 @@ private static boolean isFormData(List<ParameterObject> bodies) {

/**
* Check if a common operation exist and merge it if possible
* @param tag the tag to document
* @param paths the already documented paths in this tag
*
* @param tag the tag to document
* @param paths the already documented paths in this tag
* @param operation the current operation to document
* @param response the current operation response (without the default ones)
* @param response the current operation response (without the default ones)
*/
private void mergeCommonOperations(Tag tag, Map<String, Map<String, Operation>> paths, Operation operation, Response response) {
// Check if on operation already exist for this name (GET / POST / ...) and path
Expand All @@ -482,7 +492,7 @@ private void mergeCommonOperations(Tag tag, Map<String, Map<String, Operation>>
// Check if there is a collision in response content type.
Object existingContent = existingOperation.getResponses().get(response.getCode());
for(Entry<String, Content> responseContent : response.getContent().entrySet()) {
if(existingContent instanceof Response ) {
if(existingContent instanceof Response) {
Response existingResponse = (Response) existingContent;
if(existingResponse.getContent().containsKey(responseContent.getKey())) {
// There are too many cases: this is uncommon, but it might be a valid case.
Expand All @@ -506,13 +516,15 @@ private void mergeCommonOperations(Tag tag, Map<String, Map<String, Operation>>
}
}
// Now merging parameters
Map<String, ParameterElement> existingParametersByNames = existingOperation.getParameters().stream().collect(Collectors.toMap(ParameterElement::getName, Function.identity()));
Map<String, ParameterElement> existingParametersByNames = existingOperation.getParameters().stream()
.collect(Collectors.toMap(ParameterElement::getName, Function.identity()));
for(ParameterElement parameter : operation.getParameters()) {
ParameterElement existingParameter = existingParametersByNames.get(parameter.getName());
if(existingParameter == null) {
existingOperation.getParameters().add(parameter);
} else {
if(!existingParameter.getSchema().getType().getNode().toString().equals(parameter.getSchema().getType().getNode().toString())) {
if(!existingParameter.getSchema().getType().getNode().toString()
.equals(parameter.getSchema().getType().getNode().toString())) {
Logger.INSTANCE.getLogger().warn("Parameters incoherences detected in path " + operation.getPath());
}
}
Expand Down
Loading