class OakpalPlan implements JsonObjectConvertible {

    private static OakpalPlan fromJson(final @NotNull Builder builder,
                                       final @NotNull JsonObject json) {
        if (hasNonNull(json, keys().preInstallUrls()) && builder.base != null) {
            builder.withPreInstallUrls(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().preInstallUrls()),
                    uncheck1(url -> new URL(builder.base, url))));
        }
        if (hasNonNull(json, keys().checklists())) {
            builder.withChecklists(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().checklists())));
        }
        if (hasNonNull(json, keys().checks())) {
            builder.withChecks(JavaxJson.mapArrayOfObjects(json.getJsonArray(keys().checks()), CheckSpec::fromJson));
        }
        if (hasNonNull(json, keys().repoInitUrls()) && builder.base != null) {
            builder.withRepoInitUrls(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().repoInitUrls()),
                    uncheck1(url -> new URL(builder.base, url))));
        }
        if (hasNonNull(json, keys().repoInits())) {
            builder.withRepoInits(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().repoInits())));
        }
        if (hasNonNull(json, keys().runModes())) {
            builder.withRunModes(JavaxJson.mapArrayOfStrings(json.getJsonArray(keys().runModes())));
        }
        if (hasNonNull(json, keys().forcedRoots())) {
            builder.withForcedRoots(JavaxJson.mapArrayOfObjects(json.getJsonArray(keys().forcedRoots()),
                    ForcedRoot::fromJson));
        }
        final NamespaceMapping mapping;
        if (hasNonNull(json, keys().jcrNamespaces())) {
            final List<JcrNs> jcrNsList = JavaxJson.mapArrayOfObjects(json.getJsonArray(keys().jcrNamespaces()),
                    JcrNs::fromJson);
            mapping = JsonCnd.toNamespaceMapping(jcrNsList);
            builder.withJcrNamespaces(jcrNsList);
        } else {
            mapping = JsonCnd.BUILTIN_MAPPINGS;
        }
        if (hasNonNull(json, keys().jcrNodetypes())) {
            builder.withJcrNodetypes(JsonCnd.getQTypesFromJson(json.getJsonObject(keys().jcrNodetypes()), mapping));
        }
        if (hasNonNull(json, keys().jcrPrivileges())) {
            builder.withJcrPrivileges(JsonCnd.getPrivilegesFromJson(json.get(keys().jcrPrivileges()), mapping));
        }
        if (hasNonNull(json, keys().enablePreInstallHooks())) {
            builder.withEnablePreInstallHooks(json.getBoolean(keys().enablePreInstallHooks()));
        }
        if (hasNonNull(json, keys().installHookPolicy())) {
            builder.withInstallHookPolicy(InstallHookPolicy.forName(
                    json.getString(keys().installHookPolicy())));
        }
        return builder.build(json);
    }

    private  OakpalPlan(final @Nullable URL base,
                       final @Nullable JsonObject originalJson,
                       final @NotNull String name,
                       final @NotNull List<String> runModes,
                       final @NotNull List<String> checklists,
                       final @NotNull List<URL> preInstallUrls,
                       final @NotNull List<JcrNs> jcrNamespaces,
                       final @NotNull List<QNodeTypeDefinition> jcrNodetypes,
                       final @NotNull List<PrivilegeDefinition> jcrPrivileges,
                       final @NotNull List<ForcedRoot> forcedRoots,
                       final @NotNull List<CheckSpec> checks,
                       final boolean enablePreInstallHooks,
                       final @Nullable InstallHookPolicy installHookPolicy,
                       final @NotNull List<URL> repoInitUrls,
                       final @NotNull List<String> repoInits);

}