Skip to content

Commit 35e541b

Browse files
committed
Add code examples to package summary pages
1 parent dd0c522 commit 35e541b

File tree

3 files changed

+212
-31
lines changed

3 files changed

+212
-31
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/docs/OperationDocProvider.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ String getDocs() {
8888
docBuilder.see(crosslink);
8989
}
9090

91-
String codeExampleLink = DocumentationUtils.createLinkToCodeExample(model.getMetadata(), opModel.getOperationName());
91+
String exampleMetaPath = "software/amazon/awssdk/codegen/example-meta.json";
92+
String codeExampleLink = DocumentationUtils.createLinkToCodeExample(model.getMetadata(),
93+
opModel.getOperationName(),
94+
exampleMetaPath);
9295
if (!codeExampleLink.isEmpty()) {
9396
docBuilder.see(codeExampleLink);
9497
}

codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/PackageInfoGeneratorTasks.java

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717

1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.Map;
2021
import software.amazon.awssdk.codegen.emitters.GeneratorTask;
2122
import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
2223
import software.amazon.awssdk.codegen.emitters.SimpleGeneratorTask;
24+
import software.amazon.awssdk.codegen.internal.DocumentationUtils;
2325
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
2426

2527
/**
@@ -38,17 +40,102 @@ public final class PackageInfoGeneratorTasks extends BaseGeneratorTasks {
3840
@Override
3941
protected List<GeneratorTask> createTasks() throws Exception {
4042
Metadata metadata = model.getMetadata();
43+
44+
String baseDocumentation = metadata.getDocumentation();
45+
46+
String codeExamples = getCodeExamples(metadata);
47+
4148
String packageInfoContents =
4249
String.format("/**%n"
4350
+ " * %s%n"
51+
+ (codeExamples.isEmpty() ? "" : " *%n * %s%n")
4452
+ "*/%n"
4553
+ "package %s;",
46-
metadata.getDocumentation(),
54+
baseDocumentation,
55+
codeExamples,
4756
metadata.getFullClientPackageName());
4857
return Collections.singletonList(new SimpleGeneratorTask(baseDirectory,
4958
"package-info.java",
5059
model.getFileHeader(),
5160
() -> packageInfoContents));
5261
}
5362

63+
String getCodeExamples(Metadata metadata) {
64+
String exampleMetaPath = "software/amazon/awssdk/codegen/example-meta.json";
65+
List<DocumentationUtils.ExampleData> examples =
66+
DocumentationUtils.getServiceCodeExamples(metadata, exampleMetaPath);
67+
68+
if (examples.isEmpty()) {
69+
return "";
70+
}
71+
72+
String codeExamplesJavadoc = generateCodeExamplesJavadoc(examples);
73+
74+
StringBuilder result = new StringBuilder();
75+
String[] lines = codeExamplesJavadoc.split("\n");
76+
for (String line : lines) {
77+
if (!line.trim().isEmpty()) {
78+
result.append(line);
79+
if (!line.equals(lines[lines.length - 1])) {
80+
result.append(System.lineSeparator()).append(" * ");
81+
}
82+
}
83+
}
84+
85+
return result.toString();
86+
}
87+
88+
89+
private String generateCodeExamplesJavadoc(List<DocumentationUtils.ExampleData> examples) {
90+
Map<String, List<DocumentationUtils.ExampleData>> categorizedExamples = new java.util.LinkedHashMap<>();
91+
for (DocumentationUtils.ExampleData example : examples) {
92+
categorizedExamples.computeIfAbsent(example.getCategory(), k -> new java.util.ArrayList<>()).add(example);
93+
}
94+
95+
StringBuilder javadoc = new StringBuilder();
96+
javadoc.append("<h3>Code Examples</h3>").append("\n");
97+
javadoc.append("<p>The following code examples show how to use this service with the AWS SDK for Java v2:</p>")
98+
.append("\n");
99+
100+
Map<String, String> categoryMapping = new java.util.LinkedHashMap<>();
101+
categoryMapping.put("Hello", "Getting Started");
102+
categoryMapping.put("Api", "API Actions");
103+
categoryMapping.put("Basics", "Basics");
104+
categoryMapping.put("Scenarios", "Scenarios");
105+
categoryMapping.put("Serverless examples", "Serverless Examples");
106+
107+
for (Map.Entry<String, String> entry : categoryMapping.entrySet()) {
108+
String category = entry.getKey();
109+
String displayName = entry.getValue();
110+
List<DocumentationUtils.ExampleData> categoryExamples = categorizedExamples.get(category);
111+
if (categoryExamples != null && !categoryExamples.isEmpty()) {
112+
appendCategorySection(javadoc, displayName, categoryExamples);
113+
}
114+
}
115+
116+
for (Map.Entry<String, List<DocumentationUtils.ExampleData>> entry : categorizedExamples.entrySet()) {
117+
String category = entry.getKey();
118+
if (!categoryMapping.containsKey(category)) {
119+
List<DocumentationUtils.ExampleData> categoryExamples = entry.getValue();
120+
if (!categoryExamples.isEmpty()) {
121+
appendCategorySection(javadoc, category, categoryExamples);
122+
}
123+
}
124+
}
125+
126+
return javadoc.toString();
127+
}
128+
129+
private void appendCategorySection(StringBuilder javadoc, String displayName,
130+
List<DocumentationUtils.ExampleData> categoryExamples) {
131+
javadoc.append("<h4>").append(displayName).append("</h4>").append("\n");
132+
javadoc.append("<ul>").append("\n");
133+
134+
for (DocumentationUtils.ExampleData example : categoryExamples) {
135+
javadoc.append("<li><a href=\"").append(example.getUrl()).append("\" target=\"_top\">")
136+
.append(example.getTitle()).append("</a></li>").append("\n");
137+
}
138+
javadoc.append("</ul>").append("\n");
139+
}
140+
54141
}

codegen/src/main/java/software/amazon/awssdk/codegen/internal/DocumentationUtils.java

Lines changed: 120 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import com.fasterxml.jackson.databind.ObjectMapper;
2525
import java.io.IOException;
2626
import java.io.InputStream;
27+
import java.util.ArrayList;
2728
import java.util.Arrays;
2829
import java.util.HashMap;
2930
import java.util.HashSet;
31+
import java.util.List;
3032
import java.util.Locale;
3133
import java.util.Map;
3234
import java.util.Set;
@@ -65,7 +67,7 @@ public final class DocumentationUtils {
6567
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
6668

6769
private static final Logger log = Logger.loggerFor(DocumentationUtils.class);
68-
private static Map<String, String> exampleUrlMap;
70+
private static Map<String, JsonNode> serviceNodeCache;
6971
private static Map<String, String> normalizedServiceKeyMap;
7072

7173
private DocumentationUtils() {
@@ -158,23 +160,24 @@ public static String createLinkToServiceDocumentation(Metadata metadata, ShapeMo
158160
*
159161
* @param metadata the service metadata containing service name information
160162
* @param operationName the name of the operation to find an example for
163+
* @param exampleMetaPath the path to the example metadata JSON file
161164
* @return a '@see also' HTML link to the code example, or empty string if no example found
162165
*/
163-
public static String createLinkToCodeExample(Metadata metadata, String operationName) {
166+
public static String createLinkToCodeExample(Metadata metadata, String operationName, String exampleMetaPath) {
164167
try {
165168
String normalizedServiceName = metadata.getServiceName().toLowerCase(Locale.ROOT);
166-
167-
Map<String, String> normalizedMap = getNormalizedServiceKeyMap();
169+
Map<String, String> normalizedMap = getNormalizedServiceKeyMap(exampleMetaPath);
168170
String actualServiceKey = normalizedMap.get(normalizedServiceName);
169171

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

173-
Map<String, String> urlMap = getExampleUrlMap();
174-
String url = urlMap.get(targetExampleId);
175-
176-
if (url != null) {
177-
return String.format("<a href=\"%s\" target=\"_top\">Code Example</a>", url);
176+
if (serviceNode != null) {
177+
String url = findOperationUrl(serviceNode, targetExampleId);
178+
if (url != null) {
179+
return String.format("<a href=\"%s\" target=\"_top\">Code Example</a>", url);
180+
}
178181
}
179182
}
180183

@@ -225,54 +228,54 @@ public static String defaultExistenceCheck() {
225228

226229

227230
/**
228-
* Gets the cached example URL map for fast operation ID -> URL lookups.
231+
* Gets the cached service node
229232
*/
230-
private static Map<String, String> getExampleUrlMap() {
231-
if (exampleUrlMap == null) {
232-
buildExampleMaps();
233+
private static JsonNode getServiceNode(String serviceKey, String exampleMetaPath) {
234+
if (serviceNodeCache == null) {
235+
buildServiceCache(exampleMetaPath);
233236
}
234-
return exampleUrlMap;
237+
return serviceNodeCache.get(serviceKey);
235238
}
236239

237240
/**
238241
* Gets the cached normalized service key map for service name matching.
239242
*/
240-
private static Map<String, String> getNormalizedServiceKeyMap() {
243+
private static Map<String, String> getNormalizedServiceKeyMap(String exampleMetaPath) {
241244
if (normalizedServiceKeyMap == null) {
242-
buildExampleMaps();
245+
buildServiceCache(exampleMetaPath);
243246
}
244247
return normalizedServiceKeyMap;
245248
}
246249

247250
/**
248-
* Builds both the URL lookup map and normalized service key mapping from example-meta.json.
251+
* Builds the service node cache and normalized service key mapping from the specified example metadata file.
249252
*/
250-
private static void buildExampleMaps() {
251-
Map<String, String> urlMap = new HashMap<>();
253+
private static void buildServiceCache(String exampleMetaPath) {
254+
Map<String, JsonNode> nodeCache = new HashMap<>();
252255
Map<String, String> normalizedMap = new HashMap<>();
253256

254257
try (InputStream inputStream = DocumentationUtils.class.getClassLoader()
255-
.getResourceAsStream("software/amazon/awssdk/codegen/example-meta.json")) {
256-
258+
.getResourceAsStream(exampleMetaPath)) {
259+
257260
if (inputStream == null) {
258-
log.debug(() -> "example-meta.json not found in classpath");
261+
log.debug(() -> exampleMetaPath + " not found in classpath");
259262
} else {
260263
JsonNode root = OBJECT_MAPPER.readTree(inputStream);
261264
JsonNode servicesNode = root.get("services");
262265

263266
if (servicesNode != null) {
264267
servicesNode.fieldNames().forEachRemaining(serviceKey -> {
265268
buildNormalizedMapping(serviceKey, normalizedMap);
266-
buildUrlMappingForService(servicesNode.get(serviceKey), urlMap);
269+
nodeCache.put(serviceKey, servicesNode.get(serviceKey));
267270
});
268271
}
269272
}
270273

271274
} catch (IOException e) {
272-
log.warn(() -> "Failed to load example-meta.json", e);
275+
log.warn(() -> "Failed to load " + exampleMetaPath, e);
273276
}
274277

275-
exampleUrlMap = urlMap;
278+
serviceNodeCache = nodeCache;
276279
normalizedServiceKeyMap = normalizedMap;
277280
}
278281

@@ -285,9 +288,9 @@ private static void buildNormalizedMapping(String serviceKey, Map<String, String
285288
}
286289

287290
/**
288-
* Builds URL mappings for all examples in a service.
291+
* Finds the URL for a specific operation ID within a service node.
289292
*/
290-
private static void buildUrlMappingForService(JsonNode serviceNode, Map<String, String> urlMap) {
293+
private static String findOperationUrl(JsonNode serviceNode, String targetExampleId) {
291294
JsonNode examplesNode = serviceNode.get("examples");
292295
if (examplesNode != null && examplesNode.isArray()) {
293296
for (JsonNode example : examplesNode) {
@@ -296,12 +299,100 @@ private static void buildUrlMappingForService(JsonNode serviceNode, Map<String,
296299

297300
if (idNode != null && urlNode != null) {
298301
String id = idNode.asText();
302+
if (targetExampleId.equals(id)) {
303+
return urlNode.asText();
304+
}
305+
}
306+
}
307+
}
308+
return null;
309+
}
310+
311+
/**
312+
* Gets all code examples for a specific service.
313+
*
314+
* @param metadata the service metadata containing service name information
315+
* @param exampleMetaPath the path to the example metadata JSON file
316+
* @return a list of examples for the service
317+
*/
318+
public static List<ExampleData> getServiceCodeExamples(Metadata metadata, String exampleMetaPath) {
319+
List<ExampleData> examples = new ArrayList<>();
320+
321+
try {
322+
String normalizedServiceName = metadata.getServiceName().toLowerCase(Locale.ROOT);
323+
Map<String, String> normalizedMap = getNormalizedServiceKeyMap(exampleMetaPath);
324+
String actualServiceKey = normalizedMap.get(normalizedServiceName);
325+
326+
if (actualServiceKey != null) {
327+
JsonNode serviceNode = getServiceNode(actualServiceKey, exampleMetaPath);
328+
if (serviceNode != null) {
329+
examples = parseServiceExamples(serviceNode);
330+
}
331+
}
332+
} catch (Exception e) {
333+
log.debug(() -> "Failed to load examples for " + metadata.getServiceName(), e);
334+
}
335+
336+
return examples;
337+
}
338+
339+
/**
340+
* Parses examples from a service node in the JSON.
341+
*/
342+
private static List<ExampleData> parseServiceExamples(JsonNode serviceNode) {
343+
List<ExampleData> examples = new ArrayList<>();
344+
JsonNode examplesNode = serviceNode.get("examples");
345+
346+
if (examplesNode != null && examplesNode.isArray()) {
347+
for (JsonNode example : examplesNode) {
348+
JsonNode idNode = example.get("id");
349+
JsonNode titleNode = example.get("title");
350+
JsonNode categoryNode = example.get("category");
351+
JsonNode urlNode = example.get("url");
352+
353+
if (idNode != null && titleNode != null && urlNode != null) {
354+
String id = idNode.asText();
355+
String title = titleNode.asText();
356+
String category = categoryNode != null ? categoryNode.asText() : "Api";
299357
String url = urlNode.asText();
300-
if (!id.isEmpty() && !url.isEmpty()) {
301-
urlMap.put(id, url);
358+
359+
if (!id.isEmpty() && !title.isEmpty() && !url.isEmpty()) {
360+
examples.add(new ExampleData(id, title, category, url));
302361
}
303362
}
304363
}
305364
}
365+
366+
return examples;
367+
}
368+
369+
public static final class ExampleData {
370+
private final String id;
371+
private final String title;
372+
private final String category;
373+
private final String url;
374+
375+
public ExampleData(String id, String title, String category, String url) {
376+
this.id = id;
377+
this.title = title;
378+
this.category = category;
379+
this.url = url;
380+
}
381+
382+
public String getId() {
383+
return id;
384+
}
385+
386+
public String getTitle() {
387+
return title;
388+
}
389+
390+
public String getCategory() {
391+
return category;
392+
}
393+
394+
public String getUrl() {
395+
return url;
396+
}
306397
}
307398
}

0 commit comments

Comments
 (0)