Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -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