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 @@ -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 =
ExampleMetadataProvider.getInstance(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,41 @@

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;

/**
* 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 +60,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 = ExampleMetadataProvider.getInstance(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>The following code examples show how to use this service with the AWS SDK for Java v2:</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 (categoryExamples != null && !categoryExamples.isEmpty()) {
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