Skip to content
Open
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-7930031.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Adds links to AWS Code Library examples in package summary documentation and to individual methods documentation in Java SDK API Reference guide."
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@

package software.amazon.awssdk.codegen.docs;

import static software.amazon.awssdk.codegen.internal.Constant.EXAMPLE_META_PATH;
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.createLinkToServiceDocumentation;
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.stripHtmlTags;

import com.squareup.javapoet.ClassName;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import software.amazon.awssdk.codegen.internal.ExampleMetadataProvider;
import software.amazon.awssdk.codegen.model.intermediate.DocumentationModel;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
Expand Down Expand Up @@ -51,6 +54,9 @@ abstract class OperationDocProvider {
"this method will throw an exception. If the file is not writable by the current user then " +
"an exception will be thrown. ";

private static final ExampleMetadataProvider EXAMPLE_PROVIDER =
new ExampleMetadataProvider(EXAMPLE_META_PATH);

protected final IntermediateModel model;
protected final OperationModel opModel;
protected final DocConfiguration config;
Expand Down Expand Up @@ -86,6 +92,10 @@ String getDocs() {
if (!crosslink.isEmpty()) {
docBuilder.see(crosslink);
}

Optional<String> codeExampleLink = EXAMPLE_PROVIDER
.createLinkToCodeExample(model.getMetadata(), opModel.getOperationName());
codeExampleLink.ifPresent(docBuilder::see);
return docBuilder.build().replace("$", "&#36");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,42 @@

package software.amazon.awssdk.codegen.emitters.tasks;

import static software.amazon.awssdk.codegen.internal.Constant.EXAMPLE_META_PATH;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
import software.amazon.awssdk.codegen.emitters.SimpleGeneratorTask;
import software.amazon.awssdk.codegen.internal.ExampleMetadataProvider;
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
import software.amazon.awssdk.utils.CollectionUtils;

/**
* Emits the package-info.java for the base service package. Includes the service
* level documentation.
* level documentation and code examples for that service organized by category.
*/
public final class PackageInfoGeneratorTasks extends BaseGeneratorTasks {

/**
* Mapping from internal category names to user-friendly display names.
* This defines the preferred order and display format for code example categories.
*/
private static final Map<String, String> CATEGORY_DISPLAY_MAPPING;

static {
Map<String, String> mapping = new LinkedHashMap<>();
mapping.put("Hello", "Getting Started");
mapping.put("Basics", "Basics");
mapping.put("Api", "API Actions");
mapping.put("Scenarios", "Scenarios");
mapping.put("Serverless examples", "Serverless Examples");
CATEGORY_DISPLAY_MAPPING = Collections.unmodifiableMap(mapping);
}

private final String baseDirectory;

PackageInfoGeneratorTasks(GeneratorTaskParams dependencies) {
Expand All @@ -38,17 +61,150 @@ public final class PackageInfoGeneratorTasks extends BaseGeneratorTasks {
@Override
protected List<GeneratorTask> createTasks() throws Exception {
Metadata metadata = model.getMetadata();
String packageInfoContents =
String.format("/**%n"
+ " * %s%n"
+ "*/%n"
+ "package %s;",
metadata.getDocumentation(),
metadata.getFullClientPackageName());
String packageInfoContents = buildPackageInfoContent(metadata, EXAMPLE_META_PATH);

return Collections.singletonList(new SimpleGeneratorTask(baseDirectory,
"package-info.java",
model.getFileHeader(),
() -> packageInfoContents));
}

/**
* Builds the complete package-info.java content including Javadoc and package declaration.
*
* @param metadata the service metadata containing documentation and package information
* @param exampleMetaPath the path to the example metadata JSON file
* @return the complete package-info.java file content
*/
String buildPackageInfoContent(Metadata metadata, String exampleMetaPath) {
String baseDocumentation = metadata.getDocumentation();
String codeExamples = getCodeExamplesWithPath(metadata, exampleMetaPath);
String javadocContent = buildJavadocContent(baseDocumentation, codeExamples);

return javadocContent + System.lineSeparator() + "package " + metadata.getFullClientPackageName() + ";";
}

/**
* Builds the Javadoc comment content for the package-info.java file.
*
* @param baseDocumentation the base service documentation
* @param codeExamples the formatted code examples content, or empty string if none
* @return the complete Javadoc comment including opening and closing markers
*/
private String buildJavadocContent(String baseDocumentation, String codeExamples) {
StringBuilder javadoc = new StringBuilder();
javadoc.append("/**").append(System.lineSeparator());
javadoc.append(" * ").append(baseDocumentation).append(System.lineSeparator());

if (!codeExamples.isEmpty()) {
javadoc.append(" *").append(System.lineSeparator());
javadoc.append(" * ").append(codeExamples).append(System.lineSeparator());
}

javadoc.append(" */");
return javadoc.toString();
}

/**
* Gets code examples using a custom example metadata path.
*/
private String getCodeExamplesWithPath(Metadata metadata, String exampleMetaPath) {
ExampleMetadataProvider exampleProvider = new ExampleMetadataProvider(exampleMetaPath);
List<ExampleMetadataProvider.ExampleData> examples = exampleProvider.getServiceCodeExamples(metadata);

if (examples.isEmpty()) {
return "";
}

return generateCodeExamplesJavadoc(examples);
}

private String generateCodeExamplesJavadoc(List<ExampleMetadataProvider.ExampleData> examples) {
Map<String, List<ExampleMetadataProvider.ExampleData>> categorizedExamples =
examples.stream().collect(Collectors.groupingBy(ExampleMetadataProvider.ExampleData::getCategory,
LinkedHashMap::new,
Collectors.toList()));

StringBuilder javadoc = new StringBuilder();
javadoc.append("<h2>Code Examples</h2>").append(System.lineSeparator());
javadoc.append("<p>For code examples demonstrating how to use this service with the AWS SDK for Java v2, see:</p>")
.append(System.lineSeparator());

appendPredefinedCategories(javadoc, categorizedExamples, CATEGORY_DISPLAY_MAPPING);
appendRemainingCategories(javadoc, categorizedExamples, CATEGORY_DISPLAY_MAPPING);

return formatAsJavadocLines(javadoc.toString());
}

/**
* Formats HTML content as properly indented Javadoc comment lines.
* Each non-empty line gets prefixed with appropriate Javadoc comment formatting.
*
* @param htmlContent the HTML content to format for Javadoc
* @return formatted string ready for inclusion in Javadoc comments
*/
private String formatAsJavadocLines(String htmlContent) {
StringBuilder result = new StringBuilder();
String[] lines = htmlContent.split(System.lineSeparator());

for (int i = 0; i < lines.length; i++) {
String line = lines[i];
if (!line.trim().isEmpty()) {
result.append(line);
if (i < lines.length - 1) {
result.append(System.lineSeparator()).append(" * ");
}
}
}
return result.toString();
}

/**
* Appends predefined categories to the Javadoc in the preferred order.
* Only includes categories that exist in the categorized examples and have content.
*/
private void appendPredefinedCategories(StringBuilder javadoc,
Map<String, List<ExampleMetadataProvider.ExampleData>> categorizedExamples,
Map<String, String> categoryMapping) {
categoryMapping.forEach((category, displayName) ->
appendCategoryIfExists(javadoc, categorizedExamples, category, displayName));
}

/**
* Appends any remaining categories that weren't in the predefined mapping.
*/
private void appendRemainingCategories(StringBuilder javadoc,
Map<String, List<ExampleMetadataProvider.ExampleData>> categorizedExamples,
Map<String, String> categoryMapping) {
categorizedExamples.entrySet().stream()
.filter(entry -> !categoryMapping.containsKey(entry.getKey()))
.forEach(entry -> appendCategoryIfExists(javadoc, categorizedExamples, entry.getKey(),
entry.getKey()));
}

/**
* Appends a category section if examples exist for the given category.
*/
private void appendCategoryIfExists(StringBuilder javadoc,
Map<String, List<ExampleMetadataProvider.ExampleData>> categorizedExamples,
String category,
String displayName) {
List<ExampleMetadataProvider.ExampleData> categoryExamples = categorizedExamples.get(category);
if (!CollectionUtils.isNullOrEmpty(categoryExamples)) {
appendCategorySection(javadoc, displayName, categoryExamples);
}
}

private void appendCategorySection(StringBuilder javadoc, String displayName,
List<ExampleMetadataProvider.ExampleData> categoryExamples) {
javadoc.append("<h3>").append(displayName).append("</h3>").append(System.lineSeparator());
javadoc.append("<ul>").append(System.lineSeparator());

for (ExampleMetadataProvider.ExampleData example : categoryExamples) {
javadoc.append("<li><a href=\"").append(example.getUrl()).append("\" target=\"_top\">")
.append(example.getTitle()).append("</a></li>").append(System.lineSeparator());
}
javadoc.append("</ul>").append(System.lineSeparator());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public final class Constant {

public static final String AWS_DOCS_HOST = "docs.aws.amazon.com";

public static final String EXAMPLE_META_PATH = "software/amazon/awssdk/codegen/example-meta.json";

public static final String APPROVED_SIMPLE_METHOD_VERBS = "(get|list|describe|lookup|batchGet).*";

public static final String ASYNC_STREAMING_INPUT_PARAM = "requestBody";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,4 @@ public static String defaultFluentReturn() {
public static String defaultExistenceCheck() {
return DEFAULT_EXISTENCE_CHECK;
}
}
}
Loading
Loading