250 lines
12 KiB
Java

package p.packer.services;
import p.packer.PackerOperationStatus;
import p.packer.PackerProjectContext;
import p.packer.assets.AssetFamilyCatalog;
import p.packer.assets.AssetReference;
import p.packer.assets.OutputCodecCatalog;
import p.packer.assets.PackerAssetState;
import p.packer.assets.PackerBuildParticipation;
import p.packer.diagnostics.PackerDiagnosticCategory;
import p.packer.diagnostics.PackerDiagnosticSeverity;
import p.packer.messages.GetAssetDetailsRequest;
import p.packer.messages.GetAssetDetailsResult;
import p.packer.models.PackerAssetDeclaration;
import p.packer.models.PackerAssetDeclarationParseResult;
import p.packer.models.PackerAssetDetails;
import p.packer.models.PackerAssetIdentity;
import p.packer.models.PackerAssetSummary;
import p.packer.models.PackerDiagnostic;
import p.packer.models.PackerRegistryEntry;
import p.packer.models.PackerRuntimeAsset;
import p.packer.models.PackerRuntimeSnapshot;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public final class PackerAssetDetailsService {
private final PackerRuntimeRegistry runtimeRegistry;
public PackerAssetDetailsService(PackerRuntimeRegistry runtimeRegistry) {
this.runtimeRegistry = Objects.requireNonNull(runtimeRegistry, "runtimeRegistry");
}
public GetAssetDetailsResult getAssetDetails(GetAssetDetailsRequest request) {
final PackerProjectContext project = Objects.requireNonNull(request, "request").project();
final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot();
final ResolvedAssetReference resolved = resolveReference(project, snapshot, request.assetReference());
final List<PackerDiagnostic> diagnostics = new ArrayList<>(resolved.diagnostics());
if (resolved.runtimeAsset().isEmpty()) {
diagnostics.add(new PackerDiagnostic(
PackerDiagnosticSeverity.ERROR,
PackerDiagnosticCategory.STRUCTURAL,
"asset.json was not found for the requested asset root.",
resolved.assetRoot().resolve("asset.json"),
true));
return failureResult(project, request.assetReference(), resolved, diagnostics);
}
final PackerRuntimeAsset runtimeAsset = resolved.runtimeAsset().get();
final Path manifestPath = runtimeAsset.manifestPath();
final PackerAssetDeclarationParseResult parsed = runtimeAsset.parsedDeclaration();
diagnostics.addAll(parsed.diagnostics());
if (!parsed.valid()) {
return failureResult(project, request.assetReference(), resolved, diagnostics);
}
final PackerAssetDeclaration declaration = parsed.declaration();
diagnostics.addAll(identityMismatchDiagnostics(resolved.registryEntry(), declaration, manifestPath));
final PackerOutputContractCatalog.OutputContractDefinition outputContract = PackerOutputContractCatalog.definitionFor(
declaration.outputFormat(),
declaration.outputCodec());
final PackerAssetState state = resolved.registryEntry().isPresent()
? PackerAssetState.REGISTERED
: PackerAssetState.UNREGISTERED;
final PackerAssetSummary summary = new PackerAssetSummary(
canonicalReference(project, resolved.assetRoot(), resolved.registryEntry()),
new PackerAssetIdentity(
resolved.registryEntry().map(PackerRegistryEntry::assetId).orElse(null),
resolved.registryEntry().map(PackerRegistryEntry::assetUuid).orElse(declaration.assetUuid()),
declaration.name(),
resolved.assetRoot()),
state,
resolved.registryEntry().map(entry -> entry.includedInBuild()
? PackerBuildParticipation.INCLUDED
: PackerBuildParticipation.EXCLUDED).orElse(PackerBuildParticipation.EXCLUDED),
declaration.assetFamily(),
declaration.preloadEnabled(),
!diagnostics.isEmpty());
final PackerAssetDetails details = new PackerAssetDetails(
summary,
declaration.outputFormat(),
declaration.outputCodec(),
outputContract.availableCodecs(),
outputContract.codecConfigurationFieldsByCodec(),
resolveInputs(resolved.assetRoot(), declaration.inputsByRole()),
diagnostics);
return new GetAssetDetailsResult(
diagnostics.stream().anyMatch(PackerDiagnostic::blocking) ? PackerOperationStatus.PARTIAL : PackerOperationStatus.SUCCESS,
"Asset details resolved from runtime snapshot.",
PackerReadMessageMapper.toAssetDetailsDTO(details),
PackerReadMessageMapper.toDiagnosticDTOs(diagnostics));
}
private GetAssetDetailsResult failureResult(
PackerProjectContext project,
AssetReference requestedReference,
ResolvedAssetReference resolved,
List<PackerDiagnostic> diagnostics) {
final PackerAssetState state = resolved.registryEntry().isPresent()
? PackerAssetState.REGISTERED
: PackerAssetState.UNREGISTERED;
final PackerAssetSummary summary = new PackerAssetSummary(
canonicalReferenceOrRequested(project, requestedReference, resolved),
new PackerAssetIdentity(
resolved.registryEntry().map(PackerRegistryEntry::assetId).orElse(null),
resolved.registryEntry().map(PackerRegistryEntry::assetUuid).orElse(null),
resolved.assetRoot().getFileName().toString(),
resolved.assetRoot()),
state,
resolved.registryEntry().map(entry -> entry.includedInBuild()
? PackerBuildParticipation.INCLUDED
: PackerBuildParticipation.EXCLUDED).orElse(PackerBuildParticipation.EXCLUDED),
AssetFamilyCatalog.UNKNOWN,
false,
true);
final PackerAssetDetails details = new PackerAssetDetails(
summary,
"unknown",
OutputCodecCatalog.UNKNOWN,
List.of(OutputCodecCatalog.NONE),
Map.of(OutputCodecCatalog.NONE, List.of()),
Map.of(),
diagnostics);
return new GetAssetDetailsResult(
PackerOperationStatus.FAILED,
"Asset declaration is invalid or unreadable.",
PackerReadMessageMapper.toAssetDetailsDTO(details),
PackerReadMessageMapper.toDiagnosticDTOs(diagnostics));
}
private Map<String, List<Path>> resolveInputs(Path assetRoot, Map<String, List<String>> inputsByRole) {
final Map<String, List<Path>> resolved = new LinkedHashMap<>();
inputsByRole.forEach((role, inputs) -> resolved.put(
role,
inputs.stream().map(input -> assetRoot.resolve(input).toAbsolutePath().normalize()).toList()));
return Map.copyOf(resolved);
}
private List<PackerDiagnostic> identityMismatchDiagnostics(
Optional<PackerRegistryEntry> registryEntry,
PackerAssetDeclaration declaration,
Path manifestPath) {
if (registryEntry.isEmpty() || registryEntry.get().assetUuid().equals(declaration.assetUuid())) {
return List.of();
}
return List.of(new PackerDiagnostic(
PackerDiagnosticSeverity.ERROR,
PackerDiagnosticCategory.STRUCTURAL,
"Field 'asset_uuid' does not match the registered asset identity.",
manifestPath,
true));
}
private ResolvedAssetReference resolveReference(PackerProjectContext project, PackerRuntimeSnapshot snapshot, AssetReference assetReference) {
final PackerRegistryLookup lookup = new PackerRegistryLookup();
final String reference = Objects.requireNonNull(assetReference, "assetReference").rawValue();
final Optional<PackerRegistryEntry> byId = parseAssetId(reference).flatMap(assetId -> lookup.findByAssetId(snapshot.registry(), assetId));
if (byId.isPresent()) {
final Path assetRoot = PackerWorkspacePaths.assetRoot(project, byId.get().root());
return new ResolvedAssetReference(assetRoot, byId, findRuntimeAsset(snapshot, assetRoot), List.of());
}
final Optional<PackerRegistryEntry> byUuid = lookup.findByAssetUuid(snapshot.registry(), reference);
if (byUuid.isPresent()) {
final Path assetRoot = PackerWorkspacePaths.assetRoot(project, byUuid.get().root());
return new ResolvedAssetReference(assetRoot, byUuid, findRuntimeAsset(snapshot, assetRoot), List.of());
}
final Path candidateRoot = PackerWorkspacePaths.assetRoot(project, reference);
final Optional<PackerRuntimeAsset> runtimeAsset = findRuntimeAsset(snapshot, candidateRoot);
if (runtimeAsset.isPresent()) {
return new ResolvedAssetReference(candidateRoot, lookup.findByRoot(project, snapshot.registry(), candidateRoot), runtimeAsset, List.of());
}
final Optional<PackerRegistryEntry> registryEntry = lookup.findByRoot(project, snapshot.registry(), candidateRoot);
if (registryEntry.isPresent()) {
return new ResolvedAssetReference(candidateRoot, registryEntry, Optional.empty(), List.of());
}
return new ResolvedAssetReference(
candidateRoot,
Optional.empty(),
Optional.empty(),
List.of(new PackerDiagnostic(
PackerDiagnosticSeverity.ERROR,
PackerDiagnosticCategory.STRUCTURAL,
"Requested asset reference could not be resolved.",
candidateRoot,
true)));
}
private AssetReference canonicalReference(
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('\\', '/'));
}
private AssetReference canonicalReferenceOrRequested(
PackerProjectContext project,
AssetReference requestedReference,
ResolvedAssetReference resolved) {
if (resolved.registryEntry().isPresent() || resolved.runtimeAsset().isPresent()) {
return canonicalReference(project, resolved.assetRoot(), resolved.registryEntry());
}
return requestedReference;
}
private Optional<Integer> parseAssetId(String reference) {
try {
return Optional.of(Integer.parseInt(reference.trim()));
} catch (NumberFormatException ignored) {
return Optional.empty();
}
}
private Optional<PackerRuntimeAsset> findRuntimeAsset(PackerRuntimeSnapshot snapshot, Path assetRoot) {
final Path normalizedRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
return snapshot.assets().stream()
.filter(candidate -> candidate.assetRoot().equals(normalizedRoot))
.findFirst();
}
private record ResolvedAssetReference(
Path assetRoot,
Optional<PackerRegistryEntry> registryEntry,
Optional<PackerRuntimeAsset> runtimeAsset,
List<PackerDiagnostic> diagnostics) {
private ResolvedAssetReference {
assetRoot = Objects.requireNonNull(assetRoot, "assetRoot").toAbsolutePath().normalize();
registryEntry = Objects.requireNonNull(registryEntry, "registryEntry");
runtimeAsset = Objects.requireNonNull(runtimeAsset, "runtimeAsset");
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
}
}
}