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
+ * 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 extends Dependency>) 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);
- }
- }
-}