Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import software.amazon.awssdk.codegen.internal.DocumentationUtils;
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 @@ -86,6 +87,14 @@ String getDocs() {
if (!crosslink.isEmpty()) {
docBuilder.see(crosslink);
}

String exampleMetaPath = "software/amazon/awssdk/codegen/example-meta.json";
String codeExampleLink = DocumentationUtils.createLinkToCodeExample(model.getMetadata(),
opModel.getOperationName(),
exampleMetaPath);
if (!codeExampleLink.isEmpty()) {
docBuilder.see(codeExampleLink);
}
return docBuilder.build().replace("$", "&#36");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

import java.util.Collections;
import java.util.List;
import java.util.Map;
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.DocumentationUtils;
import software.amazon.awssdk.codegen.model.intermediate.Metadata;

/**
Expand All @@ -38,17 +40,100 @@ 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 baseDocumentation = metadata.getDocumentation();

String codeExamples = getCodeExamples(metadata);

StringBuilder sb = new StringBuilder();
sb.append(String.format("/**%n * %s%n", baseDocumentation));
if (!codeExamples.isEmpty()) {
sb.append(String.format(" *%n * %s%n", codeExamples));
}
sb.append(String.format("*/%npackage %s;", metadata.getFullClientPackageName()));
String packageInfoContents = sb.toString();
return Collections.singletonList(new SimpleGeneratorTask(baseDirectory,
"package-info.java",
model.getFileHeader(),
() -> packageInfoContents));
}

String getCodeExamples(Metadata metadata) {
String exampleMetaPath = "software/amazon/awssdk/codegen/example-meta.json";
List<DocumentationUtils.ExampleData> examples =
DocumentationUtils.getServiceCodeExamples(metadata, exampleMetaPath);

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

String codeExamplesJavadoc = generateCodeExamplesJavadoc(examples);

StringBuilder result = new StringBuilder();
String[] lines = codeExamplesJavadoc.split("\n");
for (String line : lines) {
if (!line.trim().isEmpty()) {
result.append(line);
if (!line.equals(lines[lines.length - 1])) {
result.append(System.lineSeparator()).append(" * ");
}
}
}

return result.toString();
}


private String generateCodeExamplesJavadoc(List<DocumentationUtils.ExampleData> examples) {
Map<String, List<DocumentationUtils.ExampleData>> categorizedExamples = new java.util.LinkedHashMap<>();
for (DocumentationUtils.ExampleData example : examples) {
categorizedExamples.computeIfAbsent(example.getCategory(), k -> new java.util.ArrayList<>()).add(example);
}

StringBuilder javadoc = new StringBuilder();
javadoc.append("<h2>Code Examples</h2>").append("\n");
javadoc.append("<p>The following code examples show how to use this service with the AWS SDK for Java v2:</p>")
.append("\n");

Map<String, String> categoryMapping = new java.util.LinkedHashMap<>();
categoryMapping.put("Hello", "Getting Started");
categoryMapping.put("Basics", "Basics");
categoryMapping.put("Api", "API Actions");
categoryMapping.put("Scenarios", "Scenarios");
categoryMapping.put("Serverless examples", "Serverless Examples");

for (Map.Entry<String, String> entry : categoryMapping.entrySet()) {
String category = entry.getKey();
String displayName = entry.getValue();
List<DocumentationUtils.ExampleData> categoryExamples = categorizedExamples.get(category);
if (categoryExamples != null && !categoryExamples.isEmpty()) {
appendCategorySection(javadoc, displayName, categoryExamples);
}
}

for (Map.Entry<String, List<DocumentationUtils.ExampleData>> entry : categorizedExamples.entrySet()) {
String category = entry.getKey();
if (!categoryMapping.containsKey(category)) {
List<DocumentationUtils.ExampleData> categoryExamples = entry.getValue();
if (!categoryExamples.isEmpty()) {
appendCategorySection(javadoc, category, categoryExamples);
}
}
}

return javadoc.toString();
}

private void appendCategorySection(StringBuilder javadoc, String displayName,
List<DocumentationUtils.ExampleData> categoryExamples) {
javadoc.append("<h3>").append(displayName).append("</h3>").append("\n");
javadoc.append("<ul>").append("\n");

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@
import static software.amazon.awssdk.codegen.model.intermediate.ShapeType.Request;
import static software.amazon.awssdk.codegen.model.intermediate.ShapeType.Response;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
import software.amazon.awssdk.utils.Logger;

public final class DocumentationUtils {

Expand Down Expand Up @@ -54,6 +64,11 @@ public final class DocumentationUtils {
"iot", "data.iot", "machinelearning", "rekognition", "s3", "sdb", "swf"
));
private static final Pattern COMMENT_DELIMITER = Pattern.compile("\\*\\/");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private static final Logger log = Logger.loggerFor(DocumentationUtils.class);
private static Map<String, JsonNode> serviceNodeCache;
private static Map<String, String> normalizedServiceKeyMap;

private DocumentationUtils() {
}
Expand Down Expand Up @@ -140,6 +155,39 @@ public static String createLinkToServiceDocumentation(Metadata metadata, ShapeMo
: "";
}

/**
* Create a link to a code example for the given operation.
*
* @param metadata the service metadata containing service name information
* @param operationName the name of the operation to find an example for
* @param exampleMetaPath the path to the example metadata JSON file
* @return a '@see also' HTML link to the code example, or empty string if no example found
*/
public static String createLinkToCodeExample(Metadata metadata, String operationName, String exampleMetaPath) {
try {
String normalizedServiceName = metadata.getServiceName().toLowerCase(Locale.ROOT);
Map<String, String> normalizedMap = getNormalizedServiceKeyMap(exampleMetaPath);
String actualServiceKey = normalizedMap.get(normalizedServiceName);

if (actualServiceKey != null) {
String targetExampleId = actualServiceKey + "_" + operationName;
JsonNode serviceNode = getServiceNode(actualServiceKey, exampleMetaPath);

if (serviceNode != null) {
String url = findOperationUrl(serviceNode, targetExampleId);
if (url != null) {
return String.format("<a href=\"%s\" target=\"_top\">Code Example</a>", url);
}
}
}

return "";
} catch (Exception e) {
log.debug(() -> "Failed to create code example link for " + metadata.getServiceName() + "." + operationName, e);
return "";
}
}

public static String removeFromEnd(String string, String stringToRemove) {
return string.endsWith(stringToRemove) ? string.substring(0, string.length() - stringToRemove.length()) : string;
}
Expand Down Expand Up @@ -177,4 +225,174 @@ public static String defaultFluentReturn() {
public static String defaultExistenceCheck() {
return DEFAULT_EXISTENCE_CHECK;
}


/**
* Gets the cached service node
*/
private static JsonNode getServiceNode(String serviceKey, String exampleMetaPath) {
if (serviceNodeCache == null) {
buildServiceCache(exampleMetaPath);
}
return serviceNodeCache != null ? serviceNodeCache.get(serviceKey) : null;
}

/**
* Gets the cached normalized service key map for service name matching.
*/
private static Map<String, String> getNormalizedServiceKeyMap(String exampleMetaPath) {
if (normalizedServiceKeyMap == null) {
buildServiceCache(exampleMetaPath);
}
return normalizedServiceKeyMap != null ? normalizedServiceKeyMap : new HashMap<>();
}

/**
* Builds the service node cache and normalized service key mapping from the specified example metadata file.
*/
private static void buildServiceCache(String exampleMetaPath) {
Map<String, JsonNode> nodeCache = new HashMap<>();
Map<String, String> normalizedMap = new HashMap<>();

try (InputStream inputStream = DocumentationUtils.class.getClassLoader()
.getResourceAsStream(exampleMetaPath)) {

if (inputStream == null) {
log.debug(() -> exampleMetaPath + " not found in classpath");
} else {
JsonNode root = OBJECT_MAPPER.readTree(inputStream);
JsonNode servicesNode = root.get("services");

if (servicesNode != null) {
servicesNode.fieldNames().forEachRemaining(serviceKey -> {
buildNormalizedMapping(serviceKey, normalizedMap);
nodeCache.put(serviceKey, servicesNode.get(serviceKey));
});
}
}

} catch (IOException e) {
log.warn(() -> "Failed to load " + exampleMetaPath, e);
}

serviceNodeCache = nodeCache;
normalizedServiceKeyMap = normalizedMap;
}

/**
* Builds normalized mapping for a service key (e.g., "medical-imaging" -> "medicalimaging").
*/
private static void buildNormalizedMapping(String serviceKey, Map<String, String> normalizedMap) {
String normalizedKey = serviceKey.replace("-", "").toLowerCase(Locale.ROOT);
normalizedMap.put(normalizedKey, serviceKey);
}

/**
* Finds the URL for a specific operation ID within a service node.
*/
private static String findOperationUrl(JsonNode serviceNode, String targetExampleId) {
JsonNode examplesNode = serviceNode.get("examples");
if (examplesNode != null && examplesNode.isArray()) {
for (JsonNode example : examplesNode) {
JsonNode idNode = example.get("id");
JsonNode urlNode = example.get("url");

if (idNode != null && urlNode != null) {
String id = idNode.asText();
if (targetExampleId.equals(id)) {
return urlNode.asText();
}
}
}
}
return null;
}

/**
* Gets all code examples for a specific service.
*
* @param metadata the service metadata containing service name information
* @param exampleMetaPath the path to the example metadata JSON file
* @return a list of examples for the service
*/
public static List<ExampleData> getServiceCodeExamples(Metadata metadata, String exampleMetaPath) {
List<ExampleData> examples = new ArrayList<>();

try {
String normalizedServiceName = metadata.getServiceName().toLowerCase(Locale.ROOT);
Map<String, String> normalizedMap = getNormalizedServiceKeyMap(exampleMetaPath);
String actualServiceKey = normalizedMap.get(normalizedServiceName);

if (actualServiceKey != null) {
JsonNode serviceNode = getServiceNode(actualServiceKey, exampleMetaPath);
if (serviceNode != null) {
examples = parseServiceExamples(serviceNode);
}
}
} catch (Exception e) {
log.debug(() -> "Failed to load examples for " + metadata.getServiceName(), e);
}

return examples;
}

/**
* Parses examples from a service node in the JSON.
*/
private static List<ExampleData> parseServiceExamples(JsonNode serviceNode) {
List<ExampleData> examples = new ArrayList<>();
JsonNode examplesNode = serviceNode.get("examples");

if (examplesNode != null && examplesNode.isArray()) {
for (JsonNode example : examplesNode) {
JsonNode idNode = example.get("id");
JsonNode titleNode = example.get("title");
JsonNode categoryNode = example.get("category");
JsonNode urlNode = example.get("url");

if (idNode != null && titleNode != null && urlNode != null) {
String id = idNode.asText();
String title = titleNode.asText();
String category = categoryNode != null ? categoryNode.asText() : "Api";
String url = urlNode.asText();

if (!id.isEmpty() && !title.isEmpty() && !url.isEmpty()) {
examples.add(new ExampleData(id, title, category, url));
}
}
}
}

return examples;
}

public static final class ExampleData {
private final String id;
private final String title;
private final String category;
private final String url;

public ExampleData(String id, String title, String category, String url) {
this.id = id;
this.title = title;
this.category = category;
this.url = url;
}

public String getId() {
return id;
}

public String getTitle() {
return title;
}

public String getCategory() {
return category;
}

public String getUrl() {
return url;
}
}
}
Loading
Loading