AuditChange.java
package com.rbctcsworld.ecommerce.audit;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** One changed field of one record: what the audit trail stores per row. */
public record AuditChange(String entityType, long entityId, String action, String field, String oldValue,
String newValue) {
public static final String PRODUCT = "PRODUCT";
public static final String ORDER = "ORDER";
public static final String CUSTOMER = "CUSTOMER";
public static final String COUPON = "COUPON";
public static final String WAREHOUSE = "WAREHOUSE";
public static final int MAX_VALUE = 500;
/**
* The fields whose value differs between before and after, in the order of {@code after}.
* {@code before} = null means the record was created (every field with a value is listed).
* Numbers are compared by value: 12.5 and 12.50 are the same price.
*/
public static List<AuditChange> diff(String entityType, long entityId, String action,
Map<String, Object> before, Map<String, Object> after) {
List<AuditChange> changes = new ArrayList<>();
for (Map.Entry<String, Object> e : after.entrySet()) {
Object old = before == null ? null : before.get(e.getKey());
if (!same(old, e.getValue())) {
changes.add(new AuditChange(entityType, entityId, action, e.getKey(), text(old), text(e.getValue())));
}
}
return changes;
}
/** Convenience for building the before/after maps in field order. */
public static Map<String, Object> fields(Object... namesAndValues) {
Map<String, Object> m = new LinkedHashMap<>();
for (int i = 0; i < namesAndValues.length; i += 2) {
m.put((String) namesAndValues[i], namesAndValues[i + 1]);
}
return m;
}
static boolean same(Object a, Object b) {
if (a instanceof BigDecimal x && b instanceof BigDecimal y) {
return x.compareTo(y) == 0;
}
return Objects.equals(a, b);
}
/** As stored: plain text, BigDecimal without exponent, at most MAX_VALUE characters, null stays null. */
static String text(Object value) {
if (value == null) return null;
String s = value instanceof BigDecimal d ? d.toPlainString() : String.valueOf(value);
return s.length() > MAX_VALUE ? s.substring(0, MAX_VALUE) : s;
}
}