diff --git a/.classpath b/.classpath index 4a08052..9784724 100644 --- a/.classpath +++ b/.classpath @@ -1,23 +1,24 @@ + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/.gradle/8.8/checksums/checksums.lock b/.gradle/8.8/checksums/checksums.lock new file mode 100644 index 0000000..bdd8139 Binary files /dev/null and b/.gradle/8.8/checksums/checksums.lock differ diff --git a/.gradle/8.8/checksums/md5-checksums.bin b/.gradle/8.8/checksums/md5-checksums.bin new file mode 100644 index 0000000..444ec28 Binary files /dev/null and b/.gradle/8.8/checksums/md5-checksums.bin differ diff --git a/.gradle/8.8/checksums/sha1-checksums.bin b/.gradle/8.8/checksums/sha1-checksums.bin new file mode 100644 index 0000000..6d0b104 Binary files /dev/null and b/.gradle/8.8/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.8/dependencies-accessors/gc.properties b/.gradle/8.8/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/8.8/executionHistory/executionHistory.bin b/.gradle/8.8/executionHistory/executionHistory.bin new file mode 100644 index 0000000..14bc4e3 Binary files /dev/null and b/.gradle/8.8/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.8/executionHistory/executionHistory.lock b/.gradle/8.8/executionHistory/executionHistory.lock new file mode 100644 index 0000000..b0966e6 Binary files /dev/null and b/.gradle/8.8/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.8/fileChanges/last-build.bin b/.gradle/8.8/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/8.8/fileChanges/last-build.bin differ diff --git a/.gradle/8.8/fileHashes/fileHashes.bin b/.gradle/8.8/fileHashes/fileHashes.bin new file mode 100644 index 0000000..1de2316 Binary files /dev/null and b/.gradle/8.8/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.8/fileHashes/fileHashes.lock b/.gradle/8.8/fileHashes/fileHashes.lock new file mode 100644 index 0000000..ecc8885 Binary files /dev/null and b/.gradle/8.8/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.8/fileHashes/resourceHashesCache.bin b/.gradle/8.8/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..502da8e Binary files /dev/null and b/.gradle/8.8/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/8.8/gc.properties b/.gradle/8.8/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..018db78 Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..6483e12 --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Fri Jan 03 15:30:29 CET 2025 +gradle.version=8.8 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..b1df7c0 Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000..7a4a696 Binary files /dev/null and b/.gradle/file-system.probe differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.project b/.project index b8d3538..6cff5ba 100644 --- a/.project +++ b/.project @@ -14,4 +14,15 @@ org.eclipse.jdt.core.javanature + + + 1735908922007 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bff3d8f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + + + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "MamLoader", + "request": "launch", + "mainClass": "de.thedevstack.xmpp.mamloader.MamLoader", + "projectName": "MamHistoryLoader", + "args": "tristan@jabber.mail-zur.de lookshe@jabber.thehappy.de -limit 10" + }, + { + "type": "java", + "name": "MamLoader oldest message", + "request": "launch", + "mainClass": "de.thedevstack.xmpp.mamloader.MamLoader", + "projectName": "MamHistoryLoader", + "args": "tristan@jabber.mail-zur.de lookshe@jabber.thehappy.de -limit -1" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index be0071d..8064437 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # MamHistoryLoader ## Build -Build using ant with the target "create__run__jar" +Build using ant with the target "create\_run\_jar" ## Run ``` -java -jar [-debug] [-start dd.MM.yyyy] [-end dd.MM.yyyy] [-limit ] +java -jar MamHistoryLoader.jar [-debug] [-from dd.MM.yyyy] [-to dd.MM.yyyy] [-limit ] ``` -The options -start and -limit cannot be used together. \ No newline at end of file +The options -from and -limit cannot be used together. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..49867be --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'application' +} + +repositories { + mavenCentral() +} + +apply plugin: 'application' + +application { + mainClass = 'de.thedevstack.xmpp.mamloader.MamLoader' +} + +dependencies { + implementation "org.jxmpp:jxmpp-core:1.1.0" + implementation "org.igniterealtime.smack:smack-java8:4.4.8" + // Optional for XMPPTCPConnection + implementation "org.igniterealtime.smack:smack-tcp:4.4.8" + // Optional for XMPP-IM (RFC 6121) support (Roster, Threaded Chats, …) + implementation "org.igniterealtime.smack:smack-im:4.4.8" + // Optional for XMPP extensions support + implementation "org.igniterealtime.smack:smack-extensions:4.4.8" + // Optional for XMPP extensions support + implementation "org.igniterealtime.smack:smack-experimental:4.4.8" +} diff --git a/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoadClient$1.class b/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoadClient$1.class new file mode 100644 index 0000000..4f388e4 Binary files /dev/null and b/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoadClient$1.class differ diff --git a/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoadClient.class b/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoadClient.class new file mode 100644 index 0000000..ae66629 Binary files /dev/null and b/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoadClient.class differ diff --git a/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoader.class b/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoader.class new file mode 100644 index 0000000..e68a444 Binary files /dev/null and b/build/classes/java/main/de/thedevstack/xmpp/mamloader/MamLoader.class differ diff --git a/build/classes/java/main/de/thedevstack/xmpp/mamloader/SSLHelper$1.class b/build/classes/java/main/de/thedevstack/xmpp/mamloader/SSLHelper$1.class new file mode 100644 index 0000000..af0e15c Binary files /dev/null and b/build/classes/java/main/de/thedevstack/xmpp/mamloader/SSLHelper$1.class differ diff --git a/build/classes/java/main/de/thedevstack/xmpp/mamloader/SSLHelper.class b/build/classes/java/main/de/thedevstack/xmpp/mamloader/SSLHelper.class new file mode 100644 index 0000000..3989e0f Binary files /dev/null and b/build/classes/java/main/de/thedevstack/xmpp/mamloader/SSLHelper.class differ diff --git a/build/distributions/MamHistoryLoader.tar b/build/distributions/MamHistoryLoader.tar new file mode 100644 index 0000000..5ac4ee0 Binary files /dev/null and b/build/distributions/MamHistoryLoader.tar differ diff --git a/build/distributions/MamHistoryLoader.zip b/build/distributions/MamHistoryLoader.zip new file mode 100644 index 0000000..c02656b Binary files /dev/null and b/build/distributions/MamHistoryLoader.zip differ diff --git a/build/libs/MamHistoryLoader.jar b/build/libs/MamHistoryLoader.jar new file mode 100644 index 0000000..c097b09 Binary files /dev/null and b/build/libs/MamHistoryLoader.jar differ diff --git a/build/scripts/MamHistoryLoader b/build/scripts/MamHistoryLoader new file mode 100644 index 0000000..390ae3f --- /dev/null +++ b/build/scripts/MamHistoryLoader @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# MamHistoryLoader start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh MamHistoryLoader +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and MAM_HISTORY_LOADER_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}.." > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/lib/MamHistoryLoader.jar:$APP_HOME/lib/smack-java8-4.4.8.jar:$APP_HOME/lib/smack-tcp-4.4.8.jar:$APP_HOME/lib/smack-experimental-4.4.8.jar:$APP_HOME/lib/smack-extensions-4.4.8.jar:$APP_HOME/lib/smack-im-4.4.8.jar:$APP_HOME/lib/smack-resolver-javax-4.4.8.jar:$APP_HOME/lib/smack-sasl-javax-4.4.8.jar:$APP_HOME/lib/smack-streammanagement-4.4.8.jar:$APP_HOME/lib/smack-core-4.4.8.jar:$APP_HOME/lib/jxmpp-jid-1.0.3.jar:$APP_HOME/lib/jxmpp-core-1.1.0.jar:$APP_HOME/lib/jxmpp-util-cache-1.1.0.jar:$APP_HOME/lib/smack-xmlparser-stax-4.4.8.jar:$APP_HOME/lib/hsluv-0.2.jar:$APP_HOME/lib/smack-xmlparser-4.4.8.jar:$APP_HOME/lib/minidns-core-1.0.5.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and MAM_HISTORY_LOADER_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and MAM_HISTORY_LOADER_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + -classpath "$CLASSPATH" \ + de.thedevstack.xmpp.mamloader.MamLoader \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $MAM_HISTORY_LOADER_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/build/scripts/MamHistoryLoader.bat b/build/scripts/MamHistoryLoader.bat new file mode 100644 index 0000000..20a116c --- /dev/null +++ b/build/scripts/MamHistoryLoader.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem MamHistoryLoader startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%.. + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and MAM_HISTORY_LOADER_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\lib\MamHistoryLoader.jar;%APP_HOME%\lib\smack-java8-4.4.8.jar;%APP_HOME%\lib\smack-tcp-4.4.8.jar;%APP_HOME%\lib\smack-experimental-4.4.8.jar;%APP_HOME%\lib\smack-extensions-4.4.8.jar;%APP_HOME%\lib\smack-im-4.4.8.jar;%APP_HOME%\lib\smack-resolver-javax-4.4.8.jar;%APP_HOME%\lib\smack-sasl-javax-4.4.8.jar;%APP_HOME%\lib\smack-streammanagement-4.4.8.jar;%APP_HOME%\lib\smack-core-4.4.8.jar;%APP_HOME%\lib\jxmpp-jid-1.0.3.jar;%APP_HOME%\lib\jxmpp-core-1.1.0.jar;%APP_HOME%\lib\jxmpp-util-cache-1.1.0.jar;%APP_HOME%\lib\smack-xmlparser-stax-4.4.8.jar;%APP_HOME%\lib\hsluv-0.2.jar;%APP_HOME%\lib\smack-xmlparser-4.4.8.jar;%APP_HOME%\lib\minidns-core-1.0.5.jar + + +@rem Execute MamHistoryLoader +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %MAM_HISTORY_LOADER_OPTS% -classpath "%CLASSPATH%" de.thedevstack.xmpp.mamloader.MamLoader %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable MAM_HISTORY_LOADER_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%MAM_HISTORY_LOADER_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..28054e4 Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/build/tmp/jar/MANIFEST.MF b/build/tmp/jar/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..51b556a --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.home=c:\\Program Files\\Microsoft\\jdk-21.0.5.11-hotspot\\ \ No newline at end of file diff --git a/libs/3rdParty/jxmpp-core-0.5.0.jar b/libs/3rdParty/jxmpp-core-0.5.0.jar deleted file mode 100644 index ce5f193..0000000 Binary files a/libs/3rdParty/jxmpp-core-0.5.0.jar and /dev/null differ diff --git a/libs/3rdParty/jxmpp-core-1.1.0.jar b/libs/3rdParty/jxmpp-core-1.1.0.jar new file mode 100644 index 0000000..3f959d2 Binary files /dev/null and b/libs/3rdParty/jxmpp-core-1.1.0.jar differ diff --git a/libs/3rdParty/jxmpp-jid-0.5.0.jar b/libs/3rdParty/jxmpp-jid-0.5.0.jar deleted file mode 100644 index e65dee7..0000000 Binary files a/libs/3rdParty/jxmpp-jid-0.5.0.jar and /dev/null differ diff --git a/libs/3rdParty/jxmpp-jid-1.1.0.jar b/libs/3rdParty/jxmpp-jid-1.1.0.jar new file mode 100644 index 0000000..5e86099 Binary files /dev/null and b/libs/3rdParty/jxmpp-jid-1.1.0.jar differ diff --git a/libs/3rdParty/jxmpp-repl-1.1.0.jar b/libs/3rdParty/jxmpp-repl-1.1.0.jar new file mode 100644 index 0000000..905b9aa Binary files /dev/null and b/libs/3rdParty/jxmpp-repl-1.1.0.jar differ diff --git a/libs/3rdParty/jxmpp-stringprep-libidn-1.1.0.jar b/libs/3rdParty/jxmpp-stringprep-libidn-1.1.0.jar new file mode 100644 index 0000000..3400ad7 Binary files /dev/null and b/libs/3rdParty/jxmpp-stringprep-libidn-1.1.0.jar differ diff --git a/libs/3rdParty/jxmpp-util-cache-0.5.0.jar b/libs/3rdParty/jxmpp-util-cache-0.5.0.jar deleted file mode 100644 index 724629a..0000000 Binary files a/libs/3rdParty/jxmpp-util-cache-0.5.0.jar and /dev/null differ diff --git a/libs/3rdParty/jxmpp-util-cache-1.1.0.jar b/libs/3rdParty/jxmpp-util-cache-1.1.0.jar new file mode 100644 index 0000000..3eed38d Binary files /dev/null and b/libs/3rdParty/jxmpp-util-cache-1.1.0.jar differ diff --git a/libs/3rdParty/minidns-core-0.2.4.jar b/libs/3rdParty/minidns-core-0.2.4.jar deleted file mode 100644 index bed0382..0000000 Binary files a/libs/3rdParty/minidns-core-0.2.4.jar and /dev/null differ diff --git a/libs/3rdParty/minidns-core-1.1.0.jar b/libs/3rdParty/minidns-core-1.1.0.jar new file mode 100644 index 0000000..9218e83 Binary files /dev/null and b/libs/3rdParty/minidns-core-1.1.0.jar differ diff --git a/libs/3rdParty/minidns-dnssec-0.2.4.jar b/libs/3rdParty/minidns-dnssec-0.2.4.jar deleted file mode 100644 index 85436bb..0000000 Binary files a/libs/3rdParty/minidns-dnssec-0.2.4.jar and /dev/null differ diff --git a/libs/3rdParty/minidns-hla-0.2.4.jar b/libs/3rdParty/minidns-hla-0.2.4.jar deleted file mode 100644 index 9f7ec99..0000000 Binary files a/libs/3rdParty/minidns-hla-0.2.4.jar and /dev/null differ diff --git a/libs/3rdParty/minidns-iterative-resolver-0.2.4.jar b/libs/3rdParty/minidns-iterative-resolver-0.2.4.jar deleted file mode 100644 index 176fa04..0000000 Binary files a/libs/3rdParty/minidns-iterative-resolver-0.2.4.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-core-4.2.1.jar b/libs/3rdParty/smack/smack-core-4.2.1.jar deleted file mode 100644 index 8026a4f..0000000 Binary files a/libs/3rdParty/smack/smack-core-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-core-4.4.8.jar b/libs/3rdParty/smack/smack-core-4.4.8.jar new file mode 100644 index 0000000..a21aeab Binary files /dev/null and b/libs/3rdParty/smack/smack-core-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-experimental-4.2.1.jar b/libs/3rdParty/smack/smack-experimental-4.2.1.jar deleted file mode 100644 index 2949d80..0000000 Binary files a/libs/3rdParty/smack/smack-experimental-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-experimental-4.4.8.jar b/libs/3rdParty/smack/smack-experimental-4.4.8.jar new file mode 100644 index 0000000..ad10cbe Binary files /dev/null and b/libs/3rdParty/smack/smack-experimental-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-extensions-4.2.1.jar b/libs/3rdParty/smack/smack-extensions-4.2.1.jar deleted file mode 100644 index 858bbd0..0000000 Binary files a/libs/3rdParty/smack/smack-extensions-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-extensions-4.4.8.jar b/libs/3rdParty/smack/smack-extensions-4.4.8.jar new file mode 100644 index 0000000..5212902 Binary files /dev/null and b/libs/3rdParty/smack/smack-extensions-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-im-4.2.1.jar b/libs/3rdParty/smack/smack-im-4.2.1.jar deleted file mode 100644 index 6e9c61a..0000000 Binary files a/libs/3rdParty/smack/smack-im-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-im-4.4.8.jar b/libs/3rdParty/smack/smack-im-4.4.8.jar new file mode 100644 index 0000000..4b830c2 Binary files /dev/null and b/libs/3rdParty/smack/smack-im-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-java7-4.2.1.jar b/libs/3rdParty/smack/smack-java7-4.2.1.jar deleted file mode 100644 index 15f12b3..0000000 Binary files a/libs/3rdParty/smack/smack-java7-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-java8-4.4.8.jar b/libs/3rdParty/smack/smack-java8-4.4.8.jar new file mode 100644 index 0000000..a0b4a7f Binary files /dev/null and b/libs/3rdParty/smack/smack-java8-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-resolver-javax-4.4.8.jar b/libs/3rdParty/smack/smack-resolver-javax-4.4.8.jar new file mode 100644 index 0000000..bd6bbd3 Binary files /dev/null and b/libs/3rdParty/smack/smack-resolver-javax-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-resolver-minidns-4.2.1.jar b/libs/3rdParty/smack/smack-resolver-minidns-4.2.1.jar deleted file mode 100644 index 788dc91..0000000 Binary files a/libs/3rdParty/smack/smack-resolver-minidns-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-sasl-javax-4.2.1.jar b/libs/3rdParty/smack/smack-sasl-javax-4.2.1.jar deleted file mode 100644 index 54295bc..0000000 Binary files a/libs/3rdParty/smack/smack-sasl-javax-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-sasl-javax-4.4.8.jar b/libs/3rdParty/smack/smack-sasl-javax-4.4.8.jar new file mode 100644 index 0000000..17d0895 Binary files /dev/null and b/libs/3rdParty/smack/smack-sasl-javax-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-streammanagement-4.4.8.jar b/libs/3rdParty/smack/smack-streammanagement-4.4.8.jar new file mode 100644 index 0000000..2cf2788 Binary files /dev/null and b/libs/3rdParty/smack/smack-streammanagement-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-tcp-4.2.1.jar b/libs/3rdParty/smack/smack-tcp-4.2.1.jar deleted file mode 100644 index b09eed4..0000000 Binary files a/libs/3rdParty/smack/smack-tcp-4.2.1.jar and /dev/null differ diff --git a/libs/3rdParty/smack/smack-tcp-4.4.8.jar b/libs/3rdParty/smack/smack-tcp-4.4.8.jar new file mode 100644 index 0000000..cfc3f1b Binary files /dev/null and b/libs/3rdParty/smack/smack-tcp-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-xmlparser-4.4.8.jar b/libs/3rdParty/smack/smack-xmlparser-4.4.8.jar new file mode 100644 index 0000000..9736579 Binary files /dev/null and b/libs/3rdParty/smack/smack-xmlparser-4.4.8.jar differ diff --git a/libs/3rdParty/smack/smack-xmlparser-xpp3-4.4.8.jar b/libs/3rdParty/smack/smack-xmlparser-xpp3-4.4.8.jar new file mode 100644 index 0000000..79d74df Binary files /dev/null and b/libs/3rdParty/smack/smack-xmlparser-xpp3-4.4.8.jar differ diff --git a/src/de/thedevstack/xmpp/mamloader/MamLoadClient.java b/src/de/thedevstack/xmpp/mamloader/MamLoadClient.java index 66e8969..6272170 100644 --- a/src/de/thedevstack/xmpp/mamloader/MamLoadClient.java +++ b/src/de/thedevstack/xmpp/mamloader/MamLoadClient.java @@ -20,7 +20,9 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smackx.forward.packet.Forwarded; import org.jivesoftware.smackx.mam.MamManager; -import org.jivesoftware.smackx.mam.MamManager.MamQueryResult; +import org.jivesoftware.smackx.mam.MamManager.MamQuery; +import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; +import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; @@ -42,15 +44,16 @@ public class MamLoadClient { } private void connectAndLogin(String username, String password, String host) throws SmackException, IOException, XMPPException, InterruptedException { - XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder() + XMPPTCPConnectionConfiguration.Builder configBuilder = XMPPTCPConnectionConfiguration.builder() .setUsernameAndPassword(username, password) .setXmppDomain(host) - .setHost(host) - .setDebuggerEnabled(this.debug) //.setCustomSSLContext(SSLHelper.createNaiveSSLContext()) .setResource("MAMLoader_" + UUID.randomUUID()) - .setSendPresence(false) - .build(); + .setSendPresence(false); + if (this.debug) { + configBuilder.enableDefaultDebugger(); + } + XMPPTCPConnectionConfiguration config = configBuilder.build(); connection = new XMPPTCPConnection(config); connection.connect(); connection.login(); @@ -59,63 +62,66 @@ public class MamLoadClient { } public void disconnect() { - this.connection.disconnect(); + if (null != this.connection) { + this.connection.disconnect(); + } } - public List loadHistory(String jid) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { + public List loadHistory(String jid) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { return this.loadHistory(jid, null, null); } - public List loadHistory(String jid, Date start) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { + public List loadHistory(String jid, Date start) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { return this.loadHistory(jid, start, null); } - public List loadHistory(String jid, Date start, Date end) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { - List allMessages = new ArrayList<>(); + public List loadHistory(String jid, Date start, Date end) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { + List allMessages = new ArrayList<>(); if (mamManager.isSupported()) { Jid withJid = JidCreate.bareFrom(jid); - MamQueryResult archive = mamManager.queryArchive(null, start, end, withJid, null); - allMessages.addAll(archive.forwardedMessages); - while (!archive.mamFin.isComplete()) { - archive = mamManager.pageNext(archive, LIMIT_PER_REQUEST); - allMessages.addAll(archive.forwardedMessages); - } - - allMessages = this.filterAndSort(allMessages); + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + .limitResultsToJid(withJid) + .limitResultsSince(start) + .limitResultsBefore(end) + .build(); + MamQuery archive = mamManager.queryArchive(mamQueryArgs); + allMessages = this.convertResult(archive); + } else { + throw new RuntimeException("MAM not supported"); } return allMessages; } - public List loadHistory(String jid, int limit) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException, XmppStringprepException { - List allMessages = new ArrayList<>(); + public List loadHistory(String jid, int limit) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException, XmppStringprepException { + List allMessages = new ArrayList<>(); if (mamManager.isSupported()) { Jid withJid = JidCreate.bareFrom(jid); - MamQueryResult archive = mamManager.mostRecentPage(withJid, LIMIT_PER_REQUEST); + MamQuery archive = mamManager.queryMostRecentPage(withJid, limit); - allMessages.addAll(archive.forwardedMessages); - while (!archive.mamFin.isComplete() && limit > allMessages.size()) { - int requestLimit = limit - allMessages.size(); - archive = mamManager.pagePrevious(archive, (requestLimit < LIMIT_PER_REQUEST ? requestLimit : LIMIT_PER_REQUEST)); - allMessages.addAll(archive.forwardedMessages); - } - - allMessages = this.filterAndSort(allMessages); + allMessages = this.convertResult(archive); + } else { + throw new RuntimeException("MAM not supported"); } return allMessages; } - - private List filterAndSort(List messages) { - return messages.stream() - .filter(forwarded -> forwarded.getForwardedStanza() instanceof Message - && ((Message)forwarded.getForwardedStanza()).getBody() != null) - .sorted(new Comparator() { - @Override - public int compare(Forwarded o1, Forwarded o2) { - return o1.getDelayInformation().getStamp().compareTo(o2.getDelayInformation().getStamp()); - } - }) - .collect(Collectors.toList()); + + private List convertResult(MamQuery mamQuery) { + List allMessages = new ArrayList<>(); + for (MamResultExtension mre : mamQuery.getMamResultExtensions()) { + Date date = mre.getForwarded().getDelayInformation().getStamp(); + Message message = mre.getForwarded().getForwardedStanza(); + allMessages.add(new MamMessage(message, date)); + } + + return allMessages.stream() + .sorted(new Comparator() { + @Override + public int compare(MamMessage o1, MamMessage o2) { + return o1.getDate().compareTo(o2.getDate()); + } + }) + .collect(Collectors.toList()); } } diff --git a/src/de/thedevstack/xmpp/mamloader/MamLoader.java b/src/de/thedevstack/xmpp/mamloader/MamLoader.java index da2b427..cdca064 100644 --- a/src/de/thedevstack/xmpp/mamloader/MamLoader.java +++ b/src/de/thedevstack/xmpp/mamloader/MamLoader.java @@ -12,8 +12,6 @@ import java.util.List; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smackx.forward.packet.Forwarded; public class MamLoader { private static final SimpleDateFormat SDF = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); @@ -96,10 +94,11 @@ public class MamLoader { mlc.connectAndLogin(myjid, password); // mlc.loadHistory(jid, Date.from(LocalDate.of(2017, 11, 8).atStartOfDay().toInstant(ZoneOffset.UTC)), Date.from(LocalDate.of(2017, 11, 9).atStartOfDay().toInstant(ZoneOffset.UTC))); // mlc.loadHistory(jid); - List messages = null; + List messages = null; if (start == null && -1 == limit) { messages = mlc.loadHistory(jid); + //printMessage(messages.get(0), jid, false); } else if (start == null && -1 < limit) { messages = mlc.loadHistory(jid, limit); } else if (start != null && end == null) { @@ -111,8 +110,12 @@ public class MamLoader { System.out.println(messages.size() + " messages found"); printMessagesWithBody(messages, jid); + } catch(Exception e) { + e.printStackTrace(); } finally { - mlc.disconnect(); + if (null != mlc) { + mlc.disconnect(); + } } } @@ -128,26 +131,36 @@ public class MamLoader { } } - private static void printMessagesWithBody(List messages, String withJid) { - for (Forwarded f : messages) { - Stanza stanza = f.getForwardedStanza(); - if (stanza instanceof Message) { - StringBuilder sb = new StringBuilder(); - Message message = (Message) stanza; - - if (null != message.getBody()) { - if (withJid.equals(message.getFrom().asBareJid().toString())) { - sb.append("<"); - } else { - sb.append(">"); - } - sb.append(" "); - sb.append(SDF.format(f.getDelayInformation().getStamp())); - sb.append(": "); - sb.append(message.getBody()); - System.out.println(sb.toString()); - } + private static void printMessagesWithBody(List messages, String withJid) { + for (MamMessage mamMessage : messages) { + printMessage(mamMessage, withJid, true); + } + } + + private static void printMessage(MamMessage mamMessage, String withJid, boolean onlyWithBody) { + StringBuilder sb = new StringBuilder(); + Message message = mamMessage.getMessage(); + + if (onlyWithBody && null != message.getBody()) { + if (withJid.equals(message.getFrom().asBareJid().toString())) { + sb.append("<"); + } else { + sb.append(">"); } + sb.append(" "); + sb.append(SDF.format(mamMessage.getDate())); + sb.append(": "); + sb.append(message.getBody()); + System.out.println(sb.toString()); + } else if (!onlyWithBody) { + if (withJid.equals(message.getFrom().asBareJid().toString())) { + sb.append("<"); + } else { + sb.append(">"); + } + sb.append(" "); + sb.append(SDF.format(mamMessage.getDate())); + System.out.println(sb.toString()); } } diff --git a/src/de/thedevstack/xmpp/mamloader/MamMessage.java b/src/de/thedevstack/xmpp/mamloader/MamMessage.java new file mode 100644 index 0000000..3a8dbde --- /dev/null +++ b/src/de/thedevstack/xmpp/mamloader/MamMessage.java @@ -0,0 +1,23 @@ +package de.thedevstack.xmpp.mamloader; + +import java.util.Date; + +import org.jivesoftware.smack.packet.Message; + +public final class MamMessage { + private Message message; + private Date date; + + public MamMessage(Message message, Date date) { + this.message = message; + this.date = date; + } + + public Message getMessage() { + return this.message; + } + + public Date getDate() { + return this.date; + } +} diff --git a/src/org/jxmpp/jid/BareJid.java b/src/org/jxmpp/jid/BareJid.java new file mode 100644 index 0000000..175dbf0 --- /dev/null +++ b/src/org/jxmpp/jid/BareJid.java @@ -0,0 +1,34 @@ +/** + * + * Copyright © 2015-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +/** + * An XMPP address (JID) which has no {@link org.jxmpp.jid.parts.Resourcepart}. Either + * {@link EntityBareJid} or {@link DomainBareJid}. + *

+ * Examples: + *

+ *
    + *
  • localpart@domain.part
  • + *
  • domain.part
  • + *
+ * + * @see Jid + */ +public interface BareJid extends Jid { + +} diff --git a/src/org/jxmpp/jid/DomainBareJid.java b/src/org/jxmpp/jid/DomainBareJid.java new file mode 100644 index 0000000..7ac39f2 --- /dev/null +++ b/src/org/jxmpp/jid/DomainBareJid.java @@ -0,0 +1,33 @@ +/** + * + * Copyright © 2014-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +/** + * An XMPP address (JID) consisting of the domainpart. For example "xmpp.org". + *

+ * Examples: + *

+ *
    + *
  • xmpp.org
  • + *
  • example.net
  • + *
+ * + * @see Jid + */ +public interface DomainBareJid extends Jid, BareJid, DomainJid { + +} diff --git a/src/org/jxmpp/jid/DomainFullJid.java b/src/org/jxmpp/jid/DomainFullJid.java new file mode 100644 index 0000000..79591f7 --- /dev/null +++ b/src/org/jxmpp/jid/DomainFullJid.java @@ -0,0 +1,34 @@ +/** + * + * Copyright © 2014-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +/** + * An XMPP address (JID) consisting of a domainpart and a resourcepart. For example + * "xmpp.org/resource". + *

+ * Examples: + *

+ *
    + *
  • domain.part/resource
  • + *
  • example.net/8c6def89
  • + *
+ * + * @see Jid + */ +public interface DomainFullJid extends Jid, FullJid, DomainJid { + +} diff --git a/src/org/jxmpp/jid/DomainJid.java b/src/org/jxmpp/jid/DomainJid.java new file mode 100644 index 0000000..f2f3487 --- /dev/null +++ b/src/org/jxmpp/jid/DomainJid.java @@ -0,0 +1,34 @@ +/** + * + * Copyright © 2015-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +/** + * An XMPP address (JID) which has no {@link org.jxmpp.jid.parts.Localpart}. Either + * {@link DomainBareJid} or {@link DomainFullJid}. + *

+ * Examples: + *

+ *
    + *
  • domain.part
  • + *
  • domain.part/resourcepart
  • + *
+ * + * @see Jid + */ +public interface DomainJid extends Jid { + +} diff --git a/src/org/jxmpp/jid/EntityBareJid.java b/src/org/jxmpp/jid/EntityBareJid.java new file mode 100644 index 0000000..90e3dae --- /dev/null +++ b/src/org/jxmpp/jid/EntityBareJid.java @@ -0,0 +1,34 @@ +/** + * + * Copyright © 2014-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +/** + * An XMPP address (JID) consisting of a localpart and a domainpart. For example + * "user@xmpp.org". + *

+ * Examples: + *

+ *
    + *
  • localpart@domain.part
  • + *
  • user@example.net
  • + *
+ * + * @see Jid + */ +public interface EntityBareJid extends Jid, EntityJid, BareJid { + +} diff --git a/src/org/jxmpp/jid/EntityFullJid.java b/src/org/jxmpp/jid/EntityFullJid.java new file mode 100644 index 0000000..e6beef4 --- /dev/null +++ b/src/org/jxmpp/jid/EntityFullJid.java @@ -0,0 +1,34 @@ +/** + * + * Copyright © 2014-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +/** + * An XMPP address (JID) consisting of a localpart, domainpart and resourcepart. For example + * "user@xmpp.org/resource". + *

+ * Examples: + *

+ *
    + *
  • localpart@domain.part/resource
  • + *
  • juliet@xmpp.org/balcony
  • + *
+ * + * @see Jid + */ +public interface EntityFullJid extends Jid, FullJid, EntityJid { + +} diff --git a/src/org/jxmpp/jid/EntityJid.java b/src/org/jxmpp/jid/EntityJid.java new file mode 100644 index 0000000..ea9c2e9 --- /dev/null +++ b/src/org/jxmpp/jid/EntityJid.java @@ -0,0 +1,56 @@ +/** + * + * Copyright © 2014-2018 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +import org.jxmpp.jid.parts.Localpart; + +/** + * An XMPP address (JID) which has a {@link Localpart}. Either {@link EntityBareJid} or {@link EntityFullJid}. + *

+ * Examples: + *

+ *
    + *
  • localpart@domain.part
  • + *
  • localpart@domain.part/resourcepart
  • + *
+ * + * @see Jid + */ +public interface EntityJid extends Jid { + + /** + * Return the {@link Localpart} of this JID. + * + * @return the localpart. + */ + Localpart getLocalpart(); + + /** + * Return the bare JID of this entity JID. + * + * @return the bare JID. + */ + EntityBareJid asEntityBareJid(); + + /** + * Return the bare JID string of this full JID. + * + * @return the bare JID string. + */ + String asEntityBareJidString(); + +} diff --git a/src/org/jxmpp/jid/FullJid.java b/src/org/jxmpp/jid/FullJid.java new file mode 100644 index 0000000..bcdfb4c --- /dev/null +++ b/src/org/jxmpp/jid/FullJid.java @@ -0,0 +1,42 @@ +/** + * + * Copyright © 2014-2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +import org.jxmpp.jid.parts.Resourcepart; + +/** + * An XMPP address (JID) which has a {@link Resourcepart}. Either {@link EntityFullJid} or {@link DomainFullJid}. + *

+ * Examples: + *

+ *
    + *
  • localpart@domain.part/resourcepart
  • + *
  • domain.part/resourcepart
  • + *
+ * + * @see Jid + */ +public interface FullJid extends Jid { + + /** + * Return the {@link Resourcepart} of this JID. + * + * @return the resourcepart. + */ + Resourcepart getResourcepart(); + +} diff --git a/src/org/jxmpp/jid/Jid.java b/src/org/jxmpp/jid/Jid.java new file mode 100644 index 0000000..ad8ac1c --- /dev/null +++ b/src/org/jxmpp/jid/Jid.java @@ -0,0 +1,489 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid; + +import java.io.Serializable; + +import org.jxmpp.jid.parts.Domainpart; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; + +/** + * An XMPP address, also known as JID (formerly for "Jabber Identifier"), which acts as globally unique address + * within the XMPP network. + *

+ * JIDs are created from {@link String} or {@link CharSequence} with the {@link org.jxmpp.jid.impl.JidCreate} utility. + *

+ * + *
+ * Jid jid = JidCreate.from("juliet@capulet.org/balcony");
+ * EntityBareJid bareJid = JidCreate.entityBareFrom("romeo@montague.net");
+ * 
+ *

+ * This is the super interface for all JID types, which are constructed from two dimensions: Bare/Full and + * Domain/Entity. Every JID consists at least of a {@link Domainpart}. Bare JID types do not come with a + * {@link Resourcepart}, full JID types always have a {@link Resourcepart}. Domain JID types do not possess a + * {@link Localpart}, whereas entity JID types always do. + *

+ *

+ * The following table shows a few examples of JID types. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
XMPP Address Types
ExampleType
example.org{@link DomainBareJid}
example.org/resource{@link DomainFullJid}
user@example.org{@link EntityBareJid}
user@example.org/resource{@link EntityFullJid}
+ *

+ * You can retrieve the escaped String representing the Jid with {@link #toString()} or the unescaped String of the JID + * with {@link #asUnescapedString()}. + *

+ *

URL Encoded JIDs

+ *

+ * The URL encoded representation of a JID is ideal if a JID should be stored as part of a path name, e.g. as file name. + * You can retrieve this information using {@link #asUrlEncodedString()}. The JidCreate API provides methods to create + * JIDs from URL encoded Strings like {@link org.jxmpp.jid.impl.JidCreate#fromUrlEncoded(CharSequence)}. + *

+ * + * + * @see RFC 6120 (XMPP: Core) § 2.1 Global Addresses + * @see RFC 6122 (XMPP: Address Format) § 2.1 + * Fundamentals + */ +public interface Jid extends Comparable, CharSequence, Serializable { + + /** + * Get the {@link Domainpart} of this Jid. + * + * @return the domainpart. + */ + Domainpart getDomain(); + + /** + * Returns the String representation of this JID. + * + * @return the String representation of this JID. + */ + @Override + String toString(); + + /** + * Return the unescaped String representation of this JID. + *

+ * Since certain Unicode code points are disallowed in the localpart of a JID by the required stringprep profile, + * those need to get escaped when used in a real JID. The unescaped representation of the JID is only for + * presentation to a human user or for gatewaying to a non-XMPP system. + *

+ * For example, if the users inputs {@code 'at&t guy@example.com'}, the escaped real JID created with + * {@link org.jxmpp.jid.impl.JidCreate} will be {@code 'at\26t\20guy@example.com'}, which is what + * {@link Jid#toString()} will return. But {@link Jid#asUnescapedString()} will return again + * {@code 'at&t guy@example.com'}. + * + * @return the unescaped String representation of this JID. + */ + String asUnescapedString(); + + /** + * Get the URL encoded version of this JID. + * + * @return the URL encoded version of this JID. + * @since 0.7.0 + */ + String asUrlEncodedString(); + + /** + * Check if this is a {@link EntityBareJid} or {@link EntityFullJid}. + * + * @return true if this is an instance of BareJid or FullJid. + */ + boolean isEntityJid(); + + /** + * Check if this is an instance of {@link EntityBareJid}. + * + * @return true if this is an instance of EntityBareJid + */ + boolean isEntityBareJid(); + + /** + * Check if this is an instance of {@link EntityFullJid}. + * + * @return true if this is an instance of EntityFullJid + */ + boolean isEntityFullJid(); + + /** + * Check if this is an instance of {@link DomainBareJid}. + * + * @return true if this is an instance of DomainBareJid + */ + boolean isDomainBareJid(); + + /** + * Check if this is an instance of {@link DomainFullJid}. + * + * @return true if this is an instance of DomainFullJid + */ + boolean isDomainFullJid(); + + /** + * Check if this is an instance of {@link EntityBareJid} or {@link DomainBareJid}. + * + * @return true if this is an instance of BareJid or DomainBareJid + */ + boolean hasNoResource(); + + /** + * Check if this is a Jid with a {@link Resourcepart}. + * + * @return true if this Jid has a resourcepart. + */ + boolean hasResource(); + + /** + * Check if this is a Jid with a {@link Localpart}. + * + * @return true if this Jid has a localpart. + */ + boolean hasLocalpart(); + + /** + * Return a JID created by removing the Resourcepart from this JID. + * + * @return this Jid without a Resourcepart. + */ + BareJid asBareJid(); + + /** + * Convert this Jid to a {@link EntityBareJid} if possible. + * + * @return the corresponding {@link EntityBareJid} or null. + */ + EntityBareJid asEntityBareJidIfPossible(); + + /** + * Convert this Jid to a {@link EntityBareJid} or throw an {@code IllegalStateException} if this is not possible. + * + * @return the corresponding {@link EntityBareJid}. + */ + EntityBareJid asEntityBareJidOrThrow(); + + /** + * Convert this Jid to a {@link EntityFullJid} if possible. + * + * @return the corresponding {@link EntityFullJid} or null. + */ + EntityFullJid asEntityFullJidIfPossible(); + + /** + * Convert this Jid to a {@link EntityFullJid} or throw an {@code IllegalStateException} if this is not possible. + * + * @return the corresponding {@link EntityFullJid}. + */ + EntityFullJid asEntityFullJidOrThrow(); + + /** + * Convert this Jid to a {@link EntityJid} if possible. + * + * @return the corresponding {@link EntityJid} or null. + */ + EntityJid asEntityJidIfPossible(); + + /** + * Convert this Jid to a {@link EntityJid} or throw an {@code IllegalStateException} if this is not possible. + * + * @return the corresponding {@link EntityJid}. + */ + EntityJid asEntityJidOrThrow(); + + /** + * Convert this Jid to a {@link FullJid} if possible. + * + * @return the corresponding {@link FullJid} or null. + */ + FullJid asFullJidIfPossible(); + + /** + * Convert this Jid to a {@link FullJid} or throw an {@code IllegalStateException} if this is not possible. + * + * @return the corresponding {@link FullJid}. + */ + EntityFullJid asFullJidOrThrow(); + + /** + * Convert this Jid to a {@link DomainBareJid}. + *

+ * Note that it is always possible to convert a Jid to a DomainBareJid, since every Jid has a domain part. + *

+ * + * @return the corresponding DomainBareJid. + */ + DomainBareJid asDomainBareJid(); + + /** + * Convert this Jid to a {@link DomainFullJid} if possible. + * + * @return the corresponding DomainFullJid or null. + */ + DomainFullJid asDomainFullJidIfPossible(); + + /** + * Convert this Jid to a {@link DomainFullJid} or throw an {@code IllegalStateException} if this is not possible. + * + * @return the corresponding {@link DomainFullJid}. + */ + DomainFullJid asDomainFullJidOrThrow(); + + /** + * Get the resourcepart of this JID or null. + *

+ * If the JID is of form {@code } then this method returns 'resource'. If the JID no + * resourcepart, then null is returned. + *

+ * + * @return the resource of this JID or null. + */ + Resourcepart getResourceOrNull(); + + /** + * Get the resourcepart of this JID or return the empty resourcepart. + *

+ * If the JID is of form {@code } then this method returns 'resource'. If the JID no + * resourcepart, then {@link org.jxmpp.jid.parts.Resourcepart#EMPTY} is returned. + *

+ * + * @return the resource of this JID or the empty resourcepart. + */ + Resourcepart getResourceOrEmpty(); + + /** + * Get the resourcepart of this JID or throw an {@code IllegalStateException}. + *

+ * If the JID is of form {@code } then this method returns 'resource'. If the JID no + * resourcepart, then an {@code IllegalStateException} is thrown. + *

+ * + * @return the resource of this JID. + */ + Resourcepart getResourceOrThrow(); + + /** + * Get the localpart of this JID or null. + *

+ * If the JID is of form {@code } then this method returns 'localpart'. If the JID has no + * localpart, then null is returned. + *

+ * + * @return the localpart of this JID or null. + */ + Localpart getLocalpartOrNull(); + + /** + * Get the localpart of this JID or throw an {@code IllegalStateException}. + *

+ * If the JID is of form {@code } then this method returns 'localpart'. If the JID has no + * localpart, then null is returned. + *

+ * + * @return the localpart of this JID. + */ + Localpart getLocalpartOrThrow(); + + /** + * Check if this JID is the parent of another JID. The parent of relation is defined, under the + * precondition that the JID parts (localpart, domainpart and resourcepart) are equal, as follows: + *
+	 * | this JID (parentOf) | other JID           | result |
+	 * |---------------------+---------------------+--------|
+	 * | dom.example         | dom.example         | true   |
+	 * | dom.example         | dom.example/res     | true   |
+	 * | dom.example         | loc@dom.example     | true   |
+	 * | dom.example         | loc@dom.example/res | true   |
+	 * | dom.example/res     | dom.exmple          | false  |
+	 * | dom.example/res     | dom.example/res     | true   |
+	 * | dom.example/res     | loc@dom.example     | false  |
+	 * | dom.example/res     | loc@dom.example/res | false  |
+	 * | loc@dom.example     | dom.example         | false  |
+	 * | loc@dom.example     | dom.example/res     | false  |
+	 * | loc@dom.example     | loc@dom.example     | true   |
+	 * | loc@dom.example     | loc@dom.example/res | true   |
+	 * | loc@dom.example/res | dom.example         | false  |
+	 * | loc@dom.example/res | dom.example/res     | false  |
+	 * | loc@dom.example/res | loc@dom.example     | false  |
+	 * | loc@dom.example/res | loc@dom.example/res | true   |
+	 * 
+ * + * @param jid + * the other JID to compare with + * @return true if this JID is a parent of the given JID. + */ + boolean isParentOf(Jid jid); + + /** + * See {@link #isParentOf(Jid)}. + * + * @param bareJid the bare JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isParentOf(EntityBareJid bareJid); + + /** + * See {@link #isParentOf(Jid)}. + * + * @param fullJid the full JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isParentOf(EntityFullJid fullJid); + + /** + * See {@link #isParentOf(Jid)}. + * + * @param domainBareJid the domain bare JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isParentOf(DomainBareJid domainBareJid); + + /** + * See {@link #isParentOf(Jid)}. + * + * @param domainFullJid the domain full JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isParentOf(DomainFullJid domainFullJid); + + /** + * Check if this JID is the strict parent of another JID. In other words, all parts of this JID must + * exist on the other JID, and match this JID's values. Furthermore, and this is what makes this + * method different from {@link #isParentOf(Jid)}, the other JID must have one additional part, + * that this JID does not have. The parent of relation is defined, under the + * precondition that the JID parts (localpart, domainpart and resourcepart) are equal, as follows: + *
+	 * | this JID            | other JID           | result |
+	 * |---------------------+---------------------+--------|
+	 * | dom.example         | dom.example         | false  | (different from isParentOf)
+	 * | dom.example         | dom.example/res     | true   |
+	 * | dom.example         | loc@dom.example     | true   |
+	 * | dom.example         | loc@dom.example/res | true   |
+	 * | dom.example/res     | dom.exmple          | false  |
+	 * | dom.example/res     | dom.example/res     | false  | (different from isParentOf)
+	 * | dom.example/res     | loc@dom.example     | false  |
+	 * | dom.example/res     | loc@dom.example/res | false  |
+	 * | loc@dom.example     | dom.example         | false  |
+	 * | loc@dom.example     | dom.example/res     | false  |
+	 * | loc@dom.example     | loc@dom.example     | false  | (different from isParentOf)
+	 * | loc@dom.example     | loc@dom.example/res | true   |
+	 * | loc@dom.example/res | dom.example         | false  |
+	 * | loc@dom.example/res | dom.example/res     | false  |
+	 * | loc@dom.example/res | loc@dom.example     | false  |
+	 * | loc@dom.example/res | loc@dom.example/res | false  | (different from isParentOf)
+	 * 
+ * + * @param jid + * the other JID to compare with + * @return true if this JID is a parent of the given JID. + */ + boolean isStrictParentOf(Jid jid); + + /** + * See {@link #isParentOf(Jid)}. + * + * @param bareJid the bare JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isStrictParentOf(EntityBareJid bareJid); + + /** + * See {@link #isStrictParentOf(Jid)}. + * + * @param fullJid the full JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isStrictParentOf(EntityFullJid fullJid); + + /** + * See {@link #isStrictParentOf(Jid)}. + * + * @param domainBareJid the domain bare JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isStrictParentOf(DomainBareJid domainBareJid); + + /** + * See {@link #isStrictParentOf(Jid)}. + * + * @param domainFullJid the domain full JID. + * @return true if this JID is a parent of the given JID. + */ + boolean isStrictParentOf(DomainFullJid domainFullJid); + + /** + * Return the downcasted instance of this Jid. This method is unsafe, make sure to check that this is actually of the type of are casting to. + * + * @param jidClass the class of JID to downcast too. + * @param the Jid type to downcast to. + * @return the downcasted instanced of this + * @throws ClassCastException if this JID is not assignable to the type T. + */ + T downcast(Class jidClass) throws ClassCastException; + + /** + * Compares the given CharSequence with this JID. Returns true if {@code equals(charSequence.toString()} would + * return true. + * + * @param charSequence the CharSequence to compare this JID with. + * @return true if if {@code equals(charSequence.toString()} would return true. + * @see #equals(String) + */ + @SuppressWarnings("NonOverridingEquals") + boolean equals(CharSequence charSequence); + + /** + * Compares the given String wit this JID. + *

+ * Returns true if {@code toString().equals(string)}, that is if the String representation of this JID matches the given string. + *

+ * + * @param string the String to compare this JID with. + * @return true if {@code toString().equals(string)}. + */ + @SuppressWarnings("NonOverridingEquals") + boolean equals(String string); + + /** + * Returns the canonical String representation of this JID. See {@link String#intern} for details. + * + * @return the canonical String representation. + */ + String intern(); +} diff --git a/src/org/jxmpp/jid/impl/AbstractJid.java b/src/org/jxmpp/jid/impl/AbstractJid.java new file mode 100644 index 0000000..3efdad4 --- /dev/null +++ b/src/org/jxmpp/jid/impl/AbstractJid.java @@ -0,0 +1,282 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.impl; + +import org.jxmpp.jid.EntityBareJid; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.jid.EntityJid; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; + +public abstract class AbstractJid implements Jid { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Cache for the String representation of this JID. + */ + protected String cache; + + @Override + public final boolean isEntityJid() { + return isEntityBareJid() || isEntityFullJid(); + } + + @Override + public final boolean isEntityBareJid() { + return this instanceof EntityBareJid; + } + + @Override + public final boolean isEntityFullJid() { + return this instanceof EntityFullJid; + } + + @Override + public final boolean isDomainBareJid() { + return this instanceof DomainBareJid; + } + + @Override + public final boolean isDomainFullJid() { + return this instanceof DomainFullJid; + } + + @Override + public abstract boolean hasNoResource(); + + @Override + public final boolean hasResource() { + return this instanceof FullJid; + } + + @Override + public final boolean hasLocalpart() { + return this instanceof EntityJid; + } + + @Override + public final T downcast(Class jidClass) { + return jidClass.cast(this); + } + + @Override + public int length() { + return toString().length(); + } + + @Override + public char charAt(int index) { + return toString().charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return toString().subSequence(start, end); + } + + @Override + public final EntityBareJid asEntityBareJidOrThrow() { + EntityBareJid entityBareJid = asEntityBareJidIfPossible(); + if (entityBareJid == null) throwIse("can not be converted to EntityBareJid"); + return entityBareJid; + } + + @Override + public EntityFullJid asEntityFullJidOrThrow() { + EntityFullJid entityFullJid = asEntityFullJidIfPossible(); + if (entityFullJid == null) throwIse("can not be converted to EntityFullJid"); + return entityFullJid; + } + + @Override + public EntityJid asEntityJidOrThrow() { + EntityJid entityJid = asEntityJidIfPossible(); + if (entityJid == null) throwIse("can not be converted to EntityJid"); + return entityJid; + } + + @Override + public EntityFullJid asFullJidOrThrow() { + EntityFullJid entityFullJid = asEntityFullJidIfPossible(); + if (entityFullJid == null) throwIse("can not be converted to EntityBareJid"); + return entityFullJid; + } + + @Override + public DomainFullJid asDomainFullJidOrThrow() { + DomainFullJid domainFullJid = asDomainFullJidIfPossible(); + if (domainFullJid == null) throwIse("can not be converted to DomainFullJid"); + return domainFullJid; + } + + @Override + public abstract Resourcepart getResourceOrNull(); + + @Override + public final Resourcepart getResourceOrEmpty() { + Resourcepart resourcepart = getResourceOrNull(); + if (resourcepart == null) return Resourcepart.EMPTY; + return resourcepart; + } + + @Override + public final Resourcepart getResourceOrThrow() { + Resourcepart resourcepart = getResourceOrNull(); + if (resourcepart == null) throwIse("has no resourcepart"); + return resourcepart; + } + + @Override + public abstract Localpart getLocalpartOrNull(); + + @Override + public final Localpart getLocalpartOrThrow() { + Localpart localpart = getLocalpartOrNull(); + if (localpart == null) throwIse("has no localpart"); + return localpart; + } + + @Override + public final boolean isParentOf(Jid jid) { + EntityFullJid fullJid = jid.asEntityFullJidIfPossible(); + if (fullJid != null) { + return isParentOf(fullJid); + } + EntityBareJid bareJid = jid.asEntityBareJidIfPossible(); + if (bareJid != null) { + return isParentOf(bareJid); + } + DomainFullJid domainFullJid = jid.asDomainFullJidIfPossible(); + if (domainFullJid != null) { + return isParentOf(domainFullJid); + } + + return isParentOf(jid.asDomainBareJid()); + } + + @Override + public final boolean isStrictParentOf(Jid jid) { + EntityFullJid fullJid = jid.asEntityFullJidIfPossible(); + if (fullJid != null) { + return isStrictParentOf(fullJid); + } + EntityBareJid bareJid = jid.asEntityBareJidIfPossible(); + if (bareJid != null) { + return isStrictParentOf(bareJid); + } + DomainFullJid domainFullJid = jid.asDomainFullJidIfPossible(); + if (domainFullJid != null) { + return isStrictParentOf(domainFullJid); + } + + return isStrictParentOf(jid.asDomainBareJid()); + } + + @Override + public final int hashCode() { + return toString().hashCode(); + } + + @Override + public final boolean equals(Object other) { + if (other == null) { + return false; + } + if (this == other) { + return true; + } + if (other instanceof CharSequence) { + return equals((CharSequence) other); + } + return false; + } + + @SuppressWarnings("NonOverridingEquals") + @Override + public final boolean equals(CharSequence charSequence) { + if (charSequence == null) { + return false; + } + return equals(charSequence.toString()); + } + + @SuppressWarnings("NonOverridingEquals") + @Override + public final boolean equals(String string) { + return toString().equals(string); + } + + @Override + public final int compareTo(Jid other) { + String otherString = other.toString(); + String myString = toString(); + return myString.compareTo(otherString); + } + + /** + * The cache holding the internalized value of this part. This needs to be transient so that the + * cache is recreated once the data was de-serialized. + */ + private transient String internalizedCache; + + @Override + public final String intern() { + if (internalizedCache == null) { + cache = internalizedCache = toString().intern(); + } + return internalizedCache; + } + + private transient String urlEncodedCache; + + @Override + public final String asUrlEncodedString() { + if (urlEncodedCache == null) { + String string = toString(); + try { + urlEncodedCache = URLEncoder.encode(string, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + return urlEncodedCache; + } + + private void throwIse(String message) { + String exceptionMessage = "The JID '" + this + "' " + message; + throw new IllegalStateException(exceptionMessage); + } + + static O requireNonNull(O object, String message) { + if (object != null) { + return object; + } + throw new IllegalArgumentException(message); + } +} diff --git a/src/org/jxmpp/jid/impl/DomainAndResourcepartJid.java b/src/org/jxmpp/jid/impl/DomainAndResourcepartJid.java new file mode 100644 index 0000000..6f95edf --- /dev/null +++ b/src/org/jxmpp/jid/impl/DomainAndResourcepartJid.java @@ -0,0 +1,173 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.impl; + +import org.jxmpp.JxmppContext; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.EntityJid; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.parts.Domainpart; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.stringprep.XmppStringprepException; + +/** + * RFC6122 2.4 allows JIDs with only a domain and resource part. + *

+ * Note that this implementation does not require an cache for the unescaped + * string, compared to {@link LocalDomainAndResourcepartJid}. + *

+ * + */ +public final class DomainAndResourcepartJid extends AbstractJid implements DomainFullJid { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final DomainBareJid domainBareJid; + private final Resourcepart resource; + + DomainAndResourcepartJid(String domain, String resource, JxmppContext context) throws XmppStringprepException { + this(new DomainpartJid(domain, context), Resourcepart.from(resource, context)); + } + + DomainAndResourcepartJid(DomainBareJid domainBareJid, Resourcepart resource) { + this.domainBareJid = requireNonNull(domainBareJid, "The DomainBareJid must not be null"); + this.resource = requireNonNull(resource, "The Resource must not be null"); + } + + @Override + public String toString() { + if (cache != null) { + return cache; + } + cache = domainBareJid.toString() + '/' + resource; + return cache; + } + + @Override + public DomainBareJid asDomainBareJid() { + return domainBareJid; + } + + @Override + public boolean hasNoResource() { + return false; + } + + @Override + public EntityBareJid asEntityBareJidIfPossible() { + return null; + } + + @Override + public EntityFullJid asEntityFullJidIfPossible() { + return null; + } + + @Override + public DomainFullJid asDomainFullJidIfPossible() { + return this; + } + + @Override + public Resourcepart getResourceOrNull() { + return getResourcepart(); + } + + @Override + public boolean isParentOf(EntityBareJid bareJid) { + return false; + } + + @Override + public boolean isParentOf(EntityFullJid fullJid) { + return false; + } + + @Override + public boolean isParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isParentOf(DomainFullJid domainFullJid) { + return domainBareJid.equals(domainFullJid.getDomain()) && resource.equals(domainFullJid.getResourcepart()); + } + + @Override + public boolean isStrictParentOf(EntityBareJid bareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(EntityFullJid fullJid) { + return false; + } + + @Override + public boolean isStrictParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(DomainFullJid domainFullJid) { + // A DomainFullJid can never be the strict parent of another DomainFullJid. + return false; + } + + @Override + public Resourcepart getResourcepart() { + return resource; + } + + @Override + public BareJid asBareJid() { + return asDomainBareJid(); + } + + @Override + public Domainpart getDomain() { + return domainBareJid.getDomain(); + } + + @Override + public String asUnescapedString() { + return toString(); + } + + @Override + public EntityJid asEntityJidIfPossible() { + return null; + } + + @Override + public FullJid asFullJidIfPossible() { + return this; + } + + @Override + public Localpart getLocalpartOrNull() { + return null; + } +} diff --git a/src/org/jxmpp/jid/impl/DomainpartJid.java b/src/org/jxmpp/jid/impl/DomainpartJid.java new file mode 100644 index 0000000..716a1df --- /dev/null +++ b/src/org/jxmpp/jid/impl/DomainpartJid.java @@ -0,0 +1,162 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.impl; + +import org.jxmpp.JxmppContext; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.EntityJid; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.parts.Domainpart; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.stringprep.XmppStringprepException; + +public final class DomainpartJid extends AbstractJid implements DomainBareJid { + + /** + * + */ + private static final long serialVersionUID = 1L; + + final Domainpart domain; + + DomainpartJid(String domain, JxmppContext context) throws XmppStringprepException { + this(Domainpart.from(domain, context)); + } + + DomainpartJid(Domainpart domain) { + this.domain = requireNonNull(domain, "The Domainpart must not be null"); + } + + @Override + public Domainpart getDomain() { + return domain; + } + + @Override + public String toString() { + // Prefer the cached version over the domain.toString() one, since the cached version may + // also be the internalized representation of the String. Which, e.g. provides benefits when + // comparing JIDs. + if (cache != null) { + return cache; + } + cache = domain.toString(); + return cache; + } + + @Override + public String asUnescapedString() { + // No un-escaping necessary for DomainpartJid + return toString(); + } + + @Override + public DomainBareJid asDomainBareJid() { + return this; + } + + @Override + public boolean hasNoResource() { + return true; + } + + @Override + public EntityBareJid asEntityBareJidIfPossible() { + return null; + } + + @Override + public EntityFullJid asEntityFullJidIfPossible() { + return null; + } + + @Override + public DomainFullJid asDomainFullJidIfPossible() { + return null; + } + + @Override + public boolean isParentOf(EntityBareJid bareJid) { + return domain.equals(bareJid.getDomain()); + } + + @Override + public boolean isParentOf(EntityFullJid fullJid) { + return domain.equals(fullJid.getDomain()); + } + + @Override + public boolean isParentOf(DomainBareJid domainBareJid) { + return domain.equals(domainBareJid.getDomain()); + } + + @Override + public boolean isParentOf(DomainFullJid domainFullJid) { + return domain.equals(domainFullJid.getDomain()); + } + + @Override + public boolean isStrictParentOf(EntityBareJid bareJid) { + return isParentOf(bareJid); + } + + @Override + public boolean isStrictParentOf(EntityFullJid fullJid) { + return isParentOf(fullJid); + } + + @Override + public boolean isStrictParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(DomainFullJid domainFullJid) { + return isParentOf(domainFullJid); + } + + @Override + public BareJid asBareJid() { + return this; + } + + @Override + public EntityJid asEntityJidIfPossible() { + return null; + } + + @Override + public FullJid asFullJidIfPossible() { + return null; + } + + @Override + public Resourcepart getResourceOrNull() { + return null; + } + + @Override + public Localpart getLocalpartOrNull() { + return null; + } + +} diff --git a/src/org/jxmpp/jid/impl/JidCreate.java b/src/org/jxmpp/jid/impl/JidCreate.java new file mode 100644 index 0000000..be5f60c --- /dev/null +++ b/src/org/jxmpp/jid/impl/JidCreate.java @@ -0,0 +1,1684 @@ +/** + * + * Copyright © 2014-2022 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.impl; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +import org.jxmpp.JxmppContext; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.EntityJid; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.jid.parts.Domainpart; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.stringprep.XmppStringprep; +import org.jxmpp.stringprep.XmppStringprepException; +import org.jxmpp.util.cache.Cache; +import org.jxmpp.util.cache.LruCache; +import org.jxmpp.util.XmppStringUtils; + +/** + * API to create JIDs (XMPP addresses) from Strings and CharSequences. + *

+ * If the input was user generated, e.g. captured from some sort of user + * interface, {@link #fromUnescaped(String)} should be used instead. This allows + * the user to enter unescaped JID values. You can use + * {@link org.jxmpp.jid.util.JidUtil#isValidEntityBareJid(CharSequence)} to + * query, e.g. while the user it entering it, if a given CharSequence is a valid + * bare JID. + *

+ *

+ * JIDs created from input received from an XMPP source should use + * {@link #from(String)}. + *

+ *

+ * JidCreate uses caches for efficient Jid construction, But it's not guaranteed + * that the same String or CharSequence will yield the same Jid instance. + *

+ * + * @see Jid + */ +public class JidCreate { + + private static class JidStringAndStringprep { + private final String jidString; + private final XmppStringprep stringprep; + + private JidStringAndStringprep(String jidString, JxmppContext context) { + this(jidString, context.xmppStringprep); + } + + private JidStringAndStringprep(String jidString, XmppStringprep stringprep) { + this.jidString = jidString; + this.stringprep = stringprep; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof JidStringAndStringprep)) + return false; + + JidStringAndStringprep otherJidStringAndStringprep = (JidStringAndStringprep) other; + return jidString.equals(otherJidStringAndStringprep.jidString) && stringprep.equals(otherJidStringAndStringprep.stringprep); + } + + private transient Integer hashCode; + + @Override + public int hashCode() { + if (hashCode == null) { + int result = 17; + result = 31 * result + jidString.hashCode(); + result = 31 * result + stringprep.hashCode(); + hashCode = result; + } + return hashCode; + } + } + + private static final Cache JID_CACHE = new LruCache<>(100); + private static final Cache BAREJID_CACHE = new LruCache<>(100); + private static final Cache ENTITYJID_CACHE = new LruCache<>(100); + private static final Cache FULLJID_CACHE = new LruCache<>(100); + private static final Cache ENTITY_BAREJID_CACHE = new LruCache<>(100); + private static final Cache ENTITY_FULLJID_CACHE = new LruCache<>(100); + private static final Cache DOMAINJID_CACHE = new LruCache<>(100); + private static final Cache DOMAINRESOURCEJID_CACHE = new LruCache<>(100); + + /** + * Get a {@link Jid} from the given parts. + *

+ * Only the domainpart is required. + *

+ * + * @param localpart a optional localpart. + * @param domainpart a required domainpart. + * @param resource a optional resourcepart. + * @return a JID which consists of the given parts. + * @throws XmppStringprepException if an error occurs. + */ + public static Jid from(CharSequence localpart, CharSequence domainpart, CharSequence resource) + throws XmppStringprepException { + return from(localpart.toString(), domainpart.toString(), resource.toString()); + } + + /** + * Get a {@link Jid} from the given parts. + *

+ * Only the domainpart is required. + *

+ * + * @param localpart a optional localpart. + * @param domainpart a required domainpart. + * @param resource a optional resourcepart. + * @return a JID which consists of the given parts. + * @throws XmppStringprepException if an error occurs. + */ + public static Jid from(String localpart, String domainpart, String resource) throws XmppStringprepException { + return from(localpart, domainpart, resource, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link Jid} from the given parts. + *

+ * Only the domainpart is required. + *

+ * + * @param localpart a optional localpart. + * @param domainpart a required domainpart. + * @param resource a optional resourcepart. + * @param context the JXMPP context. + * @return a JID which consists of the given parts. + * @throws XmppStringprepException if an error occurs. + */ + public static Jid from(String localpart, String domainpart, String resource, JxmppContext context) throws XmppStringprepException { + String jid = XmppStringUtils.completeJidFrom(localpart, domainpart, resource); + return from(localpart, domainpart, resource, jid, context); + } + + /** + * Get a {@link Jid} from the given parts. + + * + * @param localpart a optional localpart. + * @param domainpart a required domainpart. + * @param resource a optional resourcepart. + * @param jidString the raw String of the as parsed. + * @param context the JXMPP context. + * @return a JID which consists of the given parts. + * @throws XmppStringprepException if an error occurs. + */ + private static Jid from(String localpart, String domainpart, String resource, String jidString, JxmppContext context) throws XmppStringprepException { + // Every JID must come with an domainpart. + if (domainpart.isEmpty()) { + throw XmppStringprepException.MissingDomainpart.from(localpart, resource); + } + + // The provided jidString must be equal to the assembled parts. + assert jidString.equals(XmppStringUtils.completeJidFrom(localpart, domainpart, resource)); + + Jid jid; + + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jidString, context); + } + if (jidStringAndStringprep != null) { + jid = JID_CACHE.lookup(jidStringAndStringprep); + if (jid != null) { + return jid; + } + } + + jid = null; + if (localpart != null && resource != null) { + jid = new LocalDomainAndResourcepartJid(localpart, domainpart, resource, context); + } else if (localpart != null && resource == null) { + jid = new LocalAndDomainpartJid(localpart, domainpart, context); + } else if (localpart == null && resource == null) { + jid = new DomainpartJid(domainpart, context); + } else if (localpart == null && resource != null) { + jid = new DomainAndResourcepartJid(domainpart, resource, context); + } + assert jid != null; + + if (jidStringAndStringprep != null) { + JID_CACHE.put(jidStringAndStringprep, jid); + } + return jid; + } + + /** + * Like {@link #from(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link Jid} + * @return the {@link Jid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #from(String) + * @since 0.6.2 + */ + public static Jid fromOrThrowUnchecked(CharSequence cs) { + try { + return from(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link Jid} from a CharSequence. + * + * @param jid the input CharSequence. + * @return the Jid represented by the input CharSequence. + * @throws XmppStringprepException if an error occurs. + * @see #from(String) + */ + public static Jid from(CharSequence jid) throws XmppStringprepException { + return from(jid.toString()); + } + + /** + * Get a {@link Jid} from the given String. + * + * @param jidString the input String. + * @return the Jid represented by the input String. + * @throws XmppStringprepException if an error occurs. + * @see #from(CharSequence) + */ + public static Jid from(String jidString) throws XmppStringprepException { + return from(jidString, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link Jid} from the given String. + * + * @param jidString the input String. + * @param context the JXMPP context. + * @return the Jid represented by the input String. + * @throws XmppStringprepException if an error occurs. + * @see #from(CharSequence) + */ + public static Jid from(String jidString, JxmppContext context) throws XmppStringprepException { + String localpart = XmppStringUtils.parseLocalpart(jidString); + String domainpart = XmppStringUtils.parseDomain(jidString); + String resource = XmppStringUtils.parseResource(jidString); + try { + return from(localpart, domainpart, resource, jidString, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jidString, e); + } + } + + /** + * Get a {@link Jid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static Jid fromOrNull(CharSequence cs) { + try { + return from(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Like {@link #fromUnescaped(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link Jid} + * @return the {@link Jid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #fromUnescaped(CharSequence) + * @since 0.6.2 + */ + public static Jid fromUnescapedOrThrowUnchecked(CharSequence cs) { + try { + return fromUnescaped(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link Jid} from the given unescaped CharSequence. + * + * @param unescapedJid an unescaped CharSequence representing a JID. + * @return a JID. + * @throws XmppStringprepException if an error occurs. + */ + public static Jid fromUnescaped(CharSequence unescapedJid) throws XmppStringprepException { + return fromUnescaped(unescapedJid.toString()); + } + + /** + * Get a {@link Jid} from the given unescaped String. + * + * @param unescapedJidString a unescaped String representing a JID. + * @return a JID. + * @throws XmppStringprepException if an error occurs. + */ + public static Jid fromUnescaped(String unescapedJidString) throws XmppStringprepException { + String localpart = XmppStringUtils.parseLocalpart(unescapedJidString); + // Some as from(String), but we escape the localpart + localpart = XmppStringUtils.escapeLocalpart(localpart); + + String domainpart = XmppStringUtils.parseDomain(unescapedJidString); + String resource = XmppStringUtils.parseResource(unescapedJidString); + try { + return from(localpart, domainpart, resource); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(unescapedJidString, e); + } + } + + /** + * Get a {@link Jid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static Jid fromUnescapedOrNull(CharSequence cs) { + try { + return fromUnescaped(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link Jid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded JID. + * @return a JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static Jid fromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs); + return from(decoded); + } + + /** + * Like {@link #bareFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link BareJid} + * @return the {@link BareJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #bareFrom(CharSequence) + * @since 0.6.2 + */ + public static BareJid bareFromOrThrowUnchecked(CharSequence cs) { + try { + return bareFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link BareJid} representing the given CharSequence. + * + * @param jid the input CharSequence. + * @return a bare JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static BareJid bareFrom(CharSequence jid) throws XmppStringprepException { + return bareFrom(jid.toString()); + } + + /** + * Get a {@link BareJid} representing the given String. + * + * @param jid the input String. + * @return a bare JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static BareJid bareFrom(String jid) throws XmppStringprepException { + return bareFrom(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link BareJid} representing the given String. + * + * @param jid the input String. + * @param context the JXMPP context. + * @return a bare JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static BareJid bareFrom(String jid, JxmppContext context) throws XmppStringprepException { + BareJid bareJid; + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jid, context); + } + + if (jidStringAndStringprep != null) { + bareJid = BAREJID_CACHE.lookup(jidStringAndStringprep); + if (bareJid != null) { + return bareJid; + } + } + + String localpart = XmppStringUtils.parseLocalpart(jid); + String domainpart = XmppStringUtils.parseDomain(jid); + try { + if (localpart == null || localpart.length() == 0) { + bareJid = new DomainpartJid(domainpart, context); + } else { + bareJid = new LocalAndDomainpartJid(localpart, domainpart, context); + } + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jid, e); + } + + if (jidStringAndStringprep != null) { + BAREJID_CACHE.put(jidStringAndStringprep, bareJid); + } + return bareJid; + } + + /** + * Get a {@link BareJid} constructed from the optionally given {@link Localpart} and {link DomainBareJid}. + * + * @param localpart a optional localpart. + * @param domainBareJid a domain bare JID. + * @return a bare JID. + */ + public static BareJid bareFrom(Localpart localpart, DomainBareJid domainBareJid) { + return bareFrom(localpart, domainBareJid.getDomain()); + } + + /** + * Get a {@link BareJid} constructed from the optionally given {@link Localpart} and {@link Domainpart}. + * + * @param localpart a optional localpart. + * @param domain a domainpart. + * @return a bare JID constructed from the given parts. + */ + public static BareJid bareFrom(Localpart localpart, Domainpart domain) { + if (localpart != null) { + return new LocalAndDomainpartJid(localpart, domain); + } else { + return new DomainpartJid(domain); + } + } + + /** + * Get a {@link BareJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static BareJid bareFromOrNull(CharSequence cs) { + try { + return bareFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link BareJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded bare JID. + * @return a bare JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static BareJid bareFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs.toString()); + return bareFrom(decoded); + } + + /** + * Like {@link #fullFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link FullJid} + * @return the {@link FullJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #fullFrom(CharSequence) + * @since 0.6.2 + */ + public static FullJid fullFromOrThrowUnchecked(CharSequence cs) { + try { + return fullFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link FullJid} representing the given CharSequence. + * + * @param jid a CharSequence representing a JID. + * @return a full JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static FullJid fullFrom(CharSequence jid) throws XmppStringprepException { + return fullFrom(jid.toString()); + } + + /** + * Get a {@link FullJid} representing the given String. + * + * @param jid the JID's String. + * @return a full JID representing the input String. + * @throws XmppStringprepException if an error occurs. + */ + public static FullJid fullFrom(String jid) throws XmppStringprepException { + return fullFrom(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link FullJid} representing the given String. + * + * @param jid the JID's String. + * @param context the JXMPP context. + * @return a full JID representing the input String. + * @throws XmppStringprepException if an error occurs. + */ + public static FullJid fullFrom(String jid, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jid, context); + } + + FullJid fullJid; + if (jidStringAndStringprep != null) { + fullJid = FULLJID_CACHE.lookup(jidStringAndStringprep); + if (fullJid != null) { + return fullJid; + } + } + + String localpart = XmppStringUtils.parseLocalpart(jid); + String domainpart = XmppStringUtils.parseDomain(jid); + String resource = XmppStringUtils.parseResource(jid); + try { + fullJid = fullFrom(localpart, domainpart, resource); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jid, e); + } + + if (jidStringAndStringprep != null) { + FULLJID_CACHE.put(jidStringAndStringprep, fullJid); + } + + return fullJid; + } + + /** + * Get a {@link FullJid} constructed from the given parts. + * + * @param localpart a optional localpart. + * @param domainpart a domainpart. + * @param resource a resourcepart. + * @return a full JID. + * @throws XmppStringprepException if an error occurs. + */ + public static FullJid fullFrom(String localpart, String domainpart, String resource) throws XmppStringprepException { + return fullFrom(localpart, domainpart, resource, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link FullJid} constructed from the given parts. + * + * @param localpart a optional localpart. + * @param domainpart a domainpart. + * @param resource a resourcepart. + * @param context the JXMPP context. + * @return a full JID. + * @throws XmppStringprepException if an error occurs. + */ + public static FullJid fullFrom(String localpart, String domainpart, String resource, JxmppContext context) throws XmppStringprepException { + FullJid fullJid; + try { + if (localpart == null || localpart.length() == 0) { + fullJid = new DomainAndResourcepartJid(domainpart, resource, context); + } else { + fullJid = new LocalDomainAndResourcepartJid(localpart, domainpart, resource, context); + } + } catch (XmppStringprepException e) { + throw new XmppStringprepException(localpart + '@' + domainpart + '/' + resource, e); + } + return fullJid; + } + + /** + * Get a {@link FullJid} constructed from the given parts. + * + * @param localpart a optional localpart. + * @param domainBareJid a domain bare JID. + * @param resource a resourcepart + * @return a full JID. + */ + public static FullJid fullFrom(Localpart localpart, DomainBareJid domainBareJid, Resourcepart resource) { + return fullFrom(localpart, domainBareJid.getDomain(), resource); + } + + /** + * Get a {@link FullJid} constructed from the given parts. + * + * @param localpart the optional localpart. + * @param domainpart the domainpart. + * @param resource the resourcepart. + * @return a full JID. + */ + public static FullJid fullFrom(Localpart localpart, Domainpart domainpart, Resourcepart resource) { + return fullFrom(entityBareFrom(localpart, domainpart), resource); + } + + /** + * Get a {@link FullJid} constructed from a {@link BareJid} and a {@link Resourcepart}. + * + * @param bareJid a entity bare JID. + * @param resource a resourcepart. + * @return a full JID. + */ + public static FullJid fullFrom(BareJid bareJid, Resourcepart resource) { + if (bareJid.isEntityBareJid()) { + EntityBareJid entityBareJid = (EntityBareJid) bareJid; + return new LocalDomainAndResourcepartJid(entityBareJid, resource); + } else { + DomainBareJid domainBareJid = (DomainBareJid) bareJid; + return new DomainAndResourcepartJid(domainBareJid, resource); + } + } + + /** + * Get a {@link FullJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static FullJid fullFromOrNull(CharSequence cs) { + try { + return fullFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link FullJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded full JID. + * @return a full JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static FullJid fullFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs); + return fullFrom(decoded); + } + + /** + * Like {@link #entityFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityJid} + * @return the {@link EntityJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityFrom(CharSequence) + * @since 0.6.2 + */ + public static EntityJid entityFromOrThrowUnchecked(CharSequence cs) { + try { + return entityFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jid the JID's string. + * @return an entity JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFrom(CharSequence jid) throws XmppStringprepException { + return entityFrom(jid.toString()); + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jidString the JID's string. + * @return an entity JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFrom(String jidString) throws XmppStringprepException { + return entityFrom(jidString, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jidString the JID's string. + * @param context the JXMPP context. + * @return an entity JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFrom(String jidString, JxmppContext context) throws XmppStringprepException { + return entityFrom(jidString, false, context); + } + + /** + * Like {@link #entityFromUnescaped(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityJid} + * @return the {@link EntityJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityFromUnescaped(CharSequence) + * @since 0.6.2 + */ + public static EntityJid entityFromUnescapedOrThrowUnchecked(CharSequence cs) { + return entityFromUnescapedOrThrowUnchecked(cs, JxmppContext.getDefaultContext()); + } + + /** + * Like {@link #entityFromUnescaped(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityJid} + * @param context the JXMPP context. + * @return the {@link EntityJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityFromUnescaped(CharSequence) + * @since 0.6.2 + */ + public static EntityJid entityFromUnescapedOrThrowUnchecked(CharSequence cs, JxmppContext context) { + try { + return entityFromUnescaped(cs, context); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jid the JID. + * @return an entity JID representing the given input.. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFromUnescaped(CharSequence jid) throws XmppStringprepException { + return entityFromUnescaped(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jid the JID. + * @param context the JXMPP context. + * @return an entity JID representing the given input. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFromUnescaped(CharSequence jid, JxmppContext context) throws XmppStringprepException { + return entityFromUnescaped(jid.toString(), context); + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jidString the JID's string. + * @return an entity JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFromUnescaped(String jidString) throws XmppStringprepException { + return entityFromUnescaped(jidString, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jidString the JID's string. + * @param context the JXMPP context. + * @return an entity JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityJid entityFromUnescaped(String jidString, JxmppContext context) throws XmppStringprepException { + return entityFrom(jidString, true, context); + } + + /** + * Get a {@link EntityJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + * @deprecated use {@link #entityFromUnescapedOrNull(CharSequence)} instead. + */ + // TODO: remove in jxmpp 1.1 + @Deprecated + public static EntityJid entityFromUnesacpedOrNull(CharSequence cs) { + return entityFromUnescapedOrNull(cs); + } + + /** + * Get a {@link EntityJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static EntityJid entityFromUnescapedOrNull(CharSequence cs) { + try { + return entityFromUnescaped(cs.toString()); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link EntityJid} representing the given String. + * + * @param jidString the JID's string. + * @param unescaped if the JID string is unescaped. + * @return an entity JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + private static EntityJid entityFrom(String jidString, boolean unescaped, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jidString, context); + } + + EntityJid entityJid; + if (jidStringAndStringprep != null) { + entityJid = ENTITYJID_CACHE.lookup(jidStringAndStringprep); + if (entityJid != null) { + return entityJid; + } + } + String localpartString = XmppStringUtils.parseLocalpart(jidString); + if (localpartString == null) { + throw new XmppStringprepException("Does not contain a localpart", jidString); + } + Localpart localpart; + try { + if (unescaped) { + localpart = Localpart.fromUnescaped(localpartString); + } else { + localpart = Localpart.from(localpartString); + } + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jidString, e); + } + + String domainpartString = XmppStringUtils.parseDomain(jidString); + Domainpart domainpart; + try { + domainpart = Domainpart.from(domainpartString); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jidString, e); + } + + String resourceString = XmppStringUtils.parseResource(jidString); + if (resourceString != null) { + Resourcepart resourcepart; + try { + resourcepart = Resourcepart.from(resourceString); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jidString, e); + } + entityJid = entityFullFrom(localpart, domainpart, resourcepart); + } else { + entityJid = entityBareFrom(localpart, domainpart); + } + + if (jidStringAndStringprep != null) { + ENTITYJID_CACHE.put(jidStringAndStringprep, entityJid); + } + return entityJid; + } + + /** + * Get a {@link EntityJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static EntityJid entityFromOrNull(CharSequence cs) { + try { + return entityFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link EntityJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded entity JID. + * @return an entity JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static EntityJid entityFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs); + return entityFrom(decoded); + } + + /** + * Like {@link #entityBareFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityBareJid} + * @return the {@link EntityBareJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityBareFrom(CharSequence) + * @since 0.6.2 + */ + public static EntityBareJid entityBareFromOrThrowUnchecked(CharSequence cs) { + try { + return entityBareFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link EntityBareJid} representing the given CharSequence. + * + * @param jid the input CharSequence. + * @return a bare JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFrom(CharSequence jid) throws XmppStringprepException { + return entityBareFrom(jid.toString()); + } + + /** + * Get a {@link EntityBareJid} representing the given String. + * + * @param jid the input String. + * @return a bare JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFrom(String jid) throws XmppStringprepException { + return entityBareFrom(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityBareJid} representing the given String. + * + * @param jid the input String. + * @param context the JXMPP context. + * @return a bare JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFrom(String jid, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jid, context); + } + + EntityBareJid bareJid; + if (jidStringAndStringprep != null) { + bareJid = ENTITY_BAREJID_CACHE.lookup(jidStringAndStringprep); + if (bareJid != null) { + return bareJid; + } + } + + String localpart = XmppStringUtils.parseLocalpart(jid); + String domainpart = XmppStringUtils.parseDomain(jid); + try { + bareJid = new LocalAndDomainpartJid(localpart, domainpart, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jid, e); + } + + if (jidStringAndStringprep != null) { + ENTITY_BAREJID_CACHE.put(jidStringAndStringprep, bareJid); + } + return bareJid; + } + + /** + * Like {@link #entityBareFromUnescaped(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityBareJid} + * @return the {@link EntityBareJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityBareFromUnescaped(CharSequence) + * @since 0.6.2 + */ + public static EntityBareJid entityBareFromUnescapedOrThrowUnchecked(CharSequence cs) { + try { + return entityBareFromUnescaped(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link EntityBareJid} representing the given unescaped CharSequence. + * + * @param unescapedJid the input CharSequence. + * @return a bare JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFromUnescaped(CharSequence unescapedJid) throws XmppStringprepException { + return entityBareFromUnescaped(unescapedJid.toString()); + } + + /** + * Get a {@link EntityBareJid} representing the given unescaped String. + * + * @param unescapedJidString the input String. + * @return a bare JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFromUnescaped(String unescapedJidString) throws XmppStringprepException { + return entityBareFromUnescaped(unescapedJidString, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityBareJid} representing the given unescaped String. + * + * @param unescapedJidString the input String. + * @param context the JXMPP context. + * @return a bare JID representing the given String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFromUnescaped(String unescapedJidString, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(unescapedJidString, context); + } + + EntityBareJid bareJid; + if (jidStringAndStringprep != null) { + bareJid = ENTITY_BAREJID_CACHE.lookup(jidStringAndStringprep); + if (bareJid != null) { + return bareJid; + } + } + + String localpart = XmppStringUtils.parseLocalpart(unescapedJidString); + // Some as from(String), but we escape the localpart + localpart = XmppStringUtils.escapeLocalpart(localpart); + + String domainpart = XmppStringUtils.parseDomain(unescapedJidString); + try { + bareJid = new LocalAndDomainpartJid(localpart, domainpart, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(unescapedJidString, e); + } + + if (jidStringAndStringprep != null) { + ENTITY_BAREJID_CACHE.put(jidStringAndStringprep, bareJid); + } + + return bareJid; + } + + /** + * Get a {@link EntityBareJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static EntityBareJid entityBareFromUnescapedOrNull(CharSequence cs) { + try { + return entityBareFromUnescaped(cs.toString()); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link EntityBareJid} constructed from the given {@link Localpart} and {link DomainBareJid}. + * + * @param localpart a localpart. + * @param domainBareJid a domain bare JID. + * @return a bare JID. + */ + public static EntityBareJid entityBareFrom(Localpart localpart, DomainBareJid domainBareJid) { + return entityBareFrom(localpart, domainBareJid.getDomain()); + } + + /** + * Get a {@link EntityBareJid} constructed from the given {@link Localpart} and {@link Domainpart}. + * + * @param localpart a localpart. + * @param domain a domainpart. + * @return a bare JID constructed from the given parts. + */ + public static EntityBareJid entityBareFrom(Localpart localpart, Domainpart domain) { + return new LocalAndDomainpartJid(localpart, domain); + } + + /** + * Get a {@link EntityBareJid} constructed from the given {@link Localpart} and {@link Domainpart}. + * + * @param localpart a localpart. + * @param domain a domainpart. + * @return a bare JID constructed from the given parts. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityBareJid entityBareFrom(CharSequence localpart, Domainpart domain) throws XmppStringprepException { + return new LocalAndDomainpartJid(Localpart.fromUnescaped(localpart), domain); + } + + /** + * Get a {@link EntityBareJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static EntityBareJid entityBareFromOrNull(CharSequence cs) { + try { + return entityBareFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link EntityBareJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded entity bare JID. + * @return an entity bare JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static EntityBareJid entityBareFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs); + return entityBareFrom(decoded); + } + + /** + * Like {@link #entityFullFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityFullJid} + * @return the {@link EntityFullJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityFullFrom(CharSequence) + * @since 0.6.2 + */ + public static EntityFullJid entityFullFromOrThrowUnchecked(CharSequence cs) { + try { + return entityFullFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link EntityFullJid} representing the given CharSequence. + * + * @param jid a CharSequence representing a JID. + * @return a full JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFrom(CharSequence jid) throws XmppStringprepException { + return entityFullFrom(jid.toString(), JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityFullJid} representing the given CharSequence. + * + * @param jid a CharSequence representing a JID. + * @return a full JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFrom(String jid) throws XmppStringprepException { + return entityFullFrom(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityFullJid} representing the given String. + * + * @param jid the JID's String. + * @param context the JXMPP context. + * @return a full JID representing the input String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFrom(String jid, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jid, context); + } + + EntityFullJid fullJid; + if (jidStringAndStringprep != null) { + fullJid = ENTITY_FULLJID_CACHE.lookup(jidStringAndStringprep); + if (fullJid != null) { + return fullJid; + } + } + + String localpart = XmppStringUtils.parseLocalpart(jid); + String domainpart = XmppStringUtils.parseDomain(jid); + String resource = XmppStringUtils.parseResource(jid); + try { + fullJid = entityFullFrom(localpart, domainpart, resource); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jid, e); + } + + if (jidStringAndStringprep != null) { + ENTITY_FULLJID_CACHE.put(jidStringAndStringprep, fullJid); + } + + return fullJid; + } + + /** + * Get a {@link EntityFullJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static EntityFullJid entityFullFromOrNull(CharSequence cs) { + try { + return entityFullFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Like {@link #entityFullFromUnescaped(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityFullJid} + * @return the {@link EntityFullJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #entityFullFromUnescaped(CharSequence) + * @since 0.6.2 + */ + public static EntityFullJid entityFullFromUnescapedOrThrowUnchecked(CharSequence cs) { + try { + return entityFullFromUnescaped(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link EntityFullJid} representing the given unescaped CharSequence. + * + * @param unescapedJid a CharSequence representing a JID. + * @return a full JID representing the given CharSequence. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFromUnescaped(CharSequence unescapedJid) throws XmppStringprepException { + return entityFullFromUnescaped(unescapedJid.toString()); + } + + /** + * Get a {@link EntityFullJid} representing the given unescaped String. + * + * @param unescapedJidString the JID's String. + * @return a full JID representing the input String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFromUnescaped(String unescapedJidString) throws XmppStringprepException { + return entityFullFromUnescaped(unescapedJidString, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityFullJid} representing the given unescaped String. + * + * @param unescapedJidString the JID's String. + * @param context the JXMPP context. + * @return a full JID representing the input String. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFromUnescaped(String unescapedJidString, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(unescapedJidString, context); + } + + EntityFullJid fullJid; + if (jidStringAndStringprep != null) { + fullJid = ENTITY_FULLJID_CACHE.lookup(jidStringAndStringprep); + if (fullJid != null) { + return fullJid; + } + } + + String localpart = XmppStringUtils.parseLocalpart(unescapedJidString); + // Some as from(String), but we escape the localpart + localpart = XmppStringUtils.escapeLocalpart(localpart); + + String domainpart = XmppStringUtils.parseDomain(unescapedJidString); + String resource = XmppStringUtils.parseResource(unescapedJidString); + try { + fullJid = new LocalDomainAndResourcepartJid(localpart, domainpart, resource, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(unescapedJidString, e); + } + + if (jidStringAndStringprep != null) { + ENTITY_FULLJID_CACHE.put(jidStringAndStringprep, fullJid); + } + + return fullJid; + } + + /** + * Get a {@link EntityFullJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static EntityFullJid entityFullFromUnescapedOrNull(CharSequence cs) { + try { + return entityFullFromUnescaped(cs.toString()); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link EntityFullJid} constructed from the given parts. + * + * @param localpart a localpart. + * @param domainpart a domainpart. + * @param resource a resourcepart. + * @return a full JID. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFrom(String localpart, String domainpart, String resource) throws XmppStringprepException { + return entityFullFrom(localpart, domainpart, resource, JxmppContext.getDefaultContext()); + } + + /** + * Get a {@link EntityFullJid} constructed from the given parts. + * + * @param localpart a localpart. + * @param domainpart a domainpart. + * @param resource a resourcepart. + * @param context the JXMPP context. + * @return a full JID. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFrom(String localpart, String domainpart, String resource, JxmppContext context) throws XmppStringprepException { + EntityFullJid fullJid; + try { + fullJid = new LocalDomainAndResourcepartJid(localpart, domainpart, resource, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(localpart + '@' + domainpart + '/' + resource, e); + } + return fullJid; + } + + /** + * Get a {@link EntityFullJid} constructed from the given parts. + * + * @param localpart a localpart. + * @param domainBareJid a domain bare JID.. + * @param resource a resourcepart + * @return a full JID. + */ + public static EntityFullJid entityFullFrom(Localpart localpart, DomainBareJid domainBareJid, Resourcepart resource) { + return entityFullFrom(localpart, domainBareJid.getDomain(), resource); + } + + /** + * Get a {@link EntityFullJid} constructed from the given parts. + * + * @param localpart the localpart. + * @param domainpart the domainpart. + * @param resource the resourcepart. + * @return a full JID. + */ + public static EntityFullJid entityFullFrom(Localpart localpart, Domainpart domainpart, Resourcepart resource) { + return entityFullFrom(entityBareFrom(localpart, domainpart), resource); + } + + /** + * Get a {@link EntityFullJid} constructed from the given parts. + * + * @param localpart the localpart. + * @param domainpart the domainpart. + * @param resource the resourcepart. + * @return a full JID. + * @throws XmppStringprepException if an error occurs. + */ + public static EntityFullJid entityFullFrom(CharSequence localpart, Domainpart domainpart, CharSequence resource) throws XmppStringprepException { + return entityFullFrom(entityBareFrom(Localpart.fromUnescaped(localpart), domainpart), Resourcepart.from(resource)); + } + + /** + * Get a {@link EntityFullJid} constructed from a {@link EntityBareJid} and a {@link Resourcepart}. + * + * @param bareJid a bare JID. + * @param resource a resourcepart. + * @return a full JID. + */ + public static EntityFullJid entityFullFrom(EntityBareJid bareJid, Resourcepart resource) { + return new LocalDomainAndResourcepartJid(bareJid, resource); + } + + /** + * Get a {@link EntityFullJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded entity full JID. + * @return an entity full JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static EntityFullJid entityFullFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs); + return entityFullFrom(decoded); + } + + /** + * Like {@link #domainBareFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link EntityFullJid} + * @return the {@link EntityFullJid} if no exception occurs + * @see #from(String) + * @since 0.6.2 + */ + public static DomainBareJid domainBareFromOrThrowUnchecked(CharSequence cs) { + try { + return domainBareFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a domain bare JID. + * + * @param jid the JID CharSequence. + * @return a domain bare JID. + * @throws XmppStringprepException if an error occurs. + */ + public static DomainBareJid domainBareFrom(CharSequence jid) throws XmppStringprepException { + return domainBareFrom(jid.toString()); + } + + /** + * Get a domain bare JID. + * + * @param jid the JID String. + * @return a domain bare JID. + * @throws XmppStringprepException if an error occurs. + */ + public static DomainBareJid domainBareFrom(String jid) throws XmppStringprepException { + return domainBareFrom(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a domain bare JID. + * + * @param jid the JID String. + * @param context the JXMPP context. + * @return a domain bare JID. + * @throws XmppStringprepException if an error occurs. + */ + public static DomainBareJid domainBareFrom(String jid, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jid, context); + } + + DomainBareJid domainJid; + if (jidStringAndStringprep != null) { + domainJid = DOMAINJID_CACHE.lookup(jidStringAndStringprep); + if (domainJid != null) { + return domainJid; + } + } + + String domain = XmppStringUtils.parseDomain(jid); + try { + domainJid = new DomainpartJid(domain, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jid, e); + } + + if (context.isCachingEnabled()) { + DOMAINJID_CACHE.put(jidStringAndStringprep, domainJid); + } + return domainJid; + } + + /** + * Get a {@link DomainBareJid} consisting of the given {@link Domainpart}. + * + * @param domainpart the domainpart. + * @return a domain bare JID. + */ + public static DomainBareJid domainBareFrom(Domainpart domainpart) { + return new DomainpartJid(domainpart); + } + + /** + * Get a {@link DomainBareJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static DomainBareJid domainBareFromOrNull(CharSequence cs) { + try { + return domainBareFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link DomainBareJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded domain bare JID. + * @return a domain bare JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static DomainBareJid domainBareFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decode = urlDecode(cs); + return domainBareFrom(decode); + } + + /** + * Like {@link #domainFullFrom(CharSequence)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link DomainFullJid} + * @return the {@link DomainFullJid} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid JID + * @see #domainFullFrom(CharSequence) + * @since 0.6.2 + */ + public static DomainFullJid domainFullFromOrThrowUnchecked(CharSequence cs) { + try { + return domainFullFrom(cs); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a domain full JID from the given CharSequence. + * + * @param jid the JID. + * @return a domain full JID. + * @throws XmppStringprepException if an error happens. + */ + public static DomainFullJid domainFullFrom(CharSequence jid) throws XmppStringprepException { + return domainFullFrom(jid.toString()); + } + + /** + * Get a domain full JID from the given String. + * + * @param jid the JID. + * @return a DomainFullJid. + * @throws XmppStringprepException if an error happens. + */ + public static DomainFullJid domainFullFrom(String jid) throws XmppStringprepException { + return domainFullFrom(jid, JxmppContext.getDefaultContext()); + } + + /** + * Get a domain full JID from the given String. + * + * @param jid the JID. + * @param context the JXMPP context. + * @return a DomainFullJid. + * @throws XmppStringprepException if an error happens. + */ + public static DomainFullJid domainFullFrom(String jid, JxmppContext context) throws XmppStringprepException { + JidStringAndStringprep jidStringAndStringprep = null; + if (context.isCachingEnabled()) { + jidStringAndStringprep = new JidStringAndStringprep(jid, context); + } + + DomainFullJid domainResourceJid; + if (jidStringAndStringprep != null) { + domainResourceJid = DOMAINRESOURCEJID_CACHE.lookup(jidStringAndStringprep); + if (domainResourceJid != null) { + return domainResourceJid; + } + } + + String domain = XmppStringUtils.parseDomain(jid); + String resource = XmppStringUtils.parseResource(jid); + try { + domainResourceJid = new DomainAndResourcepartJid(domain, resource, context); + } catch (XmppStringprepException e) { + throw new XmppStringprepException(jid, e); + } + + if (jidStringAndStringprep != null) { + DOMAINRESOURCEJID_CACHE.put(jidStringAndStringprep, domainResourceJid); + } + + return domainResourceJid; + } + + /** + * Get a domain full JID. + * + * @param domainpart the domainpart. + * @param resource the resourcepart. + * @return a domain full JID. + */ + public static DomainFullJid domainFullFrom(Domainpart domainpart, Resourcepart resource) { + return domainFullFrom(domainBareFrom(domainpart), resource); + } + + /** + * Get a domain full JID. + * + * @param domainBareJid a domain bare JID. + * @param resource a resourcepart. + * @return a domain full JID. + */ + public static DomainFullJid domainFullFrom(DomainBareJid domainBareJid, Resourcepart resource) { + return new DomainAndResourcepartJid(domainBareJid, resource); + } + + /** + * Get a {@link DomainFullJid} from a given {@link CharSequence} or {@code null} if the input does not represent a JID. + * + * @param cs the input {@link CharSequence} + * @return a JID or {@code null} + */ + public static DomainFullJid domainFullFromOrNull(CharSequence cs) { + try { + return domainFullFrom(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link DomainFullJid} from an URL encoded CharSequence. + * + * @param cs a CharSequence representing an URL encoded domain full JID. + * @return a domain full JID + * @throws XmppStringprepException if an error occurs. + * @see URLDecoder + */ + public static DomainFullJid domainFullFromUrlEncoded(CharSequence cs) throws XmppStringprepException { + String decoded = urlDecode(cs); + return domainFullFrom(decoded); + } + + private static String urlDecode(CharSequence cs) { + try { + return URLDecoder.decode(cs.toString(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/org/jxmpp/jid/impl/LocalAndDomainpartJid.java b/src/org/jxmpp/jid/impl/LocalAndDomainpartJid.java new file mode 100644 index 0000000..845b6d7 --- /dev/null +++ b/src/org/jxmpp/jid/impl/LocalAndDomainpartJid.java @@ -0,0 +1,182 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.impl; + +import org.jxmpp.JxmppContext; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.EntityJid; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.parts.Domainpart; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.stringprep.XmppStringprepException; + + +public final class LocalAndDomainpartJid extends AbstractJid implements EntityBareJid { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final DomainBareJid domainBareJid; + private final Localpart localpart; + + private transient String unescapedCache; + + LocalAndDomainpartJid(String localpart, String domain, JxmppContext context) throws XmppStringprepException { + domainBareJid = new DomainpartJid(domain, context); + this.localpart = Localpart.from(localpart, context); + } + + LocalAndDomainpartJid(Localpart localpart, Domainpart domain) { + this.localpart = requireNonNull(localpart, "The Localpart must not be null"); + this.domainBareJid = new DomainpartJid(domain); + } + + @Override + public Localpart getLocalpart() { + return localpart; + } + + @Override + public String toString() { + if (cache != null) { + return cache; + } + cache = getLocalpart().toString() + '@' + domainBareJid.toString(); + return cache; + } + + @Override + public String asUnescapedString() { + if (unescapedCache != null) { + return unescapedCache; + } + unescapedCache = getLocalpart().asUnescapedString() + '@' + domainBareJid.toString(); + return unescapedCache; + } + + @Override + public EntityBareJid asEntityBareJidIfPossible() { + return this; + } + + @Override + public EntityFullJid asEntityFullJidIfPossible() { + return null; + } + + @Override + public DomainFullJid asDomainFullJidIfPossible() { + return null; + } + + @Override + public boolean isParentOf(EntityBareJid bareJid) { + return domainBareJid.equals(bareJid.getDomain()) && localpart.equals(bareJid.getLocalpart()); + } + + @Override + public boolean isParentOf(EntityFullJid fullJid) { + return isParentOf(fullJid.asBareJid()); + } + + @Override + public boolean isParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isParentOf(DomainFullJid domainFullJid) { + return false; + } + + @Override + public boolean isStrictParentOf(EntityBareJid bareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(EntityFullJid fullJid) { + return isParentOf(fullJid); + } + + @Override + public boolean isStrictParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(DomainFullJid domainFullJid) { + return false; + } + + @Override + public DomainBareJid asDomainBareJid() { + return domainBareJid; + } + + @Override + public Domainpart getDomain() { + return domainBareJid.getDomain(); + } + + @Override + public BareJid asBareJid() { + return this; + } + + @Override + public boolean hasNoResource() { + return true; + } + + @Override + public EntityJid asEntityJidIfPossible() { + return this; + } + + @Override + public FullJid asFullJidIfPossible() { + return null; + } + + @Override + public EntityBareJid asEntityBareJid() { + return this; + } + + @Override + public Resourcepart getResourceOrNull() { + return null; + } + + @Override + public Localpart getLocalpartOrNull() { + return getLocalpart(); + } + + @Override + public String asEntityBareJidString() { + return toString(); + } +} diff --git a/src/org/jxmpp/jid/impl/LocalDomainAndResourcepartJid.java b/src/org/jxmpp/jid/impl/LocalDomainAndResourcepartJid.java new file mode 100644 index 0000000..4f6be3b --- /dev/null +++ b/src/org/jxmpp/jid/impl/LocalDomainAndResourcepartJid.java @@ -0,0 +1,185 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.impl; + +import org.jxmpp.JxmppContext; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.EntityJid; +import org.jxmpp.jid.FullJid; +import org.jxmpp.jid.parts.Domainpart; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.stringprep.XmppStringprepException; + +public final class LocalDomainAndResourcepartJid extends AbstractJid implements EntityFullJid { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final EntityBareJid bareJid; + private final Resourcepart resource; + + private String unescapedCache; + + LocalDomainAndResourcepartJid(String localpart, String domain, String resource, JxmppContext context) throws XmppStringprepException { + this(new LocalAndDomainpartJid(localpart, domain, context), Resourcepart.from(resource, context)); + } + + LocalDomainAndResourcepartJid(EntityBareJid bareJid, Resourcepart resource) { + this.bareJid = requireNonNull(bareJid, "The EntityBareJid must not be null"); + this.resource = requireNonNull(resource, "The Resourcepart must not be null"); + } + + @Override + public String toString() { + if (cache != null) { + return cache; + } + cache = bareJid.toString() + '/' + resource; + return cache; + } + + @Override + public String asUnescapedString() { + if (unescapedCache != null) { + return unescapedCache; + } + unescapedCache = bareJid.asUnescapedString() + '/' + resource; + return unescapedCache; + } + + @Override + public EntityBareJid asEntityBareJid() { + return bareJid; + } + + @Override + public String asEntityBareJidString() { + return asEntityBareJid().toString(); + } + + @Override + public boolean hasNoResource() { + return false; + } + + @Override + public EntityBareJid asEntityBareJidIfPossible() { + return asEntityBareJid(); + } + + @Override + public EntityFullJid asEntityFullJidIfPossible() { + return this; + } + + @Override + public DomainFullJid asDomainFullJidIfPossible() { + return null; + } + + @Override + public Localpart getLocalpartOrNull() { + return bareJid.getLocalpart(); + } + + @Override + public Resourcepart getResourceOrNull() { + return getResourcepart(); + } + + @Override + public boolean isParentOf(EntityBareJid bareJid) { + return false; + } + + @Override + public boolean isParentOf(EntityFullJid fullJid) { + return this.equals(fullJid); + } + + @Override + public boolean isParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isParentOf(DomainFullJid domainFullJid) { + return false; + } + + @Override + public boolean isStrictParentOf(EntityBareJid bareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(EntityFullJid fullJid) { + return false; + } + + @Override + public boolean isStrictParentOf(DomainBareJid domainBareJid) { + return false; + } + + @Override + public boolean isStrictParentOf(DomainFullJid domainFullJid) { + return false; + } + + @Override + public DomainBareJid asDomainBareJid() { + return bareJid.asDomainBareJid(); + } + + @Override + public Resourcepart getResourcepart() { + return resource; + } + + @Override + public BareJid asBareJid() { + return asEntityBareJid(); + } + + @Override + public Domainpart getDomain() { + return bareJid.getDomain(); + } + + @Override + public Localpart getLocalpart() { + return bareJid.getLocalpart(); + } + + @Override + public EntityJid asEntityJidIfPossible() { + return this; + } + + @Override + public FullJid asFullJidIfPossible() { + return this; + } +} diff --git a/src/org/jxmpp/jid/impl/package-info.java b/src/org/jxmpp/jid/impl/package-info.java new file mode 100644 index 0000000..bf824ed --- /dev/null +++ b/src/org/jxmpp/jid/impl/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * JXMPP's implementation of {@link org.jxmpp.jid.Jid} and it's subtypes. + */ +package org.jxmpp.jid.impl; diff --git a/src/org/jxmpp/jid/package-info.java b/src/org/jxmpp/jid/package-info.java new file mode 100644 index 0000000..ee10d57 --- /dev/null +++ b/src/org/jxmpp/jid/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Interfaces and classes for XMPP Addresses (JIDs). + */ +package org.jxmpp.jid; diff --git a/src/org/jxmpp/jid/parts/Domainpart.java b/src/org/jxmpp/jid/parts/Domainpart.java new file mode 100644 index 0000000..832a533 --- /dev/null +++ b/src/org/jxmpp/jid/parts/Domainpart.java @@ -0,0 +1,108 @@ +/** + * + * Copyright © 2014-2018 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.parts; + +import org.jxmpp.JxmppContext; +import org.jxmpp.stringprep.XmppStringPrepUtil; +import org.jxmpp.stringprep.XmppStringprepException; + +/** + * A domainpart of an XMPP address (JID). + *

+ * You can create instances of this class from Strings using {@link #from(String)}. + *

+ * + * @see RFC 6122 § 2.2. Domainpart + */ +public class Domainpart extends Part { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private Domainpart(String domain) { + super(domain); + } + + /** + * Get a {@link Domainpart} from a given {@link CharSequence} or {@code null} if the input is not a valid domainpart. + * + * @param cs the input CharSequence + * @return a Domainpart or {@code null} + */ + public static Domainpart fromOrNull(CharSequence cs) { + try { + return from(cs.toString()); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Like {@link #from(String)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link Domainpart} + * @return the {@link Domainpart} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid {@link Domainpart} + * @see #from(String) + * @since 0.6.2 + */ + public static Domainpart fromOrThrowUnchecked(CharSequence cs) { + try { + return from(cs.toString()); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get the {@link Domainpart} representing the input String. + * + * @param domain the input String. + * @return the domainpart. + * @throws XmppStringprepException if an error occurs. + */ + public static Domainpart from(String domain) throws XmppStringprepException { + return from(domain, JxmppContext.getDefaultContext()); + } + + /** + * Get the {@link Domainpart} representing the input String. + * + * @param domain the input String. + * @param context the JXMPP context. + * @return the domainpart. + * @throws XmppStringprepException if an error occurs. + */ + public static Domainpart from(String domain, JxmppContext context) throws XmppStringprepException { + if (domain == null) { + throw new XmppStringprepException(domain, "Input 'domain' must not be null"); + } + // TODO cache + // RFC 6122 § 2.2 "If the domainpart includes a final character considered to be a label + // separator (dot) by [IDNA2003] or [DNS], this character MUST be stripped …" + if (domain.length() > 0 && domain.charAt(domain.length() - 1) == '.') { + domain = domain.substring(0, domain.length() - 1); + } + domain = XmppStringPrepUtil.domainprep(domain, context); + // First prep the String, then assure the limits of the *result* + assertNotLongerThan1023BytesOrEmpty(domain); + return new Domainpart(domain); + } +} diff --git a/src/org/jxmpp/jid/parts/Localpart.java b/src/org/jxmpp/jid/parts/Localpart.java new file mode 100644 index 0000000..90a283e --- /dev/null +++ b/src/org/jxmpp/jid/parts/Localpart.java @@ -0,0 +1,183 @@ +/** + * + * Copyright © 2014-2018 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.parts; + +import org.jxmpp.JxmppContext; +import org.jxmpp.stringprep.XmppStringPrepUtil; +import org.jxmpp.stringprep.XmppStringprepException; +import org.jxmpp.util.XmppStringUtils; + +/** + * A localpart of an XMPP address (JID). The localpart is the part before the + * first @ sign in an XMPP address and usually identifies the user (or the XMPP + * entity) within an XMPP service. It is also often referred to as "username", + * but note that the actual username used to login may be different from the + * resulting localpart of the user's JID. + *

+ * You can create instances of this class from Strings using {@link #from(String)}. + *

+ * + * @see RFC + * 6122 § 2.3. Localpart + */ +public class Localpart extends Part { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private transient String unescapedCache; + + private Localpart(String localpart) { + super(localpart); + } + + /** + * Return the unescaped String representation of this Localpart. + *

+ * Since certain Unicode code points are disallowed in the localpart of a JID by the required stringprep profile, + * those need to get escaped when used in a real JID. The unescaped representation of the JID is only for + * presentation to a human user or for gatewaying to a non-XMPP system. + *

+ * + * @return the unescaped String representation of this JID. + * @see org.jxmpp.jid.Jid#asUnescapedString() + * @since 0.6.1 + */ + public String asUnescapedString() { + if (unescapedCache != null) { + return unescapedCache; + } + unescapedCache = XmppStringUtils.unescapeLocalpart(toString()); + return unescapedCache; + } + + /** + * Like {@link #from(String)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link Localpart} + * @return the {@link Localpart} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid {@link Localpart} + * @see #from(String) + * @since 0.6.2 + */ + public static Localpart fromOrThrowUnchecked(CharSequence cs) { + try { + return from(cs.toString()); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Like {@link #fromUnescaped(String)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link Localpart} + * @return the {@link Localpart} if no exception occurs + * @see #from(String) + * @since 0.6.2 + */ + public static Localpart fromUnescapedOrThrowUnchecked(CharSequence cs) { + try { + return fromUnescaped(cs.toString()); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get a {@link Localpart} from a given {@link CharSequence} or {@code null} if the input is not a valid localpart. + * + * @param cs the input CharSequence + * @return a Localpart or {@code null} + */ + public static Localpart formUnescapedOrNull(CharSequence cs) { + try { + return fromUnescaped(cs); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get a {@link Localpart} from an unescaped String. + * + * @param unescapedLocalpart an unescaped String representing a Localpart. + * @return a Localpart + * @throws XmppStringprepException if an error occurs. + * @since 0.6.2 + */ + public static Localpart fromUnescaped(String unescapedLocalpart) throws XmppStringprepException { + String escapedLocalpartString = XmppStringUtils.escapeLocalpart(unescapedLocalpart); + return from(escapedLocalpartString); + } + + /** + * Get a {@link Localpart} from an unescaped CharSequence. + * + * @param unescapedLocalpart an unescaped CharSequence representing a Localpart. + * @return a Localpart + * @throws XmppStringprepException if an error occurs. + * @since 0.6.2 + */ + public static Localpart fromUnescaped(CharSequence unescapedLocalpart) throws XmppStringprepException { + return fromUnescaped(unescapedLocalpart.toString()); + } + + /** + * Get a {@link Localpart} from a given {@link CharSequence} or {@code null} if the input is not a valid localpart. + * + * @param cs the input CharSequence + * @return a Localpart or {@code null} + */ + public static Localpart fromOrNull(CharSequence cs) { + try { + return from(cs.toString()); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Get the {@link Localpart} representing the input String. + * + * @param localpart the input String. + * @return the localpart. + * @throws XmppStringprepException if an error occurs. + */ + public static Localpart from(String localpart) throws XmppStringprepException { + return from(localpart, JxmppContext.getDefaultContext()); + } + + /** + * Get the {@link Localpart} representing the input String. + * + * @param localpart the input String. + * @param context the JXMPP context. + * @return the localpart. + * @throws XmppStringprepException if an error occurs. + */ + public static Localpart from(String localpart, JxmppContext context) throws XmppStringprepException { + localpart = XmppStringPrepUtil.localprep(localpart, context); + // First prep the String, then assure the limits of the *result* + assertNotLongerThan1023BytesOrEmpty(localpart); + return new Localpart(localpart); + } +} diff --git a/src/org/jxmpp/jid/parts/Part.java b/src/org/jxmpp/jid/parts/Part.java new file mode 100644 index 0000000..b2c3ad3 --- /dev/null +++ b/src/org/jxmpp/jid/parts/Part.java @@ -0,0 +1,102 @@ +/** + * + * Copyright © 2014-2019 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.parts; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; + +import org.jxmpp.stringprep.XmppStringprepException; + +public abstract class Part implements CharSequence, Serializable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private final String part; + + protected Part(String part) { + this.part = part; + } + + @Override + public final int length() { + return part.length(); + } + + @Override + public final char charAt(int index) { + return part.charAt(index); + } + + @Override + public final CharSequence subSequence(int start, int end) { + return part.subSequence(start, end); + } + + @Override + public final String toString() { + return part; + } + + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + return part.equals(other.toString()); + } + + @Override + public final int hashCode() { + return part.hashCode(); + } + + protected static void assertNotLongerThan1023BytesOrEmpty(String string) throws XmppStringprepException { + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + + // Better throw XmppStringprepException instead of IllegalArgumentException here, because users don't expect an + // IAE and it also makes the error handling for users easier. + if (bytes.length > 1023) { + throw new XmppStringprepException(string, "Given string is longer then 1023 bytes"); + } else if (bytes.length == 0) { + throw new XmppStringprepException(string, "Argument can't be the empty string"); + } + } + + /** + * The cache holding the internalized value of this part. This needs to be transient so that the + * cache is recreated once the data was de-serialized. + */ + private transient String internalizedCache; + + /** + * Returns the canonical String representation of this Part. See {@link String#intern} for details. + * + * @return the canonical String representation. + */ + public final String intern() { + if (internalizedCache == null) { + internalizedCache = toString().intern(); + } + return internalizedCache; + } +} diff --git a/src/org/jxmpp/jid/parts/Resourcepart.java b/src/org/jxmpp/jid/parts/Resourcepart.java new file mode 100644 index 0000000..39be445 --- /dev/null +++ b/src/org/jxmpp/jid/parts/Resourcepart.java @@ -0,0 +1,119 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.parts; + +import org.jxmpp.JxmppContext; +import org.jxmpp.stringprep.XmppStringPrepUtil; +import org.jxmpp.stringprep.XmppStringprepException; + +/** + * A resourcepart of an XMPP address (JID). + *

+ * You can create instances of this class from Strings using {@link #from(String)}. + *

+ * + * @see RFC 6122 § 2.4. Resourcepart + */ +public class Resourcepart extends Part { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * The empty resource part. + *

+ * This empty resource part is the part that is represented by the empty String. This is useful in cases where you + * have a collection of Resourceparts that does not allow null values, but you want to deal with the + * "no resource" case. + *

+ */ + public static final Resourcepart EMPTY = new Resourcepart(""); + + private Resourcepart(String resource) { + super(resource); + } + + /** + * Get a {@link Resourcepart} from a given {@link CharSequence} or {@code null} if the input is not a valid resourcepart. + * + * @param cs the input CharSequence + * @return a Resourcepart or {@code null} + */ + public static Resourcepart fromOrNull(CharSequence cs) { + try { + return from(cs.toString()); + } catch (XmppStringprepException e) { + return null; + } + } + + /** + * Like {@link #from(String)} but does throw an unchecked {@link IllegalArgumentException} instead of a + * {@link XmppStringprepException}. + * + * @param cs the character sequence which should be transformed to a {@link Resourcepart} + * @return the {@link Resourcepart} if no exception occurs + * @throws IllegalArgumentException if the given input is not a valid {@link Resourcepart} + * @see #from(String) + * @since 0.6.2 + */ + public static Resourcepart fromOrThrowUnchecked(CharSequence cs) { + try { + return from(cs.toString()); + } catch (XmppStringprepException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Get the {@link Resourcepart} representing the input String. + * + * @param resource the input String. + * @return the resource part. + * @throws XmppStringprepException if an error occurs. + */ + public static Resourcepart from(String resource) throws XmppStringprepException { + return from(resource, JxmppContext.getDefaultContext()); + } + + /** + * Get the {@link Resourcepart} representing the input String. + * + * @param resource the input String. + * @return the resource part. + * @throws XmppStringprepException if an error occurs. + */ + public static Resourcepart from(CharSequence resource) throws XmppStringprepException { + return from(resource.toString()); + } + + /** + * Get the {@link Resourcepart} representing the input String. + * + * @param resource the input String. + * @param context the JXMPP context. + * @return the resource part. + * @throws XmppStringprepException if an error occurs. + */ + public static Resourcepart from(String resource, JxmppContext context) throws XmppStringprepException { + resource = XmppStringPrepUtil.resourceprep(resource, context); + // First prep the String, then assure the limits of the *result* + assertNotLongerThan1023BytesOrEmpty(resource); + return new Resourcepart(resource); + }} diff --git a/src/org/jxmpp/jid/parts/package-info.java b/src/org/jxmpp/jid/parts/package-info.java new file mode 100644 index 0000000..cafb433 --- /dev/null +++ b/src/org/jxmpp/jid/parts/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Classes representing JID parts (Local-, Resource- and Domainpart). + */ +package org.jxmpp.jid.parts; diff --git a/src/org/jxmpp/jid/util/JidUtil.java b/src/org/jxmpp/jid/util/JidUtil.java new file mode 100644 index 0000000..386bbfb --- /dev/null +++ b/src/org/jxmpp/jid/util/JidUtil.java @@ -0,0 +1,424 @@ +/** + * + * Copyright © 2014-2024 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jxmpp.jid.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.DomainFullJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; +import org.jxmpp.util.XmppStringUtils; + +public class JidUtil { + + /** + * Check if the given CharSequence represents a typical and valid entity bare JID. This method does perform the same + * check as {@link #isValidEntityBareJid(CharSequence)} and additionally verifies that the domainpart of the JID + * contains at least one dot ('.') character. + *

+ * For more information about the different verification methods see {@link #validateEntityBareJid(CharSequence)}. + *

+ * + * @param jid the CharSequence to check. + * @return true if {@code jid} represents a valid entity bare JID, false otherwise + * @see #isValidEntityBareJid(CharSequence) + * @see EntityBareJid + */ + public static boolean isTypicalValidEntityBareJid(CharSequence jid) { + try { + validateTypicalEntityBareJid(jid); + } catch (NotAEntityBareJidStringException | XmppStringprepException e) { + return false; + } + return true; + } + + /** + * Check if the given CharSequence is a typical and valid entity bare JID. This method does perform the same + * check as {@link #isValidEntityBareJid(CharSequence)} and additionally verifies that the domainpart of the JID + * contains at least one dot ('.') character. + *

+ * The …TypicalValidEntityBareJid(CharSequence) methods are useful if you expect your users to always + * enter a FQDN as domainpart. Whereas isValidEntityBareJid(CharSequence) and + * validateEntityBareJid accept also inputs like "foo@example", the "is typical JID" methods require + * the domainpart to contain a dot, e.g. "foo@example.org". + *

+ * + * @param jidcs the JID CharSequence + * @return a BareJid instance representing the given JID CharSequence + * @throws NotAEntityBareJidStringException if the given CharSequence is not a bare JID. + * @throws XmppStringprepException if an error happens. + */ + public static EntityBareJid validateTypicalEntityBareJid(CharSequence jidcs) throws NotAEntityBareJidStringException, XmppStringprepException { + EntityBareJid jid = validateEntityBareJid(jidcs); + if (jid.getDomain().toString().indexOf('.') == -1) { + throw new NotAEntityBareJidStringException("Domainpart does not include a dot ('.') character"); + } + return jid; + } + + /** + * Check if the given CharSequence represents a valid entity bare JID. That + * is, it must consists exactly of a local- and a domainpart + * (<localpart@domainpart>). + *

+ * This method is meant to validate user input and give fast feedback (e.g. + * with a red or green light) about if the user entered CharSequence + * represents a bare JID. + *

+ * + * @param jid + * the CharSequence to check. + * @return true if {@code jid} represents a valid entity bare JID, false otherwise + * @see EntityBareJid + */ + public static boolean isValidEntityBareJid(CharSequence jid) { + try { + validateEntityBareJid(jid); + } catch (NotAEntityBareJidStringException | XmppStringprepException e) { + return false; + } + return true; + } + + /** + * Check if the given CharSequence is a valid entity bare JID. That + * is, it must consists exactly of a local- and a domainpart + * (<localpart@domainpart>). + *

+ * This is a convenience method meant to validate user entered bare JIDs. If + * the given {@code jid} is not a valid bare JID, then this method will + * throw either {@link NotAEntityBareJidStringException} or + * {@link XmppStringprepException}. The NotABareJidStringException will + * contain a meaningful message explaining why the given CharSequence is not a + * valid bare JID (e.g. "does not contain a '@' character"). + *

+ * + * @param jidcs the JID CharSequence + * @return a BareJid instance representing the given JID CharSequence + * @throws NotAEntityBareJidStringException if the given CharSequence is not a bare JID. + * @throws XmppStringprepException if an error happens. + */ + public static EntityBareJid validateEntityBareJid(CharSequence jidcs) throws NotAEntityBareJidStringException, XmppStringprepException { + String jid = jidcs.toString(); + final int atIndex = jid.indexOf('@'); + if (atIndex == -1) { + throw new NotAEntityBareJidStringException("'" + jid + "' does not contain a '@' character"); + } else if (jid.indexOf('@', atIndex + 1) != -1) { + throw new NotAEntityBareJidStringException("'" + jid + "' contains multiple '@' characters"); + } + final String localpart = XmppStringUtils.parseLocalpart(jid); + if (localpart == null || localpart.length() == 0) { + throw new NotAEntityBareJidStringException("'" + jid + "' has empty localpart"); + } + final String domainpart = XmppStringUtils.parseDomain(jid); + if (domainpart == null || domainpart.length() == 0) { + throw new NotAEntityBareJidStringException("'" + jid + "' has empty domainpart"); + } + return JidCreate.entityBareFromUnescaped(jid); + } + + public static class NotAEntityBareJidStringException extends Exception { + /** + * + */ + private static final long serialVersionUID = -1710386661031655082L; + + /** + * Construct a new "not a entity bare JID" exception. + * + * @param message the message of the exception. + */ + public NotAEntityBareJidStringException(String message) { + super(message); + } + } + + /** + * Filter all entity bare JIDs. + * + * @param in the input collection. + * @param out the collection where the filtered JIDs are added to. + */ + public static void filterEntityBareJid(Collection in, Collection out) { + for (Jid jid : in) { + EntityBareJid bareJid = jid.asEntityBareJidIfPossible(); + if (bareJid != null) { + out.add(bareJid); + } + } + } + + /** + * Filter all entity bare JIDs. + * + * @param input the input collection. + * @return a set containing all bare JIDs of the input collection. + */ + public static Set filterEntityBareJidSet(Collection input) { + Set res = new HashSet(input.size()); + filterEntityBareJid(input, res); + return res; + } + + /** + * Filter all entity bare JIDs. + * + * @param input the input collection. + * @return a list containing all bare JIDs of the input collection. + */ + public static List filterEntityBareJidList(Collection input) { + List res = new ArrayList(input.size()); + filterEntityBareJid(input, res); + return res; + } + + /** + * Filter all entity full JIDs. + * + * @param in the input collection. + * @param out the collection where the filtered JIDs are added to. + */ + public static void filterEntityFullJid(Collection in, Collection out) { + for (Jid jid : in) { + EntityFullJid fullJid = jid.asEntityFullJidIfPossible(); + if (fullJid != null) { + out.add(fullJid); + } + } + } + + /** + * Filter all full JIDs. + * + * @param input the input collection. + * @return a set containing all full JIDs of the input collection. + */ + public static Set filterEntityFullJidSet(Collection input) { + Set res = new HashSet(input.size()); + filterEntityFullJid(input, res); + return res; + } + + /** + * Filter all full JIDs. + * + * @param input the input collection. + * @return a list containing all full JIDs of the input collection. + */ + public static List filterEntityFullJidList(Collection input) { + List res = new ArrayList(input.size()); + filterEntityFullJid(input, res); + return res; + } + + /** + * Filter all domain full JIDs. + * + * @param in the input collection. + * @param out the collection where the filtered JIDs are added to. + */ + public static void filterDomainFullJid(Collection in, Collection out) { + for (Jid jid : in) { + DomainFullJid domainFullJid = jid.asDomainFullJidIfPossible(); + if (domainFullJid != null) { + out.add(domainFullJid); + } + } + } + + /** + * Filter all domain full JIDs. + * + * @param input the input collection. + * @return a set containing all domain full JIDs of the input collection. + */ + public static Set filterDomainFullJidSet(Collection input) { + Set res = new HashSet(input.size()); + filterDomainFullJid(input, res); + return res; + } + + /** + * Filter all domain full JIDs. + * + * @param input the input collection. + * @return a list containing all domain full JIDs of the input collection. + */ + public static List filterDomainFullJidList(Collection input) { + List res = new ArrayList(input.size()); + filterDomainFullJid(input, res); + return res; + } + + /** + * Convert the given collection of CharSequences to bare JIDs. + * + * @param jidStrings the collection of CharSequences. + * @return a set of bare JIDs. + */ + public static Set entityBareJidSetFrom(Collection jidStrings) { + Set res = new HashSet(jidStrings.size()); + entityBareJidsFrom(jidStrings, res, null); + return res; + } + + /** + * Convert a collection of Strings to a Set of {@link EntityBareJid}'s. + *

+ * If the optional argument exceptions is given, then all {@link XmppStringprepException} thrown while + * converting will be added to the list. Otherwise, if an XmppStringprepExceptions is thrown, it will be wrapped in + * a AssertionError Exception and throw. + *

+ * + * @param jidStrings + * the strings that are going to get converted + * @param output + * the collection where the BareJid's will be added to + * @param exceptions the list of exceptions thrown while converting. + */ + public static void entityBareJidsFrom(Collection jidStrings, Collection output, + List exceptions) { + for (CharSequence jid : jidStrings) { + try { + EntityBareJid bareJid = JidCreate.entityBareFrom(jid); + output.add(bareJid); + } catch (XmppStringprepException e) { + if (exceptions != null) { + exceptions.add(e); + } else { + throw new AssertionError(e); + } + } + } + } + + /** + * Convert the given array of Strings to JIDs. + *

+ * Note that errors while converting the Strings will be silently ignored. + *

+ * + * @param jids a array of JID Strings. + * @return a set of JIDs. + */ + public static Set jidSetFrom(String[] jids) { + return jidSetFrom(Arrays.asList(jids)); + } + + /** + * Convert the given collection of CharSequences to JIDs. + * + * @param jidStrings the collection of CharSequences. + * @return a set of JIDs. + */ + public static Set jidSetFrom(Collection jidStrings) { + Set res = new HashSet(jidStrings.size()); + jidsFrom(jidStrings, res, null); + return res; + } + + /** + * Convert a collection of Strings to a Set of {@link Jid}'s. + *

+ * If the optional argument exceptions is given, then all {@link XmppStringprepException} thrown while + * converting will be added to the list. Otherwise, if an XmppStringprepExceptions is thrown, it will be wrapped in + * a AssertionError Exception and throw. + *

+ * + * @param jidStrings + * the strings that are going to get converted + * @param output + * the collection where the Jid's will be added to + * @param exceptions the list of exceptions thrown while converting. + */ + public static void jidsFrom(Collection jidStrings, Collection output, + List exceptions) { + for (CharSequence jidString : jidStrings) { + try { + Jid jid = JidCreate.from(jidString); + output.add(jid); + } catch (XmppStringprepException e) { + if (exceptions != null) { + exceptions.add(e); + } else { + throw new AssertionError(e); + } + } + } + } + + /** + * Convert a collection of JIDs to a list of Strings representing those JIDs. + * + * @param jids a collection of JIDs. + * @return a list of Strings. + */ + public static List toStringList(Collection jids) { + List res = new ArrayList(jids.size()); + toStrings(jids, res); + return res; + } + + /** + * convert a collection of JIDs to a set of Strings representing those JIDs. + * + * @param jids a collection of JIDs. + * @return a set of String. + */ + public static Set toStringSet(Collection jids) { + Set res = new HashSet(jids.size()); + toStrings(jids, res); + return res; + } + + /** + * Convert a collection of JIDs to a Collection of Strings. + * + * @param jids the collection of Strings to convert. + * @param jidStrings the collection of Strings to append to. + */ + public static void toStrings(Collection jids, Collection jidStrings) { + for (Jid jid : jids) { + jidStrings.add(jid.toString()); + } + } + + /** + * Check if two JIDs are equals. Takes null values into consideration. Which means that this method will return true if both JIDs are null. + * + * @param jidOne The first JID to compare. + * @param jidTwo The second JID to compare. + * @return true if both JIDs are equals. + * @since 0.7.0 + */ + public static boolean equals(Jid jidOne, Jid jidTwo) { + if (jidOne != null) { + return jidOne.equals(jidTwo); + } + + return jidTwo == null; + } +} diff --git a/src/org/jxmpp/jid/util/package-info.java b/src/org/jxmpp/jid/util/package-info.java new file mode 100644 index 0000000..f33d57f --- /dev/null +++ b/src/org/jxmpp/jid/util/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Utility classes for JIDs. + */ +package org.jxmpp.jid.util;