diff --git a/core/builder/pom.xml b/core/builder/pom.xml index 450e83a0ac9ec..1d73a59ecedf5 100644 --- a/core/builder/pom.xml +++ b/core/builder/pom.xml @@ -21,6 +21,10 @@ + + io.quarkus + quarkus-bootstrap-json + org.wildfly.common wildfly-common diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java b/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java index e7c6a45d4b14f..e5f677a86172c 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java @@ -20,8 +20,9 @@ import org.jboss.logging.Logger; -import io.quarkus.builder.Json.JsonArrayBuilder; -import io.quarkus.builder.Json.JsonObjectBuilder; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.Json.JsonArrayBuilder; +import io.quarkus.bootstrap.json.Json.JsonObjectBuilder; import io.quarkus.builder.item.BuildItem; public class BuildMetrics { diff --git a/core/builder/src/main/java/io/quarkus/builder/Json.java b/core/builder/src/main/java/io/quarkus/builder/Json.java index 1d7b425d7e661..13529fed29eda 100644 --- a/core/builder/src/main/java/io/quarkus/builder/Json.java +++ b/core/builder/src/main/java/io/quarkus/builder/Json.java @@ -19,8 +19,11 @@ import io.quarkus.builder.json.JsonValue; /** - * A simple JSON string generator. + * @deprecated since 3.31.0 in favor of io.quarkus.bootstrap.json.Json + * + * A simple JSON string generator. */ +@Deprecated(forRemoval = true) public final class Json { private static final String OBJECT_START = "{"; diff --git a/core/builder/src/main/java/io/quarkus/builder/JsonReader.java b/core/builder/src/main/java/io/quarkus/builder/JsonReader.java index b0fc9f51a01e6..185c33d24d83f 100644 --- a/core/builder/src/main/java/io/quarkus/builder/JsonReader.java +++ b/core/builder/src/main/java/io/quarkus/builder/JsonReader.java @@ -15,9 +15,12 @@ import io.quarkus.builder.json.JsonValue; /** - * A json format reader. - * It follows the ECMA-404 The JSON Data Interchange Standard.. + * @deprecated since 3.31.0 in favor of io.quarkus.bootstrap.json.JsonReader + * + * A json format reader. + * It follows the ECMA-404 The JSON Data Interchange Standard.. */ +@Deprecated(forRemoval = true) public class JsonReader { private final String text; diff --git a/core/builder/src/main/java/io/quarkus/builder/JsonTransform.java b/core/builder/src/main/java/io/quarkus/builder/JsonTransform.java index c6994add0ed69..107a4d1412bc1 100644 --- a/core/builder/src/main/java/io/quarkus/builder/JsonTransform.java +++ b/core/builder/src/main/java/io/quarkus/builder/JsonTransform.java @@ -4,6 +4,10 @@ import io.quarkus.builder.json.JsonValue; +/** + * @deprecated since 3.31.0 in favor of io.quarkus.bootstrap.json.JsonTransformer + */ +@Deprecated(forRemoval = true) @FunctionalInterface public interface JsonTransform { void accept(Json.JsonBuilder builder, JsonValue element); diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonArray.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonArray.java index f88c4f9feb89a..1ef42e050ed52 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonArray.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonArray.java @@ -5,6 +5,10 @@ import io.quarkus.builder.JsonTransform; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonArray} + */ +@Deprecated(forRemoval = true) public final class JsonArray implements JsonMultiValue { private final List value; diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonBoolean.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonBoolean.java index da01cf9aa28cc..09b6f1999aa16 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonBoolean.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonBoolean.java @@ -1,5 +1,9 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonBoolean} + */ +@Deprecated(forRemoval = true) public enum JsonBoolean implements JsonValue { TRUE(true), FALSE(false); diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonDouble.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonDouble.java index 8a567401f829a..127cb94ebbb16 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonDouble.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonDouble.java @@ -1,5 +1,9 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonDouble} + */ +@Deprecated(forRemoval = true) public final class JsonDouble implements JsonNumber { private final double value; diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonInteger.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonInteger.java index 062d613de7411..0981edfaee83d 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonInteger.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonInteger.java @@ -1,5 +1,9 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonInteger} + */ +@Deprecated(forRemoval = true) public final class JsonInteger implements JsonNumber { private final long value; diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonMember.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonMember.java index 1ce4b2a88797c..c5a08c2419c67 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonMember.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonMember.java @@ -1,5 +1,9 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonMember} + */ +@Deprecated(forRemoval = true) public final class JsonMember implements JsonValue { private final JsonString attribute; private final JsonValue value; diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonMultiValue.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonMultiValue.java index 42ce6b88d992c..4f12278bdcbf1 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonMultiValue.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonMultiValue.java @@ -2,6 +2,10 @@ import io.quarkus.builder.JsonTransform; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonMultiValue} + */ +@Deprecated(forRemoval = true) public interface JsonMultiValue extends JsonValue { default void forEach(JsonTransform transform) { transform.accept(null, this); diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonNull.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonNull.java index 13c8d995bc643..44fa0004a1cb7 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonNull.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonNull.java @@ -1,5 +1,9 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonNull} + */ +@Deprecated(forRemoval = true) public enum JsonNull implements JsonValue { INSTANCE; } diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonNumber.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonNumber.java index 2c25838dca80b..7933d03020fe6 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonNumber.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonNumber.java @@ -1,4 +1,8 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonNumber} + */ +@Deprecated(forRemoval = true) public interface JsonNumber extends JsonValue { } diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonObject.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonObject.java index 6718d8687ad60..5dffeb419dd9b 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonObject.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonObject.java @@ -6,6 +6,10 @@ import io.quarkus.builder.JsonTransform; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonObject} + */ +@Deprecated(forRemoval = true) public final class JsonObject implements JsonMultiValue { private final Map value; diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java index 5f13517539e05..d6ccbea5a82ea 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonString.java @@ -2,6 +2,10 @@ import java.util.Objects; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonString} + */ +@Deprecated(forRemoval = true) public final class JsonString implements JsonValue { private final String value; diff --git a/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java b/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java index a990951d5679b..464e6862e4d2d 100644 --- a/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java +++ b/core/builder/src/main/java/io/quarkus/builder/json/JsonValue.java @@ -1,4 +1,8 @@ package io.quarkus.builder.json; +/** + * @deprecated since 3.31.0 in favor of {@link io.quarkus.bootstrap.json.JsonValue} + */ +@Deprecated(forRemoval = true) public interface JsonValue { } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java index 864d8688d8813..62580da87c086 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java @@ -13,11 +13,10 @@ import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.BootstrapGradleException; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.devmode.DependenciesFilter; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.resolver.AppModelResolverException; -import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.bootstrap.utils.BuildToolHelper; import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.SourceDir; @@ -48,8 +47,8 @@ public void accept(CuratedApplication curatedApplication, Map st if (BuildToolHelper.isMavenProject(appClasses)) { appModel = curatedApplication.getApplicationModel(); } else { - appModel = BootstrapUtils - .deserializeQuarkusModel((Path) stringObjectMap.get(BootstrapConstants.SERIALIZED_APP_MODEL)); + appModel = ApplicationModelSerializer + .deserialize((Path) stringObjectMap.get(BootstrapConstants.SERIALIZED_APP_MODEL)); } if (appModel != null) { @@ -64,7 +63,7 @@ public void accept(CuratedApplication curatedApplication, Map st } } } - } catch (AppModelResolverException e) { + } catch (Exception e) { log.error("Failed to load workspace, hot reload will not be available", e); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java index d30fbe29133c6..9a43836afd752 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java @@ -9,11 +9,11 @@ import org.jboss.logging.Logger; -import io.quarkus.builder.JsonReader; -import io.quarkus.builder.json.JsonArray; -import io.quarkus.builder.json.JsonObject; -import io.quarkus.builder.json.JsonString; -import io.quarkus.builder.json.JsonValue; +import io.quarkus.bootstrap.json.JsonArray; +import io.quarkus.bootstrap.json.JsonObject; +import io.quarkus.bootstrap.json.JsonReader; +import io.quarkus.bootstrap.json.JsonString; +import io.quarkus.bootstrap.json.JsonValue; import io.quarkus.deployment.pkg.NativeConfig; import io.smallrye.common.process.ProcessBuilder; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java index 70e744f9b3ec7..1e8cc877d5b37 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageJNIConfigStep.java @@ -9,9 +9,9 @@ import java.util.Map; import java.util.Set; -import io.quarkus.builder.Json; -import io.quarkus.builder.Json.JsonArrayBuilder; -import io.quarkus.builder.Json.JsonObjectBuilder; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.Json.JsonArrayBuilder; +import io.quarkus.bootstrap.json.Json.JsonObjectBuilder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageProxyConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageProxyConfigStep.java index c02bc9dc695b9..eec92364cec07 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageProxyConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageProxyConfigStep.java @@ -5,9 +5,9 @@ import java.nio.charset.StandardCharsets; import java.util.List; -import io.quarkus.builder.Json; -import io.quarkus.builder.Json.JsonArrayBuilder; -import io.quarkus.builder.Json.JsonObjectBuilder; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.Json.JsonArrayBuilder; +import io.quarkus.bootstrap.json.Json.JsonObjectBuilder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java index 9ad81e43a80c4..f321cc2d1673c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java @@ -9,9 +9,9 @@ import java.util.Map; import java.util.Set; -import io.quarkus.builder.Json; -import io.quarkus.builder.Json.JsonArrayBuilder; -import io.quarkus.builder.Json.JsonObjectBuilder; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.Json.JsonArrayBuilder; +import io.quarkus.bootstrap.json.Json.JsonObjectBuilder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageResourceConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageResourceConfigStep.java index 936eb56a03614..3164ba292908f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageResourceConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageResourceConfigStep.java @@ -6,9 +6,9 @@ import java.util.List; import java.util.regex.Pattern; -import io.quarkus.builder.Json; -import io.quarkus.builder.Json.JsonArrayBuilder; -import io.quarkus.builder.Json.JsonObjectBuilder; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.Json.JsonArrayBuilder; +import io.quarkus.bootstrap.json.Json.JsonObjectBuilder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageSerializationConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageSerializationConfigStep.java index da7fd099fff3d..ae4e5a81018f5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageSerializationConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageSerializationConfigStep.java @@ -8,9 +8,9 @@ import java.util.List; import java.util.Set; -import io.quarkus.builder.Json; -import io.quarkus.builder.Json.JsonArrayBuilder; -import io.quarkus.builder.Json.JsonObjectBuilder; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.Json.JsonArrayBuilder; +import io.quarkus.bootstrap.json.Json.JsonObjectBuilder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java index 71dec231f61f8..00dd897aee861 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java @@ -54,8 +54,8 @@ import io.quarkus.bootstrap.model.gradle.impl.ModelParameterImpl; import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.DefaultArtifactSources; -import io.quarkus.bootstrap.workspace.DefaultSourceDir; import io.quarkus.bootstrap.workspace.DefaultWorkspaceModule; +import io.quarkus.bootstrap.workspace.LazySourceDir; import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.bootstrap.workspace.WorkspaceModuleId; @@ -643,7 +643,7 @@ private static void initProjectModule(Project project, WorkspaceModule.Mutable m } final List resources = new ArrayList<>(resourceDirs.size()); for (Map.Entry e : resourceDirs.entrySet()) { - resources.add(new DefaultSourceDir(e.getKey().toPath(), e.getValue(), null)); + resources.add(new LazySourceDir(e.getKey().toPath(), e.getValue(), null)); } module.addArtifactSources(new DefaultArtifactSources(classifier, sourceDirs, resources)); } @@ -698,7 +698,7 @@ private static void configureCompileTask(FileTree sources, DirectoryProperty des // we are looking for the root dirs containing sources if (visitor.getRelativePath().getSegments().length == 1) { final File srcDir = visitor.getFile().getParentFile(); - sourceDirs.add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(), + sourceDirs.add(new LazySourceDir(srcDir.toPath(), destDir.toPath(), findGeneratedSourceDir(destDir, sourceSet), Map.of("compiler", task.getName()))); } diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/ToolingUtils.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/ToolingUtils.java index 01f3882498c2f..d2551ff7ae3ef 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/ToolingUtils.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/ToolingUtils.java @@ -2,9 +2,6 @@ import java.io.File; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; @@ -21,6 +18,7 @@ import org.gradle.internal.composite.IncludedBuildInternal; import org.gradle.internal.composite.IncludedRootBuild; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.model.gradle.ModelParameter; import io.quarkus.bootstrap.model.gradle.impl.ModelParameterImpl; @@ -158,24 +156,16 @@ private static Project findIncludedBuildProject(IncludedBuild ib, ExternalModule public static Path serializeAppModel(ApplicationModel appModel, Task context, boolean test) throws IOException { final Path serializedModel = context.getTemporaryDir().toPath() .resolve("quarkus-app" + (test ? "-test" : "") + "-model.dat"); - try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(serializedModel))) { - out.writeObject(appModel); - } + ApplicationModelSerializer.serialize(appModel, serializedModel); return serializedModel; } public static ApplicationModel deserializeAppModel(Path path) throws IOException { - try (ObjectInputStream out = new ObjectInputStream(Files.newInputStream(path))) { - return (ApplicationModel) out.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } + return ApplicationModelSerializer.deserialize(path); } public static Path serializeAppModel(ApplicationModel appModel, Path serializedModelPath) throws IOException { - try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(serializedModelPath))) { - out.writeObject(appModel); - } + ApplicationModelSerializer.serialize(appModel, serializedModelPath); return serializedModelPath; } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 6244dbfa97aba..3c01caf9db22e 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -91,6 +91,7 @@ import org.fusesource.jansi.internal.Kernel32; import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.app.ConfiguredClassLoading; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.devmode.DependenciesFilter; @@ -1556,7 +1557,7 @@ private DevModeCommandLine newLauncher(String actualDebugPort, String bootstrapI .extensionDevModeJvmOptionFilter(extensionJvmOptions); // serialize the app model to avoid re-resolving it in the dev process - BootstrapUtils.serializeAppModel(appModel, appModelLocation); + ApplicationModelSerializer.serialize(appModel, appModelLocation); builder.jvmArgs("-D" + BootstrapConstants.SERIALIZED_APP_MODEL + "=" + appModelLocation); if (noDeps) { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java index d95e82c89028b..b89eb52957eb2 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java @@ -14,6 +14,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.ApplicationModel; @@ -111,8 +112,9 @@ void generateCode(PathCollection sourceParents, Consumer sourceRegistrar, final int workspaceId = getWorkspaceId(); if (workspaceId != 0) { try { - BootstrapUtils.writeAppModelWithWorkspaceId(appModel, workspaceId, BootstrapUtils - .getSerializedTestAppModelPath(Path.of(mavenProject().getBuild().getDirectory()))); + Path serializedTestAppModelPath = BootstrapUtils + .getSerializedTestAppModelPath(Path.of(mavenProject().getBuild().getDirectory())); + ApplicationModelSerializer.serialize(appModel, serializedTestAppModelPath); } catch (IOException e) { getLog().warn("Failed to serialize application model", e); } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java index ed5b9bd71e6a4..fc4a716595fd4 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeTestsMojo.java @@ -17,8 +17,8 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.json.Json; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.builder.Json; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.runtime.LaunchMode; diff --git a/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java index 9737d327586d4..192a139627480 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageAgentMojo.java @@ -17,13 +17,13 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; -import io.quarkus.builder.Json; -import io.quarkus.builder.JsonReader; -import io.quarkus.builder.JsonTransform; -import io.quarkus.builder.json.JsonMember; -import io.quarkus.builder.json.JsonObject; -import io.quarkus.builder.json.JsonString; -import io.quarkus.builder.json.JsonValue; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.JsonMember; +import io.quarkus.bootstrap.json.JsonObject; +import io.quarkus.bootstrap.json.JsonReader; +import io.quarkus.bootstrap.json.JsonString; +import io.quarkus.bootstrap.json.JsonTransform; +import io.quarkus.bootstrap.json.JsonValue; /** * Post-processes native image agent generated configuration to trim any unnecessary configuration. @@ -94,7 +94,7 @@ private void transformJsonObject(Path base, String name, Path target, JsonTransf + resourceSkipPattern); final String original = Files.readString(base.resolve(name)); final JsonObject jsonRead = JsonReader.of(original).read(); - final Json.JsonObjectBuilder jsonBuilder = Json.object(false, true); + final Json.JsonObjectBuilder jsonBuilder = Json.object(); jsonBuilder.transform(jsonRead, transform); try (BufferedWriter writer = new BufferedWriter( diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java index cca2c1291d4fd..2591891dfdc24 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java @@ -11,7 +11,7 @@ import io.quarkus.bootstrap.model.ApplicationModelBuilder; import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.DefaultArtifactSources; -import io.quarkus.bootstrap.workspace.DefaultSourceDir; +import io.quarkus.bootstrap.workspace.LazySourceDir; import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.bootstrap.workspace.WorkspaceModuleId; @@ -34,10 +34,10 @@ static WorkspaceModule toProjectModule(MavenProject project) { final Path generatedSourcesDir = Path.of(build.getDirectory(), "generated-sources/annotations"); final List sources = new ArrayList<>(project.getCompileSourceRoots().size()); project.getCompileSourceRoots() - .forEach(s -> sources.add(new DefaultSourceDir(Path.of(s), classesDir, generatedSourcesDir))); + .forEach(s -> sources.add(new LazySourceDir(Path.of(s), classesDir, generatedSourcesDir))); final List resources = new ArrayList<>(build.getResources().size()); for (Resource r : build.getResources()) { - resources.add(new DefaultSourceDir(Path.of(r.getDirectory()), + resources.add(new LazySourceDir(Path.of(r.getDirectory()), r.getTargetPath() == null ? classesDir : Path.of(r.getTargetPath()), // FIXME: generated sources? null)); @@ -46,12 +46,12 @@ static WorkspaceModule toProjectModule(MavenProject project) { final Path testClassesDir = Path.of(build.getTestOutputDirectory()); final List testSources = new ArrayList<>(project.getCompileSourceRoots().size()); - project.getTestCompileSourceRoots().forEach(s -> testSources.add(new DefaultSourceDir(Path.of(s), testClassesDir, + project.getTestCompileSourceRoots().forEach(s -> testSources.add(new LazySourceDir(Path.of(s), testClassesDir, // FIXME: do tests have generated sources? null))); final List testResources = new ArrayList<>(build.getTestResources().size()); for (Resource r : build.getTestResources()) { - testResources.add(new DefaultSourceDir(Path.of(r.getDirectory()), + testResources.add(new LazySourceDir(Path.of(r.getDirectory()), r.getTargetPath() == null ? testClassesDir : Path.of(r.getTargetPath()), // FIXME: do tests have generated sources? null)); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java index aac36ef07a8ce..b3c30ea894dfe 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java @@ -61,4 +61,66 @@ public interface BootstrapConstants { * the default parameters values of the Quarkus Maven and Gradle plugins launching an application in dev mode */ String EXT_DEV_MODE_LOCK_XX_JVM_OPTIONS = "dev-mode.lock.xx-jvm-options"; + + // ApplicationModel Mappable keys + String MAPPABLE_APP_ARTIFACT = "app-artifact"; + String MAPPABLE_PLATFORM_IMPORTS = "platform-imports"; + String MAPPABLE_CAPABILITIES = "capabilities"; + String MAPPABLE_LOCAL_PROJECTS = "local-projects"; + String MAPPABLE_EXCLUDED_RESOURCES = "excluded-resources"; + String MAPPABLE_EXTENSION_DEV_CONFIG = "extension-dev-config"; + // ArtifactDependency Mappable keys + String MAPPABLE_MAVEN_ARTIFACT = "maven-artifact"; + String MAPPABLE_SCOPE = "scope"; + String MAPPABLE_FLAGS = "flags"; + String MAPPABLE_EXCLUSIONS = "exclusions"; + // ResolvedArtifactDependency Mappable keys + String MAPPABLE_DEPENDENCIES = "dependencies"; + String MAPPABLE_MODULE = "module"; + String MAPPABLE_RESOLVED_PATHS = "resolved-paths"; + // WorkspaceModule Mappable keys + String MAPPABLE_MODULE_ID = "id"; + String MAPPABLE_MODULE_DIR = "module-dir"; + String MAPPABLE_BUILD_DIR = "build-dir"; + String MAPPABLE_BUILD_FILES = "build-files"; + String MAPPABLE_ARTIFACT_SOURCES = "artifact-sources"; + String MAPPABLE_PARENT = "parent"; + String MAPPABLE_TEST_CP_DEPENDENCY_EXCLUSIONS = "test-cp-exclusions"; + String MAPPABLE_TEST_ADDITIONAL_CP_ELEMENTS = "test-additional-cp-elements"; + String MAPPABLE_DIRECT_DEP_CONSTRAINTS = "direct-dep-constraints"; + String MAPPABLE_DIRECT_DEPS = "direct-deps"; + // ArtifactSource Mappable keys + String MAPPABLE_CLASSIFIER = "classifier"; + String MAPPABLE_SOURCES = "sources"; + String MAPPABLE_RESOURCES = "resources"; + // SourceDir Mappable keys + String MAPPABLE_SRC_DIR = "dir"; + String MAPPABLE_SRC_PATH_FILTER = "src-path-filter"; + String MAPPABLE_DEST_DIR = "dest-dir"; + String MAPPABLE_DEST_PATH_FILTER = "dest-path-filter"; + String MAPPABLE_APT_SOURCES_DIR = "apt-sources-dir"; + // PathFilter Mappable keys + String MAPPABLE_INCLUDES = "includes"; + String MAPPABLE_EXCLUDES = "excludes"; + // PlatformImports Mappable keys + String MAPPABLE_PLATFORM_PROPS = "platform-properties"; + String MAPPABLE_PLATFORM_RELEASE_INFO = "release-info"; + String MAPPABLE_IMPORTED_BOMS = "imported-boms"; + String MAPPABLE_MISALIGNED_REPORT = "misaligned-report"; + String MAPPABLE_ALIGNED = "aligned"; + // PlatformReleaseInfo Mappable keys + String MAPPABLE_PLATFORM_KEY = "platform-key"; + String MAPPABLE_STREAM = "stream"; + String MAPPABLE_VERSION = "version"; + String MAPPABLE_BOMS = "boms"; + // ExtensionCapabilities Mappable keys + String MAPPABLE_EXTENSION = "extension"; + String MAPPABLE_PROVIDED = "provided"; + String MAPPABLE_REQUIRED = "required"; + // ExtensionDevModeConfig Mappable keys + String MAPPABLE_JVM_OPTIONS = "jvm-options"; + String MAPPABLE_NAME = "name"; + String MAPPABLE_JVM_OPTION_GROUP_PREFIX = "group"; + String MAPPABLE_VALUES = "values"; + String MAPPABLE_LOCK_JVM_OPTIONS = "lock-jvm-options"; } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java index 16a6074f78843..846ea3c5921b9 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Map; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.maven.dependency.ArtifactCoords; @@ -132,4 +133,9 @@ public int getFlags() { public Collection getDependencies() { return List.of(); } + + @Override + public Map asMap(MappableCollectionFactory factory) { + throw new UnsupportedOperationException(); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java index e59c2761452b1..b02528001c929 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java @@ -3,6 +3,7 @@ import java.io.Serializable; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import io.quarkus.maven.dependency.ArtifactCoords; @@ -138,4 +139,9 @@ public PathCollection getResolvedPaths() { public Collection getDependencies() { return List.of(); } + + @Override + public Map asMap(MappableCollectionFactory factory) { + throw new UnsupportedOperationException(); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java index 76e223bf92fdc..0aada7d8b70e5 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java @@ -5,17 +5,19 @@ import java.util.Map; import java.util.Set; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.bootstrap.workspace.WorkspaceModuleId; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.Dependency; +import io.quarkus.maven.dependency.DependencyFlags; import io.quarkus.maven.dependency.ResolvedDependency; /** * Application dependency model. Allows to explore application dependencies, * Quarkus platforms found in the project configuration and Quarkus platform configuration properties. */ -public interface ApplicationModel { +public interface ApplicationModel extends Mappable { /** * Main application artifact @@ -155,10 +157,9 @@ default Collection getWorkspaceModules() { } private static void collectModules(WorkspaceModule module, Map collected) { - if (module == null) { + if (module == null || collected.putIfAbsent(module.getId(), module) != null) { return; } - collected.putIfAbsent(module.getId(), module); WorkspaceModule parent = module.getParent(); if (parent != null) { @@ -166,11 +167,9 @@ private static void collectModules(WorkspaceModule module, Map getExtensionDevModeConfig(); + + @Override + default Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(); + map.put(BootstrapConstants.MAPPABLE_APP_ARTIFACT, getAppArtifact().asMap(factory)); + map.put(BootstrapConstants.MAPPABLE_DEPENDENCIES, + Mappable.iterableAsMaps( + getDependenciesWithAnyFlag(DependencyFlags.DEPLOYMENT_CP | DependencyFlags.COMPILE_ONLY), + factory)); + map.put(BootstrapConstants.MAPPABLE_PLATFORM_IMPORTS, getPlatforms().asMap(factory)); + if (!getExtensionCapabilities().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_CAPABILITIES, Mappable.asMaps(getExtensionCapabilities(), factory)); + } + if (!getReloadableWorkspaceDependencies().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_LOCAL_PROJECTS, + Mappable.toStringCollection(getReloadableWorkspaceDependencies(), factory)); + } + if (!getRemovedResources().isEmpty()) { + final Map> removedResources = getRemovedResources(); + final Map mappedExcludedResources = factory.newMap(removedResources.size()); + for (Map.Entry> entry : removedResources.entrySet()) { + mappedExcludedResources.put(entry.getKey().toString(), Mappable.toStringCollection(entry.getValue(), factory)); + } + map.put(BootstrapConstants.MAPPABLE_EXCLUDED_RESOURCES, mappedExcludedResources); + } + if (!getExtensionDevModeConfig().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_EXTENSION_DEV_CONFIG, Mappable.asMaps(getExtensionDevModeConfig(), factory)); + } + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java index 290a1241c10ab..cffe326bf5629 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java @@ -38,6 +38,66 @@ public class ApplicationModelBuilder { private static final String COMMA = ","; + /** + * Initializes an {@link ApplicationModel} from a {@link Map}. + * + * @param map map representation of an application model + * @return an instance of an application model + */ + public static ApplicationModel fromMap(Map map) { + final ApplicationModelBuilder builder = new ApplicationModelBuilder(); + builder.setAppArtifact(ResolvedDependencyBuilder.newInstance() + .fromMap((Map) map.get(BootstrapConstants.MAPPABLE_APP_ARTIFACT))); + + final Collection> depsMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_DEPENDENCIES); + if (depsMap != null) { + for (Map depMap : depsMap) { + builder.addDependency(ResolvedDependencyBuilder.newInstance().fromMap(depMap)); + } + } + + final Map platformImportsMap = (Map) map + .get(BootstrapConstants.MAPPABLE_PLATFORM_IMPORTS); + if (platformImportsMap != null) { + builder.setPlatformImports(PlatformImports.fromMap(platformImportsMap)); + } + + final Collection> capabilitiesMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_CAPABILITIES); + if (capabilitiesMap != null) { + for (Map capabilityMap : capabilitiesMap) { + builder.addExtensionCapabilities(ExtensionCapabilities.fromMap(capabilityMap)); + } + } + + final Collection localProjectsStr = (Collection) map.get(BootstrapConstants.MAPPABLE_LOCAL_PROJECTS); + if (localProjectsStr != null) { + for (String key : localProjectsStr) { + builder.addReloadableWorkspaceModule(ArtifactKey.fromString(key)); + } + } + + final Map removedResourcesMap = (Map) map + .get(BootstrapConstants.MAPPABLE_EXCLUDED_RESOURCES); + if (removedResourcesMap != null) { + for (Map.Entry removedResource : removedResourcesMap.entrySet()) { + builder.addRemovedResources(ArtifactKey.fromString(removedResource.getKey()), + (Collection) removedResource.getValue()); + } + } + + final Collection> extDevConfigMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_EXTENSION_DEV_CONFIG); + if (extDevConfigMap != null) { + for (Map extDevConfig : extDevConfigMap) { + builder.extensionDevConfig.add(ExtensionDevModeConfig.fromMap(extDevConfig)); + } + } + + return builder.build(); + } + ResolvedDependencyBuilder appArtifact; final Map dependencies = new LinkedHashMap<>(); @@ -138,7 +198,7 @@ public ApplicationModelBuilder addExcludedArtifacts(List keys) { return this; } - public ApplicationModelBuilder addRemovedResources(ArtifactKey key, Set resources) { + public ApplicationModelBuilder addRemovedResources(ArtifactKey key, Collection resources) { this.excludedResources.computeIfAbsent(key, k -> new HashSet<>(resources.size())).addAll(resources); return this; } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionCapabilities.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionCapabilities.java index 4c5dbde9f7413..76426c18ed8c7 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionCapabilities.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionCapabilities.java @@ -1,12 +1,36 @@ package io.quarkus.bootstrap.model; import java.util.Collection; +import java.util.List; +import java.util.Map; -public interface ExtensionCapabilities { +import io.quarkus.bootstrap.BootstrapConstants; + +public interface ExtensionCapabilities extends Mappable { + + static ExtensionCapabilities fromMap(Map map) { + return new CapabilityContract( + map.get(BootstrapConstants.MAPPABLE_EXTENSION).toString(), + (Collection) map.getOrDefault(BootstrapConstants.MAPPABLE_PROVIDED, List.of()), + (Collection) map.getOrDefault(BootstrapConstants.MAPPABLE_REQUIRED, List.of())); + } String getExtension(); Collection getProvidesCapabilities(); Collection getRequiresCapabilities(); + + @Override + default Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(3); + map.put(BootstrapConstants.MAPPABLE_EXTENSION, getExtension()); + if (!getProvidesCapabilities().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_PROVIDED, Mappable.toStringCollection(getProvidesCapabilities(), factory)); + } + if (!getRequiresCapabilities().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_REQUIRED, Mappable.toStringCollection(getRequiresCapabilities(), factory)); + } + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionDevModeConfig.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionDevModeConfig.java index 5a5a4cc4d4597..45a4fc8c6767e 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionDevModeConfig.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ExtensionDevModeConfig.java @@ -1,14 +1,41 @@ package io.quarkus.bootstrap.model; import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; import java.util.Set; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.maven.dependency.ArtifactKey; /** * Extension Dev mode configuration options */ -public class ExtensionDevModeConfig implements Serializable { +public class ExtensionDevModeConfig implements Serializable, Mappable { + + static ExtensionDevModeConfig fromMap(Map map) { + JvmOptions jvmOptions = null; + Collection> jvmOptionsMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_JVM_OPTIONS); + if (jvmOptionsMap != null) { + final JvmOptionsBuilder optionsBuilder = new JvmOptionsBuilder(); + for (Map jvmOptionMap : jvmOptionsMap) { + optionsBuilder.addAllToGroup( + jvmOptionMap.get(BootstrapConstants.MAPPABLE_JVM_OPTION_GROUP_PREFIX).toString(), + jvmOptionMap.get(BootstrapConstants.MAPPABLE_NAME).toString(), + (Collection) jvmOptionMap.get(BootstrapConstants.MAPPABLE_VALUES)); + } + jvmOptions = optionsBuilder.build(); + } + + final Collection lockOptions = (Collection) map.get(BootstrapConstants.MAPPABLE_LOCK_JVM_OPTIONS); + + return new ExtensionDevModeConfig( + ArtifactKey.fromString(map.get(BootstrapConstants.MAPPABLE_EXTENSION).toString()), + jvmOptions, + lockOptions == null ? Set.of() : new HashSet<>(lockOptions)); + } private final ArtifactKey extensionKey; private final JvmOptions jvmOptions; @@ -47,4 +74,17 @@ public JvmOptions getJvmOptions() { public Set getLockJvmOptions() { return lockJvmOptions; } + + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(3); + map.put(BootstrapConstants.MAPPABLE_EXTENSION, extensionKey.toGacString()); + if (!jvmOptions.isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_JVM_OPTIONS, Mappable.iterableAsMaps(jvmOptions, factory)); + } + if (!lockJvmOptions.isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_LOCK_JVM_OPTIONS, Mappable.toStringCollection(lockJvmOptions, factory)); + } + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOption.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOption.java index 28de35927ff87..0371b850ac9de 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOption.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOption.java @@ -7,7 +7,7 @@ /** * JVM option */ -public interface JvmOption { +public interface JvmOption extends Mappable { /** * Simple option name without dashes diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOptionsBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOptionsBuilder.java index e7dfde8fdcc17..f674f578bbd44 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOptionsBuilder.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/JvmOptionsBuilder.java @@ -56,6 +56,27 @@ private JvmOptionsBuilder addToGroup(String optionGroupPrefix, String optionName return this; } + JvmOptionsBuilder addAllToGroup(String optionGroupPrefix, String optionName, Collection values) { + if (options.isEmpty()) { + options = new HashMap<>(); + } + var option = options.computeIfAbsent(optionName, n -> { + switch (optionGroupPrefix) { + case MutableStandardJvmOption.PROPERTY_GROUP_PREFIX: + return MutableStandardJvmOption.newInstance(optionName); + case MutableXxJvmOption.PROPERTY_GROUP_PREFIX: + return MutableXxJvmOption.newInstance(optionName); + } + throw new IllegalArgumentException("Unrecognized JVM option group prefix " + optionGroupPrefix); + }); + for (String value : values) { + if (!value.isBlank()) { + option.addValue(value); + } + } + return this; + } + /** * Adds a standard option without a value. * diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/Mappable.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/Mappable.java new file mode 100644 index 0000000000000..d1cd1c844a00b --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/Mappable.java @@ -0,0 +1,102 @@ +package io.quarkus.bootstrap.model; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; + +/** + * Implemented by types that can be represented as a {@link Map}. + */ +public interface Mappable { + + static Collection asMaps(Collection col, MappableCollectionFactory factory) { + if (col == null) { + return null; + } + var result = factory.newCollection(col.size()); + for (var c : col) { + result.add(c.asMap(factory)); + } + return result; + } + + static Collection iterableAsMaps(Iterable col, MappableCollectionFactory factory) { + if (col == null) { + return null; + } + var result = factory.newCollection(); + for (var c : col) { + result.add(c.asMap(factory)); + } + return result; + } + + /** + * Formats a collection of items as a comma-separated string. + * If the argument is null, the method will return null. + * If the argument is an empty collection, the method will return an empty string. + * + * @param col collection + * @return command-separated collection of items + */ + static Collection iterableToStringCollection(Iterable col, MappableCollectionFactory factory) { + if (col == null) { + return null; + } + final Collection result = factory.newCollection(); + for (Object c : col) { + result.add(c.toString()); + } + return result; + } + + /** + * Formats a collection of items as a comma-separated string. + * If the argument is null, the method will return null. + * If the argument is an empty collection, the method will return an empty string. + * + * @param col collection + * @return command-separated collection of items + */ + static Collection toStringCollection(Collection col, MappableCollectionFactory factory) { + return toStringCollection(col, Object::toString, factory); + } + + /** + * Formats a collection of items as a comma-separated string. + * If the argument is null, the method will return null. + * If the argument is an empty collection, the method will return an empty string. + * + * @param col collection + * @param converter converts an object to string + * @return command-separated collection of items + */ + static Collection toStringCollection(Collection col, Function converter, + MappableCollectionFactory factory) { + if (col == null) { + return null; + } + final Collection result = factory.newCollection(col.size()); + for (var c : col) { + result.add(converter.apply(c)); + } + return result; + } + + /** + * Invokes {@link #asMap(MappableCollectionFactory)} with the default {@link MappableCollectionFactory} implementation. + * + * @return a map representing this instance + */ + default Map asMap() { + return asMap(MappableCollectionFactory.defaultInstance()); + } + + /** + * Returns an instance of a {@link Map} that represents this instance. + * + * @param factory collection factory + * @return a map representing this instance + */ + Map asMap(MappableCollectionFactory factory); +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MappableCollectionFactory.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MappableCollectionFactory.java new file mode 100644 index 0000000000000..2e51237168b7d --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MappableCollectionFactory.java @@ -0,0 +1,72 @@ +package io.quarkus.bootstrap.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Collection factory to support {@link Mappable} implementations. + */ +public interface MappableCollectionFactory { + + /** + * Returns a default instance of the collection factory that creates {@link HashMap} and {@link ArrayList} + * instances. + * + * @return default instance of the collection factory + */ + static MappableCollectionFactory defaultInstance() { + return new MappableCollectionFactory() { + @Override + public Map newMap() { + return new HashMap<>(); + } + + @Override + public Map newMap(int initialCapacity) { + return new HashMap<>(initialCapacity); + } + + @Override + public Collection newCollection() { + return new ArrayList<>(); + } + + @Override + public Collection newCollection(int initialCapacity) { + return new ArrayList<>(initialCapacity); + } + }; + } + + /** + * Creates a new {@link Map} instance. + * + * @return an instance of a map + */ + Map newMap(); + + /** + * Creates a new {@link Map} instance with a specific initial capacity. + * + * @param initialCapacity initial map capacity + * @return an instance of a map + */ + Map newMap(int initialCapacity); + + /** + * Creates a new {@link Collection} instance. + * + * @return an instance of a collection + */ + Collection newCollection(); + + /** + * Creates a new {@link Collection} instance with a specific initial capacity. + * + * @param initialCapacity initial collection capacity + * @return an instance of a collection + */ + Collection newCollection(int initialCapacity); +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableBaseJvmOption.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableBaseJvmOption.java index 99742725f494f..40387a82dd9b6 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableBaseJvmOption.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableBaseJvmOption.java @@ -3,9 +3,12 @@ import java.io.Serializable; import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.Properties; import java.util.Set; +import io.quarkus.bootstrap.BootstrapConstants; + public abstract class MutableBaseJvmOption> implements JvmOption, Serializable { private static final String EMPTY_STR = ""; @@ -44,6 +47,8 @@ public T addValue(String value) { return (T) this; } + protected abstract String getPropertyGroupPrefix(); + protected abstract String getQuarkusExtensionPropertyPrefix(); @Override @@ -67,6 +72,15 @@ protected String toPropertyValue() { return sb.toString(); } + @Override + public Map asMap(MappableCollectionFactory factory) { + var map = factory.newMap(3); + map.put(BootstrapConstants.MAPPABLE_NAME, getName()); + map.put(BootstrapConstants.MAPPABLE_JVM_OPTION_GROUP_PREFIX, getPropertyGroupPrefix()); + map.put(BootstrapConstants.MAPPABLE_VALUES, getValues()); + return map; + } + public String toString() { return name + "=" + values; } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableStandardJvmOption.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableStandardJvmOption.java index 389df2b95a453..fe445c273e21c 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableStandardJvmOption.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableStandardJvmOption.java @@ -40,6 +40,11 @@ public static MutableStandardJvmOption newInstance(String name, String value) { return result; } + @Override + protected String getPropertyGroupPrefix() { + return PROPERTY_GROUP_PREFIX; + } + @Override protected String getQuarkusExtensionPropertyPrefix() { return COMPLETE_PROPERTY_PREFIX; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableXxJvmOption.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableXxJvmOption.java index ce9322789d667..11d7cc6b7fd61 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableXxJvmOption.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/MutableXxJvmOption.java @@ -31,6 +31,11 @@ public static MutableXxJvmOption newInstance(String name, String value) { return result; } + @Override + protected String getPropertyGroupPrefix() { + return PROPERTY_GROUP_PREFIX; + } + @Override protected String getQuarkusExtensionPropertyPrefix() { return COMPLETE_PROPERTY_PREFIX; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImports.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImports.java index 2eaf421801162..450a34cd55190 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImports.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImports.java @@ -1,18 +1,53 @@ package io.quarkus.bootstrap.model; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.maven.dependency.ArtifactCoords; -public interface PlatformImports { +public interface PlatformImports extends Mappable { + + static PlatformImports fromMap(Map map) { + return new PlatformImportsImpl( + (Map) map.getOrDefault(BootstrapConstants.MAPPABLE_PLATFORM_PROPS, Collections.emptyMap()), + importedBomsFromMap(map), releaseInfoFromMap(map)); + } + + private static List importedBomsFromMap(Map map) { + final Collection importedBomsStr = (Collection) map.get(BootstrapConstants.MAPPABLE_IMPORTED_BOMS); + if (importedBomsStr != null) { + final List importedBoms = new ArrayList<>(importedBomsStr.size()); + for (String importedBomStr : importedBomsStr) { + importedBoms.add(ArtifactCoords.fromString(importedBomStr)); + } + return importedBoms; + } + return Collections.emptyList(); + } + + private static List releaseInfoFromMap(Map map) { + final Collection> releaseInfoCol = (Collection>) map + .get(BootstrapConstants.MAPPABLE_PLATFORM_RELEASE_INFO); + if (releaseInfoCol != null) { + final List releaseInfo = new ArrayList<>(releaseInfoCol.size()); + for (Map releaseInfoMap : releaseInfoCol) { + releaseInfo.add(PlatformReleaseInfo.fromMap(releaseInfoMap)); + } + return releaseInfo; + } + return Collections.emptyList(); + } /** * Quarkus platform properties aggregated from all the platform an application is based on. * * @return aggregated platform properties */ - public Map getPlatformProperties(); + Map getPlatformProperties(); /** * Quarkus platform release information. @@ -30,16 +65,38 @@ public interface PlatformImports { /** * In case Quarkus platform member BOM imports were misaligned this method - * will return a detailed information about what was found to be in conflict. + * will return detailed information about what was found to be in conflict. * * @return platform member BOM misalignment report or null, in case no conflict was detected */ - public String getMisalignmentReport(); + String getMisalignmentReport(); /** * Checks whether the platform member BOM imports belong to the same platform release. * * @return true if imported platform member BOMs belong to the same platform release, otherwise - false */ - public boolean isAligned(); + boolean isAligned(); + + @Override + default Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(3); + + if (!getPlatformProperties().isEmpty()) { + final Map props = getPlatformProperties(); + var jsonProps = factory.newMap(props.size()); + jsonProps.putAll(props); + map.put(BootstrapConstants.MAPPABLE_PLATFORM_PROPS, jsonProps); + } + + if (getPlatformReleaseInfo() != null) { + map.put(BootstrapConstants.MAPPABLE_PLATFORM_RELEASE_INFO, Mappable.asMaps(getPlatformReleaseInfo(), factory)); + } + + if (getImportedPlatformBoms() != null) { + map.put(BootstrapConstants.MAPPABLE_IMPORTED_BOMS, + Mappable.toStringCollection(getImportedPlatformBoms(), ArtifactCoords::toGACTVString, factory)); + } + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java index cfcd32c452637..27073bd1024ab 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformImportsImpl.java @@ -45,11 +45,21 @@ public static boolean isPlatformReleaseInfo(String s) { private final Map platformImports = new HashMap<>(); - final Map collectedProps = new HashMap(); - private final Collection platformBoms = new ArrayList<>(); - private final Collection platformReleaseInfo = new ArrayList<>(); + private final Map collectedProps; + private final Collection platformBoms; + private final Collection platformReleaseInfo; public PlatformImportsImpl() { + collectedProps = new HashMap<>(); + platformBoms = new ArrayList<>(); + platformReleaseInfo = new ArrayList<>(); + } + + PlatformImportsImpl(Map platformProps, Collection importedBoms, + Collection platformReleaseInfo) { + this.collectedProps = platformProps; + this.platformBoms = importedBoms; + this.platformReleaseInfo = platformReleaseInfo; } public Collection getPlatformReleaseInfo() { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformReleaseInfo.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformReleaseInfo.java index edc14c9d71678..1bb0071292861 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformReleaseInfo.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/PlatformReleaseInfo.java @@ -2,16 +2,40 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.maven.dependency.ArtifactCoords; -import io.quarkus.maven.dependency.GACTV; /** * Platform release info that is encoded into a property in a platform properties artifact * following the format {@code platform.release-info@$#=(,)} */ -public class PlatformReleaseInfo implements Serializable { +public class PlatformReleaseInfo implements Serializable, Mappable { + + static PlatformReleaseInfo fromMap(Map map) { + final Collection bomsStr = (Collection) map.get(BootstrapConstants.MAPPABLE_BOMS); + final List boms = new ArrayList<>(bomsStr.size()); + for (String bomStr : bomsStr) { + boms.add(ArtifactCoords.fromString(bomStr)); + } + return new PlatformReleaseInfo( + (String) map.get(BootstrapConstants.MAPPABLE_PLATFORM_KEY), + (String) map.get(BootstrapConstants.MAPPABLE_STREAM), + (String) map.get(BootstrapConstants.MAPPABLE_VERSION), + boms); + } + + private static List parseArtifactCoords(String boms) { + final String[] bomCoords = boms.split(","); + final List result = new ArrayList<>(bomCoords.length); + for (String s : bomCoords) { + result.add(ArtifactCoords.fromString(s)); + } + return result; + } private static final long serialVersionUID = 7751600738849301644L; private final String platformKey; @@ -20,14 +44,14 @@ public class PlatformReleaseInfo implements Serializable { private final List boms; public PlatformReleaseInfo(String platformKey, String stream, String version, String boms) { + this(platformKey, stream, version, parseArtifactCoords(boms)); + } + + public PlatformReleaseInfo(String platformKey, String stream, String version, List boms) { this.platformKey = platformKey; this.stream = stream; this.version = version; - final String[] bomCoords = boms.split(","); - this.boms = new ArrayList<>(bomCoords.length); - for (String s : bomCoords) { - this.boms.add(GACTV.fromString(s)); - } + this.boms = boms; } /** @@ -87,6 +111,26 @@ String getPropertyValue() { return buf.toString(); } + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(4); + if (platformKey != null) { + map.put(BootstrapConstants.MAPPABLE_PLATFORM_KEY, platformKey); + } + if (stream != null) { + map.put(BootstrapConstants.MAPPABLE_STREAM, stream); + } + if (version != null) { + map.put(BootstrapConstants.MAPPABLE_VERSION, version); + } + if (!boms.isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_BOMS, + Mappable.toStringCollection(boms, ArtifactCoords::toGACTVString, factory)); + } + return map; + } + + @Override public String toString() { return getPropertyName() + '=' + getPropertyValue(); } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java index 9f779557745a6..b37b93b27c017 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java @@ -49,18 +49,30 @@ public static ArtifactKey[] parseDependencyCondition(String s) { return keys; } + /** + * @deprecated for removal since 3.31.0 in favor of ApplicationModelSerializer methods + */ + @Deprecated(forRemoval = true) public static void exportModel(ApplicationModel model, boolean test) throws AppModelResolverException, IOException { Path serializedModel = serializeAppModel(model, test); System.setProperty(test ? BootstrapConstants.SERIALIZED_TEST_APP_MODEL : BootstrapConstants.SERIALIZED_APP_MODEL, serializedModel.toString()); } + /** + * @deprecated for removal since 3.31.0 in favor of ApplicationModelSerializer methods + */ + @Deprecated(forRemoval = true) public static Path serializeAppModel(ApplicationModel model, boolean test) throws AppModelResolverException, IOException { final Path serializedModel = File.createTempFile("quarkus-" + (test ? "test-" : "") + "app-model", ".dat").toPath(); serializeAppModel(model, serializedModel); return serializedModel; } + /** + * @deprecated for removal since 3.31.0 in favor of ApplicationModelSerializer methods + */ + @Deprecated(forRemoval = true) public static void serializeAppModel(ApplicationModel model, final Path serializedModel) throws IOException { Files.createDirectories(serializedModel.getParent()); @@ -69,12 +81,20 @@ public static void serializeAppModel(ApplicationModel model, final Path serializ } } + /** + * @deprecated for removal since 3.31.0 in favor of ApplicationModelSerializer methods + */ + @Deprecated(forRemoval = true) public static Path serializeQuarkusModel(ApplicationModel model) throws IOException { final Path serializedModel = File.createTempFile("quarkus-model", ".dat").toPath(); serializeAppModel(model, serializedModel); return serializedModel; } + /** + * @deprecated for removal since 3.31.0 in favor of ApplicationModelSerializer methods + */ + @Deprecated(forRemoval = true) public static ApplicationModel deserializeQuarkusModel(Path modelPath) throws AppModelResolverException { if (Files.exists(modelPath)) { try (InputStream existing = Files.newInputStream(modelPath); @@ -114,15 +134,18 @@ private static Path getBootstrapBuildDir(Path projectBuildDir) { } /** - * Serializes an {@link ApplicationModel} along with the workspace ID for which it was resolved. - * The serialization format will be different from the one used by {@link #resolveSerializedAppModelPath(Path)} - * and {@link #getSerializedTestAppModelPath(Path)}. + * @deprecated since 3.31.0 in favor of ApplicationModelSerializer methods + *

+ * Serializes an {@link ApplicationModel} along with the workspace ID for which it was resolved. + * The serialization format will be different from the one used by {@link #resolveSerializedAppModelPath(Path)} + * and {@link #getSerializedTestAppModelPath(Path)}. * * @param appModel application model to serialize * @param workspaceId workspace ID * @param file target file * @throws IOException in case of an IO failure */ + @Deprecated(forRemoval = true) public static void writeAppModelWithWorkspaceId(ApplicationModel appModel, int workspaceId, Path file) throws IOException { Files.createDirectories(file.getParent()); try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(file))) { @@ -134,16 +157,18 @@ public static void writeAppModelWithWorkspaceId(ApplicationModel appModel, int w } /** - * Deserializes an {@link ApplicationModel} from a file. - *

- * The implementation will check whether the serialization format of the file matches the expected one. - * If it does not, the method will return null even if the file exists. - *

- * The implementation will compare the deserialized workspace ID to the argument {@code workspaceId} - * and if they don't match the method will return null. - *

- * Once the {@link ApplicationModel} was deserialized, the dependency paths will be checked for existence. - * If a dependency path does not exist, the method will throw an exception. + * @deprecated since 3.31.0 in favor of ApplicationModelSerializer methods + *

+ * Deserializes an {@link ApplicationModel} from a file. + *

+ * The implementation will check whether the serialization format of the file matches the expected one. + * If it does not, the method will return null even if the file exists. + *

+ * The implementation will compare the deserialized workspace ID to the argument {@code workspaceId} + * and if they don't match the method will return null. + *

+ * Once the {@link ApplicationModel} was deserialized, the dependency paths will be checked for existence. + * If a dependency path does not exist, the method will throw an exception. * * @param file serialized application model file * @param workspaceId expected workspace ID @@ -151,6 +176,7 @@ public static void writeAppModelWithWorkspaceId(ApplicationModel appModel, int w * @throws ClassNotFoundException in case a required class could not be loaded * @throws IOException in case of an IO failure */ + @Deprecated(forRemoval = true) public static ApplicationModel readAppModelWithWorkspaceId(Path file, int workspaceId) throws ClassNotFoundException, IOException { try (ObjectInputStream reader = new ObjectInputStream(Files.newInputStream(file))) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java index 13569de58cf3e..518898d0cb595 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java @@ -3,13 +3,17 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.Mappable; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.paths.EmptyPathTree; import io.quarkus.paths.MultiRootPathTree; import io.quarkus.paths.PathTree; -public interface ArtifactSources { +public interface ArtifactSources extends Mappable { String MAIN = ArtifactCoords.DEFAULT_CLASSIFIER; String TEST = "tests"; @@ -66,4 +70,19 @@ default PathTree getOutputTree() { } return new MultiRootPathTree(trees.toArray(new PathTree[0])); } + + @Override + default Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(3); + map.put(BootstrapConstants.MAPPABLE_CLASSIFIER, getClassifier()); + var sources = getSourceDirs(); + if (!sources.isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_SOURCES, Mappable.asMaps(sources, factory)); + } + var resources = getResourceDirs(); + if (!resources.isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_RESOURCES, Mappable.asMaps(resources, factory)); + } + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java index 57b453b157eb3..d817e819f30e1 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java @@ -1,13 +1,47 @@ package io.quarkus.bootstrap.workspace; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Map; import java.util.Objects; +import io.quarkus.bootstrap.BootstrapConstants; + public class DefaultArtifactSources implements ArtifactSources, Serializable { private static final long serialVersionUID = 2053702489268820757L; + static ArtifactSources fromMap(Map map) { + final String classifier = map.get(BootstrapConstants.MAPPABLE_CLASSIFIER).toString(); + + final Collection> sourcesMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_SOURCES); + final Collection sources; + if (sourcesMap != null) { + sources = new ArrayList<>(sourcesMap.size()); + for (Map sourceMap : sourcesMap) { + sources.add(LazySourceDir.fromMap(sourceMap)); + } + } else { + sources = Collections.emptyList(); + } + + final Collection> resourcesMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_RESOURCES); + final Collection resources; + if (resourcesMap != null) { + resources = new ArrayList<>(resourcesMap.size()); + for (Map resourceMap : resourcesMap) { + resources.add(LazySourceDir.fromMap(resourceMap)); + } + } else { + resources = Collections.emptyList(); + } + return new DefaultArtifactSources(classifier, sources, resources); + } + private final String classifier; private final Collection sources; private final Collection resources; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java index 1c76f47d60b70..b2877cb2aac57 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java @@ -10,7 +10,9 @@ import java.util.List; import java.util.Map; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.maven.dependency.Dependency; +import io.quarkus.maven.dependency.DependencyBuilder; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathList; @@ -27,6 +29,64 @@ public class Builder implements WorkspaceModule.Mutable, Serializable { private Builder() { } + @Override + public Builder fromMap(Map map) { + setModuleId(WorkspaceModuleId.fromString((String) map.get(BootstrapConstants.MAPPABLE_MODULE_ID))); + setModuleDir(Path.of((String) map.get(BootstrapConstants.MAPPABLE_MODULE_DIR))); + setBuildDir(Path.of((String) map.get(BootstrapConstants.MAPPABLE_BUILD_DIR))); + + Collection buildFilesStr = (Collection) map.get(BootstrapConstants.MAPPABLE_BUILD_FILES); + final Path[] buildFiles = new Path[buildFilesStr.size()]; + int i = 0; + for (String buildFileStr : buildFilesStr) { + buildFiles[i++] = Path.of(buildFileStr); + } + setBuildFiles(PathList.of(buildFiles)); + + Collection> sourcesMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_ARTIFACT_SOURCES); + if (sourcesMap != null) { + for (Map sourceMap : sourcesMap) { + addArtifactSources(DefaultArtifactSources.fromMap(sourceMap)); + } + } + + final Collection testCpExclusions = (Collection) map + .get(BootstrapConstants.MAPPABLE_TEST_CP_DEPENDENCY_EXCLUSIONS); + if (testCpExclusions != null) { + setTestClasspathDependencyExclusions(testCpExclusions); + } + + final Collection testAdditionalCpElements = (Collection) map + .get(BootstrapConstants.MAPPABLE_TEST_ADDITIONAL_CP_ELEMENTS); + if (testAdditionalCpElements != null) { + setAdditionalTestClasspathElements(testAdditionalCpElements); + } + + final Collection> depConstraintsMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_DIRECT_DEP_CONSTRAINTS); + if (depConstraintsMap != null) { + final List depConstraints = new ArrayList<>(depConstraintsMap.size()); + for (var depConstraintMap : depConstraintsMap) { + depConstraints.add(DependencyBuilder.newInstance().fromMap(depConstraintMap).build()); + } + setDependencyConstraints(depConstraints); + } + + final Collection> depsMap = (Collection>) map + .get(BootstrapConstants.MAPPABLE_DIRECT_DEPS); + if (depsMap != null) { + final List deps = new ArrayList<>(depsMap.size()); + for (var depMap : depsMap) { + deps.add(DependencyBuilder.newInstance().fromMap(depMap).build()); + } + setDependencies(deps); + } + + // TODO parent module + return this; + } + @Override public Builder setModuleId(WorkspaceModuleId moduleId) { DefaultWorkspaceModule.this.id = moduleId; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/LazySourceDir.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/LazySourceDir.java index 38d750e768c3f..2e952a0349cf7 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/LazySourceDir.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/LazySourceDir.java @@ -3,14 +3,14 @@ import java.io.IOException; import java.io.Serial; import java.io.Serializable; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.Map; import java.util.Objects; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.paths.DirectoryPathTree; -import io.quarkus.paths.EmptyPathTree; import io.quarkus.paths.PathFilter; import io.quarkus.paths.PathTree; @@ -19,6 +19,25 @@ */ public class LazySourceDir implements SourceDir, Serializable { + static SourceDir fromMap(Map map) { + final Path srcDir = Path.of(map.get(BootstrapConstants.MAPPABLE_SRC_DIR).toString()); + Map pathFilter = (Map) map.get(BootstrapConstants.MAPPABLE_SRC_PATH_FILTER); + final PathFilter srcFilter = pathFilter == null ? null : PathFilter.fromMap(pathFilter); + + final Path destDir = Path.of(map.get(BootstrapConstants.MAPPABLE_DEST_DIR).toString()); + pathFilter = (Map) map.get(BootstrapConstants.MAPPABLE_DEST_PATH_FILTER); + final PathFilter destFilter = pathFilter == null ? null : PathFilter.fromMap(pathFilter); + + final Path genSrcDir; + Object o = map.get(BootstrapConstants.MAPPABLE_APT_SOURCES_DIR); + if (o == null) { + genSrcDir = null; + } else { + genSrcDir = Path.of(o.toString()); + } + return new LazySourceDir(srcDir, srcFilter, destDir, destFilter, genSrcDir, Collections.emptyMap()); + } + private Path srcDir; private PathFilter srcFilter; private Path destDir; @@ -61,7 +80,7 @@ public Path getDir() { @Override public PathTree getSourceTree() { - return Files.exists(srcDir) ? new DirectoryPathTree(srcDir, srcFilter) : EmptyPathTree.getInstance(); + return new DirectoryPathTree(srcDir, srcFilter); } @Override @@ -76,7 +95,7 @@ public Path getAptSourcesDir() { @Override public PathTree getOutputTree() { - return Files.exists(destDir) ? new DirectoryPathTree(destDir, destFilter) : EmptyPathTree.getInstance(); + return new DirectoryPathTree(destDir, destFilter); } public T getValue(Object key, Class type) { @@ -84,6 +103,23 @@ public T getValue(Object key, Class type) { return o == null ? null : type.cast(o); } + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(5); + map.put(BootstrapConstants.MAPPABLE_SRC_DIR, srcDir.toString()); + if (srcFilter != null) { + map.put(BootstrapConstants.MAPPABLE_SRC_PATH_FILTER, srcFilter.asMap(factory)); + } + map.put(BootstrapConstants.MAPPABLE_DEST_DIR, destDir.toString()); + if (destFilter != null) { + map.put(BootstrapConstants.MAPPABLE_DEST_PATH_FILTER, destFilter.asMap(factory)); + } + if (genSrcDir != null) { + map.put(BootstrapConstants.MAPPABLE_APT_SOURCES_DIR, genSrcDir.toString()); + } + return map; + } + @Serial private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeUTF(srcDir.toAbsolutePath().toString()); @@ -106,4 +142,21 @@ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassN } data = (Map) in.readObject(); } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("dir=").append(srcDir); + if (srcFilter != null) { + sb.append(" src-filter=").append(srcFilter); + } + sb.append(", dest=").append(destDir); + if (destFilter != null) { + sb.append(" dest-filter=").append(destFilter); + } + if (genSrcDir != null) { + sb.append(" gen-src-dir=").append(genSrcDir); + } + return sb.toString(); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java index 494085f3b792f..01d0fb9fd3ac2 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java @@ -2,17 +2,21 @@ import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.Mappable; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.paths.PathTree; -public interface SourceDir { +public interface SourceDir extends Mappable { static SourceDir of(Path src, Path dest) { return of(src, dest, null); } static SourceDir of(Path src, Path dest, Path generatedSources) { - return new DefaultSourceDir(src, dest, generatedSources); + return new LazySourceDir(src, dest, generatedSources); } Path getDir(); @@ -33,4 +37,22 @@ default boolean isOutputAvailable() { default T getValue(Object key, Class type) { return null; } + + @Override + default Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(3); + final Path dir = getDir(); + if (dir != null) { + map.put(BootstrapConstants.MAPPABLE_SRC_DIR, dir.toString()); + } + final Path outputDir = getOutputDir(); + if (outputDir != null) { + map.put(BootstrapConstants.MAPPABLE_DEST_DIR, outputDir.toString()); + } + final Path aptSourcesDir = getAptSourcesDir(); + if (aptSourcesDir != null) { + map.put(BootstrapConstants.MAPPABLE_APT_SOURCES_DIR, aptSourcesDir.toString()); + } + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java index a8a92800e19d1..77769160f29c2 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java @@ -4,13 +4,17 @@ import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Map; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.Mappable; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.maven.dependency.Dependency; import io.quarkus.paths.EmptyPathTree; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathTree; -public interface WorkspaceModule { +public interface WorkspaceModule extends Mappable { static Mutable builder() { return DefaultWorkspaceModule.builder(); @@ -64,6 +68,48 @@ default PathTree getContentTree(String classifier) { Mutable mutable(); + @Override + default Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(); + map.put(BootstrapConstants.MAPPABLE_MODULE_ID, getId().toString()); + if (getModuleDir() != null) { + map.put(BootstrapConstants.MAPPABLE_MODULE_DIR, getModuleDir().toString()); + } + if (getBuildDir() != null) { + map.put(BootstrapConstants.MAPPABLE_BUILD_DIR, getBuildDir().toString()); + } + if (!getBuildFiles().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_BUILD_FILES, Mappable.iterableToStringCollection(getBuildFiles(), factory)); + } + var classifiers = getSourceClassifiers(); + if (!classifiers.isEmpty()) { + final Collection artifactSources = factory.newCollection(classifiers.size()); + for (String classifier : classifiers) { + artifactSources.add(getSources(classifier).asMap(factory)); + } + map.put(BootstrapConstants.MAPPABLE_ARTIFACT_SOURCES, artifactSources); + } + if (getParent() != null) { + map.put(BootstrapConstants.MAPPABLE_PARENT, getParent().getId().toString()); + } + if (!getTestClasspathDependencyExclusions().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_TEST_CP_DEPENDENCY_EXCLUSIONS, + Mappable.toStringCollection(getTestClasspathDependencyExclusions(), factory)); + } + if (!getAdditionalTestClasspathElements().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_TEST_ADDITIONAL_CP_ELEMENTS, + Mappable.toStringCollection(getAdditionalTestClasspathElements(), factory)); + } + if (!getDirectDependencyConstraints().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_DIRECT_DEP_CONSTRAINTS, + Mappable.asMaps(getDirectDependencyConstraints(), factory)); + } + if (!getDirectDependencies().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_DIRECT_DEPS, Mappable.asMaps(getDirectDependencies(), factory)); + } + return map; + } + interface Mutable extends WorkspaceModule { Mutable setModuleId(WorkspaceModuleId moduleId); @@ -90,6 +136,8 @@ interface Mutable extends WorkspaceModule { Mutable setParent(WorkspaceModule parent); + Mutable fromMap(Map moduleMap); + WorkspaceModule build(); default Mutable mutable() { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModuleId.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModuleId.java index 6c62f64d31c9e..8e0f08b3aa866 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModuleId.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModuleId.java @@ -8,6 +8,14 @@ static WorkspaceModuleId of(String groupId, String artifactId, String version) { return new GAV(groupId, artifactId, version); } + static WorkspaceModuleId fromString(String str) { + final String[] arr = str.split(":"); + if (arr.length != 3) { + throw new IllegalArgumentException("Invalid workspace module ID string: " + str); + } + return of(arr[0], arr[1], arr[2]); + } + String getGroupId(); String getArtifactId(); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/AbstractDependencyBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/AbstractDependencyBuilder.java index 159757d6e708a..02dc483a27c09 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/AbstractDependencyBuilder.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/AbstractDependencyBuilder.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; + +import io.quarkus.bootstrap.BootstrapConstants; abstract class AbstractDependencyBuilder, T> { @@ -15,6 +18,23 @@ abstract class AbstractDependencyBuilder exclusions = List.of(); + @SuppressWarnings("unchecked") + public B fromMap(Map map) { + setCoords(ArtifactCoords.fromString(map.get(BootstrapConstants.MAPPABLE_MAVEN_ARTIFACT).toString())); + Object scope = map.get(BootstrapConstants.MAPPABLE_SCOPE); + if (scope != null) { + setScope(scope.toString()); + } + setFlags(Integer.parseInt(map.get(BootstrapConstants.MAPPABLE_FLAGS).toString())); + var exclusions = map.get(BootstrapConstants.MAPPABLE_EXCLUSIONS); + if (exclusions != null) { + for (String key : (Collection) exclusions) { + addExclusion(ArtifactKey.fromString(key)); + } + } + return (B) this; + } + @SuppressWarnings("unchecked") public B setCoords(ArtifactCoords coords) { this.groupId = coords.getGroupId(); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactDependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactDependency.java index f5c06e0119d38..67ad18554070f 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactDependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactDependency.java @@ -3,8 +3,13 @@ import java.io.Serializable; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.Mappable; +import io.quarkus.bootstrap.model.MappableCollectionFactory; + public class ArtifactDependency extends GACTV implements Dependency, Serializable { private static final long serialVersionUID = 5669341172899612719L; @@ -14,6 +19,15 @@ public static Dependency of(String groupId, String artifactId, String version) { return new ArtifactDependency(groupId, artifactId, null, ArtifactCoords.TYPE_JAR, version); } + static void putInMap(Dependency dependency, Map map, MappableCollectionFactory factory) { + map.put(BootstrapConstants.MAPPABLE_MAVEN_ARTIFACT, dependency.toGACTVString()); + map.put(BootstrapConstants.MAPPABLE_SCOPE, dependency.getScope()); + map.put(BootstrapConstants.MAPPABLE_FLAGS, dependency.getFlags()); + if (!dependency.getExclusions().isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_EXCLUSIONS, Mappable.toStringCollection(dependency.getExclusions(), factory)); + } + } + private final String scope; private final int flags; private final Collection exclusions; @@ -78,6 +92,13 @@ public Collection getExclusions() { return exclusions; } + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(4); + putInMap(this, map, factory); + return map; + } + @Override public int hashCode() { final int prime = 31; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java index 2a6df30caab3c..47fbc5a6fc450 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java @@ -3,7 +3,9 @@ import java.util.Collection; import java.util.List; -public interface Dependency extends ArtifactCoords { +import io.quarkus.bootstrap.model.Mappable; + +public interface Dependency extends ArtifactCoords, Mappable { String SCOPE_COMPILE = "compile"; String SCOPE_IMPORT = "import"; diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/GACT.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/GACT.java index 8b9326ecc224f..a75558b9eb18a 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/GACT.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/GACT.java @@ -82,46 +82,43 @@ public static String[] split(String str, String[] parts, int fromIndex) { parts[3] = str.substring(i + 1, fromIndex); fromIndex = i; i = str.lastIndexOf(':', fromIndex - 1); - if (i < 0) { - parts[0] = str.substring(0, fromIndex); - if ((parts[1] = parts[3]).isEmpty()) { - throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`"); + if (i > 0) { + if (i == fromIndex - 1) { + parts[2] = ArtifactCoords.DEFAULT_CLASSIFIER; + } else { + parts[2] = str.substring(i + 1, fromIndex); } - parts[2] = ArtifactCoords.DEFAULT_CLASSIFIER; - parts[3] = null; - return parts; - } - if (i == 0) { - throw new IllegalArgumentException( - "One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'"); - } - if (i == fromIndex - 1) { - parts[2] = ArtifactCoords.DEFAULT_CLASSIFIER; - } else { - parts[2] = str.substring(i + 1, fromIndex); - } - fromIndex = i; - i = str.lastIndexOf(':', fromIndex - 1); - if (i < 0) { - parts[0] = str.substring(0, fromIndex); - if ((parts[1] = parts[2]).isEmpty()) { - throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`"); + fromIndex = i; + i = str.lastIndexOf(':', fromIndex - 1); + if (i < 0) { + parts[0] = str.substring(0, fromIndex); + if ((parts[1] = parts[2]).isEmpty()) { + throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`"); + } + parts[2] = parts[3]; + parts[3] = null; + return parts; + } + if (i == fromIndex - 1) { + throw new IllegalArgumentException( + "One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'"); + } + + parts[0] = str.substring(0, i); + parts[1] = str.substring(i + 1, fromIndex); + if (parts[3].isEmpty()) { + parts[3] = null; } - parts[2] = parts[3]; - parts[3] = null; return parts; } - if (i == 0 || i == fromIndex - 1) { - throw new IllegalArgumentException( - "One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'"); - } - parts[0] = str.substring(0, i); - parts[1] = str.substring(i + 1, fromIndex); - if (parts[3].isEmpty()) { - parts[3] = null; + parts[0] = str.substring(0, fromIndex); + if ((parts[1] = parts[3]).isEmpty()) { + throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`"); } + parts[2] = ArtifactCoords.DEFAULT_CLASSIFIER; + parts[3] = null; return parts; } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedArtifactDependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedArtifactDependency.java index 25964852a2b78..348f2cfc62419 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedArtifactDependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedArtifactDependency.java @@ -4,8 +4,10 @@ import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathList; @@ -79,6 +81,13 @@ public Collection getDependencies() { return deps; } + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(); + ResolvedDependencyBuilder.putInMap(this, map, factory); + return map; + } + @Override public int hashCode() { final int prime = 31; @@ -93,9 +102,8 @@ public boolean equals(Object obj) { return true; if (!super.equals(obj)) return false; - if (!(obj instanceof ResolvableDependency)) + if (!(obj instanceof ResolvableDependency other)) return false; - ResolvableDependency other = (ResolvableDependency) obj; return Objects.equals(module, other.getWorkspaceModule()) && Objects.equals(paths, other.getResolvedPaths()); } @@ -104,7 +112,7 @@ public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(toGACTVString()).append(paths); if (module != null) { - buf.append(" " + module); + buf.append(" ").append(module); } return buf.toString(); } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependencyBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependencyBuilder.java index f4a82193cc107..15726fe97c21a 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependencyBuilder.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependencyBuilder.java @@ -1,10 +1,16 @@ package io.quarkus.maven.dependency; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.Mappable; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathList; @@ -12,6 +18,22 @@ public class ResolvedDependencyBuilder extends AbstractDependencyBuilder implements ResolvedDependency { + static void putInMap(ResolvedDependency dependency, Map map, MappableCollectionFactory factory) { + ArtifactDependency.putInMap(dependency, map, factory); + map.put(BootstrapConstants.MAPPABLE_RESOLVED_PATHS, + Mappable.iterableToStringCollection(dependency.getResolvedPaths(), factory)); + + final Collection deps = dependency.getDependencies(); + if (!deps.isEmpty()) { + map.put(BootstrapConstants.MAPPABLE_DEPENDENCIES, + Mappable.toStringCollection(deps, ArtifactCoords::toGACTVString, factory)); + } + + if (dependency.getWorkspaceModule() != null) { + map.put(BootstrapConstants.MAPPABLE_MODULE, dependency.getWorkspaceModule().asMap(factory)); + } + } + public static ResolvedDependencyBuilder newInstance() { return new ResolvedDependencyBuilder(); } @@ -21,6 +43,34 @@ public static ResolvedDependencyBuilder newInstance() { private volatile ArtifactCoords coords; private Collection deps = Set.of(); + @Override + public ResolvedDependencyBuilder fromMap(Map map) { + super.fromMap(map); + + Collection resolvedPathsStr = (Collection) map.get(BootstrapConstants.MAPPABLE_RESOLVED_PATHS); + final Path[] pathArr = new Path[resolvedPathsStr.size()]; + int i = 0; + for (var pathStr : resolvedPathsStr) { + pathArr[i++] = Path.of(pathStr); + } + setResolvedPaths(PathList.of(pathArr)); + + Collection depsStr = (Collection) map.get(BootstrapConstants.MAPPABLE_DEPENDENCIES); + if (depsStr != null) { + final List deps = new ArrayList<>(depsStr.size()); + for (String depStr : depsStr) { + deps.add(ArtifactCoords.fromString(depStr)); + } + setDependencies(deps); + } + + Map moduleMap = (Map) map.get(BootstrapConstants.MAPPABLE_MODULE); + if (moduleMap != null) { + setWorkspaceModule(WorkspaceModule.builder().fromMap(moduleMap).build()); + } + return this; + } + @Override public PathCollection getResolvedPaths() { return resolvedPaths; @@ -88,4 +138,11 @@ public Collection getDependencies() { public ResolvedDependency build() { return new ResolvedArtifactDependency(this); } + + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map map = factory.newMap(7); + putInMap(this, map, factory); + return map; + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenContainerPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenContainerPathTree.java index 81875e01b1da9..c146d10431fb8 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenContainerPathTree.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenContainerPathTree.java @@ -102,8 +102,13 @@ public Collection getRoots() { @Override public void walk(PathVisitor visitor) { - PathTreeVisit.walk(getRootPath(), getRootPath(), getRootPath(), pathFilter, getMultiReleaseMapping(), + final Path rootPath = getRootPath(); + if (!Files.exists(rootPath)) { + return; + } + PathTreeVisit.walk(rootPath, rootPath, rootPath, pathFilter, getMultiReleaseMapping(), visitor); + } @Override diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java index 9ea7d81d5d0c5..c1f947fe1b068 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java @@ -4,12 +4,16 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.regex.Pattern; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.Mappable; +import io.quarkus.bootstrap.model.MappableCollectionFactory; import io.quarkus.util.GlobUtil; -public class PathFilter implements Serializable { +public class PathFilter implements Mappable, Serializable { private static final long serialVersionUID = -5712472676677054175L; @@ -31,12 +35,24 @@ public static PathFilter forExcludes(Collection excludes) { return new PathFilter(null, excludes); } + public static PathFilter fromMap(Map map) { + return new PathFilter( + compileRegex((Collection) map.get(BootstrapConstants.MAPPABLE_INCLUDES)), + compileRegex((Collection) map.get(BootstrapConstants.MAPPABLE_EXCLUDES))); + + } + private List includes; private List excludes; public PathFilter(Collection includes, Collection excludes) { - this.includes = compile(includes); - this.excludes = compile(excludes); + this.includes = compileGlob(includes); + this.excludes = compileGlob(excludes); + } + + private PathFilter(List includes, List excludes) { + this.includes = includes; + this.excludes = excludes; } public boolean isVisible(String pathStr) { @@ -63,6 +79,27 @@ public boolean isVisible(String pathStr) { return true; } + @Override + public Map asMap(MappableCollectionFactory factory) { + final Map result; + if (includes != null && !includes.isEmpty()) { + if (excludes != null && !excludes.isEmpty()) { + result = factory.newMap(2); + result.put(BootstrapConstants.MAPPABLE_INCLUDES, Mappable.toStringCollection(includes, factory)); + result.put(BootstrapConstants.MAPPABLE_EXCLUDES, Mappable.toStringCollection(excludes, factory)); + } else { + result = factory.newMap(1); + result.put(BootstrapConstants.MAPPABLE_INCLUDES, Mappable.toStringCollection(includes, factory)); + } + } else if (excludes != null && !excludes.isEmpty()) { + result = factory.newMap(1); + result.put(BootstrapConstants.MAPPABLE_EXCLUDES, Mappable.toStringCollection(excludes, factory)); + } else { + result = factory.newMap(0); + } + return result; + } + @Override public int hashCode() { return Objects.hash(excludes, includes); @@ -101,7 +138,7 @@ public String toString() { return s.toString(); } - private static List compile(Collection expressions) { + private static List compileGlob(Collection expressions) { if (expressions == null) { return null; } @@ -111,4 +148,15 @@ private static List compile(Collection expressions) { } return compiled; } + + private static List compileRegex(Collection regexList) { + if (regexList == null) { + return null; + } + final List compiled = new ArrayList<>(regexList.size()); + for (String regex : regexList) { + compiled.add(Pattern.compile(regex)); + } + return compiled; + } } diff --git a/independent-projects/bootstrap/bom/pom.xml b/independent-projects/bootstrap/bom/pom.xml index e62e616fd7931..fe281acd2b5f7 100644 --- a/independent-projects/bootstrap/bom/pom.xml +++ b/independent-projects/bootstrap/bom/pom.xml @@ -57,6 +57,11 @@ quarkus-classloader-commons ${project.version} + + io.quarkus + quarkus-bootstrap-json + ${project.version} + io.quarkus quarkus-bootstrap-core diff --git a/independent-projects/bootstrap/core/pom.xml b/independent-projects/bootstrap/core/pom.xml index 9b6d407781f86..b9da7ab84b7ec 100644 --- a/independent-projects/bootstrap/core/pom.xml +++ b/independent-projects/bootstrap/core/pom.xml @@ -41,6 +41,10 @@ io.quarkus quarkus-bootstrap-app-model + + io.quarkus + quarkus-bootstrap-json + io.quarkus quarkus-bootstrap-maven-resolver diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java index c7f93411a5e7c..3a549561d11f2 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapAppModelFactory.java @@ -1,11 +1,6 @@ package io.quarkus.bootstrap; -import static io.quarkus.bootstrap.util.BootstrapUtils.readAppModelWithWorkspaceId; -import static io.quarkus.bootstrap.util.BootstrapUtils.writeAppModelWithWorkspaceId; - import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -18,6 +13,7 @@ import org.jboss.logging.Logger; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.app.CurationResult; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.AppModelResolver; @@ -252,10 +248,7 @@ private CurationResult resolveAppModelForWorkspace() throws BootstrapException { if (Files.exists(cachedCpPath) && workspace.getLastModified() < Files.getLastModifiedTime(cachedCpPath).toMillis()) { try { - final ApplicationModel appModel = readAppModelWithWorkspaceId(cachedCpPath, workspace.getId()); - if (appModel != null) { - return new CurationResult(appModel); - } + return new CurationResult(ApplicationModelSerializer.deserialize(cachedCpPath)); } catch (IOException e) { log.warn("Failed to read deployment classpath cache from " + cachedCpPath + " for " + appArtifact, e); @@ -267,7 +260,7 @@ private CurationResult resolveAppModelForWorkspace() throws BootstrapException { if (cachedCpPath != null) { Files.createDirectories(cachedCpPath.getParent()); try { - writeAppModelWithWorkspaceId(curationResult.getApplicationModel(), workspace.getId(), cachedCpPath); + ApplicationModelSerializer.serialize(curationResult.getApplicationModel(), cachedCpPath); } catch (IOException e) { log.warn("Failed to write classpath cache", e); } @@ -296,9 +289,9 @@ private CurationResult loadFromSystemProperty() { if (serializedModel != null) { final Path p = Paths.get(serializedModel); if (Files.exists(p)) { - try (InputStream existing = Files.newInputStream(p)) { - return new CurationResult((ApplicationModel) new ObjectInputStream(existing).readObject()); - } catch (IOException | ClassNotFoundException e) { + try { + return new CurationResult(ApplicationModelSerializer.deserialize(p)); + } catch (IOException e) { log.error("Failed to load serialized app mode", e); } IoUtils.recursiveDelete(p); diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java index 881b28b7935d5..11a97f5ff648b 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java @@ -8,13 +8,13 @@ import java.util.Map; import io.quarkus.bootstrap.app.AdditionalDependency; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; -import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.bootstrap.utils.BuildToolHelper; import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.SourceDir; @@ -47,7 +47,8 @@ public static Closeable launch(Path classesDir, Map context) { .setTargetDirectory(classesDir.getParent()); if (BuildToolHelper.isGradleProject(classesDir)) { final ApplicationModel quarkusModel = BuildToolHelper.enableGradleAppModelForDevMode(classesDir); - context.put(BootstrapConstants.SERIALIZED_APP_MODEL, BootstrapUtils.serializeAppModel(quarkusModel, false)); + context.put(BootstrapConstants.SERIALIZED_APP_MODEL, + ApplicationModelSerializer.serializeGradleModel(quarkusModel, false)); ArtifactSources mainSources = quarkusModel.getApplicationModule().getMainSources(); diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ApplicationModelSerializer.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ApplicationModelSerializer.java new file mode 100644 index 0000000000000..e4ce6baf7b4a3 --- /dev/null +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ApplicationModelSerializer.java @@ -0,0 +1,342 @@ +package io.quarkus.bootstrap.app; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.json.Json; +import io.quarkus.bootstrap.json.JsonArray; +import io.quarkus.bootstrap.json.JsonObject; +import io.quarkus.bootstrap.json.JsonReader; +import io.quarkus.bootstrap.json.JsonValue; +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.model.ApplicationModelBuilder; +import io.quarkus.bootstrap.model.MappableCollectionFactory; + +/** + * Utility class providing various serializing and deserializing methods for {@link ApplicationModel} + */ +public class ApplicationModelSerializer { + + private static final MappableCollectionFactory JSON_CONTAINER_FACTORY = new JsonCollectionFactory(); + + private static final String QUARKUS_APPLICATION_MODEL_SERIALIZATION_FORMAT_PROP = "quarkus.bootstrap.application-model.serialization.format"; + // whether to use Java Object Serialization as a format + private static final boolean JOS; + static { + final String serializationFormat = System.getProperty(QUARKUS_APPLICATION_MODEL_SERIALIZATION_FORMAT_PROP); + if (serializationFormat == null) { + JOS = false; + } else { + if (serializationFormat.equalsIgnoreCase("jos")) { + JOS = true; + } else { + JOS = false; + if (!serializationFormat.equalsIgnoreCase("json")) { + throw new IllegalStateException("Unsupported serialization format: " + serializationFormat); + } + } + } + } + + /** + * Creates a temporary file to serialize an application model. + * + * @param test whether it's for a test model + * @return temporary file + * @throws IOException in case of a failure + */ + private static Path getTempFile(boolean test) throws IOException { + return Files.createTempFile( + "quarkus-" + (test ? "test-" : "") + "app-model", + ".dat"); + } + + /** + * Serializes an {@link ApplicationModel} to a file and sets a system property + * {@link BootstrapConstants#SERIALIZED_TEST_APP_MODEL} + * to the value of the file path the model was serialized in. + *

+ * This method will make sure the serialization will work for Gradle proxies of {@link ApplicationModel}. + * + * @param model application model to serialize + * @param test whether it's a test application model + * @throws IOException in case of a failure + */ + public static void exportGradleModel(ApplicationModel model, boolean test) throws IOException { + System.setProperty(test ? BootstrapConstants.SERIALIZED_TEST_APP_MODEL : BootstrapConstants.SERIALIZED_APP_MODEL, + serializeGradleModel(model, test).toString()); + } + + /** + * Serializes an {@link ApplicationModel} and returns a path to the file in which the application model was serialized. + *

+ * This method will make sure the serialization will work for Gradle proxies of {@link ApplicationModel}. + * + * @param model application model to serialize + * @param test whether it's a test application model + * @return file in which the application model was serialized + * @throws IOException in case of a failure + */ + public static Path serializeGradleModel(ApplicationModel model, boolean test) throws IOException { + final Path serializedModel = getTempFile(test); + if (JOS) { + serializeWithJos(model, serializedModel); + } else { + writeJson(toJsonObjectBuilder(model.asMap()), serializedModel); + } + return serializedModel; + } + + /** + * Serializes an {@link ApplicationModel} and returns a path to the file in which the application model was serialized. + * + * @param model application model to serialize + * @param test whether it's a test application model + * @return file in which the application model was serialized + * @throws IOException in case of a failure + */ + public static Path serialize(ApplicationModel model, boolean test) throws IOException { + final Path serializedModel = getTempFile(test); + serialize(model, serializedModel); + return serializedModel; + } + + /** + * Serializes an {@link ApplicationModel} to a file. + * + * @param appModel application model to serialize + * @param file target file + * @throws IOException in case of a failure + */ + public static void serialize(ApplicationModel appModel, Path file) throws IOException { + Files.createDirectories(file.getParent()); + if (JOS) { + serializeWithJos(appModel, file); + } else { + toJson(appModel, file); + } + } + + /** + * Deserializes an {@link ApplicationModel} from a given file. + * + * @param file file to deserialize the application model from + * @return deserialized application model + * @throws IOException in case of a failure + */ + public static ApplicationModel deserialize(Path file) throws IOException { + return JOS ? deserializeWithJos(file) : fromJson(file); + } + + /** + * Serialize an {@link ApplicationModel} with Java Object Serialization (the legacy way). + * + * @param appModel application model to serialize + * @param file target file + * @throws IOException in case of a failure + */ + private static void serializeWithJos(ApplicationModel appModel, Path file) throws IOException { + try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(file))) { + out.writeObject(appModel); + } + } + + /** + * Deserializes an {@link ApplicationModel} from a file with Java Object Serialization. + * + * @param file file to read an application model from + * @return deserialized application model + * @throws IOException in case of a failure + */ + private static ApplicationModel deserializeWithJos(Path file) throws IOException { + try (InputStream existing = Files.newInputStream(file)) { + return (ApplicationModel) new ObjectInputStream(existing).readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Serialize an {@link ApplicationModel} to a JSON file. + * + * @param appModel application model to serialize + * @param file target file + * @throws IOException in case of a failure + */ + private static void toJson(ApplicationModel appModel, Path file) throws IOException { + writeJson((Json.JsonObjectBuilder) appModel.asMap(JSON_CONTAINER_FACTORY), file); + } + + /** + * Serializes a {@link io.quarkus.bootstrap.json.Json.JsonObjectBuilder} to a JSON file. + * + * @param jsonObject JSON object builder + * @param file target file + * @throws IOException in case of a failure + */ + private static void writeJson(Json.JsonObjectBuilder jsonObject, Path file) throws IOException { + try (Writer writer = Files.newBufferedWriter(file)) { + jsonObject.appendTo(writer); + } + } + + /** + * Deserializes an {@link ApplicationModel} from a JSON file. + * + * @param file JSON file + * @return deserialized application model + * @throws IOException in case of a failure + */ + private static ApplicationModel fromJson(Path file) throws IOException { + final Map modelMap = asMap(JsonReader.of(Files.readString(file)).read()); + return ApplicationModelBuilder.fromMap(modelMap); + } + + /** + * Transforms a {@link JsonObject} to a {@link Map} with {@link String} keys and {@link Object} values. + * + * @param jsonObject JSON object to transform + * @return map representation of the JSON object + */ + private static Map asMap(JsonObject jsonObject) { + var members = jsonObject.members(); + final Map map = new HashMap<>(members.size()); + for (var member : members) { + map.put(member.attribute().value(), asObject(member.value())); + } + return map; + } + + /** + * Transforms a {@link JsonArray} to a {@link Collection} of objects. + * + * @param jsonArray JSON array to transform + * @return collection representation of the JSON array + */ + private static Collection asCollection(JsonArray jsonArray) { + final Collection col = new ArrayList<>(jsonArray.size()); + jsonArray.stream().map(ApplicationModelSerializer::asObject).forEach(col::add); + return col; + } + + /** + * Transforms a {@link JsonValue} to the corresponding {@link Map}, {@link Collection} or {@link String} + * value, depending on the actual JSON type. + * + * @param jsonValue JSON value to transform + * @return object + */ + private static Object asObject(JsonValue jsonValue) { + if (jsonValue instanceof JsonObject jsonObject) { + return asMap(jsonObject); + } + if (jsonValue instanceof JsonArray jsonArray) { + return asCollection(jsonArray); + } + return jsonValue.toString(); + } + + /** + * Converts a {@link Map} instance to a {@link io.quarkus.bootstrap.json.Json.JsonObjectBuilder}. + * + * @param map map to convert + * @return JSON object builder + */ + private static Json.JsonObjectBuilder toJsonObjectBuilder(Map map) { + final Json.JsonObjectBuilder result = Json.object(map.size()); + for (Map.Entry entry : map.entrySet()) { + result.put(entry.getKey(), toJsonTypeBuilder(entry.getValue())); + } + return result; + } + + /** + * Converts a {@link Collection} instance to a {@link io.quarkus.bootstrap.json.Json.JsonArrayBuilder}. + * + * @param col collection to convert + * @return JSON array builder + */ + private static Json.JsonArrayBuilder toJsonArrayBuilder(Collection col) { + final Json.JsonArrayBuilder result = Json.array(col.size()); + for (Object o : col) { + result.add(toJsonTypeBuilder(o)); + } + return result; + } + + /** + * Converts an {@link Object} to a JSON type builder. + * + * @param o object to convert + * @return JSON type builder + */ + private static Object toJsonTypeBuilder(Object o) { + if (o instanceof Map map) { + return toJsonObjectBuilder(map); + } + if (o instanceof Collection col) { + return toJsonArrayBuilder(col); + } + return o; + } + + private static void logMap(Map map, int offset) { + final String offsetString = " ".repeat(Math.max(0, offset)); + for (var entry : map.entrySet()) { + if (entry.getValue() instanceof Map) { + System.out.println(offsetString + entry.getKey()); + logMap((Map) entry.getValue(), offset + 2); + } else if (entry.getValue() instanceof Collection) { + System.out.println(offsetString + entry.getKey()); + logCollection((Collection) entry.getValue(), offset + 2); + } else { + System.out.println(offsetString + entry.getKey() + ": " + entry.getValue()); + } + } + } + + private static void logCollection(Collection col, int offset) { + final String offsetString = " ".repeat(Math.max(0, offset)); + for (var entry : col) { + if (entry instanceof Map) { + logMap((Map) entry, offset + 2); + } else if (entry instanceof Collection) { + logCollection((Collection) entry, offset + 2); + } else { + System.out.println(offsetString + entry); + } + } + } + + private static class JsonCollectionFactory implements MappableCollectionFactory { + @Override + public Map newMap() { + return Json.object(); + } + + @Override + public Map newMap(int initialCapacity) { + return Json.object(initialCapacity); + } + + @Override + public Collection newCollection() { + return Json.array(); + } + + @Override + public Collection newCollection(int initialCapacity) { + return Json.array(initialCapacity); + } + } +} diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java index 3096df893caa4..ed9acf2545bf6 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java @@ -7,10 +7,10 @@ import org.jboss.logging.Logger; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.bootstrap.resolver.QuarkusGradleModelFactory; -import io.quarkus.bootstrap.util.BootstrapUtils; /** * Helper class used to expose build tool used by the project @@ -115,7 +115,7 @@ public static ApplicationModel enableGradleAppModel(Path projectRoot, String mod final ApplicationModel model = QuarkusGradleModelFactory.create( getBuildFile(projectRoot, BuildTool.GRADLE).toFile(), mode, jvmArgs, tasks); - BootstrapUtils.exportModel(model, "TEST".equalsIgnoreCase(mode)); + ApplicationModelSerializer.exportGradleModel(model, "TEST".equalsIgnoreCase(mode)); return model; } return null; @@ -126,7 +126,7 @@ public static ApplicationModel enableGradleAppModelForDevMode(Path projectRoot) if (isGradleProject(projectRoot)) { final ApplicationModel model = QuarkusGradleModelFactory .createForTasks(getBuildFile(projectRoot, BuildTool.GRADLE).toFile(), DEVMODE_REQUIRED_TASKS); - BootstrapUtils.exportModel(model, false); + ApplicationModelSerializer.exportGradleModel(model, false); return model; } return null; diff --git a/independent-projects/bootstrap/json/pom.xml b/independent-projects/bootstrap/json/pom.xml new file mode 100644 index 0000000000000..ccbe0dee01cab --- /dev/null +++ b/independent-projects/bootstrap/json/pom.xml @@ -0,0 +1,83 @@ + + + + quarkus-bootstrap-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-bootstrap-json + Quarkus - Bootstrap - JSON + + + + + io.quarkus + quarkus-bootstrap-bom + ${project.version} + pom + import + + + io.quarkus + quarkus-bootstrap-bom-test + ${project.version} + pom + import + + + + + + org.junit.jupiter + junit-jupiter + test + + + io.quarkus + quarkus-bootstrap-bom + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-bootstrap-bom-test + ${project.version} + pom + test + + + * + * + + + + + + + + io.smallrye + jandex-maven-plugin + + + make-index + + jandex + + + + + + + diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/Json.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/Json.java new file mode 100644 index 0000000000000..d2357e9aa0821 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/Json.java @@ -0,0 +1,586 @@ +package io.quarkus.bootstrap.json; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A simple JSON string generator. + */ +public final class Json { + + private static final String OBJECT_START = "{"; + private static final String OBJECT_END = "}"; + private static final String ARRAY_START = "["; + private static final String ARRAY_END = "]"; + private static final String NAME_VAL_SEPARATOR = ":"; + private static final String ENTRY_SEPARATOR = ","; + + private static final int CONTROL_CHAR_START = 0; + private static final int CONTROL_CHAR_END = 0x1f; + private static final char CHAR_QUOTATION_MARK = '"'; + + private static final Map REPLACEMENTS; + + static { + REPLACEMENTS = new HashMap<>(); + // control characters + for (int i = CONTROL_CHAR_START; i <= CONTROL_CHAR_END; i++) { + REPLACEMENTS.put((char) i, String.format("\\u%04x", i)); + } + // quotation mark + REPLACEMENTS.put('"', "\\\""); + // reverse solidus + REPLACEMENTS.put('\\', "\\\\"); + } + + private Json() { + } + + /** + * @return the new JSON array builder, empty builders are not ignored + */ + public static JsonArrayBuilder array() { + return new JsonArrayBuilder(false); + } + + /** + * @param initialCapacity initial underlying array capacity + * @return the new JSON array builder, empty builders are not ignored + */ + public static JsonArrayBuilder array(int initialCapacity) { + return new JsonArrayBuilder(false, initialCapacity); + } + + /** + * @param ignoreEmptyBuilders + * @return the new JSON array builder + * @see JsonBuilder#ignoreEmptyBuilders + */ + public static JsonArrayBuilder array(boolean ignoreEmptyBuilders) { + return new JsonArrayBuilder(ignoreEmptyBuilders); + } + + /** + * @param ignoreEmptyBuilders + * @param initialCapacity initial underlying array capacity + * @return the new JSON array builder + * @see JsonBuilder#ignoreEmptyBuilders + */ + public static JsonArrayBuilder array(boolean ignoreEmptyBuilders, int initialCapacity) { + return new JsonArrayBuilder(ignoreEmptyBuilders, initialCapacity); + } + + /** + * @return the new JSON object builder, empty builders are not ignored + */ + public static JsonObjectBuilder object() { + return new JsonObjectBuilder(false); + } + + /** + * @param initialCapacity initial underlying map capacity + * @return the new JSON object builder, empty builders are not ignored + */ + public static JsonObjectBuilder object(int initialCapacity) { + return new JsonObjectBuilder(false, initialCapacity); + } + + /** + * @param ignoreEmptyBuilders + * @return the new JSON object builder + * @see JsonBuilder#ignoreEmptyBuilders + */ + public static JsonObjectBuilder object(boolean ignoreEmptyBuilders) { + return new JsonObjectBuilder(ignoreEmptyBuilders); + } + + /** + * @param ignoreEmptyBuilders + * @param initialCapacity initial underlying map capacity + * @return the new JSON object builder + * @see JsonBuilder#ignoreEmptyBuilders + */ + public static JsonObjectBuilder object(boolean ignoreEmptyBuilders, int initialCapacity) { + return new JsonObjectBuilder(ignoreEmptyBuilders, initialCapacity); + } + + public abstract static class JsonBuilder { + + protected final boolean ignoreEmptyBuilders; + protected JsonTransform transform; + + /** + * @param ignoreEmptyBuilders If set to true all empty builders added to this builder will be ignored during + * {@link #appendTo(Appendable)} + */ + JsonBuilder(boolean ignoreEmptyBuilders) { + this.ignoreEmptyBuilders = ignoreEmptyBuilders; + } + + /** + * @return true if there are no elements/properties, false otherwise + */ + abstract boolean isEmpty(); + + abstract void appendTo(Appendable appendable) throws IOException; + + /** + * @param value value to check + * @return true if the value is null or an empty builder and {@link #ignoreEmptyBuilders} is set to + * true, false + * otherwise + */ + protected boolean isIgnored(Object value) { + return value == null + || (ignoreEmptyBuilders && value instanceof JsonBuilder jsonBuilder && jsonBuilder.isEmpty()); + } + + protected boolean isValuesEmpty(Collection values) { + if (values.isEmpty()) { + return true; + } + for (Object object : values) { + if (!(object instanceof JsonBuilder jsonBuilder) || !jsonBuilder.isEmpty()) { + return false; + } + } + return true; + + } + + protected abstract T self(); + + abstract void add(JsonValue element); + + void setTransform(JsonTransform transform) { + this.transform = transform; + } + + public void transform(JsonMultiValue value, JsonTransform transform) { + final ResolvedTransform resolved = new ResolvedTransform(this, transform); + value.forEach(resolved); + } + } + + /** + * JSON array builder. + */ + public static class JsonArrayBuilder extends JsonBuilder implements Collection { + + private final List values; + + private JsonArrayBuilder(boolean ignoreEmptyBuilders) { + super(ignoreEmptyBuilders); + this.values = new ArrayList<>(); + } + + private JsonArrayBuilder(boolean ignoreEmptyBuilders, int initialCapacity) { + super(ignoreEmptyBuilders); + this.values = new ArrayList<>(initialCapacity); + } + + public JsonArrayBuilder add(JsonArrayBuilder value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(JsonObjectBuilder value) { + addInternal(value); + return this; + } + + public JsonArrayBuilder add(String value) { + addInternal(value); + return this; + } + + JsonArrayBuilder add(boolean value) { + addInternal(value); + return this; + } + + JsonArrayBuilder add(int value) { + addInternal(value); + return this; + } + + JsonArrayBuilder add(long value) { + addInternal(value); + return this; + } + + JsonArrayBuilder addAll(List value) { + if (value != null && !value.isEmpty()) { + values.addAll(value); + } + return this; + } + + void addInternal(Object value) { + if (value != null) { + values.add(value); + } + } + + @Override + public int size() { + return values.size(); + } + + public boolean isEmpty() { + return isValuesEmpty(values); + } + + @Override + public boolean contains(Object o) { + return values.contains(o); + } + + @Override + public Iterator iterator() { + return values.iterator(); + } + + @Override + public Object[] toArray() { + return values.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return values.toArray(a); + } + + @Override + public boolean add(Object o) { + addInternal(o); + return o != null; + } + + @Override + public boolean remove(Object o) { + return values.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return values.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + for (Object o : c) { + addInternal(o); + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + return values.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return values.retainAll(c); + } + + @Override + public void clear() { + values.clear(); + } + + @Override + public void appendTo(Appendable appendable) throws IOException { + appendable.append(ARRAY_START); + int idx = 0; + for (Object value : values) { + if (isIgnored(value)) { + continue; + } + if (++idx > 1) { + appendable.append(ENTRY_SEPARATOR); + } + appendValue(appendable, value); + } + appendable.append(ARRAY_END); + } + + @Override + protected JsonArrayBuilder self() { + return this; + } + + @Override + void add(JsonValue element) { + if (element instanceof JsonString jsonStr) { + add(jsonStr.value()); + } else if (element instanceof JsonInteger jsonInt) { + final long longValue = jsonInt.longValue(); + final int intValue = (int) longValue; + if (longValue == intValue) { + add(intValue); + } else { + add(longValue); + } + } else if (element instanceof JsonBoolean jsonBoolean) { + add(jsonBoolean.value()); + } else if (element instanceof JsonArray jsonArray) { + final JsonArrayBuilder arrayBuilder = Json.array(ignoreEmptyBuilders); + arrayBuilder.transform(jsonArray, transform); + if (!arrayBuilder.isEmpty()) { + add(arrayBuilder); + } + } else if (element instanceof JsonObject jsonObj) { + final JsonObjectBuilder objectBuilder = Json.object(ignoreEmptyBuilders); + objectBuilder.transform(jsonObj, transform); + if (!objectBuilder.isEmpty()) { + add(objectBuilder); + } + } + } + } + + /** + * JSON object builder. + */ + public static class JsonObjectBuilder extends JsonBuilder implements Map { + + private final Map properties; + + private JsonObjectBuilder(boolean ignoreEmptyBuilders) { + super(ignoreEmptyBuilders); + this.properties = new HashMap<>(); + } + + private JsonObjectBuilder(boolean ignoreEmptyBuilders, int initialCapacity) { + super(ignoreEmptyBuilders); + this.properties = new HashMap<>(initialCapacity); + } + + public JsonObjectBuilder put(String name, String value) { + putInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, JsonObjectBuilder value) { + putInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, JsonArrayBuilder value) { + putInternal(name, value); + return this; + } + + public JsonObjectBuilder put(String name, boolean value) { + putInternal(name, value); + return this; + } + + JsonObjectBuilder put(String name, int value) { + putInternal(name, value); + return this; + } + + JsonObjectBuilder put(String name, long value) { + putInternal(name, value); + return this; + } + + boolean has(String name) { + return properties.containsKey(name); + } + + Object putInternal(String name, Object value) { + Objects.requireNonNull(name); + if (value != null) { + return properties.put(name, value); + } + return null; + } + + @Override + public int size() { + return properties.size(); + } + + public boolean isEmpty() { + if (properties.isEmpty()) { + return true; + } + return isValuesEmpty(properties.values()); + } + + @Override + public boolean containsKey(Object key) { + return properties.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return properties.containsValue(value); + } + + @Override + public Object get(Object key) { + return properties.get(key); + } + + @Override + public Object put(String key, Object value) { + return putInternal(key, value); + } + + @Override + public Object remove(Object key) { + return properties.remove(key); + } + + @Override + public void putAll(Map m) { + for (Entry entry : m.entrySet()) { + putInternal(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + properties.clear(); + } + + @Override + public Set keySet() { + return properties.keySet(); + } + + @Override + public Collection values() { + return properties.values(); + } + + @Override + public Set> entrySet() { + return properties.entrySet(); + } + + @Override + public void appendTo(Appendable appendable) throws IOException { + appendable.append(OBJECT_START); + int idx = 0; + for (Entry entry : properties.entrySet()) { + if (isIgnored(entry.getValue())) { + continue; + } + if (++idx > 1) { + appendable.append(ENTRY_SEPARATOR); + } + appendStringValue(appendable, entry.getKey()); + appendable.append(NAME_VAL_SEPARATOR); + appendValue(appendable, entry.getValue()); + } + appendable.append(OBJECT_END); + } + + @Override + protected JsonObjectBuilder self() { + return this; + } + + @Override + void add(JsonValue element) { + if (element instanceof JsonMember member) { + final String attribute = member.attribute().value(); + final JsonValue value = member.value(); + if (value instanceof JsonString jsonStr) { + put(attribute, jsonStr.value()); + } else if (value instanceof JsonInteger jsonInt) { + final long longValue = jsonInt.longValue(); + final int intValue = (int) longValue; + if (longValue == intValue) { + put(attribute, intValue); + } else { + put(attribute, longValue); + } + } else if (value instanceof JsonBoolean jsonBool) { + put(attribute, jsonBool.value()); + } else if (value instanceof JsonArray jsonArr) { + final JsonArrayBuilder arrayBuilder = Json.array(ignoreEmptyBuilders); + arrayBuilder.transform(jsonArr, transform); + if (!arrayBuilder.isEmpty()) { + put(attribute, arrayBuilder); + } + } else if (value instanceof JsonObject jsonObj) { + final JsonObjectBuilder objectBuilder = Json.object(ignoreEmptyBuilders); + objectBuilder.transform(jsonObj, transform); + if (!objectBuilder.isEmpty()) { + put(attribute, objectBuilder); + } + } + } + } + } + + static void appendValue(Appendable appendable, Object value) throws IOException { + if (value instanceof JsonObjectBuilder jsonObj) { + jsonObj.appendTo(appendable); + } else if (value instanceof JsonArrayBuilder jsonArr) { + jsonArr.appendTo(appendable); + } else if (value instanceof String str) { + appendStringValue(appendable, str); + } else if (value instanceof Boolean || value instanceof Integer || value instanceof Long) { + appendable.append(value.toString()); + } else { + throw new IllegalStateException("Unsupported value type: " + value); + } + } + + static void appendStringValue(Appendable appendable, String value) throws IOException { + appendable.append(CHAR_QUOTATION_MARK); + appendEscaped(appendable, value); + appendable.append(CHAR_QUOTATION_MARK); + } + + /** + * Escape quotation mark, reverse solidus and control characters (U+0000 through U+001F). + * + * @param value value to escape + * @see https://www.ietf.org/rfc/rfc4627.txt + */ + static void appendEscaped(Appendable appendable, String value) throws IOException { + for (int i = 0; i < value.length(); i++) { + final char c = value.charAt(i); + final String replacement = REPLACEMENTS.get(c); + if (replacement != null) { + appendable.append(replacement); + } else { + appendable.append(c); + } + } + } + + private static final class ResolvedTransform implements JsonTransform { + private final JsonBuilder resolvedBuilder; + private final JsonTransform transform; + + private ResolvedTransform(JsonBuilder resolvedBuilder, JsonTransform transform) { + this.resolvedBuilder = resolvedBuilder; + this.resolvedBuilder.setTransform(transform); + this.transform = transform; + } + + @Override + public void accept(JsonBuilder builder, JsonValue element) { + if (builder == null) { + transform.accept(resolvedBuilder, element); + } + } + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonArray.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonArray.java new file mode 100644 index 0000000000000..30f02dd002aa8 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonArray.java @@ -0,0 +1,30 @@ +package io.quarkus.bootstrap.json; + +import java.util.List; +import java.util.stream.Stream; + +public final class JsonArray implements JsonMultiValue { + private final List value; + + public JsonArray(List value) { + this.value = value; + } + + public List value() { + return value; + } + + @SuppressWarnings("unchecked") + public Stream stream() { + return (Stream) value.stream(); + } + + @Override + public void forEach(JsonTransform transform) { + value.forEach(v -> transform.accept(null, v)); + } + + public int size() { + return value.size(); + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonBoolean.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonBoolean.java new file mode 100644 index 0000000000000..7116746b89c46 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonBoolean.java @@ -0,0 +1,16 @@ +package io.quarkus.bootstrap.json; + +public enum JsonBoolean implements JsonValue { + TRUE(true), + FALSE(false); + + private final boolean value; + + JsonBoolean(boolean value) { + this.value = value; + } + + public boolean value() { + return value; + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonDouble.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonDouble.java new file mode 100644 index 0000000000000..26dc8d3e04ca6 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonDouble.java @@ -0,0 +1,13 @@ +package io.quarkus.bootstrap.json; + +public final class JsonDouble implements JsonNumber { + private final double value; + + public JsonDouble(double value) { + this.value = value; + } + + public double value() { + return value; + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonInteger.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonInteger.java new file mode 100644 index 0000000000000..6843fc8e1bf50 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonInteger.java @@ -0,0 +1,22 @@ +package io.quarkus.bootstrap.json; + +public final class JsonInteger implements JsonNumber { + private final long value; + + public JsonInteger(long value) { + this.value = value; + } + + public long longValue() { + return value; + } + + public int intValue() { + return (int) value; + } + + @Override + public String toString() { + return Long.toString(value); + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonMember.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonMember.java new file mode 100644 index 0000000000000..a6a6551e0e82e --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonMember.java @@ -0,0 +1,19 @@ +package io.quarkus.bootstrap.json; + +public final class JsonMember implements JsonValue { + private final JsonString attribute; + private final JsonValue value; + + public JsonMember(JsonString attribute, JsonValue value) { + this.attribute = attribute; + this.value = value; + } + + public JsonString attribute() { + return attribute; + } + + public JsonValue value() { + return value; + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonMultiValue.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonMultiValue.java new file mode 100644 index 0000000000000..013ef19262c5a --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonMultiValue.java @@ -0,0 +1,7 @@ +package io.quarkus.bootstrap.json; + +public interface JsonMultiValue extends JsonValue { + default void forEach(JsonTransform transform) { + transform.accept(null, this); + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonNull.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonNull.java new file mode 100644 index 0000000000000..f1a2e4dbb47be --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonNull.java @@ -0,0 +1,5 @@ +package io.quarkus.bootstrap.json; + +public enum JsonNull implements JsonValue { + INSTANCE; +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonNumber.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonNumber.java new file mode 100644 index 0000000000000..c3a955725a000 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonNumber.java @@ -0,0 +1,4 @@ +package io.quarkus.bootstrap.json; + +public interface JsonNumber extends JsonValue { +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonObject.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonObject.java new file mode 100644 index 0000000000000..330c95608b15d --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonObject.java @@ -0,0 +1,29 @@ +package io.quarkus.bootstrap.json; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class JsonObject implements JsonMultiValue { + private final Map value; + + public JsonObject(Map value) { + this.value = value; + } + + @SuppressWarnings("unchecked") + public T get(String attribute) { + return (T) value.get(new JsonString(attribute)); + } + + public List members() { + return value.entrySet().stream() + .map(e -> new JsonMember(e.getKey(), e.getValue())) + .collect(Collectors.toList()); + } + + @Override + public void forEach(JsonTransform transform) { + members().forEach(member -> transform.accept(null, member)); + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonReader.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonReader.java new file mode 100644 index 0000000000000..0e717cea2e521 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonReader.java @@ -0,0 +1,347 @@ +package io.quarkus.bootstrap.json; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A json format reader. + * It follows the ECMA-404 The JSON Data Interchange Standard.. + */ +public class JsonReader { + + private final String text; + private final int length; + private int position; + + private JsonReader(String text) { + this.text = text; + this.length = text.length(); + } + + public static JsonReader of(String source) { + return new JsonReader(source); + } + + @SuppressWarnings("unchecked") + public T read() { + return (T) readElement(); + } + + /** + * element + * |---- ws value ws + */ + private JsonValue readElement() { + ignoreWhitespace(); + JsonValue result = readValue(); + ignoreWhitespace(); + return result; + } + + /** + * value + * |---- object + * |---- array + * |---- string + * |---- number + * |---- "true" + * |---- "false" + * |---- "null" + */ + private JsonValue readValue() { + final int ch = peekChar(); + if (ch < 0) { + throw new IllegalArgumentException("Unable to fully read json value"); + } + + switch (ch) { + case '{': + return readObject(); + case '[': + return readArray(); + case '"': + return readString(); + case 't': + return readConstant("true", JsonBoolean.TRUE); + case 'f': + return readConstant("false", JsonBoolean.FALSE); + case 'n': + return readConstant("null", JsonNull.INSTANCE); + default: + if (Character.isDigit(ch) || '-' == ch) { + return readNumber(position); + } + throw new IllegalArgumentException("Unknown start character for json value: " + ch); + } + } + + /** + * object + * |---- '{' ws '}' + * |---- '{' members '}' + *

+ * members + * |----- member + * |----- member ',' members + */ + private JsonValue readObject() { + position++; + + Map members = new HashMap<>(); + + while (position < length) { + ignoreWhitespace(); + switch (peekChar()) { + case '}': + position++; + return new JsonObject(members); + case ',': + position++; + break; + case '"': + readMember(members); + break; + } + } + + throw new IllegalArgumentException("Json object ended without }"); + } + + /** + * member + * |----- ws string ws ':' element + */ + private void readMember(Map members) { + final JsonString attribute = readString(); + ignoreWhitespace(); + final int colon = nextChar(); + if (':' != colon) { + throw new IllegalArgumentException("Expected : after attribute"); + } + final JsonValue element = readElement(); + members.put(attribute, element); + } + + /** + * array + * |---- '[' ws ']' + * |---- '[' elements ']' + *

+ * elements + * |----- element + * |----- element ',' elements + */ + private JsonValue readArray() { + position++; + + final List elements = new ArrayList<>(); + + while (position < length) { + ignoreWhitespace(); + switch (peekChar()) { + case ']': + position++; + return new JsonArray(elements); + case ',': + position++; + break; + default: + elements.add(readElement()); + break; + } + } + + throw new IllegalArgumentException("Json array ended without ]"); + } + + /** + * string + * |---- '"' characters '"' + *

+ * characters + * |----- "" + * |----- character characters + *

+ * character + * |----- '0020' . '10FFFF' - '"' - '\' + * |----- '\' escape + * |----- escape + * |----- '"' + * |----- '\' + * |----- '/' + * |----- 'b' + * |----- 'f' + * |----- 'n' + * |----- 'r' + * |----- 't' + * |----- 'u' hex hex hex hex + */ + private JsonString readString() { + position++; + + int start = position; + // Substring on string values that contain unicode characters won't work, + // because there are more characters read than actual characters represented. + // Use StringBuilder to buffer any string read up to unicode, + // then add unicode values into it and continue as usual. + StringBuilder unescapedValue = null; + + while (position < length) { + final int ch = nextChar(); + + if (Character.isISOControl(ch)) { + throw new IllegalArgumentException("Control characters not allowed in json string"); + } + + if ('"' == ch) { + final String value; + if (unescapedValue == null) { + value = text.substring(start, position - 1); + } else { + value = unescapedValue.toString(); + } + // End of string + return new JsonString(value); + } + + if ('\\' == ch) { + if (unescapedValue == null) { + unescapedValue = new StringBuilder().append(text, start, position - 1); + } + final int escaped = nextChar(); + if (escaped == 'u') { + unescapedValue.append(readUnicode()); + } else { + unescapedValue.appendCodePoint(unescape(escaped)); + } + } else if (unescapedValue != null) { + unescapedValue.appendCodePoint(ch); + } + } + + throw new IllegalArgumentException("String not closed"); + } + + private static int unescape(int ch) { + return switch (ch) { + case 'b' -> '\b'; + case 'n' -> '\n'; + case 't' -> '\t'; + case 'f' -> '\f'; + case 'r' -> '\r'; + default -> ch; + }; + } + + private char readUnicode() { + final int digit1 = Character.digit(nextChar(), 16); + final int digit2 = Character.digit(nextChar(), 16); + final int digit3 = Character.digit(nextChar(), 16); + final int digit4 = Character.digit(nextChar(), 16); + return (char) (digit1 << 12 | digit2 << 8 | digit3 << 4 | digit4); + } + + /** + * number + * |---- integer fraction exponent + */ + private JsonValue readNumber(int numStartIndex) { + final boolean isFraction = skipToEndOfNumber(); + final String number = text.substring(numStartIndex, position); + return isFraction + ? new JsonDouble(Double.parseDouble(number)) + : new JsonInteger(Long.parseLong(number)); + } + + private boolean skipToEndOfNumber() { + // Find the end of a number then parse with library methods + int ch = nextChar(); + if ('-' == ch) { + ch = nextChar(); + } + + if (Character.isDigit(ch) && '0' != ch) { + ignoreDigits(); + } + + boolean isFraction = false; + ch = peekChar(); + if ('.' == ch) { + isFraction = true; + position++; + ignoreDigits(); + } + + ch = peekChar(); + switch (ch) { + case 'e': + case 'E': + position++; + ch = nextChar(); + switch (ch) { + case '-': + case '+': + position++; + } + ignoreDigits(); + } + + return isFraction; + } + + private void ignoreDigits() { + while (position < length) { + final int ch = peekChar(); + if (!Character.isDigit(ch)) { + break; + } + position++; + } + } + + private JsonValue readConstant(String expected, JsonValue result) { + if (text.regionMatches(position, expected, 0, expected.length())) { + position += expected.length(); + return result; + } + throw new IllegalArgumentException("Unable to read json constant for: " + expected); + } + + /** + * ws + * |---- "" + * |---- '0020' ws + * |---- '000A' ws + * |---- '000D' ws + * |---- '0009' ws + */ + private void ignoreWhitespace() { + while (position < length) { + final int ch = peekChar(); + switch (ch) { + case ' ': // '0020' SPACE + case '\n': // '000A' LINE FEED + case '\r': // '000D' CARRIAGE RETURN + case '\t': // '0009' CHARACTER TABULATION + position++; + break; + default: + return; + } + } + } + + private int peekChar() { + return position < length + ? text.charAt(position) + : -1; + } + + private int nextChar() { + final int ch = peekChar(); + position++; + return ch; + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonString.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonString.java new file mode 100644 index 0000000000000..ab82d27bd5d05 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonString.java @@ -0,0 +1,36 @@ +package io.quarkus.bootstrap.json; + +import java.util.Objects; + +public final class JsonString implements JsonValue { + + private final String value; + + public JsonString(String value) { + this.value = value; + } + + public String value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + JsonString that = (JsonString) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return value; + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonTransform.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonTransform.java new file mode 100644 index 0000000000000..bf67ac1f20a94 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonTransform.java @@ -0,0 +1,15 @@ +package io.quarkus.bootstrap.json; + +import java.util.function.Predicate; + +@FunctionalInterface +public interface JsonTransform { + void accept(Json.JsonBuilder builder, JsonValue element); + + static JsonTransform dropping(Predicate filter) { + return (builder, element) -> { + if (!filter.test(element)) + builder.add(element); + }; + } +} diff --git a/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonValue.java b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonValue.java new file mode 100644 index 0000000000000..947f3d5b1e7e4 --- /dev/null +++ b/independent-projects/bootstrap/json/src/main/java/io/quarkus/bootstrap/json/JsonValue.java @@ -0,0 +1,4 @@ +package io.quarkus.bootstrap.json; + +public interface JsonValue { +} diff --git a/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonDeserializerTest.java b/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonDeserializerTest.java new file mode 100644 index 0000000000000..ea8e71131b2d1 --- /dev/null +++ b/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonDeserializerTest.java @@ -0,0 +1,241 @@ +package io.quarkus.bootstrap.json; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class JsonDeserializerTest { + + @Test + void testSimpleObject() { + JsonObject obj = JsonReader.of("{\"name\":\"John\",\"age\":30}").read(); + assertNotNull(obj); + assertEquals("John", ((JsonString) obj.get("name")).value()); + assertEquals(30L, ((JsonInteger) obj.get("age")).longValue()); + } + + @Test + void testSimpleArray() { + JsonArray arr = JsonReader.of("[\"apple\",\"banana\",\"cherry\"]").read(); + assertNotNull(arr); + assertEquals(3, arr.size()); + assertEquals("apple", ((JsonString) arr.value().get(0)).value()); + assertEquals("banana", ((JsonString) arr.value().get(1)).value()); + assertEquals("cherry", ((JsonString) arr.value().get(2)).value()); + } + + @Test + void testNestedObjects() { + JsonObject obj = JsonReader.of("{\"user\":{\"name\":\"Alice\",\"email\":\"alice@example.com\"},\"active\":true}") + .read(); + assertNotNull(obj); + JsonObject user = (JsonObject) obj.get("user"); + assertEquals("Alice", ((JsonString) user.get("name")).value()); + assertEquals("alice@example.com", ((JsonString) user.get("email")).value()); + assertTrue(((JsonBoolean) obj.get("active")).value()); + } + + @Test + void testNestedArrays() { + JsonArray arr = JsonReader.of("[[1,2],[3,4]]").read(); + assertNotNull(arr); + assertEquals(2, arr.size()); + JsonArray first = (JsonArray) arr.value().get(0); + JsonArray second = (JsonArray) arr.value().get(1); + assertEquals(1L, ((JsonInteger) first.value().get(0)).longValue()); + assertEquals(2L, ((JsonInteger) first.value().get(1)).longValue()); + assertEquals(3L, ((JsonInteger) second.value().get(0)).longValue()); + assertEquals(4L, ((JsonInteger) second.value().get(1)).longValue()); + } + + @Test + void testMixedTypes() { + JsonObject obj = JsonReader + .of("{\"string\":\"value\",\"integer\":42,\"long\":9876543210,\"boolean\":false}").read(); + assertNotNull(obj); + assertEquals("value", ((JsonString) obj.get("string")).value()); + assertEquals(42L, ((JsonInteger) obj.get("integer")).longValue()); + assertEquals(9876543210L, ((JsonInteger) obj.get("long")).longValue()); + assertFalse(((JsonBoolean) obj.get("boolean")).value()); + } + + @Test + void testEmptyObject() { + JsonObject obj = JsonReader.of("{}").read(); + assertNotNull(obj); + assertEquals(0, obj.members().size()); + } + + @Test + void testEmptyArray() { + JsonArray arr = JsonReader.of("[]").read(); + assertNotNull(arr); + assertEquals(0, arr.size()); + } + + @Test + void testStringEscapingQuotes() { + JsonObject obj = JsonReader.of("{\"message\":\"He said \\\"Hello\\\"\"}").read(); + assertEquals("He said \"Hello\"", ((JsonString) obj.get("message")).value()); + } + + @Test + void testStringEscapingBackslash() { + JsonObject obj = JsonReader.of("{\"path\":\"C:\\\\Users\\\\John\"}").read(); + assertEquals("C:\\Users\\John", ((JsonString) obj.get("path")).value()); + } + + @Test + void testStringEscapingControlCharacters() { + JsonObject obj = JsonReader.of("{\"text\":\"Line1\\nLine2\\tTabbed\\rReturn\"}").read(); + assertEquals("Line1\nLine2\tTabbed\rReturn", ((JsonString) obj.get("text")).value()); + } + + @Test + void testStringEscapingUnicodeSequences() { + JsonObject obj = JsonReader.of("{\"text\":\"\\u0048\\u0065\\u006c\\u006c\\u006f\"}").read(); + assertEquals("Hello", ((JsonString) obj.get("text")).value()); + } + + @Test + void testStringEscapingAllControlCharacters() { + String json = "{\"controls\":\"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" + + "\\u0008\\u0009\\u000a\\u000b\\u000c\\u000d\\u000e\\u000f" + + "\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" + + "\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\"}"; + JsonObject obj = JsonReader.of(json).read(); + String value = ((JsonString) obj.get("controls")).value(); + assertEquals(32, value.length()); + for (int i = 0; i <= 0x1f; i++) { + assertEquals((char) i, value.charAt(i)); + } + } + + @Test + void testStringWithSpecialCharacters() { + JsonObject obj = JsonReader.of("{\"special\":\"äöü ñ €\"}").read(); + assertEquals("äöü ñ €", ((JsonString) obj.get("special")).value()); + } + + @Test + void testEmptyString() { + JsonObject obj = JsonReader.of("{\"empty\":\"\"}").read(); + assertEquals("", ((JsonString) obj.get("empty")).value()); + } + + @Test + void testArrayWithMixedTypes() { + JsonArray arr = JsonReader.of("[\"text\",123,true,{\"key\":\"value\"}]").read(); + assertEquals(4, arr.size()); + assertEquals("text", ((JsonString) arr.value().get(0)).value()); + assertEquals(123L, ((JsonInteger) arr.value().get(1)).longValue()); + assertTrue(((JsonBoolean) arr.value().get(2)).value()); + JsonObject obj = (JsonObject) arr.value().get(3); + assertEquals("value", ((JsonString) obj.get("key")).value()); + } + + @Test + void testComplexNestedStructure() { + JsonObject obj = JsonReader + .of("{\"users\":[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}],\"count\":2}").read(); + JsonArray users = (JsonArray) obj.get("users"); + assertEquals(2, users.size()); + JsonObject alice = (JsonObject) users.value().get(0); + assertEquals(1L, ((JsonInteger) alice.get("id")).longValue()); + assertEquals("Alice", ((JsonString) alice.get("name")).value()); + JsonObject bob = (JsonObject) users.value().get(1); + assertEquals(2L, ((JsonInteger) bob.get("id")).longValue()); + assertEquals("Bob", ((JsonString) bob.get("name")).value()); + assertEquals(2L, ((JsonInteger) obj.get("count")).longValue()); + } + + @Test + void testWhitespaceHandling() { + JsonObject obj = JsonReader.of(" { \"name\" : \"John\" , \"age\" : 30 } ").read(); + assertEquals("John", ((JsonString) obj.get("name")).value()); + assertEquals(30L, ((JsonInteger) obj.get("age")).longValue()); + } + + @Test + void testNullValue() { + JsonObject obj = JsonReader.of("{\"value\":null}").read(); + assertInstanceOf(JsonNull.class, obj.get("value")); + } + + @Test + void testBooleanValues() { + JsonObject obj = JsonReader.of("{\"trueVal\":true,\"falseVal\":false}").read(); + assertTrue(((JsonBoolean) obj.get("trueVal")).value()); + assertFalse(((JsonBoolean) obj.get("falseVal")).value()); + } + + @Test + void testNumberFormats() { + JsonObject obj = JsonReader.of("{\"int\":42,\"negative\":-17,\"zero\":0,\"decimal\":3.14,\"exp\":1.0e10}") + .read(); + assertEquals(42L, ((JsonInteger) obj.get("int")).longValue()); + assertEquals(-17L, ((JsonInteger) obj.get("negative")).longValue()); + assertEquals(0L, ((JsonInteger) obj.get("zero")).longValue()); + assertEquals(3.14, ((JsonDouble) obj.get("decimal")).value(), 0.001); + assertEquals(1.0e10, ((JsonDouble) obj.get("exp")).value(), 0.001); + } + + @Test + void testLargeNumbers() { + JsonObject obj = JsonReader + .of("{\"maxInt\":2147483647,\"minInt\":-2147483648,\"maxLong\":9223372036854775807,\"minLong\":-9223372036854775808}") + .read(); + assertEquals(Integer.MAX_VALUE, ((JsonInteger) obj.get("maxInt")).longValue()); + assertEquals(Integer.MIN_VALUE, ((JsonInteger) obj.get("minInt")).longValue()); + assertEquals(Long.MAX_VALUE, ((JsonInteger) obj.get("maxLong")).longValue()); + assertEquals(Long.MIN_VALUE, ((JsonInteger) obj.get("minLong")).longValue()); + } + + @Test + void testStringWithQuotesAndBackslashes() { + JsonObject obj = JsonReader.of("{\"complex\":\"\\\"\\\\\\\"\\\\\\\\\\\"\"}").read(); + assertEquals("\"\\\"\\\\\"", ((JsonString) obj.get("complex")).value()); + } + + @Test + void testInvalidJsonMissingClosingBrace() { + assertThrows(IllegalArgumentException.class, () -> JsonReader.of("{\"name\":\"John\"").read()); + } + + @Test + void testInvalidJsonMissingClosingBracket() { + assertThrows(IllegalArgumentException.class, () -> JsonReader.of("[1,2,3").read()); + } + + @Test + void testInvalidJsonMissingColon() { + assertThrows(IllegalArgumentException.class, () -> JsonReader.of("{\"name\" \"John\"}").read()); + } + + @Test + void testInvalidJsonUnquotedString() { + assertThrows(IllegalArgumentException.class, () -> JsonReader.of("{\"name\":John}").read()); + } + + @Test + void testInvalidJsonControlCharacterInString() { + assertThrows(IllegalArgumentException.class, () -> JsonReader.of("{\"text\":\"Line1\nLine2\"}").read()); + } + + @Test + void testStringWithForwardSlashEscape() { + JsonObject obj = JsonReader.of("{\"url\":\"https:\\/\\/example.com\"}").read(); + assertEquals("https://example.com", ((JsonString) obj.get("url")).value()); + } + + @Test + void testStringWithBackspaceAndFormFeed() { + JsonObject obj = JsonReader.of("{\"text\":\"Hello\\b\\f\"}").read(); + assertEquals("Hello\b\f", ((JsonString) obj.get("text")).value()); + } +} diff --git a/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonRoundTripTest.java b/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonRoundTripTest.java new file mode 100644 index 0000000000000..9fb094c634aa7 --- /dev/null +++ b/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonRoundTripTest.java @@ -0,0 +1,270 @@ +package io.quarkus.bootstrap.json; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class JsonRoundTripTest { + + private String toJson(Json.JsonBuilder builder) throws IOException { + StringBuilder sb = new StringBuilder(); + builder.appendTo(sb); + return sb.toString(); + } + + @Test + void testSimpleObjectRoundTrip() throws IOException { + Json.JsonObjectBuilder builder = Json.object() + .put("name", "John") + .put("age", 30); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String jsonAgain = toJson(Json.object() + .put("name", ((JsonString) parsed.get("name")).value()) + .put("age", (int) ((JsonInteger) parsed.get("age")).longValue())); + + assertEquals(json, jsonAgain); + } + + @Test + void testSimpleArrayRoundTrip() throws IOException { + Json.JsonArrayBuilder builder = Json.array() + .add("apple") + .add("banana") + .add("cherry"); + + String json = toJson(builder); + JsonArray parsed = JsonReader.of(json).read(); + String jsonAgain = toJson(Json.array() + .add(((JsonString) parsed.value().get(0)).value()) + .add(((JsonString) parsed.value().get(1)).value()) + .add(((JsonString) parsed.value().get(2)).value())); + + assertEquals(json, jsonAgain); + } + + @Test + void testStringEscapingRoundTrip() throws IOException { + String original = "He said \"Hello\" and used \\ backslash"; + Json.JsonObjectBuilder builder = Json.object() + .put("message", original); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String extracted = ((JsonString) parsed.get("message")).value(); + + assertEquals(original, extracted); + } + + @Test + void testControlCharactersRoundTrip() throws IOException { + String original = "Line1\nLine2\tTabbed\rReturn"; + Json.JsonObjectBuilder builder = Json.object() + .put("text", original); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String extracted = ((JsonString) parsed.get("text")).value(); + + assertEquals(original, extracted); + } + + @Test + void testAllControlCharactersRoundTrip() throws IOException { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i <= 0x1f; i++) { + sb.append((char) i); + } + String original = sb.toString(); + + Json.JsonObjectBuilder builder = Json.object() + .put("controls", original); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String extracted = ((JsonString) parsed.get("controls")).value(); + + assertEquals(original, extracted); + } + + @Test + void testSpecialCharactersRoundTrip() throws IOException { + String original = "äöü ñ € 🌍"; + Json.JsonObjectBuilder builder = Json.object() + .put("special", original); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String extracted = ((JsonString) parsed.get("special")).value(); + + assertEquals(original, extracted); + } + + @Test + void testEmptyStringRoundTrip() throws IOException { + String original = ""; + Json.JsonObjectBuilder builder = Json.object() + .put("empty", original); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String extracted = ((JsonString) parsed.get("empty")).value(); + + assertEquals(original, extracted); + } + + @Test + void testNumbersRoundTrip() throws IOException { + Json.JsonObjectBuilder builder = Json.object() + .put("int", 42) + .put("long", 9876543210L) + .put("maxInt", Integer.MAX_VALUE) + .put("minInt", Integer.MIN_VALUE) + .put("maxLong", Long.MAX_VALUE) + .put("minLong", Long.MIN_VALUE); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + + String jsonAgain = toJson(Json.object() + .put("int", (int) ((JsonInteger) parsed.get("int")).longValue()) + .put("long", ((JsonInteger) parsed.get("long")).longValue()) + .put("maxInt", (int) ((JsonInteger) parsed.get("maxInt")).longValue()) + .put("minInt", (int) ((JsonInteger) parsed.get("minInt")).longValue()) + .put("maxLong", ((JsonInteger) parsed.get("maxLong")).longValue()) + .put("minLong", ((JsonInteger) parsed.get("minLong")).longValue())); + + assertEquals(json, jsonAgain); + } + + @Test + void testBooleanRoundTrip() throws IOException { + Json.JsonObjectBuilder builder = Json.object() + .put("trueVal", true) + .put("falseVal", false); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + + String jsonAgain = toJson(Json.object() + .put("trueVal", ((JsonBoolean) parsed.get("trueVal")).value()) + .put("falseVal", ((JsonBoolean) parsed.get("falseVal")).value())); + + assertEquals(json, jsonAgain); + } + + @Test + void testNestedStructureRoundTrip() throws IOException { + Json.JsonObjectBuilder builder = Json.object() + .put("user", Json.object() + .put("name", "Alice") + .put("age", 25)) + .put("active", true); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + + JsonObject user = (JsonObject) parsed.get("user"); + String jsonAgain = toJson(Json.object() + .put("user", Json.object() + .put("name", ((JsonString) user.get("name")).value()) + .put("age", (int) ((JsonInteger) user.get("age")).longValue())) + .put("active", ((JsonBoolean) parsed.get("active")).value())); + + assertEquals(json, jsonAgain); + } + + @Test + void testComplexNestedRoundTrip() throws IOException { + Json.JsonObjectBuilder builder = Json.object() + .put("users", Json.array() + .add(Json.object() + .put("id", 1) + .put("name", "Alice")) + .add(Json.object() + .put("id", 2) + .put("name", "Bob"))) + .put("count", 2); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + + JsonArray users = (JsonArray) parsed.get("users"); + JsonObject alice = (JsonObject) users.value().get(0); + JsonObject bob = (JsonObject) users.value().get(1); + + String jsonAgain = toJson(Json.object() + .put("users", Json.array() + .add(Json.object() + .put("id", (int) ((JsonInteger) alice.get("id")).longValue()) + .put("name", ((JsonString) alice.get("name")).value())) + .add(Json.object() + .put("id", (int) ((JsonInteger) bob.get("id")).longValue()) + .put("name", ((JsonString) bob.get("name")).value()))) + .put("count", (int) ((JsonInteger) parsed.get("count")).longValue())); + + assertEquals(json, jsonAgain); + } + + @Test + void testMixedArrayRoundTrip() throws IOException { + Json.JsonArrayBuilder builder = Json.array() + .add("text") + .add(123) + .add(true) + .add(Json.object().put("key", "value")); + + String json = toJson(builder); + JsonArray parsed = JsonReader.of(json).read(); + + JsonObject obj = (JsonObject) parsed.value().get(3); + String jsonAgain = toJson(Json.array() + .add(((JsonString) parsed.value().get(0)).value()) + .add((int) ((JsonInteger) parsed.value().get(1)).longValue()) + .add(((JsonBoolean) parsed.value().get(2)).value()) + .add(Json.object().put("key", ((JsonString) obj.get("key")).value()))); + + assertEquals(json, jsonAgain); + } + + @Test + void testQuotesAndBackslashesRoundTrip() throws IOException { + String original = "\"\\\"\\\\\""; + Json.JsonObjectBuilder builder = Json.object() + .put("complex", original); + + String json = toJson(builder); + JsonObject parsed = JsonReader.of(json).read(); + String extracted = ((JsonString) parsed.get("complex")).value(); + + assertEquals(original, extracted); + } + + @Test + void testMultipleRoundTrips() throws IOException { + String original = "Test with \"quotes\", \\backslashes\\ and\ncontrol\tchars"; + Json.JsonObjectBuilder builder = Json.object() + .put("text", original); + + String json1 = toJson(builder); + JsonObject parsed1 = JsonReader.of(json1).read(); + String extracted1 = ((JsonString) parsed1.get("text")).value(); + assertEquals(original, extracted1); + + String json2 = toJson(Json.object().put("text", extracted1)); + JsonObject parsed2 = JsonReader.of(json2).read(); + String extracted2 = ((JsonString) parsed2.get("text")).value(); + assertEquals(original, extracted2); + + String json3 = toJson(Json.object().put("text", extracted2)); + JsonObject parsed3 = JsonReader.of(json3).read(); + String extracted3 = ((JsonString) parsed3.get("text")).value(); + assertEquals(original, extracted3); + + assertEquals(json1, json2); + assertEquals(json2, json3); + } +} diff --git a/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonSerializerTest.java b/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonSerializerTest.java new file mode 100644 index 0000000000000..603ea2bf9dddd --- /dev/null +++ b/independent-projects/bootstrap/json/src/test/java/io/quarkus/bootstrap/json/JsonSerializerTest.java @@ -0,0 +1,197 @@ +package io.quarkus.bootstrap.json; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class JsonSerializerTest { + + private String toJson(Json.JsonBuilder builder) throws IOException { + StringBuilder sb = new StringBuilder(); + builder.appendTo(sb); + return sb.toString(); + } + + @Test + void testSimpleObject() throws IOException { + String json = toJson(Json.object() + .put("name", "John") + .put("age", 30)); + JsonObject parsed = JsonReader.of(json).read(); + assertEquals("John", ((JsonString) parsed.get("name")).value()); + assertEquals(30L, ((JsonInteger) parsed.get("age")).longValue()); + } + + @Test + void testSimpleArray() throws IOException { + String json = toJson(Json.array() + .add("apple") + .add("banana") + .add("cherry")); + assertEquals("[\"apple\",\"banana\",\"cherry\"]", json); + } + + @Test + void testNestedObjects() throws IOException { + String json = toJson(Json.object() + .put("user", Json.object() + .put("name", "Alice") + .put("email", "alice@example.com")) + .put("active", true)); + JsonObject parsed = JsonReader.of(json).read(); + JsonObject user = (JsonObject) parsed.get("user"); + assertEquals("Alice", ((JsonString) user.get("name")).value()); + assertEquals("alice@example.com", ((JsonString) user.get("email")).value()); + assertTrue(((JsonBoolean) parsed.get("active")).value()); + } + + @Test + void testNestedArrays() throws IOException { + String json = toJson(Json.array() + .add(Json.array().add(1).add(2)) + .add(Json.array().add(3).add(4))); + assertEquals("[[1,2],[3,4]]", json); + } + + @Test + void testMixedTypes() throws IOException { + String json = toJson(Json.object() + .put("string", "value") + .put("integer", 42) + .put("long", 9876543210L) + .put("boolean", false)); + JsonObject parsed = JsonReader.of(json).read(); + assertEquals("value", ((JsonString) parsed.get("string")).value()); + assertEquals(42L, ((JsonInteger) parsed.get("integer")).longValue()); + assertEquals(9876543210L, ((JsonInteger) parsed.get("long")).longValue()); + assertFalse(((JsonBoolean) parsed.get("boolean")).value()); + } + + @Test + void testEmptyObject() throws IOException { + String json = toJson(Json.object()); + assertEquals("{}", json); + } + + @Test + void testEmptyArray() throws IOException { + String json = toJson(Json.array()); + assertEquals("[]", json); + } + + @Test + void testStringEscapingQuotes() throws IOException { + String json = toJson(Json.object() + .put("message", "He said \"Hello\"")); + assertEquals("{\"message\":\"He said \\\"Hello\\\"\"}", json); + } + + @Test + void testStringEscapingBackslash() throws IOException { + String json = toJson(Json.object() + .put("path", "C:\\Users\\John")); + assertEquals("{\"path\":\"C:\\\\Users\\\\John\"}", json); + } + + @Test + void testStringEscapingControlCharacters() throws IOException { + String json = toJson(Json.object() + .put("text", "Line1\nLine2\tTabbed\rReturn")); + assertEquals("{\"text\":\"Line1\\u000aLine2\\u0009Tabbed\\u000dReturn\"}", json); + } + + @Test + void testStringEscapingAllControlCharacters() throws IOException { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i <= 0x1f; i++) { + sb.append((char) i); + } + String json = toJson(Json.object() + .put("controls", sb.toString())); + + String expected = "{\"controls\":\"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" + + "\\u0008\\u0009\\u000a\\u000b\\u000c\\u000d\\u000e\\u000f" + + "\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" + + "\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\"}"; + assertEquals(expected, json); + } + + @Test + void testStringWithSpecialCharacters() throws IOException { + String json = toJson(Json.object() + .put("special", "äöü ñ €")); + assertEquals("{\"special\":\"äöü ñ €\"}", json); + } + + @Test + void testEmptyString() throws IOException { + String json = toJson(Json.object() + .put("empty", "")); + assertEquals("{\"empty\":\"\"}", json); + } + + @Test + void testArrayWithMixedTypes() throws IOException { + String json = toJson(Json.array() + .add("text") + .add(123) + .add(true) + .add(Json.object().put("key", "value"))); + assertEquals("[\"text\",123,true,{\"key\":\"value\"}]", json); + } + + @Test + void testComplexNestedStructure() throws IOException { + String json = toJson(Json.object() + .put("users", Json.array() + .add(Json.object() + .put("id", 1) + .put("name", "Alice")) + .add(Json.object() + .put("id", 2) + .put("name", "Bob"))) + .put("count", 2)); + JsonObject parsed = JsonReader.of(json).read(); + JsonArray users = (JsonArray) parsed.get("users"); + assertEquals(2, users.size()); + JsonObject alice = (JsonObject) users.value().get(0); + assertEquals(1L, ((JsonInteger) alice.get("id")).longValue()); + assertEquals("Alice", ((JsonString) alice.get("name")).value()); + JsonObject bob = (JsonObject) users.value().get(1); + assertEquals(2L, ((JsonInteger) bob.get("id")).longValue()); + assertEquals("Bob", ((JsonString) bob.get("name")).value()); + assertEquals(2L, ((JsonInteger) parsed.get("count")).longValue()); + } + + @Test + void testLongNumbers() throws IOException { + String json = toJson(Json.object() + .put("maxInt", Integer.MAX_VALUE) + .put("minInt", Integer.MIN_VALUE) + .put("maxLong", Long.MAX_VALUE) + .put("minLong", Long.MIN_VALUE)); + JsonObject parsed = JsonReader.of(json).read(); + assertEquals(Integer.MAX_VALUE, ((JsonInteger) parsed.get("maxInt")).longValue()); + assertEquals(Integer.MIN_VALUE, ((JsonInteger) parsed.get("minInt")).longValue()); + assertEquals(Long.MAX_VALUE, ((JsonInteger) parsed.get("maxLong")).longValue()); + assertEquals(Long.MIN_VALUE, ((JsonInteger) parsed.get("minLong")).longValue()); + } + + @Test + void testStringWithQuotesAndBackslashes() throws IOException { + String json = toJson(Json.object() + .put("complex", "\"\\\"\\\\\"")); + assertEquals("{\"complex\":\"\\\"\\\\\\\"\\\\\\\\\\\"\"}", json); + } + + @Test + void testUnicodeCharacters() throws IOException { + String json = toJson(Json.object() + .put("emoji", "Hello 🌍")); + assertEquals("{\"emoji\":\"Hello 🌍\"}", json); + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java index c8ff9e1eb849a..06752988a8453 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java @@ -26,7 +26,7 @@ import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.DefaultArtifactSources; -import io.quarkus.bootstrap.workspace.DefaultSourceDir; +import io.quarkus.bootstrap.workspace.LazySourceDir; import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.maven.dependency.ArtifactCoords; @@ -36,7 +36,6 @@ import io.quarkus.maven.dependency.ResolvedArtifactDependency; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.maven.dependency.ResolvedDependencyBuilder; -import io.quarkus.paths.DirectoryPathTree; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathFilter; import io.quarkus.paths.PathList; @@ -445,14 +444,14 @@ public WorkspaceModule toWorkspaceModule(BootstrapMavenContext ctx) { } if (addDefaultSourceSet) { moduleBuilder.addArtifactSources(new DefaultArtifactSources(ArtifactSources.MAIN, - List.of(new DefaultSourceDir(getSourcesSourcesDir(), getClassesDir(), getGeneratedSourcesDir())), + List.of(new LazySourceDir(getSourcesSourcesDir(), getClassesDir(), getGeneratedSourcesDir())), collectMainResources(null))); } } if (!moduleBuilder.hasTestSources()) { // FIXME: do tests have generated sources? moduleBuilder.addArtifactSources(new DefaultArtifactSources(ArtifactSources.TEST, - List.of(new DefaultSourceDir(getTestSourcesSourcesDir(), getTestClassesDir(), null)), + List.of(new LazySourceDir(getTestSourcesSourcesDir(), getTestClassesDir(), null)), collectTestResources(null))); } } @@ -587,10 +586,10 @@ private DefaultArtifactSources processJarPluginExecutionConfig(Object config, bo final PathFilter filter = includes == null && excludes == null ? null : new PathFilter(includes, excludes); final String classifier = getClassifier(dom, test); final Collection sources = List.of( - new DefaultSourceDir(new DirectoryPathTree(test ? getTestSourcesSourcesDir() : getSourcesSourcesDir()), - new DirectoryPathTree(test ? getTestClassesDir() : getClassesDir(), filter), + new LazySourceDir(test ? getTestSourcesSourcesDir() : getSourcesSourcesDir(), null, + test ? getTestClassesDir() : getClassesDir(), filter, // FIXME: wrong for tests - new DirectoryPathTree(getGeneratedSourcesDir(), filter), + getGeneratedSourcesDir(), Map.of())); final Collection resources = test ? collectTestResources(filter) : collectMainResources(filter); return new DefaultArtifactSources(classifier, sources, resources); @@ -620,23 +619,24 @@ private Collection collectMainResources(PathFilter filter) { final Path classesDir = getClassesDir(); final Path generatedSourcesDir = getGeneratedSourcesDir(); if (resources.isEmpty()) { - return List.of(new DefaultSourceDir( - new DirectoryPathTree(resolveRelativeToBaseDir(null, SRC_MAIN_RESOURCES)), - new DirectoryPathTree(classesDir, filter), - new DirectoryPathTree(generatedSourcesDir, filter), + return List.of(new LazySourceDir( + resolveRelativeToBaseDir(null, SRC_MAIN_RESOURCES), null, + classesDir, filter, + generatedSourcesDir, Map.of())); } final List sourceDirs = new ArrayList<>(resources.size()); for (Resource r : resources) { sourceDirs.add( - new DefaultSourceDir( - new DirectoryPathTree(resolveRelativeToBaseDir(r.getDirectory(), SRC_MAIN_RESOURCES)), - new DirectoryPathTree((r.getTargetPath() == null ? classesDir - : resolveRelativeToDir(classesDir, r.getTargetPath())), - filter), - new DirectoryPathTree((r.getTargetPath() == null ? generatedSourcesDir - : resolveRelativeToDir(generatedSourcesDir, r.getTargetPath())), - filter), + new LazySourceDir( + resolveRelativeToBaseDir(r.getDirectory(), SRC_MAIN_RESOURCES), null, + r.getTargetPath() == null + ? classesDir + : resolveRelativeToDir(classesDir, r.getTargetPath()), + filter, + r.getTargetPath() == null + ? generatedSourcesDir + : resolveRelativeToDir(generatedSourcesDir, r.getTargetPath()), Map.of())); } return sourceDirs; @@ -648,9 +648,9 @@ private Collection collectTestResources(PathFilter filter) { : model.getBuild().getTestResources(); final Path testClassesDir = getTestClassesDir(); if (resources.isEmpty()) { - return List.of(new DefaultSourceDir( - new DirectoryPathTree(resolveRelativeToBaseDir(null, SRC_TEST_RESOURCES)), - new DirectoryPathTree(testClassesDir, filter), + return List.of(new LazySourceDir( + resolveRelativeToBaseDir(null, SRC_TEST_RESOURCES), null, + testClassesDir, filter, // FIXME: do tests have generated sources? null, Map.of())); @@ -658,11 +658,12 @@ private Collection collectTestResources(PathFilter filter) { final List sourceDirs = new ArrayList<>(resources.size()); for (Resource r : resources) { sourceDirs.add( - new DefaultSourceDir( - new DirectoryPathTree(resolveRelativeToBaseDir(r.getDirectory(), SRC_TEST_RESOURCES)), - new DirectoryPathTree((r.getTargetPath() == null ? testClassesDir - : resolveRelativeToDir(testClassesDir, r.getTargetPath())), - filter), + new LazySourceDir( + resolveRelativeToBaseDir(r.getDirectory(), SRC_TEST_RESOURCES), null, + r.getTargetPath() == null + ? testClassesDir + : resolveRelativeToDir(testClassesDir, r.getTargetPath()), + filter, // FIXME: do tests have generated sources? null, Map.of())); diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index c515a9937e598..c06890bf22cb2 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -82,6 +82,7 @@ app-model maven4-resolver maven-resolver + json core runner gradle-resolver diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/EnforcingPlatformForConditionalDepsTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/EnforcingPlatformForConditionalDepsTest.java index 47b623cde8147..c24ddc816c9d4 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/EnforcingPlatformForConditionalDepsTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/EnforcingPlatformForConditionalDepsTest.java @@ -1,6 +1,5 @@ package io.quarkus.gradle; -import static io.quarkus.gradle.util.AppModelDeserializer.deserializeAppModel; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; @@ -15,6 +14,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; +import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.maven.dependency.Dependency; /** @@ -135,7 +136,7 @@ private void assertConditionalDependencies(String moduleDirName, String... expec var projectDir = getProjectDir(CONSUMER_PROJECT_PATH); var fullTaskName = ":%s:quarkusGenerateDevAppModel".formatted(moduleDirName); runGradleWrapper(projectDir, "clean", fullTaskName); - var appModel = deserializeAppModel( + ApplicationModel appModel = ApplicationModelSerializer.deserialize( projectDir.toPath().resolve(moduleDirName + "/build/quarkus/application-model/quarkus-app-dev-model.dat")); var conditionalArtifacts = ((Collection) appModel.getDependencies()).stream() .filter(d -> GROUP_IDS_TO_TEST.contains(d.getGroupId())) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestCompositeBuildWithExtensionsTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestCompositeBuildWithExtensionsTest.java index 4d463eaae4150..d6583098fcbb6 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestCompositeBuildWithExtensionsTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestCompositeBuildWithExtensionsTest.java @@ -1,6 +1,5 @@ package io.quarkus.gradle; -import static io.quarkus.gradle.util.AppModelDeserializer.deserializeAppModel; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; @@ -11,6 +10,7 @@ import org.junit.jupiter.api.Test; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.maven.dependency.DependencyFlags; @@ -28,7 +28,7 @@ public void compositeBuildWithExtensions() throws Exception { .resolve("application-model").resolve("quarkus-app-test-model.dat"); assertThat(testAppModelDat).exists(); - final ApplicationModel testModel = deserializeAppModel(testAppModelDat); + final ApplicationModel testModel = ApplicationModelSerializer.deserialize(testAppModelDat); for (var d : testModel.getDependencies(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT)) { assertFlagSet(d.getFlags(), DependencyFlags.RUNTIME_CP); assertFlagSet(d.getFlags(), DependencyFlags.DEPLOYMENT_CP); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestFixtureMultiModuleTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestFixtureMultiModuleTest.java index 0bd1498749b53..e33601bde6c35 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestFixtureMultiModuleTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/TestFixtureMultiModuleTest.java @@ -1,6 +1,5 @@ package io.quarkus.gradle; -import static io.quarkus.gradle.util.AppModelDeserializer.deserializeAppModel; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; @@ -10,6 +9,7 @@ import org.junit.jupiter.api.Test; +import io.quarkus.bootstrap.app.ApplicationModelSerializer; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.maven.dependency.ArtifactKey; @@ -25,7 +25,7 @@ public void testTaskShouldUseTestFixtures() throws Exception { final Path testModelDat = projectDir.toPath().resolve("application").resolve("build").resolve("quarkus") .resolve("application-model").resolve("quarkus-app-test-model.dat"); assertThat(testModelDat).exists(); - final ApplicationModel model = deserializeAppModel(testModelDat); + final ApplicationModel model = ApplicationModelSerializer.deserialize(testModelDat); final Map actualDepFlags = new HashMap<>(); for (var dep : model.getDependencies()) { if (dep.getGroupId().equals("my-groupId")) { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/util/AppModelDeserializer.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/util/AppModelDeserializer.java deleted file mode 100644 index 29f1db043367b..0000000000000 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/util/AppModelDeserializer.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.quarkus.gradle.util; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.nio.file.Files; -import java.nio.file.Path; - -import io.quarkus.bootstrap.model.ApplicationModel; - -public class AppModelDeserializer { - - /** - * Copied from ToolingUtils - * - * @param path application model dat file - * @return deserialized ApplicationModel - * @throws IOException in case of a failure to read the model - */ - public static ApplicationModel deserializeAppModel(Path path) throws IOException { - try (ObjectInputStream out = new ObjectInputStream(Files.newInputStream(path))) { - return (ApplicationModel) out.readObject(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } -}