();
203 | collection.add(EmojiManager.getForAlias("wink")); // This is 😉
204 |
205 | System.out.println(EmojiParser.removeAllEmojis(str));
206 | System.out.println(EmojiParser.removeAllEmojisExcept(str, collection));
207 | System.out.println(EmojiParser.removeEmojis(str, collection));
208 |
209 | // Prints:
210 | // "An awesome string with a few emojis!"
211 | // "An awesome string with a few 😉emojis!"
212 | // "An 😀awesome 😃string with a few emojis!"
213 | ```
214 |
215 | #### Extract Emojis from a string
216 |
217 | You can search a string of mixed emoji/non-emoji characters and have all of the emoji characters returned as a Collection.
218 |
219 | - `EmojiParser#extractEmojis(String)`: returns all emojis as a Collection. This will include duplicates if emojis are present more than once.
220 |
221 | ## Credits
222 |
223 | **emoji-java** originally used the data provided by the [github/gemoji project](https://github.com/github/gemoji). It is still based on it but has evolved since.
224 |
225 | The emoji lists have been taken from [kcthota/emoji4j](https://github.com/kcthota/emoji4j) and [Emzi0767/discord-emoji](https://gitlab.emzi0767.dev/Emzi0767/discord-emoji).
226 |
227 | ### License
228 |
229 | This also includes the licensing for each list:
230 |
231 | - kcthota/emoji4j which is licensed under [Apache 2.0](https://github.com/kcthota/emoji4j/blob/master/LICENSE)
232 | - Emzi0767/discord-emoji which is licensed under [AGPLv3](https://gitlab.emzi0767.dev/Emzi0767/discord-emoji/-/blob/master/COPYING)
233 | - vdurmont/emoji-java which is licensed under [MIT](https://github.com/MinnDevelopment/emoji-java/blob/master/LICENSE.md)
234 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | plugins {
3 | id('java-library')
4 | id('maven-publish')
5 | id('com.github.johnrengelman.shadow').version('7.1.0')
6 | id('de.undercouch.download').version('4.1.2')
7 | }
8 |
9 | group = 'com.github.minndevelopment'
10 | version = '6.1.0'
11 |
12 | sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
13 |
14 | tasks.create("downloadDefinitions", Download) {
15 | group = "build setup"
16 | dest file("src/main/resources/emoji-definitions.json")
17 | src "https://emzi0767.gl-pages.emzi0767.dev/discord-emoji/discordEmojiMap.json"
18 | }
19 |
20 | tasks.create("downloadEmojiList", Download) {
21 | group = "build setup"
22 | dest file("src/main/resources/emoji-list.json")
23 | src "https://raw.githubusercontent.com/kcthota/emoji4j/master/src/main/resources/emoji.json"
24 | }
25 |
26 | tasks.create("downloadResources") {
27 | dependsOn(downloadDefinitions, downloadEmojiList)
28 | }
29 |
30 | processResources {
31 | dependsOn downloadResources
32 | }
33 |
34 | tasks.withType(JavaCompile) {
35 | options.encoding = 'UTF-8'
36 | }
37 |
38 | tasks.withType(Javadoc) {
39 | failOnError = false
40 | options.encoding = "UTF-8"
41 | options.memberLevel = JavadocMemberLevel.PUBLIC
42 |
43 | options.links(
44 | "https://docs.oracle.com/en/java/javase/11/docs/api/",
45 | "https://square.github.io/okhttp/3.x/okhttp/"
46 | )
47 | if (JavaVersion.current().isJava9Compatible())
48 | options.addBooleanOption("html5", true)
49 | if (JavaVersion.current().isJava11Compatible())
50 | options.addBooleanOption("-no-module-directories", true)
51 | }
52 |
53 | repositories {
54 | mavenCentral()
55 | }
56 |
57 | dependencies {
58 | compileOnly group: 'org.jetbrains', name: 'annotations', version: '23.0.0'
59 | implementation group: 'org.json', name: 'json', version:'20211205'
60 | testImplementation group: 'junit', name: 'junit', version:'4.13'
61 | }
62 |
63 | publishing.publications {
64 | Release(MavenPublication) {
65 | from components.java
66 |
67 | version = project.version
68 | groupId = project.group
69 | artifactId = project.name
70 | }
71 | }
--------------------------------------------------------------------------------
/emoji-table-generator/README.md:
--------------------------------------------------------------------------------
1 | # emoji-table-genrator
2 |
3 | This is just a "quick'n'dirty" project to generate a markdown table with all the emojis.
4 |
5 | It is used for the table in the top level README :)
6 |
7 |
8 | Run with:
9 |
10 | ```
11 | mvn exec:java -Dexec.mainClass="com.vdurmont.emoji.TableGenerator"
12 | ```
--------------------------------------------------------------------------------
/emoji-table-generator/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.vdurmont
6 | emoji-table-generator
7 | 1.0.0-SNAPSHOT
8 | jar
9 |
10 | emoji-table-generator
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 |
19 | com.vdurmont
20 | emoji-java
21 | 5.1.1
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/emoji-table-generator/src/main/java/com/vdurmont/emoji/TableGenerator.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import java.io.FileWriter;
4 | import java.io.IOException;
5 |
6 | /**
7 | * This app generate the emoji table in the README ;)
8 | *
9 | * Run with:
10 | * mvn exec:java -Dexec.mainClass="com.vdurmont.emoji.TableGenerator"
11 | */
12 | public class TableGenerator {
13 | public static void main(String[] args) throws IOException {
14 | StringBuilder sb = new StringBuilder();
15 |
16 | // Table header
17 | sb.append("| Emoji | Aliases | Emoji | Aliases |\n");
18 | sb.append("| :---: | ------- | :---: | ------- |\n");
19 |
20 | // Emojis!
21 | int i = 0;
22 | for (Emoji emoji : EmojiManager.getAll()) {
23 | String aliases = getAliases(emoji);
24 |
25 | if (i % 2 == 0) {
26 | sb.append("| ")
27 | .append(emoji.getUnicode())
28 | .append(" | ")
29 | .append(aliases)
30 | .append(" |");
31 | } else {
32 | sb.append(" ")
33 | .append(emoji.getUnicode())
34 | .append(" | ")
35 | .append(aliases)
36 | .append(" |\n");
37 | }
38 |
39 | i++;
40 | }
41 |
42 | // Output!
43 | if (args.length > 0) {
44 | String path = args[0];
45 | FileWriter writer = new FileWriter(path);
46 | writer.write(sb.toString());
47 | System.out.println("Written on " + path);
48 | } else {
49 | System.out.println(sb.toString());
50 | }
51 | }
52 |
53 | private static String getAliases(Emoji emoji) {
54 | StringBuilder result = new StringBuilder();
55 | boolean first = true;
56 | for (String alias : emoji.getAliases()) {
57 | if (first) {
58 | first = false;
59 | } else {
60 | result.append(", ");
61 | }
62 | result.append(alias);
63 | }
64 |
65 | return result.toString();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MinnDevelopment/emoji-java/5622646ccb64f07102fa786f6539c11909fc3dff/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStorePath=wrapper/dists
5 | zipStoreBase=GRADLE_USER_HOME
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'emoji-java'
2 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/Emoji.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.nio.charset.StandardCharsets;
7 | import java.util.Arrays;
8 | import java.util.Collections;
9 | import java.util.List;
10 | import java.util.Locale;
11 |
12 | /**
13 | * This class represents an emoji.
14 | *
15 | * This object is immutable, so it can be used safely in a multi-threaded context.
16 | *
17 | * @author Vincent DURMONT [vdurmont@gmail.com]
18 | */
19 | public class Emoji {
20 | protected final String description;
21 | protected final boolean supportsFitzpatrick;
22 | protected final boolean hasVariation;
23 | protected final List aliases;
24 | protected final List tags;
25 | protected final EmojiCategory category;
26 | protected final String unicode;
27 | protected final String trimmedUnicode;
28 | protected final String htmlDec;
29 | protected final String htmlHex;
30 |
31 | /**
32 | * Constructor for the Emoji.
33 | *
34 | * @param description
35 | * The description of the emoji
36 | * @param supportsFitzpatrick
37 | * Whether the emoji supports Fitzpatrick modifiers
38 | * @param category
39 | * The emoji category
40 | * @param aliases
41 | * The aliases for this emoji
42 | * @param tags
43 | * The tags associated with this emoji
44 | * @param bytes
45 | * The bytes that represent the emoji
46 | */
47 | protected Emoji(
48 | String description,
49 | boolean supportsFitzpatrick,
50 | EmojiCategory category,
51 | List aliases,
52 | List tags,
53 | byte... bytes
54 | ) {
55 | this.description = description;
56 | this.supportsFitzpatrick = supportsFitzpatrick;
57 | this.category = category;
58 | this.aliases = Collections.unmodifiableList(aliases);
59 | this.tags = Collections.unmodifiableList(tags);
60 |
61 | int count = 0;
62 | this.unicode = new String(bytes, StandardCharsets.UTF_8);
63 | int stringLength = getUnicode().length();
64 | String[] pointCodes = new String[stringLength];
65 | String[] pointCodesHex = new String[stringLength];
66 |
67 | for (int offset = 0; offset < stringLength; ) {
68 | final int codePoint = getUnicode().codePointAt(offset);
69 |
70 | pointCodes[count] = String.format(Locale.ROOT, "%d;", codePoint);
71 | pointCodesHex[count++] = String.format(Locale.ROOT, "%x;", codePoint);
72 |
73 | offset += Character.charCount(codePoint);
74 | }
75 | this.htmlDec = String.join("", Arrays.copyOf(pointCodes, count));
76 | this.htmlHex = String.join("", Arrays.copyOf(pointCodesHex, count));
77 | this.hasVariation = unicode.contains("\uFE0F");
78 | this.trimmedUnicode = hasVariation ? unicode.replace("\uFE0F", "") : unicode;
79 | }
80 |
81 | protected Emoji setDescription(String description) {
82 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, unicode.getBytes(StandardCharsets.UTF_8));
83 | }
84 |
85 | protected Emoji setFitzpatrick(boolean supportsFitzpatrick) {
86 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, unicode.getBytes(StandardCharsets.UTF_8));
87 | }
88 |
89 | protected Emoji setCategory(EmojiCategory category) {
90 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, unicode.getBytes(StandardCharsets.UTF_8));
91 | }
92 |
93 | protected Emoji setAliases(List aliases) {
94 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, unicode.getBytes(StandardCharsets.UTF_8));
95 | }
96 |
97 | protected Emoji setTags(List tags) {
98 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, unicode.getBytes(StandardCharsets.UTF_8));
99 | }
100 |
101 | protected Emoji setBytes(byte... bytes) {
102 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, bytes);
103 | }
104 |
105 | /**
106 | * Returns the description of the emoji
107 | *
108 | * @return the description
109 | */
110 | @NotNull
111 | public String getDescription() {
112 | return this.description;
113 | }
114 |
115 | /**
116 | * Returns whether the emoji supports the Fitzpatrick modifiers or not
117 | *
118 | * @return true if the emoji supports the Fitzpatrick modifiers
119 | */
120 | public boolean supportsFitzpatrick() {
121 | return this.supportsFitzpatrick;
122 | }
123 |
124 | /**
125 | * Returns whether the emoji supports a variation selection modifier or not
126 | *
127 | * @return true, if the emoji supports variation selector
128 | */
129 | public boolean supportsVariation() {
130 | return hasVariation;
131 | }
132 |
133 | /**
134 | * Returns the aliases of the emoji
135 | *
136 | * @return Immutable list of aliases
137 | */
138 | @NotNull
139 | public List getAliases() {
140 | return this.aliases;
141 | }
142 |
143 | /**
144 | * Returns the tags of the emoji
145 | *
146 | * @return Immutable list if tags
147 | */
148 | @NotNull
149 | public List getTags() {
150 | return this.tags;
151 | }
152 |
153 | /**
154 | * Returns the unicode representation of the emoji
155 | *
156 | * @return the unicode representation
157 | */
158 | @NotNull
159 | public String getUnicode() {
160 | return this.unicode;
161 | }
162 |
163 | /**
164 | * Returns the unicode representation of the emoji without the variation selector
165 | *
166 | * @return the unicode representation without variation selector
167 | */
168 | @NotNull
169 | public String getTrimmedUnicode() {
170 | return trimmedUnicode;
171 | }
172 |
173 | /**
174 | * Returns the {@link EmojiCategory} for this emoji
175 | *
176 | * @return The {@link EmojiCategory}
177 | */
178 | @NotNull
179 | public EmojiCategory getCategory() {
180 | return category;
181 | }
182 |
183 | /**
184 | * Returns the unicode representation of the emoji associated with the provided Fitzpatrick modifier.
185 | *
If the modifier is null, then the result is similar to {@link Emoji#getUnicode()}.
186 | *
187 | * @param fitzpatrick
188 | * the fitzpatrick modifier or null
189 | *
190 | * @throws IllegalStateException
191 | * if the emoji doesn't support the Fitzpatrick modifiers
192 | *
193 | * @return the unicode representation
194 | */
195 | @NotNull
196 | public String getUnicode(@Nullable Fitzpatrick fitzpatrick) {
197 | if (!this.supportsFitzpatrick()) {
198 | throw new IllegalStateException("Cannot get the unicode with a fitzpatrick modifier, the emoji doesn't support fitzpatrick.");
199 | } else if (fitzpatrick == null) {
200 | return this.getUnicode();
201 | }
202 | return this.getUnicode() + fitzpatrick.unicode;
203 | }
204 |
205 | /**
206 | * Returns the unicode representation of the emoji associated with the provided Fitzpatrick modifier.
207 | *
If the modifier is null, then the result is similar to {@link Emoji#getTrimmedUnicode()}.
208 | *
209 | * @param fitzpatrick
210 | * the fitzpatrick modifier or null
211 | *
212 | * @throws IllegalStateException
213 | * if the emoji doesn't support the Fitzpatrick modifiers
214 | *
215 | * @return the unicode representation
216 | */
217 | @NotNull
218 | public String getTrimmedUnicode(@Nullable Fitzpatrick fitzpatrick) {
219 | if (!this.supportsFitzpatrick()) {
220 | throw new IllegalStateException("Cannot get the unicode with a fitzpatrick modifier, the emoji doesn't support fitzpatrick.");
221 | } else if (fitzpatrick == null) {
222 | return this.getTrimmedUnicode();
223 | }
224 | return this.getTrimmedUnicode() + fitzpatrick.unicode;
225 | }
226 |
227 | /**
228 | * Returns the HTML decimal representation of the emoji
229 | *
230 | * @return the HTML decimal representation
231 | */
232 | @NotNull
233 | public String getHtmlDecimal() {
234 | return this.htmlDec;
235 | }
236 |
237 | /**
238 | * Returns the HTML hexadecimal representation of the emoji
239 | *
240 | * @return the HTML hexadecimal representation
241 | */
242 | @NotNull
243 | public String getHtmlHexadecimal() {
244 | return this.htmlHex;
245 | }
246 |
247 | @Override
248 | public boolean equals(Object other) {
249 | return other instanceof Emoji &&
250 | ((Emoji) other).getUnicode().equals(getUnicode());
251 | }
252 |
253 | @Override
254 | public int hashCode() {
255 | return unicode.hashCode();
256 | }
257 |
258 | /**
259 | * Returns the String representation of the Emoji object.
260 | *
261 | * Example
262 | *
263 | * {@code Emoji {
264 | * description='smiling face with open mouth and smiling eyes',
265 | * supportsFitzpatrick=false,
266 | * aliases=[smile],
267 | * tags=[happy, joy, pleased],
268 | * category='Smileys & Emotion',
269 | * unicode='😄',
270 | * htmlDec='😄',
271 | * htmlHex='😄'
272 | * }}
273 | *
274 | * @return the string representation
275 | */
276 | @Override
277 | @NotNull
278 | public String toString() {
279 | return "Emoji{" +
280 | "description='" + description + '\'' +
281 | ", supportsFitzpatrick=" + supportsFitzpatrick +
282 | ", aliases=" + aliases +
283 | ", tags=" + tags +
284 | ", category='" + category.getDisplayName() + '\'' +
285 | ", unicode='" + unicode + '\'' +
286 | ", htmlDec='" + htmlDec + '\'' +
287 | ", htmlHex='" + htmlHex + '\'' +
288 | '}';
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/EmojiCategory.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | /**
4 | * Enum representation of the category for this emoji
5 | */
6 | public enum EmojiCategory {
7 | ACTIVITY("Activities"),
8 | FLAGS("Flags"),
9 | FOOD("Food & Drink"),
10 | NATURE("Animals & Nature"),
11 | OBJECTS("Objects"),
12 | PEOPLE("People & Body"),
13 | SYMBOLS("Symbols"),
14 | TRAVEL("Travel & Places"),
15 | SMILEYS("Smileys & Emotion"),
16 | UNKNOWN("");
17 |
18 | private final String displayName;
19 |
20 | EmojiCategory(String displayName) {
21 | this.displayName = displayName;
22 | }
23 |
24 | /**
25 | * Parses the given string to the respective category constant
26 | *
27 | * @param str
28 | * The display string
29 | *
30 | * @return The category or {@link #UNKNOWN}
31 | */
32 | public static EmojiCategory fromString(String str) {
33 | for (EmojiCategory category : values()) {
34 | if (category.displayName.equalsIgnoreCase(str))
35 | return category;
36 | }
37 |
38 | return UNKNOWN;
39 | }
40 |
41 | /**
42 | * The display name of this category
43 | *
44 | * @return The display name
45 | */
46 | public String getDisplayName() {
47 | return displayName;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/EmojiLoader.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.json.JSONArray;
5 | import org.json.JSONObject;
6 | import org.json.JSONTokener;
7 |
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.InputStreamReader;
11 | import java.io.Reader;
12 | import java.nio.charset.StandardCharsets;
13 | import java.util.*;
14 |
15 | /**
16 | * Loads the emojis from a JSON database.
17 | *
18 | * @author Vincent DURMONT [vdurmont@gmail.com]
19 | */
20 | public class EmojiLoader {
21 | /**
22 | * No need for a constructor, all the methods are static.
23 | */
24 | private EmojiLoader() {
25 | }
26 |
27 | /**
28 | * Loads a JSONArray of emojis from an InputStream,
29 | * parses it and returns the associated list of {@link com.vdurmont.emoji.Emoji Emojis}.
30 | *
31 | * @param stream
32 | * The stream of the JSONArray
33 | *
34 | * @throws NullPointerException
35 | * If the provided stream is null
36 | * @throws org.json.JSONException
37 | * If the json representation is invalid
38 | * @throws IOException
39 | * If an error occurs while reading the stream or parsing the JSONArray
40 | *
41 | * @return The list of {@link com.vdurmont.emoji.Emoji Emojis}
42 | */
43 | @NotNull
44 | public static List loadEmojis(@NotNull InputStream stream) throws IOException {
45 | Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8);
46 | JSONArray emojisJSON = new JSONArray(new JSONTokener(reader));
47 | List emojis = new ArrayList<>(emojisJSON.length());
48 | for (int i = 0; i < emojisJSON.length(); i++) {
49 | Emoji emoji = buildEmojiFromJSON(emojisJSON.getJSONObject(i));
50 | if (emoji != null) {
51 | emojis.add(emoji);
52 | }
53 | }
54 | return emojis;
55 | }
56 |
57 | /**
58 | * Loads the emoji-definitions from the resources.
59 | *
60 | * @throws IOException
61 | * If there is an I/O error when trying to read the resource file
62 | *
63 | * @return {@link Map} of emoji characters to emoji instances
64 | */
65 | @NotNull
66 | public static Map loadEmojiBundle() throws IOException {
67 | try (Reader reader = new InputStreamReader(EmojiLoader.class.getResourceAsStream("/emoji-definitions.json"), StandardCharsets.UTF_8)) {
68 | JSONObject file = new JSONObject(new JSONTokener(reader));
69 | JSONArray definitions = file.getJSONArray("emojiDefinitions");
70 | Map map = new HashMap<>(definitions.length()+1);
71 | for (int i = 0; i < definitions.length(); i++) {
72 | JSONObject json = definitions.getJSONObject(i);
73 | if (!json.has("category")) continue;
74 |
75 | String key = json.getString("surrogates");
76 | String primaryName = json.getString("primaryName");
77 | boolean supportsFitzpatrick = primaryName.contains("_tone");
78 | if (supportsFitzpatrick) {
79 | key = key.substring(0, key.length() - 2);
80 | if (map.containsKey(key)) {
81 | map.put(key, map.get(key).setFitzpatrick(true));
82 | continue;
83 | }
84 | }
85 |
86 | byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
87 | List aliases = jsonArrayToStringList(json.getJSONArray("names"));
88 | List tags = Collections.emptyList();
89 | EmojiCategory category = convertCategory(json.getString("category"));
90 | Emoji emoji = new Emoji("", supportsFitzpatrick, category, aliases, tags, bytes);
91 | map.put(key, emoji);
92 | }
93 | return map;
94 | }
95 | }
96 |
97 | private static EmojiCategory convertCategory(String raw) {
98 | for (EmojiCategory category : EmojiCategory.values()) {
99 | if (category.name().equalsIgnoreCase(raw))
100 | return category;
101 | }
102 | return EmojiCategory.UNKNOWN;
103 | }
104 |
105 | protected static Emoji buildEmojiFromJSON(JSONObject json) {
106 | if (!json.has("emoji")) {
107 | return null;
108 | }
109 |
110 | byte[] bytes = json.getString("emoji").getBytes(StandardCharsets.UTF_8);
111 | boolean supportsFitzpatrick = json.optBoolean("skin_tones", false);
112 | List aliases = jsonArrayToStringList(json.getJSONArray("aliases"));
113 | List tags = jsonArrayToStringList(json.getJSONArray("tags"));
114 | String description = json.getString("description");
115 | EmojiCategory category = EmojiCategory.fromString(json.optString("category", "UNKNOWN"));
116 | return new Emoji(description, supportsFitzpatrick, category, aliases, tags, bytes);
117 | }
118 |
119 | private static List jsonArrayToStringList(JSONArray array) {
120 | List strings = new ArrayList<>(array.length());
121 | for (int i = 0; i < array.length(); i++) {
122 | strings.add(array.getString(i));
123 | }
124 | return strings;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/EmojiManager.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.UncheckedIOException;
9 | import java.util.*;
10 |
11 | /**
12 | * Holds the loaded emojis and provides search functions.
13 | *
14 | * @author Vincent DURMONT [vdurmont@gmail.com]
15 | */
16 | public class EmojiManager {
17 | static final EmojiTrie EMOJI_TRIE;
18 | private static final String PATH = "/emoji-list.json";
19 | private static final Map EMOJIS_BY_ALIAS = new HashMap<>();
20 | private static final Map> EMOJIS_BY_TAG = new HashMap<>();
21 | private static final Map> EMOJIS_BY_CATEGORY = new HashMap<>();
22 | private static final List ALL_EMOJIS;
23 |
24 | static {
25 | try (InputStream stream = EmojiLoader.class.getResourceAsStream(PATH)) {
26 | List emojis = EmojiLoader.loadEmojis(stream);
27 | Map definitions = EmojiLoader.loadEmojiBundle();
28 |
29 | ALL_EMOJIS = emojis;
30 | for (ListIterator iter = emojis.listIterator(); iter.hasNext();) {
31 | Emoji emoji = iter.next();
32 | Emoji definition = definitions.remove(emoji.getUnicode());
33 | // Update the aliases with the additional information
34 | if (definition != null) {
35 | Set joint = new HashSet<>(emoji.aliases);
36 | joint.addAll(definition.aliases);
37 | iter.set(
38 | emoji = emoji.setAliases(new ArrayList<>(joint))
39 | .setFitzpatrick(definition.supportsFitzpatrick || emoji.supportsFitzpatrick)
40 | );
41 | }
42 |
43 | loadEmoji(emoji);
44 | }
45 |
46 | // Add all the missing emojis defined in the definitions list
47 | for (Map.Entry entry : definitions.entrySet()) {
48 | Emoji emoji = entry.getValue();
49 | loadEmoji(emoji);
50 | emojis.add(emoji);
51 | }
52 |
53 | EMOJI_TRIE = new EmojiTrie(emojis);
54 | ALL_EMOJIS.sort((e1, e2) -> e2.getUnicode().length() - e1.getUnicode().length());
55 | } catch (IOException e) {
56 | throw new UncheckedIOException(e);
57 | }
58 | }
59 |
60 | private static void loadEmoji(Emoji emoji) {
61 | Set set = EMOJIS_BY_CATEGORY.computeIfAbsent(emoji.getCategory(), k -> new HashSet<>());
62 | set.add(emoji);
63 |
64 | for (String tag : emoji.getTags()) {
65 | set = EMOJIS_BY_TAG.computeIfAbsent(tag.toLowerCase(Locale.ROOT), k -> new HashSet<>());
66 | set.add(emoji);
67 | }
68 |
69 | for (String alias : emoji.getAliases()) {
70 | EMOJIS_BY_ALIAS.put(alias.toLowerCase(Locale.ROOT), emoji);
71 | }
72 | }
73 |
74 | /**
75 | * No need for a constructor, all the methods are static.
76 | */
77 | private EmojiManager() {
78 | }
79 |
80 | /**
81 | * Returns all the {@link com.vdurmont.emoji.Emoji Emojis} for a given tag.
82 | *
83 | * @param tag
84 | * the tag
85 | *
86 | * @return {@link Set} of {@link com.vdurmont.emoji.Emoji Emojis}, empty set if the tag is unknown
87 | *
88 | * @see #getAllTags()
89 | */
90 | @NotNull
91 | public static Set getForTag(@NotNull String tag) {
92 | if (tag == null) {
93 | return Collections.emptySet();
94 | }
95 |
96 | Set emojis = EMOJIS_BY_TAG.get(tag.toLowerCase(Locale.ROOT));
97 | return emojis == null ? Collections.emptySet() : emojis;
98 | }
99 |
100 | /**
101 | * Returns all the {@link com.vdurmont.emoji.Emoji Emojis} for a given category.
102 | *
103 | * @param category
104 | * the {@link EmojiCategory}
105 | *
106 | * @return {@link Set} of {@link com.vdurmont.emoji.Emoji Emojis}, empty set if the category is unknown or null
107 | */
108 | @NotNull
109 | public static Set getForCategory(@NotNull EmojiCategory category) {
110 | if (category == null) {
111 | return Collections.emptySet();
112 | }
113 |
114 | Set emojis = EMOJIS_BY_CATEGORY.get(category);
115 | return emojis == null ? Collections.emptySet() : emojis;
116 | }
117 |
118 | /**
119 | * Returns the {@link com.vdurmont.emoji.Emoji} for a given alias.
120 | *
121 | * @param alias
122 | * the alias
123 | *
124 | * @return The associated {@link com.vdurmont.emoji.Emoji}, null if the alias is unknown
125 | */
126 | @Nullable
127 | public static Emoji getForAlias(@NotNull String alias) {
128 | if (alias == null || alias.isEmpty()) {
129 | return null;
130 | }
131 |
132 | return EMOJIS_BY_ALIAS.get(trimAlias(alias).toLowerCase(Locale.ROOT));
133 | }
134 |
135 | private static String trimAlias(String alias) {
136 | int len = alias.length();
137 | return alias.substring(
138 | alias.charAt(0) == ':' ? 1 : 0,
139 | alias.charAt(len - 1) == ':' ? len - 1 : len);
140 | }
141 |
142 |
143 | /**
144 | * Returns the {@link com.vdurmont.emoji.Emoji} for a given unicode.
145 | *
146 | * @param unicode
147 | * the unicode
148 | *
149 | * @return The associated {@link com.vdurmont.emoji.Emoji}, null if the unicode is unknown
150 | */
151 | @Nullable
152 | public static Emoji getByUnicode(@NotNull String unicode) {
153 | if (unicode == null) {
154 | return null;
155 | }
156 |
157 | return EMOJI_TRIE.getEmoji(unicode);
158 | }
159 |
160 | /**
161 | * Returns all the {@link com.vdurmont.emoji.Emoji Emojis}
162 | *
163 | * @return Immutable list of all the {@link com.vdurmont.emoji.Emoji Emojis}
164 | */
165 | @NotNull
166 | public static List getAll() {
167 | return ALL_EMOJIS;
168 | }
169 |
170 | /**
171 | * Tests if a given String is an emoji.
172 | *
173 | * @param string
174 | * the string to test
175 | *
176 | * @return true, if the string is an emoji's unicode, false otherwise
177 | */
178 | public static boolean isEmoji(@NotNull String string) {
179 | if (string == null) return false;
180 |
181 | EmojiParser.UnicodeCandidate unicodeCandidate = EmojiParser.getNextUnicodeCandidate(string.toCharArray(), 0);
182 | return unicodeCandidate != null &&
183 | unicodeCandidate.getEmojiStartIndex() == 0 &&
184 | unicodeCandidate.getFitzpatrickEndIndex() == string.length();
185 | }
186 |
187 | /**
188 | * Tests if a given String contains an emoji.
189 | *
190 | * @param string
191 | * the string to test
192 | *
193 | * @return true, if the string contains an emoji's unicode, false otherwise
194 | */
195 | public static boolean containsEmoji(@NotNull String string) {
196 | if (string == null) return false;
197 |
198 | return EmojiParser.getNextUnicodeCandidate(string.toCharArray(), 0) != null;
199 | }
200 |
201 | /**
202 | * Tests if a given String only contains emojis.
203 | *
204 | * @param string
205 | * the string to test
206 | *
207 | * @return true, if the string only contains emojis, false otherwise
208 | */
209 | public static boolean isOnlyEmojis(@NotNull String string) {
210 | return string != null && EmojiParser.removeAllEmojis(string).isEmpty();
211 | }
212 |
213 | /**
214 | * Checks if sequence of chars contain an emoji.
215 | *
216 | * @param sequence
217 | * Sequence of char that may contain emoji in full or partially.
218 | *
219 | * @return
220 | *
221 | * - {@link EmojiTrie.Matches#EXACTLY} if char sequence in its entirety is an emoji
222 | * - {@link EmojiTrie.Matches#POSSIBLY} if char sequence matches prefix of an emoji
223 | * - {@link EmojiTrie.Matches#IMPOSSIBLE} if char sequence matches no emoji or prefix of an emoji
224 | *
225 | */
226 | @NotNull
227 | public static EmojiTrie.Matches isEmoji(@NotNull char[] sequence) {
228 | return EMOJI_TRIE.isEmoji(sequence);
229 | }
230 |
231 | /**
232 | * Returns all the tags in the database
233 | *
234 | * @return Immutable {@link Set} of known tags
235 | */
236 | @NotNull
237 | public static Set getAllTags() {
238 | return Collections.unmodifiableSet(EMOJIS_BY_TAG.keySet());
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/EmojiParser.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Collection;
8 | import java.util.List;
9 |
10 | /**
11 | * Provides methods to parse strings with emojis.
12 | *
13 | * @author Vincent DURMONT [vdurmont@gmail.com]
14 | */
15 | public class EmojiParser {
16 |
17 | /**
18 | * See {@link #parseToAliases(String, FitzpatrickAction)} with the action {@link FitzpatrickAction#PARSE}.
19 | *
20 | * @param input
21 | * the string to parse
22 | *
23 | * @return the string with the emojis replaced by their alias.
24 | */
25 | @NotNull
26 | public static String parseToAliases(@NotNull String input) {
27 | return parseToAliases(input, FitzpatrickAction.PARSE);
28 | }
29 |
30 | /**
31 | * Replaces the emoji's unicode occurrences by one of their alias
32 | * (between 2 ':').
33 | *
Example: {@code 😄} will be replaced by {@code :smile:}
34 | *
35 | * When a fitzpatrick modifier is present with a PARSE action, a "|" will be
36 | * appended to the alias, with the fitzpatrick type.
37 | *
Example: {@code 👦🏿} will be replaced by
38 | * {@code :boy|type_6:}
39 | *
The fitzpatrick types are: type_1_2, type_3, type_4, type_5, type_6
40 | *
41 | *
When a fitzpatrick modifier is present with a REMOVE action, the modifier
42 | * will be deleted.
43 | *
Example: {@code 👦🏿} will be replaced by {@code :boy:}
44 | *
45 | *
When a fitzpatrick modifier is present with a IGNORE action, the modifier
46 | * will be ignored.
47 | *
Example: {@code 👦🏿} will be replaced by {@code :boy:🏿}
48 | *
49 | * @param input
50 | * The string to parse
51 | * @param fitzpatrickAction
52 | * The action to apply for the fitzpatrick modifiers
53 | *
54 | * @return the string with the emojis replaced by their alias.
55 | */
56 | @NotNull
57 | public static String parseToAliases(
58 | @NotNull String input,
59 | @NotNull FitzpatrickAction fitzpatrickAction
60 | ) {
61 | EmojiTransformer emojiTransformer = unicodeCandidate -> {
62 | switch (fitzpatrickAction) {
63 | default:
64 | case PARSE:
65 | if (unicodeCandidate.hasFitzpatrick()) {
66 | return ":" +
67 | unicodeCandidate.getEmoji().getAliases().get(0) +
68 | "|" +
69 | unicodeCandidate.getFitzpatrickType() +
70 | ":";
71 | }
72 | case REMOVE:
73 | return ":" +
74 | unicodeCandidate.getEmoji().getAliases().get(0) +
75 | ":";
76 | case IGNORE:
77 | return ":" +
78 | unicodeCandidate.getEmoji().getAliases().get(0) +
79 | ":" +
80 | unicodeCandidate.getFitzpatrickUnicode();
81 | }
82 | };
83 |
84 | return parseFromUnicode(input, emojiTransformer);
85 | }
86 |
87 | /**
88 | * Replace all emojis with character
89 | *
90 | * @param str
91 | * The string to process
92 | * @param replacementString
93 | * Replacement the string that will replace all the emojis
94 | *
95 | * @return the string with replaced character
96 | */
97 | @NotNull
98 | public static String replaceAllEmojis(@NotNull String str, @NotNull String replacementString) {
99 | EmojiParser.EmojiTransformer emojiTransformer = unicodeCandidate -> replacementString;
100 |
101 | return parseFromUnicode(str, emojiTransformer);
102 | }
103 |
104 | /**
105 | * Replaces the emoji's aliases (between 2 ':') occurrences and the html
106 | * representations by their unicode.
107 | *
108 | *
Examples
109 | * {@code :smile:} will be replaced by {@code 😄}
110 | * {@code 😄} will be replaced by {@code 😄}
111 | * {@code :boy|type_6:} will be replaced by {@code 👦🏿}
112 | *
113 | * @param input
114 | * the string to parse
115 | *
116 | * @return the string with the aliases and html representations replaced by their unicode.
117 | */
118 | @NotNull
119 | public static String parseToUnicode(@NotNull String input) {
120 | StringBuilder sb = new StringBuilder(input.length());
121 |
122 | for (int last = 0; last < input.length(); last++) {
123 | AliasCandidate alias = getAliasAt(input, last);
124 | if (alias == null) {
125 | alias = getHtmlEncodedEmojiAt(input, last);
126 | }
127 |
128 | if (alias != null) {
129 | sb.append(alias.emoji.getUnicode());
130 | last = alias.endIndex;
131 |
132 | if (alias.fitzpatrick != null) {
133 | sb.append(alias.fitzpatrick.unicode);
134 | }
135 | } else {
136 | sb.append(input.charAt(last));
137 | }
138 | }
139 |
140 | return sb.toString();
141 | }
142 |
143 | /**
144 | * Finds the alias in the given string starting at the given point, null otherwise
145 | */
146 | @Nullable
147 | protected static AliasCandidate getAliasAt(String input, int start) {
148 | if (input.length() < start + 2 || input.charAt(start) != ':') return null; // Aliases start with :
149 | int aliasEnd = input.indexOf(':', start + 2); // Alias must be at least 1 char in length
150 | if (aliasEnd == -1) return null; // No alias end found
151 |
152 | int fitzpatrickStart = input.indexOf('|', start + 2);
153 | if (fitzpatrickStart != -1 && fitzpatrickStart < aliasEnd) {
154 | Emoji emoji = EmojiManager.getForAlias(input.substring(start, fitzpatrickStart));
155 | if (emoji == null) return null; // Not a valid alias
156 | if (!emoji.supportsFitzpatrick())
157 | return null; // Fitzpatrick was specified, but the emoji does not support it
158 | Fitzpatrick fitzpatrick = Fitzpatrick.fitzpatrickFromType(input.substring(fitzpatrickStart + 1, aliasEnd));
159 | return new AliasCandidate(emoji, fitzpatrick, start, aliasEnd);
160 | }
161 |
162 | Emoji emoji = EmojiManager.getForAlias(input.substring(start, aliasEnd));
163 | if (emoji == null) return null; // Not a valid alias
164 | return new AliasCandidate(emoji, null, start, aliasEnd);
165 | }
166 |
167 | /**
168 | * Finds the HTML encoded emoji in the given string starting at the given point, null otherwise
169 | */
170 | @Nullable
171 | protected static AliasCandidate getHtmlEncodedEmojiAt(String input, int start) {
172 | if (input.length() < start + 4 || input.charAt(start) != '&' || input.charAt(start + 1) != '#') return null;
173 |
174 | Emoji longestEmoji = null;
175 | int longestCodePointEnd = -1;
176 | char[] chars = new char[EmojiManager.EMOJI_TRIE.maxDepth];
177 | int charsIndex = 0;
178 | int codePointStart = start;
179 | do {
180 | int codePointEnd = input.indexOf(';', codePointStart + 3); // Code point must be at least 1 char in length
181 | if (codePointEnd == -1) break;
182 |
183 | try {
184 | int radix = input.charAt(codePointStart + 2) == 'x' ? 16 : 10;
185 | int codePoint = Integer.parseInt(input.substring(codePointStart + 2 + radix / 16, codePointEnd), radix);
186 | charsIndex += Character.toChars(codePoint, chars, charsIndex);
187 | } catch (IllegalArgumentException e) {
188 | break;
189 | }
190 | Emoji foundEmoji = EmojiManager.EMOJI_TRIE.getEmoji(chars, 0, charsIndex);
191 | if (foundEmoji != null) {
192 | longestEmoji = foundEmoji;
193 | longestCodePointEnd = codePointEnd;
194 | }
195 | codePointStart = codePointEnd + 1;
196 | } while (input.length() > codePointStart + 4 &&
197 | input.charAt(codePointStart) == '&' &&
198 | input.charAt(codePointStart + 1) == '#' &&
199 | charsIndex < chars.length &&
200 | !EmojiManager.EMOJI_TRIE.isEmoji(chars, 0, charsIndex).impossibleMatch());
201 |
202 | if (longestEmoji == null) return null;
203 | return new AliasCandidate(longestEmoji, null, start, longestCodePointEnd);
204 | }
205 |
206 | /**
207 | * See {@link #parseToHtmlDecimal(String, FitzpatrickAction)} with the action {@link FitzpatrickAction#PARSE}
208 | *
209 | * @param input
210 | * the string to parse
211 | *
212 | * @return the string with the emojis replaced by their html decimal representation.
213 | */
214 | @NotNull
215 | public static String parseToHtmlDecimal(@NotNull String input) {
216 | return parseToHtmlDecimal(input, FitzpatrickAction.PARSE);
217 | }
218 |
219 | /**
220 | * Replaces the emoji's unicode occurrences by their html representation.
221 | *
Example: {@code 😄} will be replaced by {@code 😄}
222 | *
223 | * When a fitzpatrick modifier is present with a PARSE or REMOVE action, the
224 | * modifier will be deleted from the string.
225 | *
Example: {@code 👦🏿} will be replaced by {@code 👦}
226 | *
227 | *
When a fitzpatrick modifier is present with a IGNORE action, the modifier
228 | * will be ignored and will remain in the string.
229 | *
Example: {@code 👦🏿} will be replaced by {@code 👦🏿}
230 | *
231 | * @param input
232 | * the string to parse
233 | * @param fitzpatrickAction
234 | * the action to apply for the fitzpatrick modifiers
235 | *
236 | * @return the string with the emojis replaced by their html decimal representation.
237 | */
238 | @NotNull
239 | public static String parseToHtmlDecimal(
240 | String input,
241 | final FitzpatrickAction fitzpatrickAction
242 | ) {
243 | EmojiTransformer emojiTransformer = unicodeCandidate -> {
244 | switch (fitzpatrickAction) {
245 | default:
246 | case PARSE:
247 | case REMOVE:
248 | return unicodeCandidate.getEmoji().getHtmlDecimal();
249 | case IGNORE:
250 | return unicodeCandidate.getEmoji().getHtmlDecimal() +
251 | unicodeCandidate.getFitzpatrickUnicode();
252 | }
253 | };
254 |
255 | return parseFromUnicode(input, emojiTransformer);
256 | }
257 |
258 | /**
259 | * See {@link #parseToHtmlHexadecimal(String, FitzpatrickAction)} with the action {@link FitzpatrickAction#PARSE}
260 | *
261 | * @param input
262 | * the string to parse
263 | *
264 | * @return the string with the emojis replaced by their html hex representation.
265 | */
266 | @NotNull
267 | public static String parseToHtmlHexadecimal(@NotNull String input) {
268 | return parseToHtmlHexadecimal(input, FitzpatrickAction.PARSE);
269 | }
270 |
271 | /**
272 | * Replaces the emoji's unicode occurrences by their html hex
273 | * representation.
274 | *
Example: {@code 👦} will be replaced by {@code 👦}
275 | *
276 | *
When a fitzpatrick modifier is present with a PARSE or REMOVE action, the
277 | * modifier will be deleted.
278 | *
Example: {@code 👦🏿} will be replaced by {@code 👦}
279 | *
280 | *
When a fitzpatrick modifier is present with a IGNORE action, the modifier
281 | * will be ignored and will remain in the string.
282 | *
Example: {@code 👦🏿} will be replaced by {@code 👦🏿}
283 | *
284 | * @param input
285 | * the string to parse
286 | * @param fitzpatrickAction
287 | * the action to apply for the fitzpatrick modifiers
288 | *
289 | * @return the string with the emojis replaced by their html hex representation.
290 | */
291 | @NotNull
292 | public static String parseToHtmlHexadecimal(
293 | @NotNull String input,
294 | @NotNull FitzpatrickAction fitzpatrickAction
295 | ) {
296 | EmojiTransformer emojiTransformer = unicodeCandidate -> {
297 | switch (fitzpatrickAction) {
298 | default:
299 | case PARSE:
300 | case REMOVE:
301 | return unicodeCandidate.getEmoji().getHtmlHexadecimal();
302 | case IGNORE:
303 | return unicodeCandidate.getEmoji().getHtmlHexadecimal() +
304 | unicodeCandidate.getFitzpatrickUnicode();
305 | }
306 | };
307 |
308 | return parseFromUnicode(input, emojiTransformer);
309 | }
310 |
311 | /**
312 | * Removes all emojis from a String
313 | *
314 | * @param str
315 | * the string to process
316 | *
317 | * @return the string without any emoji
318 | */
319 | @NotNull
320 | public static String removeAllEmojis(@NotNull String str) {
321 | EmojiTransformer emojiTransformer = unicodeCandidate -> "";
322 |
323 | return parseFromUnicode(str, emojiTransformer);
324 | }
325 |
326 |
327 | /**
328 | * Removes a set of emojis from a String
329 | *
330 | * @param str
331 | * the string to process
332 | * @param emojisToRemove
333 | * the emojis to remove from this string
334 | *
335 | * @return the string without the emojis that were removed
336 | */
337 | @NotNull
338 | public static String removeEmojis(
339 | @NotNull String str,
340 | @NotNull Collection extends Emoji> emojisToRemove
341 | ) {
342 | EmojiTransformer emojiTransformer = unicodeCandidate -> {
343 | if (!emojisToRemove.contains(unicodeCandidate.getEmoji())) {
344 | return unicodeCandidate.getUnicode() +
345 | unicodeCandidate.getFitzpatrickUnicode();
346 | }
347 | return "";
348 | };
349 |
350 | return parseFromUnicode(str, emojiTransformer);
351 | }
352 |
353 | /**
354 | * Removes all the emojis in a String except a provided set
355 | *
356 | * @param str
357 | * the string to process
358 | * @param emojisToKeep
359 | * the emojis to keep in this string
360 | *
361 | * @return the string without the emojis that were removed
362 | */
363 | @NotNull
364 | public static String removeAllEmojisExcept(
365 | @NotNull String str,
366 | @NotNull Collection extends Emoji> emojisToKeep
367 | ) {
368 | EmojiTransformer emojiTransformer = unicodeCandidate -> {
369 | if (emojisToKeep.contains(unicodeCandidate.getEmoji())) {
370 | return unicodeCandidate.getUnicode() +
371 | unicodeCandidate.getFitzpatrickUnicode();
372 | }
373 | return "";
374 | };
375 |
376 | return parseFromUnicode(str, emojiTransformer);
377 | }
378 |
379 |
380 | /**
381 | * Detects all unicode emojis in input string and replaces them with the return value of transformer.transform()
382 | *
383 | * @param input
384 | * the string to process
385 | * @param transformer
386 | * emoji transformer to apply to each emoji
387 | *
388 | * @return input string with all emojis transformed
389 | */
390 | @NotNull
391 | public static String parseFromUnicode(
392 | @NotNull String input,
393 | @NotNull EmojiTransformer transformer
394 | ) {
395 | int prev = 0;
396 | StringBuilder sb = new StringBuilder(input.length());
397 | List replacements = getUnicodeCandidates(input);
398 | for (UnicodeCandidate candidate : replacements) {
399 | sb.append(input, prev, candidate.getEmojiStartIndex());
400 |
401 | sb.append(transformer.transform(candidate));
402 | prev = candidate.getFitzpatrickEndIndex();
403 | }
404 |
405 | return sb.append(input.substring(prev)).toString();
406 | }
407 |
408 | /**
409 | * Parses all emoji by unicode in the given string.
410 | *
411 | * @param input
412 | * Input string to parse
413 | *
414 | * @return {@link List} of {@link String} unicode emoji
415 | */
416 | @NotNull
417 | public static List extractEmojis(@NotNull String input) {
418 | List emojis = getUnicodeCandidates(input);
419 | List result = new ArrayList<>();
420 | for (UnicodeCandidate emoji : emojis) {
421 | if (emoji.getEmoji().supportsFitzpatrick()) {
422 | result.add(emoji.getUnicode() + emoji.getFitzpatrickUnicode());
423 | } else {
424 | result.add(emoji.getUnicode());
425 | }
426 | }
427 | return result;
428 | }
429 |
430 |
431 | /**
432 | * Generates a list UnicodeCandidates found in input string.
433 | * A UnicodeCandidate is created for every unicode emoticon found in input string,
434 | * additionally if Fitzpatrick modifier follows the emoji,
435 | * it is included in UnicodeCandidate.
436 | * Finally, it contains start and end index of unicode emoji itself
437 | * (WITHOUT Fitzpatrick modifier whether it is there or not!).
438 | *
439 | * @param input
440 | * String to find all unicode emojis in
441 | *
442 | * @return List of UnicodeCandidates for each unicode emote in text
443 | */
444 | @NotNull
445 | protected static List getUnicodeCandidates(@NotNull String input) {
446 | char[] inputCharArray = input.toCharArray();
447 | List candidates = new ArrayList<>();
448 | UnicodeCandidate next;
449 | for (int i = 0; (next = getNextUnicodeCandidate(inputCharArray, i)) != null; i = next.getFitzpatrickEndIndex()) {
450 | candidates.add(next);
451 | }
452 |
453 | return candidates;
454 | }
455 |
456 | /**
457 | * Finds the next UnicodeCandidate after a given starting index
458 | *
459 | * @param chars
460 | * char array to find UnicodeCandidate in
461 | * @param start
462 | * starting index for search
463 | *
464 | * @return the next UnicodeCandidate or null if no UnicodeCandidate is found after start index
465 | */
466 | @Nullable
467 | protected static UnicodeCandidate getNextUnicodeCandidate(char[] chars, int start) {
468 | for (int i = start; i < chars.length; i++) {
469 | int emojiEnd = getEmojiEndPos(chars, i);
470 |
471 | if (emojiEnd != -1) {
472 | String unicode = new String(chars, i, emojiEnd - i);
473 | Emoji emoji = EmojiManager.getByUnicode(unicode);
474 | String fitzpatrickString = (emojiEnd + 2 <= chars.length) ?
475 | new String(chars, emojiEnd, 2) :
476 | null;
477 | return new UnicodeCandidate(
478 | emoji,
479 | fitzpatrickString,
480 | unicode,
481 | i
482 | );
483 | }
484 | }
485 |
486 | return null;
487 | }
488 |
489 |
490 | /**
491 | * Returns end index of a unicode emoji if it is found in text starting at
492 | * index startPos, -1 if not found.
493 | *
494 | * This returns the longest matching emoji, for example, in
495 | * {@code "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66"}
496 | * it will find {@code alias:family_man_woman_boy}, NOT {@code alias:man}
497 | *
498 | * @param text
499 | * the current text where we are looking for an emoji
500 | * @param startPos
501 | * the position in the text where we should start looking for an emoji end
502 | *
503 | * @return the end index of the unicode emoji starting at startPos. -1 if not found
504 | */
505 | protected static int getEmojiEndPos(char[] text, int startPos) {
506 | int best = -1;
507 | for (int j = startPos + 1; j <= text.length; j++) {
508 | EmojiTrie.Matches status = EmojiManager.EMOJI_TRIE.isEmoji(text, startPos, j);
509 |
510 | if (status.exactMatch()) {
511 | best = j;
512 | } else if (status.impossibleMatch()) {
513 | return best;
514 | }
515 | }
516 |
517 | return best;
518 | }
519 |
520 |
521 | /**
522 | * Enum used to indicate what should be done when a Fitzpatrick modifier is found.
523 | */
524 | public enum FitzpatrickAction {
525 | /**
526 | * Tries to match the Fitzpatrick modifier with the previous emoji
527 | */
528 | PARSE,
529 |
530 | /**
531 | * Removes the Fitzpatrick modifier from the string
532 | */
533 | REMOVE,
534 |
535 | /**
536 | * Ignores the Fitzpatrick modifier (it will stay in the string)
537 | */
538 | IGNORE
539 | }
540 |
541 |
542 | @FunctionalInterface
543 | public interface EmojiTransformer {
544 | String transform(UnicodeCandidate unicodeCandidate);
545 | }
546 |
547 | public static class UnicodeCandidate {
548 | private final Emoji emoji;
549 | private final Fitzpatrick fitzpatrick;
550 | private final int startIndex;
551 | private final boolean hasVariation;
552 | private final String unicode;
553 |
554 | private UnicodeCandidate(Emoji emoji, String fitzpatrick, String unicode, int startIndex) {
555 | this.emoji = emoji;
556 | this.fitzpatrick = Fitzpatrick.fitzpatrickFromUnicode(fitzpatrick);
557 | this.hasVariation = unicode.contains("\uFE0F");
558 | this.startIndex = startIndex;
559 | this.unicode = unicode;
560 | }
561 |
562 | public String getUnicode() {
563 | return unicode;
564 | }
565 |
566 | public Emoji getEmoji() {
567 | return emoji;
568 | }
569 |
570 | public boolean hasFitzpatrick() {
571 | return getFitzpatrick() != null;
572 | }
573 |
574 | public boolean hasVariation() {
575 | return hasVariation;
576 | }
577 |
578 | public Fitzpatrick getFitzpatrick() {
579 | return fitzpatrick;
580 | }
581 |
582 | public String getFitzpatrickType() {
583 | return hasFitzpatrick() ? fitzpatrick.name().toLowerCase() : "";
584 | }
585 |
586 | public String getFitzpatrickUnicode() {
587 | return hasFitzpatrick() ? fitzpatrick.unicode : "";
588 | }
589 |
590 | public int getEmojiStartIndex() {
591 | return startIndex;
592 | }
593 |
594 | public int getEmojiEndIndex() {
595 | return startIndex + emoji.getUnicode().length();
596 | }
597 |
598 | public int getFitzpatrickEndIndex() {
599 | return getEmojiEndIndex() + (fitzpatrick != null ? 2 : 0);
600 | }
601 | }
602 |
603 | protected static class AliasCandidate {
604 | public final Emoji emoji;
605 | public final Fitzpatrick fitzpatrick;
606 | public final int startIndex;
607 | public final int endIndex;
608 |
609 | private AliasCandidate(Emoji emoji, Fitzpatrick fitzpatrick, int startIndex, int endIndex) {
610 | this.emoji = emoji;
611 | this.fitzpatrick = fitzpatrick;
612 | this.startIndex = startIndex;
613 | this.endIndex = endIndex;
614 | }
615 | }
616 | }
617 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/EmojiTrie.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.util.Collection;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | public class EmojiTrie {
11 | final int maxDepth;
12 | private final Node root = new Node();
13 |
14 | public EmojiTrie(Collection emojis) {
15 | int maxDepth = 0;
16 | for (Emoji emoji : emojis) {
17 | Node tree = root;
18 | char[] chars = emoji.getUnicode().toCharArray();
19 | maxDepth = addEmoji(maxDepth, emoji, tree, chars);
20 | // Add emoji without variation selector as well
21 | if (emoji.supportsVariation()) {
22 | chars = emoji.getTrimmedUnicode().toCharArray();
23 | maxDepth = addEmoji(maxDepth, emoji, tree, chars);
24 | }
25 | }
26 | this.maxDepth = maxDepth;
27 | }
28 |
29 | private int addEmoji(int maxDepth, Emoji emoji, Node tree, char[] chars) {
30 | maxDepth = Math.max(maxDepth, chars.length);
31 | for (char c : chars) {
32 | if (!tree.hasChild(c)) {
33 | tree.addChild(c);
34 | }
35 | tree = tree.getChild(c);
36 | }
37 | tree.setEmoji(emoji);
38 | return maxDepth;
39 | }
40 |
41 |
42 | /**
43 | * Checks if sequence of chars contain an emoji.
44 | *
45 | * @param sequence
46 | * Sequence of char that may contain emoji in full or partially.
47 | *
48 | * @return
49 | *
50 | * - {@link Matches#EXACTLY} if char sequence in its entirety is an emoji
51 | * - {@link Matches#POSSIBLY} if char sequence matches prefix of an emoji
52 | * - {@link Matches#IMPOSSIBLE} if char sequence matches no emoji or prefix of an emoji
53 | *
54 | */
55 | @NotNull
56 | public Matches isEmoji(char[] sequence) {
57 | return isEmoji(sequence, 0, sequence.length);
58 | }
59 |
60 | /**
61 | * Checks if the sequence of chars within the given bound indices contain an emoji.
62 | *
63 | * @param sequence
64 | * Sequence of char that may contain emoji in full or partially.
65 | * @param start
66 | * The starting index
67 | * @param end
68 | * The end index (exclusive)
69 | *
70 | * @throws ArrayIndexOutOfBoundsException
71 | * If the provided range is invalid
72 | *
73 | * @return
74 | *
75 | * - {@link Matches#EXACTLY} if char sequence in its entirety is an emoji
76 | * - {@link Matches#POSSIBLY} if char sequence matches prefix of an emoji
77 | * - {@link Matches#IMPOSSIBLE} if char sequence matches no emoji or prefix of an emoji
78 | *
79 | *
80 | * @see #isEmoji(char[])
81 | */
82 | @NotNull
83 | public Matches isEmoji(char[] sequence, int start, int end) {
84 | if (start < 0 || start > end || end > sequence.length) {
85 | throw new ArrayIndexOutOfBoundsException(
86 | "start " + start + ", end " + end + ", length " + sequence.length);
87 | }
88 |
89 | if (sequence == null) {
90 | return Matches.POSSIBLY;
91 | }
92 |
93 | Node tree = root;
94 | for (int i = start; i < end; i++) {
95 | if (!tree.hasChild(sequence[i])) {
96 | return Matches.IMPOSSIBLE;
97 | }
98 | tree = tree.getChild(sequence[i]);
99 | }
100 |
101 | return tree.isEndOfEmoji() ? Matches.EXACTLY : Matches.POSSIBLY;
102 | }
103 |
104 | /**
105 | * Finds Emoji instance from emoji unicode
106 | *
107 | * @param unicode
108 | * unicode of emoji to get
109 | *
110 | * @return Emoji instance if unicode matches and emoji, null otherwise.
111 | */
112 | @Nullable
113 | public Emoji getEmoji(@NotNull String unicode) {
114 | return getEmoji(unicode.toCharArray(), 0, unicode.length());
115 | }
116 |
117 | @NotNull
118 | protected Emoji getEmoji(@NotNull char[] sequence, int start, int end) {
119 | if (start < 0 || start > end || end > sequence.length) {
120 | throw new ArrayIndexOutOfBoundsException(
121 | "start " + start + ", end " + end + ", length " + sequence.length);
122 | }
123 |
124 | Node tree = root;
125 | for (int i = 0; i < end; i++) {
126 | if (!tree.hasChild(sequence[i])) {
127 | return null;
128 | }
129 | tree = tree.getChild(sequence[i]);
130 | }
131 | return tree.getEmoji();
132 | }
133 |
134 | public enum Matches {
135 | EXACTLY, POSSIBLY, IMPOSSIBLE;
136 |
137 | public boolean exactMatch() {
138 | return this == EXACTLY;
139 | }
140 |
141 | public boolean impossibleMatch() {
142 | return this == IMPOSSIBLE;
143 | }
144 | }
145 |
146 | private class Node {
147 | private Map children = new HashMap<>();
148 | private Emoji emoji;
149 |
150 | private Emoji getEmoji() {
151 | return emoji;
152 | }
153 |
154 | private void setEmoji(Emoji emoji) {
155 | this.emoji = emoji;
156 | }
157 |
158 | private boolean hasChild(char child) {
159 | return children.containsKey(child);
160 | }
161 |
162 | private void addChild(char child) {
163 | children.put(child, new Node());
164 | }
165 |
166 | private Node getChild(char child) {
167 | return children.get(child);
168 | }
169 |
170 | private boolean isEndOfEmoji() {
171 | return emoji != null;
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/com/vdurmont/emoji/Fitzpatrick.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | /**
4 | * Enum that represents the Fitzpatrick modifiers supported by the emojis.
5 | */
6 | public enum Fitzpatrick {
7 | /**
8 | * Fitzpatrick modifier of type 1/2 (pale white/white)
9 | */
10 | TYPE_1_2("\uD83C\uDFFB"),
11 |
12 | /**
13 | * Fitzpatrick modifier of type 3 (cream white)
14 | */
15 | TYPE_3("\uD83C\uDFFC"),
16 |
17 | /**
18 | * Fitzpatrick modifier of type 4 (moderate brown)
19 | */
20 | TYPE_4("\uD83C\uDFFD"),
21 |
22 | /**
23 | * Fitzpatrick modifier of type 5 (dark brown)
24 | */
25 | TYPE_5("\uD83C\uDFFE"),
26 |
27 | /**
28 | * Fitzpatrick modifier of type 6 (black)
29 | */
30 | TYPE_6("\uD83C\uDFFF");
31 |
32 | /**
33 | * The unicode representation of the Fitzpatrick modifier
34 | */
35 | public final String unicode;
36 |
37 | Fitzpatrick(String unicode) {
38 | this.unicode = unicode;
39 | }
40 |
41 |
42 | public static Fitzpatrick fitzpatrickFromUnicode(String unicode) {
43 | for (Fitzpatrick v : values()) {
44 | if (v.unicode.equals(unicode)) {
45 | return v;
46 | }
47 | }
48 | return null;
49 | }
50 |
51 | public static Fitzpatrick fitzpatrickFromType(String type) {
52 | try {
53 | return Fitzpatrick.valueOf(type.toUpperCase());
54 | } catch (IllegalArgumentException e) {
55 | return null;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/com/vdurmont/emoji/EmojiJsonTest.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.junit.Ignore;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.junit.runners.Parameterized;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 | import java.util.*;
13 |
14 | import static org.junit.Assert.assertEquals;
15 | import static org.junit.Assert.assertTrue;
16 |
17 | /**
18 | * Test that checks emoji json.
19 | *
20 | * Currently contains checks for:
21 | *
22 | * - Unicode emoji presents in json
23 | * - Right fitzpatric flag for emoji
24 | *
25 | *
26 | *
27 | * The test data is taken from: Unicode test data
28 | * related to unicode 9.0
29 | */
30 | @RunWith(Parameterized.class)
31 | public class EmojiJsonTest {
32 | @Parameterized.Parameters
33 | public static Collection emojis() throws IOException {
34 | final InputStream is = EmojiJsonTest.class.getClassLoader().getResourceAsStream("emoji-test.txt");
35 | return EmojiTestDataReader.getEmojiList(is);
36 | }
37 |
38 | private static final int[] FITZPATRIC_CODEPOINTS = new int[]{
39 | EmojiTestDataReader.convertFromCodepoint("1F3FB"),
40 | EmojiTestDataReader.convertFromCodepoint("1F3FC"),
41 | EmojiTestDataReader.convertFromCodepoint("1F3FD"),
42 | EmojiTestDataReader.convertFromCodepoint("1F3FE"),
43 | EmojiTestDataReader.convertFromCodepoint("1F3FF")
44 | };
45 |
46 | @Parameterized.Parameter
47 | public String emoji;
48 |
49 | @Ignore("1665 emoji still has not been added")
50 | @Test
51 | public void checkEmojiExisting() {
52 | assertTrue("Asserting for emoji: " + emoji, EmojiManager.isEmoji(emoji));
53 | }
54 |
55 | @Test
56 | public void checkEmojiFitzpatricFlag() {
57 | final int len = emoji.toCharArray().length;
58 | boolean shouldContainFitzpatric = false;
59 | int codepoint;
60 | for (int i = 0; i < len; i++) {
61 | codepoint = emoji.codePointAt(i);
62 | shouldContainFitzpatric = Arrays.binarySearch(FITZPATRIC_CODEPOINTS, codepoint) >= 0;
63 | if (shouldContainFitzpatric) {
64 | break;
65 | }
66 | }
67 |
68 | if (shouldContainFitzpatric) {
69 | EmojiParser.parseFromUnicode(emoji, new EmojiParser.EmojiTransformer() {
70 | public String transform(EmojiParser.UnicodeCandidate unicodeCandidate) {
71 | if (unicodeCandidate.hasFitzpatrick()) {
72 | assertTrue("Asserting emoji contains fitzpatric: " + emoji + " " + unicodeCandidate.getEmoji(),
73 | unicodeCandidate.getEmoji().supportsFitzpatrick());
74 | }
75 | return "";
76 | }
77 | });
78 | }
79 | }
80 |
81 | private static class EmojiTestDataReader {
82 | static List getEmojiList(final InputStream emojiFileStream) throws IOException {
83 | final BufferedReader reader = new BufferedReader(new InputStreamReader(emojiFileStream));
84 | final List result = new LinkedList();
85 |
86 | String line = reader.readLine();
87 | String [] lineSplit;
88 | while (line != null) {
89 | if (!line.startsWith("#") && !line.startsWith(" ") && !line.startsWith("\n") &&
90 | line.length() != 0) {
91 | lineSplit = line.split(";");
92 | result.add(convertToEmoji(lineSplit[0].trim()));
93 | }
94 | line = reader.readLine();
95 | }
96 | return result;
97 | }
98 |
99 | private static String convertToEmoji(final String input) {
100 | String[] emojiCodepoints = input.split(" ");
101 | StringBuilder sb = new StringBuilder();
102 | for (String emojiCodepoint : emojiCodepoints) {
103 | int codePoint = convertFromCodepoint(emojiCodepoint);
104 | sb.append(Character.toChars(codePoint));
105 | }
106 | return sb.toString();
107 | }
108 |
109 | static int convertFromCodepoint(String emojiCodepointAsString) {
110 | return Integer.parseInt(emojiCodepointAsString, 16);
111 | }
112 |
113 | }
114 |
115 | @Test
116 | public void checkInverseParse() {
117 | assertEquals(emoji, EmojiParser.parseToUnicode(EmojiParser.parseToHtmlDecimal(emoji, EmojiParser.FitzpatrickAction.IGNORE)));
118 |
119 | assertEquals(emoji, EmojiParser.parseToUnicode(EmojiParser.parseToHtmlHexadecimal(emoji, EmojiParser.FitzpatrickAction.IGNORE)));
120 |
121 | assertEquals(emoji, EmojiParser.parseToUnicode(EmojiParser.parseToAliases(emoji, EmojiParser.FitzpatrickAction.IGNORE)));
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/test/java/com/vdurmont/emoji/EmojiLoaderTest.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.junit.runners.JUnit4;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.io.UnsupportedEncodingException;
13 | import java.nio.charset.StandardCharsets;
14 | import java.util.List;
15 |
16 | import static org.junit.Assert.assertEquals;
17 | import static org.junit.Assert.assertFalse;
18 | import static org.junit.Assert.assertNotNull;
19 | import static org.junit.Assert.assertNull;
20 | import static org.junit.Assert.assertTrue;
21 |
22 | @RunWith(JUnit4.class)
23 | public class EmojiLoaderTest {
24 | @Test
25 | public void load_empty_database_returns_empty_list() throws IOException {
26 | // GIVEN
27 | byte[] bytes = new JSONArray().toString().getBytes(StandardCharsets.UTF_8);
28 | InputStream stream = new ByteArrayInputStream(bytes);
29 |
30 | // WHEN
31 | List emojis = EmojiLoader.loadEmojis(stream);
32 |
33 | // THEN
34 | assertEquals(0, emojis.size());
35 | }
36 |
37 | @Test
38 | public void buildEmojiFromJSON() {
39 | // GIVEN
40 | JSONObject json = new JSONObject("{"
41 | + "\"emoji\": \"😄\","
42 | + "\"description\": \"smiling face with open mouth and smiling eyes\","
43 | + "\"aliases\": [\"smile\"],"
44 | + "\"tags\": [\"happy\", \"joy\", \"pleased\"]"
45 | + "}");
46 |
47 | // WHEN
48 | Emoji emoji = EmojiLoader.buildEmojiFromJSON(json);
49 |
50 | // THEN
51 | assertNotNull(emoji);
52 | assertEquals("😄", emoji.getUnicode());
53 | assertEquals(1, emoji.getAliases().size());
54 | assertEquals("smile", emoji.getAliases().get(0));
55 | }
56 |
57 | @Test
58 | public void buildEmojiFromJSON_without_unicode_returns_null() {
59 | // GIVEN
60 | JSONObject json = new JSONObject("{"
61 | + "\"aliases\": [\"smile\"],"
62 | + "\"tags\": [\"happy\", \"joy\", \"pleased\"]"
63 | + "}");
64 |
65 | // WHEN
66 | Emoji emoji = EmojiLoader.buildEmojiFromJSON(json);
67 |
68 | // THEN
69 | assertNull(emoji);
70 | }
71 |
72 | @Test
73 | public void buildEmojiFromJSON_computes_the_html_codes() {
74 | // GIVEN
75 | JSONObject json = new JSONObject("{"
76 | + "\"emoji\": \"😄\","
77 | + "\"description\": \"smiling face with open mouth and smiling eyes\","
78 | + "\"aliases\": [\"smile\"],"
79 | + "\"tags\": [\"happy\", \"joy\", \"pleased\"]"
80 | + "}");
81 |
82 | // WHEN
83 | Emoji emoji = EmojiLoader.buildEmojiFromJSON(json);
84 |
85 | // THEN
86 | assertNotNull(emoji);
87 | assertEquals("😄", emoji.getUnicode());
88 | assertEquals("😄", emoji.getHtmlDecimal());
89 | assertEquals("😄", emoji.getHtmlHexadecimal());
90 | }
91 |
92 | @Test
93 | public void buildEmojiFromJSON_with_support_for_fitzpatrick_true() {
94 | // GIVEN
95 | JSONObject json = new JSONObject("{"
96 | + "\"emoji\": \"\uD83D\uDC66\","
97 | + "\"description\": \"boy\","
98 | + "\"skin_tones\": true,"
99 | + "\"aliases\": [\"boy\"],"
100 | + "\"tags\": [\"child\"]"
101 | + "}");
102 |
103 | // WHEN
104 | Emoji emoji = EmojiLoader.buildEmojiFromJSON(json);
105 |
106 | // THEN
107 | assertNotNull(emoji);
108 | assertTrue(emoji.supportsFitzpatrick());
109 | }
110 |
111 | @Test
112 | public void buildEmojiFromJSON_with_support_for_fitzpatrick_false() {
113 | // GIVEN
114 | JSONObject json = new JSONObject("{"
115 | + "\"emoji\": \"\uD83D\uDE15\","
116 | + "\"description\": \"confused face\","
117 | + "\"skin_tones\": false,"
118 | + "\"aliases\": [\"confused\"],"
119 | + "\"tags\": []"
120 | + "}");
121 |
122 | // WHEN
123 | Emoji emoji = EmojiLoader.buildEmojiFromJSON(json);
124 |
125 | // THEN
126 | assertNotNull(emoji);
127 | assertFalse(emoji.supportsFitzpatrick());
128 | }
129 |
130 | @Test
131 | public void buildEmojiFromJSON_without_support_for_fitzpatrick() {
132 | // GIVEN
133 | JSONObject json = new JSONObject("{"
134 | + "\"emoji\": \"\uD83D\uDE15\","
135 | + "\"description\": \"confused face\","
136 | + "\"aliases\": [\"confused\"],"
137 | + "\"tags\": []"
138 | + "}");
139 |
140 | // WHEN
141 | Emoji emoji = EmojiLoader.buildEmojiFromJSON(json);
142 |
143 | // THEN
144 | assertNotNull(emoji);
145 | assertFalse(emoji.supportsFitzpatrick());
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/test/java/com/vdurmont/emoji/EmojiManagerTest.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.junit.runners.JUnit4;
6 |
7 | import java.util.Collection;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | import static org.junit.Assert.assertEquals;
12 | import static org.junit.Assert.assertFalse;
13 | import static org.junit.Assert.assertNull;
14 | import static org.junit.Assert.assertTrue;
15 |
16 | @RunWith(JUnit4.class)
17 | public class EmojiManagerTest {
18 | @Test
19 | public void getForTag_with_unknown_tag_returns_null() {
20 | // GIVEN
21 |
22 | // WHEN
23 | Set emojis = EmojiManager.getForTag("jkahsgdfjksghfjkshf");
24 |
25 | // THEN
26 | assertNull(emojis);
27 | }
28 |
29 | @Test
30 | public void getForTag_returns_the_emojis_for_the_tag() {
31 | // GIVEN
32 |
33 | // WHEN
34 | Set emojis = EmojiManager.getForTag("happy");
35 |
36 | // THEN
37 | assertEquals(4, emojis.size());
38 | assertTrue(TestTools.containsEmojis(
39 | emojis,
40 | "smile",
41 | "smiley",
42 | "grinning",
43 | "satisfied"
44 | ));
45 | }
46 |
47 | @Test
48 | public void getForTag_returns_the_haircut_emoji_for_beauty_tag() {
49 | // GIVEN
50 |
51 | // WHEN
52 | Set emojis = EmojiManager.getForTag("beauty");
53 |
54 | // THEN
55 | assertEquals(2, emojis.size());
56 | assertTrue(TestTools.containsEmojis(emojis, "haircut"));
57 | }
58 |
59 | @Test
60 | public void getForAlias_with_unknown_alias_returns_null() {
61 | // GIVEN
62 |
63 | // WHEN
64 | Emoji emoji = EmojiManager.getForAlias("jkahsgdfjksghfjkshf");
65 |
66 | // THEN
67 | assertNull(emoji);
68 | }
69 |
70 | @Test
71 | public void getForAlias_returns_the_emoji_for_the_alias() {
72 | // GIVEN
73 |
74 | // WHEN
75 | Emoji emoji = EmojiManager.getForAlias("smile");
76 |
77 | // THEN
78 | assertTrue(
79 | emoji.getAliases().contains("smile")
80 | );
81 | }
82 |
83 | @Test
84 | public void getForAlias_with_colons_returns_the_emoji_for_the_alias() {
85 | // GIVEN
86 |
87 | // WHEN
88 | Emoji emoji = EmojiManager.getForAlias(":smile:");
89 |
90 | // THEN
91 | assertTrue(
92 | emoji.getAliases().contains("smile")
93 | );
94 | }
95 |
96 | @Test
97 | public void isEmoji_for_an_emoji_returns_true() {
98 | // GIVEN
99 | String emoji = "😀";
100 |
101 | // WHEN
102 | boolean isEmoji = EmojiManager.isEmoji(emoji);
103 |
104 | // THEN
105 | assertTrue(isEmoji);
106 | }
107 |
108 | @Test
109 | public void isEmoji_with_fitzpatric_modifier_returns_true() {
110 | // GIVEN
111 | String emoji = "\uD83E\uDD30\uD83C\uDFFB";
112 |
113 | // WHEN
114 | boolean isEmoji = EmojiManager.isEmoji(emoji);
115 |
116 | // THEN
117 | assertTrue(isEmoji);
118 | }
119 |
120 | @Test
121 | public void isEmoji_for_a_non_emoji_returns_false() {
122 | // GIVEN
123 | String str = "test";
124 |
125 | // WHEN
126 | boolean isEmoji = EmojiManager.isEmoji(str);
127 |
128 | // THEN
129 | assertFalse(isEmoji);
130 | }
131 |
132 | @Test
133 | public void isEmoji_for_an_emoji_and_other_chars_returns_false() {
134 | // GIVEN
135 | String str = "😀 test";
136 |
137 | // WHEN
138 | boolean isEmoji = EmojiManager.isEmoji(str);
139 |
140 | // THEN
141 | assertFalse(isEmoji);
142 | }
143 |
144 | @Test
145 | public void containsEmoji_with_fitzpatric_modifier_returns_true() {
146 | // GIVEN
147 | String emoji = "\uD83E\uDD30\uD83C\uDFFB";
148 |
149 | // WHEN
150 | boolean containsEmoji = EmojiManager.containsEmoji(emoji);
151 |
152 | // THEN
153 | assertTrue(containsEmoji);
154 | }
155 |
156 | @Test
157 | public void containsEmoji_for_a_non_emoji_returns_false() {
158 | // GIVEN
159 | String str = "test";
160 |
161 | // WHEN
162 | boolean containsEmoji = EmojiManager.containsEmoji(str);
163 |
164 | // THEN
165 | assertFalse(containsEmoji);
166 | }
167 |
168 | @Test
169 | public void containsEmoji_for_an_emoji_and_other_chars_returns_true() {
170 | // GIVEN
171 | String str = "😀 test";
172 |
173 | // WHEN
174 | boolean containsEmoji = EmojiManager.containsEmoji(str);
175 |
176 | // THEN
177 | assertTrue(containsEmoji);
178 | }
179 |
180 | @Test
181 | public void isOnlyEmojis_for_an_emoji_returns_true() {
182 | // GIVEN
183 | String str = "😀";
184 |
185 | // WHEN
186 | boolean isEmoji = EmojiManager.isOnlyEmojis(str);
187 |
188 | // THEN
189 | assertTrue(isEmoji);
190 | }
191 |
192 | @Test
193 | public void isOnlyEmojis_for_emojis_returns_true() {
194 | // GIVEN
195 | String str = "😀😀😀";
196 |
197 | // WHEN
198 | boolean isEmoji = EmojiManager.isOnlyEmojis(str);
199 |
200 | // THEN
201 | assertTrue(isEmoji);
202 | }
203 |
204 | @Test
205 | public void isOnlyEmojis_for_random_string_returns_false() {
206 | // GIVEN
207 | String str = "😀a";
208 |
209 | // WHEN
210 | boolean isEmoji = EmojiManager.isOnlyEmojis(str);
211 |
212 | // THEN
213 | assertFalse(isEmoji);
214 | }
215 |
216 | @Test
217 | public void getAllTags_returns_the_tags() {
218 | // GIVEN
219 |
220 | // WHEN
221 | Collection tags = EmojiManager.getAllTags();
222 |
223 | // THEN
224 | // We know the number of distinct tags int the...!
225 | assertEquals(447, tags.size());
226 | }
227 |
228 | @Test
229 | public void getAll_doesnt_return_duplicates() {
230 | // GIVEN
231 |
232 | // WHEN
233 | Collection emojis = EmojiManager.getAll();
234 |
235 | // THEN
236 | Set unicodes = new HashSet();
237 | for (Emoji emoji : emojis) {
238 | assertFalse(
239 | "Duplicate: " + emoji.getDescription(),
240 | unicodes.contains(emoji.getUnicode())
241 | );
242 | unicodes.add(emoji.getUnicode());
243 | }
244 | assertEquals(unicodes.size(), emojis.size());
245 | }
246 |
247 | @Test
248 | public void no_duplicate_alias() {
249 | // GIVEN
250 |
251 | // WHEN
252 | Collection emojis = EmojiManager.getAll();
253 |
254 | // THEN
255 | Set aliases = new HashSet();
256 | Set duplicates = new HashSet();
257 | for (Emoji emoji : emojis) {
258 | for (String alias : emoji.getAliases()) {
259 | if (aliases.contains(alias)) {
260 | duplicates.add(alias);
261 | }
262 | aliases.add(alias);
263 | }
264 | }
265 | assertEquals("Duplicates: " + duplicates, duplicates.size(), 0);
266 | }
267 |
268 | @Test
269 | public void getByUnicode_returns_correct_emoji() {
270 | String wavingHand = "\uD83D\uDC4B";
271 | Emoji e = EmojiManager.getByUnicode(wavingHand);
272 | assertEquals(wavingHand, e.getUnicode());
273 | assertEquals("waving hand", e.getDescription());
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/test/java/com/vdurmont/emoji/EmojiParserTest.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | import com.vdurmont.emoji.EmojiParser.AliasCandidate;
4 | import com.vdurmont.emoji.EmojiParser.FitzpatrickAction;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.junit.runners.JUnit4;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import static org.junit.Assert.assertEquals;
13 | import static org.junit.Assert.assertNull;
14 | import static org.junit.Assert.assertTrue;
15 |
16 | @RunWith(JUnit4.class)
17 | public class EmojiParserTest {
18 | @Test
19 | public void parseToAliases_replaces_the_emojis_by_one_of_their_aliases() {
20 | // GIVEN
21 | String str = "An 😀awesome 😃string with a few 😉emojis!";
22 |
23 | // WHEN
24 | String result = EmojiParser.parseToAliases(str);
25 |
26 | // THEN
27 | assertEquals(
28 | "An :grinning:awesome :smiley:string with a few :wink:emojis!",
29 | result
30 | );
31 | }
32 |
33 | @Test
34 | public void replaceAllEmojis_replace_the_emojis_by_string() throws Exception {
35 | // GIVEN
36 | String str = "An 😀awesome 😃string with a few 😉emojis!";
37 |
38 | // WHEN
39 | String result = EmojiParser.replaceAllEmojis(str, ":)");
40 |
41 | // THEN
42 | assertEquals(
43 | "An :)awesome :)string with a few :)emojis!",
44 | result
45 | );
46 | }
47 |
48 |
49 | @Test
50 | public void parseToAliases_REPLACE_with_a_fitzpatrick_modifier() {
51 | // GIVEN
52 | String str = "\uD83D\uDC66\uD83C\uDFFF";
53 |
54 | // WHEN
55 | String result = EmojiParser.parseToAliases(str);
56 |
57 | // THEN
58 | assertEquals(":boy|type_6:", result);
59 | }
60 |
61 | @Test
62 | public void parseToAliases_REMOVE_with_a_fitzpatrick_modifier() {
63 | // GIVEN
64 | String str = "\uD83D\uDC66\uD83C\uDFFF";
65 |
66 | // WHEN
67 | String result = EmojiParser.parseToAliases(str, FitzpatrickAction.REMOVE);
68 |
69 | // THEN
70 | assertEquals(":boy:", result);
71 | }
72 |
73 | @Test
74 | public void parseToAliases_REMOVE_without_a_fitzpatrick_modifier() {
75 | // GIVEN
76 | String str = "\uD83D\uDC66";
77 |
78 | // WHEN
79 | String result = EmojiParser.parseToAliases(str, FitzpatrickAction.REMOVE);
80 |
81 | // THEN
82 | assertEquals(":boy:", result);
83 | }
84 |
85 | @Test
86 | public void parseToAliases_IGNORE_with_a_fitzpatrick_modifier() {
87 | // GIVEN
88 | String str = "\uD83D\uDC66\uD83C\uDFFF";
89 |
90 | // WHEN
91 | String result = EmojiParser.parseToAliases(str, FitzpatrickAction.IGNORE);
92 |
93 | // THEN
94 | assertEquals(":boy:\uD83C\uDFFF", result);
95 | }
96 |
97 | @Test
98 | public void parseToAliases_IGNORE_without_a_fitzpatrick_modifier() {
99 | // GIVEN
100 | String str = "\uD83D\uDC66";
101 |
102 | // WHEN
103 | String result = EmojiParser.parseToAliases(str, FitzpatrickAction.IGNORE);
104 |
105 | // THEN
106 | assertEquals(":boy:", result);
107 | }
108 |
109 | @Test
110 | public void parseToAliases_with_long_overlapping_emoji() {
111 | // GIVEN
112 | String str = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66";
113 |
114 | // WHEN
115 | String result = EmojiParser.parseToAliases(str);
116 |
117 | //With greedy parsing, this will give :man::woman::boy:
118 | //THEN
119 | assertEquals(":family_man_woman_boy:", result);
120 | }
121 |
122 | @Test
123 | public void parseToAliases_continuous_non_overlapping_emojis() {
124 | // GIVEN
125 | String str = "\uD83D\uDC69\uD83D\uDC68\uD83D\uDC66";
126 |
127 | // WHEN
128 | String result = EmojiParser.parseToAliases(str);
129 |
130 | //THEN
131 | assertEquals(":woman::man::boy:", result);
132 | }
133 |
134 | @Test
135 | public void parseToHtmlDecimal_replaces_the_emojis_by_their_html_decimal_representation() {
136 | // GIVEN
137 | String str = "An 😀awesome 😃string with a few 😉emojis!";
138 |
139 | // WHEN
140 | String result = EmojiParser.parseToHtmlDecimal(str);
141 |
142 | // THEN
143 | assertEquals(
144 | "An 😀awesome 😃string with a few 😉emojis!",
145 | result
146 | );
147 | }
148 |
149 | @Test
150 | public void parseToHtmlDecimal_PARSE_with_a_fitzpatrick_modifier() {
151 | // GIVEN
152 | String str = "\uD83D\uDC66\uD83C\uDFFF";
153 |
154 | // WHEN
155 | String result = EmojiParser.parseToHtmlDecimal(
156 | str,
157 | FitzpatrickAction.PARSE
158 | );
159 |
160 | // THEN
161 | assertEquals("👦", result);
162 | }
163 |
164 | @Test
165 | public void parseToHtmlDecimal_REMOVE_with_a_fitzpatrick_modifier() {
166 | // GIVEN
167 | String str = "\uD83D\uDC66\uD83C\uDFFF";
168 |
169 | // WHEN
170 | String result = EmojiParser.parseToHtmlDecimal(
171 | str,
172 | FitzpatrickAction.REMOVE
173 | );
174 |
175 | // THEN
176 | assertEquals("👦", result);
177 | }
178 |
179 | @Test
180 | public void parseToHtmlDecimal_IGNORE_with_a_fitzpatrick_modifier() {
181 | // GIVEN
182 | String str = "\uD83D\uDC66\uD83C\uDFFF";
183 |
184 | // WHEN
185 | String result = EmojiParser.parseToHtmlDecimal(
186 | str,
187 | FitzpatrickAction.IGNORE
188 | );
189 |
190 | // THEN
191 | assertEquals("👦\uD83C\uDFFF", result);
192 | }
193 |
194 | @Test
195 | public void parseToHtmlHexadecimal_replaces_the_emojis_by_their_htm_hex_representation() {
196 | // GIVEN
197 | String str = "An 😀awesome 😃string with a few 😉emojis!";
198 |
199 | // WHEN
200 | String result = EmojiParser.parseToHtmlHexadecimal(str);
201 |
202 | // THEN
203 | assertEquals(
204 | "An 😀awesome 😃string with a few 😉emojis!",
205 | result
206 | );
207 | }
208 |
209 | @Test
210 | public void parseToHtmlHexadecimal_PARSE_with_a_fitzpatrick_modifier() {
211 | // GIVEN
212 | String str = "\uD83D\uDC66\uD83C\uDFFF";
213 |
214 | // WHEN
215 | String result = EmojiParser.parseToHtmlHexadecimal(
216 | str,
217 | FitzpatrickAction.PARSE
218 | );
219 |
220 | // THEN
221 | assertEquals("👦", result);
222 | }
223 |
224 | @Test
225 | public void parseToHtmlHexadecimal_REMOVE_with_a_fitzpatrick_modifier() {
226 | // GIVEN
227 | String str = "\uD83D\uDC66\uD83C\uDFFF";
228 |
229 | // WHEN
230 | String result = EmojiParser.parseToHtmlHexadecimal(
231 | str,
232 | FitzpatrickAction.REMOVE
233 | );
234 |
235 | // THEN
236 | assertEquals("👦", result);
237 | }
238 |
239 | @Test
240 | public void parseToHtmlHexadecimal_IGNORE_with_a_fitzpatrick_modifier() {
241 | // GIVEN
242 | String str = "\uD83D\uDC66\uD83C\uDFFF";
243 |
244 | // WHEN
245 | String result = EmojiParser.parseToHtmlHexadecimal(
246 | str,
247 | FitzpatrickAction.IGNORE
248 | );
249 |
250 | // THEN
251 | assertEquals("👦\uD83C\uDFFF", result);
252 | }
253 |
254 | @Test
255 | public void parseToUnicode_replaces_the_aliases_and_the_html_by_their_emoji() {
256 | // GIVEN
257 | String str = "An :grinning:awesome :smiley:string " +
258 | "😄with a few 😉emojis!";
259 |
260 | // WHEN
261 | String result = EmojiParser.parseToUnicode(str);
262 |
263 | // THEN
264 | assertEquals("An 😀awesome 😃string 😄with a few 😉emojis!", result);
265 | }
266 |
267 | @Test
268 | public void parseToUnicode_with_the_thumbsup_emoji_replaces_the_alias_by_the_emoji() {
269 | // GIVEN
270 | String str = "An :+1:awesome :smiley:string " +
271 | "😄with a few :wink:emojis!";
272 |
273 | // WHEN
274 | String result = EmojiParser.parseToUnicode(str);
275 |
276 | // THEN
277 | assertEquals(
278 | "An \uD83D\uDC4Dawesome 😃string 😄with a few 😉emojis!",
279 | result
280 | );
281 | }
282 |
283 | @Test
284 | public void parseToUnicode_with_the_thumbsdown_emoji_replaces_the_alias_by_the_emoji() {
285 | // GIVEN
286 | String str = "An :-1:awesome :smiley:string 😄" +
287 | "with a few :wink:emojis!";
288 |
289 | // WHEN
290 | String result = EmojiParser.parseToUnicode(str);
291 |
292 | // THEN
293 | assertEquals(
294 | "An \uD83D\uDC4Eawesome 😃string 😄with a few 😉emojis!",
295 | result
296 | );
297 | }
298 |
299 | @Test
300 | public void parseToUnicode_with_the_thumbsup_emoji_in_hex_replaces_the_alias_by_the_emoji() {
301 | // GIVEN
302 | String str = "An :+1:awesome :smiley:string 😄" +
303 | "with a few :wink:emojis!";
304 |
305 | // WHEN
306 | String result = EmojiParser.parseToUnicode(str);
307 |
308 | // THEN
309 | assertEquals(
310 | "An \uD83D\uDC4Dawesome 😃string 😄with a few 😉emojis!",
311 | result
312 | );
313 | }
314 |
315 | @Test
316 | public void parseToUnicode_with_a_fitzpatrick_modifier() {
317 | // GIVEN
318 | String str = ":boy|type_6:";
319 |
320 | // WHEN
321 | String result = EmojiParser.parseToUnicode(str);
322 |
323 | // THEN
324 | assertEquals("\uD83D\uDC66\uD83C\uDFFF", result);
325 | }
326 |
327 | @Test
328 | public void parseToUnicode_with_an_unsupported_fitzpatrick_modifier_doesnt_replace() {
329 | // GIVEN
330 | String str = ":grinning|type_6:";
331 | // WHEN
332 | String result = EmojiParser.parseToUnicode(str);
333 |
334 | // THEN
335 | assertEquals(str, result);
336 | }
337 |
338 | @Test
339 | public void getAliasAt_with_one_alias() {
340 | // GIVEN
341 | String str = "test :boy: test";
342 |
343 | // WHEN
344 | AliasCandidate candidate = EmojiParser.getAliasAt(str, 5);
345 |
346 | // THEN
347 | assertTrue(candidate.emoji.getAliases().contains("boy"));
348 | assertNull(candidate.fitzpatrick);
349 | }
350 |
351 | @Test
352 | public void getAliasAt_with_one_alias_an_another_colon_after() {
353 | // GIVEN
354 | String str = "test :boy: test:";
355 |
356 | // WHEN
357 | AliasCandidate candidate = EmojiParser.getAliasAt(str, 5);
358 |
359 | // THEN
360 | assertTrue(candidate.emoji.getAliases().contains("boy"));
361 | assertNull(candidate.fitzpatrick);
362 | }
363 |
364 | @Test
365 | public void getAliasAt_with_one_alias_an_another_colon_right_after() {
366 | // GIVEN
367 | String str = "test :boy::test";
368 |
369 | // WHEN
370 | AliasCandidate candidate = EmojiParser.getAliasAt(str, 5);
371 |
372 | // THEN
373 | assertTrue(candidate.emoji.getAliases().contains("boy"));
374 | assertNull(candidate.fitzpatrick);
375 | }
376 |
377 | @Test
378 | public void getAliasAt_with_one_alias_an_another_colon_before_after() {
379 | // GIVEN
380 | String str = "test ::boy: test";
381 |
382 | // WHEN
383 | AliasCandidate candidate = EmojiParser.getAliasAt(str, 5);
384 | assertNull(candidate);
385 |
386 | candidate = EmojiParser.getAliasAt(str, 6);
387 |
388 | // THEN
389 | assertTrue(candidate.emoji.getAliases().contains("boy"));
390 | assertNull(candidate.fitzpatrick);
391 | }
392 |
393 | @Test
394 | public void getAliasAt_with_a_fitzpatrick_modifier() {
395 | // GIVEN
396 | String str = "test :boy|type_3: test";
397 |
398 | // WHEN
399 | AliasCandidate candidate = EmojiParser.getAliasAt(str, 5);
400 |
401 | // THEN
402 | assertTrue(candidate.emoji.getAliases().contains("boy"));
403 | assertEquals(Fitzpatrick.TYPE_3, candidate.fitzpatrick);
404 | }
405 |
406 | @Test
407 | public void test_with_a_new_flag() {
408 | String input = "Cuba has a new flag! :cuba:";
409 | String expected = "Cuba has a new flag! \uD83C\uDDE8\uD83C\uDDFA";
410 |
411 | assertEquals(expected, EmojiParser.parseToUnicode(input));
412 | assertEquals(input, EmojiParser.parseToAliases(expected));
413 | }
414 |
415 | @Test
416 | public void removeAllEmojis_removes_all_the_emojis_from_the_string() {
417 | // GIVEN
418 | String input = "An 😀awesome 😃string 😄with " +
419 | "a \uD83D\uDC66\uD83C\uDFFFfew 😉emojis!";
420 |
421 | // WHEN
422 | String result = EmojiParser.removeAllEmojis(input);
423 |
424 | // THEN
425 | String expected = "An awesome string with a few emojis!";
426 | assertEquals(expected, result);
427 | }
428 |
429 | @Test
430 | public void removeEmojis_only_removes_the_emojis_in_the_iterable_from_the_string() {
431 | // GIVEN
432 | String input = "An\uD83D\uDE03 awesome\uD83D\uDE04 string" +
433 | "\uD83D\uDC4D\uD83C\uDFFF with\uD83D\uDCAA\uD83C\uDFFD a few emojis!";
434 |
435 | List emojis = new ArrayList();
436 | emojis.add(EmojiManager.getForAlias("smile"));
437 | emojis.add(EmojiManager.getForAlias("+1"));
438 |
439 | // WHEN
440 | String result = EmojiParser.removeEmojis(input, emojis);
441 |
442 | // THEN
443 | String expected = "An\uD83D\uDE03 awesome string with" +
444 | "\uD83D\uDCAA\uD83C\uDFFD a few emojis!";
445 | assertEquals(expected, result);
446 | }
447 |
448 | @Test
449 | public void removeAllEmojisExcept_removes_all_the_emojis_from_the_string_except_those_in_the_iterable() {
450 | // GIVEN
451 | String input = "An\uD83D\uDE03 awesome\uD83D\uDE04 string" +
452 | "\uD83D\uDC4D\uD83C\uDFFF with\uD83D\uDCAA\uD83C\uDFFD a few emojis!";
453 |
454 | List emojis = new ArrayList();
455 | emojis.add(EmojiManager.getForAlias("smile"));
456 | emojis.add(EmojiManager.getForAlias("+1"));
457 |
458 | // WHEN
459 | String result = EmojiParser.removeAllEmojisExcept(input, emojis);
460 |
461 | // THEN
462 | String expected = "An awesome\uD83D\uDE04 string\uD83D\uDC4D\uD83C\uDFFF " +
463 | "with a few emojis!";
464 | assertEquals(expected, result);
465 | }
466 |
467 | @Test
468 | public void parseToUnicode_with_the_smile_emoji_replaces_the_alias_by_the_emoji() {
469 | // GIVEN
470 | String str = "Let's test the :laughing: emoji and its other alias :satisfied:";
471 |
472 | // WHEN
473 | String result = EmojiParser.parseToUnicode(str);
474 |
475 | // THEN
476 | assertEquals("Let's test the 😆 emoji and its other alias 😆", result);
477 | }
478 |
479 | @Test
480 | public void parseToAliases_NG_and_nigeria() {
481 | // GIVEN
482 | String str = "Nigeria is 🇳🇬, NG is 🆖";
483 |
484 | // WHEN
485 | String result = EmojiParser.parseToAliases(str);
486 |
487 | // THEN
488 | assertEquals("Nigeria is :nigeria:, NG is :ng:", result);
489 | }
490 |
491 | @Test
492 | public void parseToAliases_couplekiss_woman_woman() {
493 | // GIVEN
494 | String str = "👩❤️💋👩";
495 |
496 | // WHEN
497 | String result = EmojiParser.parseToAliases(str);
498 |
499 | // THEN
500 | assertEquals(":couplekiss_woman_woman:", result);
501 | }
502 |
503 | @Test
504 | public void extractEmojis() {
505 | // GIVEN
506 | String str = "An 😀awesome 😃string with a few 😉emojis!";
507 |
508 | // WHEN
509 | List result = EmojiParser.extractEmojis(str);
510 |
511 | // THEN
512 | assertEquals("😀", result.get(0));
513 | assertEquals("😃", result.get(1));
514 | assertEquals("😉", result.get(2));
515 |
516 | }
517 |
518 | @Test
519 | public void extractEmojis_withFitzpatrickModifiers() {
520 | // GIVEN
521 | final String surfer = EmojiManager.getForAlias("surfer").getUnicode();
522 | final String surfer3 = EmojiManager.getForAlias("surfer").getUnicode(Fitzpatrick.TYPE_3);
523 | final String surfer4 = EmojiManager.getForAlias("surfer").getUnicode(Fitzpatrick.TYPE_4);
524 | final String surfer5 = EmojiManager.getForAlias("surfer").getUnicode(Fitzpatrick.TYPE_5);
525 | final String surfer6 = EmojiManager.getForAlias("surfer").getUnicode(Fitzpatrick.TYPE_6);
526 | final String surfers = surfer + " " + surfer3 + " " + surfer4 + " " + surfer5 + " " + surfer6;
527 |
528 | // WHEN
529 | List result = EmojiParser.extractEmojis(surfers);
530 |
531 | // THEN
532 | assertEquals(5, result.size());
533 | assertEquals(surfer, result.get(0));
534 | assertEquals(surfer3, result.get(1));
535 | assertEquals(surfer4, result.get(2));
536 | assertEquals(surfer5, result.get(3));
537 | assertEquals(surfer6, result.get(4));
538 |
539 | }
540 |
541 | @Test
542 | public void parseToAliases_with_first_medal() {
543 | // GIVEN
544 | String str = "🥇";
545 |
546 | // WHEN
547 | String result = EmojiParser.parseToAliases(str);
548 |
549 | // THEN
550 | assertEquals(":1st_place_medal:", result);
551 | }
552 | }
553 |
--------------------------------------------------------------------------------
/src/test/java/com/vdurmont/emoji/TestTools.java:
--------------------------------------------------------------------------------
1 | package com.vdurmont.emoji;
2 |
3 | public class TestTools {
4 | public static boolean containsEmojis(
5 | Iterable emojis,
6 | String... aliases
7 | ) {
8 | for (String alias : aliases) {
9 | boolean contains = containsEmoji(emojis, alias);
10 | if (!contains) {
11 | return false;
12 | }
13 | }
14 | return true;
15 | }
16 |
17 | public static boolean containsEmoji(Iterable emojis, String alias) {
18 | for (Emoji emoji : emojis) {
19 | for (String al : emoji.getAliases()) {
20 | if (alias.equals(al)) {
21 | return true;
22 | }
23 | }
24 | }
25 | return false;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------