package org.javers.core.diff.appenders;
import org.javers.common.collections.Maps;
import org.javers.common.exception.JaversException;
import org.javers.common.validation.Validate;
import org.javers.core.diff.NodePair;
import org.javers.core.diff.changetype.map.*;
import org.javers.core.metamodel.object.DehydrateMapFunction;
import org.javers.core.metamodel.object.GlobalIdFactory;
import org.javers.core.metamodel.object.OwnerContext;
import org.javers.core.metamodel.object.PropertyOwnerContext;
import org.javers.core.metamodel.property.Property;
import org.javers.core.metamodel.type.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import static org.javers.common.exception.JaversExceptionCode.VALUE_OBJECT_IS_NOT_SUPPORTED_AS_MAP_KEY;
/**
* @author bartosz walacik
*/
class MapChangeAppender extends CorePropertyChangeAppender<MapChange> {
private static final Logger logger = LoggerFactory.getLogger(MapChangeAppender.class);
private final TypeMapper typeMapper;
private final GlobalIdFactory globalIdFactory;
MapChangeAppender(TypeMapper typeMapper, GlobalIdFactory globalIdFactory) {
Validate.argumentsAreNotNull(typeMapper, globalIdFactory);
this.typeMapper = typeMapper;
this.globalIdFactory = globalIdFactory;
}
@Override
public boolean supports(JaversType propertyType) {
if (!(propertyType instanceof MapType)){
return false;
}
MapContentType mapContentType = typeMapper.getMapContentType((MapType)propertyType);
if (mapContentType.getKeyType() instanceof ValueObjectType){
throw new JaversException(VALUE_OBJECT_IS_NOT_SUPPORTED_AS_MAP_KEY, propertyType);
}
return true;
}
@Override
public MapChange calculateChanges(NodePair pair, Property property) {
Map leftRawMap = (Map)pair.getLeftPropertyValue(property);
Map rightRawMap = (Map)pair.getRightPropertyValue(property);
MapType mapType = ((JaversProperty) property).getType();
MapContentType mapContentType = typeMapper.getMapContentType(mapType);
OwnerContext owner = new PropertyOwnerContext(pair.getGlobalId(), property.getName());
List<EntryChange> changes = calculateEntryChanges(leftRawMap, rightRawMap, owner, mapContentType);
if (!changes.isEmpty()){
renderNotParametrizedWarningIfNeeded(mapContentType.getKeyType().getBaseJavaType(), "key", "Map", property);
renderNotParametrizedWarningIfNeeded(mapContentType.getValueType().getBaseJavaType(), "value", "Map", property);
return new MapChange(pair.getGlobalId(), property.getName(), changes);
}
else {
return null;
}
}
/**
* @return never returns null
*/
List<EntryChange> calculateEntryChanges(Map leftRawMap, Map rightRawMap, OwnerContext owner, MapContentType mapContentType) {
DehydrateMapFunction dehydrateFunction = new DehydrateMapFunction(globalIdFactory, mapContentType);
Map leftMap = MapType.mapStatic(leftRawMap, dehydrateFunction, owner);
Map rightMap = MapType.mapStatic(rightRawMap, dehydrateFunction, owner);
if (Objects.equals(leftMap, rightMap)) {
return Collections.emptyList();
}
List<EntryChange> changes = new ArrayList<>();
for (Object commonKey : Maps.commonKeys(leftMap, rightMap)) {
Object leftVal = leftMap.get(commonKey);
Object rightVal = rightMap.get(commonKey);
if (!Objects.equals(leftVal, rightVal)){
changes.add( new EntryValueChange(commonKey, leftVal, rightVal));
}
}
for (Object addedKey : Maps.keysDifference(rightMap, leftMap)) {
Object addedValue = rightMap.get(addedKey);
changes.add( new EntryAdded(addedKey, addedValue));
}
for (Object removedKey : Maps.keysDifference(leftMap, rightMap)) {
Object removedValue = leftMap.get(removedKey);
changes.add( new EntryRemoved(removedKey, removedValue));
}
return changes;
}
}