Skip to content

Commit

Permalink
Core Editor: Allow copying hash of a string
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadelessFox committed Mar 21, 2024
1 parent 9843390 commit 25c675f
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
package com.shade.decima.model.util.hash;

import com.shade.decima.model.util.hash.spi.Hasher;
import com.shade.util.NotNull;

/**
* A re-implementation of {@link java.util.zip.CRC32C} but with {@code 0} as a default seed.
*/
public class CRC32C {
public static class Provider implements Hasher.ToInt {
@NotNull
@Override
public String name() {
return "CRC32C";
}

@Override
public int calculate(@NotNull byte[] data) {
return CRC32C.calculate(data);
}
}

private static final int[] LOOKUP = new int[256];

static {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.shade.decima.model.util.hash;

import com.shade.decima.model.util.hash.spi.Hasher;
import com.shade.util.NotNull;

import java.lang.invoke.MethodHandles;
Expand All @@ -13,6 +14,19 @@
* See <a href=https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp>https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp</a>
*/
public class MurmurHash3 {
public static class Provider implements Hasher.ToLong {
@NotNull
@Override
public String name() {
return "MurmurHash3";
}

@Override
public long calculate(@NotNull byte[] data) {
return mmh3(data)[0];
}
}

// Constants for 128-bit MurmurHash3 variant
private static final long C1 = 0x87c37b91114253d5L;
private static final long C2 = 0x4cf5ad432745937fL;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.shade.decima.model.util.hash.spi;

import com.shade.util.NotNull;

import java.util.ServiceLoader;

public sealed interface Hasher {
@NotNull
static Iterable<Hasher> availableHashers() {
return ServiceLoader.load(Hasher.class);
}

@NotNull
String name();

non-sealed interface ToInt extends Hasher {
int calculate(@NotNull byte[] data);
}

non-sealed interface ToLong extends Hasher {
long calculate(@NotNull byte[] data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
com.shade.decima.model.util.hash.CRC32C$Provider
com.shade.decima.model.util.hash.MurmurHash3$Provider
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.shade.decima.ui.dialogs;

import com.formdev.flatlaf.FlatClientProperties;
import com.shade.decima.model.util.hash.CRC32C;
import com.shade.decima.model.util.hash.MurmurHash3;
import com.shade.decima.model.util.hash.spi.Hasher;
import com.shade.platform.ui.controls.DocumentAdapter;
import com.shade.platform.ui.util.UIUtils;
import com.shade.util.NotNull;
import com.shade.util.Nullable;
import net.miginfocom.swing.MigLayout;

Expand All @@ -15,7 +15,9 @@
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class HashToolDialog extends JDialog {
Expand Down Expand Up @@ -51,28 +53,13 @@ public void windowDeactivated(WindowEvent e) {
}

private static class ContentPanel extends JPanel {
private final JTextField crc32FieldDec;
private final JTextField crc32FieldHex;
private final JTextField murmurFieldDec;
private final JTextField murmurFieldHex;
private final List<HasherInfo> converters = new ArrayList<>();
private final JTextField inputField;
private final JToggleButton nullTerminatedButton;

public ContentPanel() {
setLayout(new MigLayout("ins dialog,wrap 3", "[][grow,fill,150lp]"));

crc32FieldDec = new JTextField();
crc32FieldDec.setEditable(false);

crc32FieldHex = new JTextField();
crc32FieldHex.setEditable(false);

murmurFieldDec = new JTextField();
murmurFieldDec.setEditable(false);

murmurFieldHex = new JTextField();
murmurFieldHex.setEditable(false);

nullTerminatedButton = new JToggleButton(UIManager.getIcon("Action.nullTerminatorIcon"));
nullTerminatedButton.setToolTipText("Null-terminated string");
nullTerminatedButton.addActionListener(e -> update());
Expand All @@ -87,13 +74,19 @@ public ContentPanel() {
add(new JLabel("Text:"));
add(inputField, "span 2");

add(new JLabel("CRC32-C:"));
add(crc32FieldDec);
add(crc32FieldHex);
for (Hasher hasher : Hasher.availableHashers()) {
final JTextField decField = new JTextField();
decField.setEditable(false);

final JTextField hexField = new JTextField();
hexField.setEditable(false);

add(new JLabel("MurmurHash3:"));
add(murmurFieldDec);
add(murmurFieldHex);
add(new JLabel(hasher.name() + ":"));
add(decField);
add(hexField);

converters.add(new HasherInfo(hasher, decField, hexField));
}

update();
}
Expand All @@ -105,13 +98,19 @@ private void update() {
data = Arrays.copyOf(data, data.length + 1);
}

final var crc32 = CRC32C.calculate(data);
final var mmh3 = MurmurHash3.mmh3(data)[0];

crc32FieldDec.setText(Integer.toUnsignedString(crc32));
crc32FieldHex.setText(String.format("%#010x", crc32));
murmurFieldDec.setText(Long.toUnsignedString(mmh3));
murmurFieldHex.setText(String.format("%#018x", mmh3));
for (HasherInfo info : converters) {
if (info.hasher instanceof Hasher.ToInt h) {
final var value = h.calculate(data);
info.decField.setText(Integer.toUnsignedString(value));
info.hexField.setText("%#010x".formatted(value));
} else if (info.hasher instanceof Hasher.ToLong h) {
final var value = h.calculate(data);
info.decField.setText(Long.toUnsignedString(value));
info.hexField.setText("%#018x".formatted(value));
}
}
}
}

private record HasherInfo(@NotNull Hasher hasher, @NotNull JTextField decField, @NotNull JTextField hexField) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.shade.decima.ui.editor.core.menu;

import com.shade.decima.model.util.hash.spi.Hasher;
import com.shade.decima.ui.editor.core.CoreNodeObject;
import com.shade.platform.model.LazyWithMetadata;
import com.shade.platform.ui.PlatformDataKeys;
import com.shade.platform.ui.menus.MenuItem;
import com.shade.platform.ui.menus.MenuItemContext;
import com.shade.platform.ui.menus.MenuItemProvider;
import com.shade.platform.ui.menus.MenuItemRegistration;
import com.shade.platform.ui.util.UIUtils;
import com.shade.util.NotImplementedException;
import com.shade.util.NotNull;
import com.shade.util.Nullable;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

import static com.shade.decima.ui.menu.MenuConstants.*;

@MenuItemRegistration(parent = CTX_MENU_CORE_EDITOR_ID, id = CopyHashItem.ID, name = "Copy Value &Hash", group = CTX_MENU_CORE_EDITOR_GROUP_GENERAL, order = 5000)
public class CopyHashItem extends MenuItem {
public static final String ID = CTX_MENU_CORE_EDITOR_ID + ".copyHash";
public static final String GROUP = "1000," + ID + ".general";

@Override
public boolean isVisible(@NotNull MenuItemContext ctx) {
return ctx.getData(PlatformDataKeys.SELECTION_KEY) instanceof CoreNodeObject node
&& node.getValue() instanceof String;
}

@MenuItemRegistration(parent = ID, group = GROUP, order = 1000)
public static class HashPlaceholderItem extends MenuItem implements MenuItemProvider {
@NotNull
@Override
public List<LazyWithMetadata<MenuItem, MenuItemRegistration>> create(@NotNull MenuItemContext ctx) {
final List<LazyWithMetadata<MenuItem, MenuItemRegistration>> items = new ArrayList<>();

for (Hasher provider : ServiceLoader.load(Hasher.class)) {
final int index = items.size();

items.add(LazyWithMetadata.of(
() -> new HashItem(provider, index),
MenuItemProvider.createRegistration(ID, GROUP),
HashItem.class
));
}

return items;
}
}

public static class HashItem extends MenuItem {
private final Hasher hasher;
private final int index;

public HashItem(@NotNull Hasher hasher, int index) {
this.hasher = hasher;
this.index = index;
}

@Override
public void perform(@NotNull MenuItemContext ctx) {
final CoreNodeObject node = (CoreNodeObject) ctx.getData(PlatformDataKeys.SELECTION_KEY);
final String text = (String) node.getValue();
final String hash;

if (hasher instanceof Hasher.ToInt h) {
hash = "%#010x".formatted(h.calculate(text.getBytes()));
} else if (hasher instanceof Hasher.ToLong h) {
hash = "%#018x".formatted(h.calculate(text.getBytes()));
} else {
throw new NotImplementedException();
}

final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
final StringSelection selection = new StringSelection(hash);
clipboard.setContents(selection, selection);
}

@Nullable
@Override
public String getName(@NotNull MenuItemContext ctx) {
return UIUtils.getLabelWithIndexMnemonic(hasher.name(), index);
}
}
}

0 comments on commit 25c675f

Please sign in to comment.