diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java index 46a8915571b798..57198af695eeea 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepository.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -56,7 +57,7 @@ public static FetchResult fetchRecipes(MessageWriter log, MavenArtifactResolver final Artifact artifact = artifactResolver.resolve(DependencyUtils.toArtifact(gav)).getArtifact(); final ResourceLoader resourceLoader = ResourceLoaders.resolveFileResourceLoader( artifact.getFile()); - final List recipes = fetchRecipesAsList(resourceLoader, "quarkus-updates", recipeDirectoryNames); + final Map recipes = fetchUpdateRecipes(resourceLoader, "quarkus-updates", recipeDirectoryNames); final Properties props = resourceLoader.loadResourceAsPath("quarkus-updates/", p -> { final Properties properties = new Properties(); final Path propPath = p.resolve("recipes.properties"); @@ -78,7 +79,7 @@ public static FetchResult fetchRecipes(MessageWriter log, MavenArtifactResolver buildTool, propRewritePluginVersion)); return new FetchResult(artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(), - recipes, propRewritePluginVersion); + new ArrayList<>(recipes.values()), propRewritePluginVersion); } catch (BootstrapMavenException e) { throw new RuntimeException("Failed to resolve artifact: " + gav, e); } catch (IOException e) { @@ -133,38 +134,55 @@ static boolean shouldApplyRecipe(String recipeFileName, String currentVersion, S return currentAVersion.compareTo(recipeAVersion) < 0 && targetAVersion.compareTo(recipeAVersion) >= 0; } - static List fetchRecipesAsList(ResourceLoader resourceLoader, String location, + static Map fetchUpdateRecipes(ResourceLoader resourceLoader, String location, Map recipeDirectoryNames) throws IOException { return resourceLoader.loadResourceAsPath(location, path -> { try (final Stream pathStream = Files.walk(path)) { return pathStream .filter(Files::isDirectory) - .flatMap(dir -> { - String key = toKey(path.relativize(dir).toString()); - String versions[] = recipeDirectoryNames.get(key); - if (versions != null && versions.length != 0) { - try { - Stream recipePath = Files.walk(dir); - return recipePath - .filter(p -> p.getFileName().toString().matches("^\\d\\H+.ya?ml$")) - .filter(p -> shouldApplyRecipe(p.getFileName().toString(), - versions[0], versions[1])) - .map(p -> { - try { - return new String(Files.readAllBytes(p)); - } catch (IOException e) { - throw new RuntimeException("Error reading file: " + p, e); - } - }) - .onClose(() -> recipePath.close()); - } catch (IOException e) { - throw new RuntimeException("Error traversing directory: " + dir, e); - } - } - return null; - - }).filter(Objects::nonNull).collect(Collectors.toList()); + .flatMap(dir -> applyStartsWith(toKey(path, dir), recipeDirectoryNames).stream() + .flatMap(key -> { + String versions[] = recipeDirectoryNames.get(key); + if (versions != null && versions.length != 0) { + try { + Stream recipePath = Files.walk(dir); + return recipePath + .filter(p -> p.getFileName().toString() + .matches("^\\d\\H+.ya?ml$")) + .filter(p -> shouldApplyRecipe(p.getFileName().toString(), + versions[0], versions[1])) + .map(p -> { + try { + return new String[] { p.toString(), + new String(Files.readAllBytes(p)) }; + } catch (IOException e) { + throw new RuntimeException("Error reading file: " + p, + e); + } + }) + .onClose(() -> recipePath.close()); + } catch (IOException e) { + throw new RuntimeException("Error traversing directory: " + dir, e); + } + } + return null; + })) + .filter(Objects::nonNull) + //results are collected to the map, because there could be duplicated matches in case of wildcard matching + .collect(Collectors.toMap( + sa -> sa[0], + sa -> sa[1], + (v1, v2) -> { + //Recipe with the same path already loaded. This can happen because of wildcards + //in case the content differs (which can not happen in the current impl), + //content is amended + if (!v1.equals(v2)) { + return v1 + "\n" + v2; + } + return v1; + }, + LinkedHashMap::new)); } catch (IOException e) { throw new RuntimeException("Error traversing base directory", e); } @@ -177,9 +195,20 @@ private static String toKey(ExtensionUpdateInfo dep) { dep.getCurrentDep().getArtifact().getArtifactId()); } - static String toKey(String directory) { - return directory + static String toKey(Path parentDir, Path recipeDir) { + var _path = parentDir.relativize(recipeDir).toString(); + return _path .replaceAll("(^[/\\\\])|([/\\\\]$)", "") .replaceAll("[/\\\\]", ":"); } + + static List applyStartsWith(String key, Map recipeDirectoryNames) { + //list for all keys, that matches dir (could be more items in case of wildcard at the end + List matchedRecipeKeys; + //Current implementation detects whether key starts with an existing recipe folder + return recipeDirectoryNames.keySet().stream() + .filter(k -> k.startsWith(key)) + .collect(Collectors.toList()); + } + } diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java index 529aa24a6ef31b..aa1a7ee68471cf 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/update/rewrite/QuarkusUpdatesRepositoryTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; +import java.nio.file.Path; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -30,9 +31,64 @@ void testShouldLoadRecipesFromTheDirectory() throws IOException { recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" }); recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-core", new String[] { "2.7", "3.0" }); ClassPathResourceLoader resourceLoader = new ClassPathResourceLoader(); - List recipes = fetchRecipesAsList(resourceLoader, "dir/quarkus-update", recipeDirectoryNames); + Map recipes = fetchUpdateRecipes(resourceLoader, "dir/quarkus-update", recipeDirectoryNames); int noOfRecipes = recipes.size(); assertEquals(3, noOfRecipes); + } + + @Test + void testShouldLoadRecipesFromTheDirectoryWithWildcard() throws IOException { + Map recipeDirectoryNames = new LinkedHashMap<>(); + recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-file", new String[] { "2.7", "3.0" }); + ClassPathResourceLoader resourceLoader = new ClassPathResourceLoader(); + Map recipes = fetchUpdateRecipes(resourceLoader, "dir/quarkus-update", recipeDirectoryNames); + int noOfRecipes = recipes.size(); + assertEquals(3, noOfRecipes); + } + + @Test + void testShouldLoadDuplicatedRecipesFromTheDirectoryWithWildcard() throws IOException { + Map recipeDirectoryNames = new LinkedHashMap<>(); + recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-file", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-ftp", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-fhir", new String[] { "2.7", "3.1" }); + ClassPathResourceLoader resourceLoader = new ClassPathResourceLoader(); + Map recipes = fetchUpdateRecipes(resourceLoader, "dir/quarkus-update", recipeDirectoryNames); + int noOfRecipes = recipes.size(); + assertEquals(3, noOfRecipes); + } + + @Test + void testToKey() { + String key = toKey(Path.of("/home/app"), + Path.of("/home/app/target/classes/quarkus-updates/org.apache.camel.quarkus.camel-quarkus-*")); + assertEquals("target:classes:quarkus-updates:org.apache.camel.quarkus.camel-quarkus-*", key); + + key = toKey(Path.of("/home/second-app"), + Path.of("/home/app/target/classes/quarkus-updates/org.apache.camel.quarkus.camel-quarkus-*")); + assertEquals("..:app:target:classes:quarkus-updates:org.apache.camel.quarkus.camel-quarkus-*", key); + + key = toKey(Path.of("C:\\a\\d\\"), Path.of("C:\\a\\b\\quarkus-updates\\org.apache.camel.quarkus.camel-quarkus-*\\")); + assertEquals("..:C::a:b:quarkus-updates:org.apache.camel.quarkus.camel-quarkus-*", key); + } + + @Test + void testApplyStartsWith() { + Map recipeDirectoryNames = new LinkedHashMap<>(); + recipeDirectoryNames.put("core", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-something1", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-file", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-ftp", new String[] { "2.7", "3.1" }); + recipeDirectoryNames.put("org.apache.camel.quarkus:camel-quarkus-fhir", new String[] { "2.7", "3.1" }); + + List matchedKeys = applyStartsWith("org.apache.camel.quarkus:camel-quarkus", recipeDirectoryNames); + assertEquals(3, matchedKeys.size()); + assertTrue(!matchedKeys.contains("org.apache.camel.quarkus:camel-quarkus")); + matchedKeys = applyStartsWith("org.apache.camel.quarkus:camel", recipeDirectoryNames); + assertEquals(4, matchedKeys.size()); + assertTrue(!matchedKeys.contains("org.apache.camel.quarkus:camel")); } } diff --git a/independent-projects/tools/devtools-common/src/test/resources/dir/quarkus-update/org.apache.camel.quarkus/camel-quarkus-core/3.0.yaml b/independent-projects/tools/devtools-common/src/test/resources/dir/quarkus-update/org.apache.camel.quarkus/camel-quarkus/3.0.yaml similarity index 100% rename from independent-projects/tools/devtools-common/src/test/resources/dir/quarkus-update/org.apache.camel.quarkus/camel-quarkus-core/3.0.yaml rename to independent-projects/tools/devtools-common/src/test/resources/dir/quarkus-update/org.apache.camel.quarkus/camel-quarkus/3.0.yaml