├── .github ├── pom.xml └── workflows │ ├── docs.yml │ └── pom.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── emoji-table-generator ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── vdurmont │ └── emoji │ └── TableGenerator.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── vdurmont │ │ └── emoji │ │ ├── Emoji.java │ │ ├── EmojiCategory.java │ │ ├── EmojiLoader.java │ │ ├── EmojiManager.java │ │ ├── EmojiParser.java │ │ ├── EmojiTrie.java │ │ └── Fitzpatrick.java └── resources │ ├── emoji-definitions.json │ └── emoji-list.json └── test ├── java └── com │ └── vdurmont │ └── emoji │ ├── EmojiJsonTest.java │ ├── EmojiLoaderTest.java │ ├── EmojiManagerTest.java │ ├── EmojiParserTest.java │ └── TestTools.java └── resources └── emoji-test.txt /.github/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 4.0.0 10 | com.github.minndevelopment 11 | emoji-java 12 | 6.1.0 13 | 14 | 15 | org.json 16 | json 17 | 20211205 18 | runtime 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate javadoc github pages 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 11 13 | uses: actions/setup-java@v2 14 | with: 15 | java-version: 11 16 | distribution: temurin 17 | - name: Grant execute permission for gradlew 18 | run: chmod +x gradlew 19 | - name: Generate documentation directory 20 | uses: gradle/gradle-build-action@v2 21 | with: 22 | arguments: javadoc 23 | - name: Deploy 24 | uses: peaceiris/actions-gh-pages@v3 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | publish_dir: ./build/docs/javadoc 28 | allow_empty_commit: true -------------------------------------------------------------------------------- /.github/workflows/pom.yml: -------------------------------------------------------------------------------- 1 | name: Generate .github/pom.xml 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 11 13 | uses: actions/setup-java@v2 14 | with: 15 | java-version: 11 16 | distribution: temurin 17 | - name: Grant execute permission for gradlew 18 | run: chmod +x gradlew 19 | - name: Generate pom.xml 20 | uses: gradle/gradle-build-action@v2 21 | with: 22 | arguments: generatePomFileForReleasePublication 23 | - name: Move pom file to github folder 24 | run: | 25 | mv build/publications/Release/pom-default.xml .github/pom.xml 26 | - name: Commit pom.xml 27 | id: commit 28 | continue-on-error: true 29 | run: | 30 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 31 | git config --local user.name "github-actions[bot]" 32 | git add .github/pom.xml 33 | git commit -m "Update pom.xml" 34 | - name: Push changes 35 | if: steps.commit.outcome == 'success' && steps.commit.conclusion == 'success' 36 | uses: ad-m/github-push-action@8407731efefc0d8f72af254c74276b7a90be36e1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Java 2 | target 3 | 4 | # IntelliJ files 5 | *.iml 6 | .idea -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-present Vincent DURMONT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # emoji-java 2 | 3 | [![License Info](http://img.shields.io/badge/license-The%20MIT%20License-brightgreen.svg)](https://github.com/vdurmont/emoji-java/blob/master/LICENSE.md) 4 | 5 | _The missing emoji library for java._ 6 | 7 | **emoji-java** is a lightweight java library that helps you use Emojis in your java applications. 8 | 9 | Documentation is hosted on [GitHub Pages](https://minndevelopment.github.io/emoji-java/overview-tree.html) 10 | 11 | ## How to get it? 12 | 13 | ##### Via Maven: 14 | 15 | ```xml 16 | 17 | com.github.minndevelopment 18 | emoji-java 19 | master-SNAPSHOT 20 | 21 | ``` 22 | 23 | Add jitpack 24 | 25 | ```xml 26 | 27 | jitpack 28 | jitpack 29 | https://jitpack.io/ 30 | 31 | ``` 32 | 33 | 34 | ##### Via Gradle: 35 | 36 | ```gradle 37 | dependencies { 38 | implementation("com.github.minndevelopment:emoji-java:master-SNAPSHOT") 39 | } 40 | ``` 41 | 42 | Add jitpack: 43 | 44 | ```gradle 45 | repositories { 46 | maven { url 'https://jitpack.io/' } 47 | } 48 | ``` 49 | 50 | ##### Via Direct Download: 51 | 52 | You can also download the project, build it with `./gradlew shadowJar` and add the generated jar from `build/libs` to your buildpath. 53 | 54 | ## How to use it? 55 | 56 | ### EmojiManager 57 | 58 | The `EmojiManager` provides several static methods to search through the emojis database: 59 | 60 | - `getForTag` returns all the emojis for a given tag 61 | - `getForAlias` returns the emoji for an alias 62 | - `getAll` returns all the emojis 63 | - `isEmoji` checks if a string is an emoji 64 | - `containsEmoji` checks if a string contains any emoji 65 | 66 | You can also query the metadata: 67 | 68 | - `getAllTags` returns the available tags 69 | 70 | Or get everything: 71 | 72 | - `getAll` returns all the emojis 73 | 74 | ### Emoji model 75 | 76 | An `Emoji` is a POJO (plain old java object), which provides the following methods: 77 | 78 | - `getUnicode` returns the unicode representation of the emoji 79 | - `getUnicode(Fitzpatrick)` returns the unicode representation of the emoji with the provided Fitzpatrick modifier. If the emoji doesn't support the Fitzpatrick modifiers, this method will throw an `UnsupportedOperationException`. If the provided Fitzpatrick is null, this method will return the unicode of the emoji. 80 | - `getDescription` returns the (optional) description of the emoji 81 | - `getAliases` returns a list of aliases for this emoji 82 | - `getTags` returns a list of tags for this emoji 83 | - `getHtmlDecimal` returns an html decimal representation of the emoji 84 | - `getHtmlHexadecimal` returns an html decimal representation of the emoji 85 | - `supportsFitzpatrick` returns true if the emoji supports the Fitzpatrick modifiers, else false 86 | 87 | ### Fitzpatrick modifiers 88 | 89 | Some emojis now support the use of Fitzpatrick modifiers that gives the choice between 5 shades of skin tones: 90 | 91 | | Modifier | Type | 92 | | :------: | -------- | 93 | | 🏻 | type_1_2 | 94 | | 🏼 | type_3 | 95 | | 🏽 | type_4 | 96 | | 🏾 | type_5 | 97 | | 🏿 | type_6 | 98 | 99 | We defined the format of the aliases including a Fitzpatrick modifier as: 100 | 101 | ``` 102 | :ALIAS|TYPE: 103 | ``` 104 | 105 | A few examples: 106 | 107 | ``` 108 | :boy|type_1_2: 109 | :swimmer|type_4: 110 | :santa|type_6: 111 | ``` 112 | 113 | ### EmojiParser 114 | 115 | #### To unicode 116 | 117 | To replace all the aliases and the html representations found in a string by their unicode, use `EmojiParser#parseToUnicode(String)`. 118 | 119 | For example: 120 | 121 | ```java 122 | String str = "An :grinning:awesome :smiley:string 😄with a few :wink:emojis!"; 123 | String result = EmojiParser.parseToUnicode(str); 124 | System.out.println(result); 125 | // Prints: 126 | // "An 😀awesome 😃string 😄with a few 😉emojis!" 127 | ``` 128 | 129 | #### To aliases 130 | 131 | To replace all the emoji's unicodes found in a string by their aliases, use `EmojiParser#parseToAliases(String)`. 132 | 133 | For example: 134 | 135 | ```java 136 | String str = "An 😀awesome 😃string with a few 😉emojis!"; 137 | String result = EmojiParser.parseToAliases(str); 138 | System.out.println(result); 139 | // Prints: 140 | // "An :grinning:awesome :smiley:string with a few :wink:emojis!" 141 | ``` 142 | 143 | By default, the aliases will parse and include any Fitzpatrick modifier that would be provided. If you want to remove or ignore the Fitzpatrick modifiers, use `EmojiParser#parseToAliases(String, FitzpatrickAction)`. Examples: 144 | 145 | ```java 146 | String str = "Here is a boy: \uD83D\uDC66\uD83C\uDFFF!"; 147 | System.out.println(EmojiParser.parseToAliases(str)); 148 | System.out.println(EmojiParser.parseToAliases(str, FitzpatrickAction.PARSE)); 149 | // Prints twice: "Here is a boy: :boy|type_6:!" 150 | System.out.println(EmojiParser.parseToAliases(str, FitzpatrickAction.REMOVE)); 151 | // Prints: "Here is a boy: :boy:!" 152 | System.out.println(EmojiParser.parseToAliases(str, FitzpatrickAction.IGNORE)); 153 | // Prints: "Here is a boy: :boy:🏿!" 154 | ``` 155 | 156 | #### To html 157 | 158 | To replace all the emoji's unicodes found in a string by their html representation, use `EmojiParser#parseToHtmlDecimal(String)` or `EmojiParser#parseToHtmlHexadecimal(String)`. 159 | 160 | For example: 161 | 162 | ```java 163 | String str = "An 😀awesome 😃string with a few 😉emojis!"; 164 | 165 | String resultDecimal = EmojiParser.parseToHtmlDecimal(str); 166 | System.out.println(resultDecimal); 167 | // Prints: 168 | // "An 😀awesome 😃string with a few 😉emojis!" 169 | 170 | String resultHexadecimal = EmojiParser.parseToHtmlHexadecimal(str); 171 | System.out.println(resultHexadecimal); 172 | // Prints: 173 | // "An 😀awesome 😃string with a few 😉emojis!" 174 | ``` 175 | 176 | By default, any Fitzpatrick modifier will be removed. If you want to ignore the Fitzpatrick modifiers, use `EmojiParser#parseToAliases(String, FitzpatrickAction)`. Examples: 177 | 178 | ```java 179 | String str = "Here is a boy: \uD83D\uDC66\uD83C\uDFFF!"; 180 | System.out.println(EmojiParser.parseToHtmlDecimal(str)); 181 | System.out.println(EmojiParser.parseToHtmlDecimal(str, FitzpatrickAction.PARSE)); 182 | System.out.println(EmojiParser.parseToHtmlDecimal(str, FitzpatrickAction.REMOVE)); 183 | // Print 3 times: "Here is a boy: 👦!" 184 | System.out.println(EmojiParser.parseToHtmlDecimal(str, FitzpatrickAction.IGNORE)); 185 | // Prints: "Here is a boy: 👦🏿!" 186 | ``` 187 | 188 | The same applies for the methods `EmojiParser#parseToHtmlHexadecimal(String)` and `EmojiParser#parseToHtmlHexadecimal(String, FitzpatrickAction)`. 189 | 190 | #### Remove emojis 191 | 192 | You can easily remove emojis from a string using one of the following methods: 193 | 194 | - `EmojiParser#removeAllEmojis(String)`: removes all the emojis from the String 195 | - `EmojiParser#removeAllEmojisExcept(String, Collection)`: removes all the emojis from the String, except the ones in the Collection 196 | - `EmojiParser#removeEmojis(String, Collection)`: removes the emojis in the Collection from the String 197 | 198 | For example: 199 | 200 | ```java 201 | String str = "An 😀awesome 😃string with a few 😉emojis!"; 202 | Collection collection = new ArrayList(); 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%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 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 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 | --------------------------------------------------------------------------------