@@ -122,6 +122,9 @@ class RecipeMarkdownGenerator : Runnable {
122122
123123 println (" Found ${allRecipeDescriptors.size} descriptor(s)." )
124124
125+ // Detect conflicting paths between io.moderne and org.openrewrite recipes
126+ initializeConflictDetection(allRecipeDescriptors)
127+
125128 val markdownArtifacts = TreeMap <String , MarkdownRecipeArtifact >()
126129 val moderneProprietaryRecipes = TreeMap <String , MutableList <RecipeDescriptor >>()
127130
@@ -239,7 +242,54 @@ class RecipeMarkdownGenerator : Runnable {
239242
240243
241244 companion object {
242- private val SPRING_BOOT_UPGRADE_PATTERN = Regex (" ^(io\\ .moderne|org\\ .openrewrite)\\ .java\\ .spring\\ .boot(\\ d+)\\ .UpgradeSpringBoot_(\\ d+)_(\\ d+)$" )
245+ // Set of base paths that have both io.moderne and org.openrewrite recipes (conflicts)
246+ private var conflictingBasePaths: Set <String > = emptySet()
247+
248+ /* *
249+ * Initialize conflict detection by scanning all recipe descriptors.
250+ * Must be called before any getRecipePath() calls.
251+ */
252+ fun initializeConflictDetection (allDescriptors : Collection <RecipeDescriptor >) {
253+ val moderneBasePaths = mutableSetOf<String >()
254+ val openrewriteBasePaths = mutableSetOf<String >()
255+
256+ for (descriptor in allDescriptors) {
257+ val name = descriptor.name
258+ when {
259+ name.startsWith(" io.moderne" ) -> {
260+ moderneBasePaths.add(getBasePath(name))
261+ }
262+ name.startsWith(" org.openrewrite" ) -> {
263+ openrewriteBasePaths.add(getBasePath(name))
264+ }
265+ }
266+ }
267+
268+ // Find paths that exist in both sets
269+ conflictingBasePaths = moderneBasePaths.intersect(openrewriteBasePaths)
270+ }
271+
272+ /* *
273+ * Compute the base path for a recipe name (without any edition suffix).
274+ * This is used for conflict detection.
275+ */
276+ private fun getBasePath (recipeName : String ): String {
277+ return when {
278+ recipeName.startsWith(" org.openrewrite" ) -> {
279+ if (recipeName.count { it == ' .' } == 2 ) {
280+ " core/" + recipeName.substring(16 ).lowercase(Locale .getDefault())
281+ } else {
282+ recipeName.substring(16 ).replace(" \\ ." .toRegex(), " /" ).lowercase(Locale .getDefault())
283+ }
284+ }
285+ recipeName.startsWith(" io.moderne" ) -> {
286+ recipeName.substring(11 ).replace(" \\ ." .toRegex(), " /" ).lowercase(Locale .getDefault())
287+ }
288+ else -> {
289+ recipeName.replace(" \\ ." .toRegex(), " /" ).lowercase(Locale .getDefault())
290+ }
291+ }
292+ }
243293
244294 /* *
245295 * Call Closable.use() together with apply() to avoid adding two levels of indentation
@@ -254,34 +304,24 @@ class RecipeMarkdownGenerator : Runnable {
254304 // Docusaurus expects that if a file is called "assertj" inside of the folder "assertj" that it's the
255305 // README for said folder. Due to how generic we've made this recipe name, we need to change it for the
256306 // docs so that they parse correctly.
257- fun getRecipePath (recipe : RecipeDescriptor ): String =
307+ fun getRecipePath (recipe : RecipeDescriptor ): String {
308+ // Check for manual overrides first
258309 if (recipePathToDocusaurusRenamedPath.containsKey(recipe.name)) {
259- recipePathToDocusaurusRenamedPath[recipe.name]!!
260- } else if (isSpringBoot34OrHigher(recipe.name)) {
261- // The moderne and community spring boot recipes clashes with one another (deviating since 3.4) so let's make them distinct
262- generateSpringBootUpgradePath(recipe.name)
263- } else if (recipe.name.startsWith(" io.moderne.hibernate." )) {
264- recipe.name
265- .substring(11 )
266- .replace(" \\ ." .toRegex(), " /" )
267- .lowercase(Locale .getDefault()) + " -moderne-edition"
268- } else if (recipe.name.startsWith(" org.openrewrite.hibernate." )) {
269- recipe.name
270- .substring(16 )
271- .replace(" \\ ." .toRegex(), " /" )
272- .lowercase(Locale .getDefault()) + " -community-edition"
273- } else if (recipe.name.startsWith(" org.openrewrite" )) {
274- // If the recipe path only has two periods, it's part of the core recipes and should be adjusted accordingly.
275- if (recipe.name.count { it == ' .' } == 2 ) {
276- " core/" + recipe.name
277- .substring(16 )
278- .lowercase(Locale .getDefault())
279- } else {
280- recipe.name.substring(16 ).replace(" \\ ." .toRegex(), " /" ).lowercase(Locale .getDefault())
310+ return recipePathToDocusaurusRenamedPath[recipe.name]!!
311+ }
312+
313+ val basePath = getBasePath(recipe.name)
314+
315+ // Add edition suffix only if there's a detected conflict
316+ val needsSuffix = conflictingBasePaths.contains(basePath)
317+
318+ return when {
319+ recipe.name.startsWith(" org.openrewrite" ) -> {
320+ if (needsSuffix) basePath + " -community-edition" else basePath
321+ }
322+ recipe.name.startsWith(" io.moderne" ) -> {
323+ if (needsSuffix) basePath + " -moderne-edition" else basePath
281324 }
282- } else if (recipe.name.startsWith(" io.moderne" )) {
283- recipe.name.substring(11 ).replace(" \\ ." .toRegex(), " /" ).lowercase(Locale .getDefault())
284- } else if (
285325 recipe.name.startsWith(" ai.timefold" ) ||
286326 recipe.name.startsWith(" com.google" ) ||
287327 recipe.name.startsWith(" com.oracle" ) ||
@@ -290,39 +330,21 @@ class RecipeMarkdownGenerator : Runnable {
290330 recipe.name.startsWith(" org.apache" ) ||
291331 recipe.name.startsWith(" org.axonframework" ) ||
292332 recipe.name.startsWith(" software.amazon.awssdk" ) ||
293- recipe.name.startsWith(" tech.picnic" )
294- ) {
295- recipe.name.replace(" \\ ." .toRegex(), " /" ).lowercase(Locale .getDefault())
296- } else {
297- throw RuntimeException (" Recipe package unrecognized: ${recipe.name} " )
333+ recipe.name.startsWith(" tech.picnic" ) -> {
334+ basePath
335+ }
336+ else -> {
337+ throw RuntimeException (" Recipe package unrecognized: ${recipe.name} " )
338+ }
298339 }
340+ }
299341
300342 private val recipePathToDocusaurusRenamedPath: Map <String , String > = mapOf (
301343 " org.openrewrite.java.testing.assertj.Assertj" to " java/testing/assertj/assertj-best-practices" ,
302344 " org.openrewrite.java.migrate.javaee7" to " java/migrate/javaee7-recipe" ,
303345 " org.openrewrite.java.migrate.javaee8" to " java/migrate/javaee8-recipe"
304346 )
305347
306- private fun isSpringBoot34OrHigher (recipeName : String ): Boolean {
307- val matchResult = SPRING_BOOT_UPGRADE_PATTERN .find(recipeName) ? : return false
308- val (_, _, upgradeMajor, upgradeMinor) = matchResult.destructured
309- val upgradeMajorInt = upgradeMajor.toInt()
310- val upgradeMinorInt = upgradeMinor.toInt()
311- return upgradeMajorInt > 3 || (upgradeMajorInt == 3 && upgradeMinorInt >= 4 )
312- }
313-
314- private fun generateSpringBootUpgradePath (recipeName : String ): String {
315- val matchResult = SPRING_BOOT_UPGRADE_PATTERN .find(recipeName)
316-
317- return if (matchResult != null ) {
318- val (organization, majorVersion, upgradeMajor, upgradeMinor) = matchResult.destructured
319- val edition = if (organization == " io.moderne" ) " moderne-edition" else " community-edition"
320- " java/spring/boot$majorVersion /upgradespringboot_${upgradeMajor} _$upgradeMinor -$edition "
321- } else {
322- throw RuntimeException (" Invalid Spring Boot upgrade recipe format: $recipeName " )
323- }
324- }
325-
326348 @JvmStatic
327349 fun main (args : Array <String >) {
328350 val exitCode = CommandLine (RecipeMarkdownGenerator ()).execute(* args)
0 commit comments