Skip to content

Commit cd456d2

Browse files
authored
Add Gradle Task to Parse V2 supported-configurations.json Format (#10060)
* adding gradle task to allow parsing of v2 format * updating comments * respond to PR comments * adding reverse mapping for property key * respond to PR comments * pr comments
1 parent 6bbcd61 commit cd456d2

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package datadog.gradle.plugin.config
2+
3+
import org.gradle.api.DefaultTask
4+
import org.gradle.kotlin.dsl.property
5+
import org.gradle.api.model.ObjectFactory
6+
import org.gradle.api.tasks.Input
7+
import org.gradle.api.tasks.InputFile
8+
import org.gradle.api.tasks.OutputDirectory
9+
import org.gradle.api.tasks.TaskAction
10+
import com.fasterxml.jackson.core.type.TypeReference
11+
import com.fasterxml.jackson.databind.ObjectMapper
12+
import org.gradle.api.tasks.CacheableTask
13+
import org.gradle.api.tasks.PathSensitive
14+
import org.gradle.api.tasks.PathSensitivity
15+
import java.io.File
16+
import java.io.FileInputStream
17+
import java.io.PrintWriter
18+
import javax.inject.Inject
19+
20+
@CacheableTask
21+
abstract class ParseV2SupportedConfigurationsTask @Inject constructor(
22+
private val objects: ObjectFactory
23+
) : DefaultTask() {
24+
@InputFile
25+
@PathSensitive(PathSensitivity.NONE)
26+
val jsonFile = objects.fileProperty()
27+
28+
@get:OutputDirectory
29+
val destinationDirectory = objects.directoryProperty()
30+
31+
@Input
32+
val className = objects.property<String>()
33+
34+
@TaskAction
35+
fun generate() {
36+
val input = jsonFile.get().asFile
37+
val outputDir = destinationDirectory.get().asFile
38+
val finalClassName = className.get()
39+
outputDir.mkdirs()
40+
41+
// Read JSON (directly from the file, not classpath)
42+
val mapper = ObjectMapper()
43+
val fileData: Map<String, Any?> = FileInputStream(input).use { inStream ->
44+
mapper.readValue(inStream, object : TypeReference<Map<String, Any?>>() {})
45+
}
46+
47+
// Fetch top-level keys of JSON file
48+
@Suppress("UNCHECKED_CAST")
49+
val supportedRaw = fileData["supportedConfigurations"] as Map<String, List<Map<String, Any?>>>
50+
@Suppress("UNCHECKED_CAST")
51+
val deprecated = (fileData["deprecations"] as? Map<String, String>) ?: emptyMap()
52+
53+
// Parse supportedConfigurations key to into a V2 format
54+
val supported: Map<String, List<SupportedConfigurationItem>> = supportedRaw.mapValues { (_, configList) ->
55+
configList.map { configMap ->
56+
SupportedConfigurationItem(
57+
configMap["version"] as? String,
58+
configMap["type"] as? String,
59+
configMap["default"] as? String,
60+
(configMap["aliases"] as? List<String>) ?: emptyList(),
61+
(configMap["propertyKeys"] as? List<String>) ?: emptyList()
62+
)
63+
}
64+
}
65+
66+
// Generate top-level mapping from config -> list of aliases and reverse alias mapping from alias -> top-level config
67+
// Note: This top-level alias mapping will be deprecated once Config Registry is mature enough to understand which version of a config a customer is using
68+
val aliases: Map<String, List<String>> = supported.mapValues { (_, configList) ->
69+
configList.flatMap { it.aliases }.distinct()
70+
}
71+
72+
val aliasMapping = mutableMapOf<String, String>()
73+
for ((canonical, alist) in aliases) {
74+
for (alias in alist) aliasMapping[alias] = canonical
75+
}
76+
77+
val reversePropertyKeysMap: Map<String, String> = supported.flatMap { (canonical, configList) ->
78+
configList.flatMap { config ->
79+
config.propertyKeys.map { propertyKey -> propertyKey to canonical }
80+
}
81+
}.toMap()
82+
83+
// Build the output .java path from the fully-qualified class name
84+
val pkgName = finalClassName.substringBeforeLast('.', "")
85+
val pkgPath = pkgName.replace('.', File.separatorChar)
86+
val simpleName = finalClassName.substringAfterLast('.')
87+
val pkgDir = if (pkgPath.isEmpty()) outputDir else File(outputDir, pkgPath).also { it.mkdirs() }
88+
val generatedFile = File(pkgDir, "$simpleName.java").absolutePath
89+
90+
// Call your existing generator (same signature as in your Java code)
91+
generateJavaFile(
92+
generatedFile,
93+
simpleName,
94+
pkgName,
95+
supported,
96+
aliases,
97+
aliasMapping,
98+
deprecated,
99+
reversePropertyKeysMap
100+
)
101+
}
102+
103+
private fun generateJavaFile(
104+
outputPath: String,
105+
className: String,
106+
packageName: String,
107+
supported: Map<String, List<SupportedConfigurationItem>>,
108+
aliases: Map<String, List<String>>,
109+
aliasMapping: Map<String, String>,
110+
deprecated: Map<String, String>,
111+
reversePropertyKeysMap: Map<String, String>
112+
) {
113+
val outFile = File(outputPath)
114+
outFile.parentFile?.mkdirs()
115+
116+
PrintWriter(outFile).use { out ->
117+
out.println("package $packageName;")
118+
out.println()
119+
out.println("import java.util.*;")
120+
out.println()
121+
out.println("public final class $className {")
122+
out.println()
123+
out.println(" public static final Map<String, List<SupportedConfiguration>> SUPPORTED;")
124+
out.println()
125+
out.println(" public static final Map<String, List<String>> ALIASES;")
126+
out.println()
127+
out.println(" public static final Map<String, String> ALIAS_MAPPING;")
128+
out.println()
129+
out.println(" public static final Map<String, String> DEPRECATED;")
130+
out.println()
131+
out.println(" public static final Map<String, String> REVERSE_PROPERTY_KEYS_MAP;")
132+
out.println()
133+
out.println(" static {")
134+
out.println()
135+
136+
// SUPPORTED
137+
out.println(" Map<String, List<SupportedConfiguration>> supportedMap = new HashMap<>();")
138+
for ((key, configList) in supported.toSortedMap()) {
139+
out.print(" supportedMap.put(\"${esc(key)}\", Collections.unmodifiableList(Arrays.asList(")
140+
val configIter = configList.iterator()
141+
while (configIter.hasNext()) {
142+
val config = configIter.next()
143+
out.print("new SupportedConfiguration(")
144+
out.print("${escNullableString(config.version)}, ")
145+
out.print("${escNullableString(config.type)}, ")
146+
out.print("${escNullableString(config.default)}, ")
147+
out.print("Arrays.asList(${quoteList(config.aliases)}), ")
148+
out.print("Arrays.asList(${quoteList(config.propertyKeys)})")
149+
out.print(")")
150+
if (configIter.hasNext()) out.print(", ")
151+
}
152+
out.println(")));")
153+
}
154+
out.println(" SUPPORTED = Collections.unmodifiableMap(supportedMap);")
155+
out.println()
156+
157+
// ALIASES
158+
out.println(" // Note: This top-level alias mapping will be deprecated once Config Registry is mature enough to understand which version of a config a customer is using")
159+
out.println(" Map<String, List<String>> aliasesMap = new HashMap<>();")
160+
for ((canonical, list) in aliases.toSortedMap()) {
161+
out.printf(
162+
" aliasesMap.put(\"%s\", Collections.unmodifiableList(Arrays.asList(%s)));\n",
163+
esc(canonical),
164+
quoteList(list)
165+
)
166+
}
167+
out.println(" ALIASES = Collections.unmodifiableMap(aliasesMap);")
168+
out.println()
169+
170+
// ALIAS_MAPPING
171+
out.println(" Map<String, String> aliasMappingMap = new HashMap<>();")
172+
for ((alias, target) in aliasMapping.toSortedMap()) {
173+
out.printf(" aliasMappingMap.put(\"%s\", \"%s\");\n", esc(alias), esc(target))
174+
}
175+
out.println(" ALIAS_MAPPING = Collections.unmodifiableMap(aliasMappingMap);")
176+
out.println()
177+
178+
// DEPRECATED
179+
out.println(" Map<String, String> deprecatedMap = new HashMap<>();")
180+
for ((oldKey, note) in deprecated.toSortedMap()) {
181+
out.printf(" deprecatedMap.put(\"%s\", \"%s\");\n", esc(oldKey), esc(note))
182+
}
183+
out.println(" DEPRECATED = Collections.unmodifiableMap(deprecatedMap);")
184+
out.println()
185+
186+
// REVERSE_PROPERTY_KEYS_MAP
187+
out.println(" Map<String, String> reversePropertyKeysMapping = new HashMap<>();")
188+
for ((propertyKey, config) in reversePropertyKeysMap.toSortedMap()) {
189+
out.printf(" reversePropertyKeysMapping.put(\"%s\", \"%s\");\n", esc(propertyKey), esc(config))
190+
}
191+
out.println(" REVERSE_PROPERTY_KEYS_MAP = Collections.unmodifiableMap(reversePropertyKeysMapping);")
192+
out.println()
193+
194+
out.println(" }")
195+
out.println("}")
196+
}
197+
}
198+
199+
private fun quoteList(list: List<String>): String =
200+
list.joinToString(", ") { "\"${esc(it)}\"" }
201+
202+
private fun esc(s: String): String =
203+
s.replace("\\", "\\\\").replace("\"", "\\\"")
204+
205+
private fun escNullableString(s: String?): String =
206+
if (s == null) "null" else "\"${esc(s)}\""
207+
}
208+
209+
private data class SupportedConfigurationItem(
210+
val version: String?,
211+
val type: String?,
212+
val default: String?,
213+
val aliases: List<String>,
214+
val propertyKeys: List<String>
215+
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package datadog.trace.config.inversion;
2+
3+
import java.util.List;
4+
5+
public class SupportedConfiguration {
6+
private final String version;
7+
private final String type;
8+
private final String defaultValue;
9+
private final List<String> aliases;
10+
private final List<String> propertyKeys;
11+
12+
public SupportedConfiguration(
13+
String version,
14+
String type,
15+
String defaultValue,
16+
List<String> aliases,
17+
List<String> propertyKeys) {
18+
this.version = version;
19+
this.type = type;
20+
this.defaultValue = defaultValue;
21+
this.aliases = aliases;
22+
this.propertyKeys = propertyKeys;
23+
}
24+
25+
public String version() {
26+
return version;
27+
}
28+
29+
public String type() {
30+
return type;
31+
}
32+
33+
public String defaultValue() {
34+
return defaultValue;
35+
}
36+
37+
public List<String> aliases() {
38+
return aliases;
39+
}
40+
41+
public List<String> propertyKeys() {
42+
return propertyKeys;
43+
}
44+
}

0 commit comments

Comments
 (0)