package org.javers.core.diff;
import org.javers.common.exception.JaversException;
import org.javers.common.exception.JaversExceptionCode;
import org.javers.common.validation.Validate;
import org.javers.core.Javers;
import org.javers.core.JaversCoreConfiguration;
import org.javers.core.commit.CommitMetadata;
import org.javers.core.diff.appenders.NodeChangeAppender;
import org.javers.core.diff.appenders.PropertyChangeAppender;
import org.javers.core.diff.changetype.ObjectRemoved;
import org.javers.core.graph.LiveGraph;
import org.javers.core.graph.LiveGraphFactory;
import org.javers.core.graph.ObjectNode;
import org.javers.core.metamodel.object.GlobalId;
import org.javers.core.metamodel.property.Property;
import org.javers.core.metamodel.type.*;
import java.util.*;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.javers.core.diff.DiffBuilder.diff;
/**
* @author Maciej Zasada
* @author Bartosz Walacik
*/
public class DiffFactory {
private final NodeMatcher nodeMatcher = new NodeMatcher();
private final TypeMapper typeMapper;
private final List<NodeChangeAppender> nodeChangeAppenders;
private final List<PropertyChangeAppender> propertyChangeAppender;
private final LiveGraphFactory graphFactory;
private final JaversCoreConfiguration javersCoreConfiguration;
public DiffFactory(TypeMapper typeMapper, List<NodeChangeAppender> nodeChangeAppenders, List<PropertyChangeAppender> propertyChangeAppender, LiveGraphFactory graphFactory, JaversCoreConfiguration javersCoreConfiguration) {
this.typeMapper = typeMapper;
this.nodeChangeAppenders = nodeChangeAppenders;
this.graphFactory = graphFactory;
this.javersCoreConfiguration = javersCoreConfiguration;
//sort by priority
Collections.sort(propertyChangeAppender, (p1, p2) -> ((Integer)p1.priority()).compareTo(p2.priority()));
this.propertyChangeAppender = propertyChangeAppender;
}
/**
* @see Javers#compare(Object, Object)
*/
public Diff compare(Object oldVersion, Object currentVersion) {
return create(buildGraph(oldVersion), buildGraph(currentVersion), Optional.<CommitMetadata>empty());
}
public <T> Diff compareCollections(Collection<T> oldVersion, Collection<T> currentVersion, Class<T> itemClass) {
return create(buildGraph(oldVersion, itemClass), buildGraph(currentVersion, itemClass), Optional.<CommitMetadata>empty());
}
private LiveGraph buildGraph(Collection handle, Class itemClass) {
return graphFactory.createLiveGraph(handle, itemClass);
}
public Diff create(ObjectGraph leftGraph, ObjectGraph rightGraph, Optional<CommitMetadata> commitMetadata) {
Validate.argumentsAreNotNull(leftGraph, rightGraph);
GraphPair graphPair = new GraphPair(leftGraph, rightGraph);
return createAndAppendChanges(graphPair, commitMetadata);
}
public Diff singleTerminal(GlobalId removedId, CommitMetadata commitMetadata){
Validate.argumentsAreNotNull(removedId, commitMetadata);
DiffBuilder diff = diff();
diff.addChange(new ObjectRemoved(removedId, empty(), of(commitMetadata)));
return diff.build();
}
/**
* @param newDomainObject object or handle to object graph
*/
public Diff initial(Object newDomainObject) {
Validate.argumentIsNotNull(newDomainObject);
ObjectGraph currentGraph = buildGraph(newDomainObject);
GraphPair graphPair = new GraphPair(currentGraph);
return createAndAppendChanges(graphPair, empty());
}
private LiveGraph buildGraph(Object handle) {
JaversType jType = typeMapper.getJaversType(handle.getClass());
if (jType instanceof ValueType || jType instanceof PrimitiveType){
throw new JaversException(JaversExceptionCode.COMPARING_TOP_LEVEL_VALUES_NOT_SUPPORTED,
jType.getClass().getSimpleName(), handle.getClass().getSimpleName());
}
return graphFactory.createLiveGraph(handle);
}
/**
* Graph scope appender
*/
private Diff createAndAppendChanges(GraphPair graphPair, Optional<CommitMetadata> commitMetadata) {
DiffBuilder diff = diff();
//calculate node scope diff
for (NodeChangeAppender appender : nodeChangeAppenders) {
diff.addChanges(appender.getChangeSet(graphPair), commitMetadata);
}
//calculate snapshot of NewObjects
if (javersCoreConfiguration.isNewObjectsSnapshot()) {
for (ObjectNode node : graphPair.getOnlyOnRight()) {
FakeNodePair pair = new FakeNodePair(node);
appendPropertyChanges(diff, pair, commitMetadata);
}
}
//calculate property-to-property diff
for (NodePair pair : nodeMatcher.match(graphPair)) {
appendPropertyChanges(diff, pair, commitMetadata);
}
return diff.build();
}
/* Node scope appender */
private void appendPropertyChanges(DiffBuilder diff, NodePair pair, final Optional<CommitMetadata> commitMetadata) {
List<Property> nodeProperties = pair.getProperties();
for (Property property : nodeProperties) {
//optimization, skip all appenders if null on both sides
if (pair.isNullOnBothSides(property)) {
continue;
}
JaversType javersType = ((JaversProperty) property).getType();
appendChanges(diff, pair, property, javersType, commitMetadata);
}
}
private void appendChanges(DiffBuilder diff, NodePair pair, Property property, JaversType javersType, Optional<CommitMetadata> commitMetadata) {
for (PropertyChangeAppender appender : propertyChangeAppender) {
if (! appender.supports(javersType)){
continue;
}
final Change change = appender.calculateChanges(pair, property);
if (change != null) {
diff.addChange(change, pair.getRight().wrappedCdo());
commitMetadata.ifPresent(cm -> change.bindToCommit(cm));
}
break;
}
}
}