From 49d83e3ff8c43daf31275729a16018fe8b4e8e49 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 19 Mar 2026 00:42:04 +0000 Subject: [PATCH] implements PR-10a bank composition details dto projection --- .../p/packer/dtos/PackerAssetDetailsDTO.java | 2 + .../dtos/PackerBankCompositionDetailsDTO.java | 18 +++++ .../dtos/PackerBankCompositionFileDTO.java | 33 +++++++++ .../p/packer/models/PackerAssetDetails.java | 2 + .../models/PackerBankCompositionDetails.java | 20 ++++++ .../models/PackerBankCompositionFile.java | 33 +++++++++ .../services/PackerAssetDetailsService.java | 69 +++++++++++++++++++ .../services/PackerReadMessageMapper.java | 18 +++++ .../PackerAssetDetailsServiceTest.java | 31 +++++++++ .../assets/details/AssetDetailsControl.java | 20 ++++++ .../messages/AssetWorkspaceAssetDetails.java | 2 + .../AssetWorkspaceBankCompositionDetails.java | 21 ++++++ .../AssetWorkspaceBankCompositionFile.java | 33 +++++++++ 13 files changed, 302 insertions(+) create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionDetailsDTO.java create mode 100644 prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionFileDTO.java create mode 100644 prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionDetails.java create mode 100644 prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionFile.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionDetails.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionFile.java diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java index 55bd697b..e2d91a07 100644 --- a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerAssetDetailsDTO.java @@ -15,6 +15,7 @@ public record PackerAssetDetailsDTO( List availableOutputCodecs, Map> codecConfigurationFieldsByCodec, List metadataFields, + PackerBankCompositionDetailsDTO bankComposition, Map> inputsByRole, List diagnostics) { @@ -25,6 +26,7 @@ public record PackerAssetDetailsDTO( availableOutputCodecs = List.copyOf(Objects.requireNonNull(availableOutputCodecs, "availableOutputCodecs")); codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec")); metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); + bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole")); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionDetailsDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionDetailsDTO.java new file mode 100644 index 00000000..84eb59f7 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionDetailsDTO.java @@ -0,0 +1,18 @@ +package p.packer.dtos; + +import java.util.List; +import java.util.Objects; + +public record PackerBankCompositionDetailsDTO( + List availableFiles, + List selectedFiles, + long measuredBankSizeBytes) { + + public PackerBankCompositionDetailsDTO { + availableFiles = List.copyOf(Objects.requireNonNull(availableFiles, "availableFiles")); + selectedFiles = List.copyOf(Objects.requireNonNull(selectedFiles, "selectedFiles")); + if (measuredBankSizeBytes < 0L) { + throw new IllegalArgumentException("measuredBankSizeBytes must be non-negative"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionFileDTO.java b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionFileDTO.java new file mode 100644 index 00000000..1d15e328 --- /dev/null +++ b/prometeu-packer/prometeu-packer-api/src/main/java/p/packer/dtos/PackerBankCompositionFileDTO.java @@ -0,0 +1,33 @@ +package p.packer.dtos; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +public record PackerBankCompositionFileDTO( + String path, + String displayName, + long size, + long lastModified, + String fingerprint, + Map metadata) { + + public PackerBankCompositionFileDTO { + path = Objects.requireNonNull(path, "path").trim(); + displayName = Objects.requireNonNull(displayName, "displayName").trim(); + if (path.isBlank()) { + throw new IllegalArgumentException("path must not be blank"); + } + if (displayName.isBlank()) { + throw new IllegalArgumentException("displayName must not be blank"); + } + if (size < 0L) { + throw new IllegalArgumentException("size must be non-negative"); + } + if (lastModified < 0L) { + throw new IllegalArgumentException("lastModified must be non-negative"); + } + fingerprint = fingerprint == null || fingerprint.isBlank() ? null : fingerprint; + metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata"))); + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java index a52ea881..fc9e57f9 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerAssetDetails.java @@ -15,6 +15,7 @@ public record PackerAssetDetails( List availableOutputCodecs, Map> codecConfigurationFieldsByCodec, List metadataFields, + PackerBankCompositionDetails bankComposition, Map> inputsByRole, List diagnostics) { @@ -25,6 +26,7 @@ public record PackerAssetDetails( availableOutputCodecs = List.copyOf(Objects.requireNonNull(availableOutputCodecs, "availableOutputCodecs")); codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec")); metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); + bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole")); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); } diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionDetails.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionDetails.java new file mode 100644 index 00000000..27d12410 --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionDetails.java @@ -0,0 +1,20 @@ +package p.packer.models; + +import java.util.List; +import java.util.Objects; + +public record PackerBankCompositionDetails( + List availableFiles, + List selectedFiles, + long measuredBankSizeBytes) { + + public static final PackerBankCompositionDetails EMPTY = new PackerBankCompositionDetails(List.of(), List.of(), 0L); + + public PackerBankCompositionDetails { + availableFiles = List.copyOf(Objects.requireNonNull(availableFiles, "availableFiles")); + selectedFiles = List.copyOf(Objects.requireNonNull(selectedFiles, "selectedFiles")); + if (measuredBankSizeBytes < 0L) { + throw new IllegalArgumentException("measuredBankSizeBytes must be non-negative"); + } + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionFile.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionFile.java new file mode 100644 index 00000000..0804224f --- /dev/null +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerBankCompositionFile.java @@ -0,0 +1,33 @@ +package p.packer.models; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +public record PackerBankCompositionFile( + String path, + String displayName, + long size, + long lastModified, + String fingerprint, + Map metadata) { + + public PackerBankCompositionFile { + path = Objects.requireNonNull(path, "path").trim(); + displayName = Objects.requireNonNull(displayName, "displayName").trim(); + if (path.isBlank()) { + throw new IllegalArgumentException("path must not be blank"); + } + if (displayName.isBlank()) { + throw new IllegalArgumentException("displayName must not be blank"); + } + if (size < 0L) { + throw new IllegalArgumentException("size must be non-negative"); + } + if (lastModified < 0L) { + throw new IllegalArgumentException("lastModified must be non-negative"); + } + fingerprint = fingerprint == null || fingerprint.isBlank() ? null : fingerprint; + metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata"))); + } +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java index f9031ec0..16dbcfd3 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerAssetDetailsService.java @@ -8,6 +8,7 @@ import p.packer.messages.diagnostics.PackerDiagnosticSeverity; import p.packer.models.*; import p.packer.repositories.PackerRuntimeRegistry; +import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -77,6 +78,7 @@ public final class PackerAssetDetailsService { outputContract.availableCodecs(), outputContract.codecConfigurationFieldsByCodec(), metadataFields(outputContract.metadataFields(), declaration.outputMetadata()), + resolveBankCompositionDetails(runtimeAsset, declaration), resolveInputs(resolved.assetRoot(), declaration.inputsByRole()), diagnostics); return new GetAssetDetailsResult( @@ -116,6 +118,7 @@ public final class PackerAssetDetailsService { List.of(OutputCodecCatalog.NONE), Map.of(OutputCodecCatalog.NONE, List.of()), List.of(), + PackerBankCompositionDetails.EMPTY, Map.of(), diagnostics); return new GetAssetDetailsResult( @@ -135,6 +138,72 @@ public final class PackerAssetDetailsService { return Map.copyOf(resolved); } + private PackerBankCompositionDetails resolveBankCompositionDetails( + final PackerRuntimeAsset runtimeAsset, + final PackerAssetDeclaration declaration) { + final var walkProjection = runtimeAsset.walkProjection(); + final Map walkFilesByPath = new LinkedHashMap<>(); + walkProjection.buildCandidateFiles().forEach(file -> walkFilesByPath.put(file.relativePath(), file)); + + final List availableFiles = walkProjection.buildCandidateFiles().stream() + .filter(file -> file.diagnostics().stream().noneMatch(PackerDiagnostic::blocking)) + .map(this::toBankCompositionFile) + .toList(); + + final List selectedFiles = flattenSelectedInputPaths(declaration.inputsByRole()).stream() + .map(path -> resolveSelectedBankFile(runtimeAsset.assetRoot(), path, walkFilesByPath)) + .flatMap(Optional::stream) + .toList(); + + return new PackerBankCompositionDetails( + availableFiles, + selectedFiles, + walkProjection.measuredBankSizeBytes()); + } + + private List flattenSelectedInputPaths(Map> inputsByRole) { + final List selected = new ArrayList<>(); + inputsByRole.values().forEach(selected::addAll); + return List.copyOf(selected); + } + + private Optional resolveSelectedBankFile( + Path assetRoot, + String relativePath, + Map walkFilesByPath) { + final PackerRuntimeWalkFile runtimeWalkFile = walkFilesByPath.get(relativePath); + if (runtimeWalkFile != null) { + return Optional.of(toBankCompositionFile(runtimeWalkFile)); + } + + final Path filePath = assetRoot.resolve(relativePath).toAbsolutePath().normalize(); + if (!Files.isRegularFile(filePath)) { + return Optional.empty(); + } + + try { + return Optional.of(new PackerBankCompositionFile( + relativePath, + filePath.getFileName().toString(), + Files.size(filePath), + Files.getLastModifiedTime(filePath).toMillis(), + null, + Map.of())); + } catch (Exception exception) { + return Optional.empty(); + } + } + + private PackerBankCompositionFile toBankCompositionFile(PackerRuntimeWalkFile file) { + return new PackerBankCompositionFile( + file.relativePath(), + Path.of(file.relativePath()).getFileName().toString(), + file.size(), + file.lastModified(), + file.fingerprint(), + file.metadata()); + } + private List metadataFields( List definitions, Map outputMetadata) { diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java index c1fc78a3..dc11edb4 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java @@ -31,10 +31,28 @@ public final class PackerReadMessageMapper { details.availableOutputCodecs(), toCodecConfigurationFieldsByCodecDTO(details.codecConfigurationFieldsByCodec()), toCodecConfigurationFieldDTOs(details.metadataFields()), + toBankCompositionDetailsDTO(details.bankComposition()), details.inputsByRole(), toDiagnosticDTOs(details.diagnostics())); } + private static PackerBankCompositionDetailsDTO toBankCompositionDetailsDTO(PackerBankCompositionDetails details) { + return new PackerBankCompositionDetailsDTO( + details.availableFiles().stream().map(PackerReadMessageMapper::toBankCompositionFileDTO).toList(), + details.selectedFiles().stream().map(PackerReadMessageMapper::toBankCompositionFileDTO).toList(), + details.measuredBankSizeBytes()); + } + + private static PackerBankCompositionFileDTO toBankCompositionFileDTO(PackerBankCompositionFile file) { + return new PackerBankCompositionFileDTO( + file.path(), + file.displayName(), + file.size(), + file.lastModified(), + file.fingerprint(), + file.metadata()); + } + public static List toAssetSummaryDTOs(List summaries) { return summaries.stream().map(PackerReadMessageMapper::toAssetSummaryDTO).toList(); } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java index ad9c8128..37c0aa21 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/PackerAssetDetailsServiceTest.java @@ -18,6 +18,8 @@ import p.packer.repositories.PackerRuntimeLoader; import p.packer.repositories.PackerRuntimeRegistry; import p.packer.testing.PackerFixtureLocator; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; @@ -45,10 +47,39 @@ final class PackerAssetDetailsServiceTest { assertEquals("TILES/indexed_v1", result.details().outputFormat().displayName()); assertEquals(List.of(OutputCodecCatalog.NONE), result.details().availableOutputCodecs()); assertEquals(List.of(), result.details().codecConfigurationFieldsByCodec().get(OutputCodecCatalog.NONE)); + assertNotNull(result.details().bankComposition()); + assertTrue(result.details().bankComposition().selectedFiles().isEmpty()); assertTrue(result.diagnostics().stream().noneMatch(diagnostic -> diagnostic.blocking())); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Output metadata for tile bank cannot be empty"))); } + @Test + void projectsBankCompositionAvailableAndSelectedFiles() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed-bank-composition")); + final Path assetRoot = projectRoot.resolve("assets/ui/atlas"); + final BufferedImage tile = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + ImageIO.write(tile, "png", assetRoot.resolve("confirm.png").toFile()); + ImageIO.write(tile, "png", assetRoot.resolve("cancel.png").toFile()); + final Path manifestPath = assetRoot.resolve("asset.json"); + + final ObjectMapper mapper = new ObjectMapper(); + final ObjectNode manifest = (ObjectNode) mapper.readTree(manifestPath.toFile()); + final ObjectNode inputs = manifest.putObject("inputs"); + inputs.putArray("sprites").add("confirm.png"); + mapper.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); + + final PackerAssetDetailsService service = service(); + final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forAssetId(1))); + + assertEquals(PackerOperationStatus.SUCCESS, result.status()); + assertTrue(result.details().bankComposition().availableFiles().stream() + .anyMatch(file -> file.path().equals("confirm.png"))); + assertTrue(result.details().bankComposition().selectedFiles().stream() + .anyMatch(file -> file.path().equals("confirm.png"))); + assertTrue(result.details().bankComposition().availableFiles().stream() + .allMatch(file -> !file.displayName().isBlank())); + } + @Test void returnsUnregisteredDetailsForValidUnregisteredRootReference() throws Exception { final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("orphan")); diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java index cf172d54..4ec7199e 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/details/AssetDetailsControl.java @@ -18,6 +18,8 @@ import p.studio.utilities.i18n.I18n; import p.studio.workspaces.assets.details.contract.AssetDetailsContractControl; import p.studio.workspaces.assets.details.summary.AssetDetailsSummaryControl; import p.studio.workspaces.assets.messages.AssetWorkspaceAssetAction; +import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionDetails; +import p.studio.workspaces.assets.messages.AssetWorkspaceBankCompositionFile; import p.studio.workspaces.assets.messages.AssetWorkspaceAssetDetails; import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsStatus; import p.studio.workspaces.assets.messages.AssetWorkspaceDetailsViewState; @@ -417,6 +419,24 @@ public final class AssetDetailsControl extends VBox implements StudioEventAware details.availableOutputCodecs(), details.codecConfigurationFieldsByCodec(), details.metadataFields(), + mapBankComposition(details.bankComposition()), Map.copyOf(details.inputsByRole())); } + + private AssetWorkspaceBankCompositionDetails mapBankComposition(p.packer.dtos.PackerBankCompositionDetailsDTO details) { + return new AssetWorkspaceBankCompositionDetails( + details.availableFiles().stream().map(this::mapBankCompositionFile).toList(), + details.selectedFiles().stream().map(this::mapBankCompositionFile).toList(), + details.measuredBankSizeBytes()); + } + + private AssetWorkspaceBankCompositionFile mapBankCompositionFile(p.packer.dtos.PackerBankCompositionFileDTO file) { + return new AssetWorkspaceBankCompositionFile( + file.path(), + file.displayName(), + file.size(), + file.lastModified(), + file.fingerprint(), + file.metadata()); + } } diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java index d577981f..b4da19d3 100644 --- a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceAssetDetails.java @@ -17,6 +17,7 @@ public record AssetWorkspaceAssetDetails( List availableOutputCodecs, Map> codecConfigurationFieldsByCodec, List metadataFields, + AssetWorkspaceBankCompositionDetails bankComposition, Map> inputsByRole) { public AssetWorkspaceAssetDetails { @@ -27,6 +28,7 @@ public record AssetWorkspaceAssetDetails( availableOutputCodecs = List.copyOf(Objects.requireNonNull(availableOutputCodecs, "availableOutputCodecs")); codecConfigurationFieldsByCodec = Map.copyOf(Objects.requireNonNull(codecConfigurationFieldsByCodec, "codecConfigurationFieldsByCodec")); metadataFields = List.copyOf(Objects.requireNonNull(metadataFields, "metadataFields")); + bankComposition = Objects.requireNonNull(bankComposition, "bankComposition"); inputsByRole = Map.copyOf(Objects.requireNonNull(inputsByRole, "inputsByRole")); } } diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionDetails.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionDetails.java new file mode 100644 index 00000000..6d3b5b03 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionDetails.java @@ -0,0 +1,21 @@ +package p.studio.workspaces.assets.messages; + +import java.util.List; +import java.util.Objects; + +public record AssetWorkspaceBankCompositionDetails( + List availableFiles, + List selectedFiles, + long measuredBankSizeBytes) { + + public static final AssetWorkspaceBankCompositionDetails EMPTY = + new AssetWorkspaceBankCompositionDetails(List.of(), List.of(), 0L); + + public AssetWorkspaceBankCompositionDetails { + availableFiles = List.copyOf(Objects.requireNonNull(availableFiles, "availableFiles")); + selectedFiles = List.copyOf(Objects.requireNonNull(selectedFiles, "selectedFiles")); + if (measuredBankSizeBytes < 0L) { + throw new IllegalArgumentException("measuredBankSizeBytes must be non-negative"); + } + } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionFile.java b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionFile.java new file mode 100644 index 00000000..3e60a471 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/assets/messages/AssetWorkspaceBankCompositionFile.java @@ -0,0 +1,33 @@ +package p.studio.workspaces.assets.messages; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +public record AssetWorkspaceBankCompositionFile( + String path, + String displayName, + long size, + long lastModified, + String fingerprint, + Map metadata) { + + public AssetWorkspaceBankCompositionFile { + path = Objects.requireNonNull(path, "path").trim(); + displayName = Objects.requireNonNull(displayName, "displayName").trim(); + if (path.isBlank()) { + throw new IllegalArgumentException("path must not be blank"); + } + if (displayName.isBlank()) { + throw new IllegalArgumentException("displayName must not be blank"); + } + if (size < 0L) { + throw new IllegalArgumentException("size must be non-negative"); + } + if (lastModified < 0L) { + throw new IllegalArgumentException("lastModified must be non-negative"); + } + fingerprint = fingerprint == null || fingerprint.isBlank() ? null : fingerprint; + metadata = Map.copyOf(new LinkedHashMap<>(Objects.requireNonNull(metadata, "metadata"))); + } +}