ArchitectureArtifactEnvelopeFactory.java
package com.taxonomy.export.artifact;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.Objects;
/** Creates deterministic snapshot-bound architecture artifact envelopes. */
public final class ArchitectureArtifactEnvelopeFactory {
private static final String IDENTITY_VERSION =
"taxonomy-architecture-artifact-envelope-v1";
public ArchitectureArtifactEnvelope create(
ArchitectureArtifactFormat format,
ArchitectureArtifactSource source,
String payload,
ArchitectureArtifactLossManifest lossManifest) {
Objects.requireNonNull(format, "format");
return create(
format,
source,
format.defaultFileName(),
payload,
lossManifest);
}
public ArchitectureArtifactEnvelope create(
ArchitectureArtifactFormat format,
ArchitectureArtifactSource source,
String fileName,
String payload,
ArchitectureArtifactLossManifest lossManifest) {
format = Objects.requireNonNull(format, "format");
source = Objects.requireNonNull(source, "source");
fileName = ArchitectureArtifactEnvelope.requireFileName(
fileName,
format);
if (payload == null || payload.isBlank()) {
throw new IllegalArgumentException("payload must not be blank");
}
lossManifest = Objects.requireNonNull(lossManifest, "lossManifest");
String payloadSha256 = payloadSha256(payload);
String artifactId = artifactId(
format,
source,
fileName,
payloadSha256,
lossManifest);
return new ArchitectureArtifactEnvelope(
ArchitectureArtifactEnvelope.SCHEMA_VERSION,
artifactId,
format,
source,
format.mediaType(),
fileName,
payloadSha256,
lossManifest,
payload);
}
static String artifactId(
ArchitectureArtifactFormat format,
ArchitectureArtifactSource source,
String fileName,
String payloadSha256,
ArchitectureArtifactLossManifest lossManifest) {
MessageDigest digest = sha256Digest();
putText(digest, IDENTITY_VERSION);
putText(digest, ArchitectureArtifactEnvelope.SCHEMA_VERSION);
putText(digest, format.name());
putText(digest, source.snapshotId());
putText(digest, source.snapshotRevision());
putText(digest, source.workspaceId());
putText(digest, source.branchName());
putText(digest, format.mediaType());
putText(digest, fileName);
putText(digest, payloadSha256);
putText(digest, lossManifest.profileVersion());
putInt(digest, lossManifest.losses().size());
for (ArchitectureArtifactLoss loss : lossManifest.losses()) {
putText(digest, loss.code());
putText(digest, loss.sourcePath());
putText(digest, loss.disposition().name());
putText(digest, loss.detail());
}
return HexFormat.of().formatHex(digest.digest());
}
static String payloadSha256(String payload) {
return HexFormat.of().formatHex(
sha256Digest().digest(
payload.getBytes(StandardCharsets.UTF_8)));
}
private static MessageDigest sha256Digest() {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException exception) {
throw new IllegalStateException(
"SHA-256 is unavailable",
exception);
}
}
private static void putText(MessageDigest digest, String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
putInt(digest, bytes.length);
digest.update(bytes);
}
private static void putInt(MessageDigest digest, int value) {
digest.update((byte) (value >>> 24));
digest.update((byte) (value >>> 16));
digest.update((byte) (value >>> 8));
digest.update((byte) value);
}
}