clean up and organize

This commit is contained in:
bQUARKz 2026-03-16 19:07:32 +00:00
parent b88a4e3c62
commit e3dc8f10c4
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
14 changed files with 105 additions and 79 deletions

View File

@ -1,6 +1,7 @@
package p.studio;
import com.fasterxml.jackson.databind.ObjectMapper;
import p.packer.Packer;
import p.studio.events.StudioEventBus;
import p.studio.events.StudioPackerEventAdapter;
import p.studio.utilities.ThemeService;
@ -16,7 +17,7 @@ public final class AppContainer implements Container {
private final ThemeService themeService;
private final StudioEventBus studioEventBus;
private final ObjectMapper mapper;
private final Packer packer;
private final EmbeddedPacker embeddedPacker;
private final StudioBackgroundTasks backgroundTasks;
public AppContainer() {
@ -26,8 +27,8 @@ public final class AppContainer implements Container {
this.mapper = new ObjectMapper();
final ExecutorService backgroundExecutor = Executors.newFixedThreadPool(2, new StudioWorkerThreadFactory());
this.backgroundTasks = new StudioBackgroundTasks(backgroundExecutor);
final p.packer.Packer embeddedPacker = p.packer.Packer.bootstrap(new StudioPackerEventAdapter(studioEventBus));
this.packer = new Packer(embeddedPacker.workspaceService(), embeddedPacker::close);
final Packer packer = Packer.bootstrap(Container.mapper(), new StudioPackerEventAdapter(studioEventBus));
this.embeddedPacker = new EmbeddedPacker(packer.workspaceService(), packer::close);
}
@Override
@ -51,8 +52,8 @@ public final class AppContainer implements Container {
}
@Override
public Packer getPacker() {
return packer;
public EmbeddedPacker getPacker() {
return embeddedPacker;
}
@Override
@ -62,7 +63,7 @@ public final class AppContainer implements Container {
@Override
public void shutdownContainer() {
packer.shutdown();
embeddedPacker.shutdown();
backgroundTasks.shutdown();
}

View File

@ -1,5 +1,6 @@
package p.packer;
import com.fasterxml.jackson.databind.ObjectMapper;
import p.packer.events.PackerEventSink;
import p.packer.services.*;
@ -12,29 +13,29 @@ public final class Packer implements Closeable {
private final PackerProjectWriteCoordinator writeCoordinator;
private Packer(
PackerWorkspaceService workspaceService,
PackerRuntimeRegistry runtimeRegistry,
PackerProjectWriteCoordinator writeCoordinator) {
final PackerWorkspaceService workspaceService,
final PackerRuntimeRegistry runtimeRegistry,
final PackerProjectWriteCoordinator writeCoordinator) {
this.workspaceService = Objects.requireNonNull(workspaceService, "workspaceService");
this.runtimeRegistry = Objects.requireNonNull(runtimeRegistry, "runtimeRegistry");
this.writeCoordinator = Objects.requireNonNull(writeCoordinator, "writeCoordinator");
}
public static Packer bootstrap(PackerEventSink eventSink) {
final PackerEventSink resolvedEventSink = Objects.requireNonNull(eventSink, "eventSink");
final PackerWorkspaceFoundation workspaceFoundation = new PackerWorkspaceFoundation();
final PackerAssetDeclarationParser declarationParser = new PackerAssetDeclarationParser();
final PackerRuntimeRegistry runtimeRegistry = new PackerRuntimeRegistry(
new PackerRuntimeLoader(workspaceFoundation, declarationParser));
final PackerAssetReferenceResolver assetReferenceResolver = new PackerAssetReferenceResolver(workspaceFoundation.lookup());
final PackerAssetDetailsService assetDetailsService = new PackerAssetDetailsService(runtimeRegistry, assetReferenceResolver);
final PackerAssetActionReadService assetActionReadService = new PackerAssetActionReadService(
public static Packer bootstrap(final ObjectMapper mapper, final PackerEventSink eventSink) {
final var resolvedEventSink = Objects.requireNonNull(eventSink, "eventSink");
final var workspaceFoundation = new PackerWorkspaceFoundation();
final var declarationParser = new PackerAssetDeclarationParser(mapper);
final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(workspaceFoundation, declarationParser));
final var assetReferenceResolver = new PackerAssetReferenceResolver(workspaceFoundation.lookup());
final var assetDetailsService = new PackerAssetDetailsService(runtimeRegistry, assetReferenceResolver);
final var assetActionReadService = new PackerAssetActionReadService(
runtimeRegistry,
assetReferenceResolver,
workspaceFoundation.lookup());
final PackerRuntimePatchService runtimePatchService = new PackerRuntimePatchService(declarationParser);
final PackerProjectWriteCoordinator writeCoordinator = new PackerProjectWriteCoordinator();
final var runtimePatchService = new PackerRuntimePatchService(declarationParser);
final var writeCoordinator = new PackerProjectWriteCoordinator();
return new Packer(new FileSystemPackerWorkspaceService(
mapper,
workspaceFoundation,
assetDetailsService,
assetActionReadService,

View File

@ -3,6 +3,7 @@ package p.packer.services;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import p.packer.exceptions.PackerRegistryException;
import p.packer.messages.PackerProjectContext;
import p.packer.models.PackerRegistryEntry;
import p.packer.models.PackerRegistryState;
@ -82,31 +83,31 @@ public final class FileSystemPackerRegistryRepository implements PackerRegistryR
return new PackerRegistryState(REGISTRY_SCHEMA_VERSION, 1, List.of());
}
private void validateEntries(List<PackerRegistryEntry> entries) {
private void validateEntries(List<PackerRegistryEntry> entries) throws PackerRegistryException {
final Set<Integer> assetIds = new HashSet<>();
final Set<String> assetUuids = new HashSet<>();
final Set<String> roots = new HashSet<>();
for (PackerRegistryEntry entry : entries) {
if (!assetIds.add(entry.assetId())) {
throw new p.packer.exceptions.PackerRegistryException("Duplicate asset_id in registry: " + entry.assetId());
throw new PackerRegistryException("Duplicate asset_id in registry: " + entry.assetId());
}
if (!assetUuids.add(entry.assetUuid())) {
throw new p.packer.exceptions.PackerRegistryException("Duplicate asset_uuid in registry: " + entry.assetUuid());
throw new PackerRegistryException("Duplicate asset_uuid in registry: " + entry.assetUuid());
}
if (!roots.add(entry.root())) {
throw new p.packer.exceptions.PackerRegistryException("Duplicate asset root in registry: " + entry.root());
throw new PackerRegistryException("Duplicate asset root in registry: " + entry.root());
}
}
}
private String normalizeRoot(String root) {
if (root == null || root.isBlank()) {
throw new p.packer.exceptions.PackerRegistryException("Registry asset root must not be blank");
throw new PackerRegistryException("Registry asset root must not be blank");
}
final String normalized = root.trim().replace('\\', '/');
final Path normalizedPath = Path.of(normalized).normalize();
if (normalizedPath.isAbsolute() || normalizedPath.startsWith("..")) {
throw new p.packer.exceptions.PackerRegistryException("Registry asset root is outside the trusted assets boundary: " + normalized);
throw new PackerRegistryException("Registry asset root is outside the trusted assets boundary: " + normalized);
}
return normalized;
}

View File

@ -20,7 +20,7 @@ import java.util.*;
import java.util.stream.Stream;
public final class FileSystemPackerWorkspaceService implements PackerWorkspaceService {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final ObjectMapper mapper;
private final PackerWorkspaceFoundation workspaceFoundation;
private final PackerAssetDetailsService detailsService;
private final PackerAssetActionReadService actionReadService;
@ -30,6 +30,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
private final PackerEventSink eventSink;
public FileSystemPackerWorkspaceService(
ObjectMapper mapper,
PackerWorkspaceFoundation workspaceFoundation,
PackerAssetDetailsService detailsService,
PackerAssetActionReadService actionReadService,
@ -37,6 +38,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
PackerRuntimeRegistry runtimeRegistry,
PackerProjectWriteCoordinator writeCoordinator,
PackerEventSink eventSink) {
this.mapper = Objects.requireNonNull(mapper, "mapper");
this.workspaceFoundation = Objects.requireNonNull(workspaceFoundation, "workspaceFoundation");
this.detailsService = Objects.requireNonNull(detailsService, "detailsService");
this.actionReadService = Objects.requireNonNull(actionReadService, "actionReadService");
@ -471,7 +473,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
"format", request.outputFormat().manifestValue(),
"codec", request.outputCodec().manifestValue()));
manifest.put("preload", Map.of("enabled", request.preloadEnabled()));
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
}
private String normalizeRelativeAssetRoot(String candidate) {
@ -604,7 +606,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
final ObjectNode manifest;
try {
final JsonNode rawManifest = MAPPER.readTree(manifestPath.toFile());
final JsonNode rawManifest = mapper.readTree(manifestPath.toFile());
if (!(rawManifest instanceof ObjectNode objectNode)) {
return new UpdateAssetContractResponse(false, "asset.json must contain a JSON object at the root.");
}
@ -623,7 +625,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
try {
patchManifestContract(manifest, request);
MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest);
runtimeRegistry.update(project, (currentSnapshot, generation) -> runtimePatchService.afterUpdateAssetContract(
currentSnapshot,
generation,
@ -677,7 +679,7 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
if (current instanceof ObjectNode objectNode) {
return objectNode;
}
final ObjectNode created = MAPPER.createObjectNode();
final ObjectNode created = mapper.createObjectNode();
parent.set(fieldName, created);
return created;
}

View File

@ -16,15 +16,20 @@ import java.nio.file.Path;
import java.util.*;
public final class PackerAssetDeclarationParser {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final int SUPPORTED_SCHEMA_VERSION = 1;
public PackerAssetDeclarationParseResult parse(Path assetManifestPath) {
final Path manifestPath = Objects.requireNonNull(assetManifestPath, "assetManifestPath").toAbsolutePath().normalize();
private final ObjectMapper mapper;
public PackerAssetDeclarationParser(ObjectMapper mapper) {
this.mapper = Objects.requireNonNull(mapper, "mapper");
}
public PackerAssetDeclarationParseResult parse(final Path assetManifestPath) {
final var manifestPath = Objects.requireNonNull(assetManifestPath, "assetManifestPath").toAbsolutePath().normalize();
final List<PackerDiagnostic> diagnostics = new ArrayList<>();
final JsonNode root;
try {
root = MAPPER.readTree(manifestPath.toFile());
root = mapper.readTree(manifestPath.toFile());
} catch (IOException exception) {
diagnostics.add(new PackerDiagnostic(
PackerDiagnosticSeverity.ERROR,

View File

@ -60,9 +60,10 @@ public final class PackerAssetDetailsService {
declaration.name(),
resolved.assetRoot()),
state,
resolved.registryEntry().map(entry -> entry.includedInBuild()
? PackerBuildParticipation.INCLUDED
: PackerBuildParticipation.EXCLUDED).orElse(PackerBuildParticipation.EXCLUDED),
resolved.registryEntry()
.filter(PackerRegistryEntry::includedInBuild)
.map(entry -> PackerBuildParticipation.INCLUDED)
.orElse(PackerBuildParticipation.EXCLUDED),
declaration.assetFamily(),
declaration.preloadEnabled(),
!diagnostics.isEmpty());
@ -97,9 +98,10 @@ public final class PackerAssetDetailsService {
resolved.assetRoot().getFileName().toString(),
resolved.assetRoot()),
state,
resolved.registryEntry().map(entry -> entry.includedInBuild()
? PackerBuildParticipation.INCLUDED
: PackerBuildParticipation.EXCLUDED).orElse(PackerBuildParticipation.EXCLUDED),
resolved.registryEntry()
.filter(PackerRegistryEntry::includedInBuild)
.map(entry -> PackerBuildParticipation.INCLUDED)
.orElse(PackerBuildParticipation.EXCLUDED),
AssetFamilyCatalog.UNKNOWN,
false,
true);
@ -145,13 +147,12 @@ public final class PackerAssetDetailsService {
PackerProjectContext project,
Path assetRoot,
Optional<PackerRegistryEntry> registryEntry) {
if (registryEntry.isPresent()) {
return AssetReference.forAssetId(registryEntry.get().assetId());
}
return AssetReference.forRelativeAssetRoot(PackerWorkspacePaths.assetsRoot(project)
.relativize(assetRoot.toAbsolutePath().normalize())
.toString()
.replace('\\', '/'));
return registryEntry
.map(packerRegistryEntry -> AssetReference.forAssetId(packerRegistryEntry.assetId()))
.orElseGet(() -> AssetReference.forRelativeAssetRoot(PackerWorkspacePaths.assetsRoot(project)
.relativize(assetRoot.toAbsolutePath().normalize())
.toString()
.replace('\\', '/')));
}
private AssetReference canonicalReferenceOrRequested(

View File

@ -10,9 +10,9 @@ import p.packer.models.PackerRuntimeSnapshot;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
private final PackerWorkspaceFoundation workspaceFoundation;
@ -25,6 +25,10 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
this.parser = Objects.requireNonNull(parser, "parser");
}
private boolean isAssetJson(Path path, BasicFileAttributes attrs) {
return attrs.isRegularFile() && path.getFileName().toString().equals("asset.json");
}
@Override
public PackerRuntimeSnapshot load(PackerProjectContext project, long generation) {
final PackerProjectContext safeProject = Objects.requireNonNull(project, "project");
@ -37,17 +41,16 @@ public final class PackerRuntimeLoader implements PackerRuntimeSnapshotLoader {
entry -> entry));
final List<PackerRuntimeAsset> assets = new ArrayList<>();
final Path assetsRoot = PackerWorkspacePaths.assetsRoot(safeProject);
final var assetsRoot = PackerWorkspacePaths.assetsRoot(safeProject);
if (Files.isDirectory(assetsRoot)) {
try (Stream<Path> paths = Files.find(assetsRoot, Integer.MAX_VALUE, (path, attrs) ->
attrs.isRegularFile() && path.getFileName().toString().equals("asset.json"))) {
final List<Path> manifests = paths
try (final var paths = Files.find(assetsRoot, Integer.MAX_VALUE, this::isAssetJson)) {
final var manifests = paths
.map(path -> path.toAbsolutePath().normalize())
.sorted(Comparator.naturalOrder())
.toList();
for (Path manifestPath : manifests) {
final Path assetRoot = manifestPath.getParent();
final Optional<PackerRegistryEntry> registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot));
for (final var manifestPath : manifests) {
final var assetRoot = manifestPath.getParent();
final var registryEntry = Optional.ofNullable(registryByRoot.get(assetRoot));
assets.add(new PackerRuntimeAsset(
assetRoot,
manifestPath,

View File

@ -8,6 +8,7 @@ import p.packer.models.PackerRegistryEntry;
import p.packer.models.PackerRegistryState;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
@ -21,20 +22,20 @@ public final class PackerWorkspaceFoundation {
}
public PackerWorkspaceFoundation(
FileSystemPackerRegistryRepository registryRepository,
PackerIdentityAllocator identityAllocator,
PackerRegistryLookup registryLookup) {
final FileSystemPackerRegistryRepository registryRepository,
final PackerIdentityAllocator identityAllocator,
final PackerRegistryLookup registryLookup) {
this.registryRepository = Objects.requireNonNull(registryRepository, "registryRepository");
this.identityAllocator = Objects.requireNonNull(identityAllocator, "identityAllocator");
this.registryLookup = Objects.requireNonNull(registryLookup, "registryLookup");
}
public InitWorkspaceResult initWorkspace(InitWorkspaceRequest request) {
public InitWorkspaceResult initWorkspace(final InitWorkspaceRequest request) {
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
try {
Files.createDirectories(PackerWorkspacePaths.assetsRoot(project));
Files.createDirectories(PackerWorkspacePaths.registryDirectory(project));
final PackerRegistryState registryState = registryRepository.load(project);
final var registryState = registryRepository.load(project);
registryRepository.save(project, registryState);
return new InitWorkspaceResult(
PackerOperationStatus.SUCCESS,
@ -46,27 +47,34 @@ public final class PackerWorkspaceFoundation {
}
}
public PackerRegistryState loadRegistry(PackerProjectContext project) {
public PackerRegistryState loadRegistry(final PackerProjectContext project) {
return registryRepository.load(project);
}
public void saveRegistry(PackerProjectContext project, PackerRegistryState state) {
public void saveRegistry(
final PackerProjectContext project,
final PackerRegistryState state) {
registryRepository.save(project, state);
}
public PackerRegistryEntry allocateIdentity(PackerProjectContext project, PackerRegistryState state, java.nio.file.Path assetRoot) {
public PackerRegistryEntry allocateIdentity(
final PackerProjectContext project,
final PackerRegistryState state,
final Path assetRoot) {
return identityAllocator.allocate(project, state, assetRoot);
}
public PackerRegistryEntry registerExistingAsset(
PackerProjectContext project,
PackerRegistryState state,
java.nio.file.Path assetRoot,
String assetUuid) {
final PackerProjectContext project,
final PackerRegistryState state,
final Path assetRoot,
final String assetUuid) {
return identityAllocator.registerExisting(project, state, assetRoot, assetUuid);
}
public PackerRegistryState appendAllocatedEntry(PackerRegistryState state, PackerRegistryEntry entry) {
public PackerRegistryState appendAllocatedEntry(
final PackerRegistryState state,
final PackerRegistryEntry entry) {
return identityAllocator.append(state, entry);
}

View File

@ -666,7 +666,7 @@ final class FileSystemPackerWorkspaceServiceTest {
p.packer.events.PackerEventSink eventSink,
PackerRuntimeSnapshotLoader loader) {
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
final var parser = new p.packer.services.PackerAssetDeclarationParser();
final var parser = new p.packer.services.PackerAssetDeclarationParser(new ObjectMapper());
final var runtimeRegistry = new p.packer.services.PackerRuntimeRegistry(loader);
final var resolver = new p.packer.services.PackerAssetReferenceResolver(foundation.lookup());
final var detailsService = new p.packer.services.PackerAssetDetailsService(runtimeRegistry, resolver);
@ -674,6 +674,7 @@ final class FileSystemPackerWorkspaceServiceTest {
final var runtimePatchService = new p.packer.services.PackerRuntimePatchService(parser);
final var writeCoordinator = new p.packer.services.PackerProjectWriteCoordinator();
return new FileSystemPackerWorkspaceService(
new ObjectMapper(),
foundation,
detailsService,
actionReadService,
@ -685,7 +686,7 @@ final class FileSystemPackerWorkspaceServiceTest {
private CountingLoader countingLoader() {
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
final var parser = new p.packer.services.PackerAssetDeclarationParser();
final var parser = new p.packer.services.PackerAssetDeclarationParser(new ObjectMapper());
return new CountingLoader(new p.packer.services.PackerRuntimeLoader(foundation, parser));
}

View File

@ -1,5 +1,6 @@
package p.packer.services;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.messages.assets.AssetFamilyCatalog;
@ -13,7 +14,7 @@ import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
final class PackerAssetDeclarationParserTest {
private final PackerAssetDeclarationParser parser = new PackerAssetDeclarationParser();
private final PackerAssetDeclarationParser parser = new PackerAssetDeclarationParser(new ObjectMapper());
@TempDir
Path tempDir;

View File

@ -1,5 +1,6 @@
package p.packer.services;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.messages.AssetReference;
@ -87,7 +88,7 @@ final class PackerAssetDetailsServiceTest {
private PackerAssetDetailsService service() {
final var foundation = new p.packer.services.PackerWorkspaceFoundation();
final var parser = new PackerAssetDeclarationParser();
final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
final var resolver = new PackerAssetReferenceResolver(foundation.lookup());
return new PackerAssetDetailsService(runtimeRegistry, resolver);

View File

@ -1,5 +1,6 @@
package p.packer.services;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.messages.PackerProjectContext;
@ -78,7 +79,7 @@ final class PackerRuntimeRegistryTest {
private PackerRuntimeRegistry runtimeRegistry() {
final var foundation = new PackerWorkspaceFoundation();
final var parser = new PackerAssetDeclarationParser();
final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
return new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
}

View File

@ -17,7 +17,7 @@ public interface Container {
ObjectMapper getMapper();
Packer getPacker();
EmbeddedPacker getPacker();
StudioBackgroundTasks getBackgroundTasks();
@ -60,7 +60,7 @@ public interface Container {
return current().getMapper();
}
static Packer packer() {
static EmbeddedPacker packer() {
return current().getPacker();
}
@ -75,11 +75,11 @@ public interface Container {
}
}
record Packer(
record EmbeddedPacker(
p.packer.PackerWorkspaceService workspaceService,
Runnable shutdownAction) {
public Packer {
public EmbeddedPacker {
Objects.requireNonNull(workspaceService, "workspaceService");
Objects.requireNonNull(shutdownAction, "shutdownAction");
}

View File

@ -11,7 +11,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
final class StudioPackerEventAdapterTest {
final class StudioEmbeddedPackerEventAdapterTest {
@Test
void forwardsScanEventsIntoStudioOperationEvents() {
final StudioEventBus globalBus = new StudioEventBus();