Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ build/
.vscode/
**/**/.flattened-pom.xml
./.flattened-pom.xml

### Reasonix ###
.reasonix/
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>

</dependencies>
<licenses>
<license>
Expand All @@ -82,6 +89,12 @@
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>

<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.plugin.version}</version>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/openquartz/javaobjdiff/DiffComparable.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
public interface DiffComparable {

/**
* 是否不同
* 是否相同
*
* @param source source
* @param target target
* @return 是否不同,false: 相同, true: 不同
* @return 是否相同, true: 相同, false: 不同
*/
boolean diff(Object source, Object target);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.openquartz.javaobjdiff.formatter;

import com.openquartz.javaobjdiff.DiffFormatter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
Expand Down Expand Up @@ -34,7 +33,7 @@ public Object format(Object value, String pattern) {
}

if (value instanceof TemporalAccessor) {
return DateTimeFormatter.ofPattern(pattern).format((LocalDateTime) value);
return DateTimeFormatter.ofPattern(pattern).format((TemporalAccessor) value);
}

throw new IllegalArgumentException("Unsupported type: " + value.getClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,18 @@ public static String desensitizeEmail(String email) {
}

int atIndex = email.indexOf("@");
if (atIndex == 0) {
return email;
}

String prefix = email.substring(0, atIndex);
int lengthToReplace = prefix.length() - 1;

String replacement = Stream.generate(() -> "*")
.limit(lengthToReplace)
.collect(Collectors.joining());

return replacement + prefix.charAt(0) + "@" + email.substring(atIndex);
return replacement + prefix.charAt(0) + email.substring(atIndex);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public Object format(Object value, String pattern) {
}

public static String desensitizePhoneNumber(String phoneNumber) {
if (phoneNumber == null) {
return null;
}
if (MOBILE_PATTERN.matcher(phoneNumber).matches()) {
// 手机号码脱敏处理
return phoneNumber.substring(0, 3) + "****" + phoneNumber.substring(7);
Expand Down
277 changes: 277 additions & 0 deletions src/test/java/com/openquartz/javaobjdiff/DiffUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package com.openquartz.javaobjdiff;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.openquartz.javaobjdiff.test.bean.Address;
import com.openquartz.javaobjdiff.test.bean.Packet;
import com.openquartz.javaobjdiff.test.bean.Person;
import com.openquartz.javaobjdiff.test.bean.Sex;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.junit.jupiter.api.Test;

/**
* DiffUtils 核心功能单元测试
*
* @author svnee
*/
public class DiffUtilsTest {

private Person person(String name, Integer age, Sex sex, Address address) {
Person person = new Person();
person.setName(name);
person.setAge(age);
person.setSex(sex);
person.setAddress(address);
return person;
}

private Address address(String country, String city) {
Address address = new Address();
address.setCountry(country);
address.setCity(city);
return address;
}

@Test
public void testSameReferenceNoDiff() {
Person person = person("jack", 10, Sex.MAN, null);
DiffResult<Person> result = DiffUtils.diffResult(person, person);
assertFalse(result.isDiff());
assertEquals(0, result.getNumberOfDiffs());
}

@Test
public void testEqualFieldsNoDiff() {
Person p1 = person("jack", 10, Sex.MAN, address("cn", "shanghai"));
Person p2 = person("jack", 10, Sex.MAN, address("cn", "shanghai"));
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertFalse(result.isDiff());
assertEquals(0, result.getNumberOfDiffs());
}

@Test
public void testSimpleFieldDiff() {
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("tom", 10, Sex.MAN, null);
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertTrue(result.isDiff());
assertEquals(1, result.getNumberOfDiffs());
Diff<?> diff = result.getDiffs().get(0);
assertEquals("name", diff.getFieldName());
assertEquals("jack", diff.getLeft());
assertEquals("tom", diff.getRight());
}

@Test
public void testDiffIgnoreAnnotation() {
// age 字段标有 @DiffIgnore,两边 age 不同也不应产生 diff
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("jack", 11, Sex.MAN, null);
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertFalse(result.isDiff());
assertEquals(0, result.getNumberOfDiffs());
}

@Test
public void testExcludeField() {
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("tom", 10, Sex.MAN, null);
// 注意:直接传 diffResult(p1, p2, "name") 会因 DiffUtils 的重载歧义无法编译,这里用显式数组
DiffResult<Person> result = DiffUtils.diffResult(p1, p2, new String[]{"name"});
assertEquals(0, result.getNumberOfDiffs());
}

@Test
public void testExcludeMultipleFields() {
Person p1 = person("jack", 10, Sex.MAN, null);
p1.setMobile("17000000056");
p1.setEmail("wangtest1111@126.com");
Person p2 = person("tom", 10, Sex.MAN, null);
p2.setMobile("17130000056");
p2.setEmail("litest2211@126.com");
DiffResult<Person> result = DiffUtils.diffResult(p1, p2, new String[]{"name", "mobile", "email"});
assertEquals(0, result.getNumberOfDiffs());
}

@Test
public void testDiffAlias() {
Person p1 = person("jack", 10, Sex.MAN, address("tangzhen", "shanghai"));
Person p2 = person("jack", 10, Sex.MAN, address("tangzhen", "beijing"));
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(1, result.getNumberOfDiffs());
Diff<?> diff = result.getDiffs().get(0);
// Address.city 标有 @DiffAlias(alias = "城市")
assertEquals("城市", diff.getActualName());
assertEquals("shanghai", diff.getLeft());
assertEquals("beijing", diff.getRight());
}

@Test
public void testDateFormat() {
Person p1 = person("jack", 10, Sex.MAN, null);
p1.setBirthDate(LocalDateTime.of(2026, 8, 12, 10, 30));
Person p2 = person("jack", 10, Sex.MAN, null);
p2.setBirthDate(LocalDateTime.of(2026, 8, 2, 10, 30));
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(1, result.getNumberOfDiffs());
Diff<?> diff = result.getDiffs().get(0);
assertEquals("birthDate", diff.getFieldName());
// birthDate 标有 @DiffFormat(pattern = "yyyy-MM-dd")
assertEquals("2026-08-12", diff.getFormatValue(diff.getLeft()));
assertEquals("2026-08-02", diff.getFormatValue(diff.getRight()));
}

@Test
public void testCustomEnumComparator() {
// sex 字段标有 @DiffCompare(using = SelfEnumComparator.class)
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("jack", 10, Sex.WOMAN, null);
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(1, result.getNumberOfDiffs());
assertEquals("sex", result.getDiffs().get(0).getFieldName());
}

@Test
public void testCustomEnumComparatorSameValueNoDiff() {
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("jack", 10, Sex.MAN, null);
assertEquals(0, DiffUtils.diffResult(p1, p2).getNumberOfDiffs());
}

@Test
public void testNestedDiffBean() {
Person p1 = person("jack", 10, Sex.MAN, address("tangzhen", "shanghai"));
Person p2 = person("jack", 10, Sex.MAN, address("daxing", "shanghai"));
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(1, result.getNumberOfDiffs());
assertEquals("address.country", result.getDiffs().get(0).getFieldName());
}

@Test
public void testNestedDiffBeanWithNullSide() {
// 一边为 null,应逐字段比较出差异
Person p1 = person("jack", 10, Sex.MAN, address("tangzhen", "shanghai"));
Person p2 = person("jack", 10, Sex.MAN, null);
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertTrue(result.isDiff());
assertTrue(result.getNumberOfDiffs() > 0);
}

@Test
public void testBeanCollectionCustomType() {
// 集合元素为自定义类型 Packet,元素内 money 使用 BigDecimalEffectiveDiffComparator
// 1.0 与 1.00 视为相等,应无差异
Packet packet1 = new Packet();
packet1.setName("LV");
packet1.setMoney(new BigDecimal("1.0"));
Packet packet2 = new Packet();
packet2.setName("LV");
packet2.setMoney(new BigDecimal("1.00"));

Person p1 = person("jack", 10, Sex.MAN, null);
p1.setPacketList(Arrays.asList(packet1));
Person p2 = person("jack", 10, Sex.MAN, null);
p2.setPacketList(Arrays.asList(packet2));

DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(0, result.getNumberOfDiffs());
}

@Test
public void testBeanCollectionCustomTypeDiff() {
Packet packet1 = new Packet();
packet1.setName("LV");
packet1.setMoney(new BigDecimal("1.0"));
Packet packet2 = new Packet();
packet2.setName("PT");
packet2.setMoney(new BigDecimal("1.0"));

Person p1 = person("jack", 10, Sex.MAN, null);
p1.setPacketList(Arrays.asList(packet1));
Person p2 = person("jack", 10, Sex.MAN, null);
p2.setPacketList(Arrays.asList(packet2));

DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(1, result.getNumberOfDiffs());
assertEquals("packetList[0].name", result.getDiffs().get(0).getFieldName());
assertEquals("LV", result.getDiffs().get(0).getLeft());
assertEquals("PT", result.getDiffs().get(0).getRight());
}

@Test
public void testBeanCollectionCustomTypeIndexAligned() {
// 集合长度不同时按索引对齐比较,多出的元素与 null 比较
Packet packet1 = new Packet();
packet1.setName("LV");
Packet packet2 = new Packet();
packet2.setName("PT");

Person p1 = person("jack", 10, Sex.MAN, null);
p1.setPacketList(Arrays.asList(packet1, packet2));
Person p2 = person("jack", 10, Sex.MAN, null);
p2.setPacketList(Arrays.asList(packet1));

DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertTrue(result.isDiff());
assertTrue(result.getDiffs().stream().anyMatch(d -> d.getFieldName().startsWith("packetList[1]")));
}

@Test
public void testBeanCollectionJdkTypeSame() {
Person p1 = person("jack", 10, Sex.MAN, null);
p1.setFriendIdList(Arrays.asList(1, 2, 3));
Person p2 = person("jack", 10, Sex.MAN, null);
p2.setFriendIdList(Arrays.asList(1, 2, 3));
assertEquals(0, DiffUtils.diffResult(p1, p2).getNumberOfDiffs());
}

@Test
public void testBeanCollectionJdkTypeDiff() {
Person p1 = person("jack", 10, Sex.MAN, null);
p1.setFriendIdList(Arrays.asList(1, 2, 3));
Person p2 = person("jack", 10, Sex.MAN, null);
p2.setFriendIdList(Arrays.asList(1, 2, 4));
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);
assertEquals(1, result.getNumberOfDiffs());
assertEquals("friendIdList", result.getDiffs().get(0).getFieldName());
}

@Test
public void testToStringContainsArrow() {
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("tom", 10, Sex.MAN, null);
String diff = DiffUtils.diff(p1, p2);
assertTrue(diff.contains("->"));
assertTrue(diff.contains("jack"));
assertTrue(diff.contains("tom"));
}

@Test
public void testDiffResultApi() {
Person p1 = person("jack", 10, Sex.MAN, null);
Person p2 = person("tom", 10, Sex.MAN, null);
DiffResult<Person> result = DiffUtils.diffResult(p1, p2);

assertSame(p1, result.getLeft());
assertSame(p2, result.getRight());
assertTrue(result.getNumberOfDiffs() > 0);
assertTrue(result.isDiff());

// getDiffs() 返回不可修改列表
List<Diff<?>> diffs = result.getDiffs();
assertThrows(UnsupportedOperationException.class, () -> diffs.add(null));

// 支持迭代
Iterator<Diff<?>> iterator = result.iterator();
assertTrue(iterator.hasNext());
assertTrue(iterator.next().getFieldName() != null);
}
}
Loading