summaryrefslogtreecommitdiff
path: root/modules/vendor-nabu/src/main/java/io/ipfs/multibase/Base256Emoji.java
blob: e6368f3c5ac22af2e52ca5055fa83d15792371cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package io.ipfs.multibase;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/*
 * Copyright 2025 Michael Vorburger.ch
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * <a
 * href="https://github.com/multiformats/multibase/blob/master/rfcs/Base256Emoji.md">Base256Emoji</a>
 * is an encoding mapping each 0-255 byte value to (or from) a specific single Unicode Emoji
 * character.
 *
 * @author <a href="https://www.vorburger.ch/">Michael Vorburger.ch</a>
 */
public class Base256Emoji {

  // from https://github.com/multiformats/multibase/blob/master/rfcs/Base256Emoji.md
  private static final String[] EMOJIS = {
    "๐Ÿš€", "๐Ÿช", "โ˜„", "๐Ÿ›ฐ", "๐ŸŒŒ", "๐ŸŒ‘", "๐ŸŒ’", "๐ŸŒ“", "๐ŸŒ”", "๐ŸŒ•",
    "๐ŸŒ–", "๐ŸŒ—", "๐ŸŒ˜", "๐ŸŒ", "๐ŸŒ", "๐ŸŒŽ", "๐Ÿ‰", "โ˜€", "๐Ÿ’ป", "๐Ÿ–ฅ",
    "๐Ÿ’พ", "๐Ÿ’ฟ", "๐Ÿ˜‚", "โค", "๐Ÿ˜", "๐Ÿคฃ", "๐Ÿ˜Š", "๐Ÿ™", "๐Ÿ’•", "๐Ÿ˜ญ",
    "๐Ÿ˜˜", "๐Ÿ‘", "๐Ÿ˜…", "๐Ÿ‘", "๐Ÿ˜", "๐Ÿ”ฅ", "๐Ÿฅฐ", "๐Ÿ’”", "๐Ÿ’–", "๐Ÿ’™",
    "๐Ÿ˜ข", "๐Ÿค”", "๐Ÿ˜†", "๐Ÿ™„", "๐Ÿ’ช", "๐Ÿ˜‰", "โ˜บ", "๐Ÿ‘Œ", "๐Ÿค—", "๐Ÿ’œ",
    "๐Ÿ˜”", "๐Ÿ˜Ž", "๐Ÿ˜‡", "๐ŸŒน", "๐Ÿคฆ", "๐ŸŽ‰", "๐Ÿ’ž", "โœŒ", "โœจ", "๐Ÿคท",
    "๐Ÿ˜ฑ", "๐Ÿ˜Œ", "๐ŸŒธ", "๐Ÿ™Œ", "๐Ÿ˜‹", "๐Ÿ’—", "๐Ÿ’š", "๐Ÿ˜", "๐Ÿ’›", "๐Ÿ™‚",
    "๐Ÿ’“", "๐Ÿคฉ", "๐Ÿ˜„", "๐Ÿ˜€", "๐Ÿ–ค", "๐Ÿ˜ƒ", "๐Ÿ’ฏ", "๐Ÿ™ˆ", "๐Ÿ‘‡", "๐ŸŽถ",
    "๐Ÿ˜’", "๐Ÿคญ", "โฃ", "๐Ÿ˜œ", "๐Ÿ’‹", "๐Ÿ‘€", "๐Ÿ˜ช", "๐Ÿ˜‘", "๐Ÿ’ฅ", "๐Ÿ™‹",
    "๐Ÿ˜ž", "๐Ÿ˜ฉ", "๐Ÿ˜ก", "๐Ÿคช", "๐Ÿ‘Š", "๐Ÿฅณ", "๐Ÿ˜ฅ", "๐Ÿคค", "๐Ÿ‘‰", "๐Ÿ’ƒ",
    "๐Ÿ˜ณ", "โœ‹", "๐Ÿ˜š", "๐Ÿ˜", "๐Ÿ˜ด", "๐ŸŒŸ", "๐Ÿ˜ฌ", "๐Ÿ™ƒ", "๐Ÿ€", "๐ŸŒท",
    "๐Ÿ˜ป", "๐Ÿ˜“", "โญ", "โœ…", "๐Ÿฅบ", "๐ŸŒˆ", "๐Ÿ˜ˆ", "๐Ÿค˜", "๐Ÿ’ฆ", "โœ”",
    "๐Ÿ˜ฃ", "๐Ÿƒ", "๐Ÿ’", "โ˜น", "๐ŸŽŠ", "๐Ÿ’˜", "๐Ÿ˜ ", "โ˜", "๐Ÿ˜•", "๐ŸŒบ",
    "๐ŸŽ‚", "๐ŸŒป", "๐Ÿ˜", "๐Ÿ–•", "๐Ÿ’", "๐Ÿ™Š", "๐Ÿ˜น", "๐Ÿ—ฃ", "๐Ÿ’ซ", "๐Ÿ’€",
    "๐Ÿ‘‘", "๐ŸŽต", "๐Ÿคž", "๐Ÿ˜›", "๐Ÿ”ด", "๐Ÿ˜ค", "๐ŸŒผ", "๐Ÿ˜ซ", "โšฝ", "๐Ÿค™",
    "โ˜•", "๐Ÿ†", "๐Ÿคซ", "๐Ÿ‘ˆ", "๐Ÿ˜ฎ", "๐Ÿ™†", "๐Ÿป", "๐Ÿƒ", "๐Ÿถ", "๐Ÿ’",
    "๐Ÿ˜ฒ", "๐ŸŒฟ", "๐Ÿงก", "๐ŸŽ", "โšก", "๐ŸŒž", "๐ŸŽˆ", "โŒ", "โœŠ", "๐Ÿ‘‹",
    "๐Ÿ˜ฐ", "๐Ÿคจ", "๐Ÿ˜ถ", "๐Ÿค", "๐Ÿšถ", "๐Ÿ’ฐ", "๐Ÿ“", "๐Ÿ’ข", "๐ŸคŸ", "๐Ÿ™",
    "๐Ÿšจ", "๐Ÿ’จ", "๐Ÿคฌ", "โœˆ", "๐ŸŽ€", "๐Ÿบ", "๐Ÿค“", "๐Ÿ˜™", "๐Ÿ’Ÿ", "๐ŸŒฑ",
    "๐Ÿ˜–", "๐Ÿ‘ถ", "๐Ÿฅด", "โ–ถ", "โžก", "โ“", "๐Ÿ’Ž", "๐Ÿ’ธ", "โฌ‡", "๐Ÿ˜จ",
    "๐ŸŒš", "๐Ÿฆ‹", "๐Ÿ˜ท", "๐Ÿ•บ", "โš ", "๐Ÿ™…", "๐Ÿ˜Ÿ", "๐Ÿ˜ต", "๐Ÿ‘Ž", "๐Ÿคฒ",
    "๐Ÿค ", "๐Ÿคง", "๐Ÿ“Œ", "๐Ÿ”ต", "๐Ÿ’…", "๐Ÿง", "๐Ÿพ", "๐Ÿ’", "๐Ÿ˜—", "๐Ÿค‘",
    "๐ŸŒŠ", "๐Ÿคฏ", "๐Ÿท", "โ˜Ž", "๐Ÿ’ง", "๐Ÿ˜ฏ", "๐Ÿ’†", "๐Ÿ‘†", "๐ŸŽค", "๐Ÿ™‡",
    "๐Ÿ‘", "โ„", "๐ŸŒด", "๐Ÿ’ฃ", "๐Ÿธ", "๐Ÿ’Œ", "๐Ÿ“", "๐Ÿฅ€", "๐Ÿคข", "๐Ÿ‘…",
    "๐Ÿ’ก", "๐Ÿ’ฉ", "๐Ÿ‘", "๐Ÿ“ธ", "๐Ÿ‘ป", "๐Ÿค", "๐Ÿคฎ", "๐ŸŽผ", "๐Ÿฅต", "๐Ÿšฉ",
    "๐ŸŽ", "๐ŸŠ", "๐Ÿ‘ผ", "๐Ÿ’", "๐Ÿ“ฃ", "๐Ÿฅ‚"
  };

  // TODO Propose adding a Guava dependency to use ImmutableMap instead of this

  private static final Map<String, Integer> EMOJI_TO_INDEX;
  private static final int MAP_EXPECTED_SIZE = EMOJIS.length;
  private static final float MAP_LOAD_FACTOR = 1.0f;

  static {
    if (EMOJIS.length != 256) {
      throw new IllegalStateException("EMOJIS.length must be 256, but is " + EMOJIS.length);
    }

    Map<String, Integer> mutableMap = new HashMap<>(MAP_EXPECTED_SIZE, MAP_LOAD_FACTOR);
    for (int i = 0; i < EMOJIS.length; i++) {
      mutableMap.put(EMOJIS[i], i);
    }
    EMOJI_TO_INDEX = Collections.unmodifiableMap(mutableMap);
  }

  public static String encode(byte[] in) {
    StringBuilder sb = new StringBuilder(in.length);
    for (byte b : in) {
      sb.append(EMOJIS[b & 0xFF]);
    }
    return sb.toString();
  }

  public static byte[] decode(String in) {
    int length = in.codePointCount(0, in.length());
    byte[] bytes = new byte[length];

    for (int i = 0; i < in.codePointCount(0, in.length()); i++) {
      int cp = in.codePointAt(in.offsetByCodePoints(0, i));
      String emoji = new String(Character.toChars(cp));
      Integer index = EMOJI_TO_INDEX.get(emoji);
      if (index == null) {
        throw new IllegalArgumentException("Unknown Base256Emoji character: " + emoji);
      }
      bytes[i] = (byte) (index & 0xFF);
    }

    return bytes;
  }
}