ExchangeItems.java
package com.taxonomy.interop;
import com.taxonomy.extension.api.integration.IntegrationContracts.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/** Uniform comparison values; relation/hierarchy identities remain distinct from their endpoint identities. */
public final class ExchangeItems {
private ExchangeItems() {}
public static String key(Artifact value) { return value.kind().name() + ":" + value.id(); }
public static Map<String, Artifact> flatten(ExchangeDocument document) {
Map<String, Artifact> result = new TreeMap<>();
document.artifacts().forEach(a -> add(result, a));
for (Relation relation : document.relations()) {
Map<String, String> extension = new LinkedHashMap<>(relation.extensions()); extension.put("source", relation.source()); extension.put("target", relation.target());
add(result, new Artifact(relation.id(), ArtifactKind.RELATION, relation.type(), "", "", relation.attributes(), extension));
}
for (Placement placement : document.placements()) {
Map<String, String> extension = new LinkedHashMap<>(); extension.put("container", placement.containerId());
extension.put("parent", placement.parentId() == null ? "" : placement.parentId()); extension.put("artifact", placement.artifactId() == null ? "" : placement.artifactId());
extension.put("position", Integer.toString(placement.position()));
add(result, new Artifact(placement.id(), ArtifactKind.PLACEMENT, "placement", "", "", placement.attributes(), extension));
}
Map<String, String> modelMetadata = new TreeMap<>(document.metadata());
modelMetadata.remove("remoteRequest"); modelMetadata.remove("discovery");
add(result, new Artifact("package", ArtifactKind.METADATA, document.profile(), "", "", modelMetadata, Map.of()));
return result;
}
public static ExchangeDocument expand(ExchangeDocument template, Map<String, Artifact> items) {
List<Artifact> artifacts = new ArrayList<>(); List<Relation> relations = new ArrayList<>(); List<Placement> placements = new ArrayList<>();
Map<String, String> metadata = template.metadata();
for (Artifact item : new TreeMap<>(items).values()) switch (item.kind()) {
case RELATION -> {
Map<String, String> extension = new LinkedHashMap<>(item.extensions()); String source = extension.remove("source"), target = extension.remove("target");
relations.add(new Relation(item.id(), item.type(), source, target, item.attributes(), extension));
}
case PLACEMENT -> placements.add(new Placement(item.id(), item.extensions().get("container"), emptyToNull(item.extensions().get("parent")),
emptyToNull(item.extensions().get("artifact")), Integer.parseInt(item.extensions().get("position")), item.attributes()));
case METADATA -> metadata = item.attributes();
default -> artifacts.add(item);
}
return new ExchangeDocument(template.profile(), template.profileVersion(), template.externalVersion(), template.completeScope(), template.source(), artifacts, relations, placements, metadata, template.losses());
}
public static Map<String, String> fields(Artifact artifact) {
Map<String, String> result = new TreeMap<>();
if (artifact == null) return result;
result.put("title", artifact.title()); result.put("text", artifact.text()); result.put("type", artifact.type()); result.put("kind", artifact.kind().name());
artifact.attributes().forEach((key, value) -> {
if (!key.equals("LAST-CHANGE") && !artifact.extensions().getOrDefault("definition:" + key, "").startsWith("Taxonomy."))
result.put("attribute:" + key, comparable(value));
});
artifact.extensions().forEach((key, value) -> { if (!Set.of("xml", "rdfXml", "lastChange").contains(key)) result.put("extension:" + key, comparable(value)); });
return result;
}
private static String comparable(String value) {
if (value == null || !value.startsWith("<")) return value;
try { return com.taxonomy.exchange.ExchangeXml.semantic(value); }
catch (com.taxonomy.exchange.ExchangeFormatException notXml) { return value; }
}
/** Apply only fields changed externally; preserve non-intersecting local edits. */
public static Artifact merge(Artifact baselineExternal, Artifact currentInternal, Artifact incoming) {
if (currentInternal == null || baselineExternal == null) return incoming;
Map<String, String> before = fields(baselineExternal), after = fields(incoming);
Map<String, String> merged = new TreeMap<>(rawFields(currentInternal)), incomingValues = rawFields(incoming);
var keys = new java.util.TreeSet<>(before.keySet()); keys.addAll(after.keySet());
for (String key : keys) if (!java.util.Objects.equals(before.get(key), after.get(key))) {
if (after.containsKey(key)) merged.put(key, incomingValues.get(key)); else merged.remove(key);
}
Map<String, String> attributes = new LinkedHashMap<>(), extensions = new LinkedHashMap<>();
merged.forEach((key, value) -> { if (key.startsWith("attribute:")) attributes.put(key.substring(10), value);
if (key.startsWith("extension:")) extensions.put(key.substring(10), value); });
// Evidence describes the newly observed source, while mapped values above keep disjoint local edits.
for (String evidence : List.of("xml", "rdfXml", "lastChange")) {
extensions.remove(evidence);
if (incoming.extensions().containsKey(evidence)) extensions.put(evidence, incoming.extensions().get(evidence));
}
return new Artifact(incoming.id(), incoming.kind(), merged.get("type"), merged.get("title"), merged.get("text"), attributes, extensions);
}
private static Map<String, String> rawFields(Artifact value) {
Map<String, String> result = new TreeMap<>();
result.put("title", value.title()); result.put("text", value.text()); result.put("type", value.type()); result.put("kind", value.kind().name());
value.attributes().forEach((key, item) -> result.put("attribute:" + key, item));
value.extensions().forEach((key, item) -> result.put("extension:" + key, item)); return result;
}
private static void add(Map<String, Artifact> items, Artifact value) {
if (items.putIfAbsent(key(value), value) != null) throw new IntegrationProblem("DUPLICATE_IDENTITY", 400, "Exchange contains a duplicate external identity");
}
private static String emptyToNull(String value) { return value == null || value.isEmpty() ? null : value; }
}