package org.javers.shadow; import com.google.gson.JsonObject; import org.javers.common.validation.Validate; import org.javers.core.commit.CommitMetadata; import org.javers.core.json.JsonConverter; import org.javers.core.metamodel.object.*; import org.javers.core.metamodel.type.*; import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; import static org.javers.core.metamodel.object.CdoSnapshotStateBuilder.cdoSnapshotState; /** * Stateful builder * * @author bartosz.walacik */ class ShadowGraphBuilder { private final JsonConverter jsonConverter; private final BiFunction<CommitMetadata, GlobalId, CdoSnapshot> referenceResolver; private boolean built = false; private Map<GlobalId, ShadowBuilder> builtNodes = new HashMap<>(); private final TypeMapper typeMapper; private final CommitMetadata rootContext; ShadowGraphBuilder(JsonConverter jsonConverter, BiFunction<CommitMetadata, GlobalId, CdoSnapshot> referenceResolver, TypeMapper typeMapper, CommitMetadata rootContext) { this.jsonConverter = jsonConverter; this.referenceResolver = referenceResolver; this.typeMapper = typeMapper; this.rootContext = rootContext; } Object buildDeepShadow(CdoSnapshot cdoSnapshot) { Validate.argumentIsNotNull(cdoSnapshot); switchToBuilt(); ShadowBuilder root = assembleShadowStub(cdoSnapshot); doWiring(); return root.getShadow(); } private void doWiring() { builtNodes.values().forEach(ShadowBuilder::wire); } private void switchToBuilt() { if (built) { throw new IllegalStateException("already built"); } built = true; } private ShadowBuilder assembleShallowReferenceShadow(InstanceId instanceId, ShallowReferenceType shallowReferenceType) { CdoSnapshotState state = cdoSnapshotState().withPropertyValue(shallowReferenceType.getIdProperty(), instanceId.getCdoId()).build(); JsonObject jsonElement = (JsonObject)jsonConverter.toJsonElement(state); Object shadowStub = jsonConverter.fromJson(jsonElement, shallowReferenceType.getBaseJavaClass()); ShadowBuilder shadowBuilder = new ShadowBuilder(null, shadowStub); builtNodes.put(instanceId, shadowBuilder); return shadowBuilder; } private ShadowBuilder assembleShadowStub(CdoSnapshot cdoSnapshot) { ShadowBuilder shadowBuilder = new ShadowBuilder(cdoSnapshot, null); builtNodes.put(cdoSnapshot.getGlobalId(), shadowBuilder); JsonObject jsonElement = (JsonObject)jsonConverter.toJsonElement(cdoSnapshot.getState()); followReferences(shadowBuilder, jsonElement); Object shadowStub = jsonConverter.fromJson(jsonElement, cdoSnapshot.getManagedType().getBaseJavaClass()); shadowBuilder.withStub(shadowStub); return shadowBuilder; } private void followReferences(ShadowBuilder currentNode, JsonObject jsonElement) { CdoSnapshot cdoSnapshot = currentNode.getCdoSnapshot(); cdoSnapshot.getManagedType().forEachProperty( (JaversProperty property) -> { if (cdoSnapshot.isNull(property)) { return; } if (property.getType() instanceof ManagedType) { GlobalId refId = (GlobalId) cdoSnapshot.getPropertyValue(property); ShadowBuilder target = createOrReuseNodeFromRef(refId, property.getType()); if (target != null) { currentNode.addReferenceWiring(property, target); } jsonElement.remove(property.getName()); } if (typeMapper.isContainerOfManagedTypes(property.getType()) || typeMapper.isKeyValueTypeWithManagedTypes(property.getType())) { EnumerableType propertyType = property.getType(); Object containerWithRefs = cdoSnapshot.getPropertyValue(property); if (!propertyType.isEmpty(containerWithRefs)) { currentNode.addEnumerableWiring(property, propertyType.map(containerWithRefs, (value) -> passValueOrCreateNodeFromRef(value, propertyType))); jsonElement.remove(property.getName()); } } }); } private Object passValueOrCreateNodeFromRef(Object value, JaversType propertyType) { if (value instanceof GlobalId) { return createOrReuseNodeFromRef((GlobalId)value, propertyType); } return value; } private ShadowBuilder createOrReuseNodeFromRef(GlobalId globalId, JaversType propertyType) { if (builtNodes.containsKey(globalId)) { return builtNodes.get(globalId); } if (propertyType instanceof ShallowReferenceType) { return assembleShallowReferenceShadow((InstanceId)globalId, (ShallowReferenceType)propertyType); } CdoSnapshot cdoSnapshot = referenceResolver.apply(rootContext, globalId); if (cdoSnapshot != null) { return assembleShadowStub(cdoSnapshot); } return null; } }