Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -264,19 +264,81 @@ private static final class FingerprintBuilder {
private boolean needsSeparator;

private FingerprintBuilder field(Object value) {
this.startField();
this.mixText(Objects.toString(value, ""));
return this.finishField();
}

private FingerprintBuilder field(boolean value) {
this.startField();
this.mixText(value ? "true" : "false");
return this.finishField();
}

private FingerprintBuilder field(char value) {
this.startField();
this.mix(value);
return this.finishField();
}

private FingerprintBuilder field(int value) {
this.startField();
this.mixDecimal(value);
return this.finishField();
}

private FingerprintBuilder field(long value) {
this.startField();
this.mixDecimal(value);
return this.finishField();
}

private long value() {
return this.hash;
}

private void startField() {
if (this.needsSeparator) {
this.mix(':');
}
String text = Objects.toString(value, "");
for (int index = 0; index < text.length(); index++) {
this.mix(text.charAt(index));
}
}

private FingerprintBuilder finishField() {
this.needsSeparator = true;
return this;
}

private long value() {
return this.hash;
private void mixText(String value) {
for (int index = 0; index < value.length(); index++) {
this.mix(value.charAt(index));
}
}

private void mixDecimal(long value) {
long remaining = value > 0L ? -value : value;
long lowDigits = 0L;
long highDigits = 0L;
int digitCount = 0;
do {
int digit = (int) -(remaining % 10L);
if (digitCount < 16) {
lowDigits |= (long) digit << (digitCount * 4);
} else {
highDigits |= (long) digit << ((digitCount - 16) * 4);
}
digitCount++;
remaining /= 10L;
} while (remaining != 0L);

if (value < 0L) {
this.mix('-');
}
for (int index = digitCount - 1; index >= 0; index--) {
int digit = index < 16
? (int) (lowDigits >>> (index * 4)) & 0x0f
: (int) (highDigits >>> ((index - 16) * 4)) & 0x0f;
this.mix((char) ('0' + digit));
}
}

private void mix(char value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package top.ellan.mahjong.table.render;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Objects;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class TableRegionFingerprintServiceTest {
@Test
void primitiveFieldsPreserveTheDelimitedTextFingerprint() throws ReflectiveOperationException {
Class<?> builderType = Class.forName(TableRegionFingerprintService.class.getName() + "$FingerprintBuilder");
Constructor<?> constructor = builderType.getDeclaredConstructor();
constructor.setAccessible(true);
Object builder = constructor.newInstance();

Method objectField = accessible(builderType.getDeclaredMethod("field", Object.class));
Method booleanField = accessible(builderType.getDeclaredMethod("field", boolean.class));
Method charField = accessible(builderType.getDeclaredMethod("field", char.class));
Method intField = accessible(builderType.getDeclaredMethod("field", int.class));
Method longField = accessible(builderType.getDeclaredMethod("field", long.class));
Method value = accessible(builderType.getDeclaredMethod("value"));

objectField.invoke(builder, "prefix");
booleanField.invoke(builder, true);
booleanField.invoke(builder, false);
charField.invoke(builder, 'Z');
intField.invoke(builder, 0);
intField.invoke(builder, 1);
intField.invoke(builder, -1);
intField.invoke(builder, Integer.MIN_VALUE);
intField.invoke(builder, Integer.MAX_VALUE);
longField.invoke(builder, Long.MIN_VALUE);
longField.invoke(builder, Long.MAX_VALUE);
objectField.invoke(builder, new Object[] {null});

assertEquals(
referenceFingerprint(
"prefix",
true,
false,
'Z',
0,
1,
-1,
Integer.MIN_VALUE,
Integer.MAX_VALUE,
Long.MIN_VALUE,
Long.MAX_VALUE,
null
),
value.invoke(builder)
);
}

private static Method accessible(Method method) {
method.setAccessible(true);
return method;
}

private static long referenceFingerprint(Object... fields) {
long hash = 0xcbf29ce484222325L;
boolean needsSeparator = false;
for (Object field : fields) {
if (needsSeparator) {
hash = mix(hash, ':');
}
String text = Objects.toString(field, "");
for (int index = 0; index < text.length(); index++) {
hash = mix(hash, text.charAt(index));
}
needsSeparator = true;
}
return hash;
}

private static long mix(long hash, char value) {
return (hash ^ value) * 0x100000001b3L;
}
}
Loading