diff options
author | Sven Gothel <[email protected]> | 2021-01-25 01:28:36 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2021-01-25 01:28:36 +0100 |
commit | b371af6d83e0fb94cba02ec6c2837bf58c2eea39 (patch) | |
tree | 48999c7e269409ebe389e5d5e72ca141d8b9eea7 | |
parent | 1ba932716f2d382af56bf8ea69a57666e2c835d6 (diff) |
Java import and modularization: jaulib_base, jaulib_jni, jaulib_net, jaulib_pkg (WIP)
151 files changed, 25797 insertions, 682 deletions
@@ -26,3 +26,4 @@ dist*/ /Debug/ /Debug test_cow_darray_perf01/ /Profiling test_cow_darray_perf01/ +/bin/ diff --git a/CMakeLists.txt b/CMakeLists.txt index bb05c0b..9fe78d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,24 +147,78 @@ include_directories (${SYSTEM_USR_DIR}) add_subdirectory (src) +IF(BUILDJAVA) + find_package(Java 11 REQUIRED) + find_package(JNI REQUIRED) + include(UseJava) + + if (JNI_FOUND) + message (STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}") + message (STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}") + endif (JNI_FOUND) + + if (NOT DEFINED $ENV{JAVA_HOME_NATIVE}) + set (JAVA_HOME_NATIVE $ENV{JAVA_HOME}) + set (JAVAC $ENV{JAVA_HOME}/bin/javac) + set (JAR $ENV{JAVA_HOME}/bin/jar) + else () + set (JAVAC $ENV{JAVA_HOME_NATIVE}/bin/javac) + set (JAR $ENV{JAVA_HOME_NATIVE}/bin/jar) + endif () + + set(CMAKE_JAVA_COMPILE_FLAGS -source 11 -target 11) + if(DEBUG) + set(CMAKE_JAVA_COMPILE_FLAGS ${CMAKE_JAVA_COMPILE_FLAGS} -g:source,lines) + else() + set(CMAKE_JAVA_COMPILE_FLAGS ${CMAKE_JAVA_COMPILE_FLAGS} -g:none) + endif(DEBUG) + + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/java_base/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/java_base/manifest.txt) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/java_jni/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/java_jni/manifest.txt) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/java_net/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/java_net/manifest.txt) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/java_pkg/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/java_pkg/manifest.txt) + add_subdirectory (java_base) + add_subdirectory (java_jni) + add_subdirectory (java_net) + add_subdirectory (java_pkg) + # add_subdirectory (examples/java) +ENDIF(BUILDJAVA) + # add a target to generate API documentation with Doxygen find_package (Doxygen) if (DOXYGEN_FOUND) configure_file (${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.cpp.out @ONLY) - add_custom_command (OUTPUT ${jaulib_DOCS_DIR}/cpp + if (BUILDJAVA) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.java.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.java.out @ONLY) + add_custom_command (OUTPUT ${jaulib_DOCS_DIR}/cpp ${jaulib_DOCS_DIR}/java + COMMAND ${CMAKE_COMMAND} -E make_directory ${jaulib_DOCS_DIR} + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.cpp.out + COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.java.out + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/api/tinyb/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/api/direct_bt/*.hpp ${CMAKE_CURRENT_SOURCE_DIR}/java/org/tinyb/*.java ${CMAKE_CURRENT_SOURCE_DIR}/java/tinyb/dbus/*java ${CMAKE_CURRENT_SOURCE_DIR}/java/direct_bt/tinyb/*java + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" VERBATIM + ) + add_custom_target(doc_jau + DEPENDS ${jaulib_DOCS_DIR}/cpp ${jaulib_DOCS_DIR}/java) + else () + add_custom_command (OUTPUT ${jaulib_DOCS_DIR}/cpp COMMAND ${CMAKE_COMMAND} -E make_directory ${jaulib_DOCS_DIR} COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.cpp.out DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/include/ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating API documentation with Doxygen" VERBATIM - ) - add_custom_target(doc_jau - DEPENDS ${jaulib_DOCS_DIR}/cpp) + ) + add_custom_target(doc_jau + DEPENDS ${jaulib_DOCS_DIR}/cpp) + endif () endif (DOXYGEN_FOUND) if (BUILD_TESTING) enable_testing () add_subdirectory (test) + if (BUILDJAVA) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test/java/manifest.txt.in ${CMAKE_CURRENT_BINARY_DIR}/test/java/manifest.txt) + add_subdirectory (test/java) + endif(BUILDJAVA) endif(BUILD_TESTING) - diff --git a/Doxyfile.java.in b/Doxyfile.java.in new file mode 100644 index 0000000..7a4dcca --- /dev/null +++ b/Doxyfile.java.in @@ -0,0 +1,2365 @@ +# Doxyfile 1.8.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "jaulib" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @jaulib_VERSION_STRING@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Jau Support Library (C++, Java, ..)" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @jaulib_DOCS_DIR@/java + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = YES + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = YES + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/java/ \ + @CMAKE_CURRENT_SOURCE_DIR@/README.md \ + @CMAKE_CURRENT_SOURCE_DIR@/test/java/ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.java *.md + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/test/java/ + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = *.java + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = @CMAKE_CURRENT_SOURCE_DIR@/README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: NO. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 20 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 150 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. To get the times font for +# instance you can specify +# EXTRA_PACKAGES=times +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a +# higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sf.net) file that captures the +# structure of the code including all documentation. Note that this feature is +# still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = "__attribute__((x))=" \ + "__pack(x)=x" + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = YES + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = YES + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif and svg. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = svg + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = YES + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_CLEANUP = YES diff --git a/java/jni/jau/BasicTypes.cxx b/java/jni/jau/BasicTypes.cxx deleted file mode 100644 index eb10ef3..0000000 --- a/java/jni/jau/BasicTypes.cxx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Author: Sven Gothel <[email protected]> - * Copyright (c) 2020 Gothel Software e.K. - * Copyright (c) 2020 ZAFENA AB - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include "org_jau_BasicTypes.h" - -#include <cstdint> -#include <cinttypes> - -#include <time.h> - -#include <jau/dfa_utf8_decode.hpp> - -#include "helper_base.hpp" - -jstring Java_org_jau_BasicTypes_decodeUTF8String(JNIEnv *env, jclass clazz, jbyteArray jbuffer, jint offset, jint size) { - (void)clazz; - - const int buffer_size = env->GetArrayLength(jbuffer); - if( 0 == buffer_size ) { - return env->NewStringUTF(""); - } - if( buffer_size < offset+size ) { - std::string msg("buffer.length "+std::to_string(buffer_size)+ - " < offset "+std::to_string(offset)+ - " + size "+std::to_string(size)); - throw std::invalid_argument(msg.c_str()); - } - - std::string sres; - { - JNICriticalArray<uint8_t, jbyteArray> criticalArray(env); // RAII - release - uint8_t * buffer_ptr = criticalArray.get(jbuffer, criticalArray.Mode::NO_UPDATE_AND_RELEASE); - if( NULL == buffer_ptr ) { - throw std::invalid_argument("GetPrimitiveArrayCritical(byte array) is null"); - } - sres = jau::dfa_utf8_decode(buffer_ptr+offset, static_cast<size_t>(size)); - } - return jau::from_string_to_jstring(env, sres); -} diff --git a/java/org/jau/sys/PlatformToolkit.java b/java/org/jau/sys/PlatformToolkit.java deleted file mode 100644 index 6b473cb..0000000 --- a/java/org/jau/sys/PlatformToolkit.java +++ /dev/null @@ -1,587 +0,0 @@ -/** - * Author: Sven Gothel <[email protected]> - * Copyright (c) 2020 Gothel Software e.K. - * Copyright (c) 2010 Gothel Software e.K. - * Copyright (c) 2010 JogAmp Community. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package org.jau.sys; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.StringTokenizer; - -/** - * Miscellaneous platform utilities, allowed to be used within same Java package. - */ -final class PlatformToolkit { - public enum OSType { - UNIX, MACOS, IOS, WINDOWS; - } - public enum CPUFamily { - /** AMD/Intel */ - X86, - /** ARM 32bit */ - ARM32, - /** ARM 64bit */ - ARM64, - /** Power PC */ - PPC, - /** SPARC */ - SPARC, - /** Mips */ - MIPS, - /** PA RISC */ - PA_RISC, - /** Itanium */ - IA64, - /** Hitachi SuperH */ - SuperH; - } - public enum CPUType { - /** ARM 32bit default, usually little endian */ - ARM( CPUFamily.ARM32, true), - /** ARM7EJ, ARM9E, ARM10E, XScale, usually little endian */ - ARMv5( CPUFamily.ARM32, true), - /** ARM11, usually little endian */ - ARMv6( CPUFamily.ARM32, true), - /** ARM Cortex, usually little endian */ - ARMv7( CPUFamily.ARM32, true), - // 4 - - /** X86 32bit, little endian */ - X86_32( CPUFamily.X86, true), - /** PPC 32bit default, usually big endian */ - PPC( CPUFamily.PPC, true), - /** MIPS 32bit, big endian (mips) or little endian (mipsel) */ - MIPS_32( CPUFamily.MIPS, true), - /** Hitachi SuperH 32bit default, ??? endian */ - SuperH( CPUFamily.SuperH, true), - /** SPARC 32bit, big endian */ - SPARC_32( CPUFamily.SPARC, true), - // 9 - - /** ARM64 default (64bit), usually little endian */ - ARM64( CPUFamily.ARM64, false), - /** ARM AArch64 (64bit), usually little endian */ - ARMv8_A( CPUFamily.ARM64, false), - /** X86 64bit, little endian */ - X86_64( CPUFamily.X86, false), - /** PPC 64bit default, usually big endian */ - PPC64( CPUFamily.PPC, false), - /** MIPS 64bit, big endian (mips64) or little endian (mipsel64) ? */ - MIPS_64( CPUFamily.MIPS, false), - /** Itanium 64bit default, little endian */ - IA64( CPUFamily.IA64, false), - /** SPARC 64bit, big endian */ - SPARCV9_64(CPUFamily.SPARC, false), - /** PA_RISC2_0 64bit, ??? endian */ - PA_RISC2_0(CPUFamily.PA_RISC, false); - // 17 - - public final CPUFamily family; - public final boolean is32Bit; - - CPUType(final CPUFamily type, final boolean is32Bit){ - this.family = type; - this.is32Bit = is32Bit; - } - - /** - * Returns {@code true} if the given {@link CPUType} is compatible - * w/ this one, i.e. at least {@link #family} and {@link #is32Bit} is equal. - */ - public final boolean isCompatible(final CPUType other) { - if( null == other ) { - return false; - } else if( other == this ) { - return true; - } else { - return this.family == other.family && - this.is32Bit == other.is32Bit; - } - } - - public static final CPUType query(final String cpuABILower) { - if( null == cpuABILower ) { - throw new IllegalArgumentException("Null cpuABILower arg"); - } - if( cpuABILower.equals("x86") || - cpuABILower.equals("i386") || - cpuABILower.equals("i486") || - cpuABILower.equals("i586") || - cpuABILower.equals("i686") ) { - return X86_32; - } else if( cpuABILower.equals("x86_64") || - cpuABILower.equals("amd64") ) { - return X86_64; - } else if( cpuABILower.equals("ia64") ) { - return IA64; - } else if( cpuABILower.equals("aarch64") ) { - return ARM64; - } else if( cpuABILower.startsWith("arm") ) { - if( cpuABILower.equals("armv8-a") || - cpuABILower.equals("arm-v8-a") || - cpuABILower.equals("arm-8-a") || - cpuABILower.equals("arm64-v8a") ) { - return ARMv8_A; - } else if( cpuABILower.startsWith("arm64") ) { - return ARM64; - } else if( cpuABILower.startsWith("armv7") || - cpuABILower.startsWith("arm-v7") || - cpuABILower.startsWith("arm-7") || - cpuABILower.startsWith("armeabi-v7") ) { - return ARMv7; - } else if( cpuABILower.startsWith("armv5") || - cpuABILower.startsWith("arm-v5") || - cpuABILower.startsWith("arm-5") ) { - return ARMv5; - } else if( cpuABILower.startsWith("armv6") || - cpuABILower.startsWith("arm-v6") || - cpuABILower.startsWith("arm-6") ) { - return ARMv6; - } else { - return ARM; - } - } else if( cpuABILower.equals("sparcv9") ) { - return SPARCV9_64; - } else if( cpuABILower.equals("sparc") ) { - return SPARC_32; - } else if( cpuABILower.equals("pa_risc2.0") ) { - return PA_RISC2_0; - } else if( cpuABILower.startsWith("ppc64") ) { - return PPC64; - } else if( cpuABILower.startsWith("ppc") ) { - return PPC; - } else if( cpuABILower.startsWith("mips64") ) { - return MIPS_64; - } else if( cpuABILower.startsWith("mips") ) { - return MIPS_32; - } else if( cpuABILower.startsWith("superh") ) { - return SuperH; - } else { - throw new RuntimeException("Please port CPUType detection to your platform (CPU_ABI string '" + cpuABILower + "')"); - } - } - } - public enum ABIType { - GENERIC_ABI ( 0x00 ), - /** ARM GNU-EABI ARMEL -mfloat-abi=softfp */ - EABI_GNU_ARMEL ( 0x01 ), - /** ARM GNU-EABI ARMHF -mfloat-abi=hard */ - EABI_GNU_ARMHF ( 0x02 ), - /** ARM EABI AARCH64 (64bit) */ - EABI_AARCH64 ( 0x03 ); - - public final int id; - - ABIType(final int id){ - this.id = id; - } - - /** - * Returns {@code true} if the given {@link ABIType} is compatible - * w/ this one, i.e. they are equal. - */ - public final boolean isCompatible(final ABIType other) { - if( null == other ) { - return false; - } else { - return other == this; - } - } - - public static final ABIType query(final CPUType cpuType, final String cpuABILower) { - if( null == cpuType ) { - throw new IllegalArgumentException("Null cpuType"); - } else if( null == cpuABILower ) { - throw new IllegalArgumentException("Null cpuABILower"); - } else if( CPUFamily.ARM64 == cpuType.family ) { - return EABI_AARCH64; - } else if( CPUFamily.ARM32 == cpuType.family ) { - // FIXME: We only support EABI_GNU_ARMHF on ARM 32bit for now! - return EABI_GNU_ARMHF; - } else { - return GENERIC_ABI; - } - } - } - - /** Lower case system property '{@code os.name}'. */ - static final String os_name; - /** Lower case system property '{@code os.arch}' */ - static final String os_arch; - private static final String user_dir; - private static final String java_user_lib_path; - private static final String java_boot_lib_path; - - /** - * Unique platform denominator composed as '{@link #os_name}' + '-' + '{@link #os_arch}'. - */ - static final String os_and_arch; - static final OSType OS_TYPE; - private static final boolean isOSX; - - private static final String prefix; - private static final String suffix; - - static { - { - final String[] props = - AccessController.doPrivileged(new PrivilegedAction<String[]>() { - @Override - public String[] run() { - final String[] props = new String[5]; - int i=0; - props[i++] = System.getProperty("os.name").toLowerCase(); // 0 - props[i++] = System.getProperty("os.arch").toLowerCase(); // 1 - props[i++] = System.getProperty("user.dir"); // 2 - props[i++] = System.getProperty("java.library.path"); // 3 - props[i++] = System.getProperty("sun.boot.library.path"); // 4 - return props; - } - }); - int i=0; - os_name = props[i++]; - final String _os_arch1 = props[i++]; - user_dir = props[i++]; - java_user_lib_path = props[i++]; - java_boot_lib_path = props[i++]; - - final boolean LITTLE_ENDIAN = queryIsLittleEndianImpl(); - final CPUType CPU_TYPE = CPUType.query(_os_arch1); - final ABIType ABI_TYPE = ABIType.query(CPU_TYPE, _os_arch1); - final String _os_arch2 = getArchName(CPU_TYPE, ABI_TYPE, LITTLE_ENDIAN); - os_arch = null != _os_arch2 ? _os_arch2 : _os_arch1; - os_and_arch = os_name+"-"+os_arch; - if( BluetoothFactory.DEBUG ) { - System.err.println("PlatformToolkit: os_name "+os_name+", os_arch ("+_os_arch1+" -> "+_os_arch2+" ->) "+os_arch+" (final), "+ - "CPU_TYPE "+CPU_TYPE+", ABI_TYPE "+ABI_TYPE+", LITTLE_ENDIAN "+LITTLE_ENDIAN); - } - } - - if ( os_name.startsWith("mac os x") || - os_name.startsWith("darwin") ) { - OS_TYPE = OSType.MACOS; - isOSX = true; - } else if ( os_name.startsWith("ios") ) { - OS_TYPE = OSType.IOS; - isOSX = true; - } else if ( os_name.startsWith("windows") ) { - OS_TYPE = OSType.WINDOWS; - isOSX = false; - } else { - OS_TYPE = OSType.UNIX; - isOSX = false; - } - - switch (OS_TYPE) { - case WINDOWS: - prefix = ""; - suffix = ".dll"; - break; - - case MACOS: - case IOS: - prefix = "lib"; - suffix = ".dylib"; - break; - - case UNIX: - default: - prefix = "lib"; - suffix = ".so"; - break; - } - } - - private static final boolean queryIsLittleEndianImpl() { - final ByteBuffer tst_b = ByteBuffer.allocateDirect(4 /* SIZEOF_INT */).order(ByteOrder.nativeOrder()); // 32bit in native order - final IntBuffer tst_i = tst_b.asIntBuffer(); - final ShortBuffer tst_s = tst_b.asShortBuffer(); - tst_i.put(0, 0x0A0B0C0D); - return 0x0C0D == tst_s.get(0); - } - private static final String getArchName(final CPUType cpuType, final ABIType abiType, final boolean littleEndian) { - switch( abiType ) { - case EABI_GNU_ARMEL: - return "arm"; // actually not supported! - case EABI_GNU_ARMHF: - return "armhf"; - case EABI_AARCH64: - return "arm64"; - default: - break; - } - - switch( cpuType ) { - case X86_32: - return "i386"; - case PPC: - return "ppc"; - case MIPS_32: - return littleEndian ? "mipsel" : "mips"; - case SuperH: - return "superh"; - case SPARC_32: - return "sparc"; - - case X86_64: - return "amd64"; - case PPC64: - return littleEndian ? "ppc64le" : "ppc64"; - case MIPS_64: - return "mips64"; - case IA64: - return "ia64"; - case SPARCV9_64: - return "sparcv9"; - case PA_RISC2_0: - return "risc2.0"; - default: - return null; - } - } - - /** - * Produces a list of potential full native library pathnames, denoted by its {@code libBaseName}. - * - * <p> - * Basic order of library locations - * <pre> - * User locations: - * - current working directory + {@link #os_and_arch} - * - iterate through paths within 'java.library.path', adding {@link #os_and_arch} to each - * - current working directory - * - iterate through paths within 'java.library.path' - * - * System locations: - * - optional OSX path - * - iterate through paths within 'sun.boot.library.path' - * </pre> - * </p> - * - * <p> - * Example: - * <pre> - /usr/local/projects/direct_bt/dist-amd64/linux-amd64/libdirect_bt.so (addPath cwd.os_and_arch) - /usr/local/projects/direct_bt/dist-amd64/lib/linux-amd64/libdirect_bt.so (addPath java-user-libpath.os_and_arch:0) - /usr/local/projects/direct_bt/dist-amd64/libdirect_bt.so (addPath cwd) - /usr/local/projects/direct_bt/dist-amd64/lib/libdirect_bt.so (addPath java-user-libpath:0) - /usr/lib/jvm/java-14-openjdk-amd64/lib/libdirect_bt.so (addPath java-boot-libpath:0) - * </pre> - * - * @param libBaseName library basename without prefix (like 'lib') or suffix like '.so'. - * @param searchSystemPath - * @param searchSystemPathFirst - * @param loader - * @return - */ - private static final List<String> enumerateLibraryPaths(final String libBaseName, - final boolean searchSystemPath, - final boolean searchSystemPathFirst, - final ClassLoader loader) { - final List<String> paths = new ArrayList<String>(); - - if ( libBaseName == null || libBaseName.length() == 0 ) { - return paths; - } - - final String libPlatformName = getPlatformName(libBaseName); - - if( searchSystemPath && searchSystemPathFirst ) { - // Add probable Mac OS X-specific paths - if ( isOSX ) { - // Add historical location - addPath("osx-1", "/Library/Frameworks/" + libBaseName + ".framework", libPlatformName, paths); - // Add current location - addPath("osx-2", "/System/Library/Frameworks/" + libBaseName + ".framework", libPlatformName, paths); - } - addMultiPaths("java-boot-libpath", java_boot_lib_path, libPlatformName, paths); - } - - addPath("cwd.os_and_arch", user_dir+File.separator+os_and_arch, libPlatformName, paths); - addMultiPaths2("java-user-libpath.os_and_arch", java_user_lib_path, os_and_arch, libPlatformName, paths); - - addPath("cwd", user_dir, libPlatformName, paths); - addMultiPaths("java-user-libpath", java_user_lib_path, libPlatformName, paths); - - if( searchSystemPath && !searchSystemPathFirst ) { - // Add probable Mac OS X-specific paths - if ( isOSX ) { - // Add historical location - addPath("osx-1", "/Library/Frameworks/" + libBaseName + ".Framework", libPlatformName, paths); - // Add current location - addPath("osx-2", "/System/Library/Frameworks/" + libBaseName + ".Framework", libPlatformName, paths); - } - addMultiPaths("java-boot-libpath", java_boot_lib_path, libPlatformName, paths); - } - - return paths; - } - - - private static final String getPlatformName(final String libBaseName) { - return prefix + libBaseName + suffix; - } - private static final String getCanonicalPath(final String path) { - return AccessController.doPrivileged(new PrivilegedAction<String>() { - @Override - public String run() { - try { - final File f = new File(path); - // f.getCanonicalPath() also resolved '.', '..' and symbolic links in contrast to f.getAbsolutePath() - return f.getCanonicalPath(); - } catch (final Throwable t) { - if( BluetoothFactory.DEBUG ) { - System.err.println("getAbsolutePath("+path+") failed: "+t.getMessage()); - } - return null; - } - } } ); - } - private static final void addPath(final String msg, final String path, final String platformName, final List<String> paths) { - if( null != path && path.length() > 0 ) { - final String fullpath = path + File.separator + platformName; - final String abspath = getCanonicalPath(fullpath); - if( null != abspath ) { - final boolean isDup = paths.contains(abspath); - if( BluetoothFactory.DEBUG ) { - System.err.println(" "+abspath+" (addPath "+msg+", dropped duplicate "+isDup+")"); - } - if( !isDup ) { - paths.add(abspath); - } - } - } - } - private static final void addMultiPaths(final String msg, final String pathList, final String platformName, final List<String> paths) { - if( null != pathList && pathList.length() > 0 ) { - final StringTokenizer tokenizer = new StringTokenizer(pathList, File.pathSeparator); - int i=0; - while (tokenizer.hasMoreTokens()) { - addPath(msg+":"+i, tokenizer.nextToken(), platformName, paths); - i++; - } - } - } - private static final void addMultiPaths2(final String msg, final String pathList, final String subDir, final String platformName, final List<String> paths) { - if( null != pathList && pathList.length() > 0 && null != subDir && subDir.length() > 0 ) { - final StringTokenizer tokenizer = new StringTokenizer(pathList, File.pathSeparator); - int i=0; - while (tokenizer.hasMoreTokens()) { - final String path = tokenizer.nextToken() + File.separator + subDir; - addPath(msg+":"+i, path, platformName, paths); - i++; - } - } - } - - - /** - * Loads a native library, denoted by its {@code libBaseName}. - * - * <p> - * Basic order of library locations - * <pre> - * User locations: - * - current working directory + {@link #os_and_arch} - * - iterate through paths within 'java.library.path', adding {@link #os_and_arch} to each - * - current working directory - * - iterate through paths within 'java.library.path' - * - * System locations: - * - optional OSX path - * - iterate through paths within 'sun.boot.library.path' - * </pre> - * </p> - * - * <p> - * If the above fails, {@link System#loadLibrary(String)} is called using the plain {@code libBaseName}, - * exhausting all simple locations and methods. - * </p> - * - * <p> - * Example: - * <pre> - /usr/local/projects/direct_bt/dist-amd64/linux-amd64/libdirect_bt.so (addPath cwd.os_and_arch) - /usr/local/projects/direct_bt/dist-amd64/lib/linux-amd64/libdirect_bt.so (addPath java-user-libpath.os_and_arch:0) - /usr/local/projects/direct_bt/dist-amd64/libdirect_bt.so (addPath cwd) - /usr/local/projects/direct_bt/dist-amd64/lib/libdirect_bt.so (addPath java-user-libpath:0) - /usr/lib/jvm/java-14-openjdk-amd64/lib/libdirect_bt.so (addPath java-boot-libpath:0) - * </pre> - * - * @param libBaseName library basename without prefix (like 'lib') or suffix like '.so'. - * @param cl - * @param t holder to store the last Throwable, if any - * @return {@code true} if successful, otherwise {@code false}. - */ - static boolean loadLibrary(final String libBaseName, final ClassLoader cl, final Throwable[] t) { - if( BluetoothFactory.DEBUG ) { - System.err.println(); - System.err.println("PlatformToolkit.loadLibrary: libBaseName "+libBaseName+":"); - } - final List<String> possiblePaths = enumerateLibraryPaths(libBaseName, true /* searchSystemPath */, false /* searchSystemPathFirst */, cl); - if( BluetoothFactory.DEBUG ) { - System.err.println(); - } - - // Iterate down these and see which one if any we can actually find. - for (final Iterator<String> iter = possiblePaths.iterator(); iter.hasNext(); ) { - final String path = iter.next(); - try { - System.load(path); - if( BluetoothFactory.DEBUG ) { - System.err.println(" "+path+" success"); - } - return true; - } catch (final Throwable t0) { - if( BluetoothFactory.DEBUG ) { - System.err.println(" "+path+" failed: "+t0.getMessage()); - } - t[0] = t0; - } - } - - // Fall back to loadLibrary - try { - System.loadLibrary(libBaseName); - if( BluetoothFactory.DEBUG ) { - System.err.println(" "+libBaseName+" success"); - } - return true; - } catch (final Throwable t0) { - if( BluetoothFactory.DEBUG ) { - System.err.println(" "+libBaseName+" failed: "+t0.getMessage()); - } - t[0] = t0; - } - return false; - } -} diff --git a/java_base/CMakeLists.txt b/java_base/CMakeLists.txt new file mode 100644 index 0000000..86647a5 --- /dev/null +++ b/java_base/CMakeLists.txt @@ -0,0 +1,32 @@ +# java/CMakeLists.txt + +set(CMAKE_JNI_TARGET TRUE) +file(GLOB_RECURSE JAVA_SOURCES "*.java") +add_jar(jaulib_base_jar + ${JAVA_SOURCES} jau/info.txt + MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt + OUTPUT_NAME jaulib_base + GENERATE_NATIVE_HEADERS jaulib_base_javah + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_base_jar.dir/jni" +) + +# JNI header generation is satisfied by above 'add_jar(.. GENERATE_NATIVE_HEADERS <target> DESTINATION <dir>)', +# note that its GENERATE_NATIVE_HEADERS target is a dummy target, since jni/direct_bt defines target jni_jaulib_base. +# Weakness of not directly checking build dependency of javadirect_bt against generated headers exists, +# however, it is unrealistic to assume that the transient generated JNI header will be edited manually +# within the process. +# Therefor we can use the dummy target javadirect_bt_javah and JNI header generation will only +# occur when java sources have been modified, i.e. the jar file being actually build. +# +#add_custom_command (TARGET jaulib_base_jar +# POST_BUILD +# COMMAND ${CMAKE_COMMAND} -E echo "Generating JNI headers.." +# WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_base_jar.dir" +# COMMAND ${JAVAC} -h jni/ ${JAVA_SOURCES} +#) + +set(JNI_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_base_jar.dir/jni") +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/jaulib_base.jar DESTINATION ${CMAKE_INSTALL_LIBDIR}/../lib/java) + +# add_subdirectory (jni) + diff --git a/java_base/jau/info.txt b/java_base/jau/info.txt new file mode 100644 index 0000000..739c944 --- /dev/null +++ b/java_base/jau/info.txt @@ -0,0 +1,3 @@ +jaulib_base jau/info.txt Asset. + +This file exists for test purposes. diff --git a/java/jau/util/Int32ArrayBitfield.java b/java_base/jau/util/Int32ArrayBitfield.java index e5bfe02..e5bfe02 100644 --- a/java/jau/util/Int32ArrayBitfield.java +++ b/java_base/jau/util/Int32ArrayBitfield.java diff --git a/java/jau/util/Int32Bitfield.java b/java_base/jau/util/Int32Bitfield.java index 6e0b9aa..6e0b9aa 100644 --- a/java/jau/util/Int32Bitfield.java +++ b/java_base/jau/util/Int32Bitfield.java diff --git a/java/jau/util/SyncedBitfield.java b/java_base/jau/util/SyncedBitfield.java index 3185b08..3185b08 100644 --- a/java/jau/util/SyncedBitfield.java +++ b/java_base/jau/util/SyncedBitfield.java diff --git a/java_base/manifest.txt.in b/java_base/manifest.txt.in new file mode 100644 index 0000000..520f9f2 --- /dev/null +++ b/java_base/manifest.txt.in @@ -0,0 +1,30 @@ +Manifest-Version: 1.0 +Bundle-Date: @BUILD_TSTAMP@ +Bundle-ManifestVersion: 2 +Bundle-Name: org.jau +Bundle-SymbolicName: org.jau +Bundle-Version: @VERSION_SHORT@ +Export-Package: org.jau +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.9))" +Package-Title: org.jau +Package-Vendor: Gothel Software +Package-Version: @VERSION_SHORT@ +Specification-Title: Jaulib Base +Specification-Vendor: Gothel Software +Specification-Version: @VERSION_API@ +Implementation-Title: Jaulib Base +Implementation-Vendor: Gothel Software +Implementation-Version: @VERSION@ +Implementation-Commit: @VERSION_SHA1@ +Implementation-URL: http://www.jausoft.com/ +Extension-Name: org.jau +Trusted-Library: true +Permissions: all-permissions +Application-Library-Allowable-Codebase: * + +Name: org/jau/ +Sealed: false + +Name: jau/ +Sealed: false + diff --git a/java/org/jau/io/Bitstream.java b/java_base/org/jau/io/Bitstream.java index 8011d6e..8011d6e 100644 --- a/java/org/jau/io/Bitstream.java +++ b/java_base/org/jau/io/Bitstream.java diff --git a/java_base/org/jau/io/IOUtil.java b/java_base/org/jau/io/IOUtil.java new file mode 100644 index 0000000..d4cf576 --- /dev/null +++ b/java_base/org/jau/io/IOUtil.java @@ -0,0 +1,1358 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.io; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilePermission; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import org.jau.lang.ExceptionUtils; +import org.jau.lang.InterruptSource; +import org.jau.lang.NioUtil; +import org.jau.lang.ReflectionUtil; +import org.jau.sec.SecurityUtil; +import org.jau.sys.AndroidUtil; +import org.jau.sys.Debug; +import org.jau.sys.MachineDataInfo; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes; +import org.jau.sys.PropertyAccess; + +public class IOUtil { + public static final boolean DEBUG; + private static final boolean DEBUG_EXE; + private static final boolean DEBUG_EXE_NOSTREAM; + private static final boolean DEBUG_EXE_EXISTING_FILE; + private static final boolean testTempDirExec; + private static final boolean useNativeExeFile; + + private static final String auc_name = "org.jau.net.AssetURLContext"; + private static final ReflectionUtil.MethodAccessor aucGetRes; + + static { + final boolean _props[] = { false, false, false, false, false, false }; + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + try { + int i=0; + _props[i++] = Debug.debug("IOUtil"); + _props[i++] = PropertyAccess.isPropertyDefined("jau.debug.IOUtil.Exe", true); + _props[i++] = PropertyAccess.isPropertyDefined("jau.debug.IOUtil.Exe.NoStream", true); + // For security reasons, we have to hardcode this, i.e. disable this manual debug feature! + _props[i++] = false; // PropertyAccess.isPropertyDefined("jau.debug.IOUtil.Exe.ExistingFile", true); + _props[i++] = PropertyAccess.getBooleanProperty("jau.gluegen.TestTempDirExec", true, true); + _props[i++] = PropertyAccess.getBooleanProperty("jau.gluegen.UseNativeExeFile", true, false); + } catch (final Throwable t) { + if(_props[0]) { + ExceptionUtils.dumpThrowable("ioutil-init", t); + } + } + return null; + } + }); + { + int i=0; + DEBUG = _props[i++]; + DEBUG_EXE = _props[i++]; + DEBUG_EXE_NOSTREAM = _props[i++]; + DEBUG_EXE_EXISTING_FILE = _props[i++]; + testTempDirExec = _props[i++]; + useNativeExeFile = _props[i++]; + } + { + Class<?> auc = null; + try { + auc = ReflectionUtil.getClass(auc_name, false /* initializeClazz */, IOUtil.class.getClassLoader()); + } catch (final Throwable t) {} + if( null != auc ) { + aucGetRes = new ReflectionUtil.MethodAccessor(auc, "getResource", String.class, ClassLoader.class); + if( DEBUG ) { + System.err.println("IOUtil: Available <"+auc_name+">, getResource avail "+(null != aucGetRes ? aucGetRes.available() : false)); + } + } else { + aucGetRes = null; + if( DEBUG ) { + System.err.println("IOUtil: Not available <"+auc_name+">"); + } + } + } + } + + /** Std. temporary directory property key <code>java.io.tmpdir</code>. */ + private static final String java_io_tmpdir_propkey = "java.io.tmpdir"; + private static final String user_home_propkey = "user.home"; + private static final String XDG_CACHE_HOME_envkey = "XDG_CACHE_HOME"; + + /** Subdirectory within platform's temporary root directory where all JogAmp related temp files are being stored: {@code jau} */ + public static final String tmpSubDir = "jau"; + + private IOUtil() {} + + /*** + * + * STREAM COPY STUFF + * + */ + + /** + * Copy the specified URL resource to the specified output file. The total + * number of bytes written is returned. + * + * @param conn the open URLConnection + * @param outFile the destination + * @return + * @throws IOException + */ + public static int copyURLConn2File(final URLConnection conn, final File outFile) throws IOException { + conn.connect(); // redundant + + int totalNumBytes = 0; + final InputStream in = new BufferedInputStream(conn.getInputStream()); + try { + totalNumBytes = copyStream2File(in, outFile, conn.getContentLength()); + } finally { + in.close(); + } + return totalNumBytes; + } + + /** + * Copy the specified input stream to the specified output file. The total + * number of bytes written is returned. + * + * @param in the source + * @param outFile the destination + * @param totalNumBytes informal number of expected bytes, maybe used for user feedback while processing. -1 if unknown + * @return + * @throws IOException + */ + public static int copyStream2File(final InputStream in, final File outFile, int totalNumBytes) throws IOException { + final OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); + try { + totalNumBytes = copyStream2Stream(in, out, totalNumBytes); + } finally { + out.close(); + } + return totalNumBytes; + } + + /** + * Copy the specified input stream to the specified output stream. The total + * number of bytes written is returned. + * + * @param in the source + * @param out the destination + * @param totalNumBytes informal number of expected bytes, maybe used for user feedback while processing. -1 if unknown + * @return + * @throws IOException + */ + public static int copyStream2Stream(final InputStream in, final OutputStream out, final int totalNumBytes) throws IOException { + return copyStream2Stream(PlatformProps.MACH_DESC_STAT.pageSizeInBytes(), in, out, totalNumBytes); + } + + /** + * Copy the specified input stream to the specified output stream. The total + * number of bytes written is returned. + * + * @param bufferSize the intermediate buffer size, should be {@link MachineDataInfo#pageSizeInBytes()} for best performance. + * @param in the source + * @param out the destination + * @param totalNumBytes informal number of expected bytes, maybe used for user feedback while processing. -1 if unknown + * @return + * @throws IOException + */ + public static int copyStream2Stream(final int bufferSize, final InputStream in, final OutputStream out, final int totalNumBytes) throws IOException { + final byte[] buf = new byte[bufferSize]; + int numBytes = 0; + while (true) { + int count; + if ((count = in.read(buf)) == -1) { + break; + } + out.write(buf, 0, count); + numBytes += count; + } + return numBytes; + } + + public static StringBuilder appendCharStream(final StringBuilder sb, final Reader r) throws IOException { + final char[] cbuf = new char[1024]; + int count; + while( 0 < ( count = r.read(cbuf) ) ) { + sb.append(cbuf, 0, count); + } + return sb; + } + + /** + * Copy the specified input stream to a byte array, which is being returned. + */ + public static byte[] copyStream2ByteArray(InputStream stream) throws IOException { + if( !(stream instanceof BufferedInputStream) ) { + stream = new BufferedInputStream(stream); + } + int totalRead = 0; + int avail = stream.available(); + byte[] data = new byte[avail]; + int numRead = 0; + do { + if (totalRead + avail > data.length) { + final byte[] newData = new byte[totalRead + avail]; + System.arraycopy(data, 0, newData, 0, totalRead); + data = newData; + } + numRead = stream.read(data, totalRead, avail); + if (numRead >= 0) { + totalRead += numRead; + } + avail = stream.available(); + } while (avail > 0 && numRead >= 0); + + // just in case the announced avail > totalRead + if (totalRead != data.length) { + final byte[] newData = new byte[totalRead]; + System.arraycopy(data, 0, newData, 0, totalRead); + data = newData; + } + return data; + } + + /** + * Copy the specified input stream to a NIO ByteBuffer w/ native byte order, which is being returned. + * <p>The implementation creates the ByteBuffer w/ {@link #copyStream2ByteArray(InputStream)}'s returned byte array.</p> + * + * @param stream input stream, which will be wrapped into a BufferedInputStream, if not already done. + */ + public static ByteBuffer copyStream2ByteBuffer(final InputStream stream) throws IOException { + return copyStream2ByteBuffer(stream, -1); + } + + /** + * Copy the specified input stream to a NIO ByteBuffer w/ native byte order, which is being returned. + * <p>The implementation creates the ByteBuffer w/ {@link #copyStream2ByteArray(InputStream)}'s returned byte array.</p> + * + * @param stream input stream, which will be wrapped into a BufferedInputStream, if not already done. + * @param initialCapacity initial buffer capacity in bytes, if > available bytes + */ + public static ByteBuffer copyStream2ByteBuffer(InputStream stream, int initialCapacity) throws IOException { + if( !(stream instanceof BufferedInputStream) ) { + stream = new BufferedInputStream(stream); + } + int avail = stream.available(); + if( initialCapacity < avail ) { + initialCapacity = avail; + } + final MachineDataInfo machine = PlatformProps.MACH_DESC_STAT; + ByteBuffer data = NioUtil.newNativeByteBuffer( machine.pageAlignedSize( initialCapacity ) ); + final byte[] chunk = new byte[machine.pageSizeInBytes()]; + int chunk2Read = Math.min(machine.pageSizeInBytes(), avail); + int numRead = 0; + do { + if (avail > data.remaining()) { + final ByteBuffer newData = NioUtil.newNativeByteBuffer( + machine.pageAlignedSize(data.position() + avail) ); + newData.put(data); + data = newData; + } + + numRead = stream.read(chunk, 0, chunk2Read); + if (numRead > 0) { + data.put(chunk, 0, numRead); + } + avail = stream.available(); + chunk2Read = Math.min(machine.pageSizeInBytes(), avail); + } while ( numRead > 0 ); // EOS: -1 == numRead, EOF maybe reached earlier w/ 0 == numRead + + data.flip(); + return data; + } + + /*** + * + * RESOURCE / FILE NAME STUFF + * + */ + + private static final Pattern patternSingleBS = Pattern.compile("\\\\{1}"); + + /** + * + * @param path + * @param startWithSlash + * @param endWithSlash + * @return + * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> + */ + public static String slashify(final String path, final boolean startWithSlash, final boolean endWithSlash) throws URISyntaxException { + String p = patternSingleBS.matcher(path).replaceAll("/"); + if (startWithSlash && !p.startsWith("/")) { + p = "/" + p; + } + if (endWithSlash && !p.endsWith("/")) { + p = p + "/"; + } + return cleanPathString(p); + } + + /** + * Returns the lowercase suffix of the given file name (the text + * after the last '.' in the file name). Returns null if the file + * name has no suffix. Only operates on the given file name; + * performs no I/O operations. + * + * @param file name of the file + * @return lowercase suffix of the file name + * @throws NullPointerException if file is null + */ + + public static String getFileSuffix(final File file) { + return getFileSuffix(file.getName()); + } + + /** + * Returns the lowercase suffix of the given file name (the text + * after the last '.' in the file name). Returns null if the file + * name has no suffix. Only operates on the given file name; + * performs no I/O operations. + * + * @param filename name of the file + * @return lowercase suffix of the file name + * @throws NullPointerException if filename is null + */ + public static String getFileSuffix(final String filename) { + final int lastDot = filename.lastIndexOf('.'); + if (lastDot < 0) { + return null; + } + return toLowerCase(filename.substring(lastDot + 1)); + } + private static String toLowerCase(final String arg) { + if (arg == null) { + return null; + } + + return arg.toLowerCase(); + } + + /*** + * @param file + * @param allowOverwrite + * @return outputStream The resulting output stream + * @throws IOException if the file already exists and <code>allowOverwrite</code> is false, + * the class {@link java.io.FileOutputStream} is not accessible or + * the user does not have sufficient rights to access the local filesystem. + */ + public static FileOutputStream getFileOutputStream(final File file, final boolean allowOverwrite) throws IOException { + if (file.exists() && !allowOverwrite) { + throw new IOException("File already exists (" + file + ") and overwrite=false"); + } + try { + return new FileOutputStream( file ); + } catch (final Exception e) { + throw new IOException("error opening " + file + " for write. ", e); + } + } + + public static String getClassFileName(final String clazzBinName) { + // or return clazzBinName.replace('.', File.separatorChar) + ".class"; ? + return clazzBinName.replace('.', '/') + ".class"; + } + + /** + * @param clazzBinName com.jogamp.common.util.cache.TempJarCache + * @param cl ClassLoader to locate the JarFile + * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + * @throws IOException if the jar file could not been found by the ClassLoader + */ + public static URL getClassURL(final String clazzBinName, final ClassLoader cl) throws IOException { + final URL url = cl.getResource(getClassFileName(clazzBinName)); + if(null == url) { + throw new IOException("Cannot not find: "+clazzBinName); + } + return url; + } + + /** + * Returns the basename of the given fname w/o directory part + * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> + */ + public static String getBasename(String fname) throws URISyntaxException { + fname = slashify(fname, false /* startWithSlash */, false /* endWithSlash */); + final int lios = fname.lastIndexOf('/'); // strip off dirname + if(lios>=0) { + fname = fname.substring(lios+1); + } + return fname; + } + + /** + * Returns unified '/' dirname including the last '/' + * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> + */ + public static String getDirname(String fname) throws URISyntaxException { + fname = slashify(fname, false /* startWithSlash */, false /* endWithSlash */); + final int lios = fname.lastIndexOf('/'); // strip off dirname + if(lios>=0) { + fname = fname.substring(0, lios+1); + } + return fname; + } + + /*** + * + * RESOURCE LOCATION HELPER + * + */ + + /** + * Helper compound associating a class instance and resource paths + * to be {@link #resolve(int) resolved} at a later time. + */ + public static class ClassResources { + /** Optional {@link ClassLoader} used to {@link #resolve(int)} {@link #resourcePaths}. */ + public final ClassLoader classLoader; + + /** Optional class instance used to {@link #resolve(int)} relative {@link #resourcePaths}. */ + public final Class<?> contextCL; + + /** Resource paths, see {@link #resolve(int)}. */ + public final String[] resourcePaths; + + /** Returns the number of resources, i.e. <code>resourcePaths.length</code>. */ + public final int resourceCount() { return resourcePaths.length; } + + /** + * @param resourcePaths multiple relative or absolute resource locations + * @param classLoader optional {@link ClassLoader}, see {@link IOUtil#getResource(String, ClassLoader, Class)} + * @param relContext optional relative context, see {@link IOUtil#getResource(String, ClassLoader, Class)} + */ + public ClassResources(final String[] resourcePaths, final ClassLoader classLoader, final Class<?> relContext) { + for(int i=resourcePaths.length-1; i>=0; i--) { + if( null == resourcePaths[i] ) { + throw new IllegalArgumentException("resourcePath["+i+"] is null"); + } + } + this.classLoader = classLoader; + this.contextCL = relContext; + this.resourcePaths = resourcePaths; + } + + /** + * Resolving one of the {@link #resourcePaths} indexed by <code>uriIndex</code> using + * {@link #classLoader}, {@link #contextCL} through {@link IOUtil#getResource(String, ClassLoader, Class)}. + * @throws ArrayIndexOutOfBoundsException if <code>uriIndex</code> is < 0 or >= {@link #resourceCount()}. + */ + public URLConnection resolve(final int uriIndex) throws ArrayIndexOutOfBoundsException { + return getResource(resourcePaths[uriIndex], classLoader, contextCL); + } + } + + /** + * Locating a resource using {@link #getResource(String, ClassLoader)}: + * <ul> + * <li><i>relative</i>: <code>relContext</code>'s package name-path plus <code>resourcePath</code> via <code>classLoader</code>. + * This allows locations relative to JAR- and other URLs. + * The <code>resourcePath</code> may start with <code>../</code> to navigate to parent folder. + * This attempt is skipped if {@code relContext} is {@code null}.</li> + * <li><i>absolute</i>: <code>resourcePath</code> as is via <code>classLoader</code>. + * </ul> + * <p> + * Returns the resolved and open URLConnection or null if not found. + * </p> + * + * @param resourcePath the resource path to locate relative or absolute + * @param classLoader the optional {@link ClassLoader}, recommended + * @param relContext relative context, i.e. position, of the {@code resourcePath}, + * to perform the relative lookup, if not {@code null}. + * @see #getResource(String, ClassLoader) + * @see ClassLoader#getResource(String) + * @see ClassLoader#getSystemResource(String) + */ + public static URLConnection getResource(final String resourcePath, final ClassLoader classLoader, final Class<?> relContext) { + if(null == resourcePath) { + return null; + } + URLConnection conn = null; + if(null != relContext) { + // scoping the path within the class's package + final String className = relContext.getName().replace('.', '/'); + final int lastSlash = className.lastIndexOf('/'); + if (lastSlash >= 0) { + final String pkgName = className.substring(0, lastSlash + 1); + conn = getResource(pkgName + resourcePath, classLoader); + if(DEBUG) { + System.err.println("IOUtil: found <"+resourcePath+"> within class package <"+pkgName+"> of given class <"+relContext.getName()+">: "+(null!=conn)); + } + } + } else if(DEBUG) { + System.err.println("IOUtil: null context, skip rel. lookup"); + } + if(null == conn) { + conn = getResource(resourcePath, classLoader); + if(DEBUG) { + System.err.println("IOUtil: found <"+resourcePath+"> by classloader: "+(null!=conn)); + } + } + return conn; + } + + /** + * Locating a resource using the ClassLoader's facilities and {@link org.jau.net.AssetURLContext}. + * <p> + * Returns the resolved and connected URLConnection or null if not found. + * </p> + * <p> + * Return null if {@link org.jau.net.AssetURLContext} is not available. + * </p> + * + * @see ClassLoader#getResource(String) + * @see ClassLoader#getSystemResource(String) + * @see URL#URL(String) + * @see File#File(String) + */ + public static URLConnection getResource(final String resourcePath, final ClassLoader cl) { + if( null != aucGetRes && aucGetRes.available() ) { + return aucGetRes.callStaticMethod(resourcePath, cl); + } else { + return null; + } + } + + /** + * Generates a path for the 'relativeFile' relative to the 'baseLocation'. + * + * @param baseLocation denotes a directory + * @param relativeFile denotes a relative file to the baseLocation + * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> + */ + public static String getRelativeOf(final File baseLocation, final String relativeFile) throws URISyntaxException { + if(null == relativeFile) { + return null; + } + + if (baseLocation != null) { + final File file = new File(baseLocation, relativeFile); + // Handle things on Windows + return slashify(file.getPath(), false /* startWithSlash */, false /* endWithSlash */); + } + return null; + } + + /** + * @param path assuming a slashified path, either denotes a file or directory, either relative or absolute. + * @return parent of path + * @throws URISyntaxException if path is empty or has no parent directory available + */ + public static String getParentOf(final String path) throws URISyntaxException { + final int pl = null!=path ? path.length() : 0; + if(pl == 0) { + throw new IllegalArgumentException("path is empty <"+path+">"); + } + + final int e = path.lastIndexOf("/"); + if( e < 0 ) { + throw new URISyntaxException(path, "path contains no '/': <"+path+">"); + } + if( e == 0 ) { + // path is root directory + throw new URISyntaxException(path, "path has no parents: <"+path+">"); + } + if( e < pl - 1 ) { + // path is file, return it's parent directory + return path.substring(0, e+1); + } + final int j = path.lastIndexOf("!") + 1; // '!' Separates JARFile entry -> local start of path + // path is a directory .. + final int p = path.lastIndexOf("/", e-1); + if( p >= j) { + // parent itself has '/' - post '!' or no '!' at all + return path.substring(0, p+1); + } else { + // parent itself has no '/' + final String parent = path.substring(j, e); + if( parent.equals("..") ) { + throw new URISyntaxException(path, "parent is unresolved: <"+path+">"); + } else { + // parent is '!' or empty (relative path) + return path.substring(0, j); + } + } + } + + /** + * @param path assuming a slashified path, either denoting a file or directory, either relative or absolute. + * @return clean path string where {@code ./} and {@code ../} is resolved, + * while keeping a starting {@code ../} at the beginning of a relative path. + * @throws URISyntaxException if path is empty or has no parent directory available while resolving <code>../</code> + */ + public static String cleanPathString(String path) throws URISyntaxException { + // Resolve './' before '../' to handle case 'parent/./../a.txt' properly. + int idx = path.length() - 1; + while ( idx >= 1 && ( idx = path.lastIndexOf("./", idx) ) >= 0 ) { + if( 0 < idx && path.charAt(idx-1) == '.' ) { + idx-=2; // skip '../' -> idx upfront + } else { + path = path.substring(0, idx) + path.substring(idx+2); + idx--; // idx upfront + } + } + idx = 0; + while ( ( idx = path.indexOf("../", idx) ) >= 0 ) { + if( 0 == idx ) { + idx += 3; // skip starting '../' + } else { + path = getParentOf(path.substring(0, idx)) + path.substring(idx+3); + idx = 0; + } + } + return path; + } + + public static final Pattern patternSpaceEnc = Pattern.compile("%20"); + + /** + * Returns the connected URLConnection, or null if not url is not available + */ + public static URLConnection openURL(final URL url) { + return openURL(url, "."); + } + + /** + * Returns the connected URLConnection, or null if not url is not available + */ + public static URLConnection openURL(final URL url, final String dbgmsg) { + if(null!=url) { + try { + final URLConnection c = url.openConnection(); + c.connect(); // redundant + if(DEBUG) { + System.err.println("IOUtil: urlExists("+url+") ["+dbgmsg+"] - true"); + } + return c; + } catch (final IOException ioe) { + if(DEBUG) { + ExceptionUtils.dumpThrowable("IOUtil: urlExists("+url+") ["+dbgmsg+"] - false -", ioe); + } + } + } else if(DEBUG) { + System.err.println("IOUtil: no url - urlExists(null) ["+dbgmsg+"]"); + } + + return null; + } + + private static String getExeTestFileSuffix() { + switch(PlatformProps.OS) { + case WINDOWS: + if( useNativeExeFile && PlatformTypes.CPUFamily.X86 == PlatformProps.CPU.family ) { + return ".exe"; + } else { + return ".bat"; + } + default: + return ".sh"; + } + } + private static String getExeTestShellCode() { + switch(PlatformProps.OS) { + case WINDOWS: + return "echo off"+PlatformProps.NEWLINE; + default: + return "#!/bin/true"+PlatformProps.NEWLINE; + } + } + private static String[] getExeTestCommandArgs(final String scriptFile) { + switch(PlatformProps.OS) { + case WINDOWS: + // return new String[] { "cmd", "/c", scriptFile }; + default: + return new String[] { scriptFile }; + } + } + + private static void fillExeTestFile(final File exefile) throws IOException { + final String shellCode = getExeTestShellCode(); + if( isStringSet(shellCode) ) { + final FileWriter fout = new FileWriter(exefile); + try { + fout.write(shellCode); + try { + fout.flush(); + } catch (final IOException sfe) { + ExceptionUtils.dumpThrowable("", sfe); + } + } finally { + fout.close(); + } + } + } + private static boolean getOSHasNoexecFS() { + switch(PlatformProps.OS) { + case OPENKODE: + return false; + + default: + return true; + } + } + + /** + * @see <a href="http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html">Free-Desktop - XDG Base Directory Specification</a> + */ + private static boolean getOSHasFreeDesktopXDG() { + switch(PlatformProps.OS) { + case ANDROID: + case MACOS: + case IOS: + case WINDOWS: + case OPENKODE: + return false; + + default: + return true; + } + } + + /** + * Test whether {@code file} exists and matches the given requirements + * + * @param file + * @param shallBeDir + * @param shallBeWritable + * @return + */ + public static boolean testFile(final File file, final boolean shallBeDir, final boolean shallBeWritable) { + if (!file.exists()) { + if(DEBUG) { + System.err.println("IOUtil.testFile: <"+file.getAbsolutePath()+">: does not exist"); + } + return false; + } + if (shallBeDir && !file.isDirectory()) { + if(DEBUG) { + System.err.println("IOUtil.testFile: <"+file.getAbsolutePath()+">: is not a directory"); + } + return false; + } + if (shallBeWritable && !file.canWrite()) { + if(DEBUG) { + System.err.println("IOUtil.testFile: <"+file.getAbsolutePath()+">: is not writable"); + } + return false; + } + return true; + } + + public static class StreamMonitor implements Runnable { + private final InputStream[] istreams; + private final boolean[] eos; + private final PrintStream ostream; + private final String prefix; + public StreamMonitor(final InputStream[] streams, final PrintStream ostream, final String prefix) { + this.istreams = streams; + this.eos = new boolean[streams.length]; + this.ostream = ostream; + this.prefix = prefix; + final InterruptSource.Thread t = new InterruptSource.Thread(null, this, "StreamMonitor-"+Thread.currentThread().getName()); + t.setDaemon(true); + t.start(); + } + + @Override + public void run() + { + final byte[] buffer = new byte[4096]; + try { + final int streamCount = istreams.length; + int eosCount = 0; + do { + for(int i=0; i<istreams.length; i++) { + if( !eos[i] ) { + final int numReadI = istreams[i].read(buffer); + if (numReadI > 0) { + if( null != ostream ) { + if( null != prefix ) { + ostream.write(prefix.getBytes()); + } + ostream.write(buffer, 0, numReadI); + } + } else { + // numReadI == -1 + eosCount++; + eos[i] = true; + } + } + } + if( null != ostream ) { + ostream.flush(); + } + } while ( eosCount < streamCount ); + } catch (final IOException e) { + } finally { + if( null != ostream ) { + ostream.flush(); + } + // Should allow clean exit when process shuts down + } + } + } + + private static final Boolean isNioExecutableFile(final File file) { + try { + return java.nio.file.Files.isExecutable( file.toPath() ); + } catch (final Throwable t) { + throw new RuntimeException("error invoking Files.isExecutable(file.toPath())", t); + } + } + + /** + * Returns true if the given {@code dir} + * <ol> + * <li>exists, and</li> + * <li>is a directory, and</li> + * <li>is writeable, and</li> + * <li>files can be executed from the directory</li> + * </ol> + * + * @throws SecurityException if file creation and process execution is not allowed within the current security context + * @param dir + */ + public static boolean testDirExec(final File dir) + throws SecurityException + { + final boolean debug = DEBUG_EXE || DEBUG; + + if( !testTempDirExec ) { + if(DEBUG) { + System.err.println("IOUtil.testDirExec: <"+dir.getAbsolutePath()+">: Disabled TestTempDirExec"); + } + return false; + } + if (!testFile(dir, true, true)) { + if( debug ) { + System.err.println("IOUtil.testDirExec: <"+dir.getAbsolutePath()+">: Not writeable dir"); + } + return false; + } + if(!getOSHasNoexecFS()) { + if( debug ) { + System.err.println("IOUtil.testDirExec: <"+dir.getAbsolutePath()+">: Always executable"); + } + return true; + } + + final long t0 = debug ? System.currentTimeMillis() : 0; + final File exeTestFile; + final boolean existingExe; + try { + final File permExeTestFile = DEBUG_EXE_EXISTING_FILE ? new File(dir, "jau_exe_tst"+getExeTestFileSuffix()) : null; + if( null != permExeTestFile && permExeTestFile.exists() ) { + exeTestFile = permExeTestFile; + existingExe = true; + } else { + exeTestFile = File.createTempFile("jau_exe_tst", getExeTestFileSuffix(), dir); + existingExe = false; + fillExeTestFile(exeTestFile); + } + } catch (final SecurityException se) { + throw se; // fwd Security exception + } catch (final IOException e) { + if( debug ) { + e.printStackTrace(); + } + return false; + } + final long t1 = debug ? System.currentTimeMillis() : 0; + long t2; + int res = -1; + int exitValue = -1; + Boolean isNioExec = null; + if( existingExe || exeTestFile.setExecutable(true /* exec */, true /* ownerOnly */) ) { + t2 = debug ? System.currentTimeMillis() : 0; + // First soft exec test via NIO's ACL check, if available + isNioExec = isNioExecutableFile(exeTestFile); + if( null != isNioExec ) { + res = isNioExec.booleanValue() ? 0 : -1; + } + if( null == isNioExec || 0 <= res ) { + // Hard exec test via actual execution, if NIO's ACL check succeeded or not available. + // Required, since Windows 'Software Restriction Policies (SRP)' won't be triggered merely by NIO's ACL check. + Process pr = null; + try { + // Using 'Process.exec(String[])' avoids StringTokenizer of 'Process.exec(String)' + // and hence splitting up command by spaces! + // Note: All no-exec cases throw an IOExceptions at ProcessBuilder.start(), i.e. below exec() call! + pr = Runtime.getRuntime().exec( getExeTestCommandArgs( exeTestFile.getCanonicalPath() ), null, null ); + if( DEBUG_EXE && !DEBUG_EXE_NOSTREAM ) { + new StreamMonitor(new InputStream[] { pr.getInputStream(), pr.getErrorStream() }, System.err, "Exe-Tst: "); + } + pr.waitFor(); + exitValue = pr.exitValue(); // Note: Bug 1219 Comment 50: On reporter's machine exit value 1 is being returned + if( 0 == exitValue ) { + res++; // file has been executed and exited normally + } else { + res = -2; // abnormal termination + } + } catch (final SecurityException se) { + throw se; // fwd Security exception + } catch (final Throwable t) { + t2 = debug ? System.currentTimeMillis() : 0; + res = -3; + if( debug ) { + System.err.println("IOUtil.testDirExec: <"+exeTestFile.getAbsolutePath()+">: Caught "+t.getClass().getSimpleName()+": "+t.getMessage()); + t.printStackTrace(); + } + } finally { + if( null != pr ) { + // Bug 1219 Comment 58: Ensure that the launched process gets terminated! + // This is Process implementation specific and varies on different platforms, + // hence it may be required. + try { + pr.destroy(); + } catch (final Throwable t) { + ExceptionUtils.dumpThrowable("", t); + } + } + } + } + } else { + t2 = debug ? System.currentTimeMillis() : 0; + } + + final boolean ok = 0 <= res; + if( !DEBUG_EXE && !existingExe ) { + exeTestFile.delete(); + } + if( debug ) { + final long t3 = System.currentTimeMillis(); + System.err.println("IOUtil.testDirExec(): test-exe <"+exeTestFile.getAbsolutePath()+">, existingFile "+existingExe+", isNioExec "+isNioExec+", returned "+exitValue); + System.err.println("IOUtil.testDirExec(): abs-path <"+dir.getAbsolutePath()+">: res "+res+" -> "+ok); + System.err.println("IOUtil.testDirExec(): total "+(t3-t0)+"ms, create "+(t1-t0)+"ms, fill "+(t2-t1)+"ms, execute "+(t3-t2)+"ms"); + } + return ok; + } + + private static File testDirImpl(final File dir, final boolean create, final boolean executable, final String dbgMsg) + throws SecurityException + { + final File res; + if (create && !dir.exists()) { + dir.mkdirs(); + } + if( executable ) { + res = testDirExec(dir) ? dir : null; + } else { + res = testFile(dir, true, true) ? dir : null; + } + if(DEBUG) { + System.err.println("IOUtil.testDirImpl("+dbgMsg+"): <"+dir.getAbsolutePath()+">, create "+create+", exec "+executable+": "+(null != res)); + } + return res; + } + + /** + * Returns the directory {@code dir}, which is processed and tested as described below. + * <ol> + * <li>If {@code create} is {@code true} and the directory does not exist yet, it is created incl. all sub-directories.</li> + * <li>If {@code dirName} exists, but is not a directory, {@code null} is being returned.</li> + * <li>If the directory does not exist or is not writeable, {@code null} is being returned.</li> + * <li>If {@code executable} is {@code true} and files cannot be executed from the directory, {@code null} is being returned.</li> + * </ol> + * + * @param dir the directory to process + * @param create true if the directory shall be created if not existing + * @param executable true if the user intents to launch executables from the temporary directory, otherwise false. + * @throws SecurityException if file creation and process execution is not allowed within the current security context + */ + public static File testDir(final File dir, final boolean create, final boolean executable) + throws SecurityException + { + return testDirImpl(dir, create, executable, "testDir"); + } + + private static boolean isStringSet(final String s) { return null != s && 0 < s.length(); } + + /** + * This methods finds [and creates] an available temporary sub-directory: + * <pre> + File tmpBaseDir = null; + if(null != testDir(tmpRoot, true, executable)) { // check tmpRoot first + for(int i = 0; null == tmpBaseDir && i<=9999; i++) { + final String tmpDirSuffix = String.format("_%04d", i); // 4 digits for iteration + tmpBaseDir = testDir(new File(tmpRoot, tmpSubDirPrefix+tmpDirSuffix), true, executable); + } + } else { + tmpBaseDir = null; + } + return tmpBaseDir; + * </pre> + * <p> + * The iteration through [0000-9999] ensures that the code is multi-user save. + * </p> + * @param tmpRoot + * @param executable + * @param dbgMsg + * @param tmpDirPrefix + * @return a temporary directory, writable by this user + * @throws SecurityException + */ + private static File getSubTempDir(final File tmpRoot, final String tmpSubDirPrefix, final boolean executable, final String dbgMsg) + throws SecurityException + { + File tmpBaseDir = null; + if(null != testDirImpl(tmpRoot, true /* create */, executable, dbgMsg)) { // check tmpRoot first + for(int i = 0; null == tmpBaseDir && i<=9999; i++) { + final String tmpDirSuffix = String.format((Locale)null, "_%04d", i); // 4 digits for iteration + tmpBaseDir = testDirImpl(new File(tmpRoot, tmpSubDirPrefix+tmpDirSuffix), true /* create */, executable, dbgMsg); + } + } + return tmpBaseDir; + } + + private static File getFile(final String fname) { + if( isStringSet(fname) ) { + return new File(fname); + } else { + return null; + } + + } + /** + * Returns a platform independent writable directory for temporary files + * consisting of the platform's {@code temp-root} + {@link #tmpSubDir}, + * e.g. {@code /tmp/jau_0000/}. + * <p> + * On standard Java the {@code temp-root} folder is specified by <code>java.io.tempdir</code>. + * </p> + * <p> + * On Android the {@code temp-root} folder is relative to the applications local folder + * (see {@link Context#getDir(String, int)}) is returned, if + * the Android application/activity has registered it's Application Context + * via {@link jogamp.common.os.android.StaticContext.StaticContext#init(Context, ClassLoader) StaticContext.init(..)}. + * This allows using the temp folder w/o the need for <code>sdcard</code> + * access, which would be the <code>java.io.tempdir</code> location on Android! + * </p> + * <p> + * In case {@code temp-root} is the users home folder, + * a dot is being prepended to {@link #tmpSubDir}, i.e.: {@code /home/user/.jau_0000/}. + * </p> + * @param executable true if the user intents to launch executables from the temporary directory, otherwise false. + * @throws IOException if no temporary directory could be determined + * @throws SecurityException if access to <code>java.io.tmpdir</code> is not allowed within the current security context + * + * @see PropertyAccess#getProperty(String, boolean) + * @see Context#getDir(String, int) + */ + public static File getTempDir(final boolean executable) + throws SecurityException, IOException + { + if(!tempRootSet) { // volatile: ok + synchronized(IOUtil.class) { + if(!tempRootSet) { + tempRootSet = true; + { + final File ctxTempDir = AndroidUtil.getTempRoot(); // null if ( !Android || no android-ctx ) + if(null != ctxTempDir) { + tempRootNoexec = getSubTempDir(ctxTempDir, tmpSubDir, false /* executable, see below */, "Android.ctxTemp"); + tempRootExec = tempRootNoexec; // FIXME: Android temp root is always executable (?) + return tempRootExec; + } + } + + final File java_io_tmpdir = getFile( PropertyAccess.getProperty(java_io_tmpdir_propkey, false) ); + if(DEBUG) { + System.err.println("IOUtil.getTempRoot(): tempX1 <"+java_io_tmpdir+">, used "+(null!=java_io_tmpdir)); + } + + final File user_tmpdir; // only if diff than java_io_tmpdir + { + String __user_tmpdir = System.getenv("TMPDIR"); + if( !isStringSet(__user_tmpdir) ) { + __user_tmpdir = System.getenv("TEMP"); + } + final File _user_tmpdir = getFile(__user_tmpdir); + if( null != _user_tmpdir && !_user_tmpdir.equals(java_io_tmpdir) ) { + user_tmpdir = _user_tmpdir; + } else { + user_tmpdir = null; + } + if(DEBUG) { + System.err.println("IOUtil.getTempRoot(): tempX3 <"+_user_tmpdir+">, used "+(null!=user_tmpdir)); + } + } + + final File user_home = getFile( PropertyAccess.getProperty(user_home_propkey, false) ); + if(DEBUG) { + System.err.println("IOUtil.getTempRoot(): tempX4 <"+user_home+">, used "+(null!=user_home)); + } + + final File xdg_cache_home; + { + String __xdg_cache_home; + if( getOSHasFreeDesktopXDG() ) { + __xdg_cache_home = System.getenv(XDG_CACHE_HOME_envkey); + if( !isStringSet(__xdg_cache_home) && null != user_home ) { + __xdg_cache_home = user_home.getAbsolutePath() + File.separator + ".cache" ; // default + } + } else { + __xdg_cache_home = null; + } + final File _xdg_cache_home = getFile(__xdg_cache_home); + if( null != _xdg_cache_home && !_xdg_cache_home.equals(java_io_tmpdir) ) { + xdg_cache_home = _xdg_cache_home; + } else { + xdg_cache_home = null; + } + if(DEBUG) { + System.err.println("IOUtil.getTempRoot(): tempX2 <"+_xdg_cache_home+">, used "+(null!=xdg_cache_home)); + } + } + + // 1) java.io.tmpdir/jau + if( null == tempRootExec && null != java_io_tmpdir ) { + if( PlatformTypes.OSType.MACOS == PlatformProps.OS || PlatformTypes.OSType.IOS == PlatformProps.OS ) { + // Bug 865: Safari >= 6.1 [OSX] May employ xattr on 'com.apple.quarantine' on 'PluginProcess.app' + // We attempt to fix this issue _after_ gluegen native lib is loaded, see JarUtil.fixNativeLibAttribs(File). + tempRootExec = getSubTempDir(java_io_tmpdir, tmpSubDir, false /* executable */, "tempX1"); + } else { + tempRootExec = getSubTempDir(java_io_tmpdir, tmpSubDir, true /* executable */, "tempX1"); + } + } + + // 2) $XDG_CACHE_HOME/jau + if( null == tempRootExec && null != xdg_cache_home ) { + tempRootExec = getSubTempDir(xdg_cache_home, tmpSubDir, true /* executable */, "tempX2"); + } + + // 3) $TMPDIR/jau + if( null == tempRootExec && null != user_tmpdir ) { + tempRootExec = getSubTempDir(user_tmpdir, tmpSubDir, true /* executable */, "tempX3"); + } + + // 4) $HOME/.jau + if( null == tempRootExec && null != user_home ) { + tempRootExec = getSubTempDir(user_home, "." + tmpSubDir, true /* executable */, "tempX4"); + } + + if( null != tempRootExec ) { + tempRootNoexec = tempRootExec; + } else { + // 1) java.io.tmpdir/jau + if( null == tempRootNoexec && null != java_io_tmpdir ) { + tempRootNoexec = getSubTempDir(java_io_tmpdir, tmpSubDir, false /* executable */, "temp01"); + } + + // 2) $XDG_CACHE_HOME/jau + if( null == tempRootNoexec && null != xdg_cache_home ) { + tempRootNoexec = getSubTempDir(xdg_cache_home, tmpSubDir, false /* executable */, "temp02"); + } + + // 3) $TMPDIR/jau + if( null == tempRootNoexec && null != user_tmpdir ) { + tempRootNoexec = getSubTempDir(user_tmpdir, tmpSubDir, false /* executable */, "temp03"); + } + + // 4) $HOME/.jau + if( null == tempRootNoexec && null != user_home ) { + tempRootNoexec = getSubTempDir(user_home, "." + tmpSubDir, false /* executable */, "temp04"); + } + } + + if(DEBUG) { + final String tempRootExecAbsPath = null != tempRootExec ? tempRootExec.getAbsolutePath() : null; + final String tempRootNoexecAbsPath = null != tempRootNoexec ? tempRootNoexec.getAbsolutePath() : null; + System.err.println("IOUtil.getTempRoot(): temp dirs: exec: "+tempRootExecAbsPath+", noexec: "+tempRootNoexecAbsPath); + } + } + } + } + final File r = executable ? tempRootExec : tempRootNoexec ; + if(null == r) { + final String exe_s = executable ? "executable " : ""; + throw new IOException("Could not determine a temporary "+exe_s+"directory"); + } + final FilePermission fp = new FilePermission(r.getAbsolutePath(), "read,write,delete"); + SecurityUtil.checkPermission(fp); + return r; + } + private static File tempRootExec = null; // writeable and executable + private static File tempRootNoexec = null; // writeable, maybe executable + private static volatile boolean tempRootSet = false; + + /** + * Utilizing {@link File#createTempFile(String, String, File)} using + * {@link #getTempDir(boolean)} as the directory parameter, ie. location + * of the root temp folder. + * + * @see File#createTempFile(String, String) + * @see File#createTempFile(String, String, File) + * @see #getTempDir(boolean) + * + * @param prefix + * @param suffix + * @param executable true if the temporary root folder needs to hold executable files, otherwise false. + * @return + * @throws IllegalArgumentException + * @throws IOException if no temporary directory could be determined or temp file could not be created + * @throws SecurityException + */ + public static File createTempFile(final String prefix, final String suffix, final boolean executable) + throws IllegalArgumentException, IOException, SecurityException + { + return File.createTempFile( prefix, suffix, getTempDir(executable) ); + } + + public static void close(final Closeable stream, final boolean throwRuntimeException) throws RuntimeException { + if(null != stream) { + try { + stream.close(); + } catch (final IOException e) { + if(throwRuntimeException) { + throw new RuntimeException(e); + } else if(DEBUG) { + System.err.println("Caught Exception: "); + e.printStackTrace(); + } + } + } + } + + /** + * Helper to simplify closing {@link Closeable}s. + * + * @param stream the {@link Closeable} instance to close + * @param saveOneIfFree cache for one {@link IOException} to store, if not already used (excess) + * @param dumpExcess dump the excess {@link IOException} on this {@link PrintStream} + * @return the excess {@link IOException} or {@code null}. + */ + public static IOException close(final Closeable stream, final IOException[] saveOneIfFree, final PrintStream dumpExcess) { + try { + stream.close(); + } catch(final IOException e) { + if( null == saveOneIfFree[0] ) { + saveOneIfFree[0] = e; + } else { + if( null != dumpExcess ) { + dumpExcess.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(dumpExcess); + } + return e; + } + } + return null; + } + + /** + * Retrieve the list of all filenames traversing through given paths + * @param paths list of paths to traverse through, containing directories and files + * @param excludes optional list of exclude {@link Pattern}. All {@link Pattern#matcher(CharSequence) matching} files or directories will be omitted. Maybe be null or empty. + * @param includes optional list of explicit include {@link Pattern}. If given, only {@link Pattern#matcher(CharSequence) matching} files will be returned, otherwise all occurring. + * @return list of unsorted filenames within given paths + */ + public static ArrayList<String> filesOf(final List<String> paths, final List<Pattern> excludes, final List<Pattern> includes) { + final ArrayList<String> files = new ArrayList<String>(paths.size()*32); + final ArrayList<String> todo = new ArrayList<String>(paths); + while(todo.size() > 0) { + final String p = todo.remove(0); + if( null != excludes && excludes.size() > 0) { + boolean exclude = false; + for(int i=0; !exclude && i<excludes.size(); i++) { + exclude = excludes.get(i).matcher(p).matches(); + if( DEBUG ) { + if( exclude ) { + System.err.println("IOUtil.filesOf(): excluding <"+p+"> (exclude["+i+"]: "+excludes.get(i)+")"); + } + } + } + if( exclude ) { + continue; // skip further processing, continue w/ next path + } + } + final File f = new File(p); + if( !f.exists() ) { + if( DEBUG ) { + System.err.println("IOUtil.filesOf(): not existing: "+f); + } + continue; + } else if( f.isDirectory() ) { + final String[] subs = f.list(); + if( null == subs ) { + if( DEBUG ) { + System.err.println("IOUtil.filesOf(): null list of directory: "+f); + } + } else if( 0 == subs.length ) { + if( DEBUG ) { + System.err.println("IOUtil.filesOf(): empty list of directory: "+f); + } + } else { + int j=0; + final String pp = p.endsWith("/") ? p : p+"/"; + for(int i=0; i<subs.length; i++) { + todo.add(j++, pp+subs[i]); // add 'in-place' to soothe the later sorting algorithm + } + } + } else { + if( null != includes && includes.size() > 0) { + boolean include = false; + for(int i=0; !include && i<includes.size(); i++) { + include = includes.get(i).matcher(p).matches(); + if( DEBUG ) { + if( include ) { + System.err.println("IOUtil.filesOf(): including <"+p+"> (including["+i+"]: "+includes.get(i)+")"); + } + } + } + if( include ) { + files.add(p); + } + } else { + files.add(p); + } + } + } + return files; + } +} diff --git a/java_base/org/jau/io/MappedByteBufferInputStream.java b/java_base/org/jau/io/MappedByteBufferInputStream.java new file mode 100644 index 0000000..96af6b3 --- /dev/null +++ b/java_base/org/jau/io/MappedByteBufferInputStream.java @@ -0,0 +1,927 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.RandomAccessFile; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +import org.jau.lang.NioUtil; +import org.jau.sys.Debug; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes; + +/** + * An {@link InputStream} implementation based on an underlying {@link FileChannel}'s memory mapped {@link ByteBuffer}, + * {@link #markSupported() supporting} {@link #mark(int) mark} and {@link #reset()}. + * <p> + * Implementation allows full memory mapped {@link ByteBuffer} coverage via {@link FileChannel#map(MapMode, long, long) FileChannel} + * beyond its size limitation of {@link Integer#MAX_VALUE} utilizing an array of {@link ByteBuffer} slices.<br> + * </p> + * <p> + * Implementation further allows full random access via {@link #position()} and {@link #position(long)} + * and accessing the memory mapped {@link ByteBuffer} slices directly via {@link #currentSlice()} and {@link #nextSlice()}. + * </p> + * @since 2.3.0 + */ +public class MappedByteBufferInputStream extends InputStream { + public static enum CacheMode { + /** + * Keep all previous lazily cached buffer slices alive, useful for hopping readers, + * i.e. random access via {@link MappedByteBufferInputStream#position(long) position(p)} + * or {@link MappedByteBufferInputStream#reset() reset()}. + * <p> + * Note that without flushing, the platform may fail memory mapping + * due to virtual address space exhaustion.<br> + * In such case an {@link OutOfMemoryError} may be thrown directly, + * or encapsulated as the {@link IOException#getCause() the cause} + * of a thrown {@link IOException}. + * </p> + */ + FLUSH_NONE, + /** + * Soft flush the previous lazily cached buffer slice when caching the next buffer slice, + * useful for sequential forward readers, as well as for hopping readers like {@link #FLUSH_NONE} + * in case of relatively short periods between hopping across slices. + * <p> + * Implementation clears the buffer slice reference + * while preserving a {@link WeakReference} to allow its resurrection if not yet + * {@link System#gc() garbage collected}. + * </p> + */ + FLUSH_PRE_SOFT, + /** + * Hard flush the previous lazily cached buffer slice when caching the next buffer slice, + * useful for sequential forward readers. + * <p> + * Besides clearing the buffer slice reference, + * implementation attempts to hard flush the mapped buffer + * using a {@code sun.misc.Cleaner} by reflection. + * In case such method does not exist nor works, implementation falls back to {@link #FLUSH_PRE_SOFT}. + * </p> + * <p> + * This is the default. + * </p> + */ + FLUSH_PRE_HARD + }; + + /** + * File resize interface allowing a file to change its size, + * e.g. via {@link RandomAccessFile#setLength(long)}. + */ + public static interface FileResizeOp { + /** + * @param newSize the new file size + * @throws IOException if file size change is not supported or any other I/O error occurs + */ + void setLength(final long newSize) throws IOException; + } + private static final FileResizeOp NoFileResize = new FileResizeOp() { + @Override + public void setLength(final long newSize) throws IOException { + throw new IOException("file size change not supported"); + } + }; + + /** + * Default slice shift, i.e. 1L << shift, denoting slice size in MiB: + * <ul> + * <li>{@link Platform#is64Bit() 64bit machines} -> 30 = 1024 MiB</li> + * <li>{@link Platform#is32Bit() 32bit machines} -> 29 = 512 MiB</li> + * </ul> + * <p> + * In case the default is too much of-used up address-space, one may choose other values: + * <ul> + * <li>29 -> 512 MiB</li> + * <li>28 -> 256 MiB</li> + * <li>27 -> 128 MiB</li> + * <li>26 -> 64 MiB</li> + * </ul> + * </p> + */ + public static final int DEFAULT_SLICE_SHIFT; + + static final boolean DEBUG; + + static { + PlatformProps.initSingleton(); + if( PlatformProps.CPU.is32Bit ) { + DEFAULT_SLICE_SHIFT = 29; + } else { + DEFAULT_SLICE_SHIFT = 30; + } + + DEBUG = Debug.debug("ByteBufferInputStream"); + } + + private final int sliceShift; + private final FileChannel fc; + private final FileChannel.MapMode mmode; + private FileResizeOp fileResizeOp = NoFileResize; + + private int sliceCount; + private ByteBuffer[] slices; + private WeakReference<ByteBuffer>[] slices2GC; + private long totalSize; + private int slicesEntries, slices2GCEntries; + private boolean synchronous; + + private int refCount; + + private CacheMode cmode; + + private int sliceIdx; + private long mark; + + final void dbgDump(final String prefix, final PrintStream out) { + int _slicesEntries = 0; + for(int i=0; i<sliceCount; i++) { + if( null != slices[i] ) { + _slicesEntries++; + } + } + int _slices2GCEntries = 0; + int _slices2GCAliveEntries = 0; + for(int i=0; i<sliceCount; i++) { + final WeakReference<ByteBuffer> ref = slices2GC[i]; + if( null != ref ) { + _slices2GCEntries++; + if( null != ref.get() ) { + _slices2GCAliveEntries++; + } + } + } + long fcSz = 0, pos = 0, rem = 0; + if( fc.isOpen() ) { + try { + fcSz = fc.size(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + if( 0 < refCount ) { + try { + pos = position(); + rem = totalSize - pos; + } catch (final IOException e) { + e.printStackTrace(); + } + } + final int sliceCount2 = null != slices ? slices.length : 0; + out.println(prefix+" refCount "+refCount+", fcSize "+fcSz+", totalSize "+totalSize); + out.println(prefix+" position "+pos+", remaining "+rem); + out.println(prefix+" mmode "+mmode+", cmode "+cmode+", fileResizeOp "+fileResizeOp); + out.println(prefix+" slice "+sliceIdx+" / "+sliceCount+" ("+sliceCount2+"), synchronous "+synchronous); + out.println(prefix+" mapped "+slicesEntries+" / "+_slicesEntries); + out.println(prefix+" GC-queue "+slices2GCEntries+" / "+_slices2GCEntries+" (alive "+_slices2GCAliveEntries+")"); + out.println(prefix+" sliceShift "+sliceShift+" -> "+(1L << sliceShift)); + } + + MappedByteBufferInputStream(final FileChannel fc, final FileChannel.MapMode mmode, final CacheMode cmode, + final int sliceShift, final long totalSize, final int currSliceIdx) throws IOException { + this.sliceShift = sliceShift; + this.fc = fc; + this.mmode = mmode; + + if( 0 > totalSize ) { + throw new IllegalArgumentException("Negative size "+totalSize); + } + // trigger notifyLengthChange + this.totalSize = -1; + this.sliceCount = 0; + notifyLengthChange( totalSize ); + + this.refCount = 1; + this.cmode = cmode; + + this.sliceIdx = currSliceIdx; + this.mark = -1; + + currentSlice().position(0); + + if( MappedByteBufferInputStream.DEBUG ) { + this.dbgDump("CTOR", System.err); + } + } + + /** + * Creates a new instance using the given {@link FileChannel}. + * <p> + * The {@link ByteBuffer} slices will be mapped lazily at first usage. + * </p> + * @param fileChannel the file channel to be mapped lazily. + * @param mmode the map mode, default is {@link FileChannel.MapMode#READ_ONLY}. + * @param cmode the caching mode, default is {@link CacheMode#FLUSH_PRE_HARD}. + * @param sliceShift the pow2 slice size, default is {@link #DEFAULT_SLICE_SHIFT}. + * @throws IOException + */ + public MappedByteBufferInputStream(final FileChannel fileChannel, + final FileChannel.MapMode mmode, + final CacheMode cmode, + final int sliceShift) throws IOException { + this(fileChannel, mmode, cmode, sliceShift, fileChannel.size(), 0); + } + + /** + * Creates a new instance using the given {@link FileChannel}, + * given mapping-mode, given cache-mode and the {@link #DEFAULT_SLICE_SHIFT}. + * <p> + * The {@link ByteBuffer} slices will be mapped lazily at first usage. + * </p> + * @param fileChannel the file channel to be used. + * @param mmode the map mode, default is {@link FileChannel.MapMode#READ_ONLY}. + * @param cmode the caching mode, default is {@link CacheMode#FLUSH_PRE_HARD}. + * @throws IOException + */ + public MappedByteBufferInputStream(final FileChannel fileChannel, final FileChannel.MapMode mmode, final CacheMode cmode) throws IOException { + this(fileChannel, mmode, cmode, DEFAULT_SLICE_SHIFT); + } + + /** + * Creates a new instance using the given {@link FileChannel}, + * {@link FileChannel.MapMode#READ_ONLY read-only} mapping mode, {@link CacheMode#FLUSH_PRE_HARD} + * and the {@link #DEFAULT_SLICE_SHIFT}. + * <p> + * The {@link ByteBuffer} slices will be mapped {@link FileChannel.MapMode#READ_ONLY} lazily at first usage. + * </p> + * @param fileChannel the file channel to be used. + * @throws IOException + */ + public MappedByteBufferInputStream(final FileChannel fileChannel) throws IOException { + this(fileChannel, FileChannel.MapMode.READ_ONLY, CacheMode.FLUSH_PRE_HARD, DEFAULT_SLICE_SHIFT); + } + + /** + * Enable or disable synchronous mode. + * <p> + * If synchronous mode is enabled, mapped buffers will be {@link #flush(boolean) flushed} + * if {@link #notifyLengthChange(long) resized}, <i>written to</i> or {@link #close() closing} in {@link FileChannel.MapMode#READ_WRITE read-write} mapping mode. + * </p> + * <p> + * If synchronous mode is enabled, {@link FileChannel#force(boolean)} is issued + * if {@link #setLength(long) resizing} or {@link #close() closing} and not in {@link FileChannel.MapMode#READ_ONLY read-only} mapping mode. + * </p> + * @param s {@code true} to enable synchronous mode + */ + public final synchronized void setSynchronous(final boolean s) { + synchronous = s; + } + /** + * Return {@link #setSynchronous(boolean) synchronous mode}. + */ + public final synchronized boolean getSynchronous() { + return synchronous ; + } + + final synchronized void checkOpen() throws IOException { + if( 0 == refCount ) { + throw new IOException("stream closed"); + } + } + + @Override + public final synchronized void close() throws IOException { + if( 0 < refCount ) { + refCount--; + if( 0 == refCount ) { + try { + cleanAllSlices( true /* syncBuffer */ ); + } finally { + flushImpl(true /* metaData */, false /* syncBuffer */); + fc.close(); + mark = -1; + sliceIdx = -1; + super.close(); + } + } + } + if( MappedByteBufferInputStream.DEBUG ) { + this.dbgDump("Close", System.err); + } + } + + final FileChannel.MapMode getMapMode() { return mmode; } + + /** + * @param fileResizeOp the new {@link FileResizeOp}. + * @throws IllegalStateException if attempting to set the {@link FileResizeOp} to a different value than before + */ + public final synchronized void setFileResizeOp(final FileResizeOp fileResizeOp) throws IllegalStateException { + if( NoFileResize != this.fileResizeOp && this.fileResizeOp != fileResizeOp ) { + throw new IllegalStateException("FileResizeOp already set, this value differs"); + } + this.fileResizeOp = null != fileResizeOp ? fileResizeOp : NoFileResize; + } + + /** + * Resize the underlying {@link FileChannel}'s size and adjusting this instance + * via {@link #notifyLengthChange(long) accordingly}. + * <p> + * User must have a {@link FileResizeOp} {@link #setFileResizeOp(FileResizeOp) registered} before. + * </p> + * <p> + * Implementation calls {@link #notifyLengthChange(long)} after {@link FileResizeOp#setLength(long)}. + * </p> + * @param newTotalSize the new total size + * @throws IOException if no {@link FileResizeOp} has been {@link #setFileResizeOp(FileResizeOp) registered} + * or if a buffer slice operation failed + */ + public final synchronized void setLength(final long newTotalSize) throws IOException { + final long currentPosition; + if( 0 != newTotalSize && totalSize != newTotalSize ) { + currentPosition = position(); + } else { + currentPosition = -1L; + } + if( fc.size() != newTotalSize ) { + if( PlatformTypes.OSType.WINDOWS == PlatformProps.OS ) { + // On Windows, we have to close all mapped slices. + // Otherwise we will receive: + // java.io.IOException: The requested operation cannot be performed on a file with a user-mapped section open + // at java.io.RandomAccessFile.setLength(Native Method) + cleanAllSlices( synchronous ); + } + fileResizeOp.setLength(newTotalSize); + if( synchronous ) { + // buffers will be synchronized in notifyLengthChangeImpl(..) + flushImpl( true /* metaData */, false /* syncBuffer */); + } + } + notifyLengthChangeImpl(newTotalSize, currentPosition); + } + + /** + * Notify this instance that the underlying {@link FileChannel}'s size has been changed + * and adjusting this instances buffer slices and states accordingly. + * <p> + * Should be called by user API when aware of such event. + * </p> + * @param newTotalSize the new total size + * @throws IOException if a buffer slice operation failed + */ + public final synchronized void notifyLengthChange(final long newTotalSize) throws IOException { + notifyLengthChangeImpl(newTotalSize, -1L); + } + private final synchronized void notifyLengthChangeImpl(final long newTotalSize, final long currentPosition) throws IOException { + /* if( DEBUG ) { + System.err.println("notifyLengthChange.0: "+totalSize+" -> "+newTotalSize); + dbgDump("notifyLengthChange.0:", System.err); + } */ + if( totalSize == newTotalSize ) { + // NOP + return; + } else if( 0 == newTotalSize ) { + // ZERO - ensure one entry avoiding NULL checks + cleanAllSlices( synchronous ); + @SuppressWarnings("unchecked") + final WeakReference<ByteBuffer>[] newSlices2GC = new WeakReference[ 1 ]; + slices2GC = newSlices2GC; + slices = new ByteBuffer[1]; + slices[0] = ByteBuffer.allocate(0); + sliceCount = 0; + totalSize = 0; + mark = -1; + sliceIdx = 0; + } else { + final long prePosition = 0 <= currentPosition ? currentPosition : position(); + + final long sliceSize = 1L << sliceShift; + final int newSliceCount = (int)( ( newTotalSize + ( sliceSize - 1 ) ) / sliceSize ); + @SuppressWarnings("unchecked") + final WeakReference<ByteBuffer>[] newSlices2GC = new WeakReference[ newSliceCount ]; + final ByteBuffer[] newSlices = new ByteBuffer[ newSliceCount ]; + final int copySliceCount = Math.min(newSliceCount, sliceCount-1); // drop last (resize) + if( 0 <= copySliceCount ) { + if( 0 < copySliceCount ) { + System.arraycopy(slices2GC, 0, newSlices2GC, 0, copySliceCount); + System.arraycopy(slices, 0, newSlices, 0, copySliceCount); + } + for(int i=copySliceCount; i<sliceCount; i++) { // clip shrunken slices + 1 (last), incl. slices2GC! + cleanSlice(i, synchronous); + } + } + slices2GC = newSlices2GC; + slices = newSlices; + sliceCount = newSliceCount; + totalSize = newTotalSize; + if( newTotalSize < mark ) { + mark = -1; + } + position2( Math.min(prePosition, newTotalSize) ); // -> clipped position (set currSlice and re-map/-pos buffer) + } + if( MappedByteBufferInputStream.DEBUG ) { + this.dbgDump("NotifyLengthChange", System.err); + } + } + + /** + * Similar to {@link OutputStream#flush()}, synchronizes all mapped buffers + * from local storage via {@link MappedByteBuffer#force()} + * as well as the {@link FileChannel#force(boolean)} w/o {@code metaData}. + * @param metaData TODO + * @throws IOException if this stream has been {@link #close() closed}. + */ + public final synchronized void flush(final boolean metaData) throws IOException { + checkOpen(); + flushImpl(metaData, true); + } + private final synchronized void flushImpl(final boolean metaData, final boolean syncBuffer) throws IOException { + if( FileChannel.MapMode.READ_ONLY != mmode ) { + if( syncBuffer && FileChannel.MapMode.READ_WRITE == mmode ) { + for(int i=0; i<sliceCount; i++) { + syncSlice(slices[i], true); + } + for(int i=0; i<sliceCount; i++) { + final WeakReference<ByteBuffer> ref = slices2GC[i]; + if( null != ref ) { + syncSlice(ref.get(), true); + } + } + } + fc.force(metaData); + } + } + + + /** + * Returns a new MappedByteBufferOutputStream instance sharing + * all resources of this input stream, including all buffer slices. + * + * @throws IllegalStateException if attempting to set the {@link FileResizeOp} to a different value than before + * @throws IOException if this instance was opened w/ {@link FileChannel.MapMode#READ_ONLY} + * or if this stream has been {@link #close() closed}. + */ + public final synchronized MappedByteBufferOutputStream getOutputStream(final FileResizeOp fileResizeOp) + throws IllegalStateException, IOException + { + checkOpen(); + final MappedByteBufferOutputStream res = new MappedByteBufferOutputStream(this, fileResizeOp); + refCount++; + return res; + } + + /** + * Return the mapped {@link ByteBuffer} slice at the current {@link #position()}. + * <p> + * Due to the nature of using sliced buffers mapping the whole region, + * user has to determine whether the returned buffer covers the desired region + * and may fetch the {@link #nextSlice()} until satisfied.<br> + * It is also possible to repeat this operation after reposition the stream via {@link #position(long)} + * or {@link #skip(long)} to a position within the next block, similar to {@link #nextSlice()}. + * </p> + * @throws IOException if a buffer slice operation failed. + */ + public final synchronized ByteBuffer currentSlice() throws IOException { + final ByteBuffer s0 = slices[sliceIdx]; + if ( null != s0 ) { + return s0; + } else { + if( CacheMode.FLUSH_PRE_SOFT == cmode ) { + final WeakReference<ByteBuffer> ref = slices2GC[sliceIdx]; + if( null != ref ) { + final ByteBuffer mbb = ref.get(); + slices2GC[sliceIdx] = null; + slices2GCEntries--; + if( null != mbb ) { + slices[sliceIdx] = mbb; + slicesEntries++; + return mbb; + } + } + } + final long pos = (long)sliceIdx << sliceShift; + final MappedByteBuffer s1 = fc.map(mmode, pos, Math.min(1L << sliceShift, totalSize - pos)); + slices[sliceIdx] = s1; + slicesEntries++; + return s1; + } + } + + /** + * Return the <i>next</i> mapped {@link ByteBuffer} slice from the current {@link #position()}, + * implicitly setting {@link #position(long)} to the start of the returned <i>next</i> slice, + * see {@link #currentSlice()}. + * <p> + * If no subsequent slice is available, {@code null} is being returned. + * </p> + * @throws IOException if a buffer slice operation failed. + */ + public final synchronized ByteBuffer nextSlice() throws IOException { + if ( sliceIdx < sliceCount - 1 ) { + flushSlice(sliceIdx, synchronous); + sliceIdx++; + final ByteBuffer slice = currentSlice(); + slice.position( 0 ); + return slice; + } else { + return null; + } + } + + /** + * Releases the mapped {@link ByteBuffer} slices. + * @throws IOException if a buffer slice operation failed. + */ + public final synchronized void flushSlices() throws IOException { + if( null != slices ) { + for(int i=0; i<sliceCount; i++) { + flushSlice(i, synchronous); + } + } + if( MappedByteBufferInputStream.DEBUG ) { + this.dbgDump("FlushSlices", System.err); + } + } + + synchronized void syncSlice(final ByteBuffer s) throws IOException { + syncSlice(s, synchronous); + } + synchronized void syncSlice(final ByteBuffer s, final boolean syncBuffer) throws IOException { + if( syncBuffer && null != s && FileChannel.MapMode.READ_WRITE == mmode ) { + try { + ((MappedByteBuffer)s).force(); + } catch( final Throwable t ) { + // On Windows .. this may happen, like: + // java.io.IOException: The process cannot access the file because another process has locked a portion of the file + // at java.nio.MappedByteBuffer.force0(Native Method) + // at java.nio.MappedByteBuffer.force(MappedByteBuffer.java:203) + if( DEBUG ) { + System.err.println("Caught "+t.getMessage()); + t.printStackTrace(); + } + } + } + } + private synchronized void flushSlice(final int i, final boolean syncBuffer) throws IOException { + final ByteBuffer s = slices[i]; + if ( null != s ) { + if( CacheMode.FLUSH_NONE != cmode ) { + slices[i] = null; // trigger slice GC + slicesEntries--; + if( CacheMode.FLUSH_PRE_HARD == cmode ) { + if( !cleanBuffer(s, syncBuffer) ) { + // buffer already synced in cleanBuffer(..) if requested + slices2GC[i] = new WeakReference<ByteBuffer>(s); + slices2GCEntries++; + } + } else { + syncSlice(s, syncBuffer); + slices2GC[i] = new WeakReference<ByteBuffer>(s); + slices2GCEntries++; + } + } else { + syncSlice(s, syncBuffer); + } + } + } + private synchronized void cleanAllSlices(final boolean syncBuffers) throws IOException { + if( null != slices ) { + for(int i=0; i<sliceCount; i++) { + cleanSlice(i, syncBuffers); + } + if( 0 != slicesEntries || 0 != slices2GCEntries ) { // FIXME + final String err = "mappedSliceCount "+slicesEntries+", slices2GCEntries "+slices2GCEntries; + dbgDump(err+": ", System.err); + throw new InternalError(err); + } + } + } + + private synchronized void cleanSlice(final int i, final boolean syncBuffer) throws IOException { + final ByteBuffer s1 = slices[i]; + final ByteBuffer s2; + { + final WeakReference<ByteBuffer> ref = slices2GC[i]; + slices2GC[i] = null; + if( null != ref ) { + slices2GCEntries--; + s2 = ref.get(); + } else { + s2 = null; + } + } + if( null != s1 ) { + slices[i] = null; + slicesEntries--; + cleanBuffer(s1, syncBuffer); + if( null != s2 ) { + throw new InternalError("XXX"); + } + } else if( null != s2 ) { + cleanBuffer(s2, syncBuffer); + } + } + private synchronized boolean cleanBuffer(final ByteBuffer mbb, final boolean syncBuffer) throws IOException { + syncSlice(mbb, syncBuffer); + if( !mbb.isDirect() ) { + return false; + } + if( !NioUtil.Cleaner.clean(mbb) && CacheMode.FLUSH_PRE_HARD == cmode ) { + cmode = CacheMode.FLUSH_PRE_SOFT; + return false; + } else { + return true; + } + } + + /** + * Return the used {@link CacheMode}. + * <p> + * If a desired {@link CacheMode} is not available, it may fall back to an available one at runtime, + * see {@link CacheMode#FLUSH_PRE_HARD}.<br> + * This evaluation only happens if the {@link CacheMode} != {@link CacheMode#FLUSH_NONE} + * and while attempting to flush an unused buffer slice. + * </p> + */ + public final synchronized CacheMode getCacheMode() { return cmode; } + + /** + * Returns the total size in bytes of the {@link InputStream} + * <pre> + * <code>0 <= {@link #position()} <= {@link #length()}</code> + * </pre> + */ + // @Override + public final synchronized long length() { + return totalSize; + } + + /** + * Returns the number of remaining available bytes of the {@link InputStream}, + * i.e. <code>{@link #length()} - {@link #position()}</code>. + * <pre> + * <code>0 <= {@link #position()} <= {@link #length()}</code> + * </pre> + * <p> + * In contrast to {@link InputStream}'s {@link #available()} method, + * this method returns the proper return type {@code long}. + * </p> + * @throws IOException if a buffer slice operation failed. + */ + public final synchronized long remaining() throws IOException { + return 0 < refCount ? totalSize - position() : 0; + } + + /** + * <i>See {@link #remaining()} for an accurate variant.</i> + * <p> + * {@inheritDoc} + * </p> + * @throws IOException if a buffer slice operation failed. + */ + @Override + public final synchronized int available() throws IOException { + final long available = remaining(); + return available <= Integer.MAX_VALUE ? (int)available : Integer.MAX_VALUE; + } + + /** + * Returns the absolute position of the {@link InputStream}. + * <pre> + * <code>0 <= {@link #position()} <= {@link #length()}</code> + * </pre> + * @throws IOException if a buffer slice operation failed. + */ + // @Override + public final synchronized long position() throws IOException { + if( 0 < refCount ) { + return ( (long)sliceIdx << sliceShift ) + currentSlice().position(); + } else { + return 0; + } + } + + /** + * Sets the absolute position of the {@link InputStream} to {@code newPosition}. + * <pre> + * <code>0 <= {@link #position()} <= {@link #length()}</code> + * </pre> + * @param newPosition The new position, which must be non-negative and ≤ {@link #length()}. + * @return this instance + * @throws IOException if a buffer slice operation failed or stream is {@link #close() closed}. + */ + // @Override + public final synchronized MappedByteBufferInputStream position( final long newPosition ) throws IOException { + checkOpen(); + if ( totalSize < newPosition || 0 > newPosition ) { + throw new IllegalArgumentException("new position "+newPosition+" not within [0.."+totalSize+"]"); + } + final int preSlice = sliceIdx; + + if ( totalSize == newPosition ) { + // EOF, pos == maxPos + 1 + sliceIdx = Math.max(0, sliceCount - 1); // handle zero size + if( preSlice != sliceIdx ) { + flushSlice(preSlice, synchronous); + } + final ByteBuffer s = currentSlice(); + s.position( s.capacity() ); + } else { + sliceIdx = (int)( newPosition >>> sliceShift ); + if( preSlice != sliceIdx ) { + flushSlice(preSlice, synchronous); + } + currentSlice().position( (int)( newPosition - ( (long)sliceIdx << sliceShift ) ) ); + } + return this; + } + private final synchronized void position2( final long newPosition ) throws IOException { + if ( totalSize == newPosition ) { + // EOF, pos == maxPos + 1 + sliceIdx = Math.max(0, sliceCount - 1); // handle zero size + final ByteBuffer s = currentSlice(); + s.position( s.capacity() ); + } else { + sliceIdx = (int)( newPosition >>> sliceShift ); + currentSlice().position( (int)( newPosition - ( (long)sliceIdx << sliceShift ) ) ); + } + } + + @Override + public final boolean markSupported() { + return true; + } + + /** + * {@inheritDoc} + * <p> + * <i>Parameter {@code readLimit} is not used in this implementation, + * since the whole file is memory mapped and no read limitation occurs.</i> + * </p> + */ + @Override + public final synchronized void mark( final int readlimit ) { + if( 0 < refCount ) { + try { + mark = position(); + } catch (final IOException e) { + throw new RuntimeException(e); // FIXME: oops + } + } + } + + /** + * {@inheritDoc} + * @throws IOException if this stream has not been marked, + * a buffer slice operation failed or stream has been {@link #close() closed}. + */ + @Override + public final synchronized void reset() throws IOException { + checkOpen(); + if ( mark == -1 ) { + throw new IOException("mark not set"); + } + position( mark ); + } + + /** + * {@inheritDoc} + * @throws IOException if a buffer slice operation failed or stream is {@link #close() closed}. + */ + @Override + public final synchronized long skip( final long n ) throws IOException { + checkOpen(); + if( 0 > n ) { + return 0; + } + final long pos = position(); + final long rem = totalSize - pos; // remaining + final long s = Math.min( rem, n ); + position( pos + s ); + return s; + } + + @Override + public final synchronized int read() throws IOException { + checkOpen(); + ByteBuffer slice = currentSlice(); + if ( !slice.hasRemaining() ) { + if ( null == ( slice = nextSlice() ) ) { + return -1; + } + } + return slice.get() & 0xFF; + } + + @Override + public final synchronized int read( final byte[] b, final int off, final int len ) throws IOException { + checkOpen(); + if (b == null) { + throw new NullPointerException(); + } else if( off < 0 || + len < 0 || + off + len > b.length + ) { + throw new IndexOutOfBoundsException("offset "+off+", length "+len+", b.length "+b.length); + } else if ( 0 == len ) { + return 0; + } + final long totalRem = remaining(); + if ( 0 == totalRem ) { + return -1; + } + final int maxLen = (int)Math.min( totalRem, len ); + int read = 0; + while( read < maxLen ) { + ByteBuffer slice = currentSlice(); + int currRem = slice.remaining(); + if ( 0 == currRem ) { + if ( null == ( slice = nextSlice() ) ) { + throw new InternalError("Unexpected EOT"); + } + currRem = slice.remaining(); + } + final int currLen = Math.min( maxLen - read, currRem ); + slice.get( b, off + read, currLen ); + read += currLen; + } + return maxLen; + } + + /** + * Perform similar to {@link #read(byte[], int, int)} + * with {@link ByteBuffer} instead of byte array. + * @param b the {@link ByteBuffer} sink, data is written at current {@link ByteBuffer#position()} + * @param len the number of bytes to read + * @return the number of bytes read, -1 for EOS + * @throws IOException if a buffer slice operation failed or stream has been {@link #close() closed}. + */ + // @Override + public final synchronized int read(final ByteBuffer b, final int len) throws IOException { + checkOpen(); + if (b == null) { + throw new NullPointerException(); + } else if (len < 0 || len > b.remaining()) { + throw new IndexOutOfBoundsException("length "+len+", b "+b); + } else if ( 0 == len ) { + return 0; + } + final long totalRem = remaining(); + if ( 0 == totalRem ) { + return -1; + } + final int maxLen = (int)Math.min( totalRem, len ); + int read = 0; + while( read < maxLen ) { + ByteBuffer slice = currentSlice(); + int currRem = slice.remaining(); + if ( 0 == currRem ) { + if ( null == ( slice = nextSlice() ) ) { + throw new InternalError("Unexpected EOT"); + } + currRem = slice.remaining(); + } + final int currLen = Math.min( maxLen - read, currRem ); + if( slice.hasArray() && b.hasArray() ) { + System.arraycopy(slice.array(), slice.arrayOffset() + slice.position(), + b.array(), b.arrayOffset() + b.position(), + currLen); + slice.position( slice.position() + currLen ); + b.position( b.position() + currLen ); + } else if( currLen == currRem ) { + b.put(slice); + } else { + final int _limit = slice.limit(); + slice.limit(currLen); + try { + b.put(slice); + } finally { + slice.limit(_limit); + } + } + read += currLen; + } + return maxLen; + } +} diff --git a/java_base/org/jau/io/MappedByteBufferOutputStream.java b/java_base/org/jau/io/MappedByteBufferOutputStream.java new file mode 100644 index 0000000..787bd31 --- /dev/null +++ b/java_base/org/jau/io/MappedByteBufferOutputStream.java @@ -0,0 +1,349 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +import org.jau.io.MappedByteBufferInputStream.CacheMode; +import org.jau.io.MappedByteBufferInputStream.FileResizeOp; + +/** + * An {@link OutputStream} implementation based on an underlying {@link FileChannel}'s memory mapped {@link ByteBuffer}. + * <p> + * Implementation is based on {@link MappedByteBufferInputStream}, using it as its parent instance. + * </p> + * <p> + * An instance maybe created via its parent {@link MappedByteBufferInputStream#getOutputStream(FileResizeOp)} + * or directly {@link #MappedByteBufferOutputStream(FileChannel, MapMode, CacheMode, int, FileResizeOp)}. + * </p> + * @since 2.3.0 + */ +public class MappedByteBufferOutputStream extends OutputStream { + private final MappedByteBufferInputStream parent; + + MappedByteBufferOutputStream(final MappedByteBufferInputStream parent, + final FileResizeOp fileResizeOp) throws IOException { + if( FileChannel.MapMode.READ_ONLY == parent.getMapMode() ) { + throw new IOException("FileChannel map-mode is read-only"); + } + this.parent = parent; + this.parent.setFileResizeOp(fileResizeOp); + } + + /** + * Creates a new instance using the given {@link FileChannel}. + * <p> + * The {@link ByteBuffer} slices will be mapped lazily at first usage. + * </p> + * @param fileChannel the file channel to be mapped lazily. + * @param mmode the map mode, default is {@link FileChannel.MapMode#READ_WRITE}. + * @param cmode the caching mode, default is {@link MappedByteBufferInputStream.CacheMode#FLUSH_PRE_SOFT}. + * @param sliceShift the pow2 slice size, default is {@link MappedByteBufferInputStream#DEFAULT_SLICE_SHIFT}. + * @param fileResizeOp {@link MappedByteBufferInputStream.FileResizeOp} as described on {@link MappedByteBufferInputStream#setFileResizeOp(FileResizeOp)}. + * @throws IOException + */ + public MappedByteBufferOutputStream(final FileChannel fileChannel, + final FileChannel.MapMode mmode, + final CacheMode cmode, + final int sliceShift, final FileResizeOp fileResizeOp) throws IOException { + this(new MappedByteBufferInputStream(fileChannel, mmode, cmode, sliceShift, fileChannel.size(), 0), fileResizeOp); + } + + /** + * See {@link MappedByteBufferInputStream#setSynchronous(boolean)}. + */ + public final synchronized void setSynchronous(final boolean s) { + parent.setSynchronous(s); + } + /** + * See {@link MappedByteBufferInputStream#getSynchronous()}. + */ + public final synchronized boolean getSynchronous() { + return parent.getSynchronous(); + } + + /** + * See {@link MappedByteBufferInputStream#setLength(long)}. + */ + public final synchronized void setLength(final long newTotalSize) throws IOException { + parent.setLength(newTotalSize); + } + + /** + * See {@link MappedByteBufferInputStream#notifyLengthChange(long)}. + */ + public final synchronized void notifyLengthChange(final long newTotalSize) throws IOException { + parent.notifyLengthChange(newTotalSize); + } + + /** + * See {@link MappedByteBufferInputStream#length()}. + */ + public final synchronized long length() { + return parent.length(); + } + + /** + * See {@link MappedByteBufferInputStream#remaining()}. + */ + public final synchronized long remaining() throws IOException { + return parent.remaining(); + } + + /** + * See {@link MappedByteBufferInputStream#position()}. + */ + public final synchronized long position() throws IOException { + return parent.position(); + } + + /** + * See {@link MappedByteBufferInputStream#position(long)}. + */ + public final synchronized MappedByteBufferInputStream position( final long newPosition ) throws IOException { + return parent.position(newPosition); + } + + /** + * See {@link MappedByteBufferInputStream#skip(long)}. + */ + public final synchronized long skip( final long n ) throws IOException { + return parent.skip(n); + } + + @Override + public final synchronized void flush() throws IOException { + parent.flush( true /* metaData */); + } + + /** + * See {@link MappedByteBufferInputStream#flush(boolean)}. + */ + // @Override + public final synchronized void flush(final boolean metaData) throws IOException { + parent.flush(metaData); + } + + @Override + public final synchronized void close() throws IOException { + parent.close(); + } + + @Override + public final synchronized void write(final int b) throws IOException { + parent.checkOpen(); + final long totalRem = parent.remaining(); + if ( totalRem < 1 ) { // grow if required + parent.setLength( parent.length() + 1 ); + } + ByteBuffer slice = parent.currentSlice(); + final int currRem = slice.remaining(); + if ( 0 == currRem ) { + if ( null == ( slice = parent.nextSlice() ) ) { + if( MappedByteBufferInputStream.DEBUG ) { + System.err.println("EOT write: "+parent.currentSlice()); + parent.dbgDump("EOT write:", System.err); + } + throw new IOException("EOT"); // 'end-of-tape' + } + } + slice.put( (byte)(b & 0xFF) ); + + // sync last buffer (happens only in synchronous mode) + if( null != slice ) { + parent.syncSlice(slice); + } + } + + @Override + public final synchronized void write(final byte b[], final int off, final int len) throws IOException { + parent.checkOpen(); + if (b == null) { + throw new NullPointerException(); + } else if( off < 0 || + len < 0 || + off > b.length || + off + len > b.length || + off + len < 0 + ) { + throw new IndexOutOfBoundsException("offset "+off+", length "+len+", b.length "+b.length); + } else if( 0 == len ) { + return; + } + final long totalRem = parent.remaining(); + if ( totalRem < len ) { // grow if required + parent.setLength( parent.length() + len - totalRem ); + } + int written = 0; + ByteBuffer slice = null; + while( written < len ) { + slice = parent.currentSlice(); + int currRem = slice.remaining(); + if ( 0 == currRem ) { + if ( null == ( slice = parent.nextSlice() ) ) { + if( MappedByteBufferInputStream.DEBUG ) { + System.err.println("EOT write: offset "+off+", length "+len+", b.length "+b.length); + System.err.println("EOT write: written "+written+" / "+len+", currRem "+currRem); + System.err.println("EOT write: "+parent.currentSlice()); + parent.dbgDump("EOT write:", System.err); + } + throw new InternalError("EOT"); // 'end-of-tape' + } + currRem = slice.remaining(); + } + final int currLen = Math.min( len - written, currRem ); + slice.put( b, off + written, currLen ); + written += currLen; + } + // sync last buffer (happens only in synchronous mode) + if( null != slice ) { + parent.syncSlice(slice); + } + } + + /** + * Perform similar to {@link #write(byte[], int, int)} + * with {@link ByteBuffer} instead of byte array. + * @param b the {@link ByteBuffer} source, data is read from current {@link ByteBuffer#position()} + * @param len the number of bytes to write + * @throws IOException if a buffer slice operation failed or stream has been {@link #close() closed}. + */ + // @Override + public final synchronized void write(final ByteBuffer b, final int len) throws IOException { + parent.checkOpen(); + if (b == null) { + throw new NullPointerException(); + } else if (len < 0 || len > b.remaining()) { + throw new IndexOutOfBoundsException("length "+len+", b "+b); + } else if( 0 == len ) { + return; + } + final long totalRem = parent.remaining(); + if ( totalRem < len ) { // grow if required + parent.setLength( parent.length() + len - totalRem ); + } + int written = 0; + ByteBuffer slice = null; + while( written < len ) { + slice = parent.currentSlice(); + int currRem = slice.remaining(); + if ( 0 == currRem ) { + if ( null == ( slice = parent.nextSlice() ) ) { + if( MappedByteBufferInputStream.DEBUG ) { + System.err.println("EOT write: length "+len+", b "+b); + System.err.println("EOT write: written "+written+" / "+len+", currRem "+currRem); + System.err.println("EOT write: "+parent.currentSlice()); + parent.dbgDump("EOT write:", System.err); + } + throw new InternalError("EOT"); // 'end-of-tape' + } + currRem = slice.remaining(); + } + final int currLen = Math.min( len - written, currRem ); + + if( slice.hasArray() && b.hasArray() ) { + System.arraycopy(b.array(), b.arrayOffset() + b.position(), + slice.array(), slice.arrayOffset() + slice.position(), + currLen); + b.position( b.position() + currLen ); + slice.position( slice.position() + currLen ); + } else if( currLen == currRem ) { + slice.put(b); + } else { + final int _limit = b.limit(); + b.limit(currLen); + try { + slice.put(b); + } finally { + b.limit(_limit); + } + } + written += currLen; + } + // sync last buffer (happens only in synchronous mode) + if( null != slice ) { + parent.syncSlice(slice); + } + } + + /** + * Perform similar to {@link #write(ByteBuffer, int)} + * with {@link MappedByteBufferInputStream} instead of byte array. + * <p> + * Method directly copies memory mapped {@link ByteBuffer}'ed data + * from the given input stream to this stream without extra data copy. + * </p> + * @param b the {@link ByteBuffer} source, data is read from current {@link MappedByteBufferInputStream#position()} + * @param len the number of bytes to write + * @throws IOException if a buffer slice operation failed or stream has been {@link #close() closed}. + */ + // @Override + public final synchronized void write(final MappedByteBufferInputStream b, final long len) throws IOException { + parent.checkOpen(); + if (b == null) { + throw new NullPointerException(); + } else if (len < 0 || len > b.remaining()) { + throw new IndexOutOfBoundsException("length "+len+", b "+b); + } else if( 0 == len ) { + return; + } + final long totalRem = parent.remaining(); + if ( totalRem < len ) { // grow if required + parent.setLength( parent.length() + len - totalRem ); + } + long written = 0; + ByteBuffer slice = null; + while( written < len ) { + slice = parent.currentSlice(); + int currRem = slice.remaining(); + if ( 0 == currRem ) { + if ( null == ( slice = parent.nextSlice() ) ) { + if( MappedByteBufferInputStream.DEBUG ) { + System.err.println("EOT write: length "+len+", b "+b); + System.err.println("EOT write: written "+written+" / "+len+", currRem "+currRem); + System.err.println("EOT write: "+parent.currentSlice()); + parent.dbgDump("EOT write:", System.err); + } + throw new InternalError("EOT"); // 'end-of-tape' + } + currRem = slice.remaining(); + } + final int currLen = b.read(slice, (int)Math.min( len - written, currRem )); + if( 0 > currLen ) { + throw new InternalError("Unexpected InputStream EOT"); // 'end-of-tape' + } + written += currLen; + } + // sync last buffer (happens only in synchronous mode) + if( null != slice ) { + parent.syncSlice(slice); + } + } +} diff --git a/java/org/jau/lang/ExceptionUtils.java b/java_base/org/jau/lang/ExceptionUtils.java index 9e3c522..9e3c522 100644 --- a/java/org/jau/lang/ExceptionUtils.java +++ b/java_base/org/jau/lang/ExceptionUtils.java diff --git a/java_base/org/jau/lang/InterruptSource.java b/java_base/org/jau/lang/InterruptSource.java new file mode 100644 index 0000000..fb0e3e9 --- /dev/null +++ b/java_base/org/jau/lang/InterruptSource.java @@ -0,0 +1,155 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2015 Gothel Software e.K. + * Copyright (c) 2015 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.lang; + +/** + * Interface exposing {@link java.lang.Thread#interrupt()} source, + * intended for {@link java.lang.Thread} specializations. + * @since 2.3.2 + */ +public interface InterruptSource { + /** + * Returns the source of the last {@link #interrupt()} call. + * @param clear if true, issues {@link #clearInterruptSource()} + */ + Throwable getInterruptSource(final boolean clear); + + /** + * Returns the count of {@link java.lang.Thread#interrupt()} calls. + * @param clear if true, issues {@link #clearInterruptSource()} + */ + int getInterruptCounter(final boolean clear); + + /** + * Clears source and count of {@link java.lang.Thread#interrupt()} calls, if any. + */ + void clearInterruptSource(); + + public static class Util { + /** + * Casts given {@link java.lang.Thread} to {@link InterruptSource} + * if applicable, otherwise returns {@code null}. + */ + public static InterruptSource get(final java.lang.Thread t) { + if(t instanceof InterruptSource) { + return (InterruptSource)t; + } else { + return null; + } + } + /** + * Casts current {@link java.lang.Thread} to {@link InterruptSource} + * if applicable, otherwise returns {@code null}. + */ + public static InterruptSource currentThread() { + return get(java.lang.Thread.currentThread()); + } + } + + /** + * {@link java.lang.Thread} specialization implementing {@link InterruptSource} + * to track {@link java.lang.Thread#interrupt()} calls. + * @since 2.3.2 + */ + public static class Thread extends java.lang.Thread implements InterruptSource { + volatile Throwable interruptSource = null; + volatile int interruptCounter = 0; + final Object sync = new Object(); + + /** + * See {@link Thread#Thread(} for details. + */ + public Thread() { + super(); + } + /** + * See {@link Thread#Thread(ThreadGroup, Runnable)} for details. + * @param tg explicit {@link ThreadGroup}, may be {@code null} + * @param target explicit {@link Runnable}, may be {@code null} + */ + public Thread(final ThreadGroup tg, final Runnable target) { + super(tg, target); + } + /** + * See {@link Thread#Thread(ThreadGroup, Runnable, String)} for details. + * @param tg explicit {@link ThreadGroup}, may be {@code null} + * @param target explicit {@link Runnable}, may be {@code null} + * @param name explicit name of thread, must not be {@code null} + */ + public Thread(final ThreadGroup tg, final Runnable target, final String name) { + super(tg, target, name); + } + + /** + * Depending on whether {@code name} is null, either + * {@link #Thread(ThreadGroup, Runnable, String)} or + * {@link #Thread(ThreadGroup, Runnable)} is being utilized. + * @param tg explicit {@link ThreadGroup}, may be {@code null} + * @param target explicit {@link Runnable}, may be {@code null} + * @param name explicit name of thread, may be {@code null} + */ + public static Thread create(final ThreadGroup tg, final Runnable target, final String name) { + return null != name ? new Thread(tg, target, name) : new Thread(tg, target); + } + + @Override + public final Throwable getInterruptSource(final boolean clear) { + synchronized(sync) { + final Throwable r = interruptSource; + if( clear ) { + clearInterruptSource(); + } + return r; + } + } + @Override + public final int getInterruptCounter(final boolean clear) { + synchronized(sync) { + final int r = interruptCounter; + if( clear ) { + clearInterruptSource(); + } + return r; + } + } + @Override + public final void clearInterruptSource() { + synchronized(sync) { + interruptCounter = 0; + interruptSource = null; + } + } + @Override + public final void interrupt() { + synchronized(sync) { + interruptCounter++; + interruptSource = new Throwable(getName()+".interrupt() #"+interruptCounter); + } + super.interrupt(); + } + } +} diff --git a/java_base/org/jau/lang/InterruptedRuntimeException.java b/java_base/org/jau/lang/InterruptedRuntimeException.java new file mode 100644 index 0000000..15036bb --- /dev/null +++ b/java_base/org/jau/lang/InterruptedRuntimeException.java @@ -0,0 +1,76 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2015 Gothel Software e.K. + * Copyright (c) 2015 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.lang; + +/** + * <i>Unchecked exception</i> propagating an {@link InterruptedException} + * where handling of the latter is not desired. + * <p> + * {@link InterruptedRuntimeException} may be thrown either by waiting for any {@link Runnable} + * to be completed, or during its execution. + * </p> + * <p> + * The propagated {@link InterruptedException} may be of type {@link SourcedInterruptedException}. + * </p> + * <p> + * </p> + */ +@SuppressWarnings("serial") +public class InterruptedRuntimeException extends RuntimeException { + + /** + * Constructor attempts to {@link SourcedInterruptedException#wrap(InterruptedException) wrap} + * the given {@link InterruptedException} {@code cause} into a {@link SourcedInterruptedException}. + * + * @param message the message of this exception + * @param cause the propagated {@link InterruptedException} + */ + public InterruptedRuntimeException(final String message, final InterruptedException cause) { + super(message, SourcedInterruptedException.wrap(cause)); + } + + /** + * Constructor attempts to {@link SourcedInterruptedException#wrap(InterruptedException) wrap} + * the given {@link InterruptedException} {@code cause} into a {@link SourcedInterruptedException}. + * + * @param cause the propagated {@link InterruptedException} + */ + public InterruptedRuntimeException(final InterruptedException cause) { + super(SourcedInterruptedException.wrap(cause)); + } + + /** + * Returns the propagated {@link InterruptedException}, i.e. the cause of this exception. + * <p> + * {@inheritDoc} + * </p> + */ + @Override + public InterruptedException getCause() { + return (InterruptedException)super.getCause(); + } +} diff --git a/java_base/org/jau/lang/NioUtil.java b/java_base/org/jau/lang/NioUtil.java new file mode 100644 index 0000000..19851c0 --- /dev/null +++ b/java_base/org/jau/lang/NioUtil.java @@ -0,0 +1,85 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.lang; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.jau.sys.Debug; +import org.jau.sys.PlatformProps; + +/** + * Utility methods to simplify NIO buffers + */ +public class NioUtil { + static final boolean DEBUG = Debug.debug("NioUtil"); + + public static final int SIZEOF_BYTE = 1; + public static final int SIZEOF_SHORT = 2; + public static final int SIZEOF_CHAR = 2; + public static final int SIZEOF_INT = 4; + public static final int SIZEOF_FLOAT = 4; + public static final int SIZEOF_LONG = 8; + public static final int SIZEOF_DOUBLE = 8; + + private NioUtil() {} + + public static ByteBuffer newNativeByteBuffer(final int size) { + return ByteBuffer.allocateDirect( size ).order( ByteOrder.nativeOrder() ); + } + + /** + * Access to NIO {@link sun.misc.Cleaner}, allowing caller to deterministically clean a given {@link sun.nio.ch.DirectBuffer}. + */ + public static class Cleaner { + private static final boolean hasCleaner; + /** OK to be lazy on thread synchronization, just for early out **/ + private static volatile boolean cleanerError; + static { + hasCleaner = UnsafeUtil.hasInvokeCleaner(); + cleanerError = !hasCleaner; + if( DEBUG ) { + System.err.println("Buffers.Cleaner.init: hasCleaner: "+hasCleaner+", cleanerError "+cleanerError); + } + } + + /** + * If {@code b} is an direct NIO buffer, i.e {@link sun.nio.ch.DirectBuffer}, + * calls it's {@link sun.misc.Cleaner} instance {@code clean()} method once. + * @return {@code true} if successful, otherwise {@code false}. + */ + public static boolean clean(final ByteBuffer bb) { + if( !hasCleaner && ( cleanerError || !bb.isDirect() ) ) { + return false; + } + if( PlatformProps.JAVA_9 ) { + UnsafeUtil.invokeCleaner(bb); + } else { + return false; + } + return true; + } + } + +} diff --git a/java_base/org/jau/lang/ReflectionUtil.java b/java_base/org/jau/lang/ReflectionUtil.java new file mode 100644 index 0000000..2621ca2 --- /dev/null +++ b/java_base/org/jau/lang/ReflectionUtil.java @@ -0,0 +1,228 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.lang; + +import java.lang.reflect.InaccessibleObjectException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.jau.sys.Debug; + +/** + * Utility methods to simplify reflection access + */ +public class ReflectionUtil { + static final boolean DEBUG = Debug.debug("Reflection"); + + private static final String asString(final Class<?>[] argTypes) { + final StringBuilder args = new StringBuilder(); + if(null != argTypes) { + for (int i = 0; i < argTypes.length; i++) { + if(i > 0) { + args.append(", "); + } + args.append(argTypes[i].getName()); + } + } + return args.toString(); + } + + private ReflectionUtil() {} + + /** + * Returns true only if the class could be loaded but w/o class initialization. + */ + public static boolean isClassAvailable(final String clazzName, final ClassLoader cl) { + try { + return null != Class.forName(clazzName, false /* initializeClazz */, cl); + } catch( final ClassNotFoundException cnfe ) { + return false; + } catch( final ExceptionInInitializerError eiie ) { + return false; + } catch( final LinkageError le ) { + return false; + } catch( final Throwable t ) { + return false; + } + } + + /** + * Loads and returns the class or null. + * @see Class#forName(java.lang.String, boolean, java.lang.ClassLoader) + */ + public static final Class<?> getClass(final String clazzName, final boolean initializeClazz, final ClassLoader cl) + throws RuntimeException { + try { + return Class.forName(clazzName, initializeClazz, cl); + } catch( final Throwable t ) { + throw new RuntimeException(clazzName + " not available", t); + } + } + + /** + * @throws RuntimeException if the Method can not be found. + */ + public static final Method getMethod(final Class<?> clazz, final String methodName, final Class<?> ... argTypes) + throws RuntimeException + { + try { + return clazz.getDeclaredMethod(methodName, argTypes); + } catch (final Throwable t) { + throw new RuntimeException("Method: '" + clazz + "." + methodName + "(" + asString(argTypes) + ")' not found", t); + } + } + + /** + * @throws RuntimeException if the Method can not be found. + */ + public static final Method getMethod(final String clazzName, final boolean initializeClazz, final String methodName, final Class<?>[] argTypes, final ClassLoader cl) + throws RuntimeException + { + try { + return getMethod(Class.forName(clazzName, initializeClazz, cl), methodName, argTypes); + } catch (final Throwable t) { + throw new RuntimeException("Method: '" + clazzName + "." + methodName + "(" + asString(argTypes) + ")' not found", t); + } + } + + /** + * @param instance may be null in case of a static method + * @param method the method to be called + * @param args the method arguments + * @return the methods result, maybe null if void + * @throws RuntimeException if call fails + */ + @SuppressWarnings("unchecked") + public static final <R> R callMethod(final Object instance, final Method method, final Object ... args) + throws RuntimeException + { + try { + return (R)method.invoke(instance, args); + } catch (final Exception e) { + Throwable t = e; + if (t instanceof InvocationTargetException) { + t = ((InvocationTargetException) t).getTargetException(); + } + if (t instanceof Error) { + throw (Error) t; + } + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + throw new RuntimeException("calling "+method+" failed", t); + } + } + + /** + * @param method the method to be called + * @param args the method arguments + * @return the methods result, maybe null if void + * @throws RuntimeException if call fails + */ + public static final <R> R callStaticMethod(final Method method, final Object ... args) + throws RuntimeException + { + return callMethod(null, method, args); + } + + /** + * @throws RuntimeException if the instance can not be created. + */ + public static final <R> R callStaticMethod(final String clazzName, final String methodName, final Class<?>[] argTypes, final Object[] args, final ClassLoader cl) + throws RuntimeException + { + return callStaticMethod(getMethod(clazzName, true /* initializeClazz */, methodName, argTypes, cl), args); + } + + /** Convenient Method access class */ + public static class MethodAccessor { + Method m = null; + + /** Check {@link #available()} before using instance. */ + public MethodAccessor(final Class<?> clazz, final String methodName, final Class<?> ... argTypes) { + try { + m = ReflectionUtil.getMethod(clazz, methodName, argTypes); + } catch (final RuntimeException jre) { /* method n/a */ } + } + + /** Returns true if method is available, otherwise false. */ + public boolean available() { + return null != m; + } + + /** + * See {@link Method#setAccessible(boolean)}. + * @param flag new accessible flag value + * @throws InaccessibleObjectException + * @throws SecurityException + */ + public void setAccessible(final boolean flag) + throws InaccessibleObjectException, SecurityException + { + m.setAccessible(flag); + } + + /** + * See {@link #setAccessible(boolean)}. + * @param flag new accessible flag value + * @return true if successful, otherwise false in case {@link Method#setAccessible(boolean)} threw an exception. + */ + public boolean setAccessibleSafe(final boolean flag) { + try { + m.setAccessible(flag); + return true; + } catch( final Throwable t ) { + return false; + } + } + + /** + * Check {@link #available()} before calling to avoid throwing a RuntimeException. + * @param instance may be null in case of a static method + * @param args the method arguments + * @return the methods result, maybe null if void + * @throws RuntimeException if call fails or method not {@link #available()}. + */ + public <R> R callMethod(final Object instance, final Object ... args) { + if(null == m) { + throw new RuntimeException("Method not available. Instance: "+instance); + } + return ReflectionUtil.callMethod(instance, m, args); + } + + /** + * Check {@link #available()} before calling to avoid throwing a RuntimeException. + * @param args the method arguments + * @return the methods result, maybe null if void + * @throws RuntimeException if call fails or method not {@link #available()}. + */ + public <R> R callStaticMethod(final Object ... args) { + if(null == m) { + throw new RuntimeException("Method not available."); + } + return ReflectionUtil.callStaticMethod(m, args); + } + } + +} diff --git a/java_base/org/jau/lang/SourcedInterruptedException.java b/java_base/org/jau/lang/SourcedInterruptedException.java new file mode 100644 index 0000000..bbbd18b --- /dev/null +++ b/java_base/org/jau/lang/SourcedInterruptedException.java @@ -0,0 +1,163 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2015 Gothel Software e.K. + * Copyright (c) 2015 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.lang; + +import java.io.PrintStream; + +import org.jau.lang.ExceptionUtils.CustomStackTrace; + +/** + * {@link InterruptedException}, which may include the source, see {@link #getInterruptSource()}. + * <p> + * This exception may be created directly where {@link #getCause()} returns {@code null}, + * or by propagating an existing {@link InterruptedException} as returned by {@link #getCause()}. + * </p> + * @since 2.3.2 + */ +@SuppressWarnings("serial") +public class SourcedInterruptedException extends InterruptedException implements CustomStackTrace { + final Throwable interruptSource; + + /** + * Wraps the given {@link InterruptedException} into a {@link SourcedInterruptedException} + * if it is not yet of the desired type and + * if the current thread if a {@link InterruptSource}, i.e. the source is known. + * <p> + * Otherwise the given {@link InterruptedException} instance is returned. + * </p> + * <p> + * In case method is creating a new wrapping instance, + * {@link InterruptSource#clearInterruptSource()} is being issued. + * </p> + * + * @param ie the to be wrapped {@link InterruptedException} + */ + public static InterruptedException wrap(final InterruptedException ie) { + return wrap(ie, InterruptSource.Util.currentThread()); + } + + /** + * Wraps the given {@link InterruptedException} into a {@link SourcedInterruptedException} + * if it is not yet of the same type and if {@code source} is not {@code null}. + * <p> + * Otherwise the given {@link InterruptedException} instance is returned. + * </p> + * <p> + * In case method is creating a new wrapping instance, + * {@link InterruptSource#clearInterruptSource()} is being issued. + * </p> + * + * @param ie the to be wrapped {@link InterruptedException} + * @param source the {@link InterruptSource} + */ + public static InterruptedException wrap(final InterruptedException ie, final InterruptSource source) { + if( !(ie instanceof SourcedInterruptedException) && null != source ) { + return new SourcedInterruptedException(ie, source.getInterruptSource(true)); + } else { + return ie; + } + } + + /** + * @param message mandatory message of this exception + * @param cause optional propagated cause + * @param interruptSource optional propagated source of {@link Thread#interrupt()} call + */ + public SourcedInterruptedException(final String message, final InterruptedException cause, final Throwable interruptSource) { + super(message); + if( null != cause ) { + initCause(cause); + } + this.interruptSource = interruptSource; + } + + /** + * @param cause mandatory propagated cause + * @param interruptSource optional propagated source of {@link Thread#interrupt()} call + */ + public SourcedInterruptedException(final InterruptedException cause, final Throwable interruptSource) { + super(cause.getMessage()); + initCause(cause); + this.interruptSource = interruptSource; + } + + /** + * Returns the source of the {@link Thread#interrupt()} call if known, + * otherwise {@code null} is returned. + */ + public final Throwable getInterruptSource() { + return interruptSource; + } + + /** + * Returns the propagated {@link InterruptedException}, i.e. the cause of this exception, + * or {@code null} if not applicable. + * <p> + * {@inheritDoc} + * </p> + */ + @Override + public InterruptedException getCause() { + return (InterruptedException)super.getCause(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(256); + sb.append(getClass().getSimpleName()).append(": "); + if (null != interruptSource) { + sb.append("[sourced]"); + } else { + sb.append("[unknown]"); + } + final String m = getLocalizedMessage(); + if( null != m ) { + sb.append(" ").append(m); + } + return sb.toString(); + } + + @Override + public final void printCauseStack(final PrintStream s, final String causeStr, final int causeIdx, final int stackDepth) { + final String s0 = causeStr+"["+causeIdx+"]"; + s.println(s0+" by "+getClass().getSimpleName()+": "+getMessage()+" on thread "+Thread.currentThread().getName()); + ExceptionUtils.dumpStack(s, getStackTrace(), 0, stackDepth); + if( null != interruptSource ) { + ExceptionUtils.printCause(s, s0, interruptSource, 0, 1, stackDepth); + } + } + + @Override + public final void printStackTrace(final PrintStream s, final int causeDepth, final int stackDepth) { + s.println(getClass().getSimpleName()+": "+getMessage()+" on thread "+Thread.currentThread().getName()); + ExceptionUtils.dumpStack(s, getStackTrace(), 0, stackDepth); + ExceptionUtils.printCause(s, "Caused", getCause(), 0, causeDepth, stackDepth); + if( null != interruptSource ) { + ExceptionUtils.printCause(s, "InterruptSource", interruptSource, 0, causeDepth, stackDepth); + } + } +} diff --git a/java_base/org/jau/lang/StructAccessor.java b/java_base/org/jau/lang/StructAccessor.java new file mode 100644 index 0000000..61d130b --- /dev/null +++ b/java_base/org/jau/lang/StructAccessor.java @@ -0,0 +1,302 @@ +/** + * Author: Sven Gothel <[email protected]> + * Author: Kenneth Bradley Russell + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2015 Gothel Software e.K. + * Copyright (c) 2015 JogAmp Community. + * Copyright (c) 2003 Sun Microsystems. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.lang; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class StructAccessor { + + private final ByteBuffer bb; + + public StructAccessor(final ByteBuffer bb) { + // Setting of byte order is concession to native code which needs + // to instantiate these + this.bb = bb.order(ByteOrder.nativeOrder()); + } + + public final ByteBuffer getBuffer() { + return bb; + } + + /** + * Returns a slice of the current ByteBuffer starting at the + * specified byte offset and extending the specified number of + * bytes. Note that this method is not thread-safe with respect to + * the other methods in this class. + */ + public final ByteBuffer slice(final int byteOffset, final int byteLength) { + bb.position(byteOffset); + bb.limit(byteOffset + byteLength); + final ByteBuffer newBuf = bb.slice().order(bb.order()); // slice and duplicate may change byte order + bb.position(0); + bb.limit(bb.capacity()); + return newBuf; + } + + /** Retrieves the byte at the specified byteOffset. */ + public final byte getByteAt(final int byteOffset) { + return bb.get(byteOffset); + } + + /** Puts a byte at the specified byteOffset. */ + public final void setByteAt(final int byteOffset, final byte v) { + bb.put(byteOffset, v); + } + + /** Retrieves the boolean at the specified byteOffset. */ + public final boolean getBooleanAt(final int byteOffset) { + return (byte)0 != bb.get(byteOffset); + } + + /** Puts a boolean at the specified byteOffset. */ + public final void setBooleanAt(final int byteOffset, final boolean v) { + bb.put(byteOffset, v?(byte)1:(byte)0); + } + + /** Retrieves the char at the specified byteOffset. */ + public final char getCharAt(final int byteOffset) { + return bb.getChar(byteOffset); + } + + /** Puts a char at the specified byteOffset. */ + public final void setCharAt(final int byteOffset, final char v) { + bb.putChar(byteOffset, v); + } + + /** Retrieves the short at the specified byteOffset. */ + public final short getShortAt(final int byteOffset) { + return bb.getShort(byteOffset); + } + + /** Puts a short at the specified byteOffset. */ + public final void setShortAt(final int byteOffset, final short v) { + bb.putShort(byteOffset, v); + } + + /** Retrieves the int at the specified byteOffset. */ + public final int getIntAt(final int byteOffset) { + return bb.getInt(byteOffset); + } + + /** Puts a int at the specified byteOffset. */ + public final void setIntAt(final int byteOffset, final int v) { + bb.putInt(byteOffset, v); + } + + /** Retrieves the int at the specified byteOffset. */ + public final int getIntAt(final int byteOffset, final int nativeSizeInBytes) { + switch(nativeSizeInBytes) { + case 2: + return bb.getShort(byteOffset) & 0x0000FFFF ; + case 4: + return bb.getInt(byteOffset); + case 8: + return (int) ( bb.getLong(byteOffset) & 0x00000000FFFFFFFFL ) ; + default: + throw new InternalError("invalid nativeSizeInBytes "+nativeSizeInBytes); + } + } + + /** Puts a int at the specified byteOffset. */ + public final void setIntAt(final int byteOffset, final int v, final int nativeSizeInBytes) { + switch(nativeSizeInBytes) { + case 2: + bb.putShort(byteOffset, (short) ( v & 0x0000FFFF ) ); + break; + case 4: + bb.putInt(byteOffset, v); + break; + case 8: + bb.putLong(byteOffset, v & 0x00000000FFFFFFFFL ); + break; + default: + throw new InternalError("invalid nativeSizeInBytes "+nativeSizeInBytes); + } + } + + /** Retrieves the float at the specified byteOffset. */ + public final float getFloatAt(final int byteOffset) { + return bb.getFloat(byteOffset); + } + + /** Puts a float at the specified byteOffset. */ + public final void setFloatAt(final int byteOffset, final float v) { + bb.putFloat(byteOffset, v); + } + + /** Retrieves the double at the specified byteOffset. */ + public final double getDoubleAt(final int byteOffset) { + return bb.getDouble(byteOffset); + } + + /** Puts a double at the specified byteOffset. */ + public final void setDoubleAt(final int byteOffset, final double v) { + bb.putDouble(byteOffset, v); + } + + /** Retrieves the long at the specified byteOffset. */ + public final long getLongAt(final int byteOffset) { + return bb.getLong(byteOffset); + } + + /** Puts a long at the specified byteOffset. */ + public final void setLongAt(final int byteOffset, final long v) { + bb.putLong(byteOffset, v); + } + + /** Retrieves the long at the specified byteOffset. */ + public final long getLongAt(final int byteOffset, final int nativeSizeInBytes) { + switch(nativeSizeInBytes) { + case 4: + return bb.getInt(byteOffset) & 0x00000000FFFFFFFFL; + case 8: + return bb.getLong(byteOffset); + default: + throw new InternalError("invalid nativeSizeInBytes "+nativeSizeInBytes); + } + } + + /** Puts a long at the specified byteOffset. */ + public final void setLongAt(final int byteOffset, final long v, final int nativeSizeInBytes) { + switch(nativeSizeInBytes) { + case 4: + bb.putInt(byteOffset, (int) ( v & 0x00000000FFFFFFFFL ) ); + break; + case 8: + bb.putLong(byteOffset, v); + break; + default: + throw new InternalError("invalid nativeSizeInBytes "+nativeSizeInBytes); + } + } + + public final void setBytesAt(int byteOffset, final byte[] v) { + for (int i = 0; i < v.length; i++) { + bb.put(byteOffset++, v[i]); + } + } + + public final byte[] getBytesAt(int byteOffset, final byte[] v) { + for (int i = 0; i < v.length; i++) { + v[i] = bb.get(byteOffset++); + } + return v; + } + + public final void setBooleansAt(int byteOffset, final boolean[] v) { + for (int i = 0; i < v.length; i++) { + bb.put(byteOffset++, v[i]?(byte)1:(byte)0); + } + } + + public final boolean[] getBooleansAt(int byteOffset, final boolean[] v) { + for (int i = 0; i < v.length; i++) { + v[i] = (byte)0 != bb.get(byteOffset++); + } + return v; + } + + public final void setCharsAt(int byteOffset, final char[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=2) { + bb.putChar(byteOffset, v[i]); + } + } + + public final char[] getCharsAt(int byteOffset, final char[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=2) { + v[i] = bb.getChar(byteOffset); + } + return v; + } + + public final void setShortsAt(int byteOffset, final short[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=2) { + bb.putShort(byteOffset, v[i]); + } + } + + public final short[] getShortsAt(int byteOffset, final short[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=2) { + v[i] = bb.getShort(byteOffset); + } + return v; + } + + public final void setIntsAt(int byteOffset, final int[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=4) { + bb.putInt(byteOffset, v[i]); + } + } + + public final int[] getIntsAt(int byteOffset, final int[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=4) { + v[i] = bb.getInt(byteOffset); + } + return v; + } + + public final void setFloatsAt(int byteOffset, final float[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=4) { + bb.putFloat(byteOffset, v[i]); + } + } + + public final float[] getFloatsAt(int byteOffset, final float[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=4) { + v[i] = bb.getFloat(byteOffset); + } + return v; + } + + public final void setDoublesAt(int byteOffset, final double[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=8) { + bb.putDouble(byteOffset, v[i]); + } + } + + public final double[] getDoublesAt(int byteOffset, final double[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=8) { + v[i] = bb.getDouble(byteOffset); + } + return v; + } + + public final void setLongsAt(int byteOffset, final long[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=8) { + bb.putLong(byteOffset, v[i]); + } + } + + public final long[] getLongsAt(int byteOffset, final long[] v) { + for (int i = 0; i < v.length; i++, byteOffset+=8) { + v[i] = bb.getLong(byteOffset); + } + return v; + } +} diff --git a/java/org/jau/lang/UnsafeUtil.java b/java_base/org/jau/lang/UnsafeUtil.java index ad652d7..6ad0b94 100644 --- a/java/org/jau/lang/UnsafeUtil.java +++ b/java_base/org/jau/lang/UnsafeUtil.java @@ -32,10 +32,8 @@ import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedAction; -import com.jogamp.common.ExceptionUtils; - -import jogamp.common.Debug; -import jogamp.common.os.PlatformPropsImpl; +import org.jau.sys.Debug; +import org.jau.sys.PlatformProps; /** * Utility methods allowing easy access to certain {@link sun.misc.Unsafe} functionality. @@ -90,13 +88,13 @@ public class UnsafeUtil { ExceptionUtils.dumpThrowable("UnsafeUtil", t); } } - if( null != _theUnsafe[0] && PlatformPropsImpl.JAVA_9 ) { + if( null != _theUnsafe[0] && PlatformProps.JAVA_9 ) { try { _staticFieldOffset[0] = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class); _objectVolatile[0] = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class); _objectVolatile[1] = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class); - if( PlatformPropsImpl.JAVA_9 ) { + if( PlatformProps.JAVA_9 ) { _illegalAccessLoggerClass[0] = Class.forName("jdk.internal.module.IllegalAccessLogger"); final Field loggerField = _illegalAccessLoggerClass[0].getDeclaredField("logger"); _loggerOffset[0] = (Long) _staticFieldOffset[0].invoke(_theUnsafe[0], loggerField); @@ -173,7 +171,7 @@ public class UnsafeUtil { * The caller shall place this call into their own {@link AccessController#doPrivileged(PrivilegedAction)} block. * </p> * <p> - * In case the runtime is not {@link PlatformPropsImpl#JAVA_9} or the logger is not accessible or disabling caused an exception, + * In case the runtime is not {@link PlatformProps#JAVA_9} or the logger is not accessible or disabling caused an exception, * the user {@code action} is just executed w/o temporary logger modifications. * </p> * @param action the user action task diff --git a/java_base/org/jau/sec/SHASum.java b/java_base/org/jau/sec/SHASum.java new file mode 100644 index 0000000..343bcd5 --- /dev/null +++ b/java_base/org/jau/sec/SHASum.java @@ -0,0 +1,292 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2019 Gothel Software e.K. + * Copyright (c) 2019 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sec; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import org.jau.io.IOUtil; +import org.jau.sys.Debug; + +/** + * Utility class to produce secure hash (SHA) sums over diverse input sources. + * <p> + * See {@link #updateDigest(MessageDigest, List)} + * </p> + * <p> + * This implementation is being utilized at JogAmp build time to produce various + * SHA sums over sources, class files and native libraries to ensure their identity. + * See {@link JogampVersion#getImplementationSHASources()}, + * {@link JogampVersion#getImplementationSHAClasses()} + * and {@link JogampVersion#getImplementationSHANatives()}. + * </p> + * <p> + * {@link JogampVersion#getImplementationSHASources()} for module gluegen is produced via: + * <pre> + * java -cp build/gluegen-rt.jar com.jogamp.common.util.SHASum --algorithm 256 --exclude ".*\\.log" --exclude "make/lib/toolchain" src jcpp/src make + * </pre> + * </p> + * @see #SHASum(MessageDigest, List, List, List) + * @see #compute(boolean) + * @see org.jau.pkg.TempJarSHASum + * @see #main(String[]) + */ +public class SHASum { + private static final boolean DEBUG = Debug.debug("SHASum"); + + /** + * {@link MessageDigest#update(byte[], int, int) Updates} the given {@code digest} + * with the bytes contained by the files denoted by the given {@code filenames} in the given order. + * <p> + * To retrieve the list of all files traversing through directories, one may use {@link IOUtil#filesOf(List, List, List)}. + * </p> + * <p> + * The SHA implementation is sensitive to the order of input bytes and hence the given filename order. + * </p> + * <p> + * It is advised to pass given list of filenames in lexicographically sorted order to ensure reproducible outcome across all platforms, + * one may use {@link #sort(ArrayList)}. + * </p> + * <p> + * As an example, one could write + * <pre> + * final MessageDigest digest = ...; + * final long totalBytes = updateDigest(digest, sort(IOUtil.filesOf(Arrays.asList("sources"), null, null))); + * </pre> + * </p> + * @param digest to be updated digest + * @param filenames list of filenames denoting files, which bytes will be used to update the digest + * @return total number of bytes read. + * @throws FileNotFoundException see {@link FileInputStream#FileInputStream(String)} + * @throws IOException see {@link InputStream#read(byte[])} + */ + public static long updateDigest(final MessageDigest digest, final List<String> filenames) throws IOException { + long numBytes = 0; + final byte buffer[] = new byte[4096]; // avoid Platform.getMachineDataInfo().pageSizeInBytes() due to native dependency + for(int i=0; i<filenames.size(); i++) { + final InputStream in = new BufferedInputStream(new FileInputStream(filenames.get(i))); + try { + while (true) { + int count; + if ((count = in.read(buffer)) == -1) { + break; + } + digest.update(buffer, 0, count); + numBytes += count; + } + } finally { + in.close(); + } + } + return numBytes; + } + + /** + * Simple helper to print the given byte-array into a string, here appended to StringBuilder + * @param shasum the given byte-array + * @param sb optional pre-existing StringBuilder, may be null + * @return return given or new StringBuilder with appended hex-string + */ + public static StringBuilder toHexString(final byte[] shasum, StringBuilder sb) { + if( null == sb ) { + sb = new StringBuilder(); + } + for(int i=0; i<shasum.length; i++) { + sb.append(String.format((Locale)null, "%02x", shasum[i])); + } + return sb; + } + + /** + * Returns the sorted list of given strings using {@link String#compareTo(String)}'s lexicographically comparison. + * @param source given input strings + * @return sorted list of given strings + */ + public static List<String> sort(final ArrayList<String> source) { + final String s[] = source.toArray(new String[source.size()]); + Arrays.sort(s, 0, s.length, null); + return Arrays.asList(s); + } + + final MessageDigest digest; + final List<String> origins; + final List<Pattern> excludes, includes; + + /** + * Instance to ensure proper {@link #compute(boolean)} of identical SHA sums over same contents within given paths across machines. + * <p> + * Instantiation of this class is lightweight, {@link #compute(boolean)} performs all operations. + * </p> + * + * @param digest the SHA algorithm + * @param origins the mandatory path origins to be used for {@link IOUtil#filesOf(List, List, List)} + * @param excludes the optional exclude patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @param includes the optional include patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public SHASum(final MessageDigest digest, final List<String> origins, final List<Pattern> excludes, final List<Pattern> includes) { + this.digest = digest; + this.origins = origins; + this.excludes = excludes; + this.includes = includes; + } + + /** + * Implementation gathers all files traversing through given paths via {@link IOUtil#filesOf(List, List, List)}, + * sorts the resulting file list via {@link #sort(ArrayList)} and finally + * calculates the SHA sum over its byte content via {@link #updateDigest(MessageDigest, List)}. + * <p> + * This ensures identical SHA sums over same contents within given paths across machines. + * </p> + * <p> + * This method is heavyweight and performs all operations. + * </p> + * + * @param verbose if true, all used files will be dumped as well as the digest result + * @return the resulting SHA value + * @throws IOException + */ + public final byte[] compute(final boolean verbose) throws IOException { + final List<String> fnamesS = SHASum.sort(IOUtil.filesOf(origins, excludes, includes)); + if( verbose ) { + for(int i=0; i<fnamesS.size(); i++) { + System.err.println(fnamesS.get(i)); + } + } + final long numBytes = SHASum.updateDigest(digest, fnamesS); + final byte[] shasum = digest.digest(); + if( verbose ) { + System.err.println("Digested "+numBytes+" bytes, shasum size "+shasum.length+" bytes"); + System.err.println("Digested result: "+SHASum.toHexString(shasum, null).toString()); + } + return shasum; + } + + public final List<String> getOrigins() { return origins; } + public final List<Pattern> getExcludes() { return excludes; } + public final List<Pattern> getIncludes() { return includes; } + + /** + * Main entry point taking var-arg path or gnu-arguments with a leading '--'. + * <p> + * Implementation gathers all files traversing through given paths via {@link IOUtil#filesOf(List, List, List)}, + * sorts the resulting file list via {@link #sort(ArrayList)} and finally + * calculates the SHA sum over its byte content via {@link #updateDigest(MessageDigest, List)}. + * This ensures identical SHA sums over same contents within given paths. + * </p> + * <p> + * Example to calculate the SHA-256 over our source files as performed for {@link JogampVersion#getImplementationSHASources()} + * <pre> + * java -cp build/gluegen-rt.jar com.jogamp.common.util.SHASum --algorithm 256 --exclude ".*\\.log" --exclude "make/lib/toolchain" src jcpp/src make + * </pre> + * </p> + * <p> + * To validate the implementation, one can gather the sorted list of files (to ensure same order) + * <pre> + * java -cp build/gluegen-rt.jar com.jogamp.common.util.SHASum --listfilesonly --exclude ".*\\.log" --exclude "make/lib/toolchain" src jcpp/src make >& java.sorted.txt + * </pre> + * and then calculate the shasum independently + * <pre> + * find `cat java.sorted.txt` -exec cat {} + | shasum -a 256 -b - | awk '{print $1}' + * </pre> + * </p> + * @param args + * @throws IOException + * @throws URISyntaxException + * @throws IllegalArgumentException + */ + public static void main(final String[] args) throws IOException { + boolean listFilesOnly = false; + int shabits = 256; + int i; + final ArrayList<String> pathU = new ArrayList<String>(); + final ArrayList<Pattern> excludes = new ArrayList<Pattern>(); + final ArrayList<Pattern> includes = new ArrayList<Pattern>(); + { + for(i=0; i<args.length; i++) { + if(null != args[i]) { + if( args[i].startsWith("--") ) { + // options + if( args[i].equals("--algorithm")) { + shabits = Integer.parseInt(args[++i]); + } else if( args[i].equals("--exclude")) { + excludes.add(Pattern.compile(args[++i])); + if( DEBUG ) { + System.err.println("adding exclude: <"+args[i]+"> -> <"+excludes.get(excludes.size()-1)+">"); + } + } else if( args[i].equals("--include")) { + includes.add(Pattern.compile(args[++i])); + if( DEBUG ) { + System.err.println("adding include: <"+args[i]+"> -> <"+includes.get(includes.size()-1)+">"); + } + } else if( args[i].equals("--listfilesonly")) { + listFilesOnly = true; + } else { + System.err.println("Abort, unknown argument: "+args[i]); + return; + } + } else { + pathU.add(args[i]); + if( DEBUG ) { + System.err.println("adding path: <"+args[i]+">"); + } + } + } + } + if( listFilesOnly ) { + final List<String> fnamesS = sort(IOUtil.filesOf(pathU, excludes, includes)); + for(i=0; i<fnamesS.size(); i++) { + System.out.println(fnamesS.get(i)); + } + return; + } + } + final String shaalgo = "SHA-"+shabits; + final MessageDigest digest; + try { + digest = MessageDigest.getInstance(shaalgo); + } catch (final NoSuchAlgorithmException e) { + System.err.println("Abort, implementation for "+shaalgo+" not available: "+e.getMessage()); + return; + } + final SHASum shaSum = new SHASum(digest, pathU, excludes, includes); + System.out.println(toHexString(shaSum.compute(DEBUG), null).toString()); + } +} diff --git a/java_base/org/jau/sec/SecurityUtil.java b/java_base/org/jau/sec/SecurityUtil.java new file mode 100644 index 0000000..172c7fe --- /dev/null +++ b/java_base/org/jau/sec/SecurityUtil.java @@ -0,0 +1,184 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2012 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sec; + +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.security.cert.Certificate; + +public class SecurityUtil { + private static final SecurityManager securityManager; + private static final Permission allPermissions; + private static final boolean DEBUG = false; + + static { + allPermissions = new AllPermission(); + securityManager = System.getSecurityManager(); + + if( DEBUG ) { + final boolean hasAllPermissions; + { + final ProtectionDomain insecPD = AccessController.doPrivileged(new PrivilegedAction<ProtectionDomain>() { + @Override + public ProtectionDomain run() { + return SecurityUtil.class.getProtectionDomain(); + } } ); + boolean _hasAllPermissions; + try { + insecPD.implies(allPermissions); + _hasAllPermissions = true; + } catch( final SecurityException ace ) { + _hasAllPermissions = false; + } + hasAllPermissions = _hasAllPermissions; + } + + System.err.println("SecurityUtil: Has SecurityManager: "+ ( null != securityManager ) ) ; + System.err.println("SecurityUtil: Has AllPermissions: "+hasAllPermissions); + final Certificate[] certs = AccessController.doPrivileged(new PrivilegedAction<Certificate[]>() { + @Override + public Certificate[] run() { + return getCerts(SecurityUtil.class); + } } ); + System.err.println("SecurityUtil: Cert count: "+ ( null != certs ? certs.length : 0 )); + if( null != certs ) { + for(int i=0; i<certs.length; i++) { + System.err.println("\t cert["+i+"]: "+certs[i].toString()); + } + } + } + } + + /** + * Returns <code>true</code> if no {@link SecurityManager} has been installed + * or the installed {@link SecurityManager}'s <code>checkPermission(new AllPermission())</code> + * passes. Otherwise method returns <code>false</code>. + */ + public static final boolean hasAllPermissions() { + return hasPermission(allPermissions); + } + + /** + * Returns <code>true</code> if no {@link SecurityManager} has been installed + * or the installed {@link SecurityManager}'s <code>checkPermission(perm)</code> + * passes. Otherwise method returns <code>false</code>. + */ + public static final boolean hasPermission(final Permission perm) { + try { + checkPermission(perm); + return true; + } catch( final SecurityException ace ) { + return false; + } + } + + /** + * Throws an {@link SecurityException} if an installed {@link SecurityManager} + * does not permit the requested {@link AllPermission}. + */ + public static final void checkAllPermissions() throws SecurityException { + checkPermission(allPermissions); + } + + /** + * Throws an {@link SecurityException} if an installed {@link SecurityManager} + * does not permit the requested {@link Permission}. + */ + public static final void checkPermission(final Permission perm) throws SecurityException { + if( null != securityManager ) { + securityManager.checkPermission(perm); + } + } + + /** + * Returns <code>true</code> if no {@link SecurityManager} has been installed + * or the installed {@link SecurityManager}'s <code>checkLink(libName)</code> + * passes. Otherwise method returns <code>false</code>. + */ + public static final boolean hasLinkPermission(final String libName) { + try { + checkLinkPermission(libName); + return true; + } catch( final SecurityException ace ) { + return false; + } + } + + /** + * Throws an {@link SecurityException} if an installed {@link SecurityManager} + * does not permit to dynamically link the given libName. + */ + public static final void checkLinkPermission(final String libName) throws SecurityException { + if( null != securityManager ) { + securityManager.checkLink(libName); + } + } + + /** + * Throws an {@link SecurityException} if an installed {@link SecurityManager} + * does not permit to dynamically link to all libraries. + */ + public static final void checkAllLinkPermission() throws SecurityException { + if( null != securityManager ) { + securityManager.checkPermission(allLinkPermission); + } + } + private static final RuntimePermission allLinkPermission = new RuntimePermission("loadLibrary.*"); + + /** + * @param clz + * @return + * @throws SecurityException if the caller has no permission to access the ProtectedDomain of the given class. + */ + public static final Certificate[] getCerts(final Class<?> clz) throws SecurityException { + final ProtectionDomain pd = clz.getProtectionDomain(); + final CodeSource cs = (null != pd) ? pd.getCodeSource() : null; + final Certificate[] certs = (null != cs) ? cs.getCertificates() : null; + return (null != certs && certs.length>0) ? certs : null; + } + + public static final boolean equals(final Certificate[] a, final Certificate[] b) { + if(a == b) { + return true; + } + if(a==null || b==null) { + return false; + } + if(a.length != b.length) { + return false; + } + + int i = 0; + while( i < a.length && a[i].equals(b[i]) ) { + i++; + } + return i == a.length; + } +} diff --git a/java_base/org/jau/sys/AndroidUtil.java b/java_base/org/jau/sys/AndroidUtil.java new file mode 100644 index 0000000..770f844 --- /dev/null +++ b/java_base/org/jau/sys/AndroidUtil.java @@ -0,0 +1,90 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2012 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys; + +import java.io.File; +import java.lang.reflect.Method; + +import org.jau.lang.ReflectionUtil; + +public class AndroidUtil { + + private static final Method androidGetPackageInfoVersionNameMethod; + private static final Method androidGetPackageInfoVersionCodeMethod; + private static final Method androidGetTempRootMethod; + + static { + if(AndroidVersion.isAvailable) { + final ClassLoader cl = AndroidUtil.class.getClassLoader(); + final Class<?> androidAndroidUtilImplClz = ReflectionUtil.getClass("jau.sys.android.AndroidUtilImpl", true, cl); + androidGetPackageInfoVersionCodeMethod = ReflectionUtil.getMethod(androidAndroidUtilImplClz, "getPackageInfoVersionCode", String.class); + androidGetPackageInfoVersionNameMethod = ReflectionUtil.getMethod(androidAndroidUtilImplClz, "getPackageInfoVersionName", String.class); + androidGetTempRootMethod = ReflectionUtil.getMethod(androidAndroidUtilImplClz, "getTempRoot"); + } else { + androidGetPackageInfoVersionCodeMethod = null; + androidGetPackageInfoVersionNameMethod = null; + androidGetTempRootMethod = null; + } + } + + /** + * @return null if platform is not Android or no Android Context is registered + * via {@link jogamp.common.os.android.StaticContext#init(android.content.Context) StaticContext.init(..)}, + * otherwise the found package version code of <code>packageName</code> is returned. + */ + public static final int getPackageInfoVersionCode(final String packageName) { + if(null != androidGetPackageInfoVersionCodeMethod) { + return ((Integer) ReflectionUtil.callStaticMethod(androidGetPackageInfoVersionCodeMethod, packageName)).intValue(); + } + return -1; + } + + /** + * @return null if platform is not Android or no Android Context is registered + * via {@link jogamp.common.os.android.StaticContext#init(android.content.Context) StaticContext.init(..)}, + * otherwise the found package version name of <code>packageName</code> is returned. + */ + public static final String getPackageInfoVersionName(final String packageName) { + if(null != androidGetPackageInfoVersionNameMethod) { + return (String) ReflectionUtil.callStaticMethod(androidGetPackageInfoVersionNameMethod, packageName); + } + return null; + } + + /** + * @return null if platform is not Android or no Android Context is registered + * via {@link jogamp.common.os.android.StaticContext#init(android.content.Context) StaticContext.init(..)}, + * otherwise the context relative world readable <code>temp</code> directory returned. + */ + public static File getTempRoot() + throws RuntimeException { + if(null != androidGetTempRootMethod) { + return (File) ReflectionUtil.callStaticMethod(androidGetTempRootMethod); + } + return null; + } + +} diff --git a/java_base/org/jau/sys/AndroidVersion.java b/java_base/org/jau/sys/AndroidVersion.java new file mode 100644 index 0000000..dc76903 --- /dev/null +++ b/java_base/org/jau/sys/AndroidVersion.java @@ -0,0 +1,178 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys; + +import java.lang.reflect.Field; +import java.util.HashMap; + +import org.jau.lang.ReflectionUtil; +import org.jau.sys.PlatformTypes.ABIType; +import org.jau.sys.PlatformTypes.CPUType; + +public class AndroidVersion { + public static final boolean isAvailable; + + /** The name of the instruction set (CPU type + ABI convention) of native code. API-4. All lower case.*/ + public static final String CPU_ABI_NAME; + public static final CPUType CPU; + public static final ABIType ABI; + + /** The name of the second instruction set (CPU type + ABI convention) of native code. API-8. All lower case.*/ + public static final String CPU_ABI2_NAME; + public static final CPUType CPU2; + public static final ABIType ABI2; + + /** Development codename, or the string "REL" for official release */ + public static final String CODENAME; + + /** internal build value used by the underlying source control. */ + public static final String INCREMENTAL; + + /** official build version string */ + public static final String RELEASE; + + /** SDK Version number, key to VERSION_CODES */ + public static final int SDK_INT; + + /** SDK Version string */ + public static final String SDK_NAME; + + private static final String androidBuild = "android.os.Build"; + private static final String androidBuildVersion = "android.os.Build$VERSION"; + private static final String androidBuildVersionCodes = "android.os.Build$VERSION_CODES"; + + static { + final ClassLoader cl = AndroidVersion.class.getClassLoader(); + Class<?> abClass = null; + Object abObject= null; + Class<?> abvClass = null; + Object abvObject= null; + Class<?> abvcClass = null; + Object abvcObject= null; + + final boolean isDalvikVm = "Dalvik".equals(System.getProperty("java.vm.name")); + + if (isDalvikVm) { + try { + abClass = ReflectionUtil.getClass(androidBuild, true, cl); + abObject = abClass.getDeclaredConstructor().newInstance(); + abvClass = ReflectionUtil.getClass(androidBuildVersion, true, cl); + abvObject = abvClass.getDeclaredConstructor().newInstance(); + abvcClass = ReflectionUtil.getClass(androidBuildVersionCodes, true, cl); + abvcObject = abvcClass.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { /* n/a */ } + } + isAvailable = isDalvikVm && null != abObject && null != abvObject; + if(isAvailable) { + CPU_ABI_NAME = getString(abClass, abObject, "CPU_ABI", true); + CPU_ABI2_NAME = getString(abClass, abObject, "CPU_ABI2", true); + CODENAME = getString(abvClass, abvObject, "CODENAME", false); + INCREMENTAL = getString(abvClass, abvObject, "INCREMENTAL", false); + RELEASE = getString(abvClass, abvObject, "RELEASE", false); + SDK_INT = getInt(abvClass, abvObject, "SDK_INT"); + final String sdk_name; + if( null != abvcObject ) { + final HashMap<Integer, String> version_codes = getVersionCodes(abvcClass, abvcObject); + sdk_name = version_codes.get(SDK_INT); + } else { + sdk_name = null; + } + SDK_NAME = ( null != sdk_name ) ? sdk_name : "SDK_"+SDK_INT ; + + /** + * <p> + * FIXME: Where is a comprehensive list of known 'android.os.Build.CPU_ABI' and 'android.os.Build.CPU_ABI2' strings ?<br/> + * Fount this one: <code>http://www.kandroid.org/ndk/docs/CPU-ARCH-ABIS.html</code> + * <pre> + * lib/armeabi/libfoo.so + * lib/armeabi-v7a/libfoo.so + * lib/arm64-v8a/libfoo.so + * lib/x86/libfoo.so + * lib/mips/libfoo.so + * </pre> + * </p> + */ + CPU = PlatformTypes.CPUType.query(CPU_ABI_NAME); + ABI = PlatformTypes.ABIType.query(CPU, CPU_ABI_NAME); + if( null != CPU_ABI2_NAME && CPU_ABI2_NAME.length() > 0 ) { + CPU2 = PlatformTypes.CPUType.query(CPU_ABI2_NAME); + ABI2 = PlatformTypes.ABIType.query(CPU2, CPU_ABI2_NAME); + } else { + CPU2 = null; + ABI2 = null; + } + } else { + CPU_ABI_NAME = null; + CPU_ABI2_NAME = null; + CODENAME = null; + INCREMENTAL = null; + RELEASE = null; + SDK_INT = -1; + SDK_NAME = null; + CPU = null; + ABI = null; + CPU2 = null; + ABI2 = null; + } + } + + private static final HashMap<Integer, String> getVersionCodes(final Class<?> cls, final Object obj) { + final Field[] fields = cls.getFields(); + final HashMap<Integer, String> map = new HashMap<Integer, String>( 3 * fields.length / 2, 0.75f ); + for(int i=0; i<fields.length; i++) { + try { + final int version = fields[i].getInt(obj); + final String version_name = fields[i].getName(); + // System.err.println(i+": "+version+": "+version_name); + map.put(Integer.valueOf(version), version_name); + } catch (final Exception e) { e.printStackTrace(); /* n/a */ } + } + return map; + } + + private static final String getString(final Class<?> cls, final Object obj, final String name, final boolean lowerCase) { + try { + final Field f = cls.getField(name); + final String s = (String) f.get(obj); + if( lowerCase && null != s ) { + return s.toLowerCase(); + } else { + return s; + } + } catch (final Exception e) { e.printStackTrace(); /* n/a */ } + return null; + } + + private static final int getInt(final Class<?> cls, final Object obj, final String name) { + try { + final Field f = cls.getField(name); + return f.getInt(obj); + } catch (final Exception e) { e.printStackTrace(); /* n/a */ } + return -1; + } + + // android.os.Build.VERSION +} diff --git a/java/org/jau/sys/Debug.java b/java_base/org/jau/sys/Debug.java index 27a149c..4673d60 100644 --- a/java/org/jau/sys/Debug.java +++ b/java_base/org/jau/sys/Debug.java @@ -29,8 +29,6 @@ package org.jau.sys; import java.security.AccessController; import java.security.PrivilegedAction; -import com.jogamp.common.util.PropertyAccess; - /** Helper routines for logging and debugging. */ public class Debug extends PropertyAccess { @@ -42,12 +40,12 @@ public class Debug extends PropertyAccess { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { - PropertyAccess.addTrustedPrefix("jogamp."); + PropertyAccess.addTrustedPrefix("jau."); return null; } } ); - verbose = isPropertyDefined("jogamp.verbose", true); - debugAll = isPropertyDefined("jogamp.debug", true); + verbose = isPropertyDefined("jau.verbose", true); + debugAll = isPropertyDefined("jau.debug", true); } /** Ensures static init block has been issues, i.e. if calling through to {@link PropertyAccess#isPropertyDefined(String, boolean)}. */ @@ -62,6 +60,6 @@ public class Debug extends PropertyAccess { } public static final boolean debug(final String subcomponent) { - return debugAll() || isPropertyDefined("jogamp.debug." + subcomponent, true); + return debugAll() || isPropertyDefined("jau.debug." + subcomponent, true); } } diff --git a/java_base/org/jau/sys/JNILibrary.java b/java_base/org/jau/sys/JNILibrary.java new file mode 100644 index 0000000..56320bc --- /dev/null +++ b/java_base/org/jau/sys/JNILibrary.java @@ -0,0 +1,492 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys; + +import java.io.File; +import java.net.URISyntaxException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; + +import org.jau.io.IOUtil; +import org.jau.lang.ReflectionUtil; + +/** + * Static JNI Native Libraries handler. + */ +public class JNILibrary { + public static final boolean DEBUG; + protected static final boolean PERF; + + private static final String[] prefixes; + private static final String[] suffixes; + private static final boolean isOSX; + + private static final String tjc_name = "org.jau.pkg.cache.TempJarCache"; + private static final ReflectionUtil.MethodAccessor tjcIsInit; + private static final ReflectionUtil.MethodAccessor tjcFindLib; + private static final boolean tjcAvail; + + static { + Debug.initSingleton(); + DEBUG = Debug.debug("JNILibrary"); + PERF = DEBUG || PropertyAccess.isPropertyDefined("jau.debug.JNILibrary.Perf", true); + + switch (PlatformProps.OS) { + case WINDOWS: + prefixes = new String[] { "" }; + suffixes = new String[] { ".dll" }; + isOSX = false; + break; + + case MACOS: + case IOS: + prefixes = new String[] { "lib" }; + suffixes = new String[] { ".dylib" }; + isOSX = true; + break; + + /* + case ANDROID: + case FREEBSD: + case SUNOS: + case HPUX: + case OPENKODE: + case LINUX: */ + default: + prefixes = new String[] { "lib" }; + suffixes = new String[] { ".so" }; + isOSX = false; + break; + } + + Class<?> tjc = null; + try { + tjc = ReflectionUtil.getClass(tjc_name, false /* initializeClazz */, JNILibrary.class.getClassLoader()); + } catch (final Throwable t) {} + if( null != tjc ) { + tjcIsInit = new ReflectionUtil.MethodAccessor(tjc, "isInitialized"); + tjcFindLib = new ReflectionUtil.MethodAccessor(tjc, "findLibrary", String.class); + tjcAvail = tjcIsInit.available() && tjcFindLib.available(); + if( DEBUG ) { + System.err.println("JNILibrary: Available <"+tjc_name+">, fully avail "+tjcAvail); + } + } else { + tjcIsInit = null; + tjcFindLib = null; + tjcAvail = false; + if( DEBUG ) { + System.err.println("JNILibrary: Not available <"+tjc_name+">"); + } + } + } + + protected static final Object perfSync = new Object(); + protected static long perfTotal = 0; + protected static long perfCount = 0; + + private static final HashSet<String> loaded = new HashSet<String>(); + + public static synchronized boolean isLoaded(final String libName) { + return loaded.contains(libName); + } + + private static synchronized void addLoaded(final String libName) { + loaded.add(libName); + if(DEBUG) { + System.err.println("JNILibrary: Loaded Native Library: "+libName); + } + } + + /** + * Loads the library specified by libname.<br> + * The implementation should ignore, if the library has been loaded already.<br> + * @param libname the library to load + * @param ignoreError if true, errors during loading the library should be ignored + * @param cl optional ClassLoader, used to locate the library + * @return true if library loaded successful + */ + public static synchronized boolean loadLibrary(final String libname, final boolean ignoreError, final ClassLoader cl) + throws SecurityException, UnsatisfiedLinkError + { + boolean res = true; + if(!isLoaded(libname)) { + try { + loadLibraryImpl(libname, cl); + addLoaded(libname); + if(DEBUG) { + System.err.println("JNILibrary: loaded "+libname); + } + } catch (final UnsatisfiedLinkError e) { + res = false; + if(DEBUG) { + e.printStackTrace(); + } + if (!ignoreError && e.getMessage().indexOf("already loaded") < 0) { + throw e; + } + } + } + return res; + } + + /** + * Loads the library specified by libname.<br> + * Optionally preloads the libraries specified by preload.<br> + * The implementation should ignore, if any library has been loaded already.<br> + * @param libname the library to load + * @param preload the libraries to load before loading the main library if not null + * @param preloadIgnoreError if true, errors during loading the preload-libraries should be ignored + * @param cl optional ClassLoader, used to locate the library + */ + public static synchronized void loadLibrary(final String libname, final String[] preload, final boolean preloadIgnoreError, final ClassLoader cl) + throws SecurityException, UnsatisfiedLinkError + { + if(!isLoaded(libname)) { + if (null!=preload) { + for (int i=0; i<preload.length; i++) { + loadLibrary(preload[i], preloadIgnoreError, cl); + } + } + loadLibrary(libname, false, cl); + } + } + + private static void loadLibraryImpl(final String libraryName, final ClassLoader cl) throws SecurityException, UnsatisfiedLinkError { + // Note: special-casing JAWT which is built in to the JDK + int mode = 0; // 2 - System.load( TempJarCache ), 3 - System.loadLibrary( name ), 4 - System.load( enumLibNames ) + // System.err.println("sun.boot.library.path=" + Debug.getProperty("sun.boot.library.path", false)); + final String libraryPath = findLibrary(libraryName, cl); // implicit TempJarCache usage if used/initialized + if(DEBUG) { + System.err.println("JNILibrary: loadLibraryInternal("+libraryName+"), TempJarCache: "+libraryPath); + } + if(null != libraryPath) { + if(DEBUG) { + System.err.println("JNILibrary: System.load("+libraryPath+") - mode 2"); + } + System.load(libraryPath); + mode = 2; + } else { + if(DEBUG) { + System.err.println("JNILibrary: System.loadLibrary("+libraryName+") - mode 3"); + } + try { + System.loadLibrary(libraryName); + mode = 3; + } catch (final UnsatisfiedLinkError ex1) { + if(DEBUG) { + System.err.println("ERROR (retry w/ enumLibPath) - "+ex1.getMessage()); + } + final List<String> possiblePaths = enumerateLibraryPaths(libraryName, + false /* searchSystemPath */, false /* searchSystemPathFirst */, cl); + // Iterate down these and see which one if any we can actually find. + for (final Iterator<String> iter = possiblePaths.iterator(); 0 == mode && iter.hasNext(); ) { + final String path = iter.next(); + if (DEBUG) { + System.err.println("JNILibrary: System.load("+path+") - mode 4"); + } + try { + System.load(path); + mode = 4; + } catch (final UnsatisfiedLinkError ex2) { + if(DEBUG) { + System.err.println("n/a - "+ex2.getMessage()); + } + if(!iter.hasNext()) { + throw ex2; + } + } + } + } + } + if(DEBUG) { + System.err.println("JNILibrary: loadLibraryInternal("+libraryName+"): OK - mode "+mode); + } + } + + public static final String findLibrary(final String libName, final ClassLoader loader) { + String res = null; + if( tjcAvail ) { // TempJarCache .. + if( Boolean.TRUE == tjcIsInit.callStaticMethod() ) { + res = tjcFindLib.callStaticMethod(libName); + if (DEBUG) { + System.err.println("NativeLibrary.findLibrary(<"+libName+">) (TempJarCache): "+res); + } + } + } + return res; + } + + /** + * Comparison of prefix and suffix of the given libName's basename + * is performed case insensitive <br> + * + * @param libName the full path library name with prefix and suffix + * @param isLowerCaseAlready indicates if libName is already lower-case + * + * @return basename of libName w/o path, ie. /usr/lib/libDrinkBeer.so -> DrinkBeer on Unix systems, but null on Windows. + */ + public static final String isValidNativeLibraryName(final String libName, final boolean isLowerCaseAlready) { + final String libBaseName; + try { + libBaseName = IOUtil.getBasename(libName); + } catch (final URISyntaxException uriEx) { + throw new IllegalArgumentException(uriEx); + } + final String libBaseNameLC = isLowerCaseAlready ? libBaseName : libBaseName.toLowerCase(); + int prefixIdx = -1; + for(int i=0; i < prefixes.length && 0 > prefixIdx; i++) { + if ( libBaseNameLC.startsWith( prefixes[i] ) ) { + prefixIdx = i; + } + } + if( 0 <= prefixIdx ) { + for(int i=0; i < suffixes.length; i++) { + if ( libBaseNameLC.endsWith( suffixes[i] ) ) { + final int s = prefixes[prefixIdx].length(); + final int e = suffixes[i].length(); + return libBaseName.substring(s, libBaseName.length()-e); + } + } + } + return null; + } + + /** Given the base library names (no prefixes/suffixes) for the + various platforms, enumerate the possible locations and names of + the indicated native library on the system using the system path. */ + public static final List<String> enumerateLibraryPaths(final String libName, + final boolean searchSystemPath, + final boolean searchSystemPathFirst, + final ClassLoader loader) { + return enumerateLibraryPaths(libName, libName, libName, + searchSystemPath, searchSystemPathFirst, + loader); + } + + /** Given the base library names (no prefixes/suffixes) for the + various platforms, enumerate the possible locations and names of + the indicated native library on the system using the system path. */ + public static final List<String> enumerateLibraryPaths(final String windowsLibName, + final String unixLibName, + final String macOSXLibName, + final boolean searchSystemPath, + final boolean searchSystemPathFirst, + final ClassLoader loader) { + final List<String> paths = new ArrayList<String>(); + final String libName = selectName(windowsLibName, unixLibName, macOSXLibName); + if (libName == null) { + return paths; + } + + // Allow user's full path specification to override our building of paths + final File file = new File(libName); + if (file.isAbsolute()) { + paths.add(libName); + return paths; + } + + final String[] baseNames = buildNames(libName); + + if( searchSystemPath && searchSystemPathFirst ) { + // Add just the library names to use the OS's search algorithm + for (int i = 0; i < baseNames.length; i++) { + paths.add(baseNames[i]); + } + // Add probable Mac OS X-specific paths + if ( isOSX ) { + // Add historical location + addPaths("/Library/Frameworks/" + libName + ".framework", baseNames, paths); + // Add current location + addPaths("/System/Library/Frameworks/" + libName + ".framework", baseNames, paths); + } + } + + final String clPath = findLibrary(libName, loader); + if (clPath != null) { + paths.add(clPath); + } + + // Add entries from java.library.path + final String[] javaLibraryPaths = + AccessController.doPrivileged(new PrivilegedAction<String[]>() { + @Override + public String[] run() { + int count = 0; + final String usrPath = System.getProperty("java.library.path"); + if(null != usrPath) { + count++; + } + final String sysPath; + if( searchSystemPath ) { + sysPath = System.getProperty("sun.boot.library.path"); + if(null != sysPath) { + count++; + } + } else { + sysPath = null; + } + final String[] res = new String[count]; + int i=0; + if( null != sysPath && searchSystemPathFirst ) { + res[i++] = sysPath; + } + if(null != usrPath) { + res[i++] = usrPath; + } + if( null != sysPath && !searchSystemPathFirst ) { + res[i++] = sysPath; + } + return res; + } + }); + if ( null != javaLibraryPaths ) { + for( int i=0; i < javaLibraryPaths.length; i++ ) { + final StringTokenizer tokenizer = new StringTokenizer(javaLibraryPaths[i], File.pathSeparator); + while (tokenizer.hasMoreTokens()) { + addPaths(tokenizer.nextToken(), baseNames, paths); + } + } + } + + // Add current working directory + final String userDir = + AccessController.doPrivileged(new PrivilegedAction<String>() { + @Override + public String run() { + return System.getProperty("user.dir"); + } + }); + addPaths(userDir, baseNames, paths); + + // Add current working directory + natives/os-arch/ + library names + // to handle Bug 1145 cc1 using an unpacked fat-jar + addPaths(userDir+File.separator+"natives"+File.separator+PlatformProps.os_and_arch+File.separator, baseNames, paths); + + if( searchSystemPath && !searchSystemPathFirst ) { + // Add just the library names to use the OS's search algorithm + for (int i = 0; i < baseNames.length; i++) { + paths.add(baseNames[i]); + } + // Add probable Mac OS X-specific paths + if ( isOSX ) { + // Add historical location + addPaths("/Library/Frameworks/" + libName + ".Framework", baseNames, paths); + // Add current location + addPaths("/System/Library/Frameworks/" + libName + ".Framework", baseNames, paths); + } + } + + return paths; + } + + + private static final String selectName(final String windowsLibName, + final String unixLibName, + final String macOSXLibName) { + switch (PlatformProps.OS) { + case WINDOWS: + return windowsLibName; + + case MACOS: + case IOS: + return macOSXLibName; + + default: + return unixLibName; + } + } + + private static final String[] buildNames(final String libName) { + // If the library name already has the prefix / suffix added + // (principally because we want to force a version number on Unix + // operating systems) then just return the library name. + final String libBaseNameLC; + try { + libBaseNameLC = IOUtil.getBasename(libName).toLowerCase(); + } catch (final URISyntaxException uriEx) { + throw new IllegalArgumentException(uriEx); + } + + int prefixIdx = -1; + for(int i=0; i<prefixes.length && 0 > prefixIdx; i++) { + if (libBaseNameLC.startsWith(prefixes[i])) { + prefixIdx = i; + } + } + if( 0 <= prefixIdx ) { + for(int i=0; i<suffixes.length; i++) { + if (libBaseNameLC.endsWith(suffixes[i])) { + return new String[] { libName }; + } + } + int suffixIdx = -1; + for(int i=0; i<suffixes.length && 0 > suffixIdx; i++) { + suffixIdx = libBaseNameLC.indexOf(suffixes[i]); + } + boolean ok = true; + if (suffixIdx >= 0) { + // Check to see if everything after it is a Unix version number + for (int i = suffixIdx + suffixes[0].length(); + i < libName.length(); + i++) { + final char c = libName.charAt(i); + if (!(c == '.' || (c >= '0' && c <= '9'))) { + ok = false; + break; + } + } + if (ok) { + return new String[] { libName }; + } + } + } + + final String[] res = new String[prefixes.length * suffixes.length + ( isOSX ? 1 : 0 )]; + int idx = 0; + for (int i = 0; i < prefixes.length; i++) { + for (int j = 0; j < suffixes.length; j++) { + res[idx++] = prefixes[i] + libName + suffixes[j]; + } + } + if ( isOSX ) { + // Plain library-base-name in Framework folder + res[idx++] = libName; + } + return res; + } + + private static final void addPaths(final String path, final String[] baseNames, final List<String> paths) { + for (int j = 0; j < baseNames.length; j++) { + paths.add(path + File.separator + baseNames[j]); + } + } +} diff --git a/java_base/org/jau/sys/MachineDataInfo.java b/java_base/org/jau/sys/MachineDataInfo.java new file mode 100644 index 0000000..314ce6f --- /dev/null +++ b/java_base/org/jau/sys/MachineDataInfo.java @@ -0,0 +1,393 @@ +/** + * Author: Sven Gothel <[email protected]> + * Author: Kenneth Bradley Russell + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * Copyright (c) 2003 Sun Microsystems + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys; + +/** + * Machine data description for alignment and size onle, see {@link com.jogamp.gluegen}. + * <p> + * {@code little-endian} / {@code big/endian} description is left, + * allowing re-using instances in {@link MachineDataInfo.StaticConfig StaticConfig}. + * Use {@link {@link PlatformProps#LITTLE_ENDIAN}. + * </p> + * <p> + * Further more, the value {@ MachineDataInfo#pageSizeInBytes} shall be ignored + * in {@link MachineDataInfo.StaticConfig StaticConfig}, see {@link MachineDataInfo#compatible(MachineDataInfo)}. + * </p> + */ +public class MachineDataInfo { + /* arch os int, long, float, doubl, ldoubl, ptr, page */ + private final static int[] size_arm_mips_32 = { 4, 4, 4, 8, 8, 4, 4096 }; + private final static int[] size_x86_32_unix = { 4, 4, 4, 8, 12, 4, 4096 }; + private final static int[] size_x86_32_android = { 4, 4, 4, 8, 8, 4, 4096 }; + private final static int[] size_x86_32_macos = { 4, 4, 4, 8, 16, 4, 4096 }; + private final static int[] size_ppc_32_unix = { 4, 4, 4, 8, 16, 4, 4096 }; + private final static int[] size_sparc_32_sunos = { 4, 4, 4, 8, 16, 4, 8192 }; + private final static int[] size_x86_32_windows = { 4, 4, 4, 8, 12, 4, 4096 }; + private final static int[] size_lp64_unix = { 4, 8, 4, 8, 16, 8, 4096 }; + private final static int[] size_x86_64_windows = { 4, 4, 4, 8, 16, 8, 4096 }; + private final static int[] size_arm64_ios = { 4, 8, 4, 8, 8, 8, 8192 }; + + /* arch os i8, i16, i32, i64, int, long, float, doubl, ldoubl, ptr */ + private final static int[] align_arm_mips_32 = { 1, 2, 4, 8, 4, 4, 4, 8, 8, 4 }; + private final static int[] align_x86_32_unix = { 1, 2, 4, 4, 4, 4, 4, 4, 4, 4 }; + private final static int[] align_x86_32_macos = { 1, 2, 4, 4, 4, 4, 4, 4, 16, 4 }; + private final static int[] align_ppc_32_unix = { 1, 2, 4, 8, 4, 4, 4, 8, 16, 4 }; + private final static int[] align_sparc_32_sunos = { 1, 2, 4, 8, 4, 4, 4, 8, 8, 4 }; + private final static int[] align_x86_32_windows = { 1, 2, 4, 8, 4, 4, 4, 8, 4, 4 }; + private final static int[] align_lp64_unix = { 1, 2, 4, 8, 4, 8, 4, 8, 16, 8 }; + private final static int[] align_x86_64_windows = { 1, 2, 4, 8, 4, 4, 4, 8, 16, 8 }; + private final static int[] align_arm64_ios = { 1, 2, 4, 8, 4, 8, 4, 8, 8, 8 }; + + /** + * Static enumeration of {@link MachineDataInfo} instances + * used for high performance data size and alignment lookups, + * e.g. for generated structures using the {@link MachineDataInfo.StaticConfig} index. + * <p> + * The value {@link MachineDataInfo#pageSizeInBytes} shall be ignored + * for static instances! + * </p> + * <p> + * If changing this table, you need to: + * <ul> + * <li>Rebuild GlueGen.</li> + * <li>Run ant {@code build.xml} target {@code generate.os.sources}.</li> + * <li>Rebuild everything.</li> + * </ul> + * .. b/c the generated code for glued structures must reflect this change! + * </p> + */ + public enum StaticConfig { + /** {@link Platform.CPUType#ARM} or {@link Platform.CPUType#MIPS_32} */ + ARM_MIPS_32( size_arm_mips_32, align_arm_mips_32), + /** {@link Platform.CPUType#X86_32} Unix */ + X86_32_UNIX( size_x86_32_unix, align_x86_32_unix), + /** {@link Platform.CPUType#X86_32} Android/Bionic */ + X86_32_ANDROID( size_x86_32_android, align_x86_32_unix), + /** {@link Platform.CPUType#X86_32} MacOS (Special case gcc4/OSX) */ + X86_32_MACOS( size_x86_32_macos, align_x86_32_macos), + /** {@link Platform.CPUType#PPC} Unix */ + PPC_32_UNIX( size_ppc_32_unix, align_ppc_32_unix), + /** {@link Platform.CPUType#SPARC_32} Solaris */ + SPARC_32_SUNOS( size_sparc_32_sunos, align_sparc_32_sunos), + /** {@link Platform.CPUType#X86_32} Windows */ + X86_32_WINDOWS( size_x86_32_windows, align_x86_32_windows), + /** LP64 Unix, e.g.: {@link Platform.CPUType#X86_64} Unix, {@link Platform.CPUType#ARM64} EABI, {@link Platform.CPUType#PPC64} Unix, .. */ + LP64_UNIX( size_lp64_unix, align_lp64_unix), + /** {@link Platform.CPUType#X86_64} Windows */ + X86_64_WINDOWS( size_x86_64_windows, align_x86_64_windows), + /** {@link Platform.CPUType#ARM64 } iOS */ + ARM64_IOS( size_arm64_ios, align_arm64_ios); + // 9 + + public final MachineDataInfo md; + + StaticConfig(final int[] sizes, final int[] alignments) { + int i=0, j=0; + this.md = new MachineDataInfo(false, + sizes[i++], + sizes[i++], + sizes[i++], + sizes[i++], + sizes[i++], + sizes[i++], + sizes[i++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++], + alignments[j++]); + } + + public final StringBuilder toString(StringBuilder sb) { + if(null==sb) { + sb = new StringBuilder(); + } + sb.append("MachineDataInfoStatic: ").append(this.name()).append("(").append(this.ordinal()).append("): "); + md.toString(sb); + return sb; + } + public final String toShortString() { + return this.name()+"("+this.ordinal()+")"; + } + @Override + public String toString() { + return toString(null).toString(); + } + + /** + * Static's {@link MachineDataInfo} shall be unique by the + * {@link MachineDataInfo#compatible(MachineDataInfo) compatible} criteria. + */ + public static final void validateUniqueMachineDataInfo() { + final StaticConfig[] scs = StaticConfig.values(); + for(int i=scs.length-1; i>=0; i--) { + final StaticConfig a = scs[i]; + for(int j=scs.length-1; j>=0; j--) { + if( i != j ) { + final StaticConfig b = scs[j]; + if( a.md.compatible(b.md) ) { + // oops + final String msg = "Duplicate/Compatible MachineDataInfo in StaticConfigs: Elements ["+i+": "+a.toShortString()+"] and ["+j+": "+b.toShortString()+"]"; + System.err.println(msg); + System.err.println(a); + System.err.println(b); + throw new InternalError(msg); + } + } + } + } + } + public static final StaticConfig findCompatible(final MachineDataInfo md) { + final StaticConfig[] scs = StaticConfig.values(); + for(int i=scs.length-1; i>=0; i--) { + final StaticConfig a = scs[i]; + if( a.md.compatible(md) ) { + return a; + } + } + return null; + } + } + + final private boolean runtimeValidated; + + final private int int8SizeInBytes = 1; + final private int int16SizeInBytes = 2; + final private int int32SizeInBytes = 4; + final private int int64SizeInBytes = 8; + + final private int intSizeInBytes; + final private int longSizeInBytes; + final private int floatSizeInBytes; + final private int doubleSizeInBytes; + final private int ldoubleSizeInBytes; + final private int pointerSizeInBytes; + final private int pageSizeInBytes; + + final private int int8AlignmentInBytes; + final private int int16AlignmentInBytes; + final private int int32AlignmentInBytes; + final private int int64AlignmentInBytes; + final private int intAlignmentInBytes; + final private int longAlignmentInBytes; + final private int floatAlignmentInBytes; + final private int doubleAlignmentInBytes; + final private int ldoubleAlignmentInBytes; + final private int pointerAlignmentInBytes; + + public MachineDataInfo(final boolean runtimeValidated, + + final int intSizeInBytes, + final int longSizeInBytes, + final int floatSizeInBytes, + final int doubleSizeInBytes, + final int ldoubleSizeInBytes, + final int pointerSizeInBytes, + final int pageSizeInBytes, + + final int int8AlignmentInBytes, + final int int16AlignmentInBytes, + final int int32AlignmentInBytes, + final int int64AlignmentInBytes, + final int intAlignmentInBytes, + final int longAlignmentInBytes, + final int floatAlignmentInBytes, + final int doubleAlignmentInBytes, + final int ldoubleAlignmentInBytes, + final int pointerAlignmentInBytes) { + this.runtimeValidated = runtimeValidated; + + this.intSizeInBytes = intSizeInBytes; + this.longSizeInBytes = longSizeInBytes; + this.floatSizeInBytes = floatSizeInBytes; + this.doubleSizeInBytes = doubleSizeInBytes; + this.ldoubleSizeInBytes = ldoubleSizeInBytes; + this.pointerSizeInBytes = pointerSizeInBytes; + this.pageSizeInBytes = pageSizeInBytes; + + this.int8AlignmentInBytes = int8AlignmentInBytes; + this.int16AlignmentInBytes = int16AlignmentInBytes; + this.int32AlignmentInBytes = int32AlignmentInBytes; + this.int64AlignmentInBytes = int64AlignmentInBytes; + this.intAlignmentInBytes = intAlignmentInBytes; + this.longAlignmentInBytes = longAlignmentInBytes; + this.floatAlignmentInBytes = floatAlignmentInBytes; + this.doubleAlignmentInBytes = doubleAlignmentInBytes; + this.ldoubleAlignmentInBytes = ldoubleAlignmentInBytes; + this.pointerAlignmentInBytes = pointerAlignmentInBytes; + } + + /** + * @return true if all values are validated at runtime, otherwise false (i.e. for static compilation w/ preset values) + */ + public final boolean isRuntimeValidated() { + return runtimeValidated; + } + + public final int intSizeInBytes() { return intSizeInBytes; } + public final int longSizeInBytes() { return longSizeInBytes; } + public final int int8SizeInBytes() { return int8SizeInBytes; } + public final int int16SizeInBytes() { return int16SizeInBytes; } + public final int int32SizeInBytes() { return int32SizeInBytes; } + public final int int64SizeInBytes() { return int64SizeInBytes; } + public final int floatSizeInBytes() { return floatSizeInBytes; } + public final int doubleSizeInBytes() { return doubleSizeInBytes; } + public final int ldoubleSizeInBytes() { return ldoubleSizeInBytes; } + public final int pointerSizeInBytes() { return pointerSizeInBytes; } + public final int pageSizeInBytes() { return pageSizeInBytes; } + + public final int intAlignmentInBytes() { return intAlignmentInBytes; } + public final int longAlignmentInBytes() { return longAlignmentInBytes; } + public final int int8AlignmentInBytes() { return int8AlignmentInBytes; } + public final int int16AlignmentInBytes() { return int16AlignmentInBytes; } + public final int int32AlignmentInBytes() { return int32AlignmentInBytes; } + public final int int64AlignmentInBytes() { return int64AlignmentInBytes; } + public final int floatAlignmentInBytes() { return floatAlignmentInBytes; } + public final int doubleAlignmentInBytes() { return doubleAlignmentInBytes; } + public final int ldoubleAlignmentInBytes() { return ldoubleAlignmentInBytes; } + public final int pointerAlignmentInBytes() { return pointerAlignmentInBytes; } + + /** + * @return number of pages required for size in bytes + */ + public int pageCount(final int size) { + return ( size + ( pageSizeInBytes - 1) ) / pageSizeInBytes ; // integer arithmetic + } + + /** + * @return page aligned size in bytes + */ + public int pageAlignedSize(final int size) { + return pageCount(size) * pageSizeInBytes; + } + + /** + * Checks whether two size objects are equal. Two instances + * of <code>MachineDataInfo</code> are considered equal if all components + * match but {@link #runtimeValidated}, {@link #isRuntimeValidated()}. + * @return <code>true</code> if the two MachineDataInfo are equal; + * otherwise <code>false</code>. + */ + @Override + public final boolean equals(final Object obj) { + if (this == obj) { return true; } + if ( !(obj instanceof MachineDataInfo) ) { return false; } + final MachineDataInfo md = (MachineDataInfo) obj; + + return pageSizeInBytes == md.pageSizeInBytes && + compatible(md); + } + + /** + * Checks whether two {@link MachineDataInfo} objects are equal. + * <p> + * Two {@link MachineDataInfo} instances are considered equal if all components + * match but {@link #isRuntimeValidated()} and {@link #pageSizeInBytes()}. + * </p> + * @return <code>true</code> if the two {@link MachineDataInfo} are equal; + * otherwise <code>false</code>. + */ + public final boolean compatible(final MachineDataInfo md) { + return intSizeInBytes == md.intSizeInBytes && + longSizeInBytes == md.longSizeInBytes && + floatSizeInBytes == md.floatSizeInBytes && + doubleSizeInBytes == md.doubleSizeInBytes && + ldoubleSizeInBytes == md.ldoubleSizeInBytes && + pointerSizeInBytes == md.pointerSizeInBytes && + + int8AlignmentInBytes == md.int8AlignmentInBytes && + int16AlignmentInBytes == md.int16AlignmentInBytes && + int32AlignmentInBytes == md.int32AlignmentInBytes && + int64AlignmentInBytes == md.int64AlignmentInBytes && + intAlignmentInBytes == md.intAlignmentInBytes && + longAlignmentInBytes == md.longAlignmentInBytes && + floatAlignmentInBytes == md.floatAlignmentInBytes && + doubleAlignmentInBytes == md.doubleAlignmentInBytes && + ldoubleAlignmentInBytes == md.ldoubleAlignmentInBytes && + pointerAlignmentInBytes == md.pointerAlignmentInBytes ; + } + + public StringBuilder toString(StringBuilder sb) { + if(null==sb) { + sb = new StringBuilder(); + } + sb.append("MachineDataInfo: runtimeValidated ").append(isRuntimeValidated()).append(", 32Bit ").append(4 == pointerAlignmentInBytes).append(", primitive size / alignment:").append(PlatformProps.NEWLINE); + sb.append(" int8 ").append(int8SizeInBytes) .append(" / ").append(int8AlignmentInBytes); + sb.append(", int16 ").append(int16SizeInBytes) .append(" / ").append(int16AlignmentInBytes).append(PlatformProps.NEWLINE); + sb.append(" int ").append(intSizeInBytes) .append(" / ").append(intAlignmentInBytes); + sb.append(", long ").append(longSizeInBytes) .append(" / ").append(longAlignmentInBytes).append(PlatformProps.NEWLINE); + sb.append(" int32 ").append(int32SizeInBytes) .append(" / ").append(int32AlignmentInBytes); + sb.append(", int64 ").append(int64SizeInBytes) .append(" / ").append(int64AlignmentInBytes).append(PlatformProps.NEWLINE); + sb.append(" float ").append(floatSizeInBytes) .append(" / ").append(floatAlignmentInBytes); + sb.append(", double ").append(doubleSizeInBytes) .append(" / ").append(doubleAlignmentInBytes); + sb.append(", ldouble ").append(ldoubleSizeInBytes).append(" / ").append(ldoubleAlignmentInBytes).append(PlatformProps.NEWLINE); + sb.append(" pointer ").append(pointerSizeInBytes).append(" / ").append(pointerAlignmentInBytes); + sb.append(", page ").append(pageSizeInBytes); + return sb; + } + + @Override + public String toString() { + return toString(null).toString(); + } + + public static StaticConfig guessStaticMachineDataInfo(final PlatformTypes.OSType osType, final PlatformTypes.CPUType cpuType) { + if( cpuType.is32Bit ) { + if( PlatformTypes.CPUFamily.ARM32 == cpuType.family || + PlatformTypes.CPUType.MIPS_32 == cpuType ) { + return StaticConfig.ARM_MIPS_32; + } else if( PlatformTypes.OSType.WINDOWS == osType ) { + return StaticConfig.X86_32_WINDOWS; + } else if( PlatformTypes.OSType.MACOS == osType ) { + return StaticConfig.X86_32_MACOS; + } else if ( PlatformTypes.OSType.SUNOS == osType && + PlatformTypes.CPUType.SPARC_32 == cpuType ) { + return StaticConfig.SPARC_32_SUNOS; + } else if ( PlatformTypes.CPUType.PPC == cpuType ) { + return StaticConfig.PPC_32_UNIX; + } else { + return StaticConfig.X86_32_UNIX; + } + } else { + if( PlatformTypes.OSType.WINDOWS == osType ) { + return StaticConfig.X86_64_WINDOWS; + } else if( PlatformTypes.OSType.IOS == osType && PlatformTypes.CPUType.ARM64 == cpuType ) { + return StaticConfig.ARM64_IOS; + } else { + // for all 64bit unix types (x86_64, aarch64, sparcv9, ..) + return StaticConfig.LP64_UNIX; + } + } + } +} diff --git a/java_base/org/jau/sys/PlatformProps.java b/java_base/org/jau/sys/PlatformProps.java new file mode 100644 index 0000000..0f9caa8 --- /dev/null +++ b/java_base/org/jau/sys/PlatformProps.java @@ -0,0 +1,453 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.List; + +import org.jau.lang.NioUtil; +import org.jau.sys.PlatformTypes.ABIType; +import org.jau.sys.PlatformTypes.CPUType; +import org.jau.sys.PlatformTypes.OSType; +import org.jau.sys.elf.ElfHeaderPart1; +import org.jau.sys.elf.ElfHeaderPart2; +import org.jau.sys.elf.SectionArmAttributes; +import org.jau.util.VersionNumber; + +/** + * Runtime platform properties. + */ +public class PlatformProps { + public static final boolean DEBUG = Debug.debug("Platform"); + + /** + * Returns {@code true} if the given {@link CPUType}s and {@link ABIType}s are compatible. + */ + private static final boolean isCompatible(final CPUType cpu1, final ABIType abi1, final CPUType cpu2, final ABIType abi2) { + return cpu1.isCompatible(cpu2) && abi1.isCompatible(abi2); + } + + // + // static initialization order: + // + + public static final VersionNumber JAVA_VERSION_NUMBER; + + /** + * True only if being compatible w/ language level 9, e.g. JRE 9. + * <p> + * Since JRE 9, the version string has dropped the major release number, + * see JEP 223: http://openjdk.java.net/jeps/223 + * </p> + */ + public static final boolean JAVA_9; + + public static final String NEWLINE; + + public static final VersionNumber os_version; + public static final OSType OS; + + public static final boolean LITTLE_ENDIAN; + public static final CPUType CPU; + public static final ABIType ABI; + + /** Lower case system property '{@code os.name}'. */ + public static final String os_name; + + /** Lower case system property '{@code os.arch}' */ + public static final String os_arch; + + /** Static (not runtime) determined {@link MachineDataInfo}. */ + public static final MachineDataInfo MACH_DESC_STAT; + + /** + * Unique platform denominator composed as '{@link #os_name}' + '-' + '{@link #os_arch}'. + */ + public static final String os_and_arch; + + static { + final VersionNumber _version9 = new VersionNumber(9, 0, 0); + JAVA_VERSION_NUMBER = new VersionNumber(System.getProperty("java.version")); + JAVA_9 = JAVA_VERSION_NUMBER.compareTo(_version9) >= 0; + + NEWLINE = System.getProperty("line.separator"); + + final String os_name_prop; + final String os_arch_prop; + { + final String[] props = + AccessController.doPrivileged(new PrivilegedAction<String[]>() { + @Override + public String[] run() { + final String[] props = new String[3]; + int i=0; + props[i++] = System.getProperty("os.name").toLowerCase(); // 0 + props[i++] = System.getProperty("os.arch").toLowerCase(); // 1 + props[i++] = System.getProperty("os.version"); // 2 + return props; + } + }); + int i=0; + os_name_prop = props[i++]; + os_arch_prop = props[i++]; + + os_version = new VersionNumber(props[i++]); + } + + if ( os_name_prop.startsWith("linux") ) { + OS = OSType.LINUX; + } + else if ( os_name_prop.startsWith("freebsd") ) { + OS = OSType.FREEBSD; + } + else if ( os_name_prop.startsWith("android") ) { + OS = OSType.ANDROID; + } + else if ( os_name_prop.startsWith("mac os x") || + os_name_prop.startsWith("darwin") ) { + OS = OSType.MACOS; + } + else if ( os_name_prop.startsWith("sunos") ) { + OS = OSType.SUNOS; + } + else if ( os_name_prop.startsWith("hp-ux") ) { + OS = OSType.HPUX; + } + else if ( os_name_prop.startsWith("windows") ) { + OS = OSType.WINDOWS; + } + else if ( os_name_prop.startsWith("kd") ) { + OS = OSType.OPENKODE; + } + else if ( os_name_prop.startsWith("ios") ) { + OS = OSType.IOS; + } else { + OS = OSType.UNDEFINED; + } + + // Hard values, i.e. w/ probing binaries + final String elfCpuName; + final CPUType elfCpuType; + final ABIType elfABIType; + final int elfLittleEndian; + final boolean elfValid; + { + final String[] _elfCpuName = { null }; + final CPUType[] _elfCpuType = { null }; + final ABIType[] _elfAbiType = { null }; + final int[] _elfLittleEndian = { 0 }; // 1 - little, 2 - big + final boolean[] _elfValid = { false }; + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + RandomAccessFile in = null; + try { + final File file = queryElfFile(OS); + if(DEBUG) { + System.err.println("ELF-1: Using "+file); + } + in = new RandomAccessFile(file, "r"); + final ElfHeaderPart1 eh1 = readElfHeaderPart1(OS, in); + if(DEBUG) { + System.err.println("ELF-1: Got "+eh1); + } + if( null != eh1 ) { + final ElfHeaderPart2 eh2 = readElfHeaderPart2(eh1, in); + if(DEBUG) { + System.err.println("ELF-2: Got "+eh2); + } + if( null != eh2 ) { + _elfCpuName[0] = eh2.cpuName; + _elfCpuType[0] = eh2.cpuType; + _elfAbiType[0] = eh2.abiType; + if( eh1.isLittleEndian() ) { + _elfLittleEndian[0] = 1; + } else if( eh1.isBigEndian() ) { + _elfLittleEndian[0] = 2; + } + _elfValid[0] = true; + } + } + } catch (final Throwable t) { + if(DEBUG) { + t.printStackTrace(); + } + } finally { + if(null != in) { + try { + in.close(); + } catch (final IOException e) { } + } + } + return null; + } }); + elfCpuName = _elfCpuName[0]; + elfCpuType = _elfCpuType[0]; + elfABIType = _elfAbiType[0]; + elfLittleEndian = _elfLittleEndian[0]; + elfValid = _elfValid[0]; + if( DEBUG ) { + System.err.println("Platform.Elf: valid "+elfValid+", elfCpuName "+elfCpuName+", cpuType "+elfCpuType+", abiType "+elfABIType+", elfLittleEndian "+elfLittleEndian); + } + } + + // Determine endianess, favor ELF value + if( elfValid ) { + switch( elfLittleEndian ) { + case 1: + LITTLE_ENDIAN = true; + break; + case 2: + LITTLE_ENDIAN = false; + break; + default: + LITTLE_ENDIAN = queryIsLittleEndianImpl(); + break; + } + } else { + LITTLE_ENDIAN = queryIsLittleEndianImpl(); + } + if( DEBUG ) { + System.err.println("Platform.Endian: test-little "+queryIsLittleEndianImpl()+", elf[valid "+elfValid+", val "+elfLittleEndian+"] -> LITTLE_ENDIAN "+LITTLE_ENDIAN); + } + + // Property values for comparison + // We might take the property values even if ELF values are available, + // since the latter only reflect the CPU/ABI version of the binary files! + final CPUType propCpuType = CPUType.query(os_arch_prop); + final ABIType propABIType = ABIType.query(propCpuType, os_arch_prop); + if( DEBUG ) { + System.err.println("Platform.Property: ARCH "+os_arch_prop+", CpuType "+propCpuType+", ABIType "+propABIType); + } + + final int strategy; + if( elfValid ) { + if( isCompatible(elfCpuType, elfABIType, propCpuType, propABIType) ) { + // Use property ARCH, compatible w/ ELF + CPU = propCpuType; + ABI = propABIType; + strategy = 210; + } else { + // use ELF ARCH + CPU = elfCpuType; + ABI = elfABIType; + strategy = 211; + } + } else { + // Last resort: properties + CPU = propCpuType; + ABI = propABIType; + strategy = 220; + } + { + final String _os_name2 = getOSName(OS); + os_name = null != _os_name2 ? _os_name2 : os_name_prop; + } + { + final String _os_arch2 = getArchName(CPU, ABI, LITTLE_ENDIAN); + os_arch = null != _os_arch2 ? _os_arch2 : os_arch_prop; + } + os_and_arch = os_name+"-"+os_arch; + + MACH_DESC_STAT = MachineDataInfo.guessStaticMachineDataInfo(OS, CPU).md; + + if( DEBUG ) { + System.err.println("Platform.OS: os_name "+os_name+", os_arch "+os_arch+", os_version "+os_version); + System.err.println("Platform.Hard: CPU_ARCH "+CPU+", ABI_TYPE "+ABI+" - strategy "+strategy+"(elfValid "+elfValid+"), little "+LITTLE_ENDIAN); + System.err.println("Platform.MD: "+MACH_DESC_STAT); + } + } + public static void initSingleton() { } + + protected PlatformProps() {} + + /** + * Returns the {@link ABIType} of the current platform using given {@link CPUType cpuType} + * and {@link OSType osType} as a hint. + * <p> + * For Elf parsing one of the following binaries is used: + * <ul> + * <li>Linux: Current executable</li> + * <li>Android: Found gluegen_rt library</li> + * <li>Other: A found java/jvm native library.</li> + * </ul> + * </p> + * <p> + * Elf ARM Tags are read using {@link ElfHeader}, .. and {@link SectionArmAttributes#abiVFPArgsAcceptsVFPVariant(byte)}. + * </p> + */ + private static final File queryElfFile(final OSType osType) { + File file = null; + try { + if( OSType.LINUX == osType ) { + file = new File("/proc/self/exe"); + if( !checkFileReadAccess(file) ) { + file = null; + } + } + if( null == file ) { + file = findSysLib("java"); + } + if( null == file ) { + file = findSysLib("jvm"); + } + } catch(final Throwable t) { + if(DEBUG) { + t.printStackTrace(); + } + } + return file; + } + private static final ElfHeaderPart1 readElfHeaderPart1(final OSType osType, final RandomAccessFile in) { + ElfHeaderPart1 res = null; + try { + res = ElfHeaderPart1.read(osType, in); + } catch(final Throwable t) { + if(DEBUG) { + System.err.println("Caught: "+t.getMessage()); + t.printStackTrace(); + } + } + return res; + } + private static final ElfHeaderPart2 readElfHeaderPart2(final ElfHeaderPart1 eh1, final RandomAccessFile in) { + ElfHeaderPart2 res = null; + try { + res = ElfHeaderPart2.read(eh1, in); + } catch(final Throwable t) { + if(DEBUG) { + System.err.println("Caught: "+t.getMessage()); + t.printStackTrace(); + } + } + return res; + } + private static boolean checkFileReadAccess(final File file) { + try { + return file.isFile() && file.canRead(); + } catch (final Throwable t) { } + return false; + } + private static File findSysLib(final String libName) { + final ClassLoader cl = PlatformProps.class.getClassLoader(); + final List<String> possibleLibPaths = JNILibrary.enumerateLibraryPaths(libName, + true /* searchSystemPath */, true /* searchSystemPathFirst */, cl); + for(int i=0; i<possibleLibPaths.size(); i++) { + final String libPath = possibleLibPaths.get(i); + final File lib = new File(libPath); + if(DEBUG) { + System.err.println("findSysLib #"+i+": test "+lib); + } + if( checkFileReadAccess(lib) ) { + return lib; + } + if(DEBUG) { + System.err.println("findSysLib #"+i+": "+lib+" not readable"); + } + } + return null; + } + + private static final String getArchName(final CPUType cpuType, final ABIType abiType, final boolean littleEndian) { + switch( abiType ) { + case EABI_GNU_ARMEL: + return "arm"; // actually not supported! + case EABI_GNU_ARMHF: + return "armhf"; + case EABI_AARCH64: + return "arm64"; + default: + break; + } + + switch( cpuType ) { + case X86_32: + return "i386"; + case PPC: + return "ppc"; + case MIPS_32: + return littleEndian ? "mipsel" : "mips"; + case SuperH: + return "superh"; + case SPARC_32: + return "sparc"; + + case X86_64: + return "amd64"; + case PPC64: + return littleEndian ? "ppc64le" : "ppc64"; + case MIPS_64: + return "mips64"; + case IA64: + return "ia64"; + case SPARCV9_64: + return "sparcv9"; + case PA_RISC2_0: + return "risc2.0"; + default: + return null; + } + } + + private static final String getOSName(final OSType osType) { + switch( osType ) { + case ANDROID: + return "android"; + case MACOS: + return "macosx"; + case IOS: + return "ios"; + case WINDOWS: + return "windows"; + case OPENKODE: + return "openkode"; + case LINUX: + return "linux"; + case FREEBSD: + return "freebsd"; + case SUNOS: + return "solaris"; + case HPUX: + return "hpux"; + default: + return "undefined"; + } + } + private static final boolean queryIsLittleEndianImpl() { + final ByteBuffer tst_b = NioUtil.newNativeByteBuffer(NioUtil.SIZEOF_INT); // 32bit in native order + final IntBuffer tst_i = tst_b.asIntBuffer(); + final ShortBuffer tst_s = tst_b.asShortBuffer(); + tst_i.put(0, 0x0A0B0C0D); + return 0x0C0D == tst_s.get(0); + } +} diff --git a/java_base/org/jau/sys/PlatformTypes.java b/java_base/org/jau/sys/PlatformTypes.java new file mode 100644 index 0000000..e100d57 --- /dev/null +++ b/java_base/org/jau/sys/PlatformTypes.java @@ -0,0 +1,247 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys; + +/** + * Exposing types describing the underlying platform. + */ +public final class PlatformTypes { + public enum OSType { + LINUX, FREEBSD, ANDROID, MACOS, SUNOS, HPUX, WINDOWS, OPENKODE, IOS, UNDEFINED; + } + public enum CPUFamily { + /** AMD/Intel */ + X86, + /** ARM 32bit */ + ARM32, + /** ARM 64bit */ + ARM64, + /** Power PC */ + PPC, + /** SPARC */ + SPARC, + /** Mips */ + MIPS, + /** PA RISC */ + PA_RISC, + /** Itanium */ + IA64, + /** Hitachi SuperH */ + SuperH; + } + public enum CPUType { + /** ARM 32bit default, usually little endian */ + ARM( CPUFamily.ARM32, true), + /** ARM7EJ, ARM9E, ARM10E, XScale, usually little endian */ + ARMv5( CPUFamily.ARM32, true), + /** ARM11, usually little endian */ + ARMv6( CPUFamily.ARM32, true), + /** ARM Cortex, usually little endian */ + ARMv7( CPUFamily.ARM32, true), + // 4 + + /** X86 32bit, little endian */ + X86_32( CPUFamily.X86, true), + /** PPC 32bit default, usually big endian */ + PPC( CPUFamily.PPC, true), + /** MIPS 32bit, big endian (mips) or little endian (mipsel) */ + MIPS_32( CPUFamily.MIPS, true), + /** Hitachi SuperH 32bit default, ??? endian */ + SuperH( CPUFamily.SuperH, true), + /** SPARC 32bit, big endian */ + SPARC_32( CPUFamily.SPARC, true), + // 9 + + /** ARM64 default (64bit), usually little endian */ + ARM64( CPUFamily.ARM64, false), + /** ARM AArch64 (64bit), usually little endian */ + ARMv8_A( CPUFamily.ARM64, false), + /** X86 64bit, little endian */ + X86_64( CPUFamily.X86, false), + /** PPC 64bit default, usually big endian */ + PPC64( CPUFamily.PPC, false), + /** MIPS 64bit, big endian (mips64) or little endian (mipsel64) ? */ + MIPS_64( CPUFamily.MIPS, false), + /** Itanium 64bit default, little endian */ + IA64( CPUFamily.IA64, false), + /** SPARC 64bit, big endian */ + SPARCV9_64(CPUFamily.SPARC, false), + /** PA_RISC2_0 64bit, ??? endian */ + PA_RISC2_0(CPUFamily.PA_RISC, false); + // 17 + + public final CPUFamily family; + public final boolean is32Bit; + + CPUType(final CPUFamily type, final boolean is32Bit){ + this.family = type; + this.is32Bit = is32Bit; + } + + /** + * Returns {@code true} if the given {@link CPUType} is compatible + * w/ this one, i.e. at least {@link #family} and {@link #is32Bit} is equal. + */ + public final boolean isCompatible(final CPUType other) { + if( null == other ) { + return false; + } else if( other == this ) { + return true; + } else { + return this.family == other.family && + this.is32Bit == other.is32Bit; + } + } + + public static final CPUType query(final String cpuABILower) { + if( null == cpuABILower ) { + throw new IllegalArgumentException("Null cpuABILower arg"); + } + if( cpuABILower.equals("x86") || + cpuABILower.equals("i386") || + cpuABILower.equals("i486") || + cpuABILower.equals("i586") || + cpuABILower.equals("i686") ) { + return X86_32; + } else if( cpuABILower.equals("x86_64") || + cpuABILower.equals("amd64") ) { + return X86_64; + } else if( cpuABILower.equals("ia64") ) { + return IA64; + } else if( cpuABILower.equals("aarch64") ) { + return ARM64; + } else if( cpuABILower.startsWith("arm") ) { + if( cpuABILower.equals("armv8-a") || + cpuABILower.equals("arm-v8-a") || + cpuABILower.equals("arm-8-a") || + cpuABILower.equals("arm64-v8a") ) { + return ARMv8_A; // 64-bit, aarch64 + } else if( cpuABILower.startsWith("arm64") ) { + return ARM64; // 64-bit, aarch64 + } else if( cpuABILower.startsWith("armv7") || + cpuABILower.startsWith("arm-v7") || + cpuABILower.startsWith("arm-7") || + cpuABILower.startsWith("armeabi-v7") ) { + return ARMv7; // 32-bit, aarch32 + } else if( cpuABILower.startsWith("armv5") || + cpuABILower.startsWith("arm-v5") || + cpuABILower.startsWith("arm-5") ) { + return ARMv5; // 32-bit, aarch32 + } else if( cpuABILower.startsWith("armv6") || + cpuABILower.startsWith("arm-v6") || + cpuABILower.startsWith("arm-6") ) { + return ARMv6; // 32-bit, aarch32 + } else { + return ARM; // 32-bit, aarch32 + } + } else if( cpuABILower.startsWith("cortex-a") && cpuABILower.length() > 8 ) { + final String versstr = cpuABILower.substring(8); + try { + final int vers = Integer.valueOf(versstr); + if( vers <= 32 ) { + return ARMv7; // 32-bit, aarch32 + } else { + return ARMv8_A; // 64-bit, aarch64 + } + } catch( final NumberFormatException nfe) { + throw new RuntimeException("'cortex-a' post-fix not an integer: '"+cpuABILower+"', post-fix '"+versstr+"'"); + } + } else if( cpuABILower.startsWith("cortex-r") && cpuABILower.length() > 8 ) { + final String versstr = cpuABILower.substring(8); + try { + final int vers = Integer.valueOf(versstr); + if( vers < 80 ) { + return ARMv7; // 32-bit, aarch32 + } else { // >= 82 + return ARMv8_A; // 64-bit, aarch64 + } + } catch( final NumberFormatException nfe) { + throw new RuntimeException("'cortex-r' post-fix not an integer: '"+cpuABILower+"', post-fix '"+versstr+"'"); + } + } else if( cpuABILower.equals("sparcv9") ) { + return SPARCV9_64; + } else if( cpuABILower.equals("sparc") ) { + return SPARC_32; + } else if( cpuABILower.equals("pa_risc2.0") ) { + return PA_RISC2_0; + } else if( cpuABILower.startsWith("ppc64") ) { + return PPC64; + } else if( cpuABILower.startsWith("ppc") ) { + return PPC; + } else if( cpuABILower.startsWith("mips64") ) { + return MIPS_64; + } else if( cpuABILower.startsWith("mips") ) { + return MIPS_32; + } else if( cpuABILower.startsWith("superh") ) { + return SuperH; + } else { + throw new RuntimeException("Please port CPUType detection to your platform (CPU_ABI string '" + cpuABILower + "')"); + } + } + } + public enum ABIType { + GENERIC_ABI ( 0x00 ), + /** ARM GNU-EABI ARMEL -mfloat-abi=softfp */ + EABI_GNU_ARMEL ( 0x01 ), + /** ARM GNU-EABI ARMHF -mfloat-abi=hard */ + EABI_GNU_ARMHF ( 0x02 ), + /** ARM EABI AARCH64 (64bit) */ + EABI_AARCH64 ( 0x03 ); + + public final int id; + + ABIType(final int id){ + this.id = id; + } + + /** + * Returns {@code true} if the given {@link ABIType} is compatible + * w/ this one, i.e. they are equal. + */ + public final boolean isCompatible(final ABIType other) { + if( null == other ) { + return false; + } else { + return other == this; + } + } + + public static final ABIType query(final CPUType cpuType, final String cpuABILower) { + if( null == cpuType ) { + throw new IllegalArgumentException("Null cpuType"); + } else if( null == cpuABILower ) { + throw new IllegalArgumentException("Null cpuABILower"); + } else if( CPUFamily.ARM64 == cpuType.family ) { + return EABI_AARCH64; + } else if( CPUFamily.ARM32 == cpuType.family ) { + // FIXME: We only support EABI_GNU_ARMHF on ARM 32bit for now! + return EABI_GNU_ARMHF; + } else { + return GENERIC_ABI; + } + } + } +} diff --git a/java/org/jau/sys/PropertyAccess.java b/java_base/org/jau/sys/PropertyAccess.java index 84e666c..b865eb1 100644 --- a/java/org/jau/sys/PropertyAccess.java +++ b/java_base/org/jau/sys/PropertyAccess.java @@ -29,6 +29,8 @@ package org.jau.sys; import java.security.*; import java.util.HashSet; +import org.jau.sec.SecurityUtil; + /** Helper routines for accessing properties. */ public class PropertyAccess { @@ -44,6 +46,7 @@ public class PropertyAccess { trustedPrefixes = new HashSet<String>(); trustedPrefixes.add(javaws_prefix); trustedPrefixes.add(jnlp_prefix); + trustedPrefixes.add("jau."); // 'jogamp.' and maybe other trusted prefixes will be added later via 'addTrustedPrefix()' trusted = new HashSet<String>(); diff --git a/java_base/org/jau/sys/elf/Ehdr_p1.java b/java_base/org/jau/sys/elf/Ehdr_p1.java new file mode 100644 index 0000000..2aeafef --- /dev/null +++ b/java_base/org/jau/sys/elf/Ehdr_p1.java @@ -0,0 +1,138 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import java.nio.ByteBuffer; + +import org.jau.lang.NioUtil; +import org.jau.lang.StructAccessor; +// import org.jau.sys.MachineDataInfo; + +public class Ehdr_p1 { + + StructAccessor accessor; + + // private static final int mdIdx = 0; + // private final MachineDataInfo md; + + private static final int Ehdr_p1_size = 24 /* ARM_MIPS_32 */; // 24 /* X86_32_UNIX */, 24 /* X86_32_MACOS */, 24 /* PPC_32_UNIX */, 24 /* SPARC_32_SUNOS */, 24 /* X86_32_WINDOWS */, 24 /* LP64_UNIX */, 24 /* X86_64_WINDOWS */ + private static final int e_ident_offset = 0 /* ARM_MIPS_32 */; // 0 /* X86_32_UNIX */, 0 /* X86_32_MACOS */, 0 /* PPC_32_UNIX */, 0 /* SPARC_32_SUNOS */, 0 /* X86_32_WINDOWS */, 0 /* LP64_UNIX */, 0 /* X86_64_WINDOWS */ +// private static final int e_ident_size = 16 /* ARM_MIPS_32 */; // 16 /* X86_32_UNIX */, 16 /* X86_32_MACOS */, 16 /* PPC_32_UNIX */, 16 /* SPARC_32_SUNOS */, 16 /* X86_32_WINDOWS */, 16 /* LP64_UNIX */, 16 /* X86_64_WINDOWS */ + private static final int e_type_offset = 16 /* ARM_MIPS_32 */; // 16 /* X86_32_UNIX */, 16 /* X86_32_MACOS */, 16 /* PPC_32_UNIX */, 16 /* SPARC_32_SUNOS */, 16 /* X86_32_WINDOWS */, 16 /* LP64_UNIX */, 16 /* X86_64_WINDOWS */ +//private static final int e_type_size = 2 /* ARM_MIPS_32 */; // 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ + private static final int e_machine_offset = 18 /* ARM_MIPS_32 */; // 18 /* X86_32_UNIX */, 18 /* X86_32_MACOS */, 18 /* PPC_32_UNIX */, 18 /* SPARC_32_SUNOS */, 18 /* X86_32_WINDOWS */, 18 /* LP64_UNIX */, 18 /* X86_64_WINDOWS */ +//private static final int e_machine_size = 2 /* ARM_MIPS_32 */; // 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ + private static final int e_version_offset = 20 /* ARM_MIPS_32 */; // 20 /* X86_32_UNIX */, 20 /* X86_32_MACOS */, 20 /* PPC_32_UNIX */, 20 /* SPARC_32_SUNOS */, 20 /* X86_32_WINDOWS */, 20 /* LP64_UNIX */, 20 /* X86_64_WINDOWS */ +//private static final int e_version_size = 4 /* ARM_MIPS_32 */; // 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ + + public static int size() { + return Ehdr_p1_size; + } + + public static Ehdr_p1 create() { + return create( NioUtil.newNativeByteBuffer( size() ) ); + } + + public static Ehdr_p1 create(final java.nio.ByteBuffer buf) { + return new Ehdr_p1(buf); + } + + Ehdr_p1(final java.nio.ByteBuffer buf) { + // md = MachineDataInfo.StaticConfig.values()[mdIdx].md; + accessor = new StructAccessor(buf); + } + + public java.nio.ByteBuffer getBuffer() { + return accessor.getBuffer(); + } + + /** Getter for native field: CType['char *', size [fixed false, lnx64 16], [array*1]], with array length of <code>16</code> */ + public static final int getE_identArrayLength() { + return 16; + } + + /** Setter for native field: CType['char *', size [fixed false, lnx64 16], [array*1]], with array length of <code>16</code> */ + public Ehdr_p1 setE_ident(final int offset, final byte[] val) { + final int arrayLength = 16; + if( offset + val.length > arrayLength ) { throw new IndexOutOfBoundsException("offset "+offset+" + val.length "+val.length+" > array-length "+arrayLength); }; + final int elemSize = NioUtil.SIZEOF_BYTE; + final ByteBuffer destB = getBuffer(); + final int bTotal = arrayLength * elemSize; + // if( bTotal > e_ident_size ) { throw new IndexOutOfBoundsException("bTotal "+bTotal+" > size "+e_ident_size+", elemSize "+elemSize+" * "+arrayLength); }; + int bOffset = e_ident_offset; + final int bLimes = bOffset + bTotal; + if( bLimes > destB.limit() ) { throw new IndexOutOfBoundsException("bLimes "+bLimes+" > buffer.limit "+destB.limit()+", elemOff "+bOffset+", elemSize "+elemSize+" * "+arrayLength); }; + bOffset += elemSize * offset; + accessor.setBytesAt(bOffset, val); + return this; + } + + /** Getter for native field: CType['char *', size [fixed false, lnx64 16], [array*1]], with array length of <code>16</code> */ + public ByteBuffer getE_ident() { + return accessor.slice(e_ident_offset, NioUtil.SIZEOF_BYTE * 16); + } + + /** Getter for native field: CType['char *', size [fixed false, lnx64 16], [array*1]], with array length of <code>16</code> */ + public byte[] getE_ident(final int offset, final byte result[]) { + final int arrayLength = 16; + if( offset + result.length > arrayLength ) { throw new IndexOutOfBoundsException("offset "+offset+" + result.length "+result.length+" > array-length "+arrayLength); }; + return accessor.getBytesAt(e_ident_offset + (NioUtil.SIZEOF_BYTE * offset), result); + } + + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p1 setE_type(final short val) { + accessor.setShortAt(e_type_offset, val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_type() { + return accessor.getShortAt(e_type_offset); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p1 setE_machine(final short val) { + accessor.setShortAt(e_machine_offset, val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_machine() { + return accessor.getShortAt(e_machine_offset); + } + + /** Setter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public Ehdr_p1 setE_version(final int val) { + accessor.setIntAt(e_version_offset, val); + return this; + } + + /** Getter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public int getE_version() { + return accessor.getIntAt(e_version_offset); + } +} diff --git a/java_base/org/jau/sys/elf/Ehdr_p2.java b/java_base/org/jau/sys/elf/Ehdr_p2.java new file mode 100644 index 0000000..69a12c5 --- /dev/null +++ b/java_base/org/jau/sys/elf/Ehdr_p2.java @@ -0,0 +1,194 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import org.jau.lang.NioUtil; +import org.jau.lang.StructAccessor; +import org.jau.sys.MachineDataInfo; + +public class Ehdr_p2 { + + StructAccessor accessor; + + private final int mdIdx; + private final MachineDataInfo md; + + private static final int[] Ehdr_p2_size = new int[] { 28 /* ARM_MIPS_32 */, 28 /* X86_32_UNIX */, 28 /* X86_32_MACOS */, 28 /* PPC_32_UNIX */, 28 /* SPARC_32_SUNOS */, 28 /* X86_32_WINDOWS */, 40 /* LP64_UNIX */, 40 /* X86_64_WINDOWS */ }; + private static final int[] e_entry_offset = new int[] { 0 /* ARM_MIPS_32 */, 0 /* X86_32_UNIX */, 0 /* X86_32_MACOS */, 0 /* PPC_32_UNIX */, 0 /* SPARC_32_SUNOS */, 0 /* X86_32_WINDOWS */, 0 /* LP64_UNIX */, 0 /* X86_64_WINDOWS */ }; +//private static final int[] e_entry_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] e_phoff_offset = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; +//private static final int[] e_phoff_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] e_shoff_offset = new int[] { 8 /* ARM_MIPS_32 */, 8 /* X86_32_UNIX */, 8 /* X86_32_MACOS */, 8 /* PPC_32_UNIX */, 8 /* SPARC_32_SUNOS */, 8 /* X86_32_WINDOWS */, 16 /* LP64_UNIX */, 16 /* X86_64_WINDOWS */ }; +//private static final int[] e_shoff_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] e_flags_offset = new int[] { 12 /* ARM_MIPS_32 */, 12 /* X86_32_UNIX */, 12 /* X86_32_MACOS */, 12 /* PPC_32_UNIX */, 12 /* SPARC_32_SUNOS */, 12 /* X86_32_WINDOWS */, 24 /* LP64_UNIX */, 24 /* X86_64_WINDOWS */ }; +//private static final int[] e_flags_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ }; + private static final int[] e_ehsize_offset = new int[] { 16 /* ARM_MIPS_32 */, 16 /* X86_32_UNIX */, 16 /* X86_32_MACOS */, 16 /* PPC_32_UNIX */, 16 /* SPARC_32_SUNOS */, 16 /* X86_32_WINDOWS */, 28 /* LP64_UNIX */, 28 /* X86_64_WINDOWS */ }; +//private static final int[] e_ehsize_size = new int[] { 2 /* ARM_MIPS_32 */, 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ }; + private static final int[] e_phentsize_offset = new int[] { 18 /* ARM_MIPS_32 */, 18 /* X86_32_UNIX */, 18 /* X86_32_MACOS */, 18 /* PPC_32_UNIX */, 18 /* SPARC_32_SUNOS */, 18 /* X86_32_WINDOWS */, 30 /* LP64_UNIX */, 30 /* X86_64_WINDOWS */ }; +//private static final int[] e_phentsize_size = new int[] { 2 /* ARM_MIPS_32 */, 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ }; + private static final int[] e_phnum_offset = new int[] { 20 /* ARM_MIPS_32 */, 20 /* X86_32_UNIX */, 20 /* X86_32_MACOS */, 20 /* PPC_32_UNIX */, 20 /* SPARC_32_SUNOS */, 20 /* X86_32_WINDOWS */, 32 /* LP64_UNIX */, 32 /* X86_64_WINDOWS */ }; +//private static final int[] e_phnum_size = new int[] { 2 /* ARM_MIPS_32 */, 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ }; + private static final int[] e_shentsize_offset = new int[] { 22 /* ARM_MIPS_32 */, 22 /* X86_32_UNIX */, 22 /* X86_32_MACOS */, 22 /* PPC_32_UNIX */, 22 /* SPARC_32_SUNOS */, 22 /* X86_32_WINDOWS */, 34 /* LP64_UNIX */, 34 /* X86_64_WINDOWS */ }; +//private static final int[] e_shentsize_size = new int[] { 2 /* ARM_MIPS_32 */, 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ }; + private static final int[] e_shnum_offset = new int[] { 24 /* ARM_MIPS_32 */, 24 /* X86_32_UNIX */, 24 /* X86_32_MACOS */, 24 /* PPC_32_UNIX */, 24 /* SPARC_32_SUNOS */, 24 /* X86_32_WINDOWS */, 36 /* LP64_UNIX */, 36 /* X86_64_WINDOWS */ }; +//private static final int[] e_shnum_size = new int[] { 2 /* ARM_MIPS_32 */, 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ }; + private static final int[] e_shstrndx_offset = new int[] { 26 /* ARM_MIPS_32 */, 26 /* X86_32_UNIX */, 26 /* X86_32_MACOS */, 26 /* PPC_32_UNIX */, 26 /* SPARC_32_SUNOS */, 26 /* X86_32_WINDOWS */, 38 /* LP64_UNIX */, 38 /* X86_64_WINDOWS */ }; +//private static final int[] e_shstrndx_size = new int[] { 2 /* ARM_MIPS_32 */, 2 /* X86_32_UNIX */, 2 /* X86_32_MACOS */, 2 /* PPC_32_UNIX */, 2 /* SPARC_32_SUNOS */, 2 /* X86_32_WINDOWS */, 2 /* LP64_UNIX */, 2 /* X86_64_WINDOWS */ }; + + public java.nio.ByteBuffer getBuffer() { + return accessor.getBuffer(); + } + + /** Setter for native field: CType['ElfN_Addr' (typedef), size [fixed false, lnx64 8], [int]] */ + public Ehdr_p2 setE_entry(final long val) { + accessor.setLongAt(e_entry_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_Addr' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getE_entry() { + return accessor.getLongAt(e_entry_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['ElfN_Off' (typedef), size [fixed false, lnx64 8], [int]] */ + public Ehdr_p2 setE_phoff(final long val) { + accessor.setLongAt(e_phoff_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_Off' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getE_phoff() { + return accessor.getLongAt(e_phoff_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['ElfN_Off' (typedef), size [fixed false, lnx64 8], [int]] */ + public Ehdr_p2 setE_shoff(final long val) { + accessor.setLongAt(e_shoff_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_Off' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getE_shoff() { + return accessor.getLongAt(e_shoff_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public Ehdr_p2 setE_flags(final int val) { + accessor.setIntAt(e_flags_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public int getE_flags() { + return accessor.getIntAt(e_flags_offset[mdIdx]); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p2 setE_ehsize(final short val) { + accessor.setShortAt(e_ehsize_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_ehsize() { + return accessor.getShortAt(e_ehsize_offset[mdIdx]); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p2 setE_phentsize(final short val) { + accessor.setShortAt(e_phentsize_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_phentsize() { + return accessor.getShortAt(e_phentsize_offset[mdIdx]); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p2 setE_phnum(final short val) { + accessor.setShortAt(e_phnum_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_phnum() { + return accessor.getShortAt(e_phnum_offset[mdIdx]); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p2 setE_shentsize(final short val) { + accessor.setShortAt(e_shentsize_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_shentsize() { + return accessor.getShortAt(e_shentsize_offset[mdIdx]); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p2 setE_shnum(final short val) { + accessor.setShortAt(e_shnum_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_shnum() { + return accessor.getShortAt(e_shnum_offset[mdIdx]); + } + + /** Setter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public Ehdr_p2 setE_shstrndx(final short val) { + accessor.setShortAt(e_shstrndx_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint16_t', size [fixed true, lnx64 2], [int]] */ + public short getE_shstrndx() { + return accessor.getShortAt(e_shstrndx_offset[mdIdx]); + } + + // --- Begin CustomJavaCode .cfg declarations + public static int size(final int mdIdx) { + return Ehdr_p2_size[mdIdx]; + } + + public static Ehdr_p2 create(final int mdIdx) { + return create(mdIdx, NioUtil.newNativeByteBuffer( size(mdIdx) ) ); + } + + public static Ehdr_p2 create(final int mdIdx, final java.nio.ByteBuffer buf) { + return new Ehdr_p2(mdIdx, buf); + } + + Ehdr_p2(final int mdIdx, final java.nio.ByteBuffer buf) { + this.mdIdx = mdIdx; + this.md = MachineDataInfo.StaticConfig.values()[mdIdx].md; + this.accessor = new StructAccessor(buf); + } + // ---- End CustomJavaCode .cfg declarations +} diff --git a/java_base/org/jau/sys/elf/ElfHeaderPart1.java b/java_base/org/jau/sys/elf/ElfHeaderPart1.java new file mode 100644 index 0000000..558c83e --- /dev/null +++ b/java_base/org/jau/sys/elf/ElfHeaderPart1.java @@ -0,0 +1,518 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import static org.jau.sys.elf.IOUtils.readBytes; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +import org.jau.sys.Debug; +import org.jau.sys.MachineDataInfo; +import org.jau.sys.PlatformTypes.ABIType; +import org.jau.sys.PlatformTypes.CPUType; +import org.jau.sys.PlatformTypes.OSType; + +/** + * ELF ABI Header Part-1 + * <p> + * Part-1 can be read w/o knowledge of CPUType! + * </p> + * <p> + * References: + * <ul> + * <li>http://www.sco.com/developers/gabi/latest/contents.html</li> + * <li>https://en.wikipedia.org/wiki/Executable_and_Linkable_Format</li> + * <li>http://linux.die.net/man/5/elf</li> + * <li>http://infocenter.arm.com/ + * <ul> + * <li>ARM IHI 0044E, current through ABI release 2.09</li> + * <li>ARM IHI 0056B: Elf for ARM 64-bit Architecture</li> + * </ul></li> + * </ul> + * </p> + */ +public class ElfHeaderPart1 { + static final boolean DEBUG = Debug.debug("Platform"); + + /** Size of e_ident array - {@value} */ + public static int EI_NIDENT = 16; + + /** ident byte #0 - {@value} */ + public static final byte ELFMAG0 = 0x7f; + /** ident byte #1 - {@value} */ + public static final byte ELFMAG1 = 'E'; + /** ident byte #2 - {@value} */ + public static final byte ELFMAG2 = 'L'; + /** ident byte #3 - {@value} */ + public static final byte ELFMAG3 = 'F'; + + /** ident byte #4 */ + public static final int EI_CLASS = 4; + public static final byte ELFCLASSNONE = 0; + public static final byte ELFCLASS32 = 1; + public static final byte ELFCLASS64 = 2; + + /** ident byte #5 */ + public static final int EI_DATA = 5; + public static final byte ELFDATANONE = 0; + public static final byte ELFDATA2LSB = 1; + public static final byte ELFDATA2MSB = 2; + + /** ident byte #6 */ + public static final int EI_VERSION = 6; + public static final byte EV_NONE = 0; + public static final byte EV_CURRENT = 1; + + /** ident byte #7 */ + public static final int EI_OSABI = 7; + /** Unix System V ABI - {@value} */ + public static final byte ELFOSABI_SYSV = 0; + public static final byte ELFOSABI_NONE = ELFOSABI_SYSV; + /** HP-UX ABI - {@value} */ + public static final byte ELFOSABI_HPUX = 1; + /** NetBSD ABI - {@value} **/ + public static final byte ELFOSABI_NETBSD = 2; + /** Linux ABI - {@value} **/ + public static final byte ELFOSABI_LINUX = 3; + /** Solaris ABI - {@value} **/ + public static final byte ELFOSABI_SOLARIS = 6; + /** IRIX ABI - {@value} **/ + public static final byte ELFOSABI_IRIX = 7; + /** FreeBSD ABI - {@value} **/ + public static final byte ELFOSABI_FREEBSD = 8; + /** ARM architecture ABI - {@value} **/ + public static final byte ELFOSABI_ARM = 8; // FIXME + /** Stand-alone (embedded) ABI - {@value} **/ + public static final byte ELFOSABI_STANDALONE = 9; // FIXME + /** TRU64 UNIX ABI - {@value} **/ + public static final byte ELFOSABI_TRU64 = 10; + /** Novell Modesto ABI - {@value} **/ + public static final byte ELFOSABI_MODESTO = 11; + /** Open BSD ABI - {@value} **/ + public static final byte ELFOSABI_OPENBSD = 12; + /** Open VMS ABI - {@value} **/ + public static final byte ELFOSABI_OPENVMS = 13; + /** Hewlett-Packard Non-Stop Kernel ABI - {@value} **/ + public static final byte ELFOSABI_NSK = 14; + /** Amiga Research OS ABI - {@value} **/ + public static final byte ELFOSABI_AROS = 15; + /** The FenixOS highly scalable multi-core OS 64-255 Architecture-specific value range - {@value} */ + public static final byte ELFOSABI_FENIXOS = 16; + + /** ident byte #8 + * <p> + * This byte identifies the version of the ABI to which the object is targeted. + * This field is used to distinguish among incompatible versions of an ABI. + * The interpretation of this version number is dependent on the ABI identified by the EI_OSABI field. + * Applications conforming to this specification use the value 0. + * </p> + */ + public static final int EI_ABIVERSION = 8; + + /** + * ident byte #9 .. ? + * <p> + * Start of padding. + * These bytes are reserved and set to zero. + * Programs which read them should ignore them. + * The value for EI_PAD will change in the future if currently unused bytes are given meanings. + * </p> + */ + public static final int EI_PAD = 9; + + /** An unknown type - {@value} */ + public static final short ET_NONE = 0; + /** A relocatable file - {@value} */ + public static final short ET_REL = 1; + /** An executable file - {@value} */ + public static final short ET_EXEC = 2; + /** A shared object - {@value} */ + public static final short ET_DYN = 3; + /** A core file - {@value} */ + public static final short ET_CORE = 4; + + public static final short EM_NONE = 0; + public static final short EM_M32 = 1; + public static final short EM_SPARC = 2; + public static final short EM_386 = 3; + public static final short EM_68K = 4; + public static final short EM_88K = 5; + public static final short EM_486 = 6; + public static final short EM_860 = 7; + public static final short EM_MIPS = 8; + public static final short EM_S370 = 9; + public static final short EM_MIPS_RS3_LE = 10; + public static final short EM_PARISC = 15; + public static final short EM_res016 = 16; + public static final short EM_VPP550 = 17; + public static final short EM_SPARC32PLUS = 18; + public static final short EM_960 = 19; + public static final short EM_PPC = 20; + public static final short EM_PPC64 = 21; + public static final short EM_S390 = 22; + public static final short EM_SPU = 23; + public static final short EM_V800 = 36; + public static final short EM_FR20 = 37; + public static final short EM_RH32 = 38; + public static final short EM_MCORE = 39; + public static final short EM_RCE = 39; + public static final short EM_ARM = 40; + public static final short EM_OLD_ALPHA = 41; + public static final short EM_SH = 42; + public static final short EM_SPARCV9 = 43; + public static final short EM_TRICORE = 44; + public static final short EM_ARC = 45; + public static final short EM_H8_300 = 46; + public static final short EM_H8_300H = 47; + public static final short EM_H8S = 48; + public static final short EM_H8_500 = 49; + public static final short EM_IA_64 = 50; + public static final short EM_MIPS_X = 51; + public static final short EM_COLDFIRE = 52; + public static final short EM_68HC12 = 53; + public static final short EM_MMA = 54; + public static final short EM_PCP = 55; + public static final short EM_NCPU = 56; + public static final short EM_NDR1 = 57; + public static final short EM_STARCORE = 58; + public static final short EM_ME16 = 59; + public static final short EM_ST100 = 60; + public static final short EM_TINYJ = 61; + public static final short EM_X86_64 = 62; + public static final short EM_PDSP = 63; + public static final short EM_PDP10 = 64; + public static final short EM_PDP11 = 65; + public static final short EM_FX66 = 66; + public static final short EM_ST9PLUS = 67; + public static final short EM_ST7 = 68; + public static final short EM_68HC16 = 69; + public static final short EM_68HC11 = 70; + public static final short EM_68HC08 = 71; + public static final short EM_68HC05 = 72; + public static final short EM_SVX = 73; + public static final short EM_ST19 = 74; + public static final short EM_VAX = 75; + public static final short EM_CRIS = 76; + public static final short EM_JAVELIN = 77; + public static final short EM_FIREPATH = 78; + public static final short EM_ZSP = 79; + public static final short EM_MMIX = 80; + public static final short EM_HUANY = 81; + public static final short EM_PRISM = 82; + public static final short EM_AVR = 83; + public static final short EM_FR30 = 84; + public static final short EM_D10V = 85; + public static final short EM_D30V = 86; + public static final short EM_V850 = 87; + public static final short EM_M32R = 88; + public static final short EM_MN10300 = 89; + public static final short EM_MN10200 = 90; + public static final short EM_PJ = 91; + public static final short EM_OPENRISC = 92; + public static final short EM_ARC_A5 = 93; + public static final short EM_XTENSA = 94; + public static final short EM_VIDEOCORE = 95; + public static final short EM_TMM_GPP = 96; + public static final short EM_NS32K = 97; + public static final short EM_TPC = 98; + public static final short EM_SNP1K = 99; + public static final short EM_ST200 = 100; + public static final short EM_IP2K = 101; + public static final short EM_MAX = 102; + public static final short EM_CR = 103; + public static final short EM_F2MC16 = 104; + public static final short EM_MSP430 = 105; + public static final short EM_BLACKFIN = 106; + public static final short EM_SE_C33 = 107; + public static final short EM_SEP = 108; + public static final short EM_ARCA = 109; + public static final short EM_UNICORE = 110; + public static final short EM_EXCESS = 111; + public static final short EM_DXP = 112; + public static final short EM_ALTERA_NIOS2 = 113; + public static final short EM_CRX = 114; + public static final short EM_XGATE = 115; + public static final short EM_C166 = 116; + public static final short EM_M16C = 117; + public static final short EM_DSPIC30F = 118; + public static final short EM_CE = 119; + public static final short EM_M32C = 120; + public static final short EM_TSK3000 = 131; + public static final short EM_RS08 = 132; + public static final short EM_res133 = 133; + public static final short EM_ECOG2 = 134; + public static final short EM_SCORE = 135; + public static final short EM_SCORE7 = 135; + public static final short EM_DSP24 = 136; + public static final short EM_VIDEOCORE3 = 137; + public static final short EM_LATTICEMICO32 = 138; + public static final short EM_SE_C17 = 139; + public static final short EM_TI_C6000 = 140; + public static final short EM_TI_C2000 = 141; + public static final short EM_TI_C5500 = 142; + public static final short EM_MMDSP_PLUS = 160; + public static final short EM_CYPRESS_M8C = 161; + public static final short EM_R32C = 162; + public static final short EM_TRIMEDIA = 163; + public static final short EM_QDSP6 = 164; + public static final short EM_8051 = 165; + public static final short EM_STXP7X = 166; + public static final short EM_NDS32 = 167; + public static final short EM_ECOG1 = 168; + public static final short EM_ECOG1X = 168; + public static final short EM_MAXQ30 = 169; + public static final short EM_XIMO16 = 170; + public static final short EM_MANIK = 171; + public static final short EM_CRAYNV2 = 172; + public static final short EM_RX = 173; + public static final short EM_METAG = 174; + public static final short EM_MCST_ELBRUS = 175; + public static final short EM_ECOG16 = 176; + public static final short EM_CR16 = 177; + public static final short EM_ETPU = 178; + public static final short EM_SLE9X = 179; + public static final short EM_L1OM = 180; + public static final short EM_INTEL181 = 181; + public static final short EM_INTEL182 = 182; + public static final short EM_AARCH64 = 183; + public static final short EM_ARM184 = 184; + public static final short EM_AVR32 = 185; + public static final short EM_STM8 = 186; + public static final short EM_TILE64 = 187; + public static final short EM_TILEPRO = 188; + public static final short EM_MICROBLAZE = 189; + public static final short EM_CUDA = 190; + + public static final boolean isIdentityValid(final byte[] ident) { + return ELFMAG0 == ident[0] && + ELFMAG1 == ident[1] && + ELFMAG2 == ident[2] && + ELFMAG3 == ident[3] ; + } + + /** Public access to the raw elf header part-1 (CPU/ABI independent read) */ + public final Ehdr_p1 raw; + private final byte[] E_ident; + + /** Lower case CPUType name */ + public final String cpuName; + public final CPUType cpuType; + public final ABIType abiType; + + public final MachineDataInfo.StaticConfig machDesc; + + /** + * Note: The input stream shall stay untouch to be able to read sections! + * @param osType TODO + * @param in input stream of a binary file at position zero + * + * @return + * @throws IOException if reading from the given input stream fails or less then ELF Header size bytes + * @throws IllegalArgumentException if the given input stream does not represent an ELF Header + */ + public static ElfHeaderPart1 read(final OSType osType, final RandomAccessFile in) throws IOException, IllegalArgumentException { + return new ElfHeaderPart1(osType, in); + } + + /** + * @param osType TODO + * @param buf ELF Header bytes + * @throws IllegalArgumentException if the given buffer does not represent an ELF Header + * @throws IOException + */ + ElfHeaderPart1(final OSType osType, final RandomAccessFile in) throws IllegalArgumentException, IOException { + { + final byte[] buf = new byte[Ehdr_p1.size()]; + readBytes (in, buf, 0, buf.length); + final ByteBuffer eh1Bytes = ByteBuffer.wrap(buf, 0, buf.length); + raw = Ehdr_p1.create(eh1Bytes); + } + E_ident = raw.getE_ident(0, new byte[Ehdr_p1.getE_identArrayLength()]); + if( !isIdentityValid(E_ident) ) { + throw new IllegalArgumentException("Buffer is not an ELF Header"); + } + + final short machine = getMachine(); + switch ( machine ) { + case EM_ARM: + // ARM 32bit will be refine in ElfHeaderPart2 + cpuName = "arm"; // lowest 32bit denominator, ok for now + cpuType = CPUType.ARM; + abiType = ABIType.GENERIC_ABI; + break; + case EM_AARCH64: + cpuName = "aarch64"; + cpuType = CPUType.ARM64; + abiType = ABIType.EABI_AARCH64; + break; + case EM_X86_64: + cpuName = "x86_64"; + cpuType = CPUType.X86_64; + abiType = ABIType.GENERIC_ABI; + break; + case EM_386: + cpuName = "i386"; + cpuType = CPUType.X86_32; + abiType = ABIType.GENERIC_ABI; + break; + case EM_486: + cpuName = "i486"; + cpuType = CPUType.X86_32; + abiType = ABIType.GENERIC_ABI; + break; + case EM_IA_64: + cpuName = "ia64"; + cpuType = CPUType.IA64; + abiType = ABIType.GENERIC_ABI; + break; + case EM_MIPS: + // Can be little-endian or big-endian and 32 or 64 bits + if( 64 == getArchClassBits() ) { + cpuName = isLittleEndian() ? "mips64le" : "mips64"; + cpuType = CPUType.MIPS_64; + } else { + cpuName = isLittleEndian() ? "mipsle" : "mips"; + cpuType = CPUType.MIPS_32; + } + abiType = ABIType.GENERIC_ABI; + break; + case EM_MIPS_RS3_LE: + cpuName = "mipsle-rs3"; // FIXME: Only little-endian? + cpuType = CPUType.MIPS_32; + abiType = ABIType.GENERIC_ABI; + break; + case EM_MIPS_X: + cpuName = isLittleEndian() ? "mipsle-x" : "mips-x"; // Can be little-endian + cpuType = CPUType.MIPS_32; + abiType = ABIType.GENERIC_ABI; + break; + case EM_PPC: + cpuName = "ppc"; + cpuType = CPUType.PPC; + abiType = ABIType.GENERIC_ABI; + break; + case EM_PPC64: + cpuName = "ppc64"; + cpuType = CPUType.PPC64; + abiType = ABIType.GENERIC_ABI; + break; + case EM_SH: + cpuName = "superh"; + cpuType = CPUType.SuperH; + abiType = ABIType.GENERIC_ABI; + break; + default: + throw new IllegalArgumentException("CPUType and ABIType could not be determined"); + } + machDesc = MachineDataInfo.guessStaticMachineDataInfo(osType, cpuType); + if(DEBUG) { + System.err.println("ELF-1: cpuName "+cpuName+" -> "+cpuType+", "+abiType +", machDesc "+machDesc.toShortString() ); + } + } + + /** + * Returns the architecture class in bits, + * 32 for {@link #ELFCLASS32}, 64 for {@link #ELFCLASS64} + * and 0 for {@link #ELFCLASSNONE}. + */ + public final int getArchClassBits() { + switch( E_ident[EI_CLASS] ) { + case ELFCLASS32: return 32; + case ELFCLASS64: return 64; + default: return 0; + } + } + + /** + * Returns the processor's data encoding, i.e. + * {@link #ELFDATA2LSB}, {@link #ELFDATA2MSB} or {@link #ELFDATANONE}; + */ + public final byte getDataEncodingMode() { + return E_ident[EI_DATA]; + } + + /** + * Returns whether the processor's {@link #getDataEncodingMode() data encoding} is {@link #ELFDATA2LSB}. + */ + public final boolean isLittleEndian() { return ELFDATA2LSB == E_ident[EI_DATA]; } + /** + * Returns whether the processor's {@link #getDataEncodingMode() data encoding} is {@link #ELFDATA2MSB}. + */ + public final boolean isBigEndian() { return ELFDATA2MSB == E_ident[EI_DATA]; } + /** + * Returns whether the processor's {@link #getDataEncodingMode() data encoding} is {@link #ELFDATANONE}. + */ + public final boolean isNoneEndian() { return ELFDATANONE == E_ident[EI_DATA]; } + + /** Returns the ELF file version, should be {@link #EV_CURRENT}. */ + public final byte getVersion() { + return E_ident[EI_VERSION]; + } + + /** Returns the operating system and ABI for this file, 3 == Linux. Note: Often not used. */ + public final byte getOSABI() { + return E_ident[EI_OSABI]; + } + + /** Returns the version of the {@link #getOSABI() OSABI} for this file. */ + public final byte getOSABIVersion() { + return E_ident[EI_ABIVERSION]; + } + + /** Returns the object file type, e.g. {@link #ET_EXEC}, .. */ + public final short getType() { + return raw.getE_type(); + } + + /** Returns the required architecture for the file, e.g. {@link #EM_386}, .. */ + public final short getMachine() { + return raw.getE_machine(); + } + + @Override + public final String toString() { + final int enc = getDataEncodingMode(); + final String encS; + switch(enc) { + case ELFDATA2LSB: encS = "LSB"; break; + case ELFDATA2MSB: encS = "MSB"; break; + default: encS = "NON"; break; /* ELFDATANONE */ + } + final int type = getType(); + final String typeS; + switch(type) { + case ET_REL: typeS = "reloc"; break; + case ET_EXEC: typeS = "exec"; break; + case ET_DYN: typeS = "shared"; break; + case ET_CORE: typeS = "core"; break; + default: typeS = "none"; break; /* ET_NONE */ + } + return "ELF-1[vers "+getVersion()+", machine["+getMachine()+", "+cpuType+", "+abiType+", machDesc "+machDesc.toShortString()+"], bits "+getArchClassBits()+", enc "+encS+ + ", abi[os "+getOSABI()+", vers "+getOSABIVersion()+"], type "+typeS+"]"; + } +} diff --git a/java_base/org/jau/sys/elf/ElfHeaderPart2.java b/java_base/org/jau/sys/elf/ElfHeaderPart2.java new file mode 100644 index 0000000..5e09a1c --- /dev/null +++ b/java_base/org/jau/sys/elf/ElfHeaderPart2.java @@ -0,0 +1,377 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import static org.jau.sys.elf.IOUtils.readBytes; +import static org.jau.sys.elf.IOUtils.seek; +import static org.jau.sys.elf.IOUtils.shortToInt; +import static org.jau.sys.elf.IOUtils.toHexString; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +import org.jau.sys.PlatformTypes.ABIType; +import org.jau.sys.PlatformTypes.CPUFamily; +import org.jau.sys.PlatformTypes.CPUType; + +/** + * ELF ABI Header Part-2 + * <p> + * Part-2 can only be read w/ knowledge of CPUType! + * </p> + * <p> + * References: + * <ul> + * <li>http://www.sco.com/developers/gabi/latest/contents.html</li> + * <li>https://en.wikipedia.org/wiki/Executable_and_Linkable_Format</li> + * <li>http://linux.die.net/man/5/elf</li> + * <li>http://infocenter.arm.com/ + * <ul> + * <li>ARM IHI 0044E, current through ABI release 2.09</li> + * <li>ARM IHI 0056B: Elf for ARM 64-bit Architecture</li> + * </ul></li> + * </ul> + * </p> + */ +public class ElfHeaderPart2 { + /** + * This masks an 8-bit version number, the version of the ABI to which this + * ELF file conforms. This ABI is version 5. A value of 0 denotes unknown conformance. + * {@value} + */ + public static final int EF_ARM_ABIMASK = 0xFF000000; + public static final int EF_ARM_ABISHIFT = 24; + + /** + * ARM ABI version 5. + * {@value} + */ + public static final int EF_ARM_ABI5 = 0x05000000; + + /** + * The ELF file contains BE-8 code, suitable for execution on an ARM + * Architecture v6 processor. This flag must only be set on an executable file. + * {@value} + */ + public static final int EF_ARM_BE8 = 0x00800000; + + /** + * Legacy code (ABI version 4 and earlier) generated by gcc-arm-xxx might + * use these bits. + * {@value} + */ + public static final int EF_ARM_GCCMASK = 0x00400FFF; + + /** + * Set in executable file headers (e_type = ET_EXEC or ET_DYN) to note that + * the executable file was built to conform to the hardware floating-point + * procedure-call standard. + * <p> + * Compatible with legacy (pre version 5) gcc use as EF_ARM_VFP_FLOAT. + * </p> + * <p> + * Note: This is not used (anymore) + * </p> + * {@value} + */ + public static final int EF_ARM_ABI_FLOAT_HARD = 0x00000400; + + /** + * Set in executable file headers (e_type = ET_EXEC or ET_DYN) to note + * explicitly that the executable file was built to conform to the software + * floating-point procedure-call standard (the base standard). If both + * {@link #EF_ARM_ABI_FLOAT_HARD} and {@link #EF_ARM_ABI_FLOAT_SOFT} are clear, + * conformance to the base procedure-call standard is implied. + * <p> + * Compatible with legacy (pre version 5) gcc use as EF_ARM_SOFT_FLOAT. + * </p> + * <p> + * Note: This is not used (anymore) + * </p> + * {@value} + */ + public static final int EF_ARM_ABI_FLOAT_SOFT = 0x00000200; + + /** Public access to the elf header part-1 (CPU/ABI independent read) */ + public final ElfHeaderPart1 eh1; + + /** Public access to the raw elf header part-2 (CPU/ABI dependent read) */ + public final Ehdr_p2 raw; + + /** Lower case CPUType name */ + public final String cpuName; + public final CPUType cpuType; + public final ABIType abiType; + + /** Public access to the {@link SectionHeader} */ + public final SectionHeader[] sht; + + /** + * Note: The input stream shall stay untouch to be able to read sections! + * + * @param in input stream of a binary file at position zero + * @return + * @throws IOException if reading from the given input stream fails or less then ELF Header size bytes + * @throws IllegalArgumentException if the given input stream does not represent an ELF Header + */ + public static ElfHeaderPart2 read(final ElfHeaderPart1 eh1, final RandomAccessFile in) throws IOException, IllegalArgumentException { + return new ElfHeaderPart2(eh1, in); + } + + /** + * @param buf ELF Header bytes + * @throws IllegalArgumentException if the given buffer does not represent an ELF Header + * @throws IOException + */ + ElfHeaderPart2(final ElfHeaderPart1 eh1, final RandomAccessFile in) throws IllegalArgumentException, IOException { + this.eh1 = eh1; + // + // Part-2 + // + { + final byte[] buf = new byte[Ehdr_p2.size(eh1.machDesc.ordinal())]; + readBytes (in, buf, 0, buf.length); + final ByteBuffer eh2Bytes = ByteBuffer.wrap(buf, 0, buf.length); + raw = Ehdr_p2.create(eh1.machDesc.ordinal(), eh2Bytes); + } + sht = readSectionHeaderTable(in); + + if( CPUFamily.ARM32 == eh1.cpuType.family ) { + // AArch64, has no SHT_ARM_ATTRIBUTES or SHT_AARCH64_ATTRIBUTES SectionHeader defined in our builds! + String armCpuName = null; + String armCpuRawName = null; + boolean abiVFPArgsAcceptsVFPVariant = false; + final SectionHeader sh = getSectionHeader(SectionHeader.SHT_ARM_ATTRIBUTES); + if(ElfHeaderPart1.DEBUG) { + System.err.println("ELF-2: Got ARM Attribs Section Header: "+sh); + } + if( null != sh ) { + final SectionArmAttributes sArmAttrs = (SectionArmAttributes) sh.readSection(in); + if(ElfHeaderPart1.DEBUG) { + System.err.println("ELF-2: Got ARM Attribs Section Block : "+sArmAttrs); + } + final SectionArmAttributes.Attribute cpuNameArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.CPU_name); + if( null != cpuNameArgsAttr && cpuNameArgsAttr.isNTBS() ) { + armCpuName = cpuNameArgsAttr.getNTBS(); + } + final SectionArmAttributes.Attribute cpuRawNameArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.CPU_raw_name); + if( null != cpuRawNameArgsAttr && cpuRawNameArgsAttr.isNTBS() ) { + armCpuRawName = cpuRawNameArgsAttr.getNTBS(); + } + final SectionArmAttributes.Attribute abiVFPArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.ABI_VFP_args); + if( null != abiVFPArgsAttr ) { + abiVFPArgsAcceptsVFPVariant = SectionArmAttributes.abiVFPArgsAcceptsVFPVariant(abiVFPArgsAttr.getULEB128()); + } + } + { + String _cpuName; + CPUType _cpuType; + if( null != armCpuName && armCpuName.length() > 0 ) { + _cpuName = armCpuName.toLowerCase().replace(' ', '-'); + _cpuType = queryCPUTypeSafe(_cpuName, true /* isARM */); // 1st-try: native name + } else if( null != armCpuRawName && armCpuRawName.length() > 0 ) { + _cpuName = armCpuRawName.toLowerCase().replace(' ', '-'); + _cpuType = queryCPUTypeSafe(_cpuName, true /* isARM */); // 1st-try: native name + } else { + _cpuName = eh1.cpuName; + _cpuType = eh1.cpuType; + } + + if( null == _cpuType ) { + // 2nd-try: "arm-" + native name + _cpuName = "arm-"+_cpuName; + _cpuType = queryCPUTypeSafe(_cpuName, true /* isARM */); + if( null == _cpuType ) { + // finally: Use ELF-1, impossible path due to above 'arm-' prefix + _cpuName = eh1.cpuName; + _cpuType = queryCPUTypeSafe(_cpuName, true /* isARM */); + if( null == _cpuType ) { + throw new InternalError("XXX: "+_cpuName+", "+eh1); // shall not happen + } + } + } + cpuName = _cpuName; + cpuType = _cpuType; + if(ElfHeaderPart1.DEBUG) { + System.err.println("ELF-2: abiARM cpuName "+_cpuName+"[armCpuName "+armCpuName+", armCpuRawName "+armCpuRawName+"] -> "+cpuName+" -> "+cpuType+", abiVFPArgsAcceptsVFPVariant "+abiVFPArgsAcceptsVFPVariant); + } + } + abiType = abiVFPArgsAcceptsVFPVariant ? ABIType.EABI_GNU_ARMHF : ABIType.EABI_GNU_ARMEL; + } else { + // Non CPUFamily.ARM32 + cpuName = eh1.cpuName; + cpuType = eh1.cpuType; + abiType = eh1.abiType; + } + if(ElfHeaderPart1.DEBUG) { + System.err.println("ELF-2: cpuName "+cpuName+" -> "+cpuType+", "+abiType); + } + } + private static CPUType queryCPUTypeSafe(final String cpuName, final boolean isARM) { + try { + final CPUType res = CPUType.query(cpuName); + if( isARM && null != res ) { + if( res.family != CPUFamily.ARM32 || res.family != CPUFamily.ARM64 ) { + return null; // reject non-arm + } + } + return res; + } catch (final Throwable t) { + if(ElfHeaderPart1.DEBUG) { + System.err.println("ELF-2: queryCPUTypeSafe("+cpuName+"): "+t.getMessage()); + } + } + return null; + } + + public final short getSize() { return raw.getE_ehsize(); } + + /** Returns the processor-specific flags associated with the file. */ + public final int getFlags() { + return raw.getE_flags(); + } + + /** Returns the ARM EABI version from {@link #getFlags() flags}, maybe 0 if not an ARM EABI. */ + public byte getArmABI() { + return (byte) ( ( ( EF_ARM_ABIMASK & raw.getE_flags() ) >> EF_ARM_ABISHIFT ) & 0xff ); + } + + /** Returns the ARM EABI legacy GCC {@link #getFlags() flags}, maybe 0 if not an ARM EABI or not having legacy GCC flags. */ + public int getArmLegacyGCCFlags() { + final int f = raw.getE_flags(); + return 0 != ( EF_ARM_ABIMASK & f ) ? ( EF_ARM_GCCMASK & f ) : 0; + } + + /** + * Returns the ARM EABI float mode from {@link #getFlags() flags}, + * i.e. 1 for {@link #EF_ARM_ABI_FLOAT_SOFT}, 2 for {@link #EF_ARM_ABI_FLOAT_HARD} + * or 0 for none. + * <p> + * Note: This is not used (anymore) + * </p> + */ + public byte getArmFloatMode() { + final int f = raw.getE_flags(); + if( 0 != ( EF_ARM_ABIMASK & f ) ) { + if( ( EF_ARM_ABI_FLOAT_HARD & f ) != 0 ) { + return 2; + } + if( ( EF_ARM_ABI_FLOAT_SOFT & f ) != 0 ) { + return 1; + } + } + return 0; + } + + /** Returns the 1st occurence of matching SectionHeader {@link SectionHeader#getType() type}, or null if not exists. */ + public final SectionHeader getSectionHeader(final int type) { + for(int i=0; i<sht.length; i++) { + final SectionHeader sh = sht[i]; + if( sh.getType() == type ) { + return sh; + } + } + return null; + } + + /** Returns the 1st occurence of matching SectionHeader {@link SectionHeader#getName() name}, or null if not exists. */ + public final SectionHeader getSectionHeader(final String name) { + for(int i=0; i<sht.length; i++) { + final SectionHeader sh = sht[i]; + if( sh.getName().equals(name) ) { + return sh; + } + } + return null; + } + + @Override + public final String toString() { + final int armABI = getArmABI(); + final String armFlagsS; + if( 0 != armABI ) { + armFlagsS=", arm[abi "+armABI+", lGCC "+getArmLegacyGCCFlags()+", float "+getArmFloatMode()+"]"; + } else { + armFlagsS=""; + } + return "ELF-2["+cpuType+", "+abiType+", flags["+toHexString(getFlags())+armFlagsS+"], sh-num "+sht.length+"]"; + } + + final SectionHeader[] readSectionHeaderTable(final RandomAccessFile in) throws IOException, IllegalArgumentException { + // positioning + { + final long off = raw.getE_shoff(); // absolute offset + if( 0 == off ) { + return new SectionHeader[0]; + } + seek(in, off); + } + final SectionHeader[] sht; + final int strndx = raw.getE_shstrndx(); + final int size = raw.getE_shentsize(); + final int num; + int i; + if( 0 == raw.getE_shnum() ) { + // Read 1st table 1st and use it's sh_size + final byte[] buf0 = new byte[size]; + readBytes(in, buf0, 0, size); + final SectionHeader sh0 = new SectionHeader(this, buf0, 0, size, 0); + num = (int) sh0.raw.getSh_size(); + if( 0 >= num ) { + throw new IllegalArgumentException("EHdr sh_num == 0 and 1st SHdr size == 0"); + } + sht = new SectionHeader[num]; + sht[0] = sh0; + i=1; + } else { + num = raw.getE_shnum(); + sht = new SectionHeader[num]; + i=0; + } + for(; i<num; i++) { + final byte[] buf = new byte[size]; + readBytes(in, buf, 0, size); + sht[i] = new SectionHeader(this, buf, 0, size, i); + } + if( SectionHeader.SHN_UNDEF != strndx ) { + // has section name string table + if( shortToInt(SectionHeader.SHN_LORESERVE) <= strndx ) { + throw new InternalError("TODO strndx: "+SectionHeader.SHN_LORESERVE+" < "+strndx); + } + final SectionHeader strShdr = sht[strndx]; + if( SectionHeader.SHT_STRTAB != strShdr.getType() ) { + throw new IllegalArgumentException("Ref. string Shdr["+strndx+"] is of type "+strShdr.raw.getSh_type()); + } + final Section strS = strShdr.readSection(in); + for(i=0; i<num; i++) { + sht[i].initName(strS, sht[i].raw.getSh_name()); + } + } + + return sht; + } +} diff --git a/java_base/org/jau/sys/elf/IOUtils.java b/java_base/org/jau/sys/elf/IOUtils.java new file mode 100644 index 0000000..0bed0cd --- /dev/null +++ b/java_base/org/jau/sys/elf/IOUtils.java @@ -0,0 +1,129 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.jau.io.Bitstream; + +class IOUtils { + static final long MAX_INT_VALUE = ( Integer.MAX_VALUE & 0xffffffffL ) ; + + static String toHexString(final int i) { return "0x"+Integer.toHexString(i); } + + static String toHexString(final long i) { return "0x"+Long.toHexString(i); } + + static int shortToInt(final short s) { + return s & 0x0000ffff; + } + + static int long2Int(final long v) { + if( MAX_INT_VALUE < v ) { + throw new IllegalArgumentException("Read uint32 value "+toHexString(v)+" > int32-max "+toHexString(MAX_INT_VALUE)); + } + return (int)v; + } + + static void readBytes(final RandomAccessFile in, final byte[] out, final int offset, final int len) + throws IOException, IllegalArgumentException + { + in.readFully(out, offset, len); + } + + static void seek(final RandomAccessFile in, final long newPos) throws IOException { + in.seek(newPos); + } + + static int readUInt32(final boolean isBigEndian, final byte[] in, final int offset) { + final int v = Bitstream.uint32LongToInt(Bitstream.readUInt32(isBigEndian, in, offset)); + if( 0 > v ) { + throw new IllegalArgumentException("Read uint32 value "+toHexString(v)+" > int32-max "+toHexString(MAX_INT_VALUE)); + } + return v; + } + + /** + * @param sb byte source buffer to parse + * @param offset offset within byte source buffer to start parsing + * @param remaining remaining numbers of bytes to parse beginning w/ <code>sb_off</code>, + * which shall not exceed <code>sb.length - offset</code>. + * @param offset_post optional integer array holding offset post parsing + * @return the parsed string + * @throws IndexOutOfBoundsException if <code>offset + remaining > sb.length</code>. + */ + static String getString(final byte[] sb, final int offset, final int remaining, final int[] offset_post) throws IndexOutOfBoundsException { + Bitstream.checkBounds(sb, offset, remaining); + int strlen = 0; + for(; strlen < remaining && sb[strlen + offset] != 0; strlen++) { } + final String s = 0 < strlen ? new String(sb, offset, strlen) : "" ; + if( null != offset_post ) { + offset_post[0] = offset + strlen + 1; // incl. EOS + } + return s; + } + + /** + * @param sb byte source buffer to parse + * @param offset offset within byte source buffer to start parsing + * @param remaining remaining numbers of bytes to parse beginning w/ <code>sb_off</code>, + * which shall not exceed <code>sb.length - offset</code>. + * @return the number of parsed strings + * @throws IndexOutOfBoundsException if <code>offset + remaining > sb.length</code>. + */ + static int getStringCount(final byte[] sb, final int offset, final int remaining) throws IndexOutOfBoundsException { + Bitstream.checkBounds(sb, offset, remaining); + int strnum=0; + for(int i=0; i < remaining; i++) { + for(; i < remaining && sb[i + offset] != 0; i++) { } + strnum++; + } + return strnum; + } + + /** + * @param sb byte source buffer to parse + * @param offset offset within byte source buffer to start parsing + * @param remaining remaining numbers of bytes to parse beginning w/ <code>sb_off</code>, + * which shall not exceed <code>sb.length - offset</code>. + * @return the parsed strings + * @throws IndexOutOfBoundsException if <code>offset + remaining > sb.length</code>. + */ + public static String[] getStrings(final byte[] sb, final int offset, final int remaining) throws IndexOutOfBoundsException { + final int strnum = getStringCount(sb, offset, remaining); + // System.err.println("XXX: strnum "+strnum+", sb_off "+sb_off+", sb_len "+sb_len); + + final String[] sa = new String[strnum]; + final int[] io_off = new int[] { offset }; + for(int i=0; i < strnum; i++) { + // System.err.print("XXX: str["+i+"] ["+io_off[0]); + sa[i] = getString(sb, io_off[0], remaining - io_off[0], io_off); + // System.err.println(".. "+io_off[0]+"[ "+sa[i]); + } + return sa; + } + +} diff --git a/java_base/org/jau/sys/elf/Section.java b/java_base/org/jau/sys/elf/Section.java new file mode 100644 index 0000000..dd00f5f --- /dev/null +++ b/java_base/org/jau/sys/elf/Section.java @@ -0,0 +1,49 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +public class Section { + public SectionHeader sh; + public byte[] data; + public int offset; + public int length; + + Section(final SectionHeader sh, final byte[] data, final int offset, final int length) { + this.sh = sh; + this.data = data; + this.offset = offset; + this.length = length; + } + + @Override + public String toString() { + return "Section["+toSubString()+"]"; + } + String toSubString() { + return sh+", data[off "+offset+", len "+length+"/"+data.length+"]"; + } + +} diff --git a/java_base/org/jau/sys/elf/SectionArmAttributes.java b/java_base/org/jau/sys/elf/SectionArmAttributes.java new file mode 100644 index 0000000..d703132 --- /dev/null +++ b/java_base/org/jau/sys/elf/SectionArmAttributes.java @@ -0,0 +1,352 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import static org.jau.sys.elf.IOUtils.getString; +import static org.jau.sys.elf.IOUtils.readUInt32; +import static org.jau.sys.elf.IOUtils.toHexString; + +import java.util.ArrayList; +import java.util.List; + +import org.jau.io.Bitstream; + +/** + * ARM EABI attributes within section header {@link SectionHeader#SHT_ARM_ATTRIBUTES}. + * <p> + * References: + * <ul> + * <li>http://infocenter.arm.com/ + * <ul> + * <li>ARM IHI 0044E, current through ABI release 2.09</li> + * <li>ARM IHI 0045D, current through ABI release 2.09</li> + * </ul></li> + * </ul> + * </p> + */ +public class SectionArmAttributes extends Section { + public static final byte FORMAT_VERSION_A = 0x41; // 'A'; + + public static enum Type { + /** No Value */ + None, + /** A Sub-Section - following the 4 byte sub section total size (tag + size + content) - byte order of the ELF file */ + SubSection, + /** Null Terminated Byte-String */ + NTBS, + ULEB128, + } + + /** ULEB128 Value for {@link Tag#ABI_VFP_args}: FP parameter/result passing conforms to AAPCS, BASE variant. */ + public static final byte ABI_VFP_ARGS_IS_BASE_VARIANT = 0; + /** ULEB128 Value for {@link Tag#ABI_VFP_args}: FP parameter/result passing conforms to AAPCS, VFP variant. */ + public static final byte ABI_VFP_ARGS_IS_VFP_VARIANT = 1; + /** ULEB128 Value for {@link Tag#ABI_VFP_args}: FP parameter/result passing conforms to custom toolchain. */ + public static final byte ABI_VFP_ARGS_IS_CUSTOM_VARIANT = 2; + /** ULEB128 Value for {@link Tag#ABI_VFP_args}: FP parameter/result passing conforms to both , BASE and VFP variant. */ + public static final byte ABI_VFP_ARGS_IS_BOTH_BASE_AND_VFP_VARIANT = 3; + + /** + * Returns true if value is either {@link #ABI_VFP_ARGS_IS_VFP_VARIANT} or {@link #ABI_VFP_ARGS_IS_BOTH_BASE_AND_VFP_VARIANT} + * @param v ULEB128 Value from {@link Tag#ABI_VFP_args} attribute + */ + public static final boolean abiVFPArgsAcceptsVFPVariant(final byte v) { + return ABI_VFP_ARGS_IS_VFP_VARIANT == v || ABI_VFP_ARGS_IS_BOTH_BASE_AND_VFP_VARIANT == v; + } + + public static enum Tag { + None(0, Type.None), + File(1, Type.SubSection), Section(2, Type.SubSection), Symbol(3, Type.SubSection), + CPU_raw_name( 4, Type.NTBS ), + CPU_name( 5, Type.NTBS ), + CPU_arch( 6, Type.ULEB128 ), + CPU_arch_profile( 7, Type.ULEB128 ), + ARM_ISA_use( 8, Type.ULEB128 ), + THUMB_ISA_use( 9, Type.ULEB128 ), + FP_arch( 10, Type.ULEB128 ), + WMMX_arch( 11, Type.ULEB128 ), + Advanced_SIMD_arch( 12, Type.ULEB128 ), + PCS_config( 13, Type.ULEB128 ), + ABI_PCS_R9_use ( 14, Type.ULEB128 ), + ABI_PCS_RW_data( 15, Type.ULEB128 ), + ABI_PCS_RO_data( 16, Type.ULEB128 ), + ABI_PCS_GOT_use( 17, Type.ULEB128 ), + ABI_PCS_wchar_t( 18, Type.ULEB128 ), + ABI_FP_rounding( 19, Type.ULEB128 ), + ABI_FP_denormal( 20, Type.ULEB128 ), + ABI_FP_exceptions( 21, Type.ULEB128 ), + ABI_FP_user_exceptions( 22, Type.ULEB128 ), + ABI_FP_number_model( 23, Type.ULEB128 ), + ABI_align_needed( 24, Type.ULEB128 ), + ABI_align_preserved( 25, Type.ULEB128 ), + ABI_enum_size( 26, Type.ULEB128 ), + ABI_HardFP_use( 27, Type.ULEB128 ), + ABI_VFP_args( 28, Type.ULEB128 ), + ABI_WMMX_args( 29, Type.ULEB128 ), + ABI_optimization_goals( 30, Type.ULEB128 ), + ABI_FP_optimization_goals( 31, Type.ULEB128 ), + compatibility ( 32, Type.NTBS ), /** with each byte interpreted as an ULEB128 with closing EOS */ + CPU_unaligned_access( 34, Type.ULEB128 ), + FP_HP_extension( 36, Type.ULEB128 ), + ABI_FP_16bit_format( 38, Type.ULEB128 ), + MPextension_use( 42, Type.ULEB128 ), + DIV_use( 44, Type.ULEB128 ), + nodefaults( 64, Type.ULEB128 ), /* value ignored */ + also_compatible_with( 65, Type.ULEB128 ), + T2EE_use( 66, Type.ULEB128 ), + conformance( 67, Type.NTBS ), + Virtualization_use( 68, Type.ULEB128 ), + undefined69( 69, Type.None ), + MPextension_use_legacy( 70, Type.ULEB128 ) + ; + + public final int id; + public final Type type; + + /** Slow O(n) transition of a native tag value to a Tag. */ + public static Tag get(final int id) { + final Tag[] tags = Tag.values(); + final int tag_count = tags.length; + for(int i=0; i < tag_count; i++) { + if( tags[i].id == id ) { + return tags[i]; + } + } + return null; + } + + Tag(final int id, final Type type){ + this.id = id; + this.type = type; + } + } + + public static class Attribute { + public final Tag tag; + private final Object value; + + Attribute(final Tag tag, final Object value) { + this.tag = tag; + this.value = value; + } + + public final boolean isNTBS() { + return Type.NTBS == tag.type; + } + public final String getNTBS() { + if( Type.NTBS == tag.type ) { + return (String) value; + } + throw new IllegalArgumentException("Not NTBS but "+tag.type); + } + + public final boolean isULEB128() { + return Type.ULEB128 == tag.type; + } + public final byte getULEB128() { + if( Type.ULEB128== tag.type ) { + return ((Byte) value).byteValue(); + } + throw new IllegalArgumentException("Not ULEB128 but "+tag.type); + } + + @Override + public String toString() { + return tag+" = "+value; + } + } + + public static class VendorAttributes { + public final String vendor; + public final List<Attribute> attributes; + + VendorAttributes(final String vendor, final List<Attribute> attributes) { + this.vendor = vendor; + this.attributes = attributes; + } + + @Override + public String toString() { + return vendor + attributes.toString(); + } + } + public final List<VendorAttributes> vendorAttributesList; + + SectionArmAttributes(final SectionHeader sh, final byte[] data, final int offset, final int length) throws IndexOutOfBoundsException, IllegalArgumentException { + super(sh, data, offset, length); + this.vendorAttributesList = parse(sh, data, offset, length); + } + + @Override + public String toString() { + return "SectionArmAttributes["+super.toSubString()+", "+vendorAttributesList.toString()+"]"; + } + + public final Attribute get(final Tag tag) { + for(int i=0; i<vendorAttributesList.size(); i++) { + final List<Attribute> attributes = vendorAttributesList.get(i).attributes; + for(int j=0; j<attributes.size(); j++) { + final Attribute a = attributes.get(j); + if( a.tag == tag ) { + return a; + } + } + } + return null; + } + + public final List<Attribute> get(final String vendor) { + return get(vendorAttributesList, vendor); + } + + static final List<Attribute> get(final List<VendorAttributes> vendorAttributesList, final String vendor) { + for(int i=0; i<vendorAttributesList.size(); i++) { + final VendorAttributes vas = vendorAttributesList.get(i); + if( vas.vendor.equals(vendor) ) { + return vas.attributes; + } + } + return null; + } + + /** + * @param sh TODO + * @param in byte source buffer to parse + * @param offset offset within byte source buffer to start parsing + * @param remaining remaining numbers of bytes to parse beginning w/ <code>sb_off</code>, + * which shall not exceed <code>sb.length - offset</code>. + * @throws IndexOutOfBoundsException if <code>offset + remaining > sb.length</code>. + * @throws IllegalArgumentException if section parsing failed, i.e. incompatible version or data. + */ + static List<VendorAttributes> parse(final SectionHeader sh, final byte[] in, final int offset, final int remaining) throws IndexOutOfBoundsException, IllegalArgumentException { + Bitstream.checkBounds(in, offset, remaining); + int i = offset; + if( FORMAT_VERSION_A != in[ i ] ) { + throw new IllegalArgumentException("ShArmAttr: Not version A, but: "+toHexString(in[i])); + } + i++; + + final List<VendorAttributes> vendorAttributesList = new ArrayList<VendorAttributes>(); + final boolean isBigEndian = sh.eh2.eh1.isBigEndian(); + + while(i < remaining) { + final int i_pre = i; + final int secLen = readUInt32(isBigEndian, in, i); /* total section size: 4 + string + content, i.e. offset to next section */ + i+=4; + + final String vendor; + { + final int[] i_post = new int[] { 0 }; + vendor = getString(in, i, secLen - 4, i_post); + i = i_post[0]; + } + + final List<Attribute> attributes = new ArrayList<Attribute>(); + + while(i < secLen) { + final int[] i_post = new int[] { 0 }; + parseSub(isBigEndian, in, i, secLen - i, i_post, attributes); + i = i_post[0]; + } + + if( i_pre + secLen != i ) { + throw new IllegalArgumentException("ShArmAttr: Section length count mismatch, expected "+(i_pre + secLen)+", has "+i); + } + + final List<Attribute> mergeAttribs = get(vendorAttributesList, vendor); + if( null != mergeAttribs ) { + mergeAttribs.addAll(attributes); + } else { + vendorAttributesList.add(new VendorAttributes(vendor, attributes)); + } + } + + return vendorAttributesList; + } + + /** + * @param isBigEndian TODO + * @param in byte source buffer to parse + * @param offset offset within byte source buffer to start parsing + * @param remaining remaining numbers of bytes to parse beginning w/ <code>sb_off</code>, + * which shall not exceed <code>sb.length - offset</code>. + * @throws IndexOutOfBoundsException if <code>offset + remaining > sb.length</code>. + * @throws IllegalArgumentException if section parsing failed, i.e. incompatible version or data. + */ + private static void parseSub(final boolean isBigEndian, final byte[] in, final int offset, final int remaining, + final int[] offset_post, final List<Attribute> attributes) + throws IndexOutOfBoundsException, IllegalArgumentException + { + Bitstream.checkBounds(in, offset, remaining); + + // Starts w/ sub-section Tag + int i = offset; + final int i_sTag = in[i++]; + final Tag sTag = Tag.get(i_sTag); + if( null == sTag ) { + throw new IllegalArgumentException("ShArmAttr: Invalid Sub-Section tag (NaT): "+i_sTag); + } + final int subSecLen; // sub section total size (tag + size + content) + switch(sTag) { + case File: + case Section: + case Symbol: + subSecLen = readUInt32(isBigEndian, in, i); + i+=4; + break; + default: + throw new IllegalArgumentException("ShArmAttr: Invalid Sub-Section tag: "+sTag); + } + if( Tag.File == sTag ) { + while( i < offset + subSecLen ) { + final int i_tag = in[i++]; + final Tag tag = Tag.get(i_tag); + if( null == tag ) { + throw new IllegalArgumentException("ShArmAttr: Invalid Attribute tag (NaT): "+i_tag); + } + switch(tag.type) { + case NTBS: + { + final int[] i_post = new int[] { 0 }; + final String value = getString(in, i, subSecLen + offset - i, i_post); + attributes.add(new Attribute(tag, value)); + i = i_post[0]; + } + break; + case ULEB128: + { + final byte value = in[i++]; + attributes.add( new Attribute( tag, Byte.valueOf(value) ) ); + } + break; + default: + throw new IllegalArgumentException("ShArmAttr: Invalid Attribute tag: "+tag); + } + } + } + offset_post[0] = offset + subSecLen; + } +} diff --git a/java_base/org/jau/sys/elf/SectionHeader.java b/java_base/org/jau/sys/elf/SectionHeader.java new file mode 100644 index 0000000..80c3673 --- /dev/null +++ b/java_base/org/jau/sys/elf/SectionHeader.java @@ -0,0 +1,283 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import static org.jau.sys.elf.IOUtils.getString; +import static org.jau.sys.elf.IOUtils.long2Int; +import static org.jau.sys.elf.IOUtils.readBytes; +import static org.jau.sys.elf.IOUtils.seek; +import static org.jau.sys.elf.IOUtils.toHexString; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +/** + * ELF ABI Section Header + * <p> + * References: + * <ul> + * <li>http://linux.die.net/man/5/elf</li> + * <li>http://www.sco.com/developers/gabi/latest/contents.html</li> + * <li>http://infocenter.arm.com/ + * <ul> + * <li>ARM IHI 0044E, current through ABI release 2.09</li> + * </ul></li> + * </ul> + * </p> + */ +public class SectionHeader { + /** + * {@value} + */ + public static final int SHT_NULL = 0; + /** + * {@value} + */ + public static final int SHT_PROGBITS = 1; + /** + * {@value} + */ + public static final int SHT_SYMTAB = 2; + /** + * {@value} + */ + public static final int SHT_STRTAB = 3; + /** + * {@value} + */ + public static final int SHT_RELA = 4; + /** + * {@value} + */ + public static final int SHT_HASH = 5; + /** + * {@value} + */ + public static final int SHT_DYNAMIC = 6; + /** + * {@value} + */ + public static final int SHT_NOTE = 7; + /** + * {@value} + */ + public static final int SHT_NOBITS = 8; + /** + * {@value} + */ + public static final int SHT_REL = 9; + /** + * {@value} + */ + public static final int SHT_SHLIB = 10; + /** + * {@value} + */ + public static final int SHT_DYNSYM = 11; + /** + * {@value} + */ + public static final int SHT_NUM = 12; + /** + * {@value} + */ + public static final int SHT_LOPROC = 0x70000000; + /** + * {@value} + */ + public static final int SHT_HIPROC = 0x7fffffff; + /** + * {@value} + */ + public static final int SHT_LOUSER = 0x80000000; + /** + * {@value} + */ + public static final int SHT_HIUSER = 0xffffffff; + + /** + * {@value} + */ + public static final int SHT_ARM_EXIDX = 0x70000001; + /** + * {@value} + */ + public static final int SHT_ARM_PREEMPTMAP = 0x70000002; + /** + * {@value} + */ + public static final int SHT_ARM_ATTRIBUTES = 0x70000003; + + /** + * {@value}. FIXME: Same as {@link #SHT_ARM_ATTRIBUTES}, ok? + */ + public static final int SHT_AARCH64_ATTRIBUTES = 0x70000003; + + /** + * {@value} + */ + public static final int SHT_ARM_DEBUGOVERLAY = 0x70000004; + /** + * {@value} + */ + public static final int SHT_ARM_OVERLAYSECTION = 0x70000005; + + /** + * {@value} + */ + public static final short SHN_UNDEF = (short)0; + /** + * {@value} + */ + public static final short SHN_LORESERVE = (short)0xff00; + /** + * {@value} + */ + public static final short SHN_LOPROC = (short)0xff00; + /** + * {@value} + */ + public static final short SHN_HIPROC = (short)0xff1f; + /** + * {@value} + */ + public static final short SHN_ABS = (short)0xfff1; + /** + * {@value} + */ + public static final short SHN_COMMON = (short)0xfff2; + /** + * {@value} + */ + public static final short SHN_HIRESERVE = (short)0xffff; + + /** Public access to the elf header */ + public final ElfHeaderPart2 eh2; + + /** Public access to the raw elf section header */ + public final Shdr raw; + + private final int idx; + private String name; + + SectionHeader(final ElfHeaderPart2 eh, final byte[] buf, final int offset, final int length, final int sectionIdx) { + this( eh, ByteBuffer.wrap(buf, 0, buf.length), sectionIdx ); + } + SectionHeader(final ElfHeaderPart2 eh, final java.nio.ByteBuffer buf, final int idx) { + this.eh2 = eh; + this.raw = Shdr.create(eh.eh1.machDesc.ordinal(), buf); + this.idx = idx; + this.name = null; + } + + @Override + public String toString() { + return "SectionHeader[idx "+idx+", name "+name+", type "+toHexString(getType())+", link "+raw.getSh_link()+", info "+toHexString(raw.getSh_info())+", flags "+toHexString(getFlags())+"]"; + } + + /** + * @param strS the {@link SectionHeader#SHT_STRTAB} section containing all strings + * @param nameOffset name offset within strS + */ + void initName(final Section strS, final int nameOffset) throws IndexOutOfBoundsException { + name = getString(strS.data, strS.offset + nameOffset, strS.length - nameOffset, null); + } + + /** Returns the index of this section within the Elf section header table. */ + public int getIndex() { + return idx; + } + + /** Returns the type of this section. */ + public int getType() { + return raw.getSh_type(); + } + + /** Returns the flags of this section. */ + public long getFlags() { + return raw.getSh_flags(); + } + + /** Returns the size of this section. */ + public long getSize() { + return raw.getSh_size(); + } + + /** Returns this section name, maybe <code>null</code> if not read. */ + public String getName() { + return name; + } + + /** + * Returns the Section referenced w/ this section header + * + * @param in file owning the section + * @throws IOException if read error occurs + * @throws IllegalArgumentException if section offset or size mismatch including size > {@link Integer#MAX_VALUE} + */ + public Section readSection(final RandomAccessFile in) throws IOException, IllegalArgumentException { + final int s_size = long2Int(raw.getSh_size()); + if( 0 == s_size || 0 > s_size ) { + throw new IllegalArgumentException("Shdr["+idx+"] has invalid int size: "+raw.getSh_size()+" -> "+s_size); + } + final byte[] s_buf = new byte[s_size]; + return readSectionImpl(in, s_buf, 0, s_size); + } + + /** + * Returns the Section referenced w/ this section header using given byte array. + * + * @param in file owning the section + * @param b destination buffer + * @param b_off offset in destination buffer + * @param r_len requested read length in bytes, which shall be ≤ than this section size + * @throws IOException if read error occurs + * @throws IllegalArgumentException if section offset or size mismatch including size > {@link Integer#MAX_VALUE} + * @throws IllegalArgumentException if requested read length is > section size + */ + public Section readSection(final RandomAccessFile in, final byte[] b, final int b_off, final int r_len) throws IOException, IllegalArgumentException { + final int s_size = long2Int(raw.getSh_size()); + if( 0 == s_size || 0 > s_size ) { + throw new IllegalArgumentException("Shdr["+idx+"] has invalid int size: "+raw.getSh_size()+" -> "+s_size); + } + if( r_len > s_size ) { + throw new IllegalArgumentException("Shdr["+idx+"] has only "+s_size+" bytes, while read request is of "+r_len+" bytes"); + } + return readSectionImpl(in, b, b_off, r_len); + } + + Section readSectionImpl(final RandomAccessFile in, final byte[] b, final int b_off, final int r_len) throws IOException, IllegalArgumentException { + final long s_off = raw.getSh_offset(); + seek(in, s_off); + readBytes(in, b, b_off, r_len); + if( SectionHeader.SHT_ARM_ATTRIBUTES == getType() ) { + return new SectionArmAttributes(this, b, b_off, r_len); + } else { + return new Section(this, b, b_off, r_len); + } + } +} diff --git a/java_base/org/jau/sys/elf/Shdr.java b/java_base/org/jau/sys/elf/Shdr.java new file mode 100644 index 0000000..2add91e --- /dev/null +++ b/java_base/org/jau/sys/elf/Shdr.java @@ -0,0 +1,194 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import org.jau.lang.NioUtil; +import org.jau.lang.StructAccessor; +import org.jau.sys.MachineDataInfo; + +public class Shdr { + + StructAccessor accessor; + + private final int mdIdx; + private final MachineDataInfo md; + + private static final int[] Shdr_size = new int[] { 40 /* ARM_MIPS_32 */, 40 /* X86_32_UNIX */, 40 /* X86_32_MACOS */, 40 /* PPC_32_UNIX */, 40 /* SPARC_32_SUNOS */, 40 /* X86_32_WINDOWS */, 64 /* LP64_UNIX */, 64 /* X86_64_WINDOWS */ }; + private static final int[] sh_name_offset = new int[] { 0 /* ARM_MIPS_32 */, 0 /* X86_32_UNIX */, 0 /* X86_32_MACOS */, 0 /* PPC_32_UNIX */, 0 /* SPARC_32_SUNOS */, 0 /* X86_32_WINDOWS */, 0 /* LP64_UNIX */, 0 /* X86_64_WINDOWS */ }; +//private static final int[] sh_name_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ }; + private static final int[] sh_type_offset = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ }; +//private static final int[] sh_type_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ }; + private static final int[] sh_flags_offset = new int[] { 8 /* ARM_MIPS_32 */, 8 /* X86_32_UNIX */, 8 /* X86_32_MACOS */, 8 /* PPC_32_UNIX */, 8 /* SPARC_32_SUNOS */, 8 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; +//private static final int[] sh_flags_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] sh_addr_offset = new int[] { 12 /* ARM_MIPS_32 */, 12 /* X86_32_UNIX */, 12 /* X86_32_MACOS */, 12 /* PPC_32_UNIX */, 12 /* SPARC_32_SUNOS */, 12 /* X86_32_WINDOWS */, 16 /* LP64_UNIX */, 16 /* X86_64_WINDOWS */ }; +//private static final int[] sh_addr_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] sh_offset_offset = new int[] { 16 /* ARM_MIPS_32 */, 16 /* X86_32_UNIX */, 16 /* X86_32_MACOS */, 16 /* PPC_32_UNIX */, 16 /* SPARC_32_SUNOS */, 16 /* X86_32_WINDOWS */, 24 /* LP64_UNIX */, 24 /* X86_64_WINDOWS */ }; +//private static final int[] sh_offset_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] sh_size_offset = new int[] { 20 /* ARM_MIPS_32 */, 20 /* X86_32_UNIX */, 20 /* X86_32_MACOS */, 20 /* PPC_32_UNIX */, 20 /* SPARC_32_SUNOS */, 20 /* X86_32_WINDOWS */, 32 /* LP64_UNIX */, 32 /* X86_64_WINDOWS */ }; +//private static final int[] sh_size_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] sh_link_offset = new int[] { 24 /* ARM_MIPS_32 */, 24 /* X86_32_UNIX */, 24 /* X86_32_MACOS */, 24 /* PPC_32_UNIX */, 24 /* SPARC_32_SUNOS */, 24 /* X86_32_WINDOWS */, 40 /* LP64_UNIX */, 40 /* X86_64_WINDOWS */ }; +//private static final int[] sh_link_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ }; + private static final int[] sh_info_offset = new int[] { 28 /* ARM_MIPS_32 */, 28 /* X86_32_UNIX */, 28 /* X86_32_MACOS */, 28 /* PPC_32_UNIX */, 28 /* SPARC_32_SUNOS */, 28 /* X86_32_WINDOWS */, 44 /* LP64_UNIX */, 44 /* X86_64_WINDOWS */ }; +//private static final int[] sh_info_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 4 /* LP64_UNIX */, 4 /* X86_64_WINDOWS */ }; + private static final int[] sh_addralign_offset = new int[] { 32 /* ARM_MIPS_32 */, 32 /* X86_32_UNIX */, 32 /* X86_32_MACOS */, 32 /* PPC_32_UNIX */, 32 /* SPARC_32_SUNOS */, 32 /* X86_32_WINDOWS */, 48 /* LP64_UNIX */, 48 /* X86_64_WINDOWS */ }; +//private static final int[] sh_addralign_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + private static final int[] sh_entsize_offset = new int[] { 36 /* ARM_MIPS_32 */, 36 /* X86_32_UNIX */, 36 /* X86_32_MACOS */, 36 /* PPC_32_UNIX */, 36 /* SPARC_32_SUNOS */, 36 /* X86_32_WINDOWS */, 56 /* LP64_UNIX */, 56 /* X86_64_WINDOWS */ }; +//private static final int[] sh_entsize_size = new int[] { 4 /* ARM_MIPS_32 */, 4 /* X86_32_UNIX */, 4 /* X86_32_MACOS */, 4 /* PPC_32_UNIX */, 4 /* SPARC_32_SUNOS */, 4 /* X86_32_WINDOWS */, 8 /* LP64_UNIX */, 8 /* X86_64_WINDOWS */ }; + + public java.nio.ByteBuffer getBuffer() { + return accessor.getBuffer(); + } + + /** Setter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public Shdr setSh_name(final int val) { + accessor.setIntAt(sh_name_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public int getSh_name() { + return accessor.getIntAt(sh_name_offset[mdIdx]); + } + + /** Setter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public Shdr setSh_type(final int val) { + accessor.setIntAt(sh_type_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public int getSh_type() { + return accessor.getIntAt(sh_type_offset[mdIdx]); + } + + /** Setter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public Shdr setSh_flags(final long val) { + accessor.setLongAt(sh_flags_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getSh_flags() { + return accessor.getLongAt(sh_flags_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['ElfN_Addr' (typedef), size [fixed false, lnx64 8], [int]] */ + public Shdr setSh_addr(final long val) { + accessor.setLongAt(sh_addr_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_Addr' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getSh_addr() { + return accessor.getLongAt(sh_addr_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['ElfN_Off' (typedef), size [fixed false, lnx64 8], [int]] */ + public Shdr setSh_offset(final long val) { + accessor.setLongAt(sh_offset_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_Off' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getSh_offset() { + return accessor.getLongAt(sh_offset_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public Shdr setSh_size(final long val) { + accessor.setLongAt(sh_size_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getSh_size() { + return accessor.getLongAt(sh_size_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public Shdr setSh_link(final int val) { + accessor.setIntAt(sh_link_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public int getSh_link() { + return accessor.getIntAt(sh_link_offset[mdIdx]); + } + + /** Setter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public Shdr setSh_info(final int val) { + accessor.setIntAt(sh_info_offset[mdIdx], val); + return this; + } + + /** Getter for native field: CType['uint32_t', size [fixed true, lnx64 4], [int]] */ + public int getSh_info() { + return accessor.getIntAt(sh_info_offset[mdIdx]); + } + + /** Setter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public Shdr setSh_addralign(final long val) { + accessor.setLongAt(sh_addralign_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getSh_addralign() { + return accessor.getLongAt(sh_addralign_offset[mdIdx], md.longSizeInBytes()); + } + + /** Setter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public Shdr setSh_entsize(final long val) { + accessor.setLongAt(sh_entsize_offset[mdIdx], val, md.longSizeInBytes()); + return this; + } + + /** Getter for native field: CType['ElfN_size' (typedef), size [fixed false, lnx64 8], [int]] */ + public long getSh_entsize() { + return accessor.getLongAt(sh_entsize_offset[mdIdx], md.longSizeInBytes()); + } + + // --- Begin CustomJavaCode .cfg declarations + public static int size(final int mdIdx) { + return Shdr_size[mdIdx]; + } + + public static Shdr create(final int mdIdx) { + return create(mdIdx, NioUtil.newNativeByteBuffer( size(mdIdx) ) ); + } + + public static Shdr create(final int mdIdx, final java.nio.ByteBuffer buf) { + return new Shdr(mdIdx, buf); + } + + Shdr(final int mdIdx, final java.nio.ByteBuffer buf) { + this.mdIdx = mdIdx; + this.md = MachineDataInfo.StaticConfig.values()[mdIdx].md; + this.accessor = new StructAccessor(buf); + } + // ---- End CustomJavaCode .cfg declarations +} diff --git a/java/org/jau/util/ArrayHashMap.java b/java_base/org/jau/util/ArrayHashMap.java index f91086b..8e4dd19 100644 --- a/java/org/jau/util/ArrayHashMap.java +++ b/java_base/org/jau/util/ArrayHashMap.java @@ -56,7 +56,7 @@ import java.util.Set; * </ul> * * For thread safety, the application shall decorate access to instances via - * {@link com.jogamp.common.util.locks.RecursiveLock}. + * {@link org.jau.util.parallel.locks.RecursiveLock}. * */ public class ArrayHashMap<K, V> diff --git a/java/org/jau/util/ArrayHashSet.java b/java_base/org/jau/util/ArrayHashSet.java index 3f90999..a7ec77a 100644 --- a/java/org/jau/util/ArrayHashSet.java +++ b/java_base/org/jau/util/ArrayHashSet.java @@ -60,7 +60,7 @@ import java.util.ListIterator; * </ul> * * For thread safety, the application shall decorate access to instances via - * {@link com.jogamp.common.util.locks.RecursiveLock}. + * {@link org.jau.util.parallel.locks.RecursiveLock}. * */ public class ArrayHashSet<E> diff --git a/java/org/jau/util/BasicAlgos.java b/java_base/org/jau/util/BasicAlgos.java index 1565e96..1565e96 100644 --- a/java/org/jau/util/BasicAlgos.java +++ b/java_base/org/jau/util/BasicAlgos.java diff --git a/java/org/jau/util/BasicTypes.java b/java_base/org/jau/util/BasicTypes.java index 8069efa..386d497 100644 --- a/java/org/jau/util/BasicTypes.java +++ b/java_base/org/jau/util/BasicTypes.java @@ -144,6 +144,6 @@ public class BasicTypes { * See <a href="http://bjoern.hoehrmann.de/utf-8/decoder/dfa/">Bjoern Hoehrmann's site</a> for details. * </p> */ - public static native String decodeUTF8String(final byte[] buffer, final int offset, final int size); + // public static native String decodeUTF8String(final byte[] buffer, final int offset, final int size); } diff --git a/java/org/jau/util/BitMath.java b/java_base/org/jau/util/BitMath.java index d112abf..d112abf 100644 --- a/java/org/jau/util/BitMath.java +++ b/java_base/org/jau/util/BitMath.java diff --git a/java/org/jau/util/Bitfield.java b/java_base/org/jau/util/Bitfield.java index b45cbe8..b45cbe8 100644 --- a/java/org/jau/util/Bitfield.java +++ b/java_base/org/jau/util/Bitfield.java diff --git a/java_base/org/jau/util/Function.java b/java_base/org/jau/util/Function.java new file mode 100644 index 0000000..eeae364 --- /dev/null +++ b/java_base/org/jau/util/Function.java @@ -0,0 +1,47 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.util; + +/** + * Generic function interface to perform an action w/ given optional arguments + * producing an optional result. + * <p> + * For <code>void</code> functions, simply use type <code>Object</code> + * and ignore the result and/or arguments. + * </p> + * + * @param <T> the result type of {@link #eval(Object...)} + */ +public interface Function<R,A> { + /** + * Implementation may compute variable <code>args</code> list + * and returns a result. + * + * @param args variable argument list, <code>A[]</code>, maybe null + * @return the result. + */ + R eval(A... args); +} diff --git a/java/org/jau/util/Hash32.java b/java_base/org/jau/util/Hash32.java index 6e92404..6e92404 100644 --- a/java/org/jau/util/Hash32.java +++ b/java_base/org/jau/util/Hash32.java diff --git a/java/org/jau/util/Hash64.java b/java_base/org/jau/util/Hash64.java index ef09bf8..ef09bf8 100644 --- a/java/org/jau/util/Hash64.java +++ b/java_base/org/jau/util/Hash64.java diff --git a/java/org/jau/util/IntMath.java b/java_base/org/jau/util/IntMath.java index 9cab87d..9cab87d 100644 --- a/java/org/jau/util/IntMath.java +++ b/java_base/org/jau/util/IntMath.java diff --git a/java/org/jau/util/LFRingbuffer.java b/java_base/org/jau/util/LFRingbuffer.java index b288cf5..b288cf5 100644 --- a/java/org/jau/util/LFRingbuffer.java +++ b/java_base/org/jau/util/LFRingbuffer.java diff --git a/java/org/jau/util/Ringbuffer.java b/java_base/org/jau/util/Ringbuffer.java index 2167da6..2167da6 100644 --- a/java/org/jau/util/Ringbuffer.java +++ b/java_base/org/jau/util/Ringbuffer.java diff --git a/java/org/jau/util/ValueConv.java b/java_base/org/jau/util/ValueConv.java index 85616d5..85616d5 100644 --- a/java/org/jau/util/ValueConv.java +++ b/java_base/org/jau/util/ValueConv.java diff --git a/java_base/org/jau/util/VersionNumber.java b/java_base/org/jau/util/VersionNumber.java new file mode 100644 index 0000000..6f5ada1 --- /dev/null +++ b/java_base/org/jau/util/VersionNumber.java @@ -0,0 +1,283 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import java.util.regex.Matcher; + +/** + * Simple version number class containing a version number + * either being {@link #VersionNumber(int, int, int) defined explicit} + * or {@link #VersionNumber(String, String) derived from a string}. + * <p> + * For the latter case, you can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * <p> + * The state whether a component is defined explicitly <i>is not considered</i> + * in the {@link #hashCode()}, {@link #equals(Object)} or {@link #compareTo(Object)} methods, + * since the version number itself is treated regardless. + * </p> + */ +public class VersionNumber implements Comparable<Object> { + + /** + * A {@link #isZero() zero} version instance, w/o any component defined explicitly. + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + public static final VersionNumber zeroVersion = new VersionNumber(0, 0, 0, -1, (short)0); + + /** + * Returns the {@link java.util.regex.Pattern pattern} + * with Perl regular expression: + * <pre> + * "\\D*(\\d+)[^\\"+delim+"\\s]*(?:\\"+delim+"\\D*(\\d+)[^\\"+delim+"\\s]*(?:\\"+delim+"\\D*(\\d+))?)?" + * </pre> + * </p> + * <p> + * A whitespace within the version number will end the parser. + * </p> + * <p> + * Capture groups represent the major (1), optional minor (2) and optional sub version number (3) component in this order. + * </p> + * <p> + * Each capture group ignores any leading non-digit and uses only contiguous digits, i.e. ignores pending non-digits. + * </p> + * @param delim the delimiter, e.g. "." + */ + public static java.util.regex.Pattern getVersionNumberPattern(final String delim) { + return java.util.regex.Pattern.compile("\\D*(\\d+)[^\\"+delim+"\\s]*(?:\\"+delim+"\\D*(\\d+)[^\\"+delim+"\\s]*(?:\\"+delim+"\\D*(\\d+))?)?"); + } + + /** + * Returns the default {@link java.util.regex.Pattern pattern} using {@link #getVersionNumberPattern(String)} + * with delimiter "<b>.</b>". + * <p> + * Instance is cached. + * </p> + */ + public static java.util.regex.Pattern getDefaultVersionNumberPattern() { + if( null == defPattern ) { // volatile dbl-checked-locking OK + synchronized( VersionNumber.class ) { + if( null == defPattern ) { + defPattern = getVersionNumberPattern("."); + } + } + } + return defPattern; + } + private static volatile java.util.regex.Pattern defPattern = null; + + protected final int major, minor, sub, strEnd; + + protected final short state; + protected final static short HAS_MAJOR = 1 << 0 ; + protected final static short HAS_MINOR = 1 << 1 ; + protected final static short HAS_SUB = 1 << 2 ; + + protected VersionNumber(final int majorRev, final int minorRev, final int subMinorRev, final int _strEnd, final short _state) { + major = majorRev; + minor = minorRev; + sub = subMinorRev; + strEnd = _strEnd; + state = _state; + } + + /** + * Explicit version number instantiation, with all components defined explicitly. + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + public VersionNumber(final int majorRev, final int minorRev, final int subMinorRev) { + this(majorRev, minorRev, subMinorRev, -1, (short)(HAS_MAJOR | HAS_MINOR | HAS_SUB)); + } + + /** + * String derived version number instantiation. + * <p> + * Utilizing the default {@link java.util.regex.Pattern pattern} parser with delimiter "<b>.</b>", see {@link #getDefaultVersionNumberPattern()}. + * </p> + * <p> + * You can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * @param versionString should be given as [MAJOR[.MINOR[.SUB]]] + * + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + public VersionNumber(final String versionString) { + this(versionString, getDefaultVersionNumberPattern()); + } + + /** + * String derived version number instantiation. + * <p> + * Utilizing {@link java.util.regex.Pattern pattern} parser created via {@link #getVersionNumberPattern(String)}. + * </p> + * <p> + * You can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * @param versionString should be given as [MAJOR[.MINOR[.SUB]]] + * @param delim the delimiter, e.g. "." + * + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + public VersionNumber(final String versionString, final String delim) { + this(versionString, getVersionNumberPattern(delim)); + } + + /** + * String derived version number instantiation. + * <p> + * You can query whether a component has been defined explicitly by the given <code>versionString</code>, + * via {@link #hasMajor()}, {@link #hasMinor()} and {@link #hasSub()}. + * </p> + * @param versionString should be given as [MAJOR[.MINOR[.SUB]]] + * @param versionPattern the {@link java.util.regex.Pattern pattern} parser, must be compatible w/ {@link #getVersionNumberPattern(String)} + * + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + public VersionNumber(final String versionString, final java.util.regex.Pattern versionPattern) { + // group1: \d* == digits major + // group2: \d* == digits minor + // group3: \d* == digits sub + final int[] val = new int[3]; + int _strEnd = 0; + short _state = 0; + try { + final Matcher matcher = versionPattern.matcher( versionString ); + if( matcher.lookingAt() ) { + _strEnd = matcher.end(); + final int groupCount = matcher.groupCount(); + if( 1 <= groupCount ) { + val[0] = Integer.parseInt(matcher.group(1)); + _state = HAS_MAJOR; + if( 2 <= groupCount ) { + val[1] = Integer.parseInt(matcher.group(2)); + _state |= HAS_MINOR; + if( 3 <= groupCount ) { + val[2] = Integer.parseInt(matcher.group(3)); + _state |= HAS_SUB; + } + } + } + } + } catch (final Exception e) { } + + major = val[0]; + minor = val[1]; + sub = val[2]; + strEnd = _strEnd; + state = _state; + } + + /** Returns <code>true</code>, if all version components are zero, otherwise <code>false</code>. */ + public final boolean isZero() { + return major == 0 && minor == 0 && sub == 0; + } + + /** Returns <code>true</code>, if the major component is defined explicitly, otherwise <code>false</code>. Undefined components has the value <code>0</code>. */ + public final boolean hasMajor() { return 0 != ( HAS_MAJOR & state ); } + /** Returns <code>true</code>, if the optional minor component is defined explicitly, otherwise <code>false</code>. Undefined components has the value <code>0</code>. */ + public final boolean hasMinor() { return 0 != ( HAS_MINOR & state ); } + /** Returns <code>true</code>, if the optional sub component is defined explicitly, otherwise <code>false</code>. Undefined components has the value <code>0</code>. */ + public final boolean hasSub() { return 0 != ( HAS_SUB & state ); } + + /** + * If constructed with <code>version-string</code>, returns the string offset <i>after</i> the last matching character, + * or <code>0</code> if none matched, or <code>-1</code> if not constructed with a string. + */ + public final int endOfStringMatch() { return strEnd; } + + @Override + public final int hashCode() { + // 31 * x == (x << 5) - x + int hash = 31 + major; + hash = ((hash << 5) - hash) + minor; + return ((hash << 5) - hash) + sub; + } + + @Override + public final boolean equals(final Object o) { + if ( o instanceof VersionNumber ) { + return 0 == compareTo( (VersionNumber) o ); + } + return false; + } + + @Override + public final int compareTo(final Object o) { + if ( ! ( o instanceof VersionNumber ) ) { + final Class<?> c = (null != o) ? o.getClass() : null ; + throw new ClassCastException("Not a VersionNumber object: " + c); + } + return compareTo( (VersionNumber) o ); + } + + public final int compareTo(final VersionNumber vo) { + if (major > vo.major) { + return 1; + } else if (major < vo.major) { + return -1; + } else if (minor > vo.minor) { + return 1; + } else if (minor < vo.minor) { + return -1; + } else if (sub > vo.sub) { + return 1; + } else if (sub < vo.sub) { + return -1; + } + return 0; + } + + public final int getMajor() { + return major; + } + + public final int getMinor() { + return minor; + } + + public final int getSub() { + return sub; + } + + @Override + public String toString() { + return major + "." + minor + "." + sub ; + } +} diff --git a/java_base/org/jau/util/VersionNumberString.java b/java_base/org/jau/util/VersionNumberString.java new file mode 100644 index 0000000..7a63bf8 --- /dev/null +++ b/java_base/org/jau/util/VersionNumberString.java @@ -0,0 +1,88 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +/** + * {@link VersionNumber} specialization, holding the <code>versionString</code> + * this instance is derived from. + */ +public class VersionNumberString extends VersionNumber { + + /** + * A {@link #isZero() zero} version instance, w/o any component defined explicitly. + * @see #hasMajor() + * @see #hasMinor() + * @see #hasSub() + */ + public static final VersionNumberString zeroVersion = new VersionNumberString(0, 0, 0, -1, (short)0, "n/a"); + + protected final String strVal; + + protected VersionNumberString(final int majorRev, final int minorRev, final int subMinorRev, final int strEnd, final short _state, final String versionString) { + super(majorRev, minorRev, subMinorRev, strEnd, _state); + strVal = versionString; + } + + /** + * See {@link VersionNumber#VersionNumber(int, int, int)}. + */ + public VersionNumberString(final int majorRev, final int minorRev, final int subMinorRev, final String versionString) { + this(majorRev, minorRev, subMinorRev, -1, (short)(HAS_MAJOR | HAS_MINOR | HAS_SUB), versionString); + } + + /** + * See {@link VersionNumber#VersionNumber(String)}. + */ + public VersionNumberString(final String versionString) { + super( versionString); + strVal = versionString; + } + + /** + * See {@link VersionNumber#VersionNumber(String, String)}. + */ + public VersionNumberString(final String versionString, final String delim) { + super( versionString, delim); + strVal = versionString; + } + + /** + * See {@link VersionNumber#VersionNumber(String, java.util.regex.Pattern)}. + */ + public VersionNumberString(final String versionString, final java.util.regex.Pattern versionPattern) { + super( versionString, versionPattern); + strVal = versionString; + } + + /** Returns the version string this version number is derived from. */ + public final String getVersionString() { return strVal; } + + @Override + public String toString() { + return super.toString() + " ("+strVal+")" ; + } +} diff --git a/java_base/org/jau/util/parallel/FunctionTask.java b/java_base/org/jau/util/parallel/FunctionTask.java new file mode 100644 index 0000000..1c71c06 --- /dev/null +++ b/java_base/org/jau/util/parallel/FunctionTask.java @@ -0,0 +1,218 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel; + +import java.io.PrintStream; + +import org.jau.lang.InterruptSource; +import org.jau.lang.InterruptedRuntimeException; +import org.jau.util.Function; + +/** + * Helper class to provide a Runnable queue implementation with a Runnable wrapper + * which notifies after execution for the <code>invokeAndWait()</code> semantics. + */ +public class FunctionTask<R,A> extends TaskBase implements Function<R,A> { + protected Function<R,A> runnable; + protected R result; + protected A[] args; + + /** + * Invokes <code>func</code> on the current {@link Thread}. + * <p> + * The result can be retrieved via {@link FunctionTask#getResult()}, + * using the returned instance. + * </p> + * @param func the {@link Function} to execute. + * @param args the {@link Function} arguments + * @return the newly created and invoked {@link FunctionTask} + * @since 2.4.0 + */ + public static <U,V> FunctionTask<U,V> invokeOnCurrentThread(final Function<U,V> func, final V... args) { + final FunctionTask<U,V> rt = new FunctionTask<U,V>( func, null, false, null); + rt.args = args; + rt.run(); + return rt; + } + + /** + * Invokes <code>func</code> on a new {@link InterruptSource.Thread}, + * see {@link InterruptSource.Thread#Thread(ThreadGroup, Runnable, String)} for details. + * <p> + * The result can be retrieved via {@link FunctionTask#getResult()}, + * using the returned instance. + * </p> + * @param tg the {@link ThreadGroup} for the new thread, maybe <code>null</code> + * @param threadName the name for the new thread, maybe <code>null</code> + * @param waitUntilDone if <code>true</code>, waits until <code>func</code> execution is completed, otherwise returns immediately. + * @param func the {@link Function} to execute. + * @param args the {@link Function} arguments + * @return the newly created and invoked {@link FunctionTask} + * @since 2.3.2 + */ + public static <U,V> FunctionTask<U,V> invokeOnNewThread(final ThreadGroup tg, final String threadName, + final boolean waitUntilDone, final Function<U,V> func, final V... args) { + final FunctionTask<U,V> rt; + if( !waitUntilDone ) { + rt = new FunctionTask<U,V>( func, null, true, System.err ); + final InterruptSource.Thread t = InterruptSource.Thread.create(tg, rt, threadName); + rt.args = args; + t.start(); + } else { + final Object sync = new Object(); + rt = new FunctionTask<U,V>( func, sync, true, null ); + final InterruptSource.Thread t = InterruptSource.Thread.create(tg, rt, threadName); + synchronized(sync) { + rt.args = args; + t.start(); + while( rt.isInQueue() ) { + try { + sync.wait(); + } catch (final InterruptedException ie) { + throw new InterruptedRuntimeException(ie); + } + final Throwable throwable = rt.getThrowable(); + if(null!=throwable) { + throw new RuntimeException(throwable); + } + } + } + } + return rt; + } + + /** + * Create a RunnableTask object w/ synchronization, + * ie. suitable for <code>invokeAndWait()</code>. + * + * @param runnable the user action + * @param syncObject the synchronization object the caller shall wait until <code>runnable</code> execution is completed, + * or <code>null</code> if waiting is not desired. + * @param catchExceptions Influence an occurring exception during <code>runnable</code> execution. + * If <code>true</code>, the exception is silenced and can be retrieved via {@link #getThrowable()}, + * otherwise the exception is thrown. + * @param exceptionOut If not <code>null</code>, exceptions are written to this {@link PrintStream}. + */ + public FunctionTask(final Function<R,A> runnable, final Object syncObject, final boolean catchExceptions, final PrintStream exceptionOut) { + super(syncObject, catchExceptions, exceptionOut); + this.runnable = runnable ; + result = null; + args = null; + } + + /** Return the user action */ + public final Function<R,A> getRunnable() { + return runnable; + } + + /** + * Sets the arguments for {@link #run()}. + * They will be cleared after calling {@link #run()} or {@link #eval(Object...)}. + */ + public final void setArgs(final A... args) { + this.args = args; + } + + /** + * Retrieves the cached result of {@link #run()} + * and is cleared within this method. + */ + public final R getResult() { + final R res = result; + result = null; + return res; + } + + /** + * {@inheritDoc} + * <p> + * Calls {@link #eval(Object...)}. + * </p> + * <p> + * You may set the {@link #eval(Object...)} arguments via {@link #setArgs(Object...)} + * and retrieve the result via {@link #getResult()}. + * </p> + */ + @Override + public final void run() { + execThread = Thread.currentThread(); + + final A[] args = this.args; + this.args = null; + this.result = null; + runnableException = null; + tStarted = System.currentTimeMillis(); + if(null == syncObject) { + try { + this.result = runnable.eval(args); + } catch (final Throwable t) { + runnableException = t; + if(null != exceptionOut) { + exceptionOut.println("FunctionTask.run(): "+getExceptionOutIntro()+" exception occured on thread "+Thread.currentThread().getName()+": "+toString()); + printSourceTrace(); + t.printStackTrace(exceptionOut); + } + if(!catchExceptions) { + throw new RuntimeException(runnableException); + } + } finally { + tExecuted = System.currentTimeMillis(); + isExecuted = true; + } + } else { + synchronized (syncObject) { + try { + this.result = runnable.eval(args); + } catch (final Throwable t) { + runnableException = t; + if(null != exceptionOut) { + exceptionOut.println("FunctionTask.run(): "+getExceptionOutIntro()+" exception occured on thread "+Thread.currentThread().getName()+": "+toString()); + printSourceTrace(); + t.printStackTrace(exceptionOut); + } + if(!catchExceptions) { + throw new RuntimeException(runnableException); + } + } finally { + tExecuted = System.currentTimeMillis(); + isExecuted = true; + syncObject.notifyAll(); + } + } + } + } + + @Override + public final R eval(final A... args) { + this.args = args; + run(); + final R res = result; + result = null; + return res; + } +} + diff --git a/java_base/org/jau/util/parallel/RunnableExecutor.java b/java_base/org/jau/util/parallel/RunnableExecutor.java new file mode 100644 index 0000000..c52e6ee --- /dev/null +++ b/java_base/org/jau/util/parallel/RunnableExecutor.java @@ -0,0 +1,48 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2012 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.util.parallel; + +public interface RunnableExecutor { + /** This {@link RunnableExecutor} implementation simply invokes {@link Runnable#run()} + * on the current thread. + */ + public static final RunnableExecutor currentThreadExecutor = new CurrentThreadExecutor(); + + /** + * @param wait if true method waits until {@link Runnable#run()} is completed, otherwise don't wait. + * @param r the {@link Runnable} to be executed. + */ + void invoke(boolean wait, Runnable r); + + static class CurrentThreadExecutor implements RunnableExecutor { + private CurrentThreadExecutor() {} + + @Override + public void invoke(final boolean wait, final Runnable r) { + r.run(); + } + } +} diff --git a/java_base/org/jau/util/parallel/RunnableTask.java b/java_base/org/jau/util/parallel/RunnableTask.java new file mode 100644 index 0000000..8ca371b --- /dev/null +++ b/java_base/org/jau/util/parallel/RunnableTask.java @@ -0,0 +1,162 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel; + +import java.io.PrintStream; + +import org.jau.lang.InterruptSource; +import org.jau.lang.InterruptedRuntimeException; + +/** + * Helper class to provide a Runnable queue implementation with a Runnable wrapper + * which notifies after execution for the <code>invokeAndWait()</code> semantics. + */ +public class RunnableTask extends TaskBase { + protected final Runnable runnable; + + /** + * Invokes <code>runnable</code> on the current {@link Thread}. + * @param runnable the {@link Runnable} to execute on the current thread. + * The runnable <b>must exit</b>, i.e. not loop forever. + * @return the newly created and invoked {@link RunnableTask} + * @since 2.4.0 + */ + public static RunnableTask invokeOnCurrentThread(final Runnable runnable) { + final RunnableTask rt = new RunnableTask( runnable, null, false, null ); + rt.run(); + return rt; + } + + /** + * Invokes <code>runnable</code> on a new {@link InterruptSource.Thread}, + * see {@link InterruptSource.Thread#Thread(ThreadGroup, Runnable, String)} for details. + * @param tg the {@link ThreadGroup} for the new thread, maybe <code>null</code> + * @param threadName the name for the new thread, maybe <code>null</code> + * @param waitUntilDone if <code>true</code>, waits until <code>runnable</code> execution is completed, otherwise returns immediately. + * @param runnable the {@link Runnable} to execute on the new thread. If <code>waitUntilDone</code> is <code>true</code>, + * the runnable <b>must exit</b>, i.e. not loop forever. + * @return the newly created and invoked {@link RunnableTask} + * @since 2.3.2 + */ + public static RunnableTask invokeOnNewThread(final ThreadGroup tg, final String threadName, + final boolean waitUntilDone, final Runnable runnable) { + final RunnableTask rt; + if( !waitUntilDone ) { + rt = new RunnableTask( runnable, null, true, System.err ); + final InterruptSource.Thread t = InterruptSource.Thread.create(tg, rt, threadName); + t.start(); + } else { + final Object sync = new Object(); + rt = new RunnableTask( runnable, sync, true, null ); + final InterruptSource.Thread t = InterruptSource.Thread.create(tg, rt, threadName); + synchronized(sync) { + t.start(); + while( rt.isInQueue() ) { + try { + sync.wait(); + } catch (final InterruptedException ie) { + throw new InterruptedRuntimeException(ie); + } + final Throwable throwable = rt.getThrowable(); + if(null!=throwable) { + throw new RuntimeException(throwable); + } + } + } + } + return rt; + } + + /** + * Create a RunnableTask object w/ synchronization, + * ie. suitable for <code>invokeAndWait()</code>, i.e. {@link #invoke(boolean, Runnable) invoke(true, runnable)}. + * + * @param runnable The user action + * @param syncObject The synchronization object if caller wait until <code>runnable</code> execution is completed, + * or <code>null</code> if waiting is not desired. + * @param catchExceptions Influence an occurring exception during <code>runnable</code> execution. + * If <code>true</code>, the exception is silenced and can be retrieved via {@link #getThrowable()}, + * otherwise the exception is thrown. + * @param exceptionOut If not <code>null</code>, exceptions are written to this {@link PrintStream}. + */ + public RunnableTask(final Runnable runnable, final Object syncObject, final boolean catchExceptions, final PrintStream exceptionOut) { + super(syncObject, catchExceptions, exceptionOut); + this.runnable = runnable ; + } + + /** Return the user action */ + public final Runnable getRunnable() { + return runnable; + } + + @Override + public final void run() { + execThread = Thread.currentThread(); + + runnableException = null; + tStarted = System.currentTimeMillis(); + if(null == syncObject) { + try { + runnable.run(); + } catch (final Throwable t) { + runnableException = t; + if(null != exceptionOut) { + exceptionOut.println("RunnableTask.run(): "+getExceptionOutIntro()+" exception occured on thread "+Thread.currentThread().getName()+": "+toString()); + printSourceTrace(); + runnableException.printStackTrace(exceptionOut); + } + if(!catchExceptions) { + throw new RuntimeException(runnableException); + } + } finally { + tExecuted = System.currentTimeMillis(); + isExecuted = true; + } + } else { + synchronized (syncObject) { + try { + runnable.run(); + } catch (final Throwable t) { + runnableException = t; + if(null != exceptionOut) { + exceptionOut.println("RunnableTask.run(): "+getExceptionOutIntro()+" exception occured on thread "+Thread.currentThread().getName()+": "+toString()); + printSourceTrace(); + t.printStackTrace(exceptionOut); + } + if(!catchExceptions) { + throw new RuntimeException(runnableException); + } + } finally { + tExecuted = System.currentTimeMillis(); + isExecuted = true; + syncObject.notifyAll(); + } + } + } + } +} + diff --git a/java_base/org/jau/util/parallel/TaskBase.java b/java_base/org/jau/util/parallel/TaskBase.java new file mode 100644 index 0000000..6ec125e --- /dev/null +++ b/java_base/org/jau/util/parallel/TaskBase.java @@ -0,0 +1,193 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel; + +import java.io.PrintStream; + +import org.jau.sys.Debug; +import org.jau.sys.PropertyAccess; + +/** + * Helper class to provide a Runnable queue implementation with a Runnable wrapper + * which notifies after execution for the <code>invokeAndWait()</code> semantics. + */ +public abstract class TaskBase implements Runnable { + /** Enable via the property <code>jogamp.debug.TaskBase.TraceSource</code> */ + private static final boolean TRACE_SOURCE; + + static { + Debug.initSingleton(); + TRACE_SOURCE = PropertyAccess.isPropertyDefined("jau.debug.TaskBase.TraceSource", true); + } + + protected final Object syncObject; + protected final boolean catchExceptions; + protected final PrintStream exceptionOut; + protected final Throwable sourceStack; + + protected Object attachment; + protected Throwable runnableException; + protected long tCreated, tStarted; + protected volatile long tExecuted; + protected volatile boolean isExecuted; + protected volatile boolean isFlushed; + protected volatile Thread execThread; + + /** + * @param syncObject The synchronization object if caller wait until <code>runnable</code> execution is completed, + * or <code>null</code> if waiting is not desired. + * @param catchExceptions Influence an occurring exception during <code>runnable</code> execution. + * If <code>true</code>, the exception is silenced and can be retrieved via {@link #getThrowable()}, + * otherwise the exception is thrown. + * @param exceptionOut If not <code>null</code>, exceptions are written to this {@link PrintStream}. + */ + protected TaskBase(final Object syncObject, final boolean catchExceptions, final PrintStream exceptionOut) { + this.syncObject = syncObject; + this.catchExceptions = catchExceptions; + this.exceptionOut = exceptionOut; + this.sourceStack = TRACE_SOURCE ? new Throwable("Creation @") : null; + this.tCreated = System.currentTimeMillis(); + this.tStarted = 0; + this.tExecuted = 0; + this.isExecuted = false; + this.isFlushed = false; + this.execThread = null; + } + + protected final String getExceptionOutIntro() { + return catchExceptions ? "A caught" : "An uncaught"; + } + protected final void printSourceTrace() { + if( null != sourceStack && null != exceptionOut ) { + sourceStack.printStackTrace(exceptionOut); + } + } + + /** + * Returns the execution thread or {@code null} if not yet {@link #run()}. + * @since 2.3.2 + */ + public final Thread getExecutionThread() { + return execThread; + } + + /** + * Return the synchronization object if any. + * @see #RunnableTask(Runnable, Object, boolean) + */ + public final Object getSyncObject() { + return syncObject; + } + + /** + * Attach a custom object to this task. + * Useful to piggybag further information, ie tag a task final. + */ + public final void setAttachment(final Object o) { + attachment = o; + } + + /** + * Return the attachment object if any. + * @see #setAttachment(Object) + */ + public final Object getAttachment() { + return attachment; + } + + @Override + public abstract void run(); + + /** + * Simply flush this task and notify a waiting executor. + * The executor which might have been blocked until notified + * will be unblocked and the task removed from the queue. + * + * @param t optional Throwable to be assigned for later {@link #getThrowable()} query in case of an error. + * + * @see #isFlushed() + * @see #isInQueue() + */ + public final void flush(final Throwable t) { + if(!isExecuted() && hasWaiter()) { + runnableException = t; + synchronized (syncObject) { + isFlushed = true; + syncObject.notifyAll(); + } + } + } + + /** + * @return !{@link #isExecuted()} && !{@link #isFlushed()} + */ + public final boolean isInQueue() { return !isExecuted && !isFlushed; } + + /** + * @return True if executed, otherwise false; + */ + public final boolean isExecuted() { return isExecuted; } + + /** + * @return True if flushed, otherwise false; + */ + public final boolean isFlushed() { return isFlushed; } + + /** + * @return True if invoking thread waits until done, + * ie a <code>notifyObject</code> was passed, otherwise false; + */ + public final boolean hasWaiter() { return null != syncObject; } + + /** + * @return A thrown exception while execution of the user action, if any and if caught + * @see #RunnableTask(Runnable, Object, boolean) + */ + public final Throwable getThrowable() { return runnableException; } + + public final long getTimestampCreate() { return tCreated; } + public final long getTimestampBeforeExec() { return tStarted; } + public final long getTimestampAfterExec() { return tExecuted; } + public final long getDurationInQueue() { return tStarted - tCreated; } + public final long getDurationInExec() { return 0 < tExecuted ? tExecuted - tStarted : 0; } + public final long getDurationTotal() { return 0 < tExecuted ? tExecuted - tCreated : tStarted - tCreated; } + + @Override + public String toString() { + final String etn; + final String eth; + if( null != execThread ) { + etn = execThread.getName(); + eth = "0x"+Integer.toHexString(execThread.hashCode()); + } else { + etn = "n/a"; + eth = "n/a"; + } + return "RunnableTask[enqueued "+isInQueue()+"[executed "+isExecuted()+", flushed "+isFlushed()+", thread["+eth+", "+etn+"]], tTotal "+getDurationTotal()+" ms, tExec "+getDurationInExec()+" ms, tQueue "+getDurationInQueue()+" ms, attachment "+attachment+", throwable "+getThrowable()+"]"; + } +} + diff --git a/java_base/org/jau/util/parallel/package.html b/java_base/org/jau/util/parallel/package.html new file mode 100644 index 0000000..c50570d --- /dev/null +++ b/java_base/org/jau/util/parallel/package.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <title>Jaulib Parallel & Concurrency Utilities</title> +</head> + <body> +<h4><i>Jaulib</i> Parallel & Concurrency Utilities</h4> +</body> +</html> diff --git a/java_jni/CMakeLists.txt b/java_jni/CMakeLists.txt new file mode 100644 index 0000000..e758520 --- /dev/null +++ b/java_jni/CMakeLists.txt @@ -0,0 +1,21 @@ +# java/CMakeLists.txt + +set(CMAKE_JNI_TARGET TRUE) +file(GLOB_RECURSE JAVA_SOURCES "*.java") +add_jar(jaulib_jni_jar + ${JAVA_SOURCES} + INCLUDE_JARS jaulib_base_jar + MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt + OUTPUT_NAME jaulib_jni + GENERATE_NATIVE_HEADERS jaulib_jni_javah + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_jni_jar.dir/jni" +) +add_dependencies(jaulib_jni_jar jaulib_base_jar) +#add_dependencies(jaulib_net_jar jaulib_base_jar jaulib_jni_jar) +#add_dependencies(jaulib_pkg_jar jaulib_base_jar jaulib_jni_jar jaulib_net_jar) + +set(JNI_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_jni_jar.dir/jni") +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/jaulib_jni.jar DESTINATION ${CMAKE_INSTALL_LIBDIR}/../lib/java) + +add_subdirectory (jni) + diff --git a/java_jni/jau/sys/MachineDataInfoRuntime.java b/java_jni/jau/sys/MachineDataInfoRuntime.java new file mode 100644 index 0000000..d024bf6 --- /dev/null +++ b/java_jni/jau/sys/MachineDataInfoRuntime.java @@ -0,0 +1,155 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.sys; + +import org.jau.sys.MachineDataInfo; +import org.jau.sys.MachineDataInfo.StaticConfig; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes; + +/** + * Runtime operations of {@link MachineDataInfo}. + */ +public class MachineDataInfoRuntime { + + static volatile boolean initialized = false; + static volatile MachineDataInfo runtimeMD = null; + static volatile MachineDataInfo.StaticConfig staticMD = null; + + public static void initialize() { + if( !initialized ) { + synchronized(MachineDataInfo.class) { // volatile dbl-checked-locking OK + if( !initialized ) { + MachineDataInfo.StaticConfig.validateUniqueMachineDataInfo(); + + final MachineDataInfo runtimeMD = getRuntimeImpl(); + final MachineDataInfo.StaticConfig staticMD = MachineDataInfo.StaticConfig.findCompatible(runtimeMD); + if( null == staticMD ) { + throw new RuntimeException("No compatible MachineDataInfo.StaticConfig for runtime:"+PlatformProps.NEWLINE+runtimeMD); + } + if( !staticMD.md.compatible(runtimeMD) ) { + throw new RuntimeException("Incompatible MachineDataInfo:"+PlatformProps.NEWLINE+ + " Static "+staticMD+PlatformProps.NEWLINE+ + " Runtime "+runtimeMD); + } + MachineDataInfoRuntime.runtimeMD = runtimeMD; + MachineDataInfoRuntime.staticMD = staticMD; + initialized=true; + if( PlatformProps.DEBUG ) { + System.err.println("MachineDataInfoRuntime.initialize():"+PlatformProps.NEWLINE+ + " Static "+staticMD+PlatformProps.NEWLINE+ + " Runtime "+runtimeMD); + } + return; + } + } + } + throw new InternalError("Already initialized"); + } + /** + * The static {@link MachineDataInfo} is utilized for high performance + * precompiled size, offset, etc table lookup within generated structures + * using the {@link MachineDataInfo.StaticConfig} index. + */ + public static MachineDataInfo.StaticConfig getStatic() { + if(!initialized) { + synchronized(MachineDataInfo.class) { // volatile dbl-checked-locking OK + if(!initialized) { + throw new InternalError("Not set"); + } + } + } + return staticMD; + } + public static MachineDataInfo getRuntime() { + if(!initialized) { + synchronized(MachineDataInfo.class) { // volatile dbl-checked-locking OK + if(!initialized) { + throw new InternalError("Not set"); + } + } + } + return runtimeMD; + } + + private static MachineDataInfo getRuntimeImpl() { + try { + PlatformProps.initSingleton(); + } catch (final Throwable err) { + return null; + } + + final int pointerSizeInBytes = getPointerSizeInBytesImpl(); + switch(pointerSizeInBytes) { + case 4: + case 8: + break; + default: + throw new RuntimeException("Unsupported pointer size "+pointerSizeInBytes+"bytes, please implement."); + } + + final long pageSizeL = getPageSizeInBytesImpl(); + if(Integer.MAX_VALUE < pageSizeL) { + throw new InternalError("PageSize exceeds integer value: " + pageSizeL); + } + + // size: int, long, float, double, pointer, pageSize + // alignment: int8, int16, int32, int64, int, long, float, double, pointer + return new MachineDataInfo( + true /* runtime validated */, + + getSizeOfIntImpl(), getSizeOfLongImpl(), + getSizeOfFloatImpl(), getSizeOfDoubleImpl(), getSizeOfLongDoubleImpl(), + pointerSizeInBytes, (int)pageSizeL, + + getAlignmentInt8Impl(), getAlignmentInt16Impl(), getAlignmentInt32Impl(), getAlignmentInt64Impl(), + getAlignmentIntImpl(), getAlignmentLongImpl(), + getAlignmentFloatImpl(), getAlignmentDoubleImpl(), getAlignmentLongDoubleImpl(), + getAlignmentPointerImpl()); + } + + private static native int getPointerSizeInBytesImpl(); + private static native long getPageSizeInBytesImpl(); + + private static native int getAlignmentInt8Impl(); + private static native int getAlignmentInt16Impl(); + private static native int getAlignmentInt32Impl(); + private static native int getAlignmentInt64Impl(); + private static native int getAlignmentIntImpl(); + private static native int getAlignmentLongImpl(); + private static native int getAlignmentPointerImpl(); + private static native int getAlignmentFloatImpl(); + private static native int getAlignmentDoubleImpl(); + private static native int getAlignmentLongDoubleImpl(); + private static native int getSizeOfIntImpl(); + private static native int getSizeOfLongImpl(); + private static native int getSizeOfPointerImpl(); + private static native int getSizeOfFloatImpl(); + private static native int getSizeOfDoubleImpl(); + private static native int getSizeOfLongDoubleImpl(); +} + diff --git a/java_jni/jau/sys/dl/BionicDynamicLinker32bitImpl.java b/java_jni/jau/sys/dl/BionicDynamicLinker32bitImpl.java new file mode 100644 index 0000000..9aa8c02 --- /dev/null +++ b/java_jni/jau/sys/dl/BionicDynamicLinker32bitImpl.java @@ -0,0 +1,61 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +/** + * Bionic 32bit specialization of {@link UnixDynamicLinkerImpl} + * utilizing Bionic's non POSIX flags and mode values. + * <p> + * Bionic is used on Android. + * </p> + */ +public final class BionicDynamicLinker32bitImpl extends UnixDynamicLinkerImpl { + + // static final int RTLD_NOW = 0x00000; + private static final int RTLD_LAZY = 0x00001; + + private static final int RTLD_LOCAL = 0x00000; + private static final int RTLD_GLOBAL = 0x00002; + // static final int RTLD_NOLOAD = 0x00004; + + private static final long RTLD_DEFAULT = 0xffffffffL; + // static final long RTLD_NEXT = 0xfffffffeL; + + @Override + protected final long openLibraryLocalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_LOCAL); + } + + @Override + protected final long openLibraryGlobalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_GLOBAL); + } + + @Override + protected final long lookupSymbolGlobalImpl(final String symbolName) throws SecurityException { + return dlsym(RTLD_DEFAULT, symbolName); + } +} diff --git a/java_jni/jau/sys/dl/BionicDynamicLinker64BitImpl.java b/java_jni/jau/sys/dl/BionicDynamicLinker64BitImpl.java new file mode 100644 index 0000000..bfbc7f0 --- /dev/null +++ b/java_jni/jau/sys/dl/BionicDynamicLinker64BitImpl.java @@ -0,0 +1,61 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2015 Gothel Software e.K. + * Copyright (c) 2015 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +/** + * Bionic 64bit specialization of {@link UnixDynamicLinkerImpl} + * utilizing Bionic's non POSIX flags and mode values. + * <p> + * Bionic is used on Android. + * </p> + */ +public final class BionicDynamicLinker64BitImpl extends UnixDynamicLinkerImpl { + // static final int RTLD_NOW = 0x00002; + private static final int RTLD_LAZY = 0x00001; + + private static final int RTLD_LOCAL = 0x00000; + private static final int RTLD_GLOBAL = 0x00100; + // static final int RTLD_NOLOAD = 0x00004; + + private static final long RTLD_DEFAULT = 0x00000000L; + // static final long RTLD_NEXT = -1L; + + @Override + protected final long openLibraryLocalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_LOCAL); + } + + @Override + protected final long openLibraryGlobalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_GLOBAL); + } + + @Override + protected final long lookupSymbolGlobalImpl(final String symbolName) throws SecurityException { + return dlsym(RTLD_DEFAULT, symbolName); + } + +} diff --git a/java_jni/jau/sys/dl/DynamicLinkerImpl.java b/java_jni/jau/sys/dl/DynamicLinkerImpl.java new file mode 100644 index 0000000..407666e --- /dev/null +++ b/java_jni/jau/sys/dl/DynamicLinkerImpl.java @@ -0,0 +1,215 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +import java.util.HashMap; + +import org.jau.sec.SecurityUtil; +import org.jau.sys.dl.DynamicLinker; + +/* pp */ abstract class DynamicLinkerImpl implements DynamicLinker { + + // + // Package private scope of class w/ protected native code access + // and sealed jogamp.common.* package definition + // ensuring no abuse via subclassing. + // + + private final Object secSync = new Object(); + private boolean allLinkPermissionGranted = false; + + /** + * @throws SecurityException if user is not granted global access + */ + @Override +public final void claimAllLinkPermission() throws SecurityException { + synchronized( secSync ) { + allLinkPermissionGranted = true; + } + } + + /** + * @throws SecurityException if user is not granted global access + */ + @Override +public final void releaseAllLinkPermission() throws SecurityException { + synchronized( secSync ) { + allLinkPermissionGranted = false; + } + } + + private final void checkLinkPermission(final String pathname) throws SecurityException { + synchronized( secSync ) { + if( !allLinkPermissionGranted ) { + SecurityUtil.checkLinkPermission(pathname); + } + } + } + private final void checkLinkPermission(final long libraryHandle) throws SecurityException { + synchronized( secSync ) { + if( !allLinkPermissionGranted ) { + final LibRef libRef = getLibRef( libraryHandle ); + if( null == libRef ) { + throw new IllegalArgumentException("Library handle 0x"+Long.toHexString(libraryHandle)+" unknown."); + } + SecurityUtil.checkLinkPermission(libRef.getName()); + } + } + } + + private final void checkAllLinkPermission() throws SecurityException { + synchronized( secSync ) { + if( !allLinkPermissionGranted ) { + SecurityUtil.checkAllLinkPermission(); + } + } + } + + @Override + public final long openLibraryGlobal(final String pathname, final boolean debug) throws SecurityException { + checkLinkPermission(pathname); + final long handle = openLibraryGlobalImpl(pathname); + if( 0 != handle ) { + final LibRef libRef = incrLibRefCount(handle, pathname); + if( DEBUG || debug ) { + System.err.println("DynamicLinkerImpl.openLibraryGlobal \""+pathname+"\": 0x"+Long.toHexString(handle)+" -> "+libRef+")"); + } + } else if ( DEBUG || debug ) { + System.err.println("DynamicLinkerImpl.openLibraryGlobal \""+pathname+"\" failed, error: "+getLastError()); + } + return handle; + } + protected abstract long openLibraryGlobalImpl(final String pathname) throws SecurityException; + + @Override + public final long openLibraryLocal(final String pathname, final boolean debug) throws SecurityException { + checkLinkPermission(pathname); + final long handle = openLibraryLocalImpl(pathname); + if( 0 != handle ) { + final LibRef libRef = incrLibRefCount(handle, pathname); + if( DEBUG || debug ) { + System.err.println("DynamicLinkerImpl.openLibraryLocal \""+pathname+"\": 0x"+Long.toHexString(handle)+" -> "+libRef+")"); + } + } else if ( DEBUG || debug ) { + System.err.println("DynamicLinkerImpl.openLibraryLocal \""+pathname+"\" failed, error: "+getLastError()); + } + return handle; + } + protected abstract long openLibraryLocalImpl(final String pathname) throws SecurityException; + + @Override + public final long lookupSymbolGlobal(final String symbolName) throws SecurityException { + checkAllLinkPermission(); + final long addr = lookupSymbolGlobalImpl(symbolName); + if(DEBUG_LOOKUP) { + System.err.println("DynamicLinkerImpl.lookupSymbolGlobal("+symbolName+") -> 0x"+Long.toHexString(addr)); + } + return addr; + } + protected abstract long lookupSymbolGlobalImpl(final String symbolName) throws SecurityException; + + @Override + public final long lookupSymbol(final long libraryHandle, final String symbolName) throws SecurityException, IllegalArgumentException { + checkLinkPermission(libraryHandle); + final long addr = lookupSymbolLocalImpl(libraryHandle, symbolName); + if(DEBUG_LOOKUP) { + System.err.println("DynamicLinkerImpl.lookupSymbol(0x"+Long.toHexString(libraryHandle)+", "+symbolName+") -> 0x"+Long.toHexString(addr)); + } + return addr; + } + protected abstract long lookupSymbolLocalImpl(final long libraryHandle, final String symbolName) throws SecurityException; + + @Override + public final void closeLibrary(final long libraryHandle, final boolean debug) throws SecurityException, IllegalArgumentException { + final LibRef libRef = decrLibRefCount( libraryHandle ); + if( null != libRef ) { + checkLinkPermission(libRef.getName()); + } // else null libRef is OK for global lookup + if( DEBUG || debug ) { + System.err.println("DynamicLinkerImpl.closeLibrary(0x"+Long.toHexString(libraryHandle)+" -> "+libRef+")"); + } + if( 0 != libraryHandle ) { + closeLibraryImpl(libraryHandle); + } + } + protected abstract void closeLibraryImpl(final long libraryHandle) throws SecurityException; + + private static final HashMap<Long,Object> libHandle2Name = new HashMap<Long,Object>( 16 /* initialCapacity */ ); + + static final class LibRef { + LibRef(final String name) { + this.name = name; + this.refCount = 1; + } + final int incrRefCount() { return ++refCount; } + final int decrRefCount() { return --refCount; } + final int getRefCount() { return refCount; } + + final String getName() { return name; } + @Override + public final String toString() { return "LibRef["+name+", refCount "+refCount+"]"; } + + private final String name; + private int refCount; + } + + private final LibRef getLibRef(final long handle) { + synchronized( libHandle2Name ) { + return (LibRef) libHandle2Name.get(handle); + } + } + + private final LibRef incrLibRefCount(final long handle, final String libName) { + synchronized( libHandle2Name ) { + LibRef libRef = getLibRef(handle); + if( null == libRef ) { + libRef = new LibRef(libName); + libHandle2Name.put(handle, libRef); + } else { + libRef.incrRefCount(); + } + if(DEBUG) { + System.err.println("DynamicLinkerImpl.incrLibRefCount 0x"+Long.toHexString(handle)+ " -> "+libRef+", libs loaded "+libHandle2Name.size()); + } + return libRef; + } + } + + private final LibRef decrLibRefCount(final long handle) { + synchronized( libHandle2Name ) { + final LibRef libRef = getLibRef(handle); + if( null != libRef ) { + if( 0 == libRef.decrRefCount() ) { + libHandle2Name.remove(handle); + } + } + if(DEBUG) { + System.err.println("DynamicLinkerImpl.decrLibRefCount 0x"+Long.toHexString(handle)+ " -> "+libRef+", libs loaded "+libHandle2Name.size()); + } + return libRef; + } + } +} diff --git a/java_jni/jau/sys/dl/MacOSXDynamicLinkerImpl.java b/java_jni/jau/sys/dl/MacOSXDynamicLinkerImpl.java new file mode 100644 index 0000000..68c70da --- /dev/null +++ b/java_jni/jau/sys/dl/MacOSXDynamicLinkerImpl.java @@ -0,0 +1,57 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +/** + * Mac OS X specialization of {@link UnixDynamicLinkerImpl} + * utilizing OS X 's non POSIX flags and mode values. + */ +public final class MacOSXDynamicLinkerImpl extends UnixDynamicLinkerImpl { + + private static final long RTLD_DEFAULT = -2L; + // static final long RTLD_NEXT = -1L; + + private static final int RTLD_LAZY = 0x00001; + // static final int RTLD_NOW = 0x00002; + private static final int RTLD_LOCAL = 0x00004; + private static final int RTLD_GLOBAL = 0x00008; + + @Override + protected final long openLibraryLocalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_LOCAL); + } + + @Override + protected final long openLibraryGlobalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_GLOBAL); + } + + @Override + protected final long lookupSymbolGlobalImpl(final String symbolName) throws SecurityException { + return dlsym(RTLD_DEFAULT, symbolName); + } + +} diff --git a/java_jni/jau/sys/dl/PosixDynamicLinkerImpl.java b/java_jni/jau/sys/dl/PosixDynamicLinkerImpl.java new file mode 100644 index 0000000..438fa9a --- /dev/null +++ b/java_jni/jau/sys/dl/PosixDynamicLinkerImpl.java @@ -0,0 +1,52 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +public final class PosixDynamicLinkerImpl extends UnixDynamicLinkerImpl { + + private static final long RTLD_DEFAULT = 0; + // private static final long RTLD_NEXT = -1L; + + private static final int RTLD_LAZY = 0x00001; + // private static final int RTLD_NOW = 0x00002; + private static final int RTLD_LOCAL = 0x00000; + private static final int RTLD_GLOBAL = 0x00100; + + @Override + protected final long openLibraryLocalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_LOCAL); + } + + @Override + protected final long openLibraryGlobalImpl(final String pathname) throws SecurityException { + return dlopen(pathname, RTLD_LAZY | RTLD_GLOBAL); + } + + @Override + protected final long lookupSymbolGlobalImpl(final String symbolName) throws SecurityException { + return dlsym(RTLD_DEFAULT, symbolName); + } +} diff --git a/java_jni/jau/sys/dl/UnixDynamicLinkerImpl.java b/java_jni/jau/sys/dl/UnixDynamicLinkerImpl.java new file mode 100644 index 0000000..fb25782 --- /dev/null +++ b/java_jni/jau/sys/dl/UnixDynamicLinkerImpl.java @@ -0,0 +1,64 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +/* pp */ abstract class UnixDynamicLinkerImpl extends DynamicLinkerImpl { + + // + // Package private scope of class w/ protected native code access + // and sealed jogamp.common.* package definition + // ensuring no abuse via subclassing. + // + + /** Interface to C language function: <br> <code> int dlclose(void * ); </code> */ + protected static native int dlclose(long arg0); + + /** Interface to C language function: <br> <code> char * dlerror(void); </code> */ + protected static native java.lang.String dlerror(); + + /** Interface to C language function: <br> <code> void * dlopen(const char * , int); </code> */ + protected static native long dlopen(java.lang.String arg0, int arg1); + + /** Interface to C language function: <br> <code> void * dlsym(void * , const char * ); </code> */ + protected static native long dlsym(long arg0, java.lang.String arg1); + + @Override + protected final long lookupSymbolLocalImpl(final long libraryHandle, final String symbolName) throws SecurityException { + return 0 != libraryHandle ? dlsym(libraryHandle, symbolName) : 0; + } + + @Override + protected final void closeLibraryImpl(final long libraryHandle) throws SecurityException { + if( 0 != libraryHandle ) { + dlclose(libraryHandle); + } + } + + @Override + public final String getLastError() { + return dlerror(); + } +} diff --git a/java_jni/jau/sys/dl/WindowsDynamicLinkerImpl.java b/java_jni/jau/sys/dl/WindowsDynamicLinkerImpl.java new file mode 100644 index 0000000..9a4c8a5 --- /dev/null +++ b/java_jni/jau/sys/dl/WindowsDynamicLinkerImpl.java @@ -0,0 +1,92 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.sys.dl; + +public final class WindowsDynamicLinkerImpl extends DynamicLinkerImpl { + + /** Interface to C language function: <br> <code> BOOL FreeLibrary(HANDLE hLibModule); </code> */ + private static native int FreeLibrary(long hLibModule); + + /** Interface to C language function: <br> <code> DWORD GetLastError(void); </code> */ + private static native int GetLastError(); + + /** Interface to C language function: <br> <code> PROC GetProcAddressA(HANDLE hModule, LPCSTR lpProcName); </code> */ + private static native long GetProcAddressA(long hModule, java.lang.String lpProcName); + + /** Interface to C language function: <br> <code> HANDLE LoadLibraryW(LPCWSTR lpLibFileName); </code> */ + private static native long LoadLibraryW(java.lang.String lpLibFileName); + + @Override + protected final long openLibraryLocalImpl(final String libraryName) throws SecurityException { + // How does that work under Windows ? + // Don't know .. so it's an alias to global, for the time being + return LoadLibraryW(libraryName); + } + + @Override + protected final long openLibraryGlobalImpl(final String libraryName) throws SecurityException { + return LoadLibraryW(libraryName); + } + + @Override + protected final long lookupSymbolGlobalImpl(final String symbolName) throws SecurityException { + if(DEBUG_LOOKUP) { + System.err.println("lookupSymbolGlobal: Not supported on Windows"); + } + // allow DynamicLibraryBundle to continue w/ local libs + return 0; + } + + private static final int symbolArgAlignment=4; // 4 byte alignment of each argument + private static final int symbolMaxArguments=12; // experience .. + + @Override + protected final long lookupSymbolLocalImpl(final long libraryHandle, final String symbolName) throws IllegalArgumentException { + String _symbolName = symbolName; + long addr = GetProcAddressA(libraryHandle, _symbolName); + if( 0 == addr ) { + // __stdcall hack: try some @nn decorations, + // the leading '_' must not be added (same with cdecl) + for(int arg=0; 0==addr && arg<=symbolMaxArguments; arg++) { + _symbolName = symbolName+"@"+(arg*symbolArgAlignment); + addr = GetProcAddressA(libraryHandle, _symbolName); + } + } + return addr; + } + + @Override + protected final void closeLibraryImpl(final long libraryHandle) throws IllegalArgumentException { + FreeLibrary(libraryHandle); + } + + @Override + public final String getLastError() { + final int err = GetLastError(); + return "Last error: 0x"+Integer.toHexString(err)+" ("+err+")"; + } + +} diff --git a/java_jni/jni/CMakeLists.txt b/java_jni/jni/CMakeLists.txt new file mode 100644 index 0000000..5a0065c --- /dev/null +++ b/java_jni/jni/CMakeLists.txt @@ -0,0 +1,42 @@ +find_package(JNI REQUIRED) + +set (jaulib_base_LIB_INCLUDE_DIRS + ${PROJECT_SOURCE_DIR}/include +) + +include_directories( + ${JNI_INCLUDE_DIRS} + ${jaulib_base_LIB_INCLUDE_DIRS} + ${JNI_HEADER_PATH} +) + +set (jaulib_jni_JNI_SRCS + ${PROJECT_SOURCE_DIR}/java_jni/jni/jni_mem.cxx + ${PROJECT_SOURCE_DIR}/java_jni/jni/helper_jni.cxx + ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/JVM_JNI8.cxx + ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/MachineDataInfoRuntime.cxx + ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/Clock.cxx +) + +if(WIN32) + set (jaulib_jni_JNI_SRCS ${jaulib_jni_JNI_SRCS} ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/WindowsDynamicLinkerImpl_JNI.cxx) +else() + set (jaulib_jni_JNI_SRCS ${jaulib_jni_JNI_SRCS} ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/UnixDynamicLinkerImpl_JNI.cxx) +endif() + +set (CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed") + +add_library (jaulib_jni_jni SHARED ${jaulib_jni_JNI_SRCS}) +target_link_libraries(jaulib_jni_jni ${JNI_LIBRARIES} jaulib) + +set_target_properties( + jaulib_jni_jni + PROPERTIES + SOVERSION ${jaulib_VERSION_MAJOR} + VERSION ${jaulib_VERSION_STRING} +) + +install(TARGETS jaulib_jni_jni LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +add_dependencies(jaulib_jni_jni jaulib jaulib_base_jar jaulib_jni_jar) + diff --git a/java/jni/helper_jni.cxx b/java_jni/jni/helper_jni.cxx index e083f8b..e083f8b 100644 --- a/java/jni/helper_jni.cxx +++ b/java_jni/jni/helper_jni.cxx diff --git a/java/jni/jau/Environment.cxx b/java_jni/jni/jau/Clock.cxx index 89abe89..06afa03 100644 --- a/java/jni/jau/Environment.cxx +++ b/java_jni/jni/jau/Clock.cxx @@ -23,7 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "org_jau_Environment.h" +#include "org_jau_sys_Clock.h" #include <cstdint> #include <cinttypes> @@ -32,7 +32,7 @@ #include <jau/environment.hpp> -#include "helper_base.hpp" +#include "jau/jni/helper_jni.hpp" static const int64_t NanoPerMilli = 1000000L; @@ -46,7 +46,7 @@ static const int64_t MilliPerOne = 1000L; * clock_gettime seems to be well supported at least on kernel >= 4.4. * Only bfin and sh are missing, while ia64 seems to be complicated. */ -jlong Java_org_jau_Environment_currentTimeMillis(JNIEnv *env, jclass clazz) { +jlong Java_org_jau_sys_Clock_currentTimeMillis(JNIEnv *env, jclass clazz) { (void)env; (void)clazz; @@ -56,7 +56,7 @@ jlong Java_org_jau_Environment_currentTimeMillis(JNIEnv *env, jclass clazz) { return (jlong)res; } -jlong Java_org_jau_Environment_startupTimeMillisImpl(JNIEnv *env, jclass clazz) { +jlong Java_org_jau_sys_Clock_startupTimeMillisImpl(JNIEnv *env, jclass clazz) { (void)env; (void)clazz; diff --git a/java_jni/jni/jau/JVM_JNI8.cxx b/java_jni/jni/jau/JVM_JNI8.cxx new file mode 100644 index 0000000..f6487e8 --- /dev/null +++ b/java_jni/jni/jau/JVM_JNI8.cxx @@ -0,0 +1,46 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +#include <stdio.h> //required by android to identify NULL +#include <jni.h> + +#if defined (JNI_VERSION_1_8) + +JNIEXPORT jint JNICALL JNI_OnLoad_jaulib_jni_jni(JavaVM *vm, void *reserved) { + (void)vm; + (void)reserved; + return JNI_VERSION_1_8; +} + +JNIEXPORT void JNICALL JNI_OnUnload_jaulib_jni_jni(JavaVM *vm, void *reserved) { + (void)vm; + (void)reserved; +} + +#endif /* defined (JNI_VERSION_1_8) */ + diff --git a/java_jni/jni/jau/MachineDataInfoRuntime.cxx b/java_jni/jni/jau/MachineDataInfoRuntime.cxx new file mode 100644 index 0000000..98ee97a --- /dev/null +++ b/java_jni/jni/jau/MachineDataInfoRuntime.cxx @@ -0,0 +1,211 @@ + +#include <jni.h> + +#include <assert.h> + +#include "jau_sys_MachineDataInfoRuntime.h" + +#include "jau/jni/helper_jni.hpp" + +#if defined(_WIN32) + #include <windows.h> +#else /* assume POSIX sysconf() availability */ + #include <unistd.h> +#endif + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getPointerSizeInBytesImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return sizeof(void *); +} + +JNIEXPORT jlong JNICALL +Java_jau_sys_MachineDataInfoRuntime_getPageSizeInBytesImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + +#if defined(_WIN32) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (jlong) si.dwPageSize; +#else + return (jlong) sysconf(_SC_PAGESIZE); +#endif +} + +typedef struct { + int8_t c1; + int8_t v; +} struct_alignment_int8; + +typedef struct { + int8_t c1; + int16_t v; +} struct_alignment_int16; + +typedef struct { + int8_t c1; + int32_t v; +} struct_alignment_int32; + +typedef struct { + int8_t c1; + int64_t v; +} struct_alignment_int64; + +typedef struct { + int8_t c1; + int v; +} struct_alignment_int; + +typedef struct { + int8_t c1; + long v; +} struct_alignment_long; + +typedef struct { + int8_t c1; + void * v; +} struct_alignment_pointer; + +typedef struct { + int8_t c1; + float v; +} struct_alignment_float; + +typedef struct { + int8_t c1; + double v; +} struct_alignment_double; + +typedef struct { + int8_t c1; + long double v; +} struct_alignment_ldouble; + +// size_t padding(size_t totalsize, size_t typesize) { return totalsize - typesize - sizeof(char); } +// static size_t alignment(size_t totalsize, size_t typesize) { return totalsize - typesize; } +#define ALIGNMENT(a, b) ( (a) - (b) ) + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentInt8Impl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_int8 ), sizeof(int8_t)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentInt16Impl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_int16 ), sizeof(int16_t)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentInt32Impl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_int32 ), sizeof(int32_t)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentInt64Impl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_int64 ), sizeof(int64_t)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentIntImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_int ), sizeof(int)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentLongImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_long ), sizeof(long)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentPointerImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_pointer ), sizeof(void *)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentFloatImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_float ), sizeof(float)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentDoubleImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_double ), sizeof(double)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getAlignmentLongDoubleImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return ALIGNMENT(sizeof( struct_alignment_ldouble ), sizeof(long double)); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getSizeOfIntImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return sizeof(int); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getSizeOfLongImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return sizeof(long); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getSizeOfFloatImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return sizeof(float); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getSizeOfDoubleImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return sizeof(double); +} + +JNIEXPORT jint JNICALL +Java_jau_sys_MachineDataInfoRuntime_getSizeOfLongDoubleImpl(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + return sizeof(long double); +} + diff --git a/java_jni/jni/jau/UnixDynamicLinkerImpl_JNI.cxx b/java_jni/jni/jau/UnixDynamicLinkerImpl_JNI.cxx new file mode 100644 index 0000000..9a7a57c --- /dev/null +++ b/java_jni/jni/jau/UnixDynamicLinkerImpl_JNI.cxx @@ -0,0 +1,138 @@ +/* !---- DO NOT EDIT: This file autogenerated by com\sun\gluegen\JavaEmitter.java on Mon Jul 31 16:26:59 PDT 2006 ----! */ + +#include <jni.h> + +#include <assert.h> + +#include "jau_sys_dl_UnixDynamicLinkerImpl.h" + + #include <dlfcn.h> + #include <inttypes.h> + +#ifndef RTLD_DEFAULT + #define RTLD_DEFAULT ((void *) 0) +#endif + +// #define DEBUG_DLOPEN 1 + +#ifdef DEBUG_DLOPEN + typedef void *(*DLOPEN_FPTR_TYPE)(const char *filename, int flag); + #define VERBOSE_ON 1 +#endif + +// #define VERBOSE_ON 1 + +#ifdef VERBOSE_ON + #ifdef ANDROID + #include <android/log.h> + #define DBG_PRINT(...) __android_log_print(ANDROID_LOG_DEBUG, "JogAmp", __VA_ARGS__) + #else + #define DBG_PRINT(...) fprintf(stderr, __VA_ARGS__); fflush(stderr) + #endif +#else + #define DBG_PRINT(...) +#endif + +/* + * Class: jau_sys_dl_UnixDynamicLinkerImpl + * Method: dlclose + * Signature: (J)I + */ +JNIEXPORT jint JNICALL +Java_jau_sys_dl_UnixDynamicLinkerImpl_dlclose(JNIEnv *env, jclass _unused, jlong arg0) { + (void)env; + (void)_unused; + + int _res; + _res = dlclose((void *) (intptr_t) arg0); + return _res; +} + + +/* + * Class: jau_sys_dl_UnixDynamicLinkerImpl + * Method: dlerror + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL +Java_jau_sys_dl_UnixDynamicLinkerImpl_dlerror(JNIEnv *env, jclass _unused) { + (void)_unused; + + char * _res; + _res = dlerror(); + if (_res == NULL) return NULL; return env->NewStringUTF(_res); +} + +/* + * Class: jau_sys_dl_UnixDynamicLinkerImpl + * Method: dlopen + * Signature: (Ljava/lang/String;I)J + */ +JNIEXPORT jlong JNICALL +Java_jau_sys_dl_UnixDynamicLinkerImpl_dlopen(JNIEnv *env, jclass _unused, jstring arg0, jint arg1) { + (void)_unused; + + const char* _UTF8arg0 = NULL; + void * _res; +#ifdef DEBUG_DLOPEN + DLOPEN_FPTR_TYPE dlopenFunc = NULL; + DBG_PRINT("XXX dlopen.0\n"); +#endif + + if (arg0 != NULL) { + if (arg0 != NULL) { + _UTF8arg0 = env->GetStringUTFChars(arg0, (jboolean*)NULL); + if (_UTF8arg0 == NULL) { + env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), + "Failed to get UTF-8 chars for argument \"arg0\" in native dispatcher for \"dlopen\""); + return 0; + } + } + } +#ifdef DEBUG_DLOPEN + dlopenFunc = (DLOPEN_FPTR_TYPE) dlsym(RTLD_DEFAULT, "dlopen"); + DBG_PRINT("XXX dlopen.1: lib %s, dlopen-fptr %p %p (%d)\n", _UTF8arg0, dlopen, dlopenFunc, dlopen==dlopenFunc); + _res = dlopen((char *) _UTF8arg0, (int) arg1); + DBG_PRINT("XXX dlopen.2: %p\n", _res); +#else + _res = dlopen((char *) _UTF8arg0, (int) arg1); +#endif + if (arg0 != NULL) { + env->ReleaseStringUTFChars(arg0, _UTF8arg0); + } +#ifdef DEBUG_DLOPEN + DBG_PRINT("XXX dlopen.X\n"); +#endif + return (jlong) (intptr_t) _res; +} + +/* + * Class: jau_sys_dl_UnixDynamicLinkerImpl + * Method: dlsym + * Signature: (JLjava/lang/String;)J + */ +JNIEXPORT jlong JNICALL +Java_jau_sys_dl_UnixDynamicLinkerImpl_dlsym(JNIEnv *env, jclass _unused, jlong arg0, jstring arg1) { + (void)_unused; + + const char* _UTF8arg1 = NULL; + void * _res; + if (arg1 != NULL) { + if (arg1 != NULL) { + _UTF8arg1 = env->GetStringUTFChars(arg1, (jboolean*)NULL); + if (_UTF8arg1 == NULL) { + env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), + "Failed to get UTF-8 chars for argument \"arg1\" in native dispatcher for \"dlsym\""); + return 0; + } + } + } + _res = dlsym((void *) (intptr_t) arg0, (char *) _UTF8arg1); + DBG_PRINT("XXX dlsym: handle %p, symbol %s -> %p\n", (void *) (intptr_t) arg0, _UTF8arg1, _res); + + if (arg1 != NULL) { + env->ReleaseStringUTFChars(arg1, _UTF8arg1); + } + return (jlong) (intptr_t) _res; +} + diff --git a/java_jni/jni/jau/WindowsDynamicLinkerImpl_JNI.cxx b/java_jni/jni/jau/WindowsDynamicLinkerImpl_JNI.cxx new file mode 100644 index 0000000..f9ee9a7 --- /dev/null +++ b/java_jni/jni/jau/WindowsDynamicLinkerImpl_JNI.cxx @@ -0,0 +1,107 @@ +/* !---- DO NOT EDIT: This file autogenerated by com\sun\gluegen\JavaEmitter.java on Tue May 27 02:37:55 PDT 2008 ----! */ + +#include <jni.h> +#include <stdlib.h> + +#include <assert.h> + +#include "jau_sys_dl_WindowsDynamicLinkerImpl.h" + + #include <windows.h> + /* This typedef is apparently needed for compilers before VC8, + and for the embedded ARM compilers we're using */ + #if !defined(__MINGW64__) && ( (_MSC_VER < 1400) || defined(UNDER_CE) ) + typedef int intptr_t; + #endif + /* GetProcAddress doesn't exist in A/W variants under desktop Windows */ + #ifndef UNDER_CE + #define GetProcAddressA GetProcAddress + #endif + +/* Java->C glue code: + * Java package: jogamp.common.os.WindowsDynamicLinkerImpl + * Java method: int FreeLibrary(long hLibModule) + * C function: BOOL FreeLibrary(HANDLE hLibModule); + */ +JNIEXPORT jint JNICALL +Java_jau_sys_dl_WindowsDynamicLinkerImpl_FreeLibrary(JNIEnv *env, jclass _unused, jlong hLibModule) { + (void)env; + (void)_unused; + + BOOL _res; + _res = FreeLibrary((HANDLE) (intptr_t) hLibModule); + return _res; +} + + +/* Java->C glue code: + * Java package: jogamp.common.os.WindowsDynamicLinkerImpl + * Java method: int GetLastError() + * C function: DWORD GetLastError(void); + */ +JNIEXPORT jint JNICALL +Java_jau_sys_dl_WindowsDynamicLinkerImpl_GetLastError(JNIEnv *env, jclass _unused) { + (void)env; + (void)_unused; + + DWORD _res; + _res = GetLastError(); + return _res; +} + + +/* Java->C glue code: + * Java package: jogamp.common.os.WindowsDynamicLinkerImpl + * Java method: long GetProcAddressA(long hModule, java.lang.String lpProcName) + * C function: PROC GetProcAddressA(HANDLE hModule, LPCSTR lpProcName); + */ +JNIEXPORT jlong JNICALL +Java_jau_sys_dl_WindowsDynamicLinkerImpl_GetProcAddressA(JNIEnv *env, jclass _unused, jlong hModule, jstring lpProcName) { + (void)_unused; + + const char* _strchars_lpProcName = NULL; + PROC _res; + if (lpProcName != NULL) { + _strchars_lpProcName = env->GetStringUTFChars(lpProcName, (jboolean*)NULL); + if (_strchars_lpProcName == NULL) { + env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), + "Failed to get UTF-8 chars for argument \"lpProcName\" in native dispatcher for \"GetProcAddressA\""); + return 0; + } + } + _res = GetProcAddressA((HANDLE) (intptr_t) hModule, (LPCSTR) _strchars_lpProcName); + if (lpProcName != NULL) { + env->ReleaseStringUTFChars(lpProcName, _strchars_lpProcName); + } + return (jlong) (intptr_t) _res; +} + + +/* Java->C glue code: + * Java package: jogamp.common.os.WindowsDynamicLinkerImpl + * Java method: long LoadLibraryW(java.lang.String lpLibFileName) + * C function: HANDLE LoadLibraryW(LPCWSTR lpLibFileName); + */ +JNIEXPORT jlong JNICALL +Java_jau_sys_dl_WindowsDynamicLinkerImpl_LoadLibraryW(JNIEnv *env, jclass _unused, jstring lpLibFileName) { + (void)_unused; + + jchar* _strchars_lpLibFileName = NULL; + HANDLE _res; + if (lpLibFileName != NULL) { + _strchars_lpLibFileName = (jchar *) calloc(env->GetStringLength(lpLibFileName) + 1, sizeof(jchar)); + if (_strchars_lpLibFileName == NULL) { + env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), + "Could not allocate temporary buffer for copying string argument \"lpLibFileName\" in native dispatcher for \"LoadLibraryW\""); + return 0; + } + env->GetStringRegion(lpLibFileName, 0, env->GetStringLength(lpLibFileName), _strchars_lpLibFileName); + } + _res = LoadLibraryW((LPCWSTR) _strchars_lpLibFileName); + if (lpLibFileName != NULL) { + free((void*) _strchars_lpLibFileName); + } + return (jlong) (intptr_t) _res; +} + + diff --git a/java/jni/jni_mem.cxx b/java_jni/jni/jni_mem.cxx index 981c6e5..981c6e5 100644 --- a/java/jni/jni_mem.cxx +++ b/java_jni/jni/jni_mem.cxx diff --git a/java_jni/manifest.txt.in b/java_jni/manifest.txt.in new file mode 100644 index 0000000..7f70c4c --- /dev/null +++ b/java_jni/manifest.txt.in @@ -0,0 +1,30 @@ +Manifest-Version: 1.0 +Bundle-Date: @BUILD_TSTAMP@ +Bundle-ManifestVersion: 2 +Bundle-Name: org.jau.sys.dl +Bundle-SymbolicName: org.jau.sys.dl +Bundle-Version: @VERSION_SHORT@ +Export-Package: org.jau.sys.dl +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.9))" +Package-Title: org.jau.sys.dl +Package-Vendor: Gothel Software +Package-Version: @VERSION_SHORT@ +Specification-Title: Jaulib JNI +Specification-Vendor: Gothel Software +Specification-Version: @VERSION_API@ +Implementation-Title: Jaulib JNI +Implementation-Vendor: Gothel Software +Implementation-Version: @VERSION@ +Implementation-Commit: @VERSION_SHA1@ +Implementation-URL: http://www.jausoft.com/ +Extension-Name: org.jau.sys.dl +Trusted-Library: true +Permissions: all-permissions +Application-Library-Allowable-Codebase: * + +Name: org/jau/sys/dl +Sealed: false + +Name: jau/sys/dl +Sealed: false + diff --git a/java/org/jau/sys/Environment.java b/java_jni/org/jau/sys/Clock.java index 2e4e68c..43beb63 100644 --- a/java/org/jau/sys/Environment.java +++ b/java_jni/org/jau/sys/Clock.java @@ -23,7 +23,7 @@ */ package org.jau.sys; -public class Environment { +public class Clock { private static long t0; static { t0 = startupTimeMillisImpl(); diff --git a/java_jni/org/jau/sys/dl/DynamicLibraryBundle.java b/java_jni/org/jau/sys/dl/DynamicLibraryBundle.java new file mode 100644 index 0000000..6a5eb0b --- /dev/null +++ b/java_jni/org/jau/sys/dl/DynamicLibraryBundle.java @@ -0,0 +1,422 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys.dl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.jau.sys.JNILibrary; +import org.jau.util.parallel.RunnableExecutor; + +/** + * Provides bundling of:<br> + * <ul> + * <li>The to-be-glued native library, eg OpenGL32.dll. From here on this is referred as the Tool.</li> + * <li>The JNI glue-code native library, eg jogl_desktop.dll. From here on this is referred as the Glue</li> + * </ul> + * <p> + * An {@link DynamicLibraryBundleInfo} instance is being passed in the constructor, + * providing the required information about the tool and glue libraries. + * The ClassLoader of it's implementation is also being used to help locating the native libraries. + * </p> + * An instance provides a complete {@link com.jogamp.common.os.DynamicLookupHelper} + * to {@link com.jogamp.gluegen.runtime.ProcAddressTable#reset(com.jogamp.common.os.DynamicLookupHelper) reset} + * the {@link com.jogamp.gluegen.runtime.ProcAddressTable}.<br> + * At construction, it: + * <ul> + * <li> loads the Tool native library via + * {@link com.jogamp.common.os.NativeLibrary#open(java.lang.String, java.lang.ClassLoader, boolean) NativeLibrary's open method}</li> + * <li> loads the {@link org.jau.sys.JNIJarLibrary.common.jvm.JNILibLoaderBase#loadLibrary(java.lang.String, java.lang.String[], boolean, ClassLoader) Glue native library}</li> + * <li> resolves the Tool's {@link com.jogamp.common.os.DynamicLibraryBundleInfo#getToolGetProcAddressFuncNameList() GetProcAddress}. (optional)</li> + * </ul> + */ +public class DynamicLibraryBundle implements DynamicLookupHelper { + private final DynamicLibraryBundleInfo info; + + protected final List<NativeLibrary> nativeLibraries; + private final DynamicLinker dynLinkGlobal; + private final List<List<String>> toolLibNames; + private final List<String> glueLibNames; + private final boolean[] toolLibLoaded; + + private int toolLibLoadedNumber; + + private final boolean[] glueLibLoaded; + private int glueLibLoadedNumber; + + private long toolGetProcAddressHandle; + private boolean toolGetProcAddressComplete; + private HashSet<String> toolGetProcAddressFuncNameSet; + private final List<String> toolGetProcAddressFuncNameList; + + /** Returns the default {@link RunnableExecutor#currentThreadExecutor}. */ + public static RunnableExecutor getDefaultRunnableExecutor() { + return RunnableExecutor.currentThreadExecutor; + } + + /** + * Instantiates and loads all {@link NativeLibrary}s incl. JNI libraries. + * <p> + * The ClassLoader of the {@link DynamicLibraryBundleInfo} implementation class + * is being used to help locating the native libraries. + * </p> + */ + public DynamicLibraryBundle(final DynamicLibraryBundleInfo info) { + if(null==info) { + throw new RuntimeException("Null DynamicLibraryBundleInfo"); + } + this.info = info; + if(DEBUG) { + System.err.println(Thread.currentThread().getName()+" - DynamicLibraryBundle.init start with: "+info.getClass().getName()); + } + nativeLibraries = new ArrayList<NativeLibrary>(); + toolLibNames = info.getToolLibNames(); + glueLibNames = info.getGlueLibNames(); + toolLibLoaded = new boolean[toolLibNames.size()]; + if(DEBUG) { + if( toolLibNames.size() == 0 ) { + System.err.println("No Tool native library names given"); + } + + if( glueLibNames.size() == 0 ) { + System.err.println("No Glue native library names given"); + } + } + + for(int i=toolLibNames.size()-1; i>=0; i--) { + toolLibLoaded[i] = false; + } + glueLibLoaded = new boolean[glueLibNames.size()]; + for(int i=glueLibNames.size()-1; i>=0; i--) { + glueLibLoaded[i] = false; + } + + { + final DynamicLinker[] _dynLinkGlobal = { null }; + info.getLibLoaderExecutor().invoke(true, new Runnable() { + @Override + public void run() { + _dynLinkGlobal[0] = loadLibraries(); + } } ) ; + dynLinkGlobal = _dynLinkGlobal[0]; + } + + toolGetProcAddressFuncNameList = info.getToolGetProcAddressFuncNameList(); + if( null != toolGetProcAddressFuncNameList ) { + toolGetProcAddressFuncNameSet = new HashSet<String>(toolGetProcAddressFuncNameList); + toolGetProcAddressHandle = getToolGetProcAddressHandle(); + toolGetProcAddressComplete = 0 != toolGetProcAddressHandle; + } else { + toolGetProcAddressFuncNameSet = new HashSet<String>(); + toolGetProcAddressHandle = 0; + toolGetProcAddressComplete = true; + } + if(DEBUG) { + System.err.println("DynamicLibraryBundle.init Summary: "+info.getClass().getName()); + System.err.println(" toolGetProcAddressFuncNameList: "+toolGetProcAddressFuncNameList+", complete: "+toolGetProcAddressComplete+", 0x"+Long.toHexString(toolGetProcAddressHandle)); + System.err.println(" Tool Lib Names : "+toolLibNames); + System.err.println(" Tool Lib Loaded: "+getToolLibLoadedNumber()+"/"+getToolLibNumber()+" "+Arrays.toString(toolLibLoaded)+", complete "+isToolLibComplete()); + System.err.println(" Glue Lib Names : "+glueLibNames); + System.err.println(" Glue Lib Loaded: "+getGlueLibLoadedNumber()+"/"+getGlueLibNumber()+" "+Arrays.toString(glueLibLoaded)+", complete "+isGlueLibComplete()); + System.err.println(" All Complete: "+isLibComplete()); + System.err.println(" LibLoaderExecutor: "+info.getLibLoaderExecutor().getClass().getName()); + } + } + + /** Unload all {@link NativeLibrary}s, and remove all references. */ + public final void destroy() { + if(DEBUG) { + System.err.println(Thread.currentThread().getName()+" - DynamicLibraryBundle.destroy() START: "+info.getClass().getName()); + } + toolGetProcAddressFuncNameSet = null; + toolGetProcAddressHandle = 0; + toolGetProcAddressComplete = false; + for(int i = 0; i<nativeLibraries.size(); i++) { + nativeLibraries.get(i).close(); + } + nativeLibraries.clear(); + toolLibNames.clear(); + glueLibNames.clear(); + if(DEBUG) { + System.err.println(Thread.currentThread().getName()+" - DynamicLibraryBundle.destroy() END: "+info.getClass().getName()); + } + } + + public final boolean isLibComplete() { + return isToolLibComplete() && isGlueLibComplete() ; + } + + public final int getToolLibNumber() { + return toolLibNames.size(); + } + + public final int getToolLibLoadedNumber() { + return toolLibLoadedNumber; + } + + /** + * @return true if all tool libraries are loaded, + * otherwise false. + * + * @see DynamicLibraryBundleInfo#getToolLibNames() + */ + public final boolean isToolLibComplete() { + final int toolLibNumber = getToolLibNumber(); + return toolGetProcAddressComplete && + ( 0 == toolLibNumber || null != dynLinkGlobal ) && + toolLibNumber == getToolLibLoadedNumber(); + } + + public final boolean isToolLibLoaded() { + return 0 < toolLibLoadedNumber; + } + + public final boolean isToolLibLoaded(final int i) { + if(0 <= i && i < toolLibLoaded.length) { + return toolLibLoaded[i]; + } + return false; + } + + public final int getGlueLibNumber() { + return glueLibNames.size(); + } + + public final int getGlueLibLoadedNumber() { + return glueLibLoadedNumber; + } + + /** + * @return true if the last entry has been loaded, + * while ignoring the preload dependencies. + * Otherwise false. + * + * @see DynamicLibraryBundleInfo#getGlueLibNames() + */ + public final boolean isGlueLibComplete() { + return 0 == getGlueLibNumber() || isGlueLibLoaded(getGlueLibNumber() - 1); + } + + public final boolean isGlueLibLoaded(final int i) { + if(0 <= i && i < glueLibLoaded.length) { + return glueLibLoaded[i]; + } + return false; + } + + public final DynamicLibraryBundleInfo getBundleInfo() { return info; } + + protected final long getToolGetProcAddressHandle() throws SecurityException { + if(!isToolLibLoaded()) { + return 0; + } + long aptr = 0; + for (int i=0; i < toolGetProcAddressFuncNameList.size(); i++) { + final String name = toolGetProcAddressFuncNameList.get(i); + aptr = dynamicLookupFunctionOnLibs(name); + if(DEBUG) { + System.err.println("getToolGetProcAddressHandle: "+name+" -> 0x"+Long.toHexString(aptr)); + } + } + return aptr; + } + + protected static final NativeLibrary loadFirstAvailable(final List<String> libNames, + final boolean searchSystemPath, + final boolean searchSystemPathFirst, + final ClassLoader loader, final boolean global) throws SecurityException { + for (int i=0; i < libNames.size(); i++) { + final NativeLibrary lib = NativeLibrary.open(libNames.get(i), searchSystemPath, searchSystemPathFirst, loader, global); + if (lib != null) { + return lib; + } + } + return null; + } + + final DynamicLinker loadLibraries() throws SecurityException { + int i; + toolLibLoadedNumber = 0; + final ClassLoader cl = info.getClass().getClassLoader(); + NativeLibrary lib = null; + DynamicLinker dynLinkGlobal = null; + + for (i=0; i < toolLibNames.size(); i++) { + final List<String> libNames = toolLibNames.get(i); + if( null != libNames && libNames.size() > 0 ) { + lib = loadFirstAvailable(libNames, + info.searchToolLibInSystemPath(), + info.searchToolLibSystemPathFirst(), + cl, info.shallLinkGlobal()); + if ( null == lib ) { + if(DEBUG) { + System.err.println("Unable to load any Tool library of: "+libNames); + } + } else { + if( null == dynLinkGlobal ) { + dynLinkGlobal = lib.dynamicLinker(); + } + nativeLibraries.add(lib); + toolLibLoaded[i]=true; + toolLibLoadedNumber++; + if(DEBUG) { + System.err.println("Loaded Tool library: "+lib); + } + } + } + } + if( toolLibNames.size() > 0 && !isToolLibLoaded() ) { + if(DEBUG) { + System.err.println("No Tool libraries loaded"); + } + return dynLinkGlobal; + } + + glueLibLoadedNumber = 0; + for (i=0; i < glueLibNames.size(); i++) { + final String libName = glueLibNames.get(i); + final boolean ignoreError = true; + boolean res; + try { + res = JNILibrary.loadLibrary(libName, ignoreError, cl); + if(DEBUG && !res) { + System.err.println("Info: Could not load JNI/Glue library: "+libName); + } + } catch (final UnsatisfiedLinkError e) { + res = false; + if(DEBUG) { + System.err.println("Unable to load JNI/Glue library: "+libName); + e.printStackTrace(); + } + } + glueLibLoaded[i] = res; + if(res) { + glueLibLoadedNumber++; + } + } + + return dynLinkGlobal; + } + + /** + * @param funcName + * @return + * @throws SecurityException if user is not granted access for the library set. + */ + private final long dynamicLookupFunctionOnLibs(final String funcName) throws SecurityException { + if(!isToolLibLoaded() || null==funcName) { + if(DEBUG_LOOKUP && !isToolLibLoaded()) { + System.err.println("Lookup-Native: <" + funcName + "> ** FAILED ** Tool native library not loaded"); + } + return 0; + } + long addr = 0; + NativeLibrary lib = null; + + if( info.shallLookupGlobal() ) { + // Try a global symbol lookup first .. + // addr = NativeLibrary.dynamicLookupFunctionGlobal(funcName); + addr = dynLinkGlobal.lookupSymbolGlobal(funcName); + } + // Look up this function name in all known libraries + for (int i=0; 0==addr && i < nativeLibraries.size(); i++) { + lib = nativeLibraries.get(i); + addr = lib.dynamicLookupFunction(funcName); + } + if(DEBUG_LOOKUP) { + final String libName = ( null == lib ) ? "GLOBAL" : lib.toString(); + if(0!=addr) { + System.err.println("Lookup-Native: <" + funcName + "> 0x" + Long.toHexString(addr) + " in lib " + libName ); + } else { + System.err.println("Lookup-Native: <" + funcName + "> ** FAILED ** in libs " + nativeLibraries); + } + } + return addr; + } + + private final long toolDynamicLookupFunction(final String funcName) { + if(0 != toolGetProcAddressHandle) { + final long addr = info.toolGetProcAddress(toolGetProcAddressHandle, funcName); + if(DEBUG_LOOKUP) { + if(0!=addr) { + System.err.println("Lookup-Tool: <"+funcName+"> 0x"+Long.toHexString(addr)+", via tool 0x"+Long.toHexString(toolGetProcAddressHandle)); + } + } + return addr; + } + return 0; + } + + @Override + public final void claimAllLinkPermission() throws SecurityException { + for (int i=0; i < nativeLibraries.size(); i++) { + nativeLibraries.get(i).claimAllLinkPermission(); + } + } + @Override + public final void releaseAllLinkPermission() throws SecurityException { + for (int i=0; i < nativeLibraries.size(); i++) { + nativeLibraries.get(i).releaseAllLinkPermission(); + } + } + + @Override + public final long dynamicLookupFunction(final String funcName) throws SecurityException { + if(!isToolLibLoaded() || null==funcName) { + if(DEBUG_LOOKUP && !isToolLibLoaded()) { + System.err.println("Lookup: <" + funcName + "> ** FAILED ** Tool native library not loaded"); + } + return 0; + } + + if(toolGetProcAddressFuncNameSet.contains(funcName)) { + return toolGetProcAddressHandle; + } + + long addr = 0; + final boolean useToolGetProcAdressFirst = info.useToolGetProcAdressFirst(funcName); + + if(useToolGetProcAdressFirst) { + addr = toolDynamicLookupFunction(funcName); + } + if(0==addr) { + addr = dynamicLookupFunctionOnLibs(funcName); + } + if(0==addr && !useToolGetProcAdressFirst) { + addr = toolDynamicLookupFunction(funcName); + } + return addr; + } + + @Override + public final boolean isFunctionAvailable(final String funcName) throws SecurityException { + return 0 != dynamicLookupFunction(funcName); + } +} + diff --git a/java_jni/org/jau/sys/dl/DynamicLibraryBundleInfo.java b/java_jni/org/jau/sys/dl/DynamicLibraryBundleInfo.java new file mode 100644 index 0000000..a66168f --- /dev/null +++ b/java_jni/org/jau/sys/dl/DynamicLibraryBundleInfo.java @@ -0,0 +1,128 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys.dl; + +import java.util.List; + +import org.jau.util.parallel.RunnableExecutor; + +public interface DynamicLibraryBundleInfo { + public static final boolean DEBUG = DynamicLibraryBundle.DEBUG; + + /** + * Returns {@code true} if tool libraries shall be searched in the system path <i>(default)</i>, otherwise {@code false}. + * @since 2.4.0 + */ + public boolean searchToolLibInSystemPath(); + + /** + * Returns {@code true} if system path shall be searched <i>first</i> <i>(default)</i>, rather than searching it last. + * <p> + * If {@link #searchToolLibInSystemPath()} is {@code false} the return value is ignored. + * </p> + * @since 2.4.0 + */ + public boolean searchToolLibSystemPathFirst(); + + /** + * If a {@link SecurityManager} is installed, user needs link permissions + * for the named libraries. + * + * @return a list of Tool library names or alternative library name lists.<br> + * <ul> + * <li>GL/GLU example Unix: [ [ "libGL.so.1", "libGL.so", "GL" ], [ "libGLU.so", "GLU" ] ] </li> + * <li>GL/GLU example Windows: [ "OpenGL32", "GLU32" ] </li> + * <li>Cg/CgGL example: [ [ "libCg.so", "Cg" ], [ "libCgGL.so", "CgGL" ] ] </li> + * </pre> + */ + public List<List<String>> getToolLibNames(); + + /** + * If a {@link SecurityManager} is installed, user needs link permissions + * for the named libraries. + * + * @return a list of Glue library names.<br> + * <ul> + * <li>GL: [ "nativewindow_x11", "jogl_gl2es12", "jogl_desktop" ] </li> + * <li>NEWT: [ "nativewindow_x11", "newt" ] </li> + * <li>Cg: [ "nativewindow_x11", "jogl_cg" ] </li> + * </ul><br> + * Only the last entry is crucial, ie all other are optional preload dependencies and may generate errors, + * which are ignored. + */ + public List<String> getGlueLibNames(); + + /** + * May return the native libraries <pre>GetProcAddressFunc</pre> names, the first found function is being used.<br> + * This could be eg: <pre> glXGetProcAddressARB, glXGetProcAddressARB </pre>.<br> + * If your Tool does not has this facility, just return null. + * @see #toolGetProcAddress(long, String) + */ + public List<String> getToolGetProcAddressFuncNameList() ; + + /** + * May implement the lookup function using the Tools facility.<br> + * The actual function pointer is provided to allow proper bootstrapping of the ProcAddressTable, + * using one of the provided function names by {@link #getToolGetProcAddressFuncNameList()}.<br> + */ + public long toolGetProcAddress(long toolGetProcAddressHandle, String funcName); + + /** + * @param funcName + * @return true if {@link #toolGetProcAddress(long, String)} shall be tried before + * the system loader for the given function lookup. Otherwise false. + * Default is <b>true</b>. + */ + public boolean useToolGetProcAdressFirst(String funcName); + + /** @return true if the native library symbols shall be made available for symbol resolution of subsequently loaded libraries. */ + public boolean shallLinkGlobal(); + + /** + * If method returns <code>true</code> <i>and</i> if a {@link SecurityManager} is installed, user needs link permissions + * for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>! + * + * @return true if the dynamic symbol lookup shall happen system wide, over all loaded libraries. + * Otherwise only the loaded native libraries are used for lookup, which shall be the default. + */ + public boolean shallLookupGlobal(); + + /** + * Returns a suitable {@link RunnableExecutor} implementation, which is being used + * to load the <code>tool</code> and <code>glue</code> native libraries. + * <p> + * This allows the generic {@link DynamicLibraryBundle} implementation to + * load the native libraries on a designated thread. + * </p> + * <p> + * An implementation may return {@link DynamicLibraryBundle#getDefaultRunnableExecutor()}. + * </p> + */ + public RunnableExecutor getLibLoaderExecutor(); +} + + diff --git a/java_jni/org/jau/sys/dl/DynamicLinker.java b/java_jni/org/jau/sys/dl/DynamicLinker.java new file mode 100644 index 0000000..2bba77b --- /dev/null +++ b/java_jni/org/jau/sys/dl/DynamicLinker.java @@ -0,0 +1,113 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys.dl; + +/** Low level secure dynamic linker access. */ +public interface DynamicLinker { + public static final boolean DEBUG = NativeLibrary.DEBUG; + public static final boolean DEBUG_LOOKUP = NativeLibrary.DEBUG_LOOKUP; + + /** + * @throws SecurityException if user is not granted global access + */ + public void claimAllLinkPermission() throws SecurityException; + + /** + * @throws SecurityException if user is not granted global access + */ + public void releaseAllLinkPermission() throws SecurityException; + + /** + * If a {@link SecurityManager} is installed, user needs link permissions + * for the named library. + * <p> + * Opens the named library, allowing system wide access for other <i>users</i>. + * </p> + * + * @param pathname the full pathname for the library to open + * @param debug set to true to enable debugging + * @return the library handle, maybe 0 if not found. + * @throws SecurityException if user is not granted access for the named library. + */ + public long openLibraryGlobal(String pathname, boolean debug) throws SecurityException; + + /** + * If a {@link SecurityManager} is installed, user needs link permissions + * for the named library. + * <p> + * Opens the named library, restricting access to this process. + * </p> + * + * @param pathname the full pathname for the library to open + * @param debug set to true to enable debugging + * @return the library handle, maybe 0 if not found. + * @throws SecurityException if user is not granted access for the named library. + */ + public long openLibraryLocal(String pathname, boolean debug) throws SecurityException; + + /** + * If a {@link SecurityManager} is installed, user needs link permissions + * for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>! + * + * @param symbolName global symbol name to lookup up system wide. + * @return the library handle, maybe 0 if not found. + * @throws SecurityException if user is not granted access for all libraries. + */ + public long lookupSymbolGlobal(String symbolName) throws SecurityException; + + /** + * Security checks are implicit by previous call of + * {@link #openLibraryLocal(String, boolean)} or {@link #openLibraryGlobal(String, boolean)} + * retrieving the <code>librarHandle</code>. + * + * @param libraryHandle a library handle previously retrieved via {@link #openLibraryLocal(String, boolean)} or {@link #openLibraryGlobal(String, boolean)}. + * @param symbolName global symbol name to lookup up system wide. + * @return the library handle, maybe 0 if not found. + * @throws IllegalArgumentException in case case <code>libraryHandle</code> is unknown. + * @throws SecurityException if user is not granted access for the given library handle + */ + public long lookupSymbol(long libraryHandle, String symbolName) throws SecurityException, IllegalArgumentException; + + /** + * Security checks are implicit by previous call of + * {@link #openLibraryLocal(String, boolean)} or {@link #openLibraryGlobal(String, boolean)} + * retrieving the <code>librarHandle</code>. + * + * @param libraryHandle a library handle previously retrieved via {@link #openLibraryLocal(String, boolean)} or {@link #openLibraryGlobal(String, boolean)}. + * @param debug set to true to enable debugging + * @throws IllegalArgumentException in case case <code>libraryHandle</code> is unknown. + * @throws SecurityException if user is not granted access for the given library handle + */ + public void closeLibrary(long libraryHandle, boolean debug) throws SecurityException, IllegalArgumentException; + + /** + * Returns a string containing the last error. + * Maybe called for debuging purposed if any method fails. + * @return error string, maybe null. A null or non-null value has no semantics. + */ + public String getLastError(); +} diff --git a/java_jni/org/jau/sys/dl/DynamicLookupHelper.java b/java_jni/org/jau/sys/dl/DynamicLookupHelper.java new file mode 100644 index 0000000..4e9a23c --- /dev/null +++ b/java_jni/org/jau/sys/dl/DynamicLookupHelper.java @@ -0,0 +1,57 @@ +/** + * Author: Sven Gothel <[email protected]> + * Author: Kenneth Bradley Russell + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * Copyright (c) 2003-2005 Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys.dl; + +import org.jau.sys.Debug; + +public interface DynamicLookupHelper { + public static final boolean DEBUG = Debug.debug("NativeLibrary"); + public static final boolean DEBUG_LOOKUP = Debug.debug("NativeLibrary.Lookup"); + + /** + * @throws SecurityException if user is not granted access for the library set. + */ + public void claimAllLinkPermission() throws SecurityException; + /** + * @throws SecurityException if user is not granted access for the library set. + */ + public void releaseAllLinkPermission() throws SecurityException; + + /** + * Returns the function handle for function 'funcName'. + * @throws SecurityException if user is not granted access for the library set. + */ + public long dynamicLookupFunction(String funcName) throws SecurityException; + + /** + * Queries whether function 'funcName' is available. + * @throws SecurityException if user is not granted access for the library set. + */ + public boolean isFunctionAvailable(String funcName) throws SecurityException; +} diff --git a/java_jni/org/jau/sys/dl/NativeLibrary.java b/java_jni/org/jau/sys/dl/NativeLibrary.java new file mode 100644 index 0000000..d278b42 --- /dev/null +++ b/java_jni/org/jau/sys/dl/NativeLibrary.java @@ -0,0 +1,298 @@ +/** + * Author: Sven Gothel <[email protected]> + * Author: Kenneth Bradley Russell + * Copyright (c) 2020 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * Copyright (c) 2006 Sun Microsystems, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.sys.dl; + +import java.util.Iterator; +import java.util.List; + +import org.jau.lang.ExceptionUtils; +import org.jau.sys.JNILibrary; +import org.jau.sys.PlatformProps; + +import jau.sys.dl.BionicDynamicLinker32bitImpl; +import jau.sys.dl.BionicDynamicLinker64BitImpl; +import jau.sys.dl.MacOSXDynamicLinkerImpl; +import jau.sys.dl.PosixDynamicLinkerImpl; +import jau.sys.dl.WindowsDynamicLinkerImpl; + +/** Provides low-level, relatively platform-independent access to + shared ("native") libraries. The core library routines + <code>System.load()</code> and <code>System.loadLibrary()</code> + in general provide suitable functionality for applications using + native code, but are not flexible enough to support certain kinds + of glue code generation and deployment strategies. This class + supports direct linking of native libraries to other shared + objects not necessarily installed on the system (in particular, + via the use of dlopen(RTLD_GLOBAL) on Unix platforms) as well as + manual lookup of function names to support e.g. GlueGen's + ProcAddressTable glue code generation style without additional + supporting code needed in the generated library. */ + +public final class NativeLibrary implements DynamicLookupHelper { + private final DynamicLinker dynLink; + + // Platform-specific representation for the handle to the open + // library. This is an HMODULE on Windows and a void* (the result of + // a dlopen() call) on Unix and Mac OS X platforms. + private long libraryHandle; + + // May as well keep around the path to the library we opened + private final String libraryPath; + + private final boolean global; + + // Private constructor to prevent arbitrary instances from floating around + private NativeLibrary(final DynamicLinker dynLink, final long libraryHandle, final String libraryPath, final boolean global) { + this.dynLink = dynLink; + this.libraryHandle = libraryHandle; + this.libraryPath = libraryPath; + this.global = global; + if (DEBUG) { + System.err.println("NativeLibrary.open(): Successfully loaded: " + this); + } + } + + @Override + public final String toString() { + return "NativeLibrary[" + dynLink.getClass().getSimpleName() + ", " + libraryPath + ", 0x" + Long.toHexString(libraryHandle) + ", global " + global + "]"; + } + + /** Opens the given native library, assuming it has the same base + name on all platforms. + <p> + The {@code searchSystemPath} argument changes the behavior to + either use the default system path or not at all. + </p> + <p> + Assuming {@code searchSystemPath} is {@code true}, + the {@code searchSystemPathFirst} argument changes the behavior to first + search the default system path rather than searching it last. + </p> + * @param libName library name, with or without prefix and suffix + * @param searchSystemPath if {@code true} library shall be searched in the system path <i>(default)</i>, otherwise {@code false}. + * @param searchSystemPathFirst if {@code true} system path shall be searched <i>first</i> <i>(default)</i>, rather than searching it last. + * if {@code searchSystemPath} is {@code false} this argument is ignored. + * @param loader {@link ClassLoader} to locate the library + * @param global if {@code true} allows system wide access of the loaded library, otherwise access is restricted to the process. + * @return {@link NativeLibrary} instance or {@code null} if library could not be loaded. + * @throws SecurityException if user is not granted access for the named library. + * @since 2.4.0 + */ + public static final NativeLibrary open(final String libName, + final boolean searchSystemPath, + final boolean searchSystemPathFirst, + final ClassLoader loader, final boolean global) throws SecurityException { + return open(libName, libName, libName, searchSystemPath, searchSystemPathFirst, loader, global); + } + + /** Opens the given native library, assuming it has the given base + names (no "lib" prefix or ".dll/.so/.dylib" suffix) on the + Windows, Unix and Mac OS X platforms, respectively, and in the + context of the specified ClassLoader, which is used to help find + the library in the case of e.g. Java Web Start. + <p> + The {@code searchSystemPath} argument changes the behavior to + either use the default system path or not at all. + </p> + <p> + Assuming {@code searchSystemPath} is {@code true}, + the {@code searchSystemPathFirst} argument changes the behavior to first + search the default system path rather than searching it last. + </p> + Note that we do not currently handle DSO versioning on Unix. + Experience with JOAL and OpenAL has shown that it is extremely + problematic to rely on a specific .so version (for one thing, + ClassLoader.findLibrary on Unix doesn't work with files not + ending in .so, for example .so.0), and in general if this + dynamic loading facility is used correctly the version number + will be irrelevant. + * @param libName windows library name, with or without prefix and suffix + * @param unixLibName unix library name, with or without prefix and suffix + * @param macOSXLibName mac-osx library name, with or without prefix and suffix + * @param searchSystemPath if {@code true} library shall be searched in the system path <i>(default)</i>, otherwise {@code false}. + * @param searchSystemPathFirst if {@code true} system path shall be searched <i>first</i> <i>(default)</i>, rather than searching it last. + * if {@code searchSystemPath} is {@code false} this argument is ignored. + * @param loader {@link ClassLoader} to locate the library + * @param global if {@code true} allows system wide access of the loaded library, otherwise access is restricted to the process. + * @return {@link NativeLibrary} instance or {@code null} if library could not be loaded. + * @throws SecurityException if user is not granted access for the named library. + */ + public static final NativeLibrary open(final String libName, + final String unixLibName, + final String macOSXLibName, + final boolean searchSystemPath, + final boolean searchSystemPathFirst, + final ClassLoader loader, final boolean global) throws SecurityException { + final List<String> possiblePaths = JNILibrary.enumerateLibraryPaths(libName, + unixLibName, + macOSXLibName, + searchSystemPath, searchSystemPathFirst, + loader); + PlatformProps.initSingleton(); // loads native gluegen_rt library + + final DynamicLinker dynLink = getDynamicLinker(); + + // Iterate down these and see which one if any we can actually find. + for (final Iterator<String> iter = possiblePaths.iterator(); iter.hasNext(); ) { + final String path = iter.next(); + if (DEBUG) { + System.err.println("NativeLibrary.open(global "+global+"): Trying to load " + path); + } + long res; + Throwable t = null; + try { + if(global) { + res = dynLink.openLibraryGlobal(path, DEBUG); + } else { + res = dynLink.openLibraryLocal(path, DEBUG); + } + } catch (final Throwable t1) { + t = t1; + res = 0; + } + if ( 0 != res ) { + return new NativeLibrary(dynLink, res, path, global); + } else if( DEBUG ) { + if( null != t ) { + System.err.println("NativeLibrary.open: Caught "+t.getClass().getSimpleName()+": "+t.getMessage()); + } + String errstr; + try { + errstr = dynLink.getLastError(); + } catch (final Throwable t2) { errstr=null; } + System.err.println("NativeLibrary.open: Last error "+errstr); + if( null != t ) { + t.printStackTrace(); + } + } + } + + if (DEBUG) { + System.err.println("NativeLibrary.open(global "+global+"): Did not succeed in loading (" + libName + ", " + unixLibName + ", " + macOSXLibName + ")"); + } + + // For now, just return null to indicate the open operation didn't + // succeed (could also throw an exception if we could tell which + // of the openLibrary operations actually failed) + return null; + } + + @Override + public final void claimAllLinkPermission() throws SecurityException { + dynLink.claimAllLinkPermission(); + } + @Override + public final void releaseAllLinkPermission() throws SecurityException { + dynLink.releaseAllLinkPermission(); + } + + @Override + public final long dynamicLookupFunction(final String funcName) throws SecurityException { + if ( 0 == libraryHandle ) { + throw new RuntimeException("Library is not open"); + } + return dynLink.lookupSymbol(libraryHandle, funcName); + } + + @Override + public final boolean isFunctionAvailable(final String funcName) throws SecurityException { + if ( 0 == libraryHandle ) { + throw new RuntimeException("Library is not open"); + } + return 0 != dynLink.lookupSymbol(libraryHandle, funcName); + } + + /** Looks up the given function name in all loaded libraries. + * @throws SecurityException if user is not granted access for the named library. + */ + public final long dynamicLookupFunctionGlobal(final String funcName) throws SecurityException { + return dynLink.lookupSymbolGlobal(funcName); + } + + /* pp */ final DynamicLinker dynamicLinker() { return dynLink; } + + /* pp */ static DynamicLinker getDynamicLinker() { + final DynamicLinker dynLink; + switch (PlatformProps.OS) { + case WINDOWS: + dynLink = new WindowsDynamicLinkerImpl(); + break; + + case MACOS: + case IOS: + dynLink = new MacOSXDynamicLinkerImpl(); + break; + + case ANDROID: + if( PlatformProps.CPU.is32Bit ) { + dynLink = new BionicDynamicLinker32bitImpl(); + } else { + dynLink = new BionicDynamicLinker64BitImpl(); + } + break; + + default: + dynLink = new PosixDynamicLinkerImpl(); + break; + } + return dynLink; + } + + /** Retrieves the low-level library handle from this NativeLibrary + object. On the Windows platform this is an HMODULE, and on Unix + and Mac OS X platforms the void* result of calling dlopen(). */ + public final long getLibraryHandle() { + return libraryHandle; + } + + /** Retrieves the path under which this library was opened. */ + public final String getLibraryPath() { + return libraryPath; + } + + /** Closes this native library. Further lookup operations are not + allowed after calling this method. + * @throws SecurityException if user is not granted access for the named library. + */ + public final void close() throws SecurityException { + if (DEBUG) { + System.err.println("NativeLibrary.close(): closing " + this); + } + if ( 0 == libraryHandle ) { + throw new RuntimeException("Library already closed"); + } + final long handle = libraryHandle; + libraryHandle = 0; + dynLink.closeLibrary(handle, DEBUG); + if (DEBUG) { + System.err.println("NativeLibrary.close(): Successfully closed " + this); + ExceptionUtils.dumpStack(System.err); + } + } +} diff --git a/java_net/CMakeLists.txt b/java_net/CMakeLists.txt new file mode 100644 index 0000000..c1c2ac1 --- /dev/null +++ b/java_net/CMakeLists.txt @@ -0,0 +1,20 @@ +# java/CMakeLists.txt + +set(CMAKE_JNI_TARGET TRUE) +file(GLOB_RECURSE JAVA_SOURCES "*.java") +add_jar(jaulib_net_jar + ${JAVA_SOURCES} + INCLUDE_JARS jaulib_base_jar jaulib_jni_jar + MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt + OUTPUT_NAME jaulib_net + GENERATE_NATIVE_HEADERS jaulib_net_javah + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_net_jar.dir/jni" +) +add_dependencies(jaulib_net_jar jaulib_base_jar jaulib_jni_jar) +#add_dependencies(jaulib_pkg_jar jaulib_base_jar jaulib_jni_jar jaulib_net_jar) + +set(JNI_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_net_jar.dir/jni") +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/jaulib_net.jar DESTINATION ${CMAKE_INSTALL_LIBDIR}/../lib/java) + +# add_subdirectory (jni) + diff --git a/java_net/manifest.txt.in b/java_net/manifest.txt.in new file mode 100644 index 0000000..69bf6a9 --- /dev/null +++ b/java_net/manifest.txt.in @@ -0,0 +1,27 @@ +Manifest-Version: 1.0 +Bundle-Date: @BUILD_TSTAMP@ +Bundle-ManifestVersion: 2 +Bundle-Name: org.jau.net +Bundle-SymbolicName: org.jau.net +Bundle-Version: @VERSION_SHORT@ +Export-Package: org.jau.net +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.9))" +Package-Title: org.jau.net +Package-Vendor: Gothel Software +Package-Version: @VERSION_SHORT@ +Specification-Title: Jaulib NET +Specification-Vendor: Gothel Software +Specification-Version: @VERSION_API@ +Implementation-Title: Jaulib NET +Implementation-Vendor: Gothel Software +Implementation-Version: @VERSION@ +Implementation-Commit: @VERSION_SHA1@ +Implementation-URL: http://www.jausoft.com/ +Extension-Name: org.jau.net +Trusted-Library: true +Permissions: all-permissions +Application-Library-Allowable-Codebase: * + +Name: org/jau/net +Sealed: false + diff --git a/java_net/org/jau/net/AssetURLConnection.java b/java_net/org/jau/net/AssetURLConnection.java new file mode 100644 index 0000000..4c3a95b --- /dev/null +++ b/java_net/org/jau/net/AssetURLConnection.java @@ -0,0 +1,123 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; + +/** + * See base class {@link PiggybackURLConnection} for motivation. + * + * <p> + * <i>asset</i> resource location protocol connection. + * </p> + * + * <p> + * See {@link AssetURLContext#resolve(String)} how resources are being resolved. + * </p> + * + * <h3>Example:</h3> + * + * Assuming the plain <i>asset entry</i> <b><code>test/lala.txt</code></b> is being resolved by + * a class <code>test.LaLaTest</code>, ie. using the <i>asset aware</i> ClassLoader, + * one would use the following <i>asset</i> aware filesystem layout: + * + * <pre> + * test/LaLaTest.class + * assets/test/lala.txt + * </pre> + * + * The above maybe on a plain filesystem, or within a JAR or an APK file, + * e.g. <code>jogamp.test.apk</code>. + * + * The above would result in the following possible URLs + * reflecting the plain and resolved state of the <i>asset URL</i>: + * <pre> + * 0 Entry test/lala.txt + * 1 Plain asset:test/lala.txt + * 2 Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt + * </pre> + * + * <p> + * The sub protocol URL of the resolved <i>asset</i> + * <pre> + * 3 Sub-URL jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt + * </pre> + * can be retrieved using {@link #getSubProtocol()}. + * </p> + * + * In all above cases, the <i>asset entry</i> is <b><code>test/lala.txt</code></b>, + * which can be retrieved via {@link #getEntryName()}. + * + * <p> + * <h3>General Implementation Notes:</h3> + * An <i>asset</i> URL is resolved using {@link AssetURLContext#getClassLoader()}.{@link ClassLoader#getResource(String) getResource(String)}, + * hence the only requirement for an implementation is to have an <i>asset</i> aware ClassLoader + * as described in {@link AssetURLContext#getClassLoader()}. + * </p> + * <p> + * <h3>Warning:</h3> + * Since the <i>asset</i> protocol is currently not being implemented + * on all platform with an appropriate ClassLoader, a user shall not create the <i>asset</i> URL manually.<br> + * </p> + * + * <h3>Android Implementation Notes:</h3> + * <p> + * The Android ClassLoader {@link jogamp.android.launcher.AssetDexClassLoader} + * resolves the resource as an <i>asset</i> URL in it's {@link ClassLoader#findResource(String)} implementation.</p> + * <p> + * Currently we attach our <i>asset</i> {@link java.net.URLStreamHandlerFactory} + * to allow {@link java.net.URL} to handle <i>asset</i> URLs via our <i>asset</i> {@link java.net.URLStreamHandler} implementation. + * </p> + */ +public class AssetURLConnection extends PiggybackURLConnection<AssetURLContext> { + + public AssetURLConnection(final URL url, final AssetURLContext implHelper) { + super(url, implHelper); + } + + @Override + public String getEntryName() throws IOException { + if(!connected) { + throw new IOException("not connected"); + } + + final String urlPath ; + if(subConn instanceof JarURLConnection) { + urlPath = ((JarURLConnection)subConn).getEntryName(); + } else { + urlPath = subConn.getURL().getPath(); + } + + if(urlPath.startsWith(AssetURLContext.assets_folder)) { + return urlPath.substring(AssetURLContext.assets_folder.length()); + } else { + return urlPath; + } + } + +} diff --git a/java_net/org/jau/net/AssetURLContext.java b/java_net/org/jau/net/AssetURLContext.java new file mode 100644 index 0000000..10b0e2b --- /dev/null +++ b/java_net/org/jau/net/AssetURLContext.java @@ -0,0 +1,280 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import org.jau.io.IOUtil; +import org.jau.lang.ExceptionUtils; +import org.jau.sys.AndroidVersion; + +/** + * See {@link PiggybackURLConnection} for description and examples. + */ +public abstract class AssetURLContext implements PiggybackURLContext { + private static final boolean DEBUG = IOUtil.DEBUG; + + /** The <i>asset URL</i> protocol name <code>asset</code> */ + public static final String asset_protocol = "asset"; + + /** The <i>asset URL</i> protocol prefix <code>asset:</code> */ + public static final String asset_protocol_prefix = "asset:"; + + /** + * The <i>optional</i> <i>asset</i> folder name with ending slash <code>assets/</code>. + * <p> + * Note that the <i>asset</i> folder is not used on all platforms using the <i>asset</i> protocol + * and you should not rely on it, use {@link AssetURLConnection#getEntryName()}. + * </p> + **/ + public static final String assets_folder = "assets/"; + + public static AssetURLContext create(final ClassLoader cl) { + return new AssetURLContext() { + @Override + public ClassLoader getClassLoader() { + return cl; + } + }; + } + + public static AssetURLStreamHandler createHandler(final ClassLoader cl) { + return new AssetURLStreamHandler(create(cl)); + } + + /** + * Create an <i>asset</i> URL, suitable even w/o the registered <i>asset</i> URLStreamHandler. + * <p> + * This is equivalent with: + * <pre> + * return new URL(null, path.startsWith("asset:") ? path : "asset:" + path, new AssetURLStreamHandler(cl)); + * </pre> + * </p> + * @param path resource path, with or w/o <code>asset:</code> prefix + * @param cl the ClassLoader used to resolve the location, see {@link #getClassLoader()}. + * @return + * @throws MalformedURLException + */ + public static URL createURL(final String path, final ClassLoader cl) throws MalformedURLException { + return new URL(null, path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path, createHandler(cl)); + } + + /** + * Create an <i>asset</i> URL, suitable only with the registered <i>asset</i> URLStreamHandler. + * <p> + * This is equivalent with: + * <pre> + * return new URL(path.startsWith("asset:") ? path : "asset:" + path); + * </pre> + * </p> + * @param path resource path, with or w/o <code>asset:</code> prefix + * @return + * @throws MalformedURLException + */ + public static URL createURL(final String path) throws MalformedURLException { + return new URL(path.startsWith(asset_protocol_prefix) ? path : asset_protocol_prefix + path); + } + + /** + * Returns the <i>asset</i> handler previously set via {@link #registerHandler(ClassLoader)}, + * or null if none was set. + */ + public static URLStreamHandler getRegisteredHandler() { + final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register(); + return ( null != f ) ? f.getHandler(asset_protocol) : null; + } + + /** + * Registers the generic URLStreamHandlerFactory via {@link GenericURLStreamHandlerFactory#register()} + * and if successful sets the <i>asset</i> <code>handler</code> for the given ClassLoader <code>cl</code>. + * + * @return true if successful, otherwise false + */ + public static boolean registerHandler(final ClassLoader cl) { + final GenericURLStreamHandlerFactory f = GenericURLStreamHandlerFactory.register(); + if( null != f ) { + f.setHandler(asset_protocol, createHandler(cl)); + return true; + } else { + return false; + } + } + + /** + * Returns an <i>asset</i> aware ClassLoader. + * <p> + * The ClassLoader is required to find the <i>asset</i> resource + * via it's <code>URL findResource(String)</code> implementation. + * </p> + * <p> + * It's <code>URL findResource(String)</code> implementation shall return either + * an <i>asset</i> URL <code>asset:sub-protocol</code> or just the sub-protocol URL. + * </p> + * <p> + * For example, on Android, we <i>redirect</i> all <code>path</code> request to <i>assets/</i><code>path</code>. + * </p> + */ + public abstract ClassLoader getClassLoader(); + + @Override + public String getImplementedProtocol() { + return asset_protocol; + } + + /** + * {@inheritDoc} + * <p> + * This implementation attempts to resolve <code>path</code> in the following order: + * <ol> + * <li> as a valid URL: <code>new URL(path)</code>, use sub-protocol if <i>asset</i> URL</li> + * <li> via ClassLoader: {@link #getClassLoader()}.{@link ClassLoader#getResource(String) getResource(path)}, use sub-protocol if <i>asset</i> URL </li> + * <li> as a File: <code>new File(path).toURI().toURL()</code> + * </ol> + * </p> + * <p> + * In case of using the ClassLoader (2) <b>and</b> if running on Android, + * the {@link #assets_folder} is being prepended to <code>path</code> if missing. + * </p> + **/ + @Override + public URLConnection resolve(final String path) throws IOException { + return resolve(path, getClassLoader()); + } + + public static URLConnection resolve(String path, final ClassLoader cl) throws IOException { + URL url = null; + URLConnection conn = null; + int type = -1; + + if(DEBUG) { + System.err.println("AssetURLContext.resolve: <"+path+">"); + } + try { + path = IOUtil.cleanPathString(path); + } catch (final URISyntaxException uriEx) { + throw new IOException(uriEx); + } + + try { + // lookup as valid sub-protocol + url = new URL(path); + conn = open(url); + type = null != conn ? 1 : -1; + } catch(final MalformedURLException e1) { if(DEBUG) { System.err.println("FAIL(1): "+e1.getMessage()); } } + + if(null == conn && null != cl) { + // lookup via ClassLoader .. cleanup leading '/' + String cpath = path; + while(cpath.startsWith("/")) { + cpath = cpath.substring(1); + } + if(AndroidVersion.isAvailable) { + cpath = cpath.startsWith(assets_folder) ? cpath : assets_folder + cpath; + } + url = cl.getResource(cpath); + conn = open(url); + type = null != conn ? 2 : -1; + } + + if(null == conn) { + // lookup as File + try { + final File file = new File(path); + if(file.exists()) { + url = Uri.valueOf(file).toURL(); + conn = open(url); + type = null != conn ? 3 : -1; + } + } catch (final Throwable e) { if(DEBUG) { System.err.println("FAIL(3): "+e.getMessage()); } } + } + + if(DEBUG) { + System.err.println("AssetURLContext.resolve: type "+type+": url <"+url+">, conn <"+conn+">, connURL <"+(null!=conn?conn.getURL():null)+">"); + } + if(null == conn) { + throw new FileNotFoundException("Could not look-up: "+path+" as URL, w/ ClassLoader or as File"); + } + return conn; + } + + private static URLConnection open(final URL url) { + if(null==url) { + return null; + } + try { + final URLConnection c = url.openConnection(); + c.connect(); // redundant + return c; + } catch (final IOException ioe) { if(DEBUG) { System.err.println("FAIL(2): "+ioe.getMessage()); } } + return null; + } + + /** + * Locating a resource using the ClassLoader's facilities. + * <p> + * Returns the resolved and connected URLConnection or null if not found. + * </p> + * + * @see ClassLoader#getResource(String) + * @see ClassLoader#getSystemResource(String) + * @see URL#URL(String) + * @see File#File(String) + */ + public static URLConnection getResource(final String resourcePath, final ClassLoader cl) { + if(null == resourcePath) { + return null; + } + if(DEBUG) { + System.err.println("AssetURLContext: locating <"+resourcePath+">, has cl: "+(null!=cl)); + } + if(resourcePath.startsWith(AssetURLContext.asset_protocol_prefix)) { + try { + return AssetURLContext.createURL(resourcePath, cl).openConnection(); + } catch (final IOException ioe) { + if(DEBUG) { + ExceptionUtils.dumpThrowable("AssetURLContext", ioe); + } + return null; + } + } else { + try { + return AssetURLContext.resolve(resourcePath, cl); + } catch (final IOException ioe) { + if(DEBUG) { + ExceptionUtils.dumpThrowable("AssetURLContext", ioe); + } + } + } + return null; + } +} diff --git a/java_net/org/jau/net/AssetURLStreamHandler.java b/java_net/org/jau/net/AssetURLStreamHandler.java new file mode 100644 index 0000000..05c5328 --- /dev/null +++ b/java_net/org/jau/net/AssetURLStreamHandler.java @@ -0,0 +1,60 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * {@link URLStreamHandler} to handle the asset protocol. + * + * <p> + * This is the <i>asset</i> URLStreamHandler variation + * for manual use. + * </p> + * <p> + * It requires passing a valid {@link AssetURLContext} + * for construction, hence it's not suitable for the pkg factory model. + * </p> + */ +public class AssetURLStreamHandler extends URLStreamHandler { + AssetURLContext ctx; + + public AssetURLStreamHandler(final AssetURLContext ctx) { + this.ctx = ctx; + } + + @Override + protected URLConnection openConnection(final URL u) throws IOException { + final AssetURLConnection c = new AssetURLConnection(u, ctx); + c.connect(); + return c; + } + + +} diff --git a/java_net/org/jau/net/GenericURLStreamHandlerFactory.java b/java_net/org/jau/net/GenericURLStreamHandlerFactory.java new file mode 100644 index 0000000..87c5823 --- /dev/null +++ b/java_net/org/jau/net/GenericURLStreamHandlerFactory.java @@ -0,0 +1,92 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.net.URL; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; + +public class GenericURLStreamHandlerFactory implements URLStreamHandlerFactory { + private static GenericURLStreamHandlerFactory factory = null; + + private final Map<String, URLStreamHandler> protocolHandlers; + + private GenericURLStreamHandlerFactory() { + protocolHandlers = new HashMap<String, URLStreamHandler>(); + } + + /** + * Sets the <code>handler</code> for <code>protocol</code>. + * + * @return the previous set <code>handler</code>, or null if none was set. + */ + public synchronized final URLStreamHandler setHandler(final String protocol, final URLStreamHandler handler) { + return protocolHandlers.put(protocol, handler); + } + + /** + * Returns the <code>protocol</code> handler previously set via {@link #setHandler(String, URLStreamHandler)}, + * or null if none was set. + */ + public synchronized final URLStreamHandler getHandler(final String protocol) { + return protocolHandlers.get(protocol); + } + + @Override + public synchronized final URLStreamHandler createURLStreamHandler(final String protocol) { + return getHandler(protocol); + } + + /** + * Returns the singleton instance of the registered GenericURLStreamHandlerFactory + * or null if registration was not successful. + * <p> + * Registration is only performed once. + * </p> + */ + public synchronized static GenericURLStreamHandlerFactory register() { + if(null == factory) { + factory = AccessController.doPrivileged(new PrivilegedAction<GenericURLStreamHandlerFactory>() { + @Override + public GenericURLStreamHandlerFactory run() { + boolean ok = false; + final GenericURLStreamHandlerFactory f = new GenericURLStreamHandlerFactory(); + try { + URL.setURLStreamHandlerFactory(f); + ok = true; + } catch (final Throwable e) { + System.err.println("GenericURLStreamHandlerFactory: Setting URLStreamHandlerFactory failed: "+e.getMessage()); + } + return ok ? f : null; + } } ); + } + return factory; + } +} diff --git a/java_net/org/jau/net/PiggybackURLConnection.java b/java_net/org/jau/net/PiggybackURLConnection.java new file mode 100644 index 0000000..3fab1f3 --- /dev/null +++ b/java_net/org/jau/net/PiggybackURLConnection.java @@ -0,0 +1,109 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * Generic resource location protocol connection, + * using another sub-protocol as the vehicle for a piggyback protocol. + * <p> + * The details of the sub-protocol can be queried using {@link #getSubProtocol()}. + * </p> + * <p> + * See example in {@link AssetURLConnection}. + * </p> + */ +public abstract class PiggybackURLConnection<I extends PiggybackURLContext> extends URLConnection { + protected URL subUrl; + protected URLConnection subConn; + protected I context; + + /** + * @param url the specific URL for this instance + * @param context the piggyback context, defining state independent code and constants + */ + protected PiggybackURLConnection(final URL url, final I context) { + super(url); + this.context = context; + } + + /** + * <p> + * Resolves the URL via {@link PiggybackURLContext#resolve(String)}, + * see {@link AssetURLContext#resolve(String)} for an example. + * </p> + * + * {@inheritDoc} + */ + @Override + public synchronized void connect() throws IOException { + if(!connected) { + subConn = context.resolve(url.getPath()); + subUrl = subConn.getURL(); + connected = true; + } + } + + @Override + public InputStream getInputStream() throws IOException { + if(!connected) { + throw new IOException("not connected"); + } + return subConn.getInputStream(); + } + + /** + * Returns the <i>entry name</i> of the asset. + * <pre> + * Plain asset:test/lala.txt + * Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt + * Result test/lala.txt + * </pre> + * @throws IOException is not connected + **/ + public abstract String getEntryName() throws IOException; + + /** + * Returns the resolved <i>sub protocol</i> of the asset or null, ie: + * <pre> + * Plain asset:test/lala.txt + * Resolved asset:jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt + * Result jar:file:/data/app/jogamp.test.apk!/assets/test/lala.txt + * </pre> + * + * @throws IOException is not connected + */ + public URL getSubProtocol() throws IOException { + if(!connected) { + throw new IOException("not connected"); + } + return subUrl; + } +} diff --git a/java_net/org/jau/net/PiggybackURLContext.java b/java_net/org/jau/net/PiggybackURLContext.java new file mode 100644 index 0000000..7cb188e --- /dev/null +++ b/java_net/org/jau/net/PiggybackURLContext.java @@ -0,0 +1,43 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.io.IOException; +import java.net.URLConnection; + +/** + * See {@link PiggybackURLConnection} for description and examples. + */ +public interface PiggybackURLContext { + + /** Returns the specific protocol, constant for this implementation. */ + public String getImplementedProtocol(); + + /** + * Resolving path to a URL sub protocol and return it's open URLConnection + **/ + public URLConnection resolve(String path) throws IOException; +} diff --git a/java_net/org/jau/net/Uri.java b/java_net/org/jau/net/Uri.java new file mode 100644 index 0000000..b47fe2e --- /dev/null +++ b/java_net/org/jau/net/Uri.java @@ -0,0 +1,2546 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * Copyright (c) 2006, 2010 The Apache Software Foundation. + * + * This code is derived from or inspired by the Apache Harmony project's {@code class java.net.URI.Helper}, + * and has been heavily modified for GlueGen/JogAmp. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.StringTokenizer; +import java.util.regex.Pattern; + +import org.jau.io.IOUtil; +import org.jau.sys.Debug; +import org.jau.sys.PropertyAccess; + +/** + * This class implements an immutable Uri as defined by <a href="https://tools.ietf.org/html/rfc2396">RFC 2396</a>. + * <p> + * Character encoding is employed as defined by <a href="https://tools.ietf.org/html/rfc3986">RFC 3986</a>, + * see <a href="https://tools.ietf.org/html/rfc3986#section-2.1">RFC 3986 section 2.1</a>, + * while multibyte unicode characters are preserved in encoded parts. + * </p> + * + * <pre> + 1 [scheme:]scheme-specific-part[#fragment] + 2 [scheme:][//authority]path[?query][#fragment] + 3 [scheme:][//[user-info@]host[:port]]path[?query][#fragment] + + scheme-specific-part: [//authority]path[?query] + authority: [user-info@]host[:port] + * </pre> + * <p> + * <a href="https://tools.ietf.org/html/rfc3986#section-2.2">RFC 3986 section 2.2</a> <i>Reserved Characters</i> (January 2005) + * <table border="1"> + <tr> + <td><code>!</code></td> + <td><code>*</code></td> + <td><code>'</code></td> + <td><code>(</code></td> + <td><code>)</code></td> + <td><code>;</code></td> + <td><code>:</code></td> + <td><code>@</code></td> + <td><code>&</code></td> + <td><code>=</code></td> + <td><code>+</code></td> + <td><code>$</code></td> + <td><code>,</code></td> + <td><code>/</code></td> + <td><code>?</code></td> + <td><code>#</code></td> + <td><code>[</code></td> + <td><code>]</code></td> + </tr> + * </table> + * </p> + * <p> + * <a href="https://tools.ietf.org/html/rfc3986#section-2.3">RFC 3986 section 2.3</a> <i>Unreserved Characters</i> (January 2005) + * <table border="1"> + <tr> + <td><code>A</code></td> + <td><code>B</code></td> + <td><code>C</code></td> + <td><code>D</code></td> + <td><code>E</code></td> + <td><code>F</code></td> + <td><code>G</code></td> + <td><code>H</code></td> + <td><code>I</code></td> + <td><code>J</code></td> + <td><code>K</code></td> + <td><code>L</code></td> + <td><code>M</code></td> + <td><code>N</code></td> + <td><code>O</code></td> + <td><code>P</code></td> + <td><code>Q</code></td> + <td><code>R</code></td> + <td><code>S</code></td> + <td><code>T</code></td> + <td><code>U</code></td> + <td><code>V</code></td> + <td><code>W</code></td> + <td><code>X</code></td> + <td><code>Y</code></td> + <td><code>Z</code></td> + </tr> + <tr> + <td><code>a</code></td> + <td><code>b</code></td> + <td><code>c</code></td> + <td><code>d</code></td> + <td><code>e</code></td> + <td><code>f</code></td> + <td><code>g</code></td> + <td><code>h</code></td> + <td><code>i</code></td> + <td><code>j</code></td> + <td><code>k</code></td> + <td><code>l</code></td> + <td><code>m</code></td> + <td><code>n</code></td> + <td><code>o</code></td> + <td><code>p</code></td> + <td><code>q</code></td> + <td><code>r</code></td> + <td><code>s</code></td> + <td><code>t</code></td> + <td><code>u</code></td> + <td><code>v</code></td> + <td><code>w</code></td> + <td><code>x</code></td> + <td><code>y</code></td> + <td><code>z</code></td> + </tr> + <tr> + <td><code>0</code></td> + <td><code>1</code></td> + <td><code>2</code></td> + <td><code>3</code></td> + <td><code>4</code></td> + <td><code>5</code></td> + <td><code>6</code></td> + <td><code>7</code></td> + <td><code>8</code></td> + <td><code>9</code></td> + <td><code>-</code></td> + <td><code>_</code></td> + <td><code>.</code></td> + <td><code>~</code></td> + </tr> + * </table> + * </p> + * <p> + * Other characters in a Uri must be percent encoded. + * </p> + * @since 2.2.1 + */ +public class Uri { + private static final boolean DEBUG; + private static final boolean DEBUG_SHOWFIX; + + static { + Debug.initSingleton(); + DEBUG = IOUtil.DEBUG || Debug.debug("Uri"); + DEBUG_SHOWFIX = PropertyAccess.isPropertyDefined("jau.debug.Uri.ShowFix", true); + } + + /** + * Usually used to fix a path from a previously contained and opaque Uri, + * i.e. {@link #getContainedUri()}. + * <p> + * Such an opaque Uri w/ erroneous encoding may have been injected via + * {@link #valueOf(URI)} and {@link #valueOf(URL)} where the given URL or URI was opaque! + * </p> + * <p> + * This remedies issues when dealing w/ java URI/URL opaque sources, + * which do not comply to the spec, i.e. containe un-encoded chars, e.g. ':', '$', .. + * </p> + */ + private static final int PARSE_HINT_FIX_PATH = 1 << 0; + + private static final String DIGITS = "0123456789ABCDEF"; + + private static final String ENCODING = "UTF8"; + private static final String MSG_ENCODING_NA = "Charset UTF8 not available"; + private static final Pattern patternSingleFS = Pattern.compile("/{1}"); + + /** + * RFC 3986 section 2.3 Unreserved Characters (January 2005) + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String UNRESERVED = "_-.~"; + // Harmony: _ - ! . ~ ' ( ) * + + private static final String punct = ",;:$&+="; + // Harmony: , ; : $ & + = + + /** + * RFC 3986 section 2.2 Reserved Characters (January 2005) + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String RESERVED = punct + "!*\'()@/?#[]"; + // Harmony: , ; : $ & + = ? / [ ] @ + + public static final String RESERVED_2 = punct + "!*\'()@/?[]"; + // Harmony: , ; : $ & + = ? / [ ] @ + + // Bug 908, issues w/ windows file path char: $ ^ ~ # [ ] + // Windows invalid File characters: * ? " < > | + + /** + * Valid charset for RFC 2396 {@code authority}'s {@code user-info}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String USERINFO_LEGAL = UNRESERVED + punct; + // Harmony: someLegal = unreserved + punct -> _ - ! . ~ ' ( ) * , ; : $ & + = + + /** + * Valid charset for RFC 2396 {@code authority}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String AUTHORITY_LEGAL = "@[]" + USERINFO_LEGAL; + + /** + * Valid charset for RFC 2396 {@code path}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String PATH_LEGAL = "/!" + UNRESERVED; // no RESERVED chars but '!', to allow JAR Uris; + // Harmony: "/@" + unreserved + punct -> / @ _ - ! . ~ \ ' ( ) * , ; : $ & + = + + /** + * Valid charset for RFC 2396 {@code query}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String QUERY_LEGAL = UNRESERVED + RESERVED_2 + "\\\""; + // Harmony: unreserved + reserved + "\\\"" + + /** + * Valid charset for RFC 2396 {@code scheme-specific-part}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String SSP_LEGAL = QUERY_LEGAL; + // Harmony: unreserved + reserved + + /** + * Valid charset for RFC 2396 {@code fragment}, + * additional to legal {@code alphanum} characters. + * <p> + * {@value} + {@code alphanum} + * </p> + */ + public static final String FRAG_LEGAL = UNRESERVED + RESERVED; + // Harmony: unreserved + reserved + + /** {@value} */ + public static final char SCHEME_SEPARATOR = ':'; + /** {@value} */ + public static final char QUERY_SEPARATOR = '?'; + /** {@value} */ + public static final char FRAGMENT_SEPARATOR = '#'; + /** {@value} */ + public static final String FILE_SCHEME = "file"; + /** {@value} */ + public static final String HTTP_SCHEME = "http"; + /** {@value} */ + public static final String HTTPS_SCHEME = "https"; + /** {@value} */ + public static final String JAR_SCHEME = "jar"; + /** A JAR sub-protocol is separated from the JAR entry w/ this separator {@value}. Even if no class is specified '!/' must follow!. */ + public static final char JAR_SCHEME_SEPARATOR = '!'; + + /** + * Immutable RFC3986 encoded string. + */ + public static class Encoded implements Comparable<Encoded>, CharSequence { + private final String s; + + /** + * Casts the given encoded String by creating a new Encoded instance. + * <p> + * No encoding will be performed, use with care. + * </p> + */ + public static Encoded cast(final String encoded) { + return new Encoded(encoded); + } + + Encoded(final String encodedString) { + this.s = encodedString; + } + + /** + * Encodes all characters into their hexadecimal value prepended by '%', except: + * <ol> + * <li>letters ('a'..'z', 'A'..'Z')</li> + * <li>numbers ('0'..'9')</li> + * <li>characters in the legal-set parameter</li> + * <li> others (unicode characters that are not in + * US-ASCII set, and are not ISO Control or are not ISO Space characters)</li> + * </ol> + * <p> + * Uses {@link Uri#encode(String, String)} for implementation.. + * </p> + * + * @param vanilla the string to be encoded + * @param legal extended character set, allowed to be preserved in the vanilla string + */ + public Encoded(final String vanilla, final String legal) { + this.s = encode(vanilla, legal); + } + + public boolean isASCII() { return false; } + + /** Returns the encoded String */ + public final String get() { return s; } + + /** + * Decodes the string argument which is assumed to be encoded in the {@code + * x-www-form-urlencoded} MIME content type using the UTF-8 encoding scheme. + * <p> + *'%' and two following hex digit characters are converted to the + * equivalent byte value. All other characters are passed through + * unmodified. + * </p> + * <p> + * e.g. "A%20B%20C %24%25" -> "A B C $%" + * </p> + * <p> + * Uses {@link Uri#decode(String)} for implementation.. + * </p> + */ + public final String decode() { return Uri.decode(s); } + + // + // Basic Object / Identity + // + + /** + * {@inheritDoc} + * <p> + * Returns the encoded String, same as {@link #get()}. + * </p> + */ + @Override + public final String toString() { return s; } + + @Override + public final int hashCode() { return s.hashCode(); } + + /** + * {@inheritDoc} + * + * @param o The comparison argument, either a {@link Encoded} or a {@link String} + * + * @return {@code true} if the given object is equivalent to this instance, + * otherwise {@code false}. + * + * @see #compareTo(Encoded) + * @see #equalsIgnoreCase(Encoded) + */ + @Override + public final boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o instanceof Encoded) { + return s.equals(((Encoded)o).s); + } + return s.equals(o); + } + + // + // CharSequence + // + + @Override + public final int length() { return s.length(); } + + @Override + public final char charAt(final int index) { return s.charAt(index); } + + @Override + public final CharSequence subSequence(final int start, final int end) { return s.subSequence(start, end); } + + @Override + public final int compareTo(final Encoded o) { return s.compareTo(o.s); } + + // + // String derived .. + // + /** See {@link String#concat(String)}. */ + public Encoded concat(final Encoded encoded) { return new Encoded(s.concat(encoded.s)); } + + /** See {@link String#substring(int)}. */ + public final Encoded substring(final int start) { return new Encoded(s.substring(start)); } + /** See {@link String#substring(int, int)}. */ + public final Encoded substring(final int start, final int end) { return new Encoded(s.substring(start, end)); } + + /** See {@link String#indexOf(int)}. */ + public final int indexOf(final int ch) { return s.indexOf(ch); } + /** See {@link String#indexOf(int, int)}. */ + public final int indexOf(final int ch, final int fromIndex) { return s.indexOf(ch, fromIndex); } + /** See {@link String#indexOf(String)}. */ + public final int indexOf(final String str) { return s.indexOf(str); } + /** See {@link String#indexOf(String, int)}. */ + public final int indexOf(final String str, final int fromIndex) { return s.indexOf(str, fromIndex); } + + /** See {@link String#lastIndexOf(int)}. */ + public final int lastIndexOf(final int ch) { return s.lastIndexOf(ch); } + /** See {@link String#lastIndexOf(int, int)}. */ + public int lastIndexOf(final int ch, final int fromIndex) { return s.lastIndexOf(ch, fromIndex); } + /** See {@link String#lastIndexOf(String)}. */ + public int lastIndexOf(final String str) { return s.lastIndexOf(str); } + /** See {@link String#lastIndexOf(String, int)}. */ + public int lastIndexOf(final String str, final int fromIndex) { return s.lastIndexOf(str, fromIndex); } + + /** See {@link String#startsWith(String)} */ + public boolean startsWith(final String prefix) { return s.startsWith(prefix); } + /** See {@link String#startsWith(String, int)} */ + public boolean startsWith(final String prefix, final int toffset) { return s.startsWith(prefix, toffset); } + /** See {@link String#endsWith(String)} */ + public boolean endsWith(final String suffix) { return s.endsWith(suffix); } + + /** See {@link String#equalsIgnoreCase(String)}. */ + public final boolean equalsIgnoreCase(final Encoded anotherEncoded) { return s.equalsIgnoreCase(anotherEncoded.s); } + } + + public static class ASCIIEncoded extends Encoded { + /** + * Casts the given encoded String by creating a new ASCIIEncoded instance. + * <p> + * No encoding will be performed, use with care. + * </p> + */ + public static ASCIIEncoded cast(final String encoded) { + return new ASCIIEncoded(encoded, null); + } + private ASCIIEncoded(final String encoded, final Object unused) { + super(encoded); + } + + /** + * Other characters, which are Unicode chars that are not US-ASCII, and are + * not ISO Control or are not ISO Space chars are not preserved + * and encoded into their hexidecimal value prepended by '%'. + * <p> + * For example: Euro currency symbol -> "%E2%82%AC". + * </p> + * <p> + * Uses {@link Uri#encodeToASCIIString(String)} for implementation. + * </p> + * @param unicode unencoded input + */ + public ASCIIEncoded(final String unicode) { + super(encodeToASCIIString(unicode)); + } + @Override + public boolean isASCII() { return true; } + } + + private static void encodeChar2UTF8(final StringBuilder buf, final char ch) { + final byte[] bytes; + try { + bytes = new String(new char[] { ch }).getBytes(ENCODING); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(MSG_ENCODING_NA, e); + } + // FIXME: UTF-8 produces more than one byte ? Optimization might be possible. + for (int j = 0; j < bytes.length; j++) { + final byte b = bytes[j]; + buf.append('%'); + buf.append(DIGITS.charAt( ( b & 0xf0 ) >> 4 )); + buf.append(DIGITS.charAt( b & 0xf )); + } + } + + /** + * All characters are encoded into their hexadecimal value prepended by '%', except: + * <ol> + * <li>letters ('a'..'z', 'A'..'Z')</li> + * <li>numbers ('0'..'9')</li> + * <li>characters in the legal-set parameter</li> + * <li> others (unicode characters that are not in + * US-ASCII set, and are not ISO Control or are not ISO Space characters)</li> + * </ol> + * <p> + * Use {@link #encodeToASCIIString(String)} for US-ASCII encoding. + * </p> + * <p> + * Consider using {@link Encoded#Encoded(String, String)} in APIs + * to distinguish encoded from unencoded data by type. + * </p> + * + * @param vanilla the string to be encoded + * @param legal extended character set, allowed to be preserved in the vanilla string + * @return java.lang.String the converted string + */ + public static String encode(final String vanilla, final String legal) { + if( null == vanilla ) { + return null; + } + final StringBuilder buf = new StringBuilder(); + for (int i = 0; i < vanilla.length(); i++) { + final char ch = vanilla.charAt(i); + if ( (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + legal.indexOf(ch) > -1 || + ( ch > 127 && !Character.isSpaceChar(ch) && !Character.isISOControl(ch) ) + ) { + buf.append(ch); + } else { + encodeChar2UTF8(buf, ch); + } + } + return buf.toString(); + } + + /** + * Other characters, which are Unicode chars that are not US-ASCII, and are + * not ISO Control or are not ISO Space chars are not preserved + * and encoded into their hexidecimal value prepended by '%'. + * <p> + * For example: Euro currency symbol -> "%E2%82%AC". + * </p> + * <p> + * Consider using {@link ASCIIEncoded#ASCIIEncoded(String)} in APIs + * to distinguish encoded from unencoded data by type. + * </p> + * @param unicode string to be converted + * @return java.lang.String the converted string + */ + public static String encodeToASCIIString(final String unicode) { + final StringBuilder buf = new StringBuilder(); + for (int i = 0; i < unicode.length(); i++) { + final char ch = unicode.charAt(i); + if (ch <= 127) { + buf.append(ch); + } else { + encodeChar2UTF8(buf, ch); + } + } + return buf.toString(); + } + + /** + * Safe {@link Encoded#decode()} call on optional {@code encoded} instance. + * @param encoded {@link Encoded} instance to be decoded, may be {@code null}. + * @return the {@link Encoded#decode() decoded} String or {@code null} if {@code encoded} was {@code null}. + */ + public static String decode(final Encoded encoded) { + return null != encoded ? encoded.decode() : null; + } + + /** + * Decodes the string argument which is assumed to be encoded in the {@code + * x-www-form-urlencoded} MIME content type using the UTF-8 encoding scheme. + * <p> + *'%' and two following hex digit characters are converted to the + * equivalent byte value. All other characters are passed through + * unmodified. + * </p> + * <p> + * e.g. "A%20B%20C %24%25" -> "A B C $%" + * </p> + * + * @param encoded The encoded string. + * @return java.lang.String The decoded version. + */ + public static String decode(final String encoded) { + if( null == encoded ) { + return null; + } + final StringBuilder result = new StringBuilder(); + final byte[] buf = new byte[32]; + int bufI = 0; + for (int i = 0; i < encoded.length();) { + final char c = encoded.charAt(i); + if (c == '%') { + bufI = 0; + do { + if (i + 2 >= encoded.length()) { + throw new IllegalArgumentException("missing '%' hex-digits at index "+i); + } + final int d1 = Character.digit(encoded.charAt(i + 1), 16); + final int d2 = Character.digit(encoded.charAt(i + 2), 16); + if (d1 == -1 || d2 == -1) { + throw new IllegalArgumentException("invalid hex-digits at index "+i+": "+encoded.substring(i, i + 3)); + } + buf[bufI++] = (byte) ((d1 << 4) + d2); + if( 32 == bufI ) { + appendUTF8(result, buf, bufI); + bufI = 0; + } + i += 3; + } while (i < encoded.length() && encoded.charAt(i) == '%'); + if( 0 < bufI ) { + appendUTF8(result, buf, bufI); + } + } else { + result.append(c); + i++; + } + } + return result.toString(); + } + private static void appendUTF8(final StringBuilder sb, final byte[] buf, final int count) { + try { + sb.append(new String(buf, 0, count, ENCODING)); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(MSG_ENCODING_NA, e); + } + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:]scheme-specific-part[#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid within {@code scheme-specific-part}. + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param ssp the unencoded scheme-specific-part of the Uri. + * @param fragment the unencoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final String scheme, final String ssp, final String fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(ssp) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + if ( !emptyString(ssp) ) { + // QUOTE ILLEGAL CHARACTERS + uri.append(encode(ssp, SSP_LEGAL)); + } + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + // QUOTE ILLEGAL CHARACTERS + uri.append(encode(fragment, FRAG_LEGAL)); + } + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** + * Creates a new Uri instance using the given encoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:]scheme-specific-part[#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid within {@code scheme-specific-part}. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param ssp the encoded scheme-specific-part of the Uri. + * @param fragment the encoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final Encoded scheme, final Encoded ssp, final Encoded fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(ssp) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + if ( !emptyString(ssp) ) { + uri.append(ssp.get()); + } + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + uri.append(fragment.get()); + } + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>must</i> be defined and valid, if any {@code authority} components are defined, + * i.e. {@code user-info}, {@code host} or {@code port}. + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param userinfo the unencoded user information of the Uri for authentication and authorization, {@code null} for undefined. + * @param host the unencoded host name of the Uri, {@code null} for undefined. + * @param port the port number of the Uri, -1 for undefined. + * @param path the unencoded path to the resource on the host. + * @param query the unencoded query part of the Uri to specify parameters for the resource. + * @param fragment the unencoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create (final String scheme, final String userinfo, String host, final int port, + final String path, final String query, final String fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(userinfo) && emptyString(host) && emptyString(path) && + emptyString(query) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + + if ( !emptyString(scheme) && !emptyString(path) && path.length() > 0 && path.charAt(0) != '/') { + throw new URISyntaxException(path, "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + + if ( !emptyString(userinfo) || !emptyString(host) || port != -1) { + uri.append("//"); + } + + if ( !emptyString(userinfo) ) { + // QUOTE ILLEGAL CHARACTERS in userinfo + uri.append(encode(userinfo, USERINFO_LEGAL)); + uri.append('@'); + } + + if ( !emptyString(host) ) { + // check for ipv6 addresses that hasn't been enclosed + // in square brackets + if (host.indexOf(SCHEME_SEPARATOR) != -1 && host.indexOf(']') == -1 + && host.indexOf('[') == -1) { + host = "[" + host + "]"; + } + uri.append(host); + } + + if ( port != -1 ) { + uri.append(SCHEME_SEPARATOR); + uri.append(port); + } + + if ( !emptyString(path) ) { + // QUOTE ILLEGAL CHARS + uri.append(encode(path, PATH_LEGAL)); + } + + if ( !emptyString(query) ) { + uri.append(QUERY_SEPARATOR); + // QUOTE ILLEGAL CHARS + uri.append(encode(query, QUERY_LEGAL)); + } + + if ( !emptyString(fragment) ) { + // QUOTE ILLEGAL CHARS + uri.append(FRAGMENT_SEPARATOR); + uri.append(encode(fragment, FRAG_LEGAL)); + } + return new Uri(new Encoded(uri.toString()), true, 0); + } + + /** + * Creates a new Uri instance using the given encoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:][user-info@]host[:port][path][?query][#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>must</i> be defined and valid, if any {@code authority} components are defined, + * i.e. {@code user-info}, {@code host} or {@code port}. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param userinfo the encoded user information of the Uri for authentication and authorization, {@code null} for undefined. + * @param host the encoded host name of the Uri, {@code null} for undefined. + * @param port the port number of the Uri, -1 for undefined. + * @param path the encoded path to the resource on the host. + * @param query the encoded query part of the Uri to specify parameters for the resource. + * @param fragment the encoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create (final Encoded scheme, final Encoded userinfo, final Encoded host, final int port, + final Encoded path, final Encoded query, final Encoded fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(userinfo) && emptyString(host) && emptyString(path) && + emptyString(query) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + + if ( !emptyString(scheme) && !emptyString(path) && path.length() > 0 && path.charAt(0) != '/') { + throw new URISyntaxException(path.get(), "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + + if ( !emptyString(userinfo) || !emptyString(host) || port != -1) { + uri.append("//"); + } + + if ( !emptyString(userinfo) ) { + uri.append(userinfo.get()); + uri.append('@'); + } + + if ( !emptyString(host) ) { + uri.append(host.get()); + } + + if ( port != -1 ) { + uri.append(SCHEME_SEPARATOR); + uri.append(port); + } + + if ( !emptyString(path) ) { + uri.append(path.get()); + } + + if ( !emptyString(query) ) { + uri.append(QUERY_SEPARATOR); + uri.append(query.get()); + } + + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + uri.append(fragment.get()); + } + return new Uri(new Encoded(uri.toString()), true, 0); + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:]host[path][#fragment]} + * </p> + * <p> + * {@code host} <i>must</i> be valid, if defined. + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param host the unencoded host name of the Uri. + * @param path the unencoded path to the resource on the host. + * @param fragment the unencoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final String scheme, final String host, final String path, final String fragment) throws URISyntaxException { + return create(scheme, null, host, -1, path, null, fragment); + } + + /** + * Creates a new Uri instance using the given encoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:]host[path][#fragment]} + * </p> + * <p> + * {@code host} <i>must</i> be valid, if defined. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param host the encoded host name of the Uri. + * @param path the encoded path to the resource on the host. + * @param fragment the encoded fragment part of the Uri. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final Encoded scheme, final Encoded host, final Encoded path, final Encoded fragment) throws URISyntaxException { + return create(scheme, null, host, -1, path, null, fragment); + } + + /** + * Creates a new Uri instance using the given unencoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given unencoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code [scheme:][//authority][path][?query][#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid, in the optional {@code authority}. + * </p> + * + * @param scheme the unencoded scheme part of the Uri. + * @param authority the unencoded authority part of the Uri. + * @param path the unencoded path to the resource on the host. + * @param query the unencoded query part of the Uri to specify parameters for the resource. + * @param fragment the unencoded fragment part of the Uri. + * + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final String scheme, final String authority, final String path, final String query, final String fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(authority) && emptyString(path) && + emptyString(query) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + if ( !emptyString(scheme) && !emptyString(path) && path.length() > 0 && path.charAt(0) != '/') { + throw new URISyntaxException(path, "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + if ( !emptyString(authority) ) { + uri.append("//"); + // QUOTE ILLEGAL CHARS + uri.append(encode(authority, AUTHORITY_LEGAL)); + } + + if ( !emptyString(path) ) { + // QUOTE ILLEGAL CHARS + uri.append(encode(path, PATH_LEGAL)); + } + if ( !emptyString(query) ) { + // QUOTE ILLEGAL CHARS + uri.append(QUERY_SEPARATOR); + uri.append(encode(query, QUERY_LEGAL)); + } + if ( !emptyString(fragment) ) { + // QUOTE ILLEGAL CHARS + uri.append(FRAGMENT_SEPARATOR); + uri.append(encode(fragment, FRAG_LEGAL)); + } + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** + * Creates a new Uri instance using the given encoded arguments. + * <p> + * This constructor first creates a temporary Uri string from the given encoded encoded components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * The given encoded components are taken as-is, i.e. no re-encoding will be performed! + * However, Uri parsing will re-evaluate encoding of the resulting components. + * </p> + * <p> + * {@code [scheme:][//authority][path][?query][#fragment]} + * </p> + * <p> + * {@code host} and {@code port} <i>may</i> be undefined or invalid, in the optional {@code authority}. + * </p> + * + * @param scheme the encoded scheme part of the Uri. + * @param authority the encoded authority part of the Uri. + * @param path the encoded path to the resource on the host. + * @param query the encoded query part of the Uri to specify parameters for the resource. + * @param fragment the encoded fragment part of the Uri. + * + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri create(final Encoded scheme, final Encoded authority, final Encoded path, final Encoded query, final Encoded fragment) throws URISyntaxException { + if ( emptyString(scheme) && emptyString(authority) && emptyString(path) && + emptyString(query) && emptyString(fragment) ) { + throw new URISyntaxException("", "all empty parts"); + } + if ( !emptyString(scheme) && !emptyString(path) && path.length() > 0 && path.charAt(0) != '/') { + throw new URISyntaxException(path.get(), "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + if ( !emptyString(scheme) ) { + uri.append(scheme); + uri.append(SCHEME_SEPARATOR); + } + if ( !emptyString(authority) ) { + uri.append("//"); + uri.append(authority.get()); + } + + if ( !emptyString(path) ) { + uri.append(path.get()); + } + if ( !emptyString(query) ) { + uri.append(QUERY_SEPARATOR); + uri.append(query.get()); + } + if ( !emptyString(fragment) ) { + uri.append(FRAGMENT_SEPARATOR); + uri.append(fragment.get()); + } + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** + * Casts the given encoded String to a {@link Encoded#cast(String) new Encoded instance} + * used to create the resulting Uri instance via {@link #Uri(Encoded)}. + * <p> + * No encoding will be performed on the given {@code encodedUri}, use with care. + * </p> + * @throws URISyntaxException + */ + public static Uri cast(final String encodedUri) throws URISyntaxException { + return new Uri(Encoded.cast(encodedUri)); + } + + /** + * Creates a new Uri instance using the given file-path argument. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code file:path} + * </p> + * + * @param path the unencoded path of the {@code file} {@code schema}. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOfFilepath(final String path) throws URISyntaxException { + if ( emptyString(path) ) { + throw new URISyntaxException("", "empty path"); + } + if ( path.charAt(0) != '/' ) { + throw new URISyntaxException(path, "path doesn't start with '/'"); + } + + final StringBuilder uri = new StringBuilder(); + uri.append(FILE_SCHEME); + uri.append(SCHEME_SEPARATOR); + + // QUOTE ILLEGAL CHARS + uri.append(encode(path, PATH_LEGAL)); + + return new Uri(new Encoded(uri.toString()), false, 0); + } + + /** + * Creates a new Uri instance using the given File instance. + * <p> + * This constructor first creates a temporary Uri string from the given components. This + * string will be parsed later on to create the Uri instance. + * </p> + * <p> + * {@code file:path} + * </p> + * + * @param file using {@link IOUtil#slashify(String, boolean, boolean) slashified} {@link File#getAbsolutePath() absolute-path} + * for the path of the {@code file} {@code schema}, utilizing {@link #valueOfFilepath(String)}. + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOf(final File file) throws URISyntaxException { + return Uri.valueOfFilepath(IOUtil.slashify(file.getAbsolutePath(), true, file.isDirectory())); + } + + /** + * Creates a new Uri instance using the given URI instance. + * <p> + * Re-encoding will be performed if the given URI is {@link URI#isOpaque() not opaque}. + * </p> + * <p> + * See {@link #PARSE_HINT_FIX_PATH} for issues of injecting opaque URLs. + * </p> + * + * @param uri A given URI instance + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOf(final java.net.URI uri) throws URISyntaxException { + if( uri.isOpaque()) { + // opaque, without host validation. + // Note: This may induce encoding errors of authority and path, see {@link #PARSE_HINT_FIX_PATH} + return new Uri(new Encoded( uri.toString() ), false, 0); + } else { + // with host validation if authority is defined + return Uri.create(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), + uri.getPath(), uri.getQuery(), uri.getFragment()); + } + } + + /** + * Creates a new Uri instance using the given URL instance, + * convenient wrapper for {@link #valueOf(URI)} and {@link URL#toURI()}. + * <p> + * Re-encoding will be performed if the given URL is {@link URI#isOpaque() not opaque}, see {@link #valueOf(URI)}. + * </p> + * <p> + * See {@link #PARSE_HINT_FIX_PATH} for issues of injecting opaque URLs. + * </p> + * + * @param url A given URL instance + * + * @throws URISyntaxException + * if the temporary created string doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + */ + public static Uri valueOf(final java.net.URL url) throws URISyntaxException { + return valueOf(url.toURI()); + } + + // + // All string fields are encoded! + // + + /** Encoded input string used at construction, never {@code null}. */ + public final Encoded input; + + private final Object lazyLock = new Object(); + + /** Encoded input string used at construction, in US-ASCII encoding. */ + private ASCIIEncoded inputASCII; + + private int hash; + + /** Encoded {@code scheme}, {@code null} if undefined. */ + public final Encoded scheme; + + /** Encoded {@code scheme-specific-part}, never {@code null}. */ + public final Encoded schemeSpecificPart; + /** Encoded {@code path} part of {@code scheme-specific-part}, never {@code null}. */ + public final Encoded path; + + /** Indicating whether {@code authority} part is defined or not. */ + public final boolean hasAuthority; + /** Encoded {@code authority} part of {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded authority; + /** Encoded {@code userinfo} part of {@code authority} and {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded userInfo; // part of authority + /** Encoded {@code host} part of {@code authority} and {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded host; // part of authority + /** Encoded {@code port} part of {@code authority} and {@code scheme-specific-part}, {@code -1} if undefined. */ + public final int port; // part of authority + + /** Encoded {@code query} part of {@code scheme-specific-part}, {@code null} if undefined. */ + public final Encoded query; + + /** Encoded {@code fragment}, {@code null} if undefined. */ + public final Encoded fragment; + + /** Indicating whether this Uri is absolute, i.e. has a {@code scheme} and hence an absolute {@code scheme-specific-part}. */ + public final boolean absolute; + + /** + * Indicating whether this Uri is opaque, i.e. non-hierarchical {@code scheme-specific-part}. + * <p> + * An opaque Uri has no {@code scheme-specific-part} being parsed, + * i.e. {@code path}, {@code query} and {@code authority} are {@code null}. + * </p> + */ + public final boolean opaque; + + /** + * Creates a new Uri instance according to the given encoded string {@code uri}. + * + * @param uri the RFC3986 encoded RFC2396 Uri representation to be parsed into a Uri object + * @throws URISyntaxException + * if the given string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public Uri(final Encoded uri) throws URISyntaxException { + this(uri, false, 0); + } + + /** Returns true, if this instance is a {@code file} {@code scheme}, otherwise false. */ + public final boolean isFileScheme() { + return null != scheme && FILE_SCHEME.equals( scheme.get() ); + } + + /** + * Returns true, if this instance is a {@code jar} {@code scheme}, otherwise false. + * @since 2.3.2 + */ + public final boolean isJarScheme() { + return null != scheme && JAR_SCHEME.equals( scheme.get() ); + } + + /** + * Returns the encoded {@link #input}, never {@code null}. + */ + public final Encoded getEncoded() { + return input; + } + + /** + * Returns the encoded {@link #input} as String, never {@code null}, same as {@link #getEncoded()}. + */ + @Override + public final String toString() { + return input.get(); + } + + /** + * Returns the encoded {@link #input} encoded in US-ASCII. + */ + public ASCIIEncoded toASCIIString() { + synchronized( lazyLock ) { + if( null == inputASCII ) { + inputASCII = new ASCIIEncoded(input.get()); + } + return inputASCII; + } + } + + /** + * Returns a new {@link URI} instance using the encoded {@link #input} string, {@code new URI(uri.input)}, + * i.e. no re-encoding will be performed. + * @see #toURIReencoded(boolean) + * @see #valueOf(URI) + */ + public final java.net.URI toURI() { + try { + return new java.net.URI(input.get()); + } catch (final URISyntaxException e) { + throw new Error(e); // Can't happen + } + } + + /** + * Returns a new {@link URI} instance based upon this instance. + * <p> + * All Uri parts of this instance will be decoded + * and encoded by the URI constructor, i.e. re-encoding will be performed. + * </p> + * + * @throws URISyntaxException + * if the given string {@code uri} doesn't fit to the + * specification RFC2396 or could not be parsed correctly. + * @see #toURI() + * @see #valueOf(URI) + */ + public final java.net.URI toURIReencoded() throws URISyntaxException { + final java.net.URI recomposedURI; + if( opaque ) { + // opaque, without host validation + recomposedURI = new java.net.URI(decode(scheme), decode(schemeSpecificPart), decode(fragment)); + } else if( null != host ) { + // with host validation + recomposedURI = new java.net.URI(decode(scheme), decode(userInfo), decode(host), port, + decode(path), decode(query), decode(fragment)); + } else { + // without host validation + recomposedURI = new java.net.URI(decode(scheme), decode(authority), + decode(path), decode(query), decode(fragment)); + } + return recomposedURI; + } + + + /** + * Returns a new {@link URL} instance using the encoded {@link #input} string, {@code new URL(uri.input)}, + * i.e. no re-encoding will be performed. + * @throws MalformedURLException + * if an error occurs while creating the URL or no protocol + * handler could be found. + */ + public final java.net.URL toURL() throws MalformedURLException { + if (!absolute) { + throw new IllegalArgumentException("Cannot convert relative Uri: "+input); + } + return new java.net.URL(input.get()); + } + + /** + * If this instance {@link #isFileScheme() is a file scheme}, + * implementation decodes <i>[ "//"+{@link #authority} ] + {@link #path}</i>,<br> + * then it processes the result if {@link File#separatorChar} <code> == '\\'</code> + * as follows: + * <ul> + * <li>slash -> backslash</li> + * <li>drop a starting single backslash, preserving windows UNC</li> + * </ul> + * and returns the resulting new {@link File} instance. + * <p> + * Otherwise implementation returns {@code null}. + * </p> + */ + public final File toFile() { + if( isFileScheme() && !emptyString(path) ) { + final String authorityS; + if( null == authority ) { + authorityS = ""; + } else { + authorityS = "//"+authority.decode(); + } + final String path = authorityS+this.path.decode(); + if( File.separator.equals("\\") ) { + final String r = patternSingleFS.matcher(path).replaceAll("\\\\"); + if( r.startsWith("\\") && !r.startsWith("\\\\") ) { // '\\\\' denotes UNC hostname, which shall not be cut-off + return new File(r.substring(1)); + } else { + return new File(r); + } + } + return new File(path); + } + return null; + } + + /** + * If this <code>uri</code> is a <i>file scheme</i> + * implementation returns {@link #toFile()}.{@link File#getPath()}. + * <p> + * Otherwise it returns the {@link #toASCIIString()} encoded URI. + * </p> + */ + public final String getUriFilePathOrASCII() { + if( isFileScheme() ) { + return toFile().getPath(); + } else { + return toASCIIString().get(); + } + } + + /** + * If this instance's {@link #schemeSpecificPart} contains a Uri itself, a sub-Uri, + * return {@link #schemeSpecificPart} + {@code #} {@link #fragment} via it's own new Uri instance. + * <p> + * In case this Uri is a {@code jar-scheme}, the {@code query} is omitted, + * since it shall be invalid for {@code jar-schemes} anyway. + * </p> + * <p> + * Otherwise method returns {@code null}. + * </p> + * <pre> + * Example 1: + * This instance: <code>jar:<i>scheme2</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar</code> + * + * Example 2: + * This instance: <code>jar:<i>scheme2</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class?lala=01#fragment</code> + * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar#fragment</code> + * + * Example 3: + * This instance: <code>scheme1:<i>scheme2</i>:/some/path/gluegen-rt.jar!/?lala=01#fragment</code> + * Returned Uri: <code><i>scheme2</i>:/some/path/gluegen-rt.jar?lala=01#fragment</code> + * </pre> + * @throws URISyntaxException if this Uri is a container Uri and does not comply with the container spec, i.e. a JAR Uri + */ + public final Uri getContainedUri() throws URISyntaxException { + if( !emptyString(schemeSpecificPart) ) { + final StringBuilder sb = new StringBuilder(); + + if( isJarScheme() ) { + final int idx = schemeSpecificPart.lastIndexOf(JAR_SCHEME_SEPARATOR); + if (0 > idx) { + throw new URISyntaxException(input.get(), "missing jar separator"); + } + sb.append( schemeSpecificPart.get().substring(0, idx) ); // exclude '!/' + } else { + sb.append( schemeSpecificPart.get() ); + } + if ( !emptyString(fragment) ) { + sb.append(FRAGMENT_SEPARATOR); + sb.append(fragment); + } + try { + final int parseHints = opaque ? PARSE_HINT_FIX_PATH : 0; + final Uri res = new Uri(new Encoded(sb.toString()), false, parseHints); + if( null != res.scheme ) { + return res; + } + } catch(final URISyntaxException e) { + // OK, does not contain uri + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); + } + } + } + return null; + } + + private static final boolean cutoffLastPathSegementImpl(final StringBuilder pathBuf, + final boolean cutoffFile, + final boolean cutoffDir, + final Encoded appendPath) throws URISyntaxException { + final boolean cleaned; + {// clean-up existing path + final String pathS = pathBuf.toString(); + if( 0 > pathS.indexOf("/") && emptyString(appendPath) ) { + return false; // nothing to cut-off + } + pathBuf.setLength(0); + pathBuf.append( IOUtil.cleanPathString( pathS ) ); + cleaned = pathBuf.length() != pathS.length(); + } + + {// cut-off file or last dir-segment + final String pathS = pathBuf.toString(); + final int jarSepIdx = pathS.lastIndexOf(JAR_SCHEME_SEPARATOR); + final int e = pathS.lastIndexOf("/"); + if( 0 > jarSepIdx || e - 1 > jarSepIdx ) { // stop at jar-separator '!/', if exist + if( cutoffFile && e < pathS.length() - 1 ) { + // cut-off file + pathBuf.setLength(0); + pathBuf.append( pathS.substring(0, e+1) ); + } else if( cutoffDir ) { + // cut-off dir-segment + final int p = pathS.lastIndexOf("/", e-1); + if( p >= 0 ) { + pathBuf.setLength(0); + pathBuf.append( pathS.substring(0, p+1) ); + } // else keep + } // else keep + } + final boolean cutoff = pathBuf.length() != pathS.length(); + if( !cutoff && ( cutoffDir || !cleaned ) && emptyString(appendPath) ) { + return false; // no modifications! + } + } + if( !emptyString(appendPath) ) { + pathBuf.append(appendPath.get()); + // 2nd round of cleaning! + final String pathS = pathBuf.toString(); + pathBuf.setLength(0); + pathBuf.append( IOUtil.cleanPathString( pathS ) ); + } + return true; // continue processing w/ buffer + } + private final Uri cutoffLastPathSegementImpl(final boolean cutoffFile, final boolean cutoffDir, final Encoded appendPath) throws URISyntaxException { + if( opaque ) { + if( emptyString(schemeSpecificPart) ) { + // nothing to cut-off + if( !emptyString(appendPath) ) { + return Uri.create(scheme, appendPath, fragment); + } else { + return null; + } + } + final StringBuilder sspBuf = new StringBuilder(); // without path! + + // save optional query in scheme-specific-part + final Encoded queryTemp; + final int queryI = schemeSpecificPart.lastIndexOf(QUERY_SEPARATOR); + if( queryI >= 0 ) { + queryTemp = schemeSpecificPart.substring(queryI+1); + sspBuf.append( schemeSpecificPart.substring(0, queryI).get() ); + } else { + queryTemp = null; + sspBuf.append( schemeSpecificPart.get() ); + } + + if( !cutoffLastPathSegementImpl(sspBuf, cutoffFile, cutoffDir, appendPath) ) { + return null; // no modifications + } + + if ( !emptyString(queryTemp) ) { + sspBuf.append(QUERY_SEPARATOR); + sspBuf.append( queryTemp.get() ); + } + + // without host validation if authority is defined + return Uri.create(scheme, new Encoded(sspBuf.toString()), fragment); + } else { + if( emptyString(path) ) { + return null; // nothing to cut-off + } + final StringBuilder pathBuf = new StringBuilder(); + pathBuf.append( path.get() ); + + if( !cutoffLastPathSegementImpl(pathBuf, cutoffFile, cutoffDir, appendPath) ) { + return null; // no modifications + } + + // with host validation if authority is defined + return Uri.create(scheme, userInfo, host, port, new Encoded(pathBuf.toString()), query, fragment); + } + } + + /** + * {@link IOUtil#cleanPathString(String) Normalizes} this Uri's path and return the + * {@link IOUtil#cleanPathString(String) normalized} form if it differs, otherwise {@code this} instance. + * <p> + * <pre> + * Example-1: + * This instance : <code>jar:http://some/path/../gluegen-rt.jar!/com/Test.class?arg=1#frag</code> + * Normalized : <code>jar:http://some/gluegen-rt.jar!/com/Test.class?arg=1#frag</code> + * + * Example-2: + * This instance : <code>http://some/path/../gluegen-rt.jar?arg=1#frag</code> + * Normalized : <code>http://some/gluegen-rt.jar?arg=1#frag</code> + * </pre> + * </p> + */ + public final Uri getNormalized() { + try { + final Uri res = cutoffLastPathSegementImpl(false, false, null); + return null != res ? res : this; + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); + } + return this; + } + } + + /** + * Returns this Uri's directory Uri. + * <p> + * This Uri path will be {@link IOUtil#cleanPathString(String) normalized} before returning the directory. + * </p> + * <p> + * If this Uri's directory cannot be found, or already denotes a directory, method returns {@code this} instance. + * </p> + * <p> + * <pre> + * Example-1: + * this-uri: http:/some/path/gluegen-rt.jar?arg=1#frag + * result: http:/some/path/?arg=1#frag + * + * Example-2: + * this-uri: file:/some/path/ + * result: file:/some/path/ + * + * Example-3: + * this-uri: file:/some/path/lala/lili/../../hello.txt + * result: file:/some/path/ + * </pre> + * </p> + * @throws URISyntaxException if the new string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public Uri getDirectory() { + try { + final Uri res = cutoffLastPathSegementImpl(true, false, null); + return null != res ? res : this; + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); + } + return this; + } + } + + /** + * Returns this Uri's parent directory Uri.. + * <p> + * This Uri path will be {@link IOUtil#cleanPathString(String) normalized} before traversing up one directory. + * </p> + * <p> + * If a parent folder cannot be found, method returns {@code null}. + * </p> + * <p> + * <pre> + * Example-1: + * This instance : <code>jar:http://some/path/gluegen-rt.jar!/com/Test.class?arg=1#frag</code> + * Returned Uri #1: <code>jar:http://some/path/gluegen-rt.jar!/com/?arg=1#frag</code> + * Returned Uri #2: <code>jar:http://some/path/gluegen-rt.jar!/?arg=1#frag</code> + * Returned Uri #3: <code>null</code> + * + * Example-2: + * This instance : <code>http://some/path/gluegen-rt.jar?arg=1#frag</code> + * Returned Uri #1: <code>http://some/path/?arg=1#frag</code> + * Returned Uri #2: <code>http://some/?arg=1#frag</code> + * Returned Uri #2: <code>null</code> + * + * Example-3: + * This instance : <code>http://some/path/../gluegen-rt.jar?arg=1#frag</code> + * Returned Uri #1: <code>http://some/?arg=1#frag</code> + * Returned Uri #2: <code>null</code> + * </pre> + * </p> + */ + public final Uri getParent() { + try { + return cutoffLastPathSegementImpl(true, true, null); + } catch (final URISyntaxException e) { + if( DEBUG ) { + System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); + e.printStackTrace(); + } + return null; + } + } + + /** + * Returns a new Uri appending the given {@code appendPath} + * to this instance's {@link #getDirectory() directory}. + * <p> + * If {@code appendPath} is empty, method behaves like {@link #getNormalized()}. + * </p> + * <p> + * This resulting path will be {@link IOUtil#cleanPathString(String) normalized}. + * </p> + * <p> + * <pre> + * Example-1: + * append: null + * this-uri: http:/some/path/gluegen-rt.jar + * result: http:/some/path/gluegen-rt.jar + * + * Example-2: + * append: test.txt + * this-uri: file:/some/path/gluegen-rt.jar + * result: file:/some/path/test.txt + * + * Example-3: + * append: test.txt + * this-uri: file:/some/path/lala/lili/../../hello.txt + * result: file:/some/path/test.txt + * </pre> + * </p> + * + * @param appendPath denotes a relative path to be appended to this Uri's directory + * @throws URISyntaxException + * if the resulting {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public Uri getRelativeOf(final Encoded appendPath) throws URISyntaxException { + if( emptyString(appendPath) ) { + return getNormalized(); + } else { + return cutoffLastPathSegementImpl(true, false, appendPath); + } + } + + /** + * Concatenates the given encoded string to the {@link #getEncoded() encoded uri} + * of this instance and returns {@link #Uri(Encoded) a new Uri instance} with the result. + * + * @throws URISyntaxException + * if the concatenated string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public final Uri concat(final Encoded suffix) throws URISyntaxException { + if( null == suffix ) { + return this; + } else { + return new Uri( input.concat(suffix) ); + } + } + + /** + * Returns a new Uri instance w/ the given new query {@code newQuery}. + * + * @throws URISyntaxException if this Uri is {@link #opaque} + * or if the new string {@code uri} doesn't fit to the + * specification RFC2396 and RFC3986 or could not be parsed correctly. + */ + public final Uri getNewQuery(final Encoded newQuery) throws URISyntaxException { + if( opaque ) { + throw new URISyntaxException(input.decode(), "Opaque Uri cannot permute by query"); + } else { + // with host validation if authority is defined + return Uri.create(scheme, userInfo, host, port, path, newQuery, fragment); + } + } + + /** + * {@inheritDoc} + * <p> + * Compares this Uri instance with the given argument {@code o} and + * determines if both are equal. Two Uri instances are equal if all single + * parts are identical in their meaning. + * </p> + * + * @param o + * the Uri this instance has to be compared with. + * @return {@code true} if both Uri instances point to the same resource, + * {@code false} otherwise. + */ + @Override + public final boolean equals(final Object o) { + if (!(o instanceof Uri)) { + return false; + } + final Uri uri = (Uri) o; + + if (uri.fragment == null && fragment != null || uri.fragment != null && fragment == null) { + return false; + } else if (uri.fragment != null && fragment != null) { + if (!equalsHexCaseInsensitive(uri.fragment, fragment)) { + return false; + } + } + + if (uri.scheme == null && scheme != null || uri.scheme != null && scheme == null) { + return false; + } else if (uri.scheme != null && scheme != null) { + if (!uri.scheme.equalsIgnoreCase(scheme)) { + return false; + } + } + + if (uri.opaque && opaque) { + return equalsHexCaseInsensitive(uri.schemeSpecificPart, schemeSpecificPart); + } else if (!uri.opaque && !opaque) { + if (!equalsHexCaseInsensitive(path, uri.path)) { + return false; + } + + if (uri.query != null && query == null || uri.query == null && query != null) { + return false; + } else if (uri.query != null && query != null) { + if (!equalsHexCaseInsensitive(uri.query, query)) { + return false; + } + } + + if (uri.authority != null && authority == null || uri.authority == null && authority != null) { + return false; + } else if (uri.authority != null && authority != null) { + if (uri.host != null && host == null || uri.host == null && host != null) { + return false; + } else if (uri.host == null && host == null) { + // both are registry based, so compare the whole authority + return equalsHexCaseInsensitive(uri.authority, authority); + } else { // uri.host != null && host != null, so server-based + if (!host.equalsIgnoreCase(uri.host)) { + return false; + } + + if (port != uri.port) { + return false; + } + + if ( uri.userInfo != null && userInfo == null || + uri.userInfo == null && userInfo != null + ) { + return false; + } else if (uri.userInfo != null && userInfo != null) { + return equalsHexCaseInsensitive(userInfo, uri.userInfo); + } else { + return true; + } + } + } else { + // no authority + return true; + } + + } else { + // one is opaque, the other hierarchical + return false; + } + } + + /** + * {@inheritDoc} + * <p> + * Gets the hashcode value of this Uri instance. + * </p> + */ + @Override + public final int hashCode() { + synchronized( lazyLock ) { + if (hash == -1) { + hash = getHashString().hashCode(); + } + return hash; + } + } + + /* + * Takes a string that may contain hex sequences like %F1 or %2b and + * converts the hex values following the '%' to lowercase + */ + private String convertHexToLowerCase(final String s) { + if (s.indexOf('%') == -1) { + return s; + } + final StringBuilder result = new StringBuilder(""); + int index = 0, previndex = 0; + while ((index = s.indexOf('%', previndex)) != -1) { + result.append(s.substring(previndex, index + 1)); + result.append(s.substring(index + 1, index + 3).toLowerCase()); + index += 3; + previndex = index; + } + return result.toString(); + } + + /* + * Takes two strings that may contain hex sequences like %F1 or %2b and + * compares them, ignoring case for the hex values. Hex values must always + * occur in pairs as above + */ + private boolean equalsHexCaseInsensitive(final Encoded first, final Encoded second) { + if (first.indexOf('%') != second.indexOf('%')) { + return first.equals(second); + } + + int index = 0, previndex = 0; + while ( ( index = first.indexOf('%', previndex) ) != -1 && + second.indexOf('%', previndex) == index + ) { + if( !first.get().substring(previndex, index).equals( second.get().substring(previndex, index) ) ) { + return false; + } + if( !first.get().substring(index + 1, index + 3).equalsIgnoreCase( second.get().substring(index + 1, index + 3) ) ) { + return false; + } + index += 3; + previndex = index; + } + return first.get().substring(previndex).equals( second.get().substring(previndex) ); + } + + /* + * Form a string from the components of this Uri, similarly to the + * toString() method. But this method converts scheme and host to lowercase, + * and converts escaped octets to lowercase. + */ + private String getHashString() { + final StringBuilder result = new StringBuilder(); + if (scheme != null) { + result.append(scheme.get().toLowerCase()); + result.append(SCHEME_SEPARATOR); + } + if (opaque) { + result.append(schemeSpecificPart.get()); + } else { + if (authority != null) { + result.append("//"); + if (host == null) { + result.append(authority.get()); + } else { + if (userInfo != null) { + result.append(userInfo.get() + "@"); + } + result.append(host.get().toLowerCase()); + if (port != -1) { + result.append(SCHEME_SEPARATOR + port); + } + } + } + + if (path != null) { + result.append(path.get()); + } + + if (query != null) { + result.append(QUERY_SEPARATOR); + result.append(query.get()); + } + } + + if (fragment != null) { + result.append(FRAGMENT_SEPARATOR); + result.append(fragment.get()); + } + return convertHexToLowerCase(result.toString()); + } + + /** + * + * @param input + * @param expectServer + * @param parseHints TODO + * @throws URISyntaxException + */ + private Uri(final Encoded input, final boolean expectServer, final int parseHints) throws URISyntaxException { + if( emptyString(input) ) { + throw new URISyntaxException(input.get(), "empty input"); + } + String temp = input.get(); + int index; + // parse into Fragment, Scheme, and SchemeSpecificPart + // then parse SchemeSpecificPart if necessary + + // Fragment + index = temp.indexOf(FRAGMENT_SEPARATOR); + if (index != -1) { + // remove the fragment from the end + fragment = new Encoded( temp.substring(index + 1) ); + validateFragment(input, fragment, index + 1); + temp = temp.substring(0, index); + } else { + fragment = null; + } + + String inputTemp = input.get(); // may get modified due to error correction + + // Scheme and SchemeSpecificPart + final int indexSchemeSep = temp.indexOf(SCHEME_SEPARATOR); + index = indexSchemeSep; + final int indexSSP = temp.indexOf('/'); + final int indexQuerySep = temp.indexOf(QUERY_SEPARATOR); + + String sspTemp; // may get modified due to error correction + + // if a '/' or '?' occurs before the first ':' the uri has no + // specified scheme, and is therefore not absolute + if ( indexSchemeSep != -1 && + ( indexSSP >= indexSchemeSep || indexSSP == -1 ) && + ( indexQuerySep >= indexSchemeSep || indexQuerySep == -1 ) + ) { + // the characters up to the first ':' comprise the scheme + absolute = true; + scheme = new Encoded( temp.substring(0, indexSchemeSep) ); + if (scheme.length() == 0) { + failExpecting(input, "scheme", indexSchemeSep); + } + validateScheme(input, scheme, 0); + sspTemp = temp.substring(indexSchemeSep + 1); + if (sspTemp.length() == 0) { + failExpecting(input, "scheme-specific-part", indexSchemeSep); + } + } else { + absolute = false; + scheme = null; + sspTemp = temp; + } + + if ( scheme == null || sspTemp.length() > 0 && sspTemp.charAt(0) == '/' ) { + // Uri is hierarchical, not opaque + opaque = false; + + // Query + temp = sspTemp; + index = temp.indexOf(QUERY_SEPARATOR); + if (index != -1) { + query = new Encoded( temp.substring(index + 1) ); + temp = temp.substring(0, index); + validateQuery(input, query, indexSSP + 1 + index); + } else { + query = null; + } + + String pathTemp; // may get modified due to error correction + final int indexPathInSSP; + + // Authority and Path + if (temp.startsWith("//")) { + index = temp.indexOf('/', 2); + final String authorityS; + if (index != -1) { + authorityS = temp.substring(2, index); + pathTemp = temp.substring(index); + indexPathInSSP = index; + } else { + authorityS = temp.substring(2); + if (authorityS.length() == 0 && query == null && fragment == null) { + failExpecting(input, "authority, path [, query, fragment]", index); + } + pathTemp = ""; + indexPathInSSP = -1; + // nothing left, so path is empty + // (not null, path should never be null if hierarchical/non-opaque) + } + if ( emptyString(authorityS) ) { + authority = null; + } else { + authority = new Encoded( authorityS ); + validateAuthority(input, authority, indexSchemeSep + 3); + } + } else { // no authority specified + pathTemp = temp; + indexPathInSSP = 0; + authority = null; + } + + int indexPath = 0; // in input + if (indexSSP > -1) { + indexPath += indexSSP; + } + if (indexPathInSSP > -1) { + indexPath += indexPathInSSP; + } + + final int pathErrIdx = validateEncoded(pathTemp, PATH_LEGAL); + if( 0 <= pathErrIdx ) { + // Perform error correction on PATH if requested! + if( 0 != ( parseHints & PARSE_HINT_FIX_PATH ) ) { + if( DEBUG_SHOWFIX ) { + System.err.println("Uri FIX_FILEPATH: input at index "+(indexPath+pathErrIdx)+": "+inputTemp); + System.err.println("Uri FIX_FILEPATH: ssp at index "+(indexPathInSSP+pathErrIdx)+": "+sspTemp); + System.err.println("Uri FIX_FILEPATH: path at index "+pathErrIdx+": "+pathTemp); + } + final int pathTempOldLen = pathTemp.length(); + pathTemp = encode( decode( pathTemp ), PATH_LEGAL); // re-encode, and hope for the best! + validatePath(input, pathTemp, indexPath); // re-validate! + { + // Patch SSP + INPUT ! + final StringBuilder sb = new StringBuilder(); + if( indexPathInSSP > 0 ) { + sb.append( sspTemp.substring(0, indexPathInSSP) ); + } + sb.append( pathTemp ).append( sspTemp.substring( indexPathInSSP + pathTempOldLen ) ); + sspTemp = sb.toString(); // update + + sb.setLength(0); + if( indexPath > 0 ) { + sb.append( inputTemp.substring(0, indexPath) ); + } + sb.append( pathTemp ).append( inputTemp.substring( indexPath + pathTempOldLen ) ); + inputTemp = sb.toString(); // update + } + if( DEBUG_SHOWFIX ) { + System.err.println("Uri FIX_FILEPATH: result : "+pathTemp); + System.err.println("Uri FIX_FILEPATH: ssp after : "+sspTemp); + System.err.println("Uri FIX_FILEPATH: input after : "+inputTemp); + } + } else { + fail(input, "invalid path", indexPath+pathErrIdx); + } + } + path = new Encoded( pathTemp ); + } else { + // Uri is not hierarchical, Uri is opaque + opaque = true; + query = null; + path = null; + authority = null; + validateSsp(input, sspTemp, indexSchemeSep + 1); + } + schemeSpecificPart = new Encoded( sspTemp ); + this.input = inputTemp == input.get() ? input : new Encoded( inputTemp ); + + /** + * determine the host, port and userinfo if the authority parses + * successfully to a server based authority + * + * Behavior in error cases: if forceServer is true, throw + * URISyntaxException with the proper diagnostic messages. if + * forceServer is false assume this is a registry based uri, and just + * return leaving the host, port and userinfo fields undefined. + * + * and there are some error cases where URISyntaxException is thrown + * regardless of the forceServer parameter e.g. malformed ipv6 address + */ + Encoded tempUserinfo = null, tempHost = null; + int tempPort = -1; + boolean authorityComplete; + + if ( null != authority ) { + authorityComplete = true; // set to false later + int hostindex = 0; + + temp = authority.get(); + index = temp.indexOf('@'); + if (index != -1) { + // remove user info + tempUserinfo = new Encoded( temp.substring(0, index) ); + validateUserinfo(authority, tempUserinfo, 0); + temp = temp.substring(index + 1); // host[:port] is left + hostindex = index + 1; + } + + index = temp.lastIndexOf(SCHEME_SEPARATOR); + final int endindex = temp.indexOf(']'); + + if (index != -1 && endindex < index) { + // determine port and host + tempHost = new Encoded( temp.substring(0, index) ); + + if (index < (temp.length() - 1)) { // port part is not empty + try { + tempPort = Integer.parseInt(temp.substring(index + 1)); + if (tempPort < 0) { + if (expectServer) { + fail(authority, "invalid port <"+authority+">", hostindex + index + 1); + } + authorityComplete = false; + } + } catch (final NumberFormatException e) { + if (expectServer) { + fail(authority, "invalid port <"+authority+">, "+e.getMessage(), hostindex + index + 1); + } + authorityComplete = false; + } + } + } else { + tempHost = new Encoded( temp ); + } + + if( authorityComplete ) { + if ( emptyString(tempHost) ) { + if (expectServer) { + fail(authority, "empty host <"+authority+">", hostindex); + } + authorityComplete = false; + } else if (!isValidHost(expectServer, tempHost)) { + if (expectServer) { + fail(authority, "invalid host <"+tempHost+">", hostindex); + } + authorityComplete = false; + } + } + } else { + authorityComplete = false; + } + + if( authorityComplete ) { + // this is a server based uri, + // fill in the userinfo, host and port fields + userInfo = tempUserinfo; + host = tempHost; + port = tempPort; + hasAuthority = true; + } else { + userInfo = null; + host = null; + port = -1; + hasAuthority = false; + } + } + + private static void validateScheme(final Encoded uri, final Encoded scheme, final int index) throws URISyntaxException { + // first char needs to be an alpha char + final char ch = scheme.charAt(0); + if ( !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) ) { + fail(uri, "invalid scheme", index); + } + final int errIdx = validateAlphaNum(scheme.get(), "+-."); + if( 0 <= errIdx ) { + fail(uri, "invalid scheme", index+errIdx); + } + } + + private static void validateSsp(final Encoded uri, final String ssp, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(ssp, SSP_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid scheme-specific-part", index+errIdx); + } + } + + private static void validateAuthority(final Encoded uri, final Encoded authority, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(authority.get(), AUTHORITY_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid authority", index+errIdx); + } + } + + private static void validatePath(final Encoded uri, final String path, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(path, PATH_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid path", index+errIdx); + } + } + + private static void validateQuery(final Encoded uri, final Encoded query, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(query.get(), QUERY_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid query", index+errIdx); + } + } + + private static void validateFragment(final Encoded uri, final Encoded fragment, final int index) throws URISyntaxException { + final int errIdx = validateEncoded(fragment.get(), FRAG_LEGAL); + if( 0 <= errIdx ) { + fail(uri, "invalid fragment", index+errIdx); + } + } + + private static void validateUserinfo(final Encoded uri, final Encoded userinfo, final int index) throws URISyntaxException { + for (int i = 0; i < userinfo.length(); i++) { + final char ch = userinfo.charAt(i); + if (ch == ']' || ch == '[') { + fail(uri, "invalid userinfo", index+i); + } + } + } + + /** + * distinguish between IPv4, IPv6, domain name and validate it based on + * its type + */ + private boolean isValidHost(final boolean expectServer, final Encoded host) throws URISyntaxException { + if (host.charAt(0) == '[') { + // ipv6 address + if (host.charAt(host.length() - 1) != ']') { + fail(input, "invalid host, missing closing ipv6: "+host, 0); + } + if (!isValidIP6Address(host.get())) { + fail(input, "invalid ipv6: "+host, 0); + } + return true; + } + + // '[' and ']' can only be the first char and last char + // of the host name + if (host.indexOf('[') != -1 || host.indexOf(']') != -1) { + fail(input, "invalid host: "+host, 0); + } + + final int index = host.lastIndexOf('.'); + if ( index < 0 || index == host.length() - 1 || + !Character.isDigit(host.charAt(index + 1)) ) + { + // domain name + if (isValidDomainName(host)) { + return true; + } + if (expectServer) { + fail(input, "invalid host, invalid domain-name or ipv4: "+host, 0); + } + return false; + } + + // IPv4 address + if (isValidIPv4Address(host.get())) { + return true; + } + if (expectServer) { + fail(input, "invalid host, invalid ipv4: "+host, 0); + } + return false; + } + + private static boolean isValidDomainName(final Encoded host) { + final String hostS = host.get(); + if( 0 <= validateAlphaNum(hostS, "-.") ) { + return false; + } + String label = null; + final StringTokenizer st = new StringTokenizer(hostS, "."); + while (st.hasMoreTokens()) { + label = st.nextToken(); + if (label.startsWith("-") || label.endsWith("-")) { + return false; + } + } + + if (!label.equals(hostS)) { + final char ch = label.charAt(0); + if (ch >= '0' && ch <= '9') { + return false; + } + } + return true; + } + + private static boolean isValidIPv4Address(final String ipv4Address) { + int index; + int index2; + try { + int num; + index = ipv4Address.indexOf('.'); + num = Integer.parseInt(ipv4Address.substring(0, index)); + if (num < 0 || num > 255) { + return false; + } + index2 = ipv4Address.indexOf('.', index + 1); + num = Integer.parseInt(ipv4Address.substring(index + 1, index2)); + if (num < 0 || num > 255) { + return false; + } + index = ipv4Address.indexOf('.', index2 + 1); + num = Integer.parseInt(ipv4Address.substring(index2 + 1, index)); + if (num < 0 || num > 255) { + return false; + } + num = Integer.parseInt(ipv4Address.substring(index + 1)); + if (num < 0 || num > 255) { + return false; + } + } catch (final Exception e) { + return false; + } + return true; + } + + private static boolean isValidIP6Address(final String ipv6Address) { + final int length = ipv6Address.length(); + boolean doubleColon = false; + int numberOfColons = 0; + int numberOfPeriods = 0; + String word = ""; + char c = 0; + char prevChar = 0; + int offset = 0; // offset for [] ip addresses + + if (length < 2) { + return false; + } + + for (int i = 0; i < length; i++) { + prevChar = c; + c = ipv6Address.charAt(i); + switch (c) { + + // case for an open bracket [x:x:x:...x] + case '[': + if (i != 0) { + return false; // must be first character + } + if (ipv6Address.charAt(length - 1) != ']') { + return false; // must have a close ] + } + if ((ipv6Address.charAt(1) == SCHEME_SEPARATOR) + && (ipv6Address.charAt(2) != SCHEME_SEPARATOR)) { + return false; + } + offset = 1; + if (length < 4) { + return false; + } + break; + + // case for a closed bracket at end of IP [x:x:x:...x] + case ']': + if (i != length - 1) { + return false; // must be last character + } + if (ipv6Address.charAt(0) != '[') { + return false; // must have a open [ + } + break; + + // case for the last 32-bits represented as IPv4 + // x:x:x:x:x:x:d.d.d.d + case '.': + numberOfPeriods++; + if (numberOfPeriods > 3) { + return false; + } + if (!isValidIP4Word(word)) { + return false; + } + if (numberOfColons != 6 && !doubleColon) { + return false; + } + // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons + // with + // an IPv4 ending, otherwise 7 :'s is bad + if (numberOfColons == 7 + && ipv6Address.charAt(0 + offset) != SCHEME_SEPARATOR + && ipv6Address.charAt(1 + offset) != SCHEME_SEPARATOR) { + return false; + } + word = ""; + break; + + case SCHEME_SEPARATOR: + numberOfColons++; + if (numberOfColons > 7) { + return false; + } + if (numberOfPeriods > 0) { + return false; + } + if (prevChar == SCHEME_SEPARATOR) { + if (doubleColon) { + return false; + } + doubleColon = true; + } + word = ""; + break; + + default: + if (word.length() > 3) { + return false; + } + if (!isValidHexChar(c)) { + return false; + } + word += c; + } + } + + // Check if we have an IPv4 ending + if (numberOfPeriods > 0) { + if (numberOfPeriods != 3 || !isValidIP4Word(word)) { + return false; + } + } else { + // If we're at then end and we haven't had 7 colons then there + // is a problem unless we encountered a doubleColon + if (numberOfColons != 7 && !doubleColon) { + return false; + } + + // If we have an empty word at the end, it means we ended in + // either a : or a . + // If we did not end in :: then this is invalid + if (word == "" && ipv6Address.charAt(length - 1 - offset) != SCHEME_SEPARATOR + && ipv6Address.charAt(length - 2 - offset) != SCHEME_SEPARATOR) { + return false; + } + } + + return true; + } + + private static boolean isValidIP4Word(final String word) { + char c; + if (word.length() < 1 || word.length() > 3) { + return false; + } + for (int i = 0; i < word.length(); i++) { + c = word.charAt(i); + if (!(c >= '0' && c <= '9')) { + return false; + } + } + if (Integer.parseInt(word) > 255) { + return false; + } + return true; + } + + /** + * Validate a string by checking if it contains any characters other than: + * <ol> + * <li>letters ('a'..'z', 'A'..'Z')</li> + * <li>numbers ('0'..'9')</li> + * <li>characters in the legal-set parameter</li> + * <li> others (unicode characters that are not in + * US-ASCII set, and are not ISO Control or are not ISO Space characters)</li> + * </ol> + * + * @param encoded + * {@code java.lang.String} the string to be validated + * @param legal + * {@code java.lang.String} the characters allowed in the String + * s + */ + private static int validateEncoded(final String encoded, final String legal) { + for (int i = 0; i < encoded.length();) { + final char ch = encoded.charAt(i); + if (ch == '%') { + do { + if (i + 2 >= encoded.length()) { + throw new IllegalArgumentException("missing '%' hex-digits at index "+i); + } + final int d1 = Character.digit(encoded.charAt(i + 1), 16); + final int d2 = Character.digit(encoded.charAt(i + 2), 16); + if (d1 == -1 || d2 == -1) { + throw new IllegalArgumentException("invalid hex-digits at index "+i+": "+encoded.substring(i, i + 3)); + } + i += 3; + } while (i < encoded.length() && encoded.charAt(i) == '%'); + continue; + } + if ( !( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || legal.indexOf(ch) > -1 || + (ch > 127 && !Character.isSpaceChar(ch) && !Character.isISOControl(ch)) + ) + ) { + return i; + } + i++; + } + return -1; + } + private static int validateAlphaNum(final String s, final String legal) { + for (int i = 0; i < s.length();) { + final char ch = s.charAt(i); + if ( !( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || legal.indexOf(ch) > -1 + ) + ) { + return i; + } + i++; + } + return -1; + } + + private static boolean isValidHexChar(final char c) { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + } + private static boolean emptyString(final Encoded s) { + return null == s || 0 == s.length(); + } + private static boolean emptyString(final String s) { + return null == s || 0 == s.length(); + } + + private static void fail(final Encoded input, final String reason, final int p) throws URISyntaxException { + throw new URISyntaxException(input.get(), reason, p); + } + private static void failExpecting(final Encoded input, final String expected, final int p) throws URISyntaxException { + fail(input, "Expecting " + expected, p); + } +}
\ No newline at end of file diff --git a/java_net/org/jau/net/UriQueryProps.java b/java_net/org/jau/net/UriQueryProps.java new file mode 100644 index 0000000..d8999c1 --- /dev/null +++ b/java_net/org/jau/net/UriQueryProps.java @@ -0,0 +1,136 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net; + +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Helper class to process URI's query, handled as properties. + * <p> + * The order of the URI segments (any properties) are <i>not</i> preserved. + * </p> + * <pre> + * URI: [scheme:][//authority][path][?query][#fragment] + * w/ authority: [user-info@]host[:port] + * Note: 'path' starts w/ fwd slash + * </pre> + * <p> + * Since 2.3.0 renamed from {@code URIQueryProps} to {@code UriQueryProps}, + * and using {@link Uri} instead of {@link java.net.URI}. + * </p> + */ +public class UriQueryProps { + private static final String QMARK = "?"; + private static final char ASSIG = '='; + private static final String EMPTY = ""; + private final String query_separator; + + private final HashMap<String, String> properties = new HashMap<String, String>(); + + private UriQueryProps(final char querySeparator) { + query_separator = String.valueOf(querySeparator); + } + + public final Map<String, String> getProperties() { return properties; } + public final char getQuerySeparator() { return query_separator.charAt(0); } + + public final Uri.Encoded appendQuery(Uri.Encoded baseQuery) { + boolean needsSep = false; + final StringBuilder sb = new StringBuilder(); + if ( null != baseQuery ) { + if( baseQuery.startsWith(QMARK) ) { + baseQuery = baseQuery.substring(1); // cut off '?' + } + sb.append(baseQuery.get()); + if( !baseQuery.endsWith(query_separator) ) { + needsSep = true; + } + } + final Iterator<Entry<String, String>> entries = properties.entrySet().iterator(); + while(entries.hasNext()) { + if(needsSep) { + sb.append(query_separator); + } + final Entry<String, String> entry = entries.next(); + sb.append(entry.getKey()); + if( EMPTY != entry.getValue() ) { + sb.append(ASSIG).append(entry.getValue()); + } + needsSep = true; + } + return new Uri.Encoded(sb.toString(), Uri.QUERY_LEGAL); + } + + public final Uri appendQuery(final Uri base) throws URISyntaxException { + return base.getNewQuery( appendQuery( base.query ) ); + } + + /** + * + * @param uri + * @param querySeparator should be either <i>;</i> or <i>&</i>, <i>;</i> is encouraged due to troubles of escaping <i>&</i>. + * @return + * @throws IllegalArgumentException if <code>querySeparator</code> is illegal, i.e. neither <i>;</i> nor <i>&</i> + */ + public static final UriQueryProps create(final Uri uri, final char querySeparator) throws IllegalArgumentException { + if( ';' != querySeparator && '&' != querySeparator ) { + throw new IllegalArgumentException("querySeparator is invalid: "+querySeparator); + } + final UriQueryProps data = new UriQueryProps(querySeparator); + final String q = Uri.decode(uri.query); + final int q_l = null != q ? q.length() : -1; + int q_e = -1; + while(q_e < q_l) { + final int q_b = q_e + 1; // next term + q_e = q.indexOf(querySeparator, q_b); + if(0 == q_e) { + // single separator + continue; + } + if(0 > q_e) { + // end + q_e = q_l; + } + // n-part + final String part = q.substring(q_b, q_e); + final int assignment = part.indexOf(ASSIG); + if(0 < assignment) { + // assignment + final String k = part.substring(0, assignment); + final String v = part.substring(assignment+1); + data.properties.put(k, v); + } else { + // property key only + data.properties.put(part, EMPTY); + } + } + return data; + } +} diff --git a/java_net/org/jau/net/asset/Handler.java b/java_net/org/jau/net/asset/Handler.java new file mode 100644 index 0000000..4aa628a --- /dev/null +++ b/java_net/org/jau/net/asset/Handler.java @@ -0,0 +1,63 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2012 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.net.asset; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import org.jau.net.AssetURLConnection; +import org.jau.net.AssetURLContext; + +/** + * {@link URLStreamHandler} to handle the asset protocol. + * + * <p> + * This is the <i>asset</i> URLStreamHandler variation + * using this class ClassLoader for the pkg factory model. + * </p> + */ +public class Handler extends URLStreamHandler { + static final AssetURLContext localCL = new AssetURLContext() { + @Override + public ClassLoader getClassLoader() { + return Handler.class.getClassLoader(); + } + }; + + public Handler() { + super(); + } + + @Override + protected URLConnection openConnection(final URL u) throws IOException { + final AssetURLConnection c = new AssetURLConnection(u, localCL); + c.connect(); + return c; + } + +} diff --git a/java_pkg/CMakeLists.txt b/java_pkg/CMakeLists.txt new file mode 100644 index 0000000..72e5d5c --- /dev/null +++ b/java_pkg/CMakeLists.txt @@ -0,0 +1,19 @@ +# java/CMakeLists.txt + +set(CMAKE_JNI_TARGET TRUE) +file(GLOB_RECURSE JAVA_SOURCES "*.java") +add_jar(jaulib_pkg_jar + ${JAVA_SOURCES} + INCLUDE_JARS jaulib_base_jar jaulib_jni_jar jaulib_net_jar + MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt + OUTPUT_NAME jaulib_pkg + GENERATE_NATIVE_HEADERS jaulib_pkg_javah + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_pkg_jar.dir/jni" +) +add_dependencies(jaulib_pkg_jar jaulib_base_jar jaulib_jni_jar jaulib_net_jar) + +set(JNI_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/jaulib_pkg_jar.dir/jni") +install (FILES ${CMAKE_CURRENT_BINARY_DIR}/jaulib_pkg.jar DESTINATION ${CMAKE_INSTALL_LIBDIR}/../lib/java) + +add_subdirectory (jni) + diff --git a/java_pkg/jni/CMakeLists.txt b/java_pkg/jni/CMakeLists.txt new file mode 100644 index 0000000..68c8d54 --- /dev/null +++ b/java_pkg/jni/CMakeLists.txt @@ -0,0 +1,33 @@ +find_package(JNI REQUIRED) + +set (jaulib_base_LIB_INCLUDE_DIRS + ${PROJECT_SOURCE_DIR}/include +) + +include_directories( + ${JNI_INCLUDE_DIRS} + ${jaulib_base_LIB_INCLUDE_DIRS} + ${JNI_HEADER_PATH} +) + +set (jaulib_pkg_JNI_SRCS + ${PROJECT_SOURCE_DIR}/java_pkg/jni/jau/JarUtil.cxx + ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/JVM_JNI8.cxx +) + +set (CMAKE_SHARED_LINKER_FLAGS "-Wl,--as-needed") + +add_library (jaulib_pkg_jni SHARED ${jaulib_pkg_JNI_SRCS}) +target_link_libraries(jaulib_pkg_jni ${JNI_LIBRARIES}) + +set_target_properties( + jaulib_pkg_jni + PROPERTIES + SOVERSION ${jaulib_VERSION_MAJOR} + VERSION ${jaulib_VERSION_STRING} +) + +install(TARGETS jaulib_pkg_jni LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +add_dependencies(jaulib_pkg_jni jaulib jaulib_base_jar jaulib_jni_jar jaulib_net_jar jaulib_pkg_jar) + diff --git a/java_pkg/jni/jau/JVM_JNI8.cxx b/java_pkg/jni/jau/JVM_JNI8.cxx new file mode 100644 index 0000000..cde8bd4 --- /dev/null +++ b/java_pkg/jni/jau/JVM_JNI8.cxx @@ -0,0 +1,46 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +#include <stdio.h> //required by android to identify NULL +#include <jni.h> + +#if defined (JNI_VERSION_1_8) + +JNIEXPORT jint JNICALL JNI_OnLoad_jaulib_pkg_jni(JavaVM *vm, void *reserved) { + (void)vm; + (void)reserved; + return JNI_VERSION_1_8; +} + +JNIEXPORT void JNICALL JNI_OnUnload_jaulib_pkg_jni(JavaVM *vm, void *reserved) { + (void)vm; + (void)reserved; +} + +#endif /* defined (JNI_VERSION_1_8) */ + diff --git a/java_pkg/jni/jau/JarUtil.cxx b/java_pkg/jni/jau/JarUtil.cxx new file mode 100644 index 0000000..cb2b3c8 --- /dev/null +++ b/java_pkg/jni/jau/JarUtil.cxx @@ -0,0 +1,43 @@ +#include <jni.h> + +#include <assert.h> + +#include "org_jau_pkg_JarUtil.h" + +#include "jau/jni/helper_jni.hpp" + +#if defined(__APPLE__) + #include <sys/xattr.h> + static const char kQuarantineAttrName[] = "com.apple.quarantine"; +#endif + +/* + * Class: com_org_jau_pkg_JarUtil + * Method: fixNativeLibAttribs + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_com_org_jau_pkg_JarUtil_fixNativeLibAttribs + (JNIEnv *env, jclass _unused, jstring fname) { + (void)_unused; + + const char* _UTF8fname = NULL; + int status = 0; + if (fname != NULL) { + if (fname != NULL) { + _UTF8fname = env->GetStringUTFChars(fname, (jboolean*)NULL); + if (_UTF8fname == NULL) { + env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), + "Failed to get UTF-8 chars for argument \"fname\" in native dispatcher for \"removexattr\""); + return 0; + } + } + } +#if defined(__APPLE__) + status = removexattr(_UTF8fname, kQuarantineAttrName, 0); +#endif + if (fname != NULL) { + env->ReleaseStringUTFChars(fname, _UTF8fname); + } + return 0 == status ? JNI_TRUE : JNI_FALSE; +} + diff --git a/java_pkg/manifest.txt.in b/java_pkg/manifest.txt.in new file mode 100644 index 0000000..69bf6a9 --- /dev/null +++ b/java_pkg/manifest.txt.in @@ -0,0 +1,27 @@ +Manifest-Version: 1.0 +Bundle-Date: @BUILD_TSTAMP@ +Bundle-ManifestVersion: 2 +Bundle-Name: org.jau.net +Bundle-SymbolicName: org.jau.net +Bundle-Version: @VERSION_SHORT@ +Export-Package: org.jau.net +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.9))" +Package-Title: org.jau.net +Package-Vendor: Gothel Software +Package-Version: @VERSION_SHORT@ +Specification-Title: Jaulib NET +Specification-Vendor: Gothel Software +Specification-Version: @VERSION_API@ +Implementation-Title: Jaulib NET +Implementation-Vendor: Gothel Software +Implementation-Version: @VERSION@ +Implementation-Commit: @VERSION_SHA1@ +Implementation-URL: http://www.jausoft.com/ +Extension-Name: org.jau.net +Trusted-Library: true +Permissions: all-permissions +Application-Library-Allowable-Codebase: * + +Name: org/jau/net +Sealed: false + diff --git a/java_pkg/org/jau/pkg/JNIJarLibrary.java b/java_pkg/org/jau/pkg/JNIJarLibrary.java new file mode 100644 index 0000000..0febf68 --- /dev/null +++ b/java_pkg/org/jau/pkg/JNIJarLibrary.java @@ -0,0 +1,333 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.pkg; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.Locale; + +import org.jau.net.Uri; +import org.jau.pkg.cache.TempJarCache; +import org.jau.sys.JNILibrary; +import org.jau.sys.PlatformProps; + +/** + * Static JNI Native Libraries handler including functionality for native JAR files using {@link JarUtil} and {@link TempJarCache}. + */ +public class JNIJarLibrary extends JNILibrary { + private static final String nativeJarTagPackage = "jau.nativetag"; // TODO: sync with gluegen-cpptasks-base.xml + + /** + * + * @param classFromJavaJar + * @param classJarUri + * @param jarBasename jar basename w/ suffix + * @param nativeJarBasename native jar basename w/ suffix + * @return + * @throws IOException + * @throws SecurityException + * @throws URISyntaxException + */ + private static final boolean addNativeJarLibsImpl(final Class<?> classFromJavaJar, final Uri classJarUri, + final Uri.Encoded jarBasename, final Uri.Encoded nativeJarBasename) + throws IOException, SecurityException, URISyntaxException + { + if (DEBUG) { + final StringBuilder msg = new StringBuilder(); + msg.append("JNILibrary: addNativeJarLibsImpl(").append(PlatformProps.NEWLINE); + msg.append(" classFromJavaJar = ").append(classFromJavaJar).append(PlatformProps.NEWLINE); + msg.append(" classJarURI = ").append(classJarUri).append(PlatformProps.NEWLINE); + msg.append(" jarBasename = ").append(jarBasename).append(PlatformProps.NEWLINE); + msg.append(" os.and.arch = ").append(PlatformProps.os_and_arch).append(PlatformProps.NEWLINE); + msg.append(" nativeJarBasename = ").append(nativeJarBasename).append(PlatformProps.NEWLINE); + msg.append(")"); + System.err.println(msg.toString()); + } + final long t0 = PERF ? System.currentTimeMillis() : 0; // 'Platform.currentTimeMillis()' not yet available! + + boolean ok = false; + + final Uri jarSubURI = classJarUri.getContainedUri(); + if (null == jarSubURI) { + throw new IllegalArgumentException("JarSubURI is null of: "+classJarUri); + } + + final Uri jarSubUriRoot = jarSubURI.getDirectory(); + + if (DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: initial: %s -> %s%n", jarSubURI, jarSubUriRoot); + } + + final String nativeLibraryPath = String.format((Locale)null, "natives/%s/", PlatformProps.os_and_arch); + if (DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: nativeLibraryPath: %s%n", nativeLibraryPath); + } + { + // Attempt-1 a 'one slim native jar file' per 'os.and.arch' layout + // with native platform libraries under 'natives/os.and.arch'! + final Uri nativeJarURI = JarUtil.getJarFileUri( jarSubUriRoot.getEncoded().concat(nativeJarBasename) ); + + if (DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: module: %s -> %s%n", nativeJarBasename, nativeJarURI); + } + + try { + ok = TempJarCache.addNativeLibs(classFromJavaJar, nativeJarURI, nativeLibraryPath); + } catch(final Exception e) { + if(DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: Caught %s%n", e.getMessage()); + e.printStackTrace(); + } + } + } + if (!ok) { + final ClassLoader cl = classFromJavaJar.getClassLoader(); + { + // Attempt-2 a 'one big-fat jar file' layout, containing java classes + // and all native platform libraries under 'natives/os.and.arch' per platform! + final URL nativeLibraryURI = cl.getResource(nativeLibraryPath); + if (null != nativeLibraryURI) { + final Uri nativeJarURI = JarUtil.getJarFileUri( jarSubUriRoot.getEncoded().concat(jarBasename) ); + try { + if( TempJarCache.addNativeLibs(classFromJavaJar, nativeJarURI, nativeLibraryPath) ) { + ok = true; + if (DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: fat: %s -> %s%n", jarBasename, nativeJarURI); + } + } + } catch(final Exception e) { + if(DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: Caught %s%n", e.getMessage()); + e.printStackTrace(); + } + } + } + } + if (!ok) { + // Attempt-3 to find via ClassLoader and Native-Jar-Tag, + // assuming one slim native jar file per 'os.and.arch' + // and native platform libraries under 'natives/os.and.arch'! + final String moduleName; + { + final String packageName = classFromJavaJar.getPackage().getName(); + final int idx = packageName.lastIndexOf('.'); + if( 0 <= idx ) { + moduleName = packageName.substring(idx+1); + } else { + moduleName = packageName; + } + } + final String os_and_arch_dot = PlatformProps.os_and_arch.replace('-', '.'); + final String nativeJarTagClassName = nativeJarTagPackage + "." + moduleName + "." + os_and_arch_dot + ".TAG"; // TODO: sync with gluegen-cpptasks-base.xml + try { + if(DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: ClassLoader/TAG: Locating module %s, os.and.arch %s: %s%n", + moduleName, os_and_arch_dot, nativeJarTagClassName); + } + final Uri nativeJarTagClassJarURI = JarUtil.getJarUri(nativeJarTagClassName, cl); + if (DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: ClassLoader/TAG: %s -> %s%n", nativeJarTagClassName, nativeJarTagClassJarURI); + } + ok = TempJarCache.addNativeLibs(classFromJavaJar, nativeJarTagClassJarURI, nativeLibraryPath); + } catch (final Exception e ) { + if(DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsImpl: Caught %s%n", e.getMessage()); + e.printStackTrace(); + } + } + } + } + + if (DEBUG || PERF) { + final long tNow = System.currentTimeMillis() - t0; + final long tTotal, tCount; + synchronized(perfSync) { + tCount = perfCount+1; + tTotal = perfTotal + tNow; + perfTotal = tTotal; + perfCount = tCount; + } + final double tAvrg = tTotal / (double)tCount; + System.err.printf("JNILibrary: addNativeJarLibsImpl.X: %s / %s -> ok: %b; duration: now %d ms, total %d ms (count %d, avrg %.3f ms)%n", + jarBasename, nativeJarBasename, ok, tNow, tTotal, tCount, tAvrg); + } + return ok; + } + + /** + * Loads and adds a JAR file's native library to the TempJarCache.<br> + * The native library JAR file's URI is derived as follows: + * <ul> + * <li> [1] <code>GLProfile.class</code> -> </li> + * <li> [2] <code>http://lala/gluegen-rt.jar</code> -> </li> + * <li> [3] <code>http://lala/gluegen-rt</code> -> </li> + * <li> [4] <code>http://lala/gluegen-rt-natives-'os.and.arch'.jar</code> </li> + * </ul> + * Where: + * <ul> + * <li> [1] is one of <code>classesFromJavaJars</code></li> + * <li> [2] is it's complete URI</li> + * <li> [3] is it's <i>base URI</i></li> + * <li> [4] is the derived native JAR filename</li> + * </ul> + * <p> + * Generic description: + * <pre> + final Class<?>[] classesFromJavaJars = new Class<?>[] { Class1.class, Class2.class }; + JNILibrary.addNativeJarLibs(classesFromJavaJars, "-all"); + * </pre> + * If <code>Class1.class</code> is contained in a JAR file which name includes <code>singleJarMarker</code>, here <i>-all</i>, + * implementation will attempt to resolve the native JAR file as follows: + * <ul> + * <li><i>ClassJar-all</i>.jar to <i>ClassJar-all</i>-natives-<i>os.and.arch</i>.jar</li> + * </ul> + * Otherwise the native JAR files will be resolved for each class's JAR file: + * <ul> + * <li><i>Class1Jar</i>.jar to <i>Class1Jar</i>-natives-<i>os.and.arch</i>.jar</li> + * <li><i>Class2Jar</i>.jar to <i>Class2Jar</i>-natives-<i>os.and.arch</i>.jar</li> + * </ul> + * </p> + * <p> + * Examples: + * </p> + * <p> + * JOCL: + * <pre> + // only: jocl.jar -> jocl-natives-<i>os.and.arch</i>.jar + addNativeJarLibs(new Class<?>[] { JOCLJNILibrary.class }, null, null ); + * </pre> + * </p> + * <p> + * JOGL: + * <pre> + final ClassLoader cl = GLProfile.class.getClassLoader(); + // jogl-all.jar -> jogl-all-natives-<i>os.and.arch</i>.jar + // jogl-all-noawt.jar -> jogl-all-noawt-natives-<i>os.and.arch</i>.jar + // jogl-all-mobile.jar -> jogl-all-mobile-natives-<i>os.and.arch</i>.jar + // jogl-all-android.jar -> jogl-all-android-natives-<i>os.and.arch</i>.jar + // nativewindow.jar -> nativewindow-natives-<i>os.and.arch</i>.jar + // jogl.jar -> jogl-natives-<i>os.and.arch</i>.jar + // newt.jar -> newt-natives-<i>os.and.arch</i>.jar (if available) + final String newtFactoryClassName = "com.jogamp.newt.NewtFactory"; + final Class<?>[] classesFromJavaJars = new Class<?>[] { NWJNILibrary.class, GLProfile.class, null }; + if( ReflectionUtil.isClassAvailable(newtFactoryClassName, cl) ) { + classesFromJavaJars[2] = ReflectionUtil.getClass(newtFactoryClassName, false, cl); + } + JNILibrary.addNativeJarLibs(classesFromJavaJars, "-all"); + * </pre> + * </p> + * + * @param classesFromJavaJars For each given Class, load the native library JAR. + * @param singleJarMarker Optional string marker like "-all" to identify the single 'all-in-one' JAR file + * after which processing of the class array shall stop. + * + * @return true if either the 'all-in-one' native JAR or all native JARs loaded successful or were loaded already, + * false in case of an error + */ + public static boolean addNativeJarLibs(final Class<?>[] classesFromJavaJars, final String singleJarMarker) { + if(DEBUG) { + final StringBuilder msg = new StringBuilder(); + msg.append("JNILibrary: addNativeJarLibs(").append(PlatformProps.NEWLINE); + msg.append(" classesFromJavaJars = ").append(Arrays.asList(classesFromJavaJars)).append(PlatformProps.NEWLINE); + msg.append(" singleJarMarker = ").append(singleJarMarker).append(PlatformProps.NEWLINE); + msg.append(")"); + System.err.println(msg.toString()); + } + + boolean ok = false; + if ( TempJarCache.isInitialized(true) ) { + ok = addNativeJarLibsWithTempJarCache(classesFromJavaJars, singleJarMarker); + } else if(DEBUG) { + System.err.println("JNILibrary: addNativeJarLibs0: disabled due to uninitialized TempJarCache"); + } + return ok; + } + + private static boolean addNativeJarLibsWithTempJarCache(final Class<?>[] classesFromJavaJars, final String singleJarMarker) { + boolean ok; + int count = 0; + try { + boolean done = false; + ok = true; + + for (int i = 0; i < classesFromJavaJars.length; ++i) { + final Class<?> c = classesFromJavaJars[i]; + if (c == null) { + continue; + } + + final ClassLoader cl = c.getClassLoader(); + final Uri classJarURI = JarUtil.getJarUri(c.getName(), cl); + final Uri.Encoded jarName = JarUtil.getJarBasename(classJarURI); + + if (jarName == null) { + continue; + } + + final Uri.Encoded jarBasename = jarName.substring(0, jarName.indexOf(".jar")); + + if(DEBUG) { + System.err.printf("JNILibrary: jarBasename: %s%n", jarBasename); + } + + /** + * If a jar marker was specified, and the basename contains the + * marker, we're done. + */ + + if (singleJarMarker != null) { + if (jarBasename.indexOf(singleJarMarker) >= 0) { + done = true; + } + } + + final Uri.Encoded nativeJarBasename = + Uri.Encoded.cast( String.format((Locale)null, "%s-natives-%s.jar", jarBasename.get(), PlatformProps.os_and_arch) ); + + ok = JNIJarLibrary.addNativeJarLibsImpl(c, classJarURI, jarName, nativeJarBasename); + if (ok) { + count++; + } + if (DEBUG && done) { + System.err.printf("JNILibrary: addNativeJarLibs0: done: %s%n", jarBasename); + } + } + } catch (final Exception x) { + System.err.printf("JNILibrary: Caught %s: %s%n", x.getClass().getSimpleName(), x.getMessage()); + if(DEBUG) { + x.printStackTrace(); + } + ok = false; + } + if(DEBUG) { + System.err.printf("JNILibrary: addNativeJarLibsWhenInitialized: count %d, ok %b%n", count, ok); + } + return ok; + } + +} diff --git a/java_pkg/org/jau/pkg/JarUtil.java b/java_pkg/org/jau/pkg/JarUtil.java new file mode 100644 index 0000000..c46d7e6 --- /dev/null +++ b/java_pkg/org/jau/pkg/JarUtil.java @@ -0,0 +1,740 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.pkg; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.security.cert.Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.jau.io.IOUtil; +import org.jau.net.Uri; +import org.jau.sec.SecurityUtil; +import org.jau.sys.Debug; +import org.jau.sys.JNILibrary; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes; + +public class JarUtil { + private static final boolean DEBUG = Debug.debug("JarUtil"); + + private static final int BUFFER_SIZE = 4096; + + /** + * Interface allowing users to provide an URL resolver that will convert custom classloader + * URLs like Eclipse/OSGi <i>bundleresource:</i> URLs to normal <i>jar:</i> URLs. + * <p> + * This might be required for custom classloader where the URI protocol is unknown + * to the standard runtime environment. + * </p> + * <p> + * Note: The provided resolver is only utilized if a given URI's protocol could not be resolved. + * I.e. it will not be invoked for known protocols like <i>http</i>, <i>https</i>, <i>jar</i> or <i>file</i>. + * </p> + */ + public interface Resolver { + URL resolve(URL url); + } + + private static Resolver resolver; + + /** + * Setting a custom {@link Resolver} instance. + * + * @param r {@link Resolver} to use after querying class file URLs from the classloader. + * @throws IllegalArgumentException if the passed resolver is <code>null</code> + * @throws IllegalStateException if the resolver has already been set. + * @throws SecurityException if the security manager doesn't have the setFactory + * permission + */ + public static void setResolver(final Resolver r) throws IllegalArgumentException, IllegalStateException, SecurityException { + if(r == null) { + throw new IllegalArgumentException("Null Resolver passed"); + } + + if(resolver != null) { + throw new IllegalStateException("Resolver already set!"); + } + + final SecurityManager security = System.getSecurityManager(); + if(security != null) { + security.checkSetFactory(); + } + + resolver = r; + } + + /** + * Returns <code>true</code> if the Class's <code>"com.jogamp.common.GlueGenVersion"</code> + * is loaded from a JarFile and hence has a Jar URI like + * URI <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"</code>. + * <p> + * <i>sub_protocol</i> may be "file", "http", etc.. + * </p> + * + * @param clazzBinName "com.jogamp.common.GlueGenVersion" + * @param cl + * @return true if the class is loaded from a Jar file, otherwise false. + * @see {@link #getJarUri(String, ClassLoader)} + */ + public static boolean hasJarUri(final String clazzBinName, final ClassLoader cl) { + try { + return null != getJarUri(clazzBinName, cl); + } catch (final Exception e) { /* ignore */ } + return false; + } + + /** + * The Class's <code>"com.jogamp.common.GlueGenVersion"</code> + * Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"</code> + * will be returned. + * <p> + * <i>sub_protocol</i> may be "file", "http", etc.. + * </p> + * + * @param clazzBinName "com.jogamp.common.GlueGenVersion" + * @param cl ClassLoader to locate the JarFile + * @return "jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class" + * @throws IllegalArgumentException if the Uri doesn't match the expected formatting or null arguments + * @throws IOException if the class's Jar file could not been found by the ClassLoader + * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static Uri getJarUri(final String clazzBinName, final ClassLoader cl) throws IllegalArgumentException, IOException, URISyntaxException { + if(null == clazzBinName || null == cl) { + throw new IllegalArgumentException("null arguments: clazzBinName "+clazzBinName+", cl "+cl); + } + final Uri uri; + final URL url; + { + url = IOUtil.getClassURL(clazzBinName, cl); + final String scheme = url.getProtocol(); + if( null != resolver && + !scheme.equals( Uri.JAR_SCHEME ) && + !scheme.equals( Uri.FILE_SCHEME ) && + !scheme.equals( Uri.HTTP_SCHEME ) && + !scheme.equals( Uri.HTTPS_SCHEME ) ) + { + final URL _url = resolver.resolve( url ); + uri = Uri.valueOf(_url); + if(DEBUG) { + System.err.println("getJarUri Resolver: "+url+"\n\t-> "+_url+"\n\t-> "+uri); + } + } else { + uri = Uri.valueOf(url); + if(DEBUG) { + System.err.println("getJarUri Default "+url+"\n\t-> "+uri); + } + } + } + if( !uri.isJarScheme() ) { + throw new IllegalArgumentException("Uri is not using scheme "+Uri.JAR_SCHEME+": <"+uri+">"); + } + if(DEBUG) { + System.err.println("getJarUri res: "+clazzBinName+" -> "+url+" -> "+uri); + } + return uri; + } + + + /** + * The Class's Jar Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * Jar basename <code>gluegen-rt.jar</code> will be returned. + * <p> + * <i>sub_protocol</i> may be "file", "http", etc.. + * </p> + * + * @param classJarUri as retrieved w/ {@link #getJarUri(String, ClassLoader) getJarUri("com.jogamp.common.GlueGenVersion", cl)}, + * i.e. <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * @return <code>gluegen-rt.jar</code> + * @throws IllegalArgumentException if the Uri doesn't match the expected formatting or is null + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static Uri.Encoded getJarBasename(final Uri classJarUri) throws IllegalArgumentException { + if(null == classJarUri) { + throw new IllegalArgumentException("Uri is null"); + } + if( !classJarUri.isJarScheme() ) { + throw new IllegalArgumentException("Uri is not using scheme "+Uri.JAR_SCHEME+": <"+classJarUri+">"); + } + Uri.Encoded ssp = classJarUri.schemeSpecificPart; + + // from + // file:/some/path/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class + // to + // file:/some/path/gluegen-rt.jar + int idx = ssp.lastIndexOf(Uri.JAR_SCHEME_SEPARATOR); + if (0 <= idx) { + ssp = ssp.substring(0, idx); // exclude '!/' + } else { + throw new IllegalArgumentException("Uri does not contain jar uri terminator '!', in <"+classJarUri+">"); + } + + // from + // file:/some/path/gluegen-rt.jar + // to + // gluegen-rt.jar + idx = ssp.lastIndexOf('/'); + if(0 > idx) { + // no abs-path, check for protocol terminator ':' + idx = ssp.lastIndexOf(':'); + if(0 > idx) { + throw new IllegalArgumentException("Uri does not contain protocol terminator ':', in <"+classJarUri+">"); + } + } + ssp = ssp.substring(idx+1); // just the jar name + + if(0 >= ssp.lastIndexOf(".jar")) { + throw new IllegalArgumentException("No Jar name in <"+classJarUri+">"); + } + if(DEBUG) { + System.err.println("getJarName res: "+ssp); + } + return ssp; + } + + /** + * The Class's <code>com.jogamp.common.GlueGenVersion</code> + * Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * Jar basename <code>gluegen-rt.jar</code> will be returned. + * <p> + * <i>sub_protocol</i> may be "file", "http", etc.. + * </p> + * + * @param clazzBinName <code>com.jogamp.common.GlueGenVersion</code> + * @param cl + * @return <code>gluegen-rt.jar</code> + * @throws IllegalArgumentException if the Uri doesn't match the expected formatting + * @throws IOException if the class's Jar file could not been found by the ClassLoader. + * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static Uri.Encoded getJarBasename(final String clazzBinName, final ClassLoader cl) throws IllegalArgumentException, IOException, URISyntaxException { + return getJarBasename( getJarUri(clazzBinName, cl) ); + } + + /** + * The Class's Jar Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * Jar file's entry <code>/com/jogamp/common/GlueGenVersion.class</code> will be returned. + * + * @param classJarUri as retrieved w/ {@link #getJarUri(String, ClassLoader) getJarUri("com.jogamp.common.GlueGenVersion", cl)}, + * i.e. <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class</code> + * @return <code>/com/jogamp/common/GlueGenVersion.class</code> + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static Uri.Encoded getJarEntry(final Uri classJarUri) { + if(null == classJarUri) { + throw new IllegalArgumentException("Uri is null"); + } + if( !classJarUri.isJarScheme() ) { + throw new IllegalArgumentException("Uri is not a using scheme "+Uri.JAR_SCHEME+": <"+classJarUri+">"); + } + final Uri.Encoded uriSSP = classJarUri.schemeSpecificPart; + + // from + // file:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class + // to + // /com/jogamp/common/GlueGenVersion.class + final int idx = uriSSP.lastIndexOf(Uri.JAR_SCHEME_SEPARATOR); + if (0 <= idx) { + final Uri.Encoded res = uriSSP.substring(idx+1); // right of '!' + // Uri TODO ? final String res = Uri.decode(uriSSP.substring(idx+1)); // right of '!' + if(DEBUG) { + System.err.println("getJarEntry res: "+classJarUri+" -> "+uriSSP+" -> "+idx+" -> "+res); + } + return res; + } else { + throw new IllegalArgumentException("JAR Uri does not contain jar uri terminator '!', uri <"+classJarUri+">"); + } + } + + /** + * The Class's <code>"com.jogamp.common.GlueGenVersion"</code> + * Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class"</code> + * Jar file Uri <code>jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/</code> will be returned. + * <p> + * <i>sub_protocol</i> may be "file", "http", etc.. + * </p> + * + * @param clazzBinName "com.jogamp.common.GlueGenVersion" + * @param cl + * @return "jar:<i>sub_protocol</i>:/some/path/gluegen-rt.jar!/" + * @throws IllegalArgumentException if the Uri doesn't match the expected formatting or null arguments + * @throws IOException if the class's Jar file could not been found by the ClassLoader + * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri + * @see {@link IOUtil#getClassURL(String, ClassLoader)} + */ + public static Uri getJarFileUri(final String clazzBinName, final ClassLoader cl) throws IllegalArgumentException, IOException, URISyntaxException { + if(null == clazzBinName || null == cl) { + throw new IllegalArgumentException("null arguments: clazzBinName "+clazzBinName+", cl "+cl); + } + final Uri jarSubUri = getJarUri(clazzBinName, cl).getContainedUri(); + final Uri uri = Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+jarSubUri.toString()+"!/"); + if(DEBUG) { + System.err.println("getJarFileUri res: "+uri); + } + return uri; + } + + /** + * @param baseUri file:/some/path/ + * @param jarFileName gluegen-rt.jar (Uri encoded) + * @return jar:file:/some/path/gluegen-rt.jar!/ + * @throws URISyntaxException + * @throws IllegalArgumentException null arguments + */ + public static Uri getJarFileUri(final Uri baseUri, final Uri.Encoded jarFileName) throws IllegalArgumentException, URISyntaxException { + if(null == baseUri || null == jarFileName) { + throw new IllegalArgumentException("null arguments: baseUri "+baseUri+", jarFileName "+jarFileName); + } + return Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+baseUri.toString()+jarFileName+"!/"); + } + + /** + * @param jarSubUri file:/some/path/gluegen-rt.jar + * @return jar:file:/some/path/gluegen-rt.jar!/ + * @throws IllegalArgumentException null arguments + * @throws URISyntaxException + */ + public static Uri getJarFileUri(final Uri jarSubUri) throws IllegalArgumentException, URISyntaxException { + if(null == jarSubUri) { + throw new IllegalArgumentException("jarSubUri is null"); + } + return Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+jarSubUri.toString()+"!/"); + } + + /** + * @param jarSubUriS file:/some/path/gluegen-rt.jar (Uri encoded) + * @return jar:file:/some/path/gluegen-rt.jar!/ + * @throws IllegalArgumentException null arguments + * @throws URISyntaxException + */ + public static Uri getJarFileUri(final Uri.Encoded jarSubUriS) throws IllegalArgumentException, URISyntaxException { + if(null == jarSubUriS) { + throw new IllegalArgumentException("jarSubUriS is null"); + } + return Uri.cast(Uri.JAR_SCHEME+Uri.SCHEME_SEPARATOR+jarSubUriS+"!/"); + } + + /** + * @param jarFileUri jar:file:/some/path/gluegen-rt.jar!/ + * @param jarEntry com/jogamp/common/GlueGenVersion.class + * @return jar:file:/some/path/gluegen-rt.jar!/com/jogamp/common/GlueGenVersion.class + * @throws IllegalArgumentException null arguments + * @throws URISyntaxException + */ + public static Uri getJarEntryUri(final Uri jarFileUri, final Uri.Encoded jarEntry) throws IllegalArgumentException, URISyntaxException { + if(null == jarEntry) { + throw new IllegalArgumentException("jarEntry is null"); + } + return Uri.cast(jarFileUri.toString()+jarEntry); + } + + /** + * @param clazzBinName com.jogamp.common.util.cache.TempJarCache + * @param cl domain + * @return JarFile containing the named class within the given ClassLoader + * @throws IOException if the class's Jar file could not been found by the ClassLoader + * @throws IllegalArgumentException null arguments + * @throws URISyntaxException if the Uri could not be translated into a RFC 2396 Uri + * @see {@link #getJarFileUri(String, ClassLoader)} + */ + public static JarFile getJarFile(final String clazzBinName, final ClassLoader cl) throws IOException, IllegalArgumentException, URISyntaxException { + return getJarFile( getJarFileUri(clazzBinName, cl) ); + } + + /** + * @param jarFileUri jar:file:/some/path/gluegen-rt.jar!/ + * @return JarFile as named by Uri within the given ClassLoader + * @throws IllegalArgumentException null arguments + * @throws IOException if the Jar file could not been found + * @throws URISyntaxException + */ + public static JarFile getJarFile(final Uri jarFileUri) throws IOException, IllegalArgumentException, URISyntaxException { + if(null == jarFileUri) { + throw new IllegalArgumentException("null jarFileUri"); + } + if(DEBUG) { + System.err.println("getJarFile.0: "+jarFileUri.toString()); + } + final URL jarFileURL = jarFileUri.toURL(); + if(DEBUG) { + System.err.println("getJarFile.1: "+jarFileURL.toString()); + } + final URLConnection urlc = jarFileURL.openConnection(); + if(urlc instanceof JarURLConnection) { + final JarURLConnection jarConnection = (JarURLConnection)jarFileURL.openConnection(); + final JarFile jarFile = jarConnection.getJarFile(); + if(DEBUG) { + System.err.println("getJarFile res: "+jarFile.getName()); + } + return jarFile; + } + if(DEBUG) { + System.err.println("getJarFile res: NULL"); + } + return null; + } + + /** + * Locates the {@link JarUtil#getJarFileUri(Uri) Jar file Uri} of a given resource + * relative to a given class's Jar's Uri. + * <pre> + * class's jar url path + cutOffInclSubDir + relResPath, + * </pre> + * Example #1 + * <pre> + * classFromJavaJar = com.lighting.Test (in: file:/storage/TestLighting.jar) + * cutOffInclSubDir = lights/ + * relResPath = LightAssets.jar + * Result : file:/storage/lights/LightAssets.jar + * </pre> + * Example #2 + * <pre> + * classFromJavaJar = com.lighting.Test (in: file:/storage/lights/TestLighting.jar) + * cutOffInclSubDir = lights/ + * relResPath = LightAssets.jar + * Result : file:/storage/lights/LightAssets.jar + * </pre> + * + * TODO: Enhance documentation! + * + * @param classFromJavaJar Used to get the root Uri for the class's Jar Uri. + * @param cutOffInclSubDir The <i>cut off</i> included sub-directory prepending the relative resource path. + * If the root Uri includes cutOffInclSubDir, it is no more added to the result. + * @param relResPath The relative resource path. (Uri encoded) + * @return The resulting resource Uri, which is not tested. + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public static Uri getRelativeOf(final Class<?> classFromJavaJar, final Uri.Encoded cutOffInclSubDir, final Uri.Encoded relResPath) throws IllegalArgumentException, IOException, URISyntaxException { + final ClassLoader cl = classFromJavaJar.getClassLoader(); + final Uri classJarUri = JarUtil.getJarUri(classFromJavaJar.getName(), cl); + if( DEBUG ) { + System.err.println("JarUtil.getRelativeOf: "+"(classFromJavaJar "+classFromJavaJar+", classJarUri "+classJarUri+ + ", cutOffInclSubDir "+cutOffInclSubDir+", relResPath "+relResPath+"): "); + } + + final Uri jarSubUri = classJarUri.getContainedUri(); + if(null == jarSubUri) { + throw new IllegalArgumentException("JarSubUri is null of: "+classJarUri); + } + final Uri.Encoded jarUriRoot = jarSubUri.getDirectory().getEncoded(); + if( DEBUG ) { + System.err.println("JarUtil.getRelativeOf: "+"uri "+jarSubUri.toString()+" -> "+jarUriRoot); + } + + final Uri.Encoded resUri; + if( jarUriRoot.endsWith(cutOffInclSubDir.get()) ) { + resUri = jarUriRoot.concat(relResPath); + } else { + resUri = jarUriRoot.concat(cutOffInclSubDir).concat(relResPath); + } + if( DEBUG ) { + System.err.println("JarUtil.getRelativeOf: "+"... -> "+resUri); + } + + final Uri resJarUri = JarUtil.getJarFileUri(resUri); + if( DEBUG ) { + System.err.println("JarUtil.getRelativeOf: "+"fin "+resJarUri); + } + return resJarUri; + } + + /** + * Return a map from native-lib-base-name to entry-name. + */ + public static Map<String, String> getNativeLibNames(final JarFile jarFile) { + if (DEBUG) { + System.err.println("JarUtil: getNativeLibNames: "+jarFile); + } + + final Map<String,String> nameMap = new HashMap<String, String>(); + final Enumeration<JarEntry> entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + final String entryName = entry.getName(); + final String baseName = JNILibrary.isValidNativeLibraryName(entryName, false); + + if(null != baseName) { + nameMap.put(baseName, entryName); + } + } + + return nameMap; + } + + /** + * Extract the files of the given jar file. + * <p> + * If <code>extractNativeLibraries</code> is true, + * native libraries are added to the given <code>nativeLibMap</code> + * with the base name to temp file location.<br> + * A file is identified as a native library, + * if it's name complies with the running platform's native library naming scheme.<br> + * Root entries are favored over non root entries in case of naming collisions.<br> + * Example on a Unix like machine:<br> + * <pre> + * mylib.jar!/sub1/libsour.so -> sour (mapped, unique name) + * mylib.jar!/sub1/libsweet.so (dropped, root entry favored) + * mylib.jar!/libsweet.so -> sweet (mapped, root entry favored) + * mylib.jar!/sweet.dll -> (dropped, not a unix library name) + * </pre> + * </p> + * <p> + * In order to be compatible with Java Web Start, we need + * to extract all root entries from the jar file.<br> + * In this case, set all flags to true <code>extractNativeLibraries </code>. + * <code>extractClassFiles</code>, <code>extractOtherFiles</code>. + * </p> + * + * @param dest + * @param nativeLibMap + * @param jarFile + * @param nativeLibraryPath if not null, only extracts native libraries within this path. + * @param extractNativeLibraries + * @param extractClassFiles + * @param extractOtherFiles + * @param deepDirectoryTraversal + * @return + * @throws IOException + */ + public static final int extract(final File dest, final Map<String, String> nativeLibMap, + final JarFile jarFile, + final String nativeLibraryPath, + final boolean extractNativeLibraries, + final boolean extractClassFiles, final boolean extractOtherFiles) throws IOException { + + if (DEBUG) { + System.err.println("JarUtil: extract: "+jarFile.getName()+" -> "+dest+ + ", extractNativeLibraries "+extractNativeLibraries+" ("+nativeLibraryPath+")"+ + ", extractClassFiles "+extractClassFiles+ + ", extractOtherFiles "+extractOtherFiles); + } + int num = 0; + + final Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + final String entryName = entry.getName(); + + // Match entries with correct prefix and suffix (ignoring case) + final String libBaseName = JNILibrary.isValidNativeLibraryName(entryName, false); + final boolean isNativeLib = null != libBaseName; + if(isNativeLib) { + if(!extractNativeLibraries) { + if (DEBUG) { + System.err.println("JarUtil: JarEntry : " + entryName + " native-lib skipped, skip all native libs"); + } + continue; + } + if(null != nativeLibraryPath) { + final String nativeLibraryPathS; + final String dirnameS; + try { + nativeLibraryPathS = IOUtil.slashify(nativeLibraryPath, false /* startWithSlash */, true /* endWithSlash */); + dirnameS = IOUtil.getDirname(entryName); + } catch (final URISyntaxException e) { + throw new IOException(e); + } + if( !nativeLibraryPathS.equals(dirnameS) ) { + if (DEBUG) { + System.err.println("JarUtil: JarEntry : " + entryName + " native-lib skipped, not in path: "+nativeLibraryPathS); + } + continue; + } + } + } + + final boolean isClassFile = entryName.endsWith(".class"); + if(isClassFile && !extractClassFiles) { + if (DEBUG) { + System.err.println("JarUtil: JarEntry : " + entryName + " class-file skipped"); + } + continue; + } + + if(!isNativeLib && !isClassFile && !extractOtherFiles) { + if (DEBUG) { + System.err.println("JarUtil: JarEntry : " + entryName + " other-file skipped"); + } + continue; + } + + final boolean isDir = entryName.endsWith("/"); + + final boolean isRootEntry = entryName.indexOf('/') == -1 && + entryName.indexOf(File.separatorChar) == -1; + + if (DEBUG) { + System.err.println("JarUtil: JarEntry : isNativeLib " + isNativeLib + + ", isClassFile " + isClassFile + ", isDir " + isDir + + ", isRootEntry " + isRootEntry ); + } + + final File destFile = new File(dest, entryName); + if(isDir) { + if (DEBUG) { + System.err.println("JarUtil: MKDIR: " + entryName + " -> " + destFile ); + } + destFile.mkdirs(); + } else { + final File destFolder = new File(destFile.getParent()); + if(!destFolder.exists()) { + if (DEBUG) { + System.err.println("JarUtil: MKDIR (parent): " + entryName + " -> " + destFolder ); + } + destFolder.mkdirs(); + } + final InputStream in = new BufferedInputStream(jarFile.getInputStream(entry)); + final OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile)); + int numBytes = -1; + try { + numBytes = IOUtil.copyStream2Stream(BUFFER_SIZE, in, out, -1); + } finally { + in.close(); + out.close(); + } + boolean addedAsNativeLib = false; + if (numBytes>0) { + num++; + if (isNativeLib && ( isRootEntry || !nativeLibMap.containsKey(libBaseName) ) ) { + nativeLibMap.put(libBaseName, destFile.getAbsolutePath()); + addedAsNativeLib = true; + fixNativeLibAttribs(destFile); + } + } + if (DEBUG) { + System.err.println("JarUtil: EXTRACT["+num+"]: [" + libBaseName + " -> ] " + entryName + " -> " + destFile + ": "+numBytes+" bytes, addedAsNativeLib: "+addedAsNativeLib); + } + } + } + return num; + } + + /** + * Mitigate file permission issues of native library files, i.e.: + * <ul> + * <li>Bug 865: Safari >= 6.1 [OSX]: May employ xattr on 'com.apple.quarantine' on 'PluginProcess.app'</li> + * </ul> + */ + private final static void fixNativeLibAttribs(final File file) { + // We tolerate UnsatisfiedLinkError (and derived) to solve the chicken and egg problem + // of loading gluegen's native library. + // On Safari(OSX), Bug 865, we simply hope the destination folder is executable. + if( PlatformTypes.OSType.MACOS == PlatformProps.OS || PlatformTypes.OSType.IOS == PlatformProps.OS ) { + final String fileAbsPath = file.getAbsolutePath(); + try { + fixNativeLibAttribs(fileAbsPath); + if( DEBUG ) { + System.err.println("JarUtil.fixNativeLibAttribs: "+fileAbsPath+" - OK"); + } + } catch (final Throwable t) { + if( DEBUG ) { + System.err.println("JarUtil.fixNativeLibAttribs: "+fileAbsPath+" - "+t.getClass().getSimpleName()+": "+t.getMessage()); + } + } + } + } + private native static boolean fixNativeLibAttribs(String fname); + + /** + * Validate the certificates for each native Lib in the jar file. + * Throws an IOException if any certificate is not valid. + * <pre> + Certificate[] rootCerts = Something.class.getProtectionDomain(). + getCodeSource().getCertificates(); + </pre> + */ + public static final void validateCertificates(final Certificate[] rootCerts, final JarFile jarFile) + throws IOException, SecurityException { + + if (DEBUG) { + System.err.println("JarUtil: validateCertificates: "+jarFile.getName()); + } + + if (rootCerts == null || rootCerts.length == 0) { + throw new IllegalArgumentException("Null certificates passed"); + } + + final byte[] buf = new byte[1024]; + final Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + if( ! entry.isDirectory() && ! entry.getName().startsWith("META-INF/") ) { + // only validate non META-INF and non directories + validateCertificate(rootCerts, jarFile, entry, buf); + } + } + } + + /** + * Check the certificates with the ones in the jar file + * (all must match). + */ + private static final void validateCertificate(final Certificate[] rootCerts, + final JarFile jar, final JarEntry entry, final byte[] buf) throws IOException, SecurityException { + + if (DEBUG) { + System.err.println("JarUtil: validate JarEntry : " + entry.getName()); + } + + // API states that we must read all of the data from the entry's + // InputStream in order to be able to get its certificates + + final InputStream is = jar.getInputStream(entry); + try { + while (is.read(buf) > 0) { } + } finally { + is.close(); + } + + // Get the certificates for the JAR entry + final Certificate[] nativeCerts = entry.getCertificates(); + if (nativeCerts == null || nativeCerts.length == 0) { + throw new SecurityException("no certificate for " + entry.getName() + " in " + jar.getName()); + } + + if( !SecurityUtil.equals(rootCerts, nativeCerts) ) { + throw new SecurityException("certificates not equal for " + entry.getName() + " in " + jar.getName()); + } + } +} diff --git a/java_pkg/org/jau/pkg/TempJarSHASum.java b/java_pkg/org/jau/pkg/TempJarSHASum.java new file mode 100644 index 0000000..4ad3223 --- /dev/null +++ b/java_pkg/org/jau/pkg/TempJarSHASum.java @@ -0,0 +1,70 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2019 Gothel Software e.K. + * Copyright (c) 2019 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.pkg; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import org.jau.io.IOUtil; +import org.jau.pkg.cache.TempJarCache; +import org.jau.sec.SHASum; + +/** + * {@link SHASum} specialization utilizing {@link TempJarCache} to access jar file content for SHA computation + */ +public class TempJarSHASum extends SHASum { + /** + * Instance to ensure proper {@link #compute(boolean)} of identical SHA sums over same contents within given paths across machines. + * <p> + * Instantiation of this class is lightweight, {@link #compute(boolean)} performs all operations. + * </p> + * <p> + * {@link TempJarCache#getTempFileCache()}'s {@link TempFileCache#getTempDir()} is used as origin for {@link IOUtil#filesOf(List, List, List)} + * </p> + * + * @param digest the SHA algorithm + * @param jarclazz a class from the desired classpath jar file used for {@link TempJarCache#addAll(Class, com.jogamp.common.net.Uri)} + * @param excludes the optional exclude patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @param includes the optional include patterns to be used for {@link IOUtil#filesOf(List, List, List)} + * @throws SecurityException + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public TempJarSHASum(final MessageDigest digest, final Class<?> jarclazz, final List<Pattern> excludes, final List<Pattern> includes) + throws SecurityException, IllegalArgumentException, IOException, URISyntaxException + { + super(digest, Arrays.asList(IOUtil.slashify(TempJarCache.getTempFileCache().getTempDir().getAbsolutePath(), false, false)), + excludes, includes); + TempJarCache.addAll(jarclazz, JarUtil.getJarFileUri(jarclazz.getName(), jarclazz.getClassLoader())); + } + + public final String getOrigin() { return getOrigins().get(0); } +}
\ No newline at end of file diff --git a/java_pkg/org/jau/pkg/cache/TempCacheReg.java b/java_pkg/org/jau/pkg/cache/TempCacheReg.java new file mode 100644 index 0000000..e53e8a9 --- /dev/null +++ b/java_pkg/org/jau/pkg/cache/TempCacheReg.java @@ -0,0 +1,39 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.pkg.cache; + +public class TempCacheReg { + public static boolean isTempFileCacheUsed() { + return null != System.getProperty(TempFileCache.tmpRootPropName); + } + /** + * @param forExecutables if {@code true}, method also tests whether {@link TempJarCache}'s underlying cache is suitable to load native libraries or launch executables + * @return true if {@link TempJarCache} has been properly initialized, ie. is in use. Otherwise returns false. + */ + public static boolean isTempJarCacheUsed(final boolean forExecutables) { + return TempJarCache.isInitialized(forExecutables); + } +} diff --git a/java_pkg/org/jau/pkg/cache/TempFileCache.java b/java_pkg/org/jau/pkg/cache/TempFileCache.java new file mode 100644 index 0000000..f95a41d --- /dev/null +++ b/java_pkg/org/jau/pkg/cache/TempFileCache.java @@ -0,0 +1,562 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.pkg.cache; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; + +import org.jau.io.IOUtil; +import org.jau.lang.InterruptSource; +import org.jau.sys.Debug; + +public class TempFileCache { + private static final boolean DEBUG = Debug.debug("TempFileCache"); + + // Flag indicating that we got a fatal error in the static initializer. + private static boolean staticInitError = false; + + // Flag indicating that the temp root folder can be used for executable files + private static boolean staticTempIsExecutable = true; + + private static final String tmpDirPrefix = "file_cache"; + + // Lifecycle: For one user's JVMs, ClassLoader and time. + private static final File tmpBaseDir; + + // Get the value of the tmproot system property + // Lifecycle: For one user's concurrently running JVMs and ClassLoader + /* package */ static final String tmpRootPropName = "jnlp.jogamp.tmp.cache.root"; + + // String representing the name of the temp root directory relative to the + // tmpBaseDir. Its value is "jlnNNNNN", which is the unique filename created + // by File.createTempFile() without the ".tmp" extension. + // + // Lifecycle: For one user's concurrently running JVMs and ClassLoader + // + private static String tmpRootPropValue; + + // Lifecycle: For one user's concurrently running JVMs and ClassLoader + private static File tmpRootDir; + + // Flag indicating that we got a fatal error in the initializer. + private boolean initError = false; + + private File individualTmpDir; + + static { + // Global Lock ! + synchronized (System.out) { + // Create / initialize the temp root directory, starting the Reaper + // thread to reclaim old installations if necessary. If we get an + // exception, set an error code. + File _tmpBaseDir = null; + try { + _tmpBaseDir = new File(IOUtil.getTempDir(true /* executable */), tmpDirPrefix); + _tmpBaseDir = IOUtil.testDir(_tmpBaseDir, true /* create */, false /* executable */); // executable already checked + staticTempIsExecutable = true; + } catch (final Exception ex) { + System.err.println("Warning: Caught Exception while retrieving executable temp base directory:"); + ex.printStackTrace(); + staticTempIsExecutable = false; + try { + _tmpBaseDir = new File(IOUtil.getTempDir(false /* executable */), tmpDirPrefix); + _tmpBaseDir = IOUtil.testDir(_tmpBaseDir, true /* create */, false /* executable */); + } catch (final Exception ex2) { + System.err.println("Warning: Caught Exception while retrieving non-executable temp base directory:"); + ex2.printStackTrace(); + staticInitError = true; + } + } + tmpBaseDir = _tmpBaseDir; + + if (DEBUG) { + final String tmpBaseDirAbsPath = null != tmpBaseDir ? tmpBaseDir.getAbsolutePath() : null; + System.err.println("TempFileCache: Static Initialization ---------------------------------------------- OK: "+(!staticInitError)); + System.err.println("TempFileCache: Thread: "+Thread.currentThread().getName()+ + ", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+ + ", tempBaseDir "+tmpBaseDirAbsPath+", executable "+staticTempIsExecutable); + } + + if(!staticInitError) { + try { + initTmpRoot(); + } catch (final Exception ex) { + System.err.println("Warning: Caught Exception due to initializing TmpRoot:"); + ex.printStackTrace(); + staticInitError = true; + staticTempIsExecutable = false; + } + } + if (DEBUG) { + System.err.println("------------------------------------------------------------------ OK: "+(!staticInitError)); + } + } + } + + /** + * Documented way to kick off static initialization + * @return true is static initialization was successful + */ + public static boolean initSingleton() { + return !staticInitError; + } + + /** + * This method is called by the static initializer to create / initialize + * the temp root directory that will hold the temp directories for this + * instance of the JVM. This is done as follows: + * + * 1. Synchronize on a global lock. Note that for this purpose we will + * use System.out in the absence of a true global lock facility. + * We are careful not to hold this lock too long. + * The global lock is claimed in the static initializer block, calling this method! + * + * 2. Check for the existence of the "jnlp.jogamp.tmp.cache.root" + * system property. + * + * a. If set, then some other thread in a different ClassLoader has + * already created the tmpRootDir, so we just need to + * use it. The remaining steps are skipped. + * However, we check the existence of the tmpRootDir + * and if non existent, we assume a new launch and continue. + * + * b. If not set, then we are the first thread in this JVM to run, + * and we need to create the the tmpRootDir. + * + * 3. Create the tmpRootDir, along with the appropriate locks. + * Note that we perform the operations in the following order, + * prior to creating tmpRootDir itself, to work around the fact that + * the file creation and file lock steps are not atomic, and we need + * to ensure that a newly-created tmpRootDir isn't reaped by a + * concurrently running JVM. + * + * create jlnNNNN.tmp using File.createTempFile() + * lock jlnNNNN.tmp + * create jlnNNNN.lck while holding the lock on the .tmp file + * lock jlnNNNN.lck + * + * Since the Reaper thread will enumerate the list of *.lck files + * before starting, we can guarantee that if there exists a *.lck file + * for an active process, then the corresponding *.tmp file is locked + * by that active process. This guarantee lets us avoid reaping an + * active process' files. + * + * 4. Set the "jnlp.jogamp.tmp.cache.root" system property. + * + * 5. Add a shutdown hook to cleanup jlnNNNN.lck and jlnNNNN.tmp. We + * don't actually expect that this shutdown hook will ever be called, + * but the act of doing this, ensures that the locks never get + * garbage-collected, which is necessary for correct behavior when + * the first ClassLoader is later unloaded, while subsequent Applets + * are still running. + * + * 6. Start the Reaper thread to cleanup old installations. + */ + private static void initTmpRoot() throws IOException { + tmpRootPropValue = System.getProperty(tmpRootPropName); + + if (tmpRootPropValue != null) { + // Make sure that the property is not set to an illegal value + if (tmpRootPropValue.indexOf('/') >= 0 || + tmpRootPropValue.indexOf(File.separatorChar) >= 0) { + throw new IOException("Illegal value of: " + tmpRootPropName); + } + + // Set tmpRootDir = ${tmpbase}/${jnlp.applet.launcher.tmproot} + if (DEBUG) { + System.err.println("TempFileCache: Trying existing value of: " + + tmpRootPropName + "=" + tmpRootPropValue); + } + tmpRootDir = new File(tmpBaseDir, tmpRootPropValue); + if (DEBUG) { + System.err.println("TempFileCache: Trying tmpRootDir = " + tmpRootDir.getAbsolutePath()); + } + if (tmpRootDir.isDirectory()) { + if (!tmpRootDir.canWrite()) { + throw new IOException("Temp root directory is not writable: " + tmpRootDir.getAbsolutePath()); + } + } else { + // It is possible to move to a new GlueGen version within the same JVM + // In case tmpBaseDir has changed, we should assume a new tmpRootDir. + System.err.println("TempFileCache: None existing tmpRootDir = " + tmpRootDir.getAbsolutePath()+", assuming new path due to update"); + tmpRootPropValue = null; + tmpRootDir = null; + System.clearProperty(tmpRootPropName); + } + } + + if (tmpRootPropValue == null) { + // Create ${tmpbase}/jlnNNNN.tmp then lock the file + final File tmpFile = File.createTempFile("jln", ".tmp", tmpBaseDir); + if (DEBUG) { + System.err.println("TempFileCache: tmpFile = " + tmpFile.getAbsolutePath()); + } + final FileOutputStream tmpOut = new FileOutputStream(tmpFile); + final FileChannel tmpChannel = tmpOut.getChannel(); + final FileLock tmpLock = tmpChannel.lock(); + + // Strip off the ".tmp" to get the name of the tmprootdir + final String tmpFileName = tmpFile.getAbsolutePath(); + final String tmpRootName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp")); + + // create ${tmpbase}/jlnNNNN.lck then lock the file + final String lckFileName = tmpRootName + ".lck"; + final File lckFile = new File(lckFileName); + if (DEBUG) { + System.err.println("TempFileCache: lckFile = " + lckFile.getAbsolutePath()); + } + lckFile.createNewFile(); + final FileOutputStream lckOut = new FileOutputStream(lckFile); + final FileChannel lckChannel = lckOut.getChannel(); + final FileLock lckLock = lckChannel.lock(); + + // Create tmprootdir + tmpRootDir = new File(tmpRootName); + if (DEBUG) { + System.err.println("TempFileCache: tmpRootDir = " + tmpRootDir.getAbsolutePath()); + } + if (!tmpRootDir.mkdir()) { + throw new IOException("Cannot create " + tmpRootDir); + } + + // Add shutdown hook to cleanup the OutputStream, FileChannel, + // and FileLock for the jlnNNNN.lck and jlnNNNN.lck files. + // We do this so that the locks never get garbage-collected. + Runtime.getRuntime().addShutdownHook(new InterruptSource.Thread() { + /* @Override */ + @Override + public void run() { + // NOTE: we don't really expect that this code will ever + // be called. If it does, we will close the output + // stream, which will in turn close the channel. + // We will then release the lock. + try { + tmpOut.close(); + tmpLock.release(); + lckOut.close(); + lckLock.release(); + } catch (final IOException ex) { + // Do nothing + } + } + }); + + // Set the system property... + tmpRootPropValue = tmpRootName.substring(tmpRootName.lastIndexOf(File.separator) + 1); + System.setProperty(tmpRootPropName, tmpRootPropValue); + if (DEBUG) { + System.err.println("TempFileCache: Setting " + tmpRootPropName + "=" + tmpRootPropValue); + } + + // Start a new Reaper thread to do stuff... + final Thread reaperThread = new InterruptSource.Thread() { + /* @Override */ + @Override + public void run() { + deleteOldTempDirs(); + } + }; + reaperThread.setName("TempFileCache-Reaper"); + reaperThread.start(); + } + } + + /** + * Called by the Reaper thread to delete old temp directories + * Only one of these threads will run per JVM invocation. + */ + private static void deleteOldTempDirs() { + if (DEBUG) { + System.err.println("TempFileCache: *** Reaper: deleteOldTempDirs in " + + tmpBaseDir.getAbsolutePath()); + } + + // enumerate list of jnl*.lck files, ignore our own jlnNNNN file + final String ourLockFile = tmpRootPropValue + ".lck"; + final FilenameFilter lckFilter = new FilenameFilter() { + /* @Override */ + @Override + public boolean accept(final File dir, final String name) { + return name.endsWith(".lck") && !name.equals(ourLockFile); + } + }; + + // For each file <file>.lck in the list we will first try to lock + // <file>.tmp if that succeeds then we will try to lock <file>.lck + // (which should always succeed unless there is a problem). If we can + // get the lock on both files, then it must be an old installation, and + // we will delete it. + final String[] fileNames = tmpBaseDir.list(lckFilter); + if (fileNames != null) { + for (int i = 0; i < fileNames.length; i++) { + final String lckFileName = fileNames[i]; + final String tmpDirName = lckFileName.substring(0, lckFileName.lastIndexOf(".lck")); + final String tmpFileName = tmpDirName + ".tmp"; + + final File lckFile = new File(tmpBaseDir, lckFileName); + final File tmpFile = new File(tmpBaseDir, tmpFileName); + final File tmpDir = new File(tmpBaseDir, tmpDirName); + + if (lckFile.exists() && tmpFile.exists() && tmpDir.isDirectory()) { + FileOutputStream tmpOut = null; + FileChannel tmpChannel = null; + FileLock tmpLock = null; + + try { + tmpOut = new FileOutputStream(tmpFile); + tmpChannel = tmpOut.getChannel(); + tmpLock = tmpChannel.tryLock(); + } catch (final Exception ex) { + // Ignore exceptions + if (DEBUG) { + ex.printStackTrace(); + } + } + + if (tmpLock != null) { + FileOutputStream lckOut = null; + FileChannel lckChannel = null; + FileLock lckLock = null; + + try { + lckOut = new FileOutputStream(lckFile); + lckChannel = lckOut.getChannel(); + lckLock = lckChannel.tryLock(); + } catch (final Exception ex) { + if (DEBUG) { + ex.printStackTrace(); + } + } + + if (lckLock != null) { + // Recursively remove the old tmpDir and all of + // its contents + removeAll(tmpDir); + + // Close the streams and delete the .lck and .tmp + // files. Note that there is a slight race condition + // in that another process could open a stream at + // the same time we are trying to delete it, which will + // prevent deletion, but we won't worry about it, since + // the worst that will happen is we might have an + // occasional 0-byte .lck or .tmp file left around + try { + lckOut.close(); + } catch (final IOException ex) { + } + lckFile.delete(); + try { + tmpOut.close(); + } catch (final IOException ex) { + } + tmpFile.delete(); + } else { + try { + // Close the file and channel for the *.lck file + if (lckOut != null) { + lckOut.close(); + } + // Close the file/channel and release the lock + // on the *.tmp file + tmpOut.close(); + tmpLock.release(); + } catch (final IOException ex) { + if (DEBUG) { + ex.printStackTrace(); + } + } + } + } + } else { + if (DEBUG) { + System.err.println("TempFileCache: Skipping: " + tmpDir.getAbsolutePath()); + } + } + } + } + } + + /** + * Remove the specified file or directory. If "path" is a directory, then + * recursively remove all entries, then remove the directory itself. + */ + private static void removeAll(final File path) { + if (DEBUG) { + System.err.println("TempFileCache: removeAll(" + path + ")"); + } + + if (path.isDirectory()) { + // Recursively remove all files/directories in this directory + final File[] list = path.listFiles(); + if (list != null) { + for (int i = 0; i < list.length; i++) { + removeAll(list[i]); + } + } + } + path.delete(); + } + + /** Create the {@link #getTempDir()} */ + public TempFileCache () { + if (DEBUG) { + System.err.println("TempFileCache: new TempFileCache() --------------------- (static ok: "+(!staticInitError)+")"); + System.err.println("TempFileCache: Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+", this 0x"+Integer.toHexString(hashCode())); + } + if(!staticInitError) { + try { + createTmpDir(); + } catch (final Exception ex) { + ex.printStackTrace(); + initError = true; + } + } + if (DEBUG) { + System.err.println("TempFileCache: tempDir "+individualTmpDir+" (ok: "+(!initError)+")"); + System.err.println("----------------------------------------------------------"); + } + } + + /** Delete the {@link #getTempDir()} recursively and remove it's reference. */ + public void destroy() { + if (DEBUG) { + System.err.println("TempFileCache: destroy() --------------------- (static ok: "+(!staticInitError)+")"); + System.err.println("TempFileCache: Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+", this 0x"+Integer.toHexString(hashCode())); + } + if(!staticInitError) { + try { + removeAll(individualTmpDir); + } catch (final Exception ex) { + ex.printStackTrace(); + } + } + individualTmpDir = null; + if (DEBUG) { + System.err.println("TempFileCache: destroy() END"); + } + } + + /** + * @param forExecutables if {@code true}, method also tests whether the underlying {@link #getBaseDir()} is suitable to load native libraries or launch executables + * @return true if static and object initialization was successful + * @see #isTempExecutable() + * @see #isValid() + */ + public boolean isValid(final boolean forExecutables) { + return !staticInitError && !initError && ( !forExecutables || staticTempIsExecutable ); + } + + /** + * Base temp directory used by {@link TempFileCache}. + * + * <p> + * Lifecycle: For one user's concurrently running JVMs and ClassLoader + * </p> + * + * This is set to: + * <pre> + * ${java.io.tmpdir}/tmpDirPrefix + * </pre> + * + * @return + */ + public static File getBaseDir() { return tmpBaseDir; } + + /** + * Root temp directory for this JVM instance. Used to store individual + * directories. + * <p> + * This directory is a sub-folder to {@link #getBaseDir()}. + * </p> + * + * <p> + * Lifecycle: For one user's concurrently running JVMs and ClassLoader + * </p> + * + * <pre> + * tmpBaseDir/tmpRootPropValue + * </pre> + * + * <p> + * Use Case: Per ClassLoader files, eg. native libraries. + * </p> + * + * <p> + * Old temp directories are cleaned up the next time a JVM is launched that + * uses TempFileCache. + * </p> + * + * + * @return + */ + public static File getRootDir() { return tmpRootDir; } + + /** + * Temporary directory for individual files (eg. native libraries of one ClassLoader instance). + * <p> + * This directory is a sub-folder to {@link #getRootDir()}. + * </p> + * + * <p> + * Lifecycle: Within each JVM .. use case dependent, ie. per ClassLoader <b>and</b> per {@link TempFileCache} instance! + * </p> + * <p> + * The directory name is: + * + * <pre> + * tmpRootDir/jlnMMMMM + * </pre> + * + * where jlnMMMMM is the unique filename created by File.createTempFile() + * without the ".tmp" extension. + * </p> + * + * @return + */ + public File getTempDir() { return individualTmpDir; } + + + /** + * Create the temp directory in tmpRootDir. To do this, we create a temp + * file with a ".tmp" extension, and then create a directory of the + * same name but without the ".tmp". The temp file, directory, and all + * files in the directory will be reaped the next time this is started. + * We avoid deleteOnExit, because it doesn't work reliably. + */ + private void createTmpDir() throws IOException { + final File tmpFile = File.createTempFile("jln", ".tmp", tmpRootDir); + final String tmpFileName = tmpFile.getAbsolutePath(); + final String tmpDirName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp")); + individualTmpDir = new File(tmpDirName); + if (!individualTmpDir.mkdir()) { + throw new IOException("Cannot create " + individualTmpDir); + } + } +} diff --git a/java_pkg/org/jau/pkg/cache/TempJarCache.java b/java_pkg/org/jau/pkg/cache/TempJarCache.java new file mode 100644 index 0000000..5db9ce9 --- /dev/null +++ b/java_pkg/org/jau/pkg/cache/TempJarCache.java @@ -0,0 +1,502 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.pkg.cache; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarFile; + +import org.jau.net.Uri; +import org.jau.pkg.JarUtil; +import org.jau.sec.SecurityUtil; +import org.jau.sys.Debug; +import org.jau.sys.JNILibrary; + +/** + * Static Jar file cache handler using an underlying instance of {@link TempFileCache}, see {@link #getTempFileCache()}. + * <p> + * Lifecycle: Concurrently running JVMs and ClassLoader + * </p> + */ +public class TempJarCache { + private static final boolean DEBUG = Debug.debug("TempJarCache"); + + // A HashMap of native libraries that can be loaded with System.load() + // The key is the string name of the library as passed into the loadLibrary + // call; it is the file name without the directory or the platform-dependent + // library prefix and suffix. The value is the absolute path name to the + // unpacked library file in nativeTmpDir. + private static Map<String, String> nativeLibMap; + + public enum LoadState { + LOOKED_UP, LOADED; + + public boolean compliesWith(final LoadState o2) { + return null != o2 ? compareTo(o2) >= 0 : false; + } + } + private static boolean testLoadState(final LoadState has, final LoadState exp) { + if(null == has) { + return null == exp; + } + return has.compliesWith(exp); + } + + // Set of jar files added + private static Map<Uri, LoadState> nativeLibJars; + private static Map<Uri, LoadState> classFileJars; + private static Map<Uri, LoadState> resourceFileJars; + + private static TempFileCache tmpFileCache; + + private static volatile boolean staticInitError = false; + private static volatile boolean staticTempIsExecutable = true; + private static volatile boolean isInit = false; + + /** + * Documented way to kick off static initialization. + * + * @return true is static initialization was successful + */ + public static boolean initSingleton() { + if (!isInit) { // volatile: ok + synchronized (TempJarCache.class) { + if (!isInit) { + staticInitError = !TempFileCache.initSingleton(); + + if(!staticInitError) { + tmpFileCache = new TempFileCache(); + staticInitError = !tmpFileCache.isValid(false); + staticTempIsExecutable = tmpFileCache.isValid(true); + } + + if(!staticInitError) { + // Initialize the collections of resources + nativeLibMap = new HashMap<String, String>(); + nativeLibJars = new HashMap<Uri, LoadState>(); + classFileJars = new HashMap<Uri, LoadState>(); + resourceFileJars = new HashMap<Uri, LoadState>(); + } + if(DEBUG) { + final File tempDir = null != tmpFileCache ? tmpFileCache.getTempDir() : null; + final String tempDirAbsPath = null != tempDir ? tempDir.getAbsolutePath() : null; + System.err.println("TempJarCache.initSingleton(): ok "+(false==staticInitError)+", "+ tempDirAbsPath+", executable "+staticTempIsExecutable); + } + isInit = true; + } + } + } + return !staticInitError; + } + + /** + * This is <b>not recommended</b> since the JNI libraries may still be + * in use by the ClassLoader they are loaded via {@link System#load(String)}. + * </p> + * <p> + * In JogAmp, JNI native libraries loaded and registered by {@link JNIJarLibrary} + * derivations, where the native JARs might be loaded via {@link JNIJarLibrary#addNativeJarLibs(Class, String) }. + * </p> + * <p> + * The only valid use case to shutdown the TempJarCache is at bootstrapping, + * i.e. when no native library is guaranteed to be loaded. This could be useful + * if bootstrapping needs to find the proper native library type. + * </p> + * + public static void shutdown() { + if (isInit) { // volatile: ok + synchronized (TempJarCache.class) { + if (isInit) { + if(DEBUG) { + System.err.println("TempJarCache.shutdown(): real "+(false==staticInitError)+", "+ tmpFileCache.getTempDir()); + } + isInit = false; + if(!staticInitError) { + nativeLibMap.clear(); + nativeLibMap = null; + nativeLibJars.clear(); + nativeLibJars = null; + classFileJars.clear(); + classFileJars = null; + resourceFileJars.clear(); + resourceFileJars = null; + + tmpFileCache.destroy(); + tmpFileCache = null; + } + } + } + } + } */ + + private static boolean isInitializedImpl() { + if (!isInit) { // volatile: ok + synchronized (TempJarCache.class) { + if (!isInit) { + return false; + } + } + } + return true; + } + + /** + * @param forExecutables if {@code true}, method also tests whether the underlying cache is suitable to load native libraries or launch executables + * @return true if this class has been properly initialized, ie. is in use. Otherwise returns false. + */ + public static boolean isInitialized(final boolean forExecutables) { + return isInitializedImpl() && !staticInitError && ( !forExecutables || staticTempIsExecutable ); + } + + /** + * @param forExecutables if {@code true}, method also tests whether the underlying cache is suitable to load native libraries or launch executables + */ + /* package */ static void checkInitialized(final boolean forExecutables) { + if(!isInitializedImpl()) { + throw new RuntimeException("initSingleton() has to be called first."); + } + if(staticInitError) { + throw new RuntimeException("initSingleton() failed."); + } + if( forExecutables && !staticTempIsExecutable ) { + throw new RuntimeException("TempJarCache folder not suitable for executables"); + } + } + + /** + * @return the underlying {@link TempFileCache} + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public static TempFileCache getTempFileCache() { + checkInitialized(false); + return tmpFileCache; + } + + /** + * @param jarUri + * @param exp + * @return + * @throws IOException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static boolean checkNativeLibs(final Uri jarUri, final LoadState exp) throws IOException { + checkInitialized(false); + if(null == jarUri) { + throw new IllegalArgumentException("jarUri is null"); + } + return testLoadState(nativeLibJars.get(jarUri), exp); + } + + /** + * @param jarUri + * @param exp + * @return + * @throws IOException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static boolean checkClasses(final Uri jarUri, final LoadState exp) throws IOException { + checkInitialized(false); + if(null == jarUri) { + throw new IllegalArgumentException("jarUri is null"); + } + return testLoadState(classFileJars.get(jarUri), exp); + } + + /** + * + * @param jarUri + * @param exp + * @return + * @throws IOException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static boolean checkResources(final Uri jarUri, final LoadState exp) throws IOException { + checkInitialized(false); + if(null == jarUri) { + throw new IllegalArgumentException("jarUri is null"); + } + return testLoadState(resourceFileJars.get(jarUri), exp); + } + + /** + * Adds native libraries, if not yet added. + * + * @param certClass if class is certified, the JarFile entries needs to have the same certificate + * @param jarUri + * @param nativeLibraryPath if not null, only extracts native libraries within this path. + * @return true if native libraries were added or previously loaded from given jarUri, otherwise false + * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed + * @throws SecurityException + * @throws URISyntaxException + * @throws IllegalArgumentException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(true)} + */ + public synchronized static final boolean addNativeLibs(final Class<?> certClass, final Uri jarUri, final String nativeLibraryPath) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException { + checkInitialized(true); + final LoadState nativeLibJarsLS = nativeLibJars.get(jarUri); + if( !testLoadState(nativeLibJarsLS, LoadState.LOOKED_UP) ) { + nativeLibJars.put(jarUri, LoadState.LOOKED_UP); + final JarFile jarFile = JarUtil.getJarFile(jarUri); + if(DEBUG) { + System.err.println("TempJarCache: addNativeLibs: "+jarUri+": nativeJar "+jarFile.getName()+" (NEW)"); + } + validateCertificates(certClass, jarFile); + final int num = JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile, nativeLibraryPath, true, false, false); + nativeLibJars.put(jarUri, LoadState.LOADED); + return num > 0; + } else if( testLoadState(nativeLibJarsLS, LoadState.LOADED) ) { + if(DEBUG) { + System.err.println("TempJarCache: addNativeLibs: "+jarUri+": nativeJar "+jarUri+" (REUSE)"); + } + return true; + } + throw new IOException("TempJarCache: addNativeLibs: "+jarUri+", previous load attempt failed"); + } + + /** + * Adds native classes, if not yet added. + * + * TODO class access pending + * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed .. + * + * @param certClass if class is certified, the JarFile entries needs to have the same certificate + * @param jarUri + * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed + * @throws SecurityException + * @throws URISyntaxException + * @throws IllegalArgumentException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static final void addClasses(final Class<?> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException { + checkInitialized(false); + final LoadState classFileJarsLS = classFileJars.get(jarUri); + if( !testLoadState(classFileJarsLS, LoadState.LOOKED_UP) ) { + classFileJars.put(jarUri, LoadState.LOOKED_UP); + final JarFile jarFile = JarUtil.getJarFile(jarUri); + if(DEBUG) { + System.err.println("TempJarCache: addClasses: "+jarUri+": nativeJar "+jarFile.getName()); + } + validateCertificates(certClass, jarFile); + JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile, + null /* nativeLibraryPath */, false, true, false); + classFileJars.put(jarUri, LoadState.LOADED); + } else if( !testLoadState(classFileJarsLS, LoadState.LOADED) ) { + throw new IOException("TempJarCache: addClasses: "+jarUri+", previous load attempt failed"); + } + } + + /** + * Adds native resources, if not yet added. + * + * @param certClass if class is certified, the JarFile entries needs to have the same certificate + * @param jarUri + * @return + * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed + * @throws SecurityException + * @throws URISyntaxException + * @throws IllegalArgumentException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static final void addResources(final Class<?> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException { + checkInitialized(false); + final LoadState resourceFileJarsLS = resourceFileJars.get(jarUri); + if( !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP) ) { + resourceFileJars.put(jarUri, LoadState.LOOKED_UP); + final JarFile jarFile = JarUtil.getJarFile(jarUri); + if(DEBUG) { + System.err.println("TempJarCache: addResources: "+jarUri+": nativeJar "+jarFile.getName()); + } + validateCertificates(certClass, jarFile); + JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile, + null /* nativeLibraryPath */, false, false, true); + resourceFileJars.put(jarUri, LoadState.LOADED); + } else if( !testLoadState(resourceFileJarsLS, LoadState.LOADED) ) { + throw new IOException("TempJarCache: addResources: "+jarUri+", previous load attempt failed"); + } + } + + /** + * Adds all types, native libraries, class files and other files (resources) + * if not yet added. + * + * TODO class access pending + * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed .. + * + * @param certClass if class is certified, the JarFile entries needs to have the same certificate + * @param jarUri + * @throws IOException if the <code>jarUri</code> could not be loaded or a previous load attempt failed + * @throws SecurityException + * @throws URISyntaxException + * @throws IllegalArgumentException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static final void addAll(final Class<?> certClass, final Uri jarUri) throws IOException, SecurityException, IllegalArgumentException, URISyntaxException { + checkInitialized(false); + if(null == jarUri) { + throw new IllegalArgumentException("jarUri is null"); + } + final LoadState nativeLibJarsLS = nativeLibJars.get(jarUri); + final LoadState classFileJarsLS = classFileJars.get(jarUri); + final LoadState resourceFileJarsLS = resourceFileJars.get(jarUri); + if( !testLoadState(nativeLibJarsLS, LoadState.LOOKED_UP) || + !testLoadState(classFileJarsLS, LoadState.LOOKED_UP) || + !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP) ) { + + final boolean extractNativeLibraries = staticTempIsExecutable && !testLoadState(nativeLibJarsLS, LoadState.LOADED); + final boolean extractClassFiles = !testLoadState(classFileJarsLS, LoadState.LOADED); + final boolean extractOtherFiles = !testLoadState(resourceFileJarsLS, LoadState.LOOKED_UP); + + // mark looked-up (those who are not loaded) + if(extractNativeLibraries) { + nativeLibJars.put(jarUri, LoadState.LOOKED_UP); + } + if(extractClassFiles) { + classFileJars.put(jarUri, LoadState.LOOKED_UP); + } + if(extractOtherFiles) { + resourceFileJars.put(jarUri, LoadState.LOOKED_UP); + } + + final JarFile jarFile = JarUtil.getJarFile(jarUri); + if(DEBUG) { + System.err.println("TempJarCache: addAll: "+jarUri+": nativeJar "+jarFile.getName()); + } + validateCertificates(certClass, jarFile); + JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile, + null /* nativeLibraryPath */, extractNativeLibraries, extractClassFiles, extractOtherFiles); + + // mark loaded (those were just loaded) + if(extractNativeLibraries) { + nativeLibJars.put(jarUri, LoadState.LOADED); + } + if(extractClassFiles) { + classFileJars.put(jarUri, LoadState.LOADED); + } + if(extractOtherFiles) { + resourceFileJars.put(jarUri, LoadState.LOADED); + } + } else if( !testLoadState(nativeLibJarsLS, LoadState.LOADED) || + !testLoadState(classFileJarsLS, LoadState.LOADED) || + !testLoadState(resourceFileJarsLS, LoadState.LOADED) ) { + throw new IOException("TempJarCache: addAll: "+jarUri+", previous load attempt failed"); + } + } + + /** + * If {@link #isInitialized(boolean) isInitialized(true)} is false due to lack of executable support only, + * this method always returns false. + * @param libName + * @return the found native library path within this cache or null if not found + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static final String findLibrary(final String libName) { + checkInitialized(false); + if( !staticTempIsExecutable ) { + return null; + } + // try with mapped library basename first + String path = nativeLibMap.get(libName); + if(null == path) { + // if valid library name, try absolute path in temp-dir + if(null != JNILibrary.isValidNativeLibraryName(libName, false)) { + final File f = new File(tmpFileCache.getTempDir(), libName); + if(f.exists()) { + path = f.getAbsolutePath(); + } + } + } + return path; + } + + /** TODO class access pending + * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed .. + public static Class<?> findClass(String name, ClassLoader cl) throws IOException, ClassFormatError { + checkInitialized(); + final File f = new File(nativeTmpFileCache.getTempDir(), IOUtil.getClassFileName(name)); + if(f.exists()) { + Class.forName(fname, initialize, loader) + URL url = new URL(f.getAbsolutePath()); + byte[] b = IOUtil.copyStream2ByteArray(new BufferedInputStream( url.openStream() )); + MyClassLoader mcl = new MyClassLoader(cl); + return mcl.defineClass(name, b, 0, b.length); + } + return null; + } */ + + /** + * Similar to {@link ClassLoader#getResource(String)}. + * @param name + * @return + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static final String findResource(final String name) { + checkInitialized(false); + final File f = new File(tmpFileCache.getTempDir(), name); + if(f.exists()) { + return f.getAbsolutePath(); + } + return null; + } + + /** + * Similar to {@link ClassLoader#getResource(String)}. + * @param name + * @return + * @throws URISyntaxException + * @throws RuntimeException if not {@link #isInitialized(boolean) isInitialized(false)} + */ + public synchronized static final Uri getResourceUri(final String name) throws URISyntaxException { + checkInitialized(false); + final File f = new File(tmpFileCache.getTempDir(), name); + if(f.exists()) { + return Uri.valueOf(f); + } + return null; + } + + private static void validateCertificates(final Class<?> certClass, final JarFile jarFile) throws IOException, SecurityException { + if(null == certClass) { + throw new IllegalArgumentException("certClass is null"); + } + final Certificate[] rootCerts = SecurityUtil.getCerts(certClass); + if( null != rootCerts ) { + // Only validate the jarFile's certs with ours, if we have any. + // Otherwise we may run uncertified JARs (application). + // In case one tries to run uncertified JARs, the wrapping applet/JNLP + // SecurityManager will kick in and throw a SecurityException. + JarUtil.validateCertificates(rootCerts, jarFile); + if(DEBUG) { + System.err.println("TempJarCache: validateCertificates: OK - Matching rootCerts in given class "+certClass.getName()+", nativeJar "+jarFile.getName()); + } + } else if(DEBUG) { + System.err.println("TempJarCache: validateCertificates: OK - No rootCerts in given class "+certClass.getName()+", nativeJar "+jarFile.getName()); + } + } +} diff --git a/scripts/build.sh b/scripts/build.sh index 756a471..0e07625 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -28,13 +28,13 @@ buildit() { mkdir -p build-$archabi cd build-$archabi CLANG_ARGS="-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++" - cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON .. - # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON -DDEBUG=ON .. - # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON -DGPROF=ON .. - # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON -DPERF_ANALYSIS=ON .. - # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON -DDEBUG=ON -DINSTRUMENTATION=ON .. - # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON -DDEBUG=ON -DINSTRUMENTATION_UNDEFINED=ON .. - # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILD_TESTING=ON -DDEBUG=ON -DINSTRUMENTATION_THREAD=ON .. + # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON .. + cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DDEBUG=ON .. + # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DGPROF=ON .. + # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DPERF_ANALYSIS=ON .. + # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DDEBUG=ON -DINSTRUMENTATION=ON .. + # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DDEBUG=ON -DINSTRUMENTATION_UNDEFINED=ON .. + # cmake $CLANG_ARGS -DCMAKE_INSTALL_PREFIX=$rootdir/dist-$archabi -DBUILDJAVA=ON -DBUILD_TESTING=ON -DDEBUG=ON -DINSTRUMENTATION_THREAD=ON .. make -j $CPU_COUNT install test if [ $? -eq 0 ] ; then echo "BUILD SUCCESS $bname $archabi" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e7e800..8b0cadc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,5 +31,5 @@ set_target_properties( ) install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/jau/ DESTINATION include/jau) -# install(TARGETS jaulib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(TARGETS jaulib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/test/java/CMakeLists.txt b/test/java/CMakeLists.txt new file mode 100644 index 0000000..cf0feb0 --- /dev/null +++ b/test/java/CMakeLists.txt @@ -0,0 +1,45 @@ +# java/CMakeLists.txt + +find_jar(JUNIT_JAR + NAMES junit4 junit + PATHS "/usr/share/java") + +file(GLOB_RECURSE TEST_JAVA_SOURCES "*.java") + +file(GLOB_RECURSE TEST_JAVA_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "Test*.java") + +add_jar(jaulib_test + ${TEST_JAVA_SOURCES} + jau/info.txt + org/jau/net/data2/RelativeData2.txt + org/jau/net/data/AssetURLConnectionTest.txt + org/jau/net/data/RelativeData.txt + jau-test/info.txt + INCLUDE_JARS jaulib_base_jar jaulib_jni_jar jaulib_net_jar jaulib_pkg_jar ${JUNIT_JAR} + MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/manifest.txt + OUTPUT_NAME jaulib_test +) +add_dependencies(jaulib_test jaulib_base_jar jaulib_jni_jar jaulib_net_jar jaulib_pkg_jar) + +string( REPLACE ".java" "" TEST_JAVA_FILES2 "${TEST_JAVA_FILES}" ) +string( REPLACE "/" "." BASENAMES_IDIOMATIC_EXAMPLES "${TEST_JAVA_FILES2}" ) +set( TARGETS_IDIOMATIC_EXAMPLES ${BASENAMES_IDIOMATIC_EXAMPLES} ) + +set(ALL_EXAMPLE_TARGETS + ${TARGETS_IDIOMATIC_EXAMPLES} +) + +find_jar(jaulib_base_jar_file NAMES jaulib_base PATHS "${CMAKE_CURRENT_BINARY_DIR}/../../java_base") +find_jar(jaulib_jni_jar_file NAMES jaulib_jni PATHS "${CMAKE_CURRENT_BINARY_DIR}/../../java_jni") +find_jar(jaulib_net_jar_file NAMES jaulib_net PATHS "${CMAKE_CURRENT_BINARY_DIR}/../../java_net") +find_jar(jaulib_pkg_jar_file NAMES jaulib_pkg PATHS "${CMAKE_CURRENT_BINARY_DIR}/../../java_pkg") + +foreach(name ${ALL_EXAMPLE_TARGETS}) +# add_dependencies(${name} jaulib_test ${JUNIT_JAR}) + add_test (NAME ${name} COMMAND ${JAVA_RUNTIME} + -cp ${JUNIT_JAR}:${jaulib_base_jar_file}:${jaulib_jni_jar_file}:${jaulib_net_jar_file}:${jaulib_pkg_jar_file}:jaulib_test.jar + org.junit.runner.JUnitCore ${name}) +endforeach() + + + diff --git a/test/java/jau-test/info.txt b/test/java/jau-test/info.txt new file mode 100644 index 0000000..842ac02 --- /dev/null +++ b/test/java/jau-test/info.txt @@ -0,0 +1,3 @@ +jaulib_test jau-test/info.txt Asset. + +This file exists for test purposes. diff --git a/test/java/jau/info.txt b/test/java/jau/info.txt new file mode 100644 index 0000000..5968e53 --- /dev/null +++ b/test/java/jau/info.txt @@ -0,0 +1,3 @@ +jaulib_test jau/info.txt Asset. + +This file exists for test purposes. diff --git a/test/java/jau/util/parallel/locks/LockDebugUtil.java b/test/java/jau/util/parallel/locks/LockDebugUtil.java new file mode 100644 index 0000000..67bae65 --- /dev/null +++ b/test/java/jau/util/parallel/locks/LockDebugUtil.java @@ -0,0 +1,78 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.util.parallel.locks; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jau.util.parallel.locks.Lock; + +/** + * Functionality enabled if {@link Lock#DEBUG} is <code>true</code>. + */ +public class LockDebugUtil { + private static final ThreadLocal<ArrayList<Throwable>> tlsLockedStacks; + private static final List<Throwable> dummy; + static { + if(Lock.DEBUG) { + tlsLockedStacks = new ThreadLocal<ArrayList<Throwable>>(); + dummy = null; + } else { + tlsLockedStacks = null; + dummy = new ArrayList<Throwable>(0); + } + } + + public static List<Throwable> getRecursiveLockTrace() { + if(Lock.DEBUG) { + ArrayList<Throwable> ls = tlsLockedStacks.get(); + if(null == ls) { + ls = new ArrayList<Throwable>(); + tlsLockedStacks.set(ls); + } + return ls; + } else { + return dummy; + } + } + + public static void dumpRecursiveLockTrace(final PrintStream out) { + if(Lock.DEBUG) { + final List<Throwable> ls = getRecursiveLockTrace(); + if(null!=ls && ls.size()>0) { + int j=0; + out.println("TLSLockedStacks: locks "+ls.size()); + for(final Iterator<Throwable> i=ls.iterator(); i.hasNext(); j++) { + out.print(j+": "); + i.next().printStackTrace(out); + } + } + } + } +} diff --git a/test/java/jau/util/parallel/locks/RecursiveLockImpl01CompleteFair.java b/test/java/jau/util/parallel/locks/RecursiveLockImpl01CompleteFair.java new file mode 100644 index 0000000..1f73444 --- /dev/null +++ b/test/java/jau/util/parallel/locks/RecursiveLockImpl01CompleteFair.java @@ -0,0 +1,319 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.util.parallel.locks; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.AbstractOwnableSynchronizer; + +import org.jau.lang.SourcedInterruptedException; +import org.jau.util.parallel.locks.RecursiveLock; + +/** + * Reentrance locking toolkit, impl a complete fair FIFO scheduler + * + * <p> + * Sync object extends {@link AbstractOwnableSynchronizer}, hence monitoring is possible.</p> + */ +public class RecursiveLockImpl01CompleteFair implements RecursiveLock { + + private static class WaitingThread { + WaitingThread(final Thread t) { + thread = t; + signaledByUnlock = false; + } + final Thread thread; + boolean signaledByUnlock; // if true, it's also removed from queue + } + + @SuppressWarnings("serial") + private static class Sync extends AbstractOwnableSynchronizer { + private Sync() { + super(); + } + private final Thread getOwner() { + return getExclusiveOwnerThread(); + } + private final void setOwner(final Thread t) { + setExclusiveOwnerThread(t); + } + private final void setLockedStack(final Throwable s) { + final List<Throwable> ls = LockDebugUtil.getRecursiveLockTrace(); + if(s==null) { + ls.remove(lockedStack); + } else { + ls.add(s); + } + lockedStack = s; + } + /** lock count by same thread */ + private int holdCount = 0; + /** waiting thread queue */ + final ArrayList<WaitingThread> queue = new ArrayList<WaitingThread>(); + /** stack trace of the lock, only used if DEBUG */ + private Throwable lockedStack = null; + } + private final Sync sync = new Sync(); + + public RecursiveLockImpl01CompleteFair() { + } + + /** + * Returns the Throwable instance generated when this lock was taken the 1st time + * and if {@link org.jau.util.parallel.locks.Lock#DEBUG} is turned on, otherwise it returns always <code>null</code>. + * @see org.jau.util.parallel.locks.Lock#DEBUG + */ + public final Throwable getLockedStack() { + synchronized(sync) { + return sync.lockedStack; + } + } + + @Override + public final Thread getOwner() { + synchronized(sync) { + return sync.getOwner(); + } + } + + @Override + public final boolean isOwner(final Thread thread) { + synchronized(sync) { + return sync.getOwner() == thread ; + } + } + + @Override + public final boolean isLocked() { + synchronized(sync) { + return null != sync.getOwner(); + } + } + + @Override + public final boolean isLockedByOtherThread() { + synchronized(sync) { + final Thread o = sync.getOwner(); + return null != o && Thread.currentThread() != o ; + } + } + + @Override + public final int getHoldCount() { + synchronized(sync) { + return sync.holdCount; + } + } + + @Override + public final void validateLocked() throws RuntimeException { + synchronized(sync) { + if ( Thread.currentThread() != sync.getOwner() ) { + if ( null == sync.getOwner() ) { + throw new RuntimeException(threadName(Thread.currentThread())+": Not locked: "+toString()); + } + if(null!=sync.lockedStack) { + sync.lockedStack.printStackTrace(); + } + throw new RuntimeException(Thread.currentThread()+": Not owner: "+toString()); + } + } + } + + @Override + public final void lock() { + synchronized(sync) { + try { + if(!tryLock(TIMEOUT)) { + if(null!=sync.lockedStack) { + sync.lockedStack.printStackTrace(); + } + throw new RuntimeException("Waited "+TIMEOUT+"ms for: "+toString()+" - "+threadName(Thread.currentThread())); + } + } catch (final InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + } + + @Override + public final boolean tryLock(long timeout) throws InterruptedException { + synchronized(sync) { + final Thread cur = Thread.currentThread(); + if(TRACE_LOCK) { + System.err.println("+++ LOCK 0 "+toString()+", cur "+threadName(cur)); + } + if (sync.getOwner() == cur) { + ++sync.holdCount; + if(TRACE_LOCK) { + System.err.println("+++ LOCK XR "+toString()+", cur "+threadName(cur)); + } + return true; + } + + if ( sync.getOwner() != null || ( 0<timeout && 0<sync.queue.size() ) ) { + + if ( 0 >= timeout ) { + // locked by other thread and no waiting requested + if(TRACE_LOCK) { + System.err.println("+++ LOCK XY "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms"); + } + return false; + } + + // enqueue at the start + final WaitingThread wCur = new WaitingThread(cur); + sync.queue.add(0, wCur); + do { + final long t0 = System.currentTimeMillis(); + try { + sync.wait(timeout); + timeout -= System.currentTimeMillis() - t0; + } catch (final InterruptedException e) { + if( !wCur.signaledByUnlock ) { + sync.queue.remove(wCur); // O(n) + throw SourcedInterruptedException.wrap(e); // propagate interruption not send by unlock + } else if( cur != sync.getOwner() ) { + // Issued by unlock, but still locked by other thread + // + timeout -= System.currentTimeMillis() - t0; + + if(TRACE_LOCK) { + System.err.println("+++ LOCK 1 "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms, signaled: "+wCur.signaledByUnlock); + } + + if(0 < timeout) { + // not timed out, re-enque - lock was 'stolen' + wCur.signaledByUnlock = false; + sync.queue.add(sync.queue.size(), wCur); + } + } // else: Issued by unlock, owning lock .. expected! + } + } while ( cur != sync.getOwner() && 0 < timeout ) ; + Thread.interrupted(); // clear slipped interrupt + + if( 0 >= timeout && cur != sync.getOwner() ) { + // timed out + if(!wCur.signaledByUnlock) { + sync.queue.remove(wCur); // O(n) + } + if(TRACE_LOCK) { + System.err.println("+++ LOCK XX "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms"); + } + return false; + } + + ++sync.holdCount; + if(TRACE_LOCK) { + System.err.println("+++ LOCK X1 "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms"); + } + } else { + ++sync.holdCount; + if(TRACE_LOCK) { + System.err.println("+++ LOCK X0 "+toString()+", cur "+threadName(cur)); + } + } + + sync.setOwner(cur); + if(DEBUG) { + sync.setLockedStack(new Throwable("Previously locked by "+toString())); + } + return true; + } + } + + + @Override + public final void unlock() { + synchronized(sync) { + unlock(null); + } + } + + @Override + public final void unlock(final Runnable taskAfterUnlockBeforeNotify) { + synchronized(sync) { + validateLocked(); + final Thread cur = Thread.currentThread(); + + --sync.holdCount; + + if (sync.holdCount > 0) { + if(TRACE_LOCK) { + System.err.println("--- LOCK XR "+toString()+", cur "+threadName(cur)); + } + return; + } + + if(DEBUG) { + sync.setLockedStack(null); + } + if(null!=taskAfterUnlockBeforeNotify) { + taskAfterUnlockBeforeNotify.run(); + } + + if(sync.queue.size() > 0) { + // fair, wakeup the oldest one .. + // final WaitingThread oldest = queue.removeLast(); + final WaitingThread oldest = sync.queue.remove(sync.queue.size()-1); + sync.setOwner(oldest.thread); + + if(TRACE_LOCK) { + System.err.println("--- LOCK X1 "+toString()+", cur "+threadName(cur)+", signal: "+threadName(oldest.thread)); + } + + oldest.signaledByUnlock = true; + oldest.thread.interrupt(); // Propagate SecurityException if it happens + } else { + sync.setOwner(null); + if(TRACE_LOCK) { + System.err.println("--- LOCK X0 "+toString()+", cur "+threadName(cur)+", signal any"); + } + sync.notify(); + } + } + } + + @Override + public final int getQueueLength() { + synchronized(sync) { + return sync.queue.size(); + } + } + + @Override + public String toString() { + return syncName()+"[count "+sync.holdCount+ + ", qsz "+sync.queue.size()+", owner "+threadName(sync.getOwner())+"]"; + } + + private final String syncName() { + return "<"+Integer.toHexString(this.hashCode())+", "+Integer.toHexString(sync.hashCode())+">"; + } + private final String threadName(final Thread t) { return null!=t ? "<"+t.getName()+">" : "<NULL>" ; } +} + diff --git a/test/java/jau/util/parallel/locks/RecursiveLockImpl01Unfairish.java b/test/java/jau/util/parallel/locks/RecursiveLockImpl01Unfairish.java new file mode 100644 index 0000000..def7ad6 --- /dev/null +++ b/test/java/jau/util/parallel/locks/RecursiveLockImpl01Unfairish.java @@ -0,0 +1,318 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.util.parallel.locks; + +import java.util.List; +import java.util.concurrent.locks.AbstractOwnableSynchronizer; + +import org.jau.util.parallel.locks.RecursiveLock; + +/** + * Reentrance locking toolkit, impl a non-complete fair FIFO scheduler. + * <p> + * Fair scheduling is not guaranteed due to the usage of {@link Object#notify()}, + * however new lock-applicants will wait if queue is not empty for {@link #lock()} + * and {@link #tryLock(long) tryLock}(timeout>0).</p> + * + * <p> + * Sync object extends {@link AbstractOwnableSynchronizer}, hence monitoring is possible.</p> + */ +public class RecursiveLockImpl01Unfairish implements RecursiveLock { + + /* package */ static interface Sync { + Thread getOwner(); + boolean isOwner(Thread t); + void setOwner(Thread t); + + Throwable getLockedStack(); + void setLockedStack(Throwable s); + + int getHoldCount(); + void incrHoldCount(Thread t); + void decrHoldCount(Thread t); + + int getQSz(); + void incrQSz(); + void decrQSz(); + } + + @SuppressWarnings("serial") + /* package */ static class SingleThreadSync extends AbstractOwnableSynchronizer implements Sync { + /* package */ SingleThreadSync() { + super(); + } + @Override + public final Thread getOwner() { + return getExclusiveOwnerThread(); + } + @Override + public boolean isOwner(final Thread t) { + return getExclusiveOwnerThread()==t; + } + @Override + public final void setOwner(final Thread t) { + setExclusiveOwnerThread(t); + } + @Override + public final Throwable getLockedStack() { + return lockedStack; + } + @Override + public final void setLockedStack(final Throwable s) { + final List<Throwable> ls = LockDebugUtil.getRecursiveLockTrace(); + if(s==null) { + ls.remove(lockedStack); + } else { + ls.add(s); + } + lockedStack = s; + } + @Override + public final int getHoldCount() { return holdCount; } + @Override + public void incrHoldCount(final Thread t) { holdCount++; } + @Override + public void decrHoldCount(final Thread t) { holdCount--; } + + @Override + public final int getQSz() { return qsz; } + @Override + public final void incrQSz() { qsz++; } + @Override + public final void decrQSz() { qsz--; } + + /** lock count by same thread */ + private int holdCount = 0; + /** queue size of waiting threads */ + private int qsz = 0; + /** stack trace of the lock, only used if DEBUG */ + private Throwable lockedStack = null; + } + + protected final Sync sync; + + public RecursiveLockImpl01Unfairish(final Sync sync) { + this.sync = sync; + } + + public RecursiveLockImpl01Unfairish() { + this(new SingleThreadSync()); + } + + /** + * Returns the Throwable instance generated when this lock was taken the 1st time + * and if {@link org.jau.util.parallel.locks.Lock#DEBUG} is turned on, otherwise it returns always <code>null</code>. + * @see org.jau.util.parallel.locks.Lock#DEBUG + */ + public final Throwable getLockedStack() { + synchronized(sync) { + return sync.getLockedStack(); + } + } + + @Override + public final Thread getOwner() { + synchronized(sync) { + return sync.getOwner(); + } + } + + @Override + public final boolean isOwner(final Thread thread) { + synchronized(sync) { + return sync.isOwner(thread); + } + } + + @Override + public final boolean isLocked() { + synchronized(sync) { + return null != sync.getOwner(); + } + } + + @Override + public final boolean isLockedByOtherThread() { + synchronized(sync) { + final Thread o = sync.getOwner(); + return null != o && Thread.currentThread() != o ; + } + } + + @Override + public final int getHoldCount() { + synchronized(sync) { + return sync.getHoldCount(); + } + } + + @Override + public final void validateLocked() throws RuntimeException { + synchronized(sync) { + if ( !sync.isOwner(Thread.currentThread()) ) { + if ( null == sync.getOwner() ) { + throw new RuntimeException(threadName(Thread.currentThread())+": Not locked: "+toString()); + } + if(null!=sync.getLockedStack()) { + sync.getLockedStack().printStackTrace(); + } + throw new RuntimeException(Thread.currentThread()+": Not owner: "+toString()); + } + } + } + + @Override + public final void lock() { + synchronized(sync) { + try { + if(!tryLock(TIMEOUT)) { + if(null!=sync.getLockedStack()) { + sync.getLockedStack().printStackTrace(); + } + throw new RuntimeException("Waited "+TIMEOUT+"ms for: "+toString()+" - "+threadName(Thread.currentThread())); + } + } catch (final InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + } + + @Override + public final boolean tryLock(long timeout) throws InterruptedException { + synchronized(sync) { + final Thread cur = Thread.currentThread(); + if(TRACE_LOCK) { + System.err.println("+++ LOCK 0 "+toString()+", timeout "+timeout+" ms, cur "+threadName(cur)); + } + if (sync.isOwner(cur)) { + sync.incrHoldCount(cur); + if(TRACE_LOCK) { + System.err.println("+++ LOCK XR "+toString()+", cur "+threadName(cur)); + } + return true; + } + + if ( sync.getOwner() != null || ( 0<timeout && 0<sync.getQSz() ) ) { + + if ( 0 >= timeout ) { + // locked by other thread and no waiting requested + if(TRACE_LOCK) { + System.err.println("+++ LOCK XY "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms"); + } + return false; + } + + sync.incrQSz(); + do { + final long t0 = System.currentTimeMillis(); + sync.wait(timeout); + timeout -= System.currentTimeMillis() - t0; + } while (null != sync.getOwner() && 0 < timeout) ; + sync.decrQSz(); + + if( 0 >= timeout && sync.getOwner() != null ) { + // timed out + if(TRACE_LOCK) { + System.err.println("+++ LOCK XX "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms"); + } + return false; + } + + if(TRACE_LOCK) { + System.err.println("+++ LOCK X1 "+toString()+", cur "+threadName(cur)+", left "+timeout+" ms"); + } + } else if(TRACE_LOCK) { + System.err.println("+++ LOCK X0 "+toString()+", cur "+threadName(cur)); + } + + sync.setOwner(cur); + sync.incrHoldCount(cur); + + if(DEBUG) { + sync.setLockedStack(new Throwable("Previously locked by "+toString())); + } + return true; + } + } + + + @Override + public final void unlock() { + synchronized(sync) { + unlock(null); + } + } + + @Override + public void unlock(final Runnable taskAfterUnlockBeforeNotify) { + synchronized(sync) { + validateLocked(); + final Thread cur = Thread.currentThread(); + + sync.decrHoldCount(cur); + + if (sync.getHoldCount() > 0) { + if(TRACE_LOCK) { + System.err.println("--- LOCK XR "+toString()+", cur "+threadName(cur)); + } + return; + } + + sync.setOwner(null); + if(DEBUG) { + sync.setLockedStack(null); + } + if(null!=taskAfterUnlockBeforeNotify) { + taskAfterUnlockBeforeNotify.run(); + } + + if(TRACE_LOCK) { + System.err.println("--- LOCK X0 "+toString()+", cur "+threadName(cur)+", signal any"); + } + sync.notify(); + } + } + + @Override + public final int getQueueLength() { + synchronized(sync) { + return sync.getQSz(); + } + } + + @Override + public String toString() { + return syncName()+"[count "+sync.getHoldCount()+ + ", qsz "+sync.getQSz()+", owner "+threadName(sync.getOwner())+"]"; + } + + /* package */ final String syncName() { + return "<"+Integer.toHexString(this.hashCode())+", "+Integer.toHexString(sync.hashCode())+">"; + } + /* package */ final String threadName(final Thread t) { return null!=t ? "<"+t.getName()+">" : "<NULL>" ; } +} + diff --git a/test/java/jau/util/parallel/locks/RecursiveThreadGroupLockImpl01Unfairish.java b/test/java/jau/util/parallel/locks/RecursiveThreadGroupLockImpl01Unfairish.java new file mode 100644 index 0000000..36b62d3 --- /dev/null +++ b/test/java/jau/util/parallel/locks/RecursiveThreadGroupLockImpl01Unfairish.java @@ -0,0 +1,233 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package jau.util.parallel.locks; + +import java.util.Arrays; + +import org.jau.util.parallel.locks.RecursiveThreadGroupLock; + +public class RecursiveThreadGroupLockImpl01Unfairish + extends RecursiveLockImpl01Unfairish + implements RecursiveThreadGroupLock +{ + /* package */ @SuppressWarnings("serial") + static class ThreadGroupSync extends SingleThreadSync { + /* package */ ThreadGroupSync() { + super(); + threadNum = 0; + threads = null; + holdCountAdditionOwner = 0; + waitingOrigOwner = null; + } + @Override + public final void incrHoldCount(final Thread t) { + super.incrHoldCount(t); + if(!isOriginalOwner(t)) { + holdCountAdditionOwner++; + } + } + @Override + public final void decrHoldCount(final Thread t) { + super.decrHoldCount(t); + if(!isOriginalOwner(t)) { + holdCountAdditionOwner--; + } + } + public final int getAdditionalOwnerHoldCount() { + return holdCountAdditionOwner; + } + + public final boolean isOriginalOwner(final Thread t) { + return super.isOwner(t); + } + public final void setWaitingOrigOwner(final Thread origOwner) { + waitingOrigOwner = origOwner; + } + public final Thread getWaitingOrigOwner() { + return waitingOrigOwner; + } + @Override + public final boolean isOwner(final Thread t) { + if(getExclusiveOwnerThread()==t) { + return true; + } + for(int i=threadNum-1; 0<=i; i--) { + if(threads[i]==t) { + return true; + } + } + return false; + } + + public final int getAddOwnerCount() { + return threadNum; + } + public final void addOwner(final Thread t) throws IllegalArgumentException { + if(null == threads) { + if(threadNum>0) { + throw new InternalError("XXX"); + } + threads = new Thread[4]; + } + for(int i=threadNum-1; 0<=i; i--) { + if(threads[i]==t) { + throw new IllegalArgumentException("Thread already added: "+t); + } + } + if (threadNum == threads.length) { + threads = Arrays.copyOf(threads, threadNum * 2); + } + threads[threadNum] = t; + threadNum++; + } + + public final void removeAllOwners() { + for(int i=threadNum-1; 0<=i; i--) { + threads[i]=null; + } + threadNum=0; + } + + public final void removeOwner(final Thread t) throws IllegalArgumentException { + for (int i = 0 ; i < threadNum ; i++) { + if (threads[i] == t) { + threadNum--; + System.arraycopy(threads, i + 1, threads, i, threadNum - i); + threads[threadNum] = null; // cleanup 'dead' [or duplicate] reference for GC + return; + } + } + throw new IllegalArgumentException("Not an owner: "+t); + } + + String addOwnerToString() { + final StringBuilder sb = new StringBuilder(); + for(int i=0; i<threadNum; i++) { + if(i>0) { + sb.append(", "); + } + sb.append(threads[i].getName()); + } + return sb.toString(); + } + + // lock count by addition owner threads + private int holdCountAdditionOwner; + private Thread[] threads; + private int threadNum; + private Thread waitingOrigOwner; + } + + public RecursiveThreadGroupLockImpl01Unfairish() { + super(new ThreadGroupSync()); + } + + @Override + public final boolean isOriginalOwner() { + return isOriginalOwner(Thread.currentThread()); + } + + @Override + public final boolean isOriginalOwner(final Thread thread) { + synchronized(sync) { + return ((ThreadGroupSync)sync).isOriginalOwner(thread) ; + } + } + + @Override + public final void addOwner(final Thread t) throws RuntimeException, IllegalArgumentException { + validateLocked(); + final Thread cur = Thread.currentThread(); + final ThreadGroupSync tgSync = (ThreadGroupSync)sync; + if(!tgSync.isOriginalOwner(cur)) { + throw new IllegalArgumentException("Current thread is not the original owner: orig-owner: "+tgSync.getOwner()+", current "+cur+": "+toString()); + } + if(tgSync.isOriginalOwner(t)) { + throw new IllegalArgumentException("Passed thread is original owner: "+t+", "+toString()); + } + tgSync.addOwner(t); + } + + @Override + public final void unlock(final Runnable taskAfterUnlockBeforeNotify) { + synchronized(sync) { + final Thread cur = Thread.currentThread(); + final ThreadGroupSync tgSync = (ThreadGroupSync)sync; + + if( tgSync.getAddOwnerCount()>0 ) { + if(TRACE_LOCK) { + System.err.println("--- LOCK XR (tg) "+toString()+", cur "+threadName(cur)+" -> owner..."); + } + if( tgSync.isOriginalOwner(cur) ) { + // original locking owner thread + if( tgSync.getHoldCount() - tgSync.getAdditionalOwnerHoldCount() == 1 ) { + // release orig. lock + tgSync.setWaitingOrigOwner(cur); + try { + while ( tgSync.getAdditionalOwnerHoldCount() > 0 ) { + try { + sync.wait(); + } catch (final InterruptedException e) { + // regular wake up! + } + } + } finally { + tgSync.setWaitingOrigOwner(null); + Thread.interrupted(); // clear slipped interrupt + } + tgSync.removeAllOwners(); + } + } else if( tgSync.getAdditionalOwnerHoldCount() == 1 ) { + // last additional owner thread wakes up original owner if waiting in unlock(..) + final Thread originalOwner = tgSync.getWaitingOrigOwner(); + if( null != originalOwner ) { + originalOwner.interrupt(); + } + } + } + if(TRACE_LOCK) { + System.err.println("++ unlock(X): currentThread "+cur.getName()+", lock: "+this.toString()); + System.err.println("--- LOCK X0 (tg) "+toString()+", cur "+threadName(cur)+" -> unlock!"); + } + super.unlock(taskAfterUnlockBeforeNotify); + } + } + + @Override + public final void removeOwner(final Thread t) throws RuntimeException, IllegalArgumentException { + validateLocked(); + ((ThreadGroupSync)sync).removeOwner(t); + } + + @Override + public String toString() { + final ThreadGroupSync tgSync = (ThreadGroupSync)sync; + final int hc = sync.getHoldCount(); + final int addHC = tgSync.getAdditionalOwnerHoldCount(); + return syncName()+"[count "+hc+" [ add. "+addHC+", orig "+(hc-addHC)+ + "], qsz "+sync.getQSz()+", owner "+threadName(sync.getOwner())+", add.owner "+tgSync.addOwnerToString()+"]"; + } +} diff --git a/test/java/jau/util/parallel/locks/SingletonInstanceFileLock.java b/test/java/jau/util/parallel/locks/SingletonInstanceFileLock.java new file mode 100644 index 0000000..15ea217 --- /dev/null +++ b/test/java/jau/util/parallel/locks/SingletonInstanceFileLock.java @@ -0,0 +1,134 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.util.parallel.locks; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; + +import org.jau.lang.InterruptSource; +import org.jau.util.parallel.locks.SingletonInstance; + +public class SingletonInstanceFileLock extends SingletonInstance { + + static final String temp_file_path; + + static { + String s = null; + try { + final File tmpFile = File.createTempFile("TEST", "tst"); + final String absTmpFile = tmpFile.getCanonicalPath(); + tmpFile.delete(); + s = absTmpFile.substring(0, absTmpFile.lastIndexOf(File.separator)); + } catch (final IOException ex) { + ex.printStackTrace(); + } + temp_file_path = s; + } + + public static String getCanonicalTempPath() { + return temp_file_path; + } + + public static String getCanonicalTempLockFilePath(final String basename) { + return getCanonicalTempPath() + File.separator + basename; + } + + public SingletonInstanceFileLock(final long poll_ms, final String lockFileBasename) { + super(poll_ms); + file = new File ( getCanonicalTempLockFilePath ( lockFileBasename ) ); + setupFileCleanup(); + } + + public SingletonInstanceFileLock(final long poll_ms, final File lockFile) { + super(poll_ms); + file = lockFile ; + setupFileCleanup(); + } + + @Override + public final String getName() { return file.getPath(); } + + private void setupFileCleanup() { + file.deleteOnExit(); + Runtime.getRuntime().addShutdownHook(new InterruptSource.Thread() { + @Override + public void run() { + if(isLocked()) { + System.err.println(infoPrefix()+" XXX "+SingletonInstanceFileLock.this.getName()+" - Unlock @ JVM Shutdown"); + } + unlock(); + } + }); + } + + @Override + protected boolean tryLockImpl() { + try { + randomAccessFile = new RandomAccessFile(file, "rw"); + fileLock = randomAccessFile.getChannel().tryLock(); + + if (fileLock != null) { + return true; + } + } catch (final Exception e) { + System.err.println(infoPrefix()+" III "+getName()+" - Unable to create and/or lock file"); + e.printStackTrace(); + } + return false; + } + + @Override + protected boolean unlockImpl() { + try { + if(null != fileLock) { + fileLock.release(); + fileLock = null; + } + if(null != randomAccessFile) { + randomAccessFile.close(); + randomAccessFile = null; + } + if(null != file) { + file.delete(); + } + return true; + } catch (final Exception e) { + System.err.println(infoPrefix()+" EEE "+getName()+" - Unable to remove lock file"); + e.printStackTrace(); + } finally { + fileLock = null; + randomAccessFile = null; + } + return false; + } + + private final File file; + private RandomAccessFile randomAccessFile = null; + private FileLock fileLock = null; +} diff --git a/test/java/jau/util/parallel/locks/SingletonInstanceServerSocket.java b/test/java/jau/util/parallel/locks/SingletonInstanceServerSocket.java new file mode 100644 index 0000000..32e1ed5 --- /dev/null +++ b/test/java/jau/util/parallel/locks/SingletonInstanceServerSocket.java @@ -0,0 +1,290 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.util.parallel.locks; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.jau.lang.ExceptionUtils; +import org.jau.lang.InterruptSource; +import org.jau.lang.InterruptedRuntimeException; +import org.jau.lang.SourcedInterruptedException; +import org.jau.util.parallel.locks.SingletonInstance; + +public class SingletonInstanceServerSocket extends SingletonInstance { + + private static int serverInstanceCount = 0; + private final Server singletonServer; + private final String fullName; + + public SingletonInstanceServerSocket(final long poll_ms, final int portNumber) { + super(poll_ms); + + // Gather the local InetAddress, loopback is prioritized + InetAddress ilh = null; + try { + ilh = InetAddress.getByName(null); // loopback + } catch (final UnknownHostException e1) { } + if(null == ilh) { + try { + ilh = InetAddress.getByName("localhost"); + if(null!=ilh && !ilh.isLoopbackAddress()) { ilh = null; } + } catch (final UnknownHostException e1) { } + } + if(null == ilh) { + try { + ilh = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 } ); + if(null!=ilh && !ilh.isLoopbackAddress()) { ilh = null; } + } catch (final UnknownHostException e) { } + } + if(null == ilh) { + try { + ilh = InetAddress.getLocalHost(); + } catch (final UnknownHostException e) { } + } + if(null == ilh) { + throw new RuntimeException(infoPrefix()+" EEE Could not determine local InetAddress"); + } + + fullName = ilh.toString()+":"+portNumber; + singletonServer = new Server(ilh, portNumber); + Runtime.getRuntime().addShutdownHook(new InterruptSource.Thread() { + @Override + public void run() { + singletonServer.kill(); + } + }); + } + + public final InetAddress getLocalInetAddress() { + return singletonServer.getLocalInetAddress(); + } + + public final int getPortNumber() { + return singletonServer.getPortNumber(); + } + + @Override + public final String getName() { return fullName; } + + @Override + protected boolean tryLockImpl() { + if( singletonServer.isRunning() ) { + return false; // same JVM .. server socket already installed ! + } + + // check if other JVM's locked the server socket .. + final Socket clientSocket = singletonServer.connect(); + if(null != clientSocket) { + try { + clientSocket.close(); + } catch (final IOException e) { } + return false; + } + + if( !singletonServer.start() ) { + return false; + } + + return true; + } + + @Override + protected boolean unlockImpl() { + return singletonServer.shutdown(); + } + + public class Server implements Runnable { + private final InetAddress localInetAddress; + private final int portNumber; + + private volatile boolean shallQuit = false; + private volatile boolean alive = false; + + private final Object syncOnStartStop = new Object(); + private ServerSocket serverSocket = null; + private Thread serverThread = null; // allowing kill() to force-stop last server-thread + + public Server(final InetAddress localInetAddress, final int portNumber) { + this.localInetAddress = localInetAddress; + this.portNumber = portNumber; + } + + public final InetAddress getLocalInetAddress() { return localInetAddress; } + public final int getPortNumber() { return portNumber; } + + public final boolean start() { + if(alive) return true; + + final String sname; + synchronized (Server.class) { + serverInstanceCount++; + sname = "SingletonServerSocket"+serverInstanceCount+"-"+fullName; + } + synchronized (syncOnStartStop) { + shallQuit = false; + serverThread = new InterruptSource.Thread(null, this, sname); + serverThread.setDaemon(true); // be a daemon, don't keep the JVM running + serverThread.start(); + try { + while( !alive && !shallQuit ) { + syncOnStartStop.wait(); + } + } catch (final InterruptedException ie) { + final InterruptedException ie2 = SourcedInterruptedException.wrap(ie); + shutdown(false); + throw new InterruptedRuntimeException(ie2); + } + } + final boolean ok = isBound(); + if(!ok) { + shutdown(true); + } + return ok; + } + + public final boolean shutdown() { + return shutdown(true); + } + private final boolean shutdown(final boolean wait) { + if(!alive) return true; + + try { + synchronized (syncOnStartStop) { + shallQuit = true; + connect(); + if( wait ) { + try { + while( alive ) { + syncOnStartStop.wait(); + } + } catch (final InterruptedException ie) { + throw new InterruptedRuntimeException(ie); + } + } + } + } finally { + if(alive) { + System.err.println(infoPrefix()+" EEE "+getName()+" - Unable to remove lock: ServerThread still alive ?"); + kill(); + } + } + return true; + } + + /** + * Brutally kill server thread and close socket regardless. + * This is out last chance for JVM shutdown. + */ + @SuppressWarnings("deprecation") + public final void kill() { + if(alive) { + System.err.println(infoPrefix()+" XXX "+getName()+" - Kill @ JVM Shutdown"); + } + alive = false; + shallQuit = false; + if(null != serverThread && serverThread.isAlive() ) { + try { + serverThread.stop(); + } catch(final Throwable t) { } + } + if(null != serverSocket) { + try { + final ServerSocket ss = serverSocket; + serverSocket = null; + ss.close(); + } catch (final Throwable t) { } + } + } + + public final boolean isRunning() { return alive; } + + public final boolean isBound() { + return alive && null != serverSocket && serverSocket.isBound() ; + } + + public final Socket connect() { + try { + return new Socket(localInetAddress, portNumber); + } catch (final Exception e) { } + return null; + } + + @Override + public void run() { + if(DEBUG) { + System.err.println(infoPrefix()+" III - Start"); + } + try { + synchronized (syncOnStartStop) { + try { + serverSocket = new ServerSocket(portNumber, 1, localInetAddress); + serverSocket.setReuseAddress(true); // reuse same port w/ subsequent instance, i.e. overcome TO state when JVM crashed + alive = true; + } catch (final IOException e) { + System.err.println(infoPrefix()+" III - Unable to install ServerSocket: "+e.getMessage()); + shallQuit = true; + } finally { + syncOnStartStop.notifyAll(); + } + } + + while (!shallQuit) { + try { + final Socket clientSocket = serverSocket.accept(); + clientSocket.close(); + } catch (final IOException ioe) { + System.err.println(infoPrefix()+" EEE - Exception during accept: " + ioe.getMessage()); + } + } + } catch(final ThreadDeath td) { + if( DEBUG ) { + ExceptionUtils.dumpThrowable("", td); + } + } finally { + synchronized (syncOnStartStop) { + if(DEBUG) { + System.err.println(infoPrefix()+" III - Stopping: alive "+alive+", shallQuit "+shallQuit+", hasSocket "+(null!=serverSocket)); + } + if(null != serverSocket) { + try { + serverSocket.close(); + } catch (final IOException e) { + System.err.println(infoPrefix()+" EEE - Exception during close: " + e.getMessage()); + } + } + serverSocket = null; + alive = false; + syncOnStartStop.notifyAll(); + } + } + } + } +} diff --git a/test/java/manifest.txt.in b/test/java/manifest.txt.in new file mode 100644 index 0000000..5cdbab0 --- /dev/null +++ b/test/java/manifest.txt.in @@ -0,0 +1,24 @@ +Manifest-Version: 1.0 +Bundle-Date: @BUILD_TSTAMP@ +Bundle-ManifestVersion: 2 +Bundle-Name: org.jau.test +Bundle-SymbolicName: org.jau.test +Bundle-Version: @VERSION_SHORT@ +Export-Package: org.jau.test +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.9))" +Package-Title: org.jau.test +Package-Vendor: Gothel Software +Package-Version: @VERSION_SHORT@ +Specification-Title: Jaulib Unit Tests +Specification-Vendor: Gothel Software +Specification-Version: @VERSION_API@ +Implementation-Title: Jaulib Unit Tests +Implementation-Vendor: Gothel Software +Implementation-Version: @VERSION@ +Implementation-Commit: @VERSION_SHA1@ +Implementation-URL: http://www.jausoft.com/ +Extension-Name: org.jau.test +Trusted-Library: true +Permissions: all-permissions +Application-Library-Allowable-Codebase: * +Class-Path: ../../java_base/jaulib_base.jar ../../java_jni/jaulib_jni.jar ../../java_net/jaulib_net.jar ../../java_pkg/jaulib_pkg.jar diff --git a/test/java/org/jau/junit/util/JunitTracer.java b/test/java/org/jau/junit/util/JunitTracer.java new file mode 100644 index 0000000..6ea52dc --- /dev/null +++ b/test/java/org/jau/junit/util/JunitTracer.java @@ -0,0 +1,107 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.junit.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.rules.TestName; +import org.junit.runners.MethodSorters; + + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public abstract class JunitTracer { + @Rule public final TestName _unitTestName = new TestName(); + + static volatile boolean testSupported = true; + + public static final boolean isTestSupported() { + return testSupported; + } + + public static final void setTestSupported(final boolean v) { + System.err.println("setTestSupported: "+v); + testSupported = v; + } + + public final String getTestMethodName() { + return _unitTestName.getMethodName(); + } + + public final String getSimpleTestName(final String separator) { + return getClass().getSimpleName()+separator+getTestMethodName(); + } + + public final String getFullTestName(final String separator) { + return getClass().getName()+separator+getTestMethodName(); + } + + @BeforeClass + public static final void oneTimeSetUpBase() { + // one-time initialization code + } + + @AfterClass + public static final void oneTimeTearDownBase() { + // one-time cleanup code + System.gc(); // force cleanup + } + + @Before + public final void setUpBase() { + System.err.print("++++ TestCase.setUp: "+getFullTestName(" - ")); + if(!testSupported) { + System.err.println(" - "+unsupportedTestMsg); + Assume.assumeTrue(testSupported); + } + System.err.println(); + } + + @After + public final void tearDownBase() { + System.err.println("++++ TestCase.tearDown: "+getFullTestName(" - ")); + } + + static final String unsupportedTestMsg = "Test not supported on this platform."; + + public static void waitForKey(final String preMessage) { + final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + System.err.println(preMessage+"> Press enter to continue"); + try { + System.err.println(stdin.readLine()); + } catch (final IOException e) { e.printStackTrace(); } + } +} + diff --git a/test/java/org/jau/junit/util/MiscUtils.java b/test/java/org/jau/junit/util/MiscUtils.java new file mode 100644 index 0000000..f489cc3 --- /dev/null +++ b/test/java/org/jau/junit/util/MiscUtils.java @@ -0,0 +1,110 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.junit.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.jau.io.IOUtil; + +public class MiscUtils { + public static class CopyStats { + public int totalBytes = 0; + public int totalFiles = 0; + public int totalFolders = 0; + public int currentDepth = 0; + public int maxDepth = 0; + public boolean trackFiles; + public final List<File> srcFiles = new ArrayList<File>(); + public final List<File> dstFiles = new ArrayList<File>(); + + public void dump(final String prefix, final boolean folderOnly) { + System.err.println(prefix+"Total bytes: "+totalBytes); + System.err.println(prefix+"Total files: "+totalFiles); + System.err.println(prefix+"Total folder: "+totalFolders); + System.err.println(prefix+"Depth: "+currentDepth/maxDepth); + System.err.println(prefix+"Tracking: "+trackFiles); + if( trackFiles ) { + for(int i=0; i<srcFiles.size(); i++) { + final File src = srcFiles.get(i); + final File dst = dstFiles.get(i); + if( !folderOnly || src.isDirectory() ) { + System.err.printf("%s\t src %4d: %s%n", prefix, i, src.toString()); + System.err.printf("%s\t dst %4d: %s%n%n", prefix, i, dst.toString()); + } + } + } + } + } + public static CopyStats copy(final File src, final File dest, final int maxDepth, final boolean trackFiles) throws IOException { + final CopyStats cs = new CopyStats(); + cs.maxDepth = maxDepth; + cs.trackFiles = trackFiles; + copy(src, dest, cs); + return cs; + } + private static void copy(final File src, final File dest, final CopyStats stats) throws IOException { + if(src.isDirectory()){ + if( stats.maxDepth >= 0 && stats.currentDepth >= stats.maxDepth ) { + return; + } + stats.totalFolders++; + if( stats.trackFiles ) { + stats.srcFiles.add(src); + stats.dstFiles.add(dest); + } + stats.currentDepth++; + if(!dest.exists()){ + dest.mkdirs(); + } + final String fileNames[] = src.list(); + for (int i=0; i<fileNames.length; i++) { + final String fileName = fileNames[i]; + final File srcFile = new File(src, fileName); + final File destFile = new File(dest, fileName); + copy(srcFile, destFile, stats); + } + stats.currentDepth--; + } else { + stats.totalFiles++; + if( stats.trackFiles ) { + stats.srcFiles.add(src); + stats.dstFiles.add(dest); + } + final InputStream in = new BufferedInputStream(new FileInputStream(src)); + try { + stats.totalBytes += IOUtil.copyStream2File(in, dest, 0); + } finally { + in.close(); + } + } + } +} diff --git a/test/java/org/jau/junit/util/SingletonJunitCase.java b/test/java/org/jau/junit/util/SingletonJunitCase.java new file mode 100644 index 0000000..264db04 --- /dev/null +++ b/test/java/org/jau/junit/util/SingletonJunitCase.java @@ -0,0 +1,88 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.junit.util; + +import org.junit.BeforeClass; +import org.jau.util.parallel.locks.SingletonInstance; +import org.junit.AfterClass; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public abstract class SingletonJunitCase extends JunitTracer { + public static final String SINGLE_INSTANCE_LOCK_FILE = "SingletonTestCase.lock"; + public static final int SINGLE_INSTANCE_LOCK_PORT = 59999; + + public static final long SINGLE_INSTANCE_LOCK_TO = 15*60*1000; // wait up to 15 mins + public static final long SINGLE_INSTANCE_LOCK_POLL = 500; // poll every 500 ms + + private static SingletonInstance singletonInstance = null; // system wide lock via port locking + private static final Object singletonSync = new Object(); // classloader wide lock + private static boolean enabled = true; + + /** + * Default is {@code true}. + */ + public static final void enableSingletonLock(final boolean v) { + enabled = v; + } + + @BeforeClass + public static final void oneTimeSetUpSingleton() { + // one-time initialization code + synchronized( singletonSync ) { + if( enabled ) { + if( null == singletonInstance ) { + System.err.println("++++ Test Singleton.ctor()"); + // singletonInstance = SingletonInstance.createFileLock(SINGLE_INSTANCE_LOCK_POLL, SINGLE_INSTANCE_LOCK_FILE); + singletonInstance = SingletonInstance.createServerSocket(SINGLE_INSTANCE_LOCK_POLL, SINGLE_INSTANCE_LOCK_PORT); + } + System.err.println("++++ Test Singleton.lock()"); + if(!singletonInstance.tryLock(SINGLE_INSTANCE_LOCK_TO)) { + throw new RuntimeException("Fatal: Could not lock single instance: "+singletonInstance.getName()); + } + } + } + } + + @AfterClass + public static final void oneTimeTearDownSingleton() { + // one-time cleanup code + synchronized( singletonSync ) { + System.gc(); // force cleanup + if( enabled ) { + System.err.println("++++ Test Singleton.unlock()"); + singletonInstance.unlock(); + try { + // allowing other JVM instances to pick-up socket + Thread.sleep( SINGLE_INSTANCE_LOCK_POLL ); + } catch (final InterruptedException e) { } + } + } + } +} + diff --git a/test/java/org/jau/net/AssetURLConnectionBase.java b/test/java/org/jau/net/AssetURLConnectionBase.java new file mode 100644 index 0000000..d1e99d5 --- /dev/null +++ b/test/java/org/jau/net/AssetURLConnectionBase.java @@ -0,0 +1,64 @@ +package org.jau.net; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.JarURLConnection; +import java.net.URLConnection; + +import org.jau.io.IOUtil; +import org.jau.junit.util.SingletonJunitCase; +import org.jau.sys.AndroidVersion; +import org.junit.Assert; + +public abstract class AssetURLConnectionBase extends SingletonJunitCase { + + /** In jaulib_base.jar */ + protected static final String test_asset_rt_url = "asset:jau/info.txt"; + protected static final String test_asset_rt_entry = "jau/info.txt"; + + protected static final String test_asset_rt2_url = "asset:/jau/info.txt"; + + /** In gluegen.test.jar */ + protected static final String test_asset_test1_url = "asset:jau-test/info.txt"; + protected static final String test_asset_test1_entry = "jau-test/info.txt"; + protected static final Uri.Encoded test_asset_test2_rel = Uri.Encoded.cast("data/AssetURLConnectionTest.txt"); + protected static final String test_asset_test2a_url = "asset:org/jau/net/data/AssetURLConnectionTest.txt"; + protected static final String test_asset_test2b_url = "asset:/org/jau/net/data/AssetURLConnectionTest.txt"; + protected static final String test_asset_test2_entry = "org/jau/net/data/AssetURLConnectionTest.txt"; + protected static final Uri.Encoded test_asset_test3_rel = Uri.Encoded.cast("RelativeData.txt"); + protected static final String test_asset_test3a_url = "asset:org/jau/net/data/RelativeData.txt"; + protected static final String test_asset_test3b_url = "asset:/org/jau/net/data/RelativeData.txt"; + protected static final String test_asset_test3_entry = "org/jau/net/data/RelativeData.txt"; + protected static final Uri.Encoded test_asset_test4_rel = Uri.Encoded.cast("../data2/RelativeData2.txt"); + protected static final String test_asset_test4a_url = "asset:org/jau/net/data2/RelativeData2.txt"; + protected static final String test_asset_test4b_url = "asset:/org/jau/net/data2/RelativeData2.txt"; + protected static final String test_asset_test4_entry = "org/jau/net/data2/RelativeData2.txt"; + + protected static void testAssetConnection(final URLConnection c, final String entry_name) throws IOException { + Assert.assertNotNull(c); + if(c instanceof AssetURLConnection) { + final AssetURLConnection ac = (AssetURLConnection) c; + Assert.assertEquals(entry_name, ac.getEntryName()); + } else if(c instanceof JarURLConnection) { + final JarURLConnection jc = (JarURLConnection) c; + if(AndroidVersion.isAvailable) { + Assert.assertEquals("assets/"+entry_name, jc.getEntryName()); + } else { + Assert.assertEquals(entry_name, jc.getEntryName()); + } + } + + final BufferedReader reader = new BufferedReader(new InputStreamReader(c.getInputStream())); + try { + String line = null; + int l = 0; + while ((line = reader.readLine()) != null) { + System.err.println(c.getURL()+":"+l+"> "+line); + l++; + } + } finally { + IOUtil.close(reader, false); + } + } +}
\ No newline at end of file diff --git a/test/java/org/jau/net/TestAssetURLConnectionRegistered.java b/test/java/org/jau/net/TestAssetURLConnectionRegistered.java new file mode 100644 index 0000000..1871982 --- /dev/null +++ b/test/java/org/jau/net/TestAssetURLConnectionRegistered.java @@ -0,0 +1,89 @@ +package org.jau.net; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; + +import org.jau.io.IOUtil; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestAssetURLConnectionRegistered extends AssetURLConnectionBase { + + @BeforeClass + public static void assetRegistration() throws Exception { + try { + System.err.println("******* Asset URL Stream Handler Registration: PRE"); + Assert.assertTrue("GenericURLStreamHandlerFactory.register() failed", AssetURLContext.registerHandler(TestAssetURLConnectionRegistered.class.getClassLoader())); + Assert.assertNotNull(AssetURLContext.getRegisteredHandler()); + System.err.println("******* Asset URL Stream Handler Registration: POST"); + } catch (final Exception e) { + setTestSupported(false); + throw e; + } + } + + @Test + public void assetRegisteredURLConnection_RT() throws IOException { + testAssetConnection(createAssetURLConnection(test_asset_rt_url), test_asset_rt_entry); + } + + @Test + public void assetRegisteredURLConnection_Test() throws IOException { + testAssetConnection(createAssetURLConnection(test_asset_test1_url), test_asset_test1_entry); + } + + @Test + public void assetRegisteredIOUtilGetResourceRel1_RT() throws IOException, URISyntaxException { + final URLConnection urlConn0 = IOUtil.getResource(test_asset_test2a_url, this.getClass().getClassLoader()); + Assert.assertNotNull(urlConn0); + Assert.assertEquals(test_asset_test2a_url, urlConn0.getURL().toExternalForm()); + testAssetConnection(urlConn0, test_asset_test2_entry); + + final Uri uri1 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test3_rel); + Assert.assertNotNull(uri1); + Assert.assertEquals(test_asset_test3a_url, uri1.toString()); + testAssetConnection(uri1.toURL().openConnection(), test_asset_test3_entry); + + final Uri uri2 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test4_rel); + Assert.assertNotNull(uri2); + Assert.assertEquals(test_asset_test4a_url, uri2.toString()); + testAssetConnection(uri2.toURL().openConnection(), test_asset_test4_entry); + } + + @Test + public void assetRegisteredIOUtilGetResourceRel2_RT() throws IOException, URISyntaxException { + final URLConnection urlConn0 = IOUtil.getResource(test_asset_test2b_url, this.getClass().getClassLoader()); + Assert.assertNotNull(urlConn0); + Assert.assertEquals(test_asset_test2b_url, urlConn0.getURL().toExternalForm()); + testAssetConnection(urlConn0, test_asset_test2_entry); + + final Uri uri1 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test3_rel); + Assert.assertNotNull(uri1); + Assert.assertEquals(test_asset_test3b_url, uri1.toString()); + testAssetConnection(uri1.toURL().openConnection(), test_asset_test3_entry); + + final Uri uri2 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test4_rel); + Assert.assertNotNull(uri2); + Assert.assertEquals(test_asset_test4b_url, uri2.toString()); + testAssetConnection(uri2.toURL().openConnection(), test_asset_test4_entry); + } + + URLConnection createAssetURLConnection(final String path) throws IOException { + final URL url = AssetURLContext.createURL(path); + final URLConnection c = url.openConnection(); + System.err.println("createAssetURL: "+path+" -> url: "+url+" -> conn: "+c+" / connURL "+(null!=c?c.getURL():null)); + return c; + + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestAssetURLConnectionRegistered.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/TestAssetURLConnectionUnregistered.java b/test/java/org/jau/net/TestAssetURLConnectionUnregistered.java new file mode 100644 index 0000000..805e167 --- /dev/null +++ b/test/java/org/jau/net/TestAssetURLConnectionUnregistered.java @@ -0,0 +1,62 @@ +package org.jau.net; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; + +import org.jau.io.IOUtil; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestAssetURLConnectionUnregistered extends AssetURLConnectionBase { + @Test + public void assetUnregisteredURLConnection_RT2() throws IOException { + testAssetConnection(createAssetURLConnection(test_asset_rt2_url, this.getClass().getClassLoader()), test_asset_rt_entry); + } + + @Test + public void assetUnregisteredURLConnection_RT() throws IOException { + testAssetConnection(createAssetURLConnection(test_asset_rt_url, this.getClass().getClassLoader()), test_asset_rt_entry); + } + + @Test + public void assetUnregisteredURLConnection_Test() throws IOException { + testAssetConnection(createAssetURLConnection(test_asset_test1_url, this.getClass().getClassLoader()), test_asset_test1_entry); + } + + @Test + public void assetUnregisteredIOUtilGetResourceAbs_RT() throws IOException { + final URLConnection c = IOUtil.getResource(test_asset_rt_entry, this.getClass().getClassLoader()); + testAssetConnection(c, test_asset_rt_entry); + } + + @Test + public void assetUnregisteredIOUtilGetResourceRel0_RT() throws IOException, URISyntaxException { + final URLConnection urlConn0 = IOUtil.getResource(test_asset_test2_rel.get(), this.getClass().getClassLoader(), this.getClass()); + testAssetConnection(urlConn0, test_asset_test2_entry); + + final Uri uri1 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test3_rel); + Assert.assertNotNull(uri1); // JARFile URL .. + testAssetConnection(uri1.toURL().openConnection(), test_asset_test3_entry); + + final Uri uri2 = Uri.valueOf(urlConn0.getURL()).getRelativeOf(test_asset_test4_rel); + Assert.assertNotNull(uri2); + testAssetConnection(uri2.toURL().openConnection(), test_asset_test4_entry); + } + + protected static URLConnection createAssetURLConnection(final String path, final ClassLoader cl) throws IOException { + final URL url = AssetURLContext.createURL(path, cl); + final URLConnection c = url.openConnection(); + System.err.println("createAssetURL: "+path+" -> url: "+url+" -> conn: "+c+" / connURL "+(null!=c?c.getURL():null)); + return c; + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestAssetURLConnectionUnregistered.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/TestUri01.java b/test/java/org/jau/net/TestUri01.java new file mode 100644 index 0000000..ea77b75 --- /dev/null +++ b/test/java/org/jau/net/TestUri01.java @@ -0,0 +1,458 @@ +package org.jau.net; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.jau.io.IOUtil; +import org.jau.junit.util.SingletonJunitCase; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUri01 extends SingletonJunitCase { + + @Test + public void test00BasicCoding() throws IOException, URISyntaxException { + final String string = "Hallo Welt öä"; + System.err.println("sp1 "+string); + final File file = new File(string); + System.err.println("file "+file); + System.err.println("file.path.dec "+file.getPath()); + System.err.println("file.path.abs "+file.getAbsolutePath()); + System.err.println("file.path.can "+file.getCanonicalPath()); + final Uri uri0 = Uri.valueOf(file); + URIDumpUtil.showUri(uri0); + URIDumpUtil.showReencodedURIOfUri(uri0); + + boolean ok = true; + { + final String s2 = IOUtil.slashify(file.getAbsolutePath(), true /* startWithSlash */, file.isDirectory() /* endWithSlash */); + System.err.println("uri2.slashify: "+s2); + final Uri uri1 = Uri.create(Uri.FILE_SCHEME, null, s2, null); + final boolean equalEncoded= uri0.getEncoded().equals(uri1.getEncoded()); + final boolean equalPath = uri0.path.decode().equals(uri1.path.decode()); + final boolean equalASCII= uri0.toASCIIString().equals(uri1.toASCIIString().get()); + System.err.println("uri2.enc : "+uri1.getEncoded()+" - "+(equalEncoded?"OK":"ERROR")); + System.err.println("uri2.pathD : "+uri1.path.decode()+" - "+(equalPath?"OK":"ERROR")); + System.err.println("uri2.asciiE: "+uri1.toASCIIString()+" - "+(equalASCII?"OK":"ERROR")); + ok = equalEncoded && equalPath && equalASCII && ok; + } + { + final String s2 = "/"+string; + System.err.println("uri3.orig: "+s2); + final Uri uri1 = Uri.create(Uri.FILE_SCHEME, s2, null); + final String rString = "file:/Hallo%20Welt%20öä"; + final String rPath = s2; + final String rASCII = "file:/Hallo%20Welt%20%C3%B6%C3%A4"; + final boolean equalEncoded = rString.equals(uri1.toString()); + final boolean equalPath = rPath.equals(uri1.path.decode()); + final boolean equalASCII= rASCII.equals(uri1.toASCIIString().get()); + System.err.println("uri3.enc : "+uri1.toString()+" - "+(equalEncoded?"OK":"ERROR")); + System.err.println("uri3.pathD : "+uri1.path.decode()+" - "+(equalPath?"OK":"ERROR")); + System.err.println("uri3.asciiE: "+uri1.toASCIIString()+" - "+(equalASCII?"OK":"ERROR")); + ok = equalEncoded && equalPath && equalASCII && ok; + } + { + final String s2 = "//lala.org/"+string; + System.err.println("uri4.orig: "+s2); + final Uri uri1 = Uri.create(Uri.HTTP_SCHEME, s2, null); + final String rString = "http://lala.org/Hallo%20Welt%20öä"; + final String rPath = "/"+string; + final String rASCII = "http://lala.org/Hallo%20Welt%20%C3%B6%C3%A4"; + final boolean equalString= rString.equals(uri1.toString()); + final boolean equalPath = rPath.equals(uri1.path.decode()); + final boolean equalASCII= rASCII.equals(uri1.toASCIIString().get()); + System.err.println("uri4.enc : "+uri1.toString()+" - "+(equalString?"OK":"ERROR")); + System.err.println("uri4.pathD : "+uri1.path.decode()+" - "+(equalPath?"OK":"ERROR")); + System.err.println("uri4.asciiE: "+uri1.toASCIIString()+" - "+(equalASCII?"OK":"ERROR")); + ok = equalString && equalPath && equalASCII && ok; + } + Assert.assertTrue("One or more errors occured see stderr above", ok); + } + + @Test + public void test02URIEscapeSpecialChars() throws IOException, URISyntaxException { + { + final String vanilla = "XXX ! # $ & ' ( ) * + , / : ; = ? @ [ ]"; + final Uri.Encoded escaped = Uri.Encoded.cast("XXX%20!%20%23%20%24%20%26%20%27%20%28%20%29%20%2A%20%2B%20%2C%20/%20%3A%20%3B%20%3D%20%3F%20%40%20%5B%20%5D"); + System.err.println("vanilla "+vanilla); + final Uri.Encoded esc1 = new Uri.Encoded(vanilla, Uri.PATH_LEGAL); + System.err.println("esc1 "+esc1); + Assert.assertEquals(escaped, esc1); + + final String invEsc1 = esc1.decode(); + System.err.println("inv(esc1) "+invEsc1); + Assert.assertEquals(vanilla, invEsc1); + } + { + final String vanilla = "/XXX R!# R$&'()*+,/:;=?z@y[x]"; + final Uri.Encoded escaped = Uri.Encoded.cast("/XXX%20R!%23%20R%24%26%27%28%29%2A%2B%2C/%3A%3B%3D%3Fz%40y%5Bx%5D"); + System.err.println("vanilla "+vanilla); + final Uri.Encoded esc1 = new Uri.Encoded(vanilla, Uri.PATH_LEGAL); + System.err.println("esc1 "+esc1); + Assert.assertEquals(escaped, esc1); + + final String invEsc1 = esc1.decode(); + System.err.println("inv(esc1) "+invEsc1); + Assert.assertEquals(vanilla, invEsc1); + } + { + // Bug 908: $ ^ ~ # [ ] + final String vanilla = "/XXX $ ^ ~ # [ ]"; + showDump0x(vanilla); + } + { + // Windows invalid File characters: * ? " < > | + final String vanilla = "/XXX ! & ' ( ) + , / ; = @ [ ]"; + showDump0x(vanilla); + } + } + @Test + public void test03URIEscapeCommonChars() throws IOException, URISyntaxException { + { + final String vanilla = "/XXX \"%-.<>\\^_`{|}~"; + final Uri.Encoded escaped = Uri.Encoded.cast("/XXX%20%22%25-.%3C%3E%5C%5E_%60%7B%7C%7D~"); + System.err.println("vanilla "+vanilla); + final Uri.Encoded esc1 = new Uri.Encoded(vanilla, Uri.PATH_LEGAL); + System.err.println("esc1 "+esc1); + Assert.assertEquals(escaped, esc1); + + final String invEsc1 = esc1.decode(); + System.err.println("inv(esc1) "+invEsc1); + Assert.assertEquals(vanilla, invEsc1); + showDump0x(vanilla); + } + } + private static void showDump0x(final String string) throws IOException, URISyntaxException { + final File file = new File(string); + System.err.println("file "+file); + System.err.println("file.path.dec "+file.getPath()); + System.err.println("file.path.abs "+file.getAbsolutePath()); + System.err.println("file.path.can "+file.getCanonicalPath()); + + System.err.println("File-path -> Uri:"); + final Uri uri0 = Uri.valueOfFilepath(string); + URIDumpUtil.showUri(uri0); + + System.err.println("Uri -> File:"); + final Uri uri2 = Uri.valueOf(file); + URIDumpUtil.showUri(uri2); + + System.err.println("Uri -> URI:"); + final URI uri3 = uri2.toURI(); + URIDumpUtil.showURI(uri3); + + System.err.println("URI -> Uri (keep encoding):"); + final Uri uri4 = Uri.valueOf(uri3); + URIDumpUtil.showUri(uri4); + + System.err.println("URI -> Uri (re-encode):"); + final Uri uri5 = Uri.valueOf(uri3); + URIDumpUtil.showUri(uri5); + } + + @Test + public void test04EqualsAndHashCode() throws IOException, URISyntaxException { + { + final Uri uri0 = Uri.cast("http://localhost/test01.html#tag01"); + final Uri uri1 = Uri.create("http", null, "localhost", -1, "/test01.html", null, "tag01"); + final Uri uri2 = Uri.create("http", "localhost", "/test01.html", "tag01"); + + Assert.assertEquals(uri0, uri1); + Assert.assertEquals(uri0.hashCode(), uri1.hashCode()); + + Assert.assertEquals(uri0, uri2); + Assert.assertEquals(uri0.hashCode(), uri2.hashCode()); + + Assert.assertEquals(uri1, uri2); + Assert.assertEquals(uri1.hashCode(), uri2.hashCode()); + + final Uri uriA = Uri.create("http", null, "localhost", -1, "/test02.html", null, "tag01"); + final Uri uriB = Uri.create("http", null, "localhost", -1, "/test01.html", null, "tag02"); + final Uri uriC = Uri.create("http", null, "lalalhost", -1, "/test01.html", null, "tag01"); + final Uri uriD = Uri.create("sftp", null, "localhost", -1, "/test01.html", null, "tag01"); + + Assert.assertNotEquals(uri1, uriA); + Assert.assertNotEquals(uri1, uriB); + Assert.assertNotEquals(uri1, uriC); + Assert.assertNotEquals(uri1, uriD); + } + { // 3 [scheme:][//[user-info@]host[:port]]path[?query][#fragment] + final Uri uri0 = Uri.cast("http://user@localhost:80/test01.html?test=01&test=02#tag01"); + final Uri uri1 = Uri.create("http", "user", "localhost", 80, "/test01.html", "test=01&test=02", "tag01"); + + Assert.assertEquals(uri0, uri1); + Assert.assertEquals(uri0.hashCode(), uri1.hashCode()); + + final Uri uriA = Uri.cast("http://user@localhost:80/test01.html?test=01&test=02#tag02"); + final Uri uriB = Uri.cast("http://user@localhost:80/test01.html?test=01&test=03#tag01"); + final Uri uriC = Uri.cast("http://user@localhost:80/test04.html?test=01&test=02#tag01"); + final Uri uriD = Uri.cast("http://user@localhost:88/test01.html?test=01&test=02#tag01"); + final Uri uriE = Uri.cast("http://user@lalalhost:80/test01.html?test=01&test=02#tag01"); + final Uri uriF = Uri.cast("http://test@localhost:80/test01.html?test=01&test=02#tag01"); + final Uri uriG = Uri.cast("sftp://user@localhost:80/test01.html?test=01&test=02#tag01"); + + Assert.assertNotEquals(uri1, uriA); + Assert.assertNotEquals(uri1, uriB); + Assert.assertNotEquals(uri1, uriC); + Assert.assertNotEquals(uri1, uriD); + Assert.assertNotEquals(uri1, uriE); + Assert.assertNotEquals(uri1, uriF); + Assert.assertNotEquals(uri1, uriG); + } + } + + @Test + public void test05Contained() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("http://localhost/test01.html#tag01"); + final Uri contained = input.getContainedUri(); + Assert.assertNull(contained); + } + { + final Uri input = Uri.cast("jar:http://localhost/test01.jar!/com/jogamp/Lala.class#tag01"); + final Uri expected = Uri.cast("http://localhost/test01.jar#tag01"); + final Uri contained = input.getContainedUri(); + URIDumpUtil.showUri(input); + URIDumpUtil.showUri(contained); + Assert.assertEquals(expected, contained); + Assert.assertEquals(expected.hashCode(), contained.hashCode()); + } + { + final Uri input = Uri.cast("jar:file://localhost/test01.jar!/"); + final Uri expected = Uri.cast("file://localhost/test01.jar"); + final Uri contained = input.getContainedUri(); + URIDumpUtil.showUri(input); + URIDumpUtil.showUri(contained); + Assert.assertEquals(expected, contained); + Assert.assertEquals(expected.hashCode(), contained.hashCode()); + } + { + final Uri input = Uri.cast("sftp:http://localhost/test01.jar?lala=01#tag01"); + final Uri expected = Uri.cast("http://localhost/test01.jar?lala=01#tag01"); + final Uri contained = input.getContainedUri(); + URIDumpUtil.showUri(input); + URIDumpUtil.showUri(contained); + Assert.assertEquals(expected, contained); + Assert.assertEquals(expected.hashCode(), contained.hashCode()); + } + } + + @Test + public void test08NormalizedHierarchy() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("./dummy/nop/../a.txt"); + final Uri expected = Uri.cast("dummy/a.txt"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("../dummy/nop/../a.txt"); + final Uri expected = Uri.cast("../dummy/a.txt"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/dummy/../"); + final Uri expected = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/dummy/./../"); + final Uri expected = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/dummy/../aa/././../"); + final Uri expected = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/test/dummy/./../text.txt"); + final Uri expected = Uri.cast("http://localhost/test/text.txt"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("http://localhost/test/dummy/../text.txt?lala=01&lili=02#frag01"); + final Uri expected = Uri.cast("http://localhost/test/text.txt?lala=01&lili=02#frag01"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + } + + @Test + public void test09NormalizedOpaque() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("jar:http://localhost/dummy/../abc.jar!/"); + final Uri expected = Uri.cast("jar:http://localhost/abc.jar!/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("jar:http://localhost/test/./dummy/../abc.jar!/"); + final Uri expected = Uri.cast("jar:http://localhost/test/abc.jar!/"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("jar:http://localhost/test/dummy/../abc.jar!/a/b/C.class"); + final Uri expected = Uri.cast("jar:http://localhost/test/abc.jar!/a/b/C.class"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + { + final Uri input = Uri.cast("jar:http://localhost/test/dummy/../abc.jar!/a/b/C.class?lala=01&lili=02#frag01"); + final Uri expected = Uri.cast("jar:http://localhost/test/abc.jar!/a/b/C.class?lala=01&lili=02#frag01"); + URIDumpUtil.showUri(input); + final Uri normal = input.getNormalized(); + Assert.assertEquals(expected, normal); + } + } + + @Test + public void test10ParentAndDirHierarchy() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(input, directory); + final Uri parent = input.getParent(); + Assert.assertNull(parent); + } + { + final Uri input = Uri.cast("http://localhost/dummy/../test/"); + final Uri expectedD = Uri.cast("http://localhost/test/"); + final Uri expectedP = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("http://localhost/dummy/../test/dummy/../"); + final Uri expectedD = Uri.cast("http://localhost/test/"); + final Uri expectedP = Uri.cast("http://localhost/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("http://localhost/dir/test01.jar?lala=01#frag01"); + final Uri expParen1 = Uri.cast("http://localhost/dir/?lala=01#frag01"); + final Uri expFolde1 = expParen1; + final Uri expParen2 = Uri.cast("http://localhost/?lala=01#frag01"); + final Uri expFolde2 = expParen1; // is folder already + final Uri expParen3 = null; + final Uri expFolde3 = expParen2; + Assert.assertNotEquals(input, expParen1); + Assert.assertNotEquals(expParen1, expParen2); + Assert.assertNotEquals(expParen1, expParen3); + URIDumpUtil.showUri(input); + + final Uri parent1 = input.getParent(); + Assert.assertEquals(expParen1, parent1); + Assert.assertEquals(expParen1.hashCode(), parent1.hashCode()); + final Uri folder1 = input.getDirectory(); + Assert.assertEquals(expFolde1, folder1); + + final Uri parent2 = parent1.getParent(); + Assert.assertEquals(expParen2, parent2); + Assert.assertEquals(expParen2.hashCode(), parent2.hashCode()); + final Uri folder2 = parent1.getDirectory(); + Assert.assertEquals(expFolde2, folder2); + + final Uri parent3 = parent2.getParent(); + Assert.assertEquals(expParen3, parent3); // NULL! + final Uri folder3 = parent2.getDirectory(); + Assert.assertEquals(expFolde3, folder3); // NULL! + } + } + + @Test + public void test11ParentAndDirOpaque() throws IOException, URISyntaxException { + { + final Uri input = Uri.cast("jar:http://localhost/test.jar!/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(input, directory); + final Uri parent = input.getParent(); + Assert.assertNull(parent); + } + { + final Uri input = Uri.cast("jar:http://localhost/dummy/../test/test.jar!/"); + final Uri expectedD = Uri.cast("jar:http://localhost/test/test.jar!/"); + final Uri expectedP = null; + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("jar:http://localhost/dummy/../test/dummy/../test.jar!/a/b/C.class"); + final Uri expectedD = Uri.cast("jar:http://localhost/test/test.jar!/a/b/"); + final Uri expectedP = Uri.cast("jar:http://localhost/test/test.jar!/a/b/"); + URIDumpUtil.showUri(input); + final Uri directory = input.getDirectory(); + Assert.assertEquals(expectedD, directory); + final Uri parent = input.getParent(); + Assert.assertEquals(expectedP, parent); + } + { + final Uri input = Uri.cast("jar:http://localhost/test01.jar!/com/Lala.class?lala=01#frag01"); + final Uri expParen1 = Uri.cast("jar:http://localhost/test01.jar!/com/?lala=01#frag01"); + final Uri expFolde1 = expParen1; + final Uri expParen2 = Uri.cast("jar:http://localhost/test01.jar!/?lala=01#frag01"); + final Uri expFolde2 = expParen1; // is folder already + final Uri expParen3 = null; + final Uri expFolde3 = expParen2; // is folder already + Assert.assertNotEquals(input, expParen1); + Assert.assertNotEquals(expParen1, expParen2); + Assert.assertNotEquals(expParen1, expParen3); + URIDumpUtil.showUri(input); + + final Uri parent1 = input.getParent(); + Assert.assertEquals(expParen1, parent1); + Assert.assertEquals(expParen1.hashCode(), parent1.hashCode()); + final Uri folder1 = input.getDirectory(); + Assert.assertEquals(expFolde1, folder1); + + final Uri parent2 = parent1.getParent(); + Assert.assertEquals(expParen2, parent2); + Assert.assertEquals(expParen2.hashCode(), parent2.hashCode()); + final Uri folder2 = parent1.getDirectory(); + Assert.assertEquals(expFolde2, folder2); + + final Uri parent3 = parent2.getParent(); + Assert.assertEquals(expParen3, parent3); // NULL + final Uri folder3 = parent2.getDirectory(); + Assert.assertEquals(expFolde3, folder3); + } + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestUri01.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/TestUri02Composing.java b/test/java/org/jau/net/TestUri02Composing.java new file mode 100644 index 0000000..a80d130 --- /dev/null +++ b/test/java/org/jau/net/TestUri02Composing.java @@ -0,0 +1,94 @@ +package org.jau.net; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.jau.junit.util.SingletonJunitCase; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUri02Composing extends SingletonJunitCase { + + @BeforeClass + public static void assetRegistration() throws Exception { + try { + System.err.println("******* Asset URL Stream Handler Registration: PRE"); + Assert.assertTrue("GenericURLStreamHandlerFactory.register() failed", AssetURLContext.registerHandler(TestAssetURLConnectionRegistered.class.getClassLoader())); + Assert.assertNotNull(AssetURLContext.getRegisteredHandler()); + System.err.println("******* Asset URL Stream Handler Registration: POST"); + } catch (final Exception e) { + setTestSupported(false); + throw e; + } + } + + @Test + public void test01URLCompositioning() throws IOException, URISyntaxException { + testURNCompositioning("file:///rootDir/file1.txt"); + testURNCompositioning("file://host/rootDir/file1.txt"); + testURNCompositioning("jar:file:/web1/file1.jar!/rootDir/file1.txt"); + testURNCompositioning("asset:gluegen-test/info.txt"); + testURNCompositioning("asset:/gluegen-test/info.txt"); + testURNCompositioning("http://domain.com/web1/index.html?lala=23&lili=24#anchor"); + testURNCompositioning("http://domain.com:1234/web1/index.html?lala=23&lili=24#anchor"); + + final Uri file1URI = Uri.cast("asset:jar:file:/web1/file1.jar!/rootDir/file1.txt"); + testURICompositioning(file1URI); + testUriCompositioning(file1URI, Uri.cast("asset:jar:file:/web1/file1.jar!/rootDir/./file1.txt")); + testUriCompositioning(file1URI, Uri.cast("asset:jar:file:/web1/file1.jar!/rootDir/dummyParent/../file1.txt")); + + final URL file1URL = new URL("asset:jar:file:/web1/file1.jar!/rootDir/file1.txt"); + testURLCompositioning(file1URL); + testURLCompositioning(file1URL, new URL("asset:jar:file:/web1/file1.jar!/rootDir/./file1.txt")); + testURLCompositioning(file1URL, new URL("asset:jar:file:/web1/file1.jar!/rootDir/dummyParent/../file1.txt")); + } + + static void testURNCompositioning(final String urn) throws MalformedURLException, URISyntaxException { + testURICompositioning( Uri.cast(urn) ); + testURLCompositioning( new URL(urn) ); + } + + static void testURICompositioning(final Uri uri) throws MalformedURLException, URISyntaxException { + testUriCompositioning(uri, uri); + } + static void testUriCompositioning(final Uri refURI, final Uri uri1) throws MalformedURLException, URISyntaxException { + System.err.println("scheme <"+uri1.scheme+">, ssp <"+uri1.schemeSpecificPart+">, fragment <"+uri1.fragment+">"); + final Uri uri2 = uri1.getRelativeOf(null); + + System.err.println("URL-equals: "+refURI.equals(uri2)); + System.err.println("URL-ref : <"+refURI+">"); + System.err.println("URL-orig : <"+uri1+">"); + System.err.println("URL-comp : <"+uri2+">"); + Assert.assertEquals(refURI, uri2); + } + + static void testURLCompositioning(final URL url) throws MalformedURLException, URISyntaxException { + testURLCompositioning(url, url); + } + static void testURLCompositioning(final URL refURL, final URL url1) throws MalformedURLException, URISyntaxException { + final Uri uri1 = Uri.valueOf(url1); + System.err.println("scheme <"+uri1.scheme+">, ssp <"+uri1.schemeSpecificPart+">, fragment <"+uri1.fragment+">"); + final Uri uri2 = uri1.getRelativeOf(null); + + System.err.println("URL-equals(1): "+refURL.toURI().equals(uri2)); + System.err.println("URL-equals(2): "+refURL.equals(uri2.toURL())); + System.err.println("URL-same : "+refURL.sameFile(uri2.toURL())); + System.err.println("URL-ref : <"+refURL+">"); + System.err.println("URL-orig : <"+url1+">"); + System.err.println("URL-comp : <"+uri2+">"); + Assert.assertEquals(Uri.valueOf(refURL), uri2); + Assert.assertEquals(refURL, uri2.toURL()); + Assert.assertTrue(refURL.sameFile(uri2.toURL())); + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestUri02Composing.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/TestUri03Resolving.java b/test/java/org/jau/net/TestUri03Resolving.java new file mode 100644 index 0000000..f4ee77d --- /dev/null +++ b/test/java/org/jau/net/TestUri03Resolving.java @@ -0,0 +1,426 @@ +package org.jau.net; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; + +import org.jau.junit.util.SingletonJunitCase; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUri03Resolving extends SingletonJunitCase { + + // Bug 908, issues w/ windows file path char: $ ^ ~ # [ ] + + private static final String[][] uriHttpSArray = new String[][] { + new String[] {"http://localhost/gluegen/build-x86_64/gluegen-rt.jar"}, + + new String[] {"http://localhost/gluegen/"+'\u0394'+"/gluegen-rt.jar"}, + + new String[] {"http://localhost/gluegen/build-x86_64%20lala/gluegen-rt.jar"}, + + new String[] {"http://localhost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar"}, + + new String[] {"jar:http://localhost/gluegen/build-x86_64/gluegen-rt.jar!/"}, + + new String[] {"jar:http://localhost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/"}, + + new String[] {"jar:http://localhost/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:http://localhost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:http://localhost/gluegen/R%23/gluegen-rt.jar!/"}, + + new String[] {"jar:http://localhost/gluegen/A%24/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:http://localhost/gluegen/%24/%5E/~/%23/%5B/%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:http://localhost/gluegen/"+'\u0394'+"/gluegen-rt.jar!/"}, + }; + + private static final String[][] uriFileSArrayUnix = new String[][] { + new String[] {"file:/gluegen/build-x86_64/gluegen-rt.jar"}, + + new String[] {"file:/gluegen/"+'\u0394'+"/gluegen-rt.jar"}, + + new String[] {"file:/gluegen/build-x86_64%20lala/gluegen-rt.jar"}, + + new String[] {"file:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar"}, + + new String[] {"jar:file:/gluegen/build-x86_64/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file://filehost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:/gluegen/R%23/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/gluegen/A%24/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/gluegen/%24/%5E/~/%23/%5B/%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/gluegen/"+'\u0394'+"/gluegen-rt.jar!/"}, + }; + + private static final String[][] uriFileSArrayWindows = new String[][] { + new String[] {"file:/C%3A/gluegen/build-x86_64/gluegen-rt.jar"}, + + new String[] {"file:/C%3A/gluegen/"+'\u0394'+"/gluegen-rt.jar"}, + + new String[] {"file:/C%3A/gluegen/build-x86_64%20lala/gluegen-rt.jar"}, + + new String[] {"file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar"}, + + new String[] {"jar:file:/C%3A/gluegen/build-x86_64/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C%3A/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:///C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file://filehost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:/C%3A/gluegen/R%23/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C%3A/gluegen/A%24/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C%3A/gluegen/%24/%5E/~/%23/%5B/%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C%3A/gluegen/"+'\u0394'+"/gluegen-rt.jar!/"}, + }; + + private static final String[][] urlFileSArrayWindows = new String[][] { + new String[] {"file:/C:/gluegen/build-x86_64/gluegen-rt.jar"}, + + new String[] {"file:/C:/gluegen/"+'\u0394'+"/gluegen-rt.jar"}, + + new String[] {"file:/C:/gluegen/build-x86_64%20lala/gluegen-rt.jar"}, + + new String[] {"file:/C:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar"}, + + new String[] {"jar:file:/C:/gluegen/build-x86_64/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C:/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:/C:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:///C:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file://filehost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar!/com/jogamp/common/os/Platform.class"}, + + new String[] {"jar:file:/C:/gluegen/R%23/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C:/gluegen/A%24/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C:/gluegen/%24/%5E/~/%23/%5B/%5D/gluegen-rt.jar!/"}, + + new String[] {"jar:file:/C:/gluegen/"+'\u0394'+"/gluegen-rt.jar!/"}, + }; + + public static final String[][] fileSArrayUnix = new String[][] { + new String[] {"/gluegen/build-x86_64/gluegen-rt.jar", + "file:/gluegen/build-x86_64/gluegen-rt.jar", + "/gluegen/build-x86_64/gluegen-rt.jar"}, + + new String[] {"/gluegen/"+'\u0394'+"/gluegen-rt.jar", + "file:/gluegen/"+'\u0394'+"/gluegen-rt.jar", + "/gluegen/"+'\u0394'+"/gluegen-rt.jar"}, + + new String[] {"/gluegen/build-x86_64 lala/gluegen-rt.jar", + "file:/gluegen/build-x86_64%20lala/gluegen-rt.jar", + "/gluegen/build-x86_64 lala/gluegen-rt.jar"}, + + new String[] {"/gluegen/build-x86_64 öä lala/gluegen-rt.jar", + "file:/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", + "/gluegen/build-x86_64 öä lala/gluegen-rt.jar"}, + + new String[] {"/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar", + "file:/gluegen/A%24/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar", + "/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar" }, + + new String[] {"/gluegen/$/^/~/#/[/]/gluegen-rt.jar", + "file:/gluegen/%24/%5E/~/%23/%5B/%5D/gluegen-rt.jar", + "/gluegen/$/^/~/#/[/]/gluegen-rt.jar" }, + }; + + public static final String[][] fileSArrayWindows = new String[][] { + new String[] {"C:/gluegen/build-x86_64/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64/gluegen-rt.jar", + "C:\\gluegen\\build-x86_64\\gluegen-rt.jar"}, + + new String[] {"C:/gluegen/"+'\u0394'+"/gluegen-rt.jar", + "file:/C%3A/gluegen/"+'\u0394'+"/gluegen-rt.jar", + "C:\\gluegen\\"+'\u0394'+"\\gluegen-rt.jar"}, + + new String[] {"C:/gluegen/build-x86_64 lala/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64%20lala/gluegen-rt.jar", + "C:\\gluegen\\build-x86_64 lala\\gluegen-rt.jar"}, + + new String[] {"C:/gluegen/build-x86_64 öä lala/gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", + "C:\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar"}, + + new String[] {"C:\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar", + "file:/C%3A/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", + "C:\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar"}, + + new String[] {"\\\\filehost\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar", + "file://filehost/gluegen/build-x86_64%20öä%20lala/gluegen-rt.jar", + "\\\\filehost\\gluegen\\build-x86_64 öä lala\\gluegen-rt.jar"}, + + new String[] {"C:/gluegen/A$/B^/C~/D#/E[/F]/gluegen-rt.jar", + "file:/C%3A/gluegen/A%24/B%5E/C~/D%23/E%5B/F%5D/gluegen-rt.jar", + "C:\\gluegen\\A$\\B^\\C~\\D#\\E[\\F]\\gluegen-rt.jar" }, + + new String[] {"C:/gluegen/$/^/~/#/[/]/gluegen-rt.jar", + "file:/C%3A/gluegen/%24/%5E/~/%23/%5B/%5D/gluegen-rt.jar", + "C:\\gluegen\\$\\^\\~\\#\\[\\]\\gluegen-rt.jar" }, + }; + + @Test + public void test01HttpUri2URL() throws IOException, URISyntaxException { + testUri2URL(getSimpleTestName("."), uriHttpSArray); + } + + @Test + public void test02FileUnixUri2URL() throws IOException, URISyntaxException { + testUri2URL(getSimpleTestName("."), uriFileSArrayUnix); + } + + @Test + public void test03FileWindowsUri2URL() throws IOException, URISyntaxException { + testUri2URL(getSimpleTestName("."), uriFileSArrayWindows); + } + + @Test + public void test11HttpURL2Uri() throws IOException, URISyntaxException { + testURL2Uri(getSimpleTestName("."), uriHttpSArray); + } + + @Test + public void test12FileUnixURL2Uri() throws IOException, URISyntaxException { + testURL2Uri(getSimpleTestName("."), uriFileSArrayUnix); + } + + @Test + public void test13FileWindowsURL2Uri() throws IOException, URISyntaxException { + testURL2Uri(getSimpleTestName("."), urlFileSArrayWindows); + } + + @Test + public void test24FileUnixURI2URL() throws IOException, URISyntaxException { + if( PlatformTypes.OSType.WINDOWS != PlatformProps.OS ) { + testFile2Uri(getSimpleTestName("."), fileSArrayUnix); + } + } + + @Test + public void test25FileWindowsURI2URL() throws IOException, URISyntaxException { + if( PlatformTypes.OSType.WINDOWS == PlatformProps.OS ) { + testFile2Uri(getSimpleTestName("."), fileSArrayWindows); + } + } + + static void testUri2URL(final String testname, final String[][] uriSArray) throws IOException, URISyntaxException { + boolean ok = true; + for(int i=0; i<uriSArray.length; i++) { + final String[] uriSPair = uriSArray[i]; + final String uriSource = uriSPair[0]; + System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+uriSArray.length); + ok = testUri2URL(Uri.Encoded.cast(uriSource)) && ok; + System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+uriSArray.length); + } + Assert.assertTrue("One or more errors occured see stderr above", ok); + } + + static boolean testUri2URL(final Uri.Encoded uriSource) throws IOException, URISyntaxException { + System.err.println("uriSource : "+uriSource); + final Uri uri0 = new Uri(uriSource); + URIDumpUtil.showUri(uri0); + + final URI actualURI = uri0.toURI(); + URIDumpUtil.showURI(actualURI); + final Uri.Encoded actualURIStr = Uri.Encoded.cast(actualURI.toString()); + + final URL actualURL = uri0.toURL(); + URIDumpUtil.showURL(actualURL); + final Uri.Encoded actualURLStr = Uri.Encoded.cast(actualURL.toExternalForm()); + + System.err.println("expected_URX: "+uriSource); + + final boolean equalsURI = uriSource.equals(actualURIStr); + System.err.println("actual URI: "+actualURIStr+" - "+(equalsURI?"OK":"ERROR")); + final boolean equalsURL = uriSource.equals(actualURLStr); + System.err.println("actual URL: "+actualURLStr+" - "+(equalsURL?"OK":"ERROR")); + URIDumpUtil.showReencodedURIOfUri(uri0); + URIDumpUtil.showReencodedUriOfURI(actualURI); + + boolean ok = equalsURL && equalsURI; + + // now test open .. + Throwable t = null; + URLConnection con = null; + try { + con = actualURL.openConnection(); + } catch (final Throwable _t) { + t = _t; + } + if( null != t ) { + System.err.println("XXX: "+t.getClass().getName()+": "+t.getMessage()); + t.printStackTrace(); + } else { + System.err.println("XXX: No openConnection() failure"); + System.err.println("XXX: "+con); + } + + if( uri0.scheme.equals(Uri.JAR_SCHEME) ) { + // Extended tests on JAR Uri + final Uri uriSub0 = uri0.getContainedUri(); + Assert.assertNotNull(uriSub0); + System.err.println("EXT JAR contained:"); + URIDumpUtil.showUri(uriSub0); + final Uri uriSubDir0 = uriSub0.getDirectory(); + final Uri uriSubParent0 = uriSub0.getParent(); + System.err.println("EXT JAR contained Dir:"); + URIDumpUtil.showUri(uriSubDir0); + System.err.println("EXT JAR contained Parent:"); + URIDumpUtil.showUri(uriSubParent0); + ok = uriSubDir0.equals(uriSubParent0) && ok; + } + return ok; + } + + static void testURL2Uri(final String testname, final String[][] urlSArray) throws IOException, URISyntaxException { + boolean ok = true; + for(int i=0; i<urlSArray.length; i++) { + final String[] uriSPair = urlSArray[i]; + final String uriSource = uriSPair[0]; + System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+urlSArray.length); + ok = testURL2Uri(new URL(uriSource)) && ok; + System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+urlSArray.length); + } + Assert.assertTrue("One or more errors occured see stderr above", ok); + } + + static boolean testURL2Uri(final URL urlSource) throws IOException, URISyntaxException { + System.err.println("URL Source : "+urlSource); + URIDumpUtil.showURL(urlSource); + + final URI uriSource = urlSource.toURI(); + URIDumpUtil.showURI(uriSource); + + final Uri uri0 = Uri.valueOf(urlSource); + URIDumpUtil.showUri(uri0); + + final URL uriToURL = uri0.toURL(); + URIDumpUtil.showURL(uriToURL); + + // now test open .. + Throwable t = null; + URLConnection con = null; + try { + con = uriToURL.openConnection(); + } catch (final Throwable _t) { + t = _t; + } + if( null != t ) { + System.err.println("XXX: "+t.getClass().getName()+": "+t.getMessage()); + t.printStackTrace(); + } else { + System.err.println("XXX: No openConnection() failure"); + System.err.println("XXX: "+con); + } + + boolean ok = true; + + if( uri0.scheme.equals(Uri.JAR_SCHEME) ) { + // Extended tests on JAR Uri + final Uri uriSub0 = uri0.getContainedUri(); + Assert.assertNotNull(uriSub0); + System.err.println("EXT JAR contained:"); + URIDumpUtil.showUri(uriSub0); + final Uri uriSubDir0 = uriSub0.getDirectory(); + final Uri uriSubParent0 = uriSub0.getParent(); + System.err.println("EXT JAR contained Dir:"); + URIDumpUtil.showUri(uriSubDir0); + System.err.println("EXT JAR contained Parent:"); + URIDumpUtil.showUri(uriSubParent0); + ok = uriSubDir0.equals(uriSubParent0) && ok; + } + return ok; + } + + static void testFile2Uri(final String testname, final String[][] uriSArray) throws IOException, URISyntaxException { + boolean ok = true; + for(int i=0; i<uriSArray.length; i++) { + final String[] uriSPair = uriSArray[i]; + final String uriSource = uriSPair[0]; + final String uriEncExpected= uriSPair[1]; + final String fileExpected= uriSPair[2]; + System.err.println("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS "+testname+": "+(i+1)+"/"+uriSArray.length); + ok = testFile2Uri(uriSource, Uri.Encoded.cast(uriEncExpected), fileExpected) && ok; + System.err.println("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE "+testname+": "+(i+1)+"/"+uriSArray.length); + } + Assert.assertTrue("One or more errors occured see stderr above", ok); + } + + static boolean testFile2Uri(final String fileSource, final Uri.Encoded uriEncExpected, final String fileExpected) throws IOException, URISyntaxException { + System.err.println("fileSource: "+fileSource); + final File file = new File(fileSource); + System.err.println("file: "+file.getAbsolutePath()); + + final Uri uri0 = Uri.valueOf(file); + URIDumpUtil.showReencodedURIOfUri(uri0); + + final URL actualUrl = uri0.toURL(); + final File actualFile = uri0.toFile(); + final boolean equalsFilePath = fileExpected.equals(actualFile.getPath()); + System.err.println("expected_path: "+fileExpected); + System.err.println("actual___file-path: "+actualFile+" - "+(equalsFilePath?"OK":"ERROR")); + final boolean equalsEncUri = uriEncExpected.equals(uri0.getEncoded()); + System.err.println("expected__encUri: "+uriEncExpected); + System.err.println("actual_______Uri: "+uri0.getEncoded()+" - "+(equalsEncUri?"OK":"ERROR")); + final boolean ok = equalsEncUri && equalsFilePath; + + System.err.println("actual_______URL: "+actualUrl.toExternalForm()); + + // now test open .. + Throwable t = null; + URLConnection con = null; + try { + con = actualUrl.openConnection(); + } catch (final Throwable _t) { + t = _t; + } + if( null != t ) { + System.err.println("XXX: "+t.getClass().getName()+": "+t.getMessage()); + t.printStackTrace(); + } else { + System.err.println("XXX: No openConnection() failure"); + System.err.println("XXX: "+con); + } + return ok; + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestUri03Resolving.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/TestUri99LaunchOnReservedCharPathBug908.java b/test/java/org/jau/net/TestUri99LaunchOnReservedCharPathBug908.java new file mode 100644 index 0000000..f55d447 --- /dev/null +++ b/test/java/org/jau/net/TestUri99LaunchOnReservedCharPathBug908.java @@ -0,0 +1,166 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.net; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; + +import org.jau.junit.util.MiscUtils; +import org.jau.junit.util.SingletonJunitCase; +import org.jau.lang.ReflectionUtil; +import org.jau.pkg.JarUtil; +import org.jau.sys.AndroidVersion; +import org.jau.sys.PlatformProps; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * Bug 908: Automated test, launching GlueGen jar file from an <i>odd pathname</i>. + * <p> + * The currently used jar folder is copied into [1] + * <pre> + * [1] build/test/build/TestNetIOURIReservedCharsBug908/test01TempJarCacheOddPath/A\$-B\^-C~-D#-E\]-F\[-öä + * [2] build/test/build/TestNetIOURIReservedCharsBug908/test01TempJarCacheOddPath/A\$-B\^-C~-D#-E\]-F\[-öä/gluegen-rt.jar + * </pre> + * A ClassLoader w/ the URL [2] is used to issue Platform.initSingleton(), + * i.e. issues a whole initialization sequence w/ native jar loading in the new ClassPath. + * </p> + * <p> + * The manual test below on the created odd folder [1] has also succeeded: + * <pre> + * java \ + * -Djogamp.debug.IOUtil -Djogamp.debug.JNILibLoader -Djogamp.debug.TempFileCache \ + * -Djogamp.debug.JarUtil -Djogamp.debug.TempJarCache \ + * -cp ../build-x86_64/test/build/TestNetIOURIReservedCharsBug908/test01TempJarCacheOddPath/A\$-B\^-C~-D#-E\]-F\[-öä/gluegen-rt.jar \ + * com.jogamp.common.GlueGenVersion + * </pre> + * </p> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUri99LaunchOnReservedCharPathBug908 extends SingletonJunitCase { + static class TestClassLoader extends URLClassLoader { + public TestClassLoader(final URL[] urls) { + super(urls); + } + public TestClassLoader(final URL[] urls, final ClassLoader parent) { + super(urls, parent); + } + } + + + @Test + public void test00TempJarCacheSimplePath() throws IOException, IllegalArgumentException, URISyntaxException { + testTempJarCacheOddJarPathImpl("simpletons/"); + } + + @Test + public void test01TempJarCacheOddPath() throws IOException, IllegalArgumentException, URISyntaxException { + // Bug 908, issues w/ windows file path char: $ ^ ~ # [ ] + testTempJarCacheOddJarPathImpl("A$-B^-C~-D#-E]-F[-öä/"); + // "A$-B%5E-C~-D#-E]-F[-%C3%B6%C3%A4/"); <- Firefox URI encoding! '#' -> [1] + // "A$-B%5E-C~-D%23-E]-F[-%C3%B6%C3%A4/"); <- '[' ']' -> [2] + // "A$-B%5E-C~-D%23-E%5D-F%5B-%C3%B6%C3%A4/"); + /** + * [1] '#' + java.lang.IllegalArgumentException: URI has a fragment component + at java.io.File.<init>(Unknown Source) + at com.jogamp.common.net.TestNetIOURIReservedCharsBug908.testTempJarCacheOddJarPathImpl(TestNetIOURIReservedCharsBug908.java:101) + [2] '[' ']' + java.net.URISyntaxException: Illegal character in path at index 137: file:/usr/local/projects/JOGL/gluegen/build-x86_64/test/build/TestNetIOURIReservedCharsBug908/test01TempJarCacheOddPath/A$-B%5E-C~-D%23-E]-F[-%C3%B6%C3%A4/ + at java.net.URI$Parser.fail(Unknown Source) + at java.net.URI$Parser.checkChars(Unknown Source) + at java.net.URI$Parser.parseHierarchical(Unknown Source) + at java.net.URI$Parser.parse(Unknown Source) + at java.net.URI.<init>(Unknown Source) + at com.jogamp.common.net.TestNetIOURIReservedCharsBug908.testTempJarCacheOddJarPathImpl(TestNetIOURIReservedCharsBug908.java:106) + + */ + + } + private void testTempJarCacheOddJarPathImpl(final String subPathUTF) throws IOException, IllegalArgumentException, URISyntaxException { + if(AndroidVersion.isAvailable) { System.err.println("n/a on Android"); return; } + + final Uri.Encoded subPathEncoded = new Uri.Encoded(subPathUTF, Uri.PATH_LEGAL); + final String reservedCharPathUnencoded = "test/build/"+getClass().getSimpleName()+"/"+getTestMethodName()+"/"+subPathUTF; + final Uri.Encoded reservedCharPathEncoded = Uri.Encoded.cast("test/build/"+getClass().getSimpleName()+"/"+getTestMethodName()+"/").concat(subPathEncoded); + + System.err.println("0 Unencoded: "+reservedCharPathUnencoded); + System.err.println("0 Encoded: "+reservedCharPathEncoded); + + // jar:file:/dir1/dir2/gluegen-rt.jar!/ + final Uri jarFileURI = JarUtil.getJarFileUri(PlatformProps.class.getName(), getClass().getClassLoader()); + System.err.println("1 jarFileURI: "+jarFileURI.toString()); + // gluegen-rt.jar + final Uri.Encoded jarBasename = JarUtil.getJarBasename(jarFileURI); + System.err.println("2 jarBasename: "+jarBasename); + + // file:/dir1/build/gluegen-rt.jar + final Uri fileURI = jarFileURI.getContainedUri(); + System.err.println("3 fileURI: "+fileURI.toString()); + // file:/dir1/build/ + final Uri fileFolderURI = fileURI.getParent(); + System.err.println("4 fileFolderURI: "+fileFolderURI.toString()); + // file:/dir1/build/test/build/A$-B^-C~-D#-E]-F[/ + final Uri fileNewFolderURI = fileFolderURI.concat(reservedCharPathEncoded); + System.err.println("5 fileNewFolderURI: "+fileNewFolderURI.toString()); + + final File srcFolder = fileFolderURI.toFile(); + final File dstFolder = fileNewFolderURI.toFile(); + System.err.println("6 srcFolder: "+srcFolder.toString()); + System.err.println("7 dstFolder: "+dstFolder.toString()); + try { + final MiscUtils.CopyStats copyStats = MiscUtils.copy(srcFolder, dstFolder, 1, true); + copyStats.dump("Copy ", true); + Assert.assertEquals(1, copyStats.totalFolders); + Assert.assertTrue(copyStats.totalBytes > 0); + Assert.assertEquals(0, copyStats.currentDepth); + + final URI jarFileNewFolderURI = new URI(fileNewFolderURI.toString()+jarBasename); + System.err.println("8 jarFileNewFolderURI: "+jarFileNewFolderURI.toString()); + + final URL[] urls = new URL[] { jarFileNewFolderURI.toURL() }; + System.err.println("url: "+urls[0]); + + final ClassLoader cl = new TestClassLoader(urls, null); + ReflectionUtil.callStaticMethod(PlatformProps.class.getName(), "initSingleton", null, null, cl); + } finally { + // cleanup ? Skip this for now .. + // dstFolder.delete(); + } + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestUri99LaunchOnReservedCharPathBug908.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/net/TestUriQueryProps.java b/test/java/org/jau/net/TestUriQueryProps.java new file mode 100644 index 0000000..5cca3ec --- /dev/null +++ b/test/java/org/jau/net/TestUriQueryProps.java @@ -0,0 +1,47 @@ +package org.jau.net; + +import static org.jau.net.URIDumpUtil.showUri; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.jau.junit.util.SingletonJunitCase; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUriQueryProps extends SingletonJunitCase { + + @Test + public void test() throws IOException, URISyntaxException { + final String SCHEME = "camera"; + final String HOST = "somewhere"; + final String PATH = "0"; + final String[] args = new String[] { + SCHEME+"://"+HOST+"/"+PATH, + SCHEME+"://"+HOST+"/"+PATH+"?p1=1", + }; + for(int i=0; i<args.length-1; i+=2) { + final String uri_s0 = args[i]; + final String uri_s1 = args[i+1]; + final Uri uri0 = Uri.cast(uri_s0); + final Uri uri1 = Uri.cast(uri_s1); + showUri(uri0); + showUri(uri1); + final UriQueryProps data = UriQueryProps.create(uri1, ';'); + if(null == data) { + System.err.println("Error: NULL: <"+uri_s1+"> -> "+uri1+" -> NULL"); + } else { + final Uri uri1T = data.appendQuery(uri0); + showUri(uri1T); + Assert.assertEquals(uri1, uri1T); + } + } + } + public static void main(final String args[]) throws IOException { + final String tstname = TestUriQueryProps.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/TestUrisWithAssetHandler.java b/test/java/org/jau/net/TestUrisWithAssetHandler.java new file mode 100644 index 0000000..f81dfe8 --- /dev/null +++ b/test/java/org/jau/net/TestUrisWithAssetHandler.java @@ -0,0 +1,49 @@ +package org.jau.net; + +import static org.jau.net.URIDumpUtil.showURX; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.jau.junit.util.SingletonJunitCase; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestUrisWithAssetHandler extends SingletonJunitCase { + + @BeforeClass + public static void assetRegistration() throws Exception { + try { + System.err.println("******* Asset URL Stream Handler Registration: PRE"); + Assert.assertTrue("GenericURLStreamHandlerFactory.register() failed", AssetURLContext.registerHandler(TestUrisWithAssetHandler.class.getClassLoader())); + Assert.assertNotNull(AssetURLContext.getRegisteredHandler()); + System.err.println("******* Asset URL Stream Handler Registration: POST"); + } catch (final Exception e) { + setTestSupported(false); + throw e; + } + } + + @Test + public void showURLComponents0() throws IOException, URISyntaxException { + showURX("file:///rootDir/file1.txt"); + showURX("file://host/rootDir/file1.txt"); + showURX("jar:file:/web1/file1.jar!/rootDir/file1.txt"); + showURX("asset:gluegen-test/info.txt"); + showURX("asset:/gluegen-test/info.txt"); + showURX("http://domain.com/web1/index.html?lala=23&lili=24#anchor"); + showURX("http://domain.com:1234/web1/index.html?lala=23&lili=24#anchor"); + showURX("asset:jar:file:/web1/file1.jar!/rootDir/file1.txt"); + showURX("asset:jar:file:/web1/file1.jar!/rootDir/./file1.txt"); + showURX("asset:jar:file:/web1/file1.jar!/rootDir/dummyParent/../file1.txt"); + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestUrisWithAssetHandler.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } +} diff --git a/test/java/org/jau/net/URIDumpUtil.java b/test/java/org/jau/net/URIDumpUtil.java new file mode 100644 index 0000000..46e16e1 --- /dev/null +++ b/test/java/org/jau/net/URIDumpUtil.java @@ -0,0 +1,98 @@ +package org.jau.net; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +public class URIDumpUtil { + public static void showURX(final String urx) throws MalformedURLException, URISyntaxException { + System.err.println("WWWWWW "+urx); + showURL(new URL(urx)); + showURI(new URI(urx)); + System.err.println("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"); + } + + public static void showURL(final URL url) { + System.err.println("XXXXXX URL "+url.toString()); + System.err.println("protocol: "+url.getProtocol()); + System.err.println("auth: "+url.getAuthority()); + System.err.println("host: "+url.getHost()); + System.err.println("port: "+url.getPort() + " ( " + url.getDefaultPort() + " ) " ); + System.err.println("file: "+url.getFile() + " ( path " + url.getPath() + ", query " + url.getQuery() + " ) " ); + System.err.println("ref: "+url.getRef()); + } + + public static void showURI(final URI uri) { + showURI("YYYYYY URI "+uri+", isOpaque "+uri.isOpaque()+", isAbs "+uri.isAbsolute(), uri); + } + public static void showURI(final String message, final URI uri) { + System.err.println(message); + + System.err.println("0.0.0 string: "+uri.toString()); + System.err.println("0.0.0 ascii : "+uri.toASCIIString()); + + System.err.println("1.0.0 scheme: "+uri.getScheme()); + System.err.println("2.0.0 scheme-part: "+uri.getRawSchemeSpecificPart()+" (raw), "+uri.getSchemeSpecificPart()+" (dec)"); + System.err.println("2.1.0 auth: "+uri.getRawAuthority()+" (raw), "+uri.getAuthority()+" (dec)"); + System.err.println("2.1.1 user-info: "+uri.getRawUserInfo()+" (raw), "+uri.getUserInfo()+" (dec)"); + System.err.println("2.1.1 host: "+uri.getHost()); + System.err.println("2.1.1 port: "+uri.getPort()); + System.err.println("2.2.0 path: "+uri.getRawPath()+" (raw), "+uri.getPath()+" (dec)"); + System.err.println("2.3.0 query: "+uri.getRawQuery()+" (raw), "+uri.getQuery()+" (dec)"); + System.err.println("3.0.0 fragment: "+uri.getRawFragment()+" (raw), "+uri.getFragment()+" (dec)"); + } + + public static void showUri(final Uri uri) throws URISyntaxException { + showUri("ZZZZZZ Uri "+uri+", isOpaque "+uri.opaque+", isAbs "+uri.absolute+", hasAuth "+uri.hasAuthority, uri); + } + + public static void showUri(final String message, final Uri uri) throws URISyntaxException { + System.err.println(message); + + System.err.println("0.0.0 string: "+uri.toString()); + System.err.println("0.0.0 ascii : "+uri.toASCIIString()); + System.err.println("0.0.0 native-file: "+uri.toFile()); + System.err.println("0.0.0 contained: "+uri.getContainedUri()); + + System.err.println("1.0.0 scheme: "+uri.scheme); + System.err.println("2.0.0 scheme-part: "+uri.schemeSpecificPart+" (raw), "+Uri.decode(uri.schemeSpecificPart)+" (dec)"); + System.err.println("2.1.0 auth: "+uri.authority+" (raw), "+Uri.decode(uri.authority)+" (dec)"); + System.err.println("2.1.1 user-info: "+uri.userInfo+" (raw), "+Uri.decode(uri.userInfo)+" (dec)"); + System.err.println("2.1.1 host: "+uri.host); + System.err.println("2.1.1 port: "+uri.port); + System.err.println("2.2.0 path: "+uri.path+" (raw), "+Uri.decode(uri.path)+" (dec)"); + System.err.println("2.3.0 query: "+uri.query+" (raw), "+Uri.decode(uri.query)+" (dec)"); + System.err.println("3.0.0 fragment: "+uri.fragment+" (raw), "+Uri.decode(uri.fragment)+" (dec)"); + } + + /** + * Just showing different encoding of Uri -> URI + * + * @param uri + * @throws URISyntaxException + */ + public static void showReencodedURIOfUri(final Uri uri) throws URISyntaxException { + final URI recomposedURI = uri.toURIReencoded(); + showURI("YYYYYY Recomposed URI "+recomposedURI+", isOpaque "+recomposedURI.isOpaque()+", isAbs "+recomposedURI.isAbsolute(), recomposedURI); + final String recomposedURIStr = recomposedURI.toString(); + final boolean equalsRecompURI = uri.input.equals(recomposedURIStr); + System.err.println("source Uri: "+uri.input); + System.err.println("recomp URI: "+recomposedURIStr+" - "+(equalsRecompURI?"EQUAL":"UNEQUAL")); + } + + /** + * Just showing different encoding of URI -> Uri + * + * @param uri + * @throws URISyntaxException + */ + public static void showReencodedUriOfURI(final URI uri) throws URISyntaxException { + final Uri recomposedUri = Uri.valueOf(uri); + showUri("ZZZZZZ Recomposed Uri "+recomposedUri+", isOpaque "+recomposedUri.opaque+", isAbs "+recomposedUri.absolute+", hasAuth "+recomposedUri.hasAuthority, recomposedUri); + final String recomposedUriStr = recomposedUri.toString(); + final boolean equalsRecompUri = uri.toString().equals(recomposedUriStr); + System.err.println("source URI: "+uri.toString()); + System.err.println("recomp Uri: "+recomposedUriStr+" - "+(equalsRecompUri?"EQUAL":"UNEQUAL")); + } +} diff --git a/test/java/org/jau/net/data/AssetURLConnectionTest.txt b/test/java/org/jau/net/data/AssetURLConnectionTest.txt new file mode 100644 index 0000000..97d70a7 --- /dev/null +++ b/test/java/org/jau/net/data/AssetURLConnectionTest.txt @@ -0,0 +1,3 @@ +AssetURLConnectionTest Data Testing Relative Location + +This file exists for test purposes. diff --git a/test/java/org/jau/net/data/RelativeData.txt b/test/java/org/jau/net/data/RelativeData.txt new file mode 100644 index 0000000..02be06f --- /dev/null +++ b/test/java/org/jau/net/data/RelativeData.txt @@ -0,0 +1,3 @@ +Relative Data 1 - com/jogamp/common/net/data/RelativeData.txt + +This file exists for test purposes. diff --git a/test/java/org/jau/net/data2/RelativeData2.txt b/test/java/org/jau/net/data2/RelativeData2.txt new file mode 100644 index 0000000..adf3d6e --- /dev/null +++ b/test/java/org/jau/net/data2/RelativeData2.txt @@ -0,0 +1,3 @@ +Relative Data 2 - com/jogamp/common/net/data2/RelativeData2.txt + +This file exists for test purposes. diff --git a/test/java/org/jau/sys/elf/TestElfReader01.java b/test/java/org/jau/sys/elf/TestElfReader01.java new file mode 100644 index 0000000..b1df190 --- /dev/null +++ b/test/java/org/jau/sys/elf/TestElfReader01.java @@ -0,0 +1,185 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2013 Gothel Software e.K. + * Copyright (c) 2013 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.sys.elf; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.List; + +import org.jau.junit.util.SingletonJunitCase; +import org.jau.sys.JNILibrary; +import org.jau.sys.PlatformProps; +import org.jau.sys.PlatformTypes.OSType; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestElfReader01 extends SingletonJunitCase { + public static String GNU_LINUX_SELF_EXE = "/proc/self/exe"; + public static String ARM_HF_EXE = "tst-exe-armhf"; + public static String ARM_SF_EXE = "tst-exe-arm"; + static File userFile = null; + + private static boolean checkFileReadAccess(final File file) { + try { + return file.isFile() && file.canRead(); + } catch (final Throwable t) { } + return false; + } + static File findJVMLib(final String libName) { + final ClassLoader cl = TestElfReader01.class.getClassLoader(); + final List<String> possibleLibPaths = JNILibrary.enumerateLibraryPaths(libName, + true /* searchSystemPath */, true /* searchSystemPathFirst */, cl); + for(int i=0; i<possibleLibPaths.size(); i++) { + final String libPath = possibleLibPaths.get(i); + final File lib = new File(libPath); + System.err.println("XXX2 #"+i+": test "+lib); + if( checkFileReadAccess(lib) ) { + return lib; + } + System.err.println("XXX2 #"+i+": "+lib+" not readable"); + } + return null; + } + + @Test + public void test01GNULinuxSelfExe () throws IOException { + if( null == userFile ) { + if( OSType.LINUX == PlatformProps.OS ) { + final File f = new File(GNU_LINUX_SELF_EXE); + if( checkFileReadAccess(f) ) { + testElfHeaderImpl(f, false); + } + } + } + } + + @Test + public void test02JavaLib () throws IOException { + if( null == userFile ) { + File jvmLib = findJVMLib("java"); + if( null == jvmLib ) { + jvmLib = findJVMLib("jvm"); + } + if( null != jvmLib ) { + testElfHeaderImpl(jvmLib, false); + } + } + } + + @Test + public void test99UserFile() throws IOException { + if( null != userFile ) { + testElfHeaderImpl(userFile, false); + } + } + + void testElfHeaderImpl(final File file, final boolean fileOutSections) throws IOException { + PlatformProps.initSingleton(); + System.err.println("Test file "+file.getAbsolutePath()); + final RandomAccessFile in = new RandomAccessFile(file, "r"); + try { + final ElfHeaderPart1 eh1; + final ElfHeaderPart2 eh2; + try { + eh1 = ElfHeaderPart1.read(PlatformProps.OS, in); + eh2 = ElfHeaderPart2.read(eh1, in); + } catch (final Exception e) { + System.err.println("Probably not an ELF file - or not in current format: (caught) "+e.getMessage()); + e.printStackTrace(); + return; + } + int i=0; + System.err.println(eh1); + System.err.println(eh2); + System.err.println("SH entsz "+eh2.raw.getE_shentsize()); + System.err.println("SH off "+toHexString(eh2.raw.getE_shoff())); + System.err.println("SH strndx "+eh2.raw.getE_shstrndx()); + System.err.println("SH num "+eh2.sht.length); + if( 0 < eh2.sht.length ) { + System.err.println("SH size "+eh2.sht[0].raw.getBuffer().limit()); + } + { + final SectionHeader sh = eh2.getSectionHeader(SectionHeader.SHT_ARM_ATTRIBUTES); + boolean abiVFPArgsAcceptsVFPVariant = false; + if( null != sh ) { + final SectionArmAttributes sArmAttrs = (SectionArmAttributes) sh.readSection(in); + final SectionArmAttributes.Attribute abiVFPArgsAttr = sArmAttrs.get(SectionArmAttributes.Tag.ABI_VFP_args); + if( null != abiVFPArgsAttr ) { + abiVFPArgsAcceptsVFPVariant = SectionArmAttributes.abiVFPArgsAcceptsVFPVariant(abiVFPArgsAttr.getULEB128()); + } + } + System.err.println("abiVFPArgsAcceptsVFPVariant "+abiVFPArgsAcceptsVFPVariant); + } + for(i=0; i<eh2.sht.length; i++) { + final SectionHeader sh = eh2.sht[i]; + System.err.println(sh); + final int type = sh.getType(); + if( SectionHeader.SHT_STRTAB == type ) { + dumpSection(in, sh, "SHT_STRTAB", fileOutSections); + } else if( SectionHeader.SHT_ARM_ATTRIBUTES == type ) { + dumpSection(in, sh, "SHT_ARM_ATTRIBUTES", fileOutSections); + } + } + } finally { + in.close(); + } + } + + static void dumpSection(final RandomAccessFile in, final SectionHeader sh, final String name, final boolean fileOut) throws IllegalArgumentException, IOException { + final Section s = sh.readSection(in); + if(fileOut) { + final File outFile = new File("ElfSection-"+sh.getIndex()+"-"+name); + final OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile)); + try { + out.write(s.data, s.offset, s.length); + } finally { + out.close(); + } + } + System.err.println(name+": read "+s.length+", "+s); + } + + public static void main(final String args[]) throws IOException { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-file")) { + i++; + userFile = new File(args[i]); + } + } + final String tstname = TestElfReader01.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + + static String toHexString(final int i) { return "0x"+Integer.toHexString(i); } + static String toHexString(final long i) { return "0x"+Long.toHexString(i); } + +} diff --git a/test/java/org/jau/util/BitDemoData.java b/test/java/org/jau/util/BitDemoData.java new file mode 100644 index 0000000..f8b6de1 --- /dev/null +++ b/test/java/org/jau/util/BitDemoData.java @@ -0,0 +1,178 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import java.nio.ByteBuffer; +import java.util.Locale; + +public class BitDemoData { + public static final long UNSIGNED_INT_MAX_VALUE = 0xffffffffL; + + public static final String[] pyramid32bit_one = { + "00000000000000000000000000000001", + "00000000000000000000000000000010", + "00000000000000000000000000000100", + "00000000000000000000000000001000", + "00000000000000000000000000010000", + "00000000000000000000000000100000", + "00000000000000000000000001000000", + "00000000000000000000000010000000", + "00000000000000000000000100000000", + "00000000000000000000001000000000", + "00000000000000000000010000000000", + "00000000000000000000100000000000", + "00000000000000000001000000000000", + "00000000000000000010000000000000", + "00000000000000000100000000000000", + "00000000000000001000000000000000", + "00000000000000010000000000000000", + "00000000000000100000000000000000", + "00000000000001000000000000000000", + "00000000000010000000000000000000", + "00000000000100000000000000000000", + "00000000001000000000000000000000", + "00000000010000000000000000000000", + "00000000100000000000000000000000", + "00000001000000000000000000000000", + "00000010000000000000000000000000", + "00000100000000000000000000000000", + "00001000000000000000000000000000", + "00010000000000000000000000000000", + "00100000000000000000000000000000", + "01000000000000000000000000000000", + "10000000000000000000000000000000" + }; + + // + // MSB -> LSB over whole data + // + public static final byte[] testBytesMSB = new byte[] { (byte)0xde, (byte)0xaf, (byte)0xca, (byte)0xfe }; + public static final int testIntMSB = 0xdeafcafe; // 11011110 10101111 11001010 11111110 + public static final String[] testStringsMSB = new String[] { "11011110", "10101111", "11001010", "11111110" }; + public static final String testStringMSB = testStringsMSB[0]+testStringsMSB[1]+testStringsMSB[2]+testStringsMSB[3]; + + // + // MSB -> LSB, reverse bit-order over each byte of testBytesLSB + // + public static final byte[] testBytesMSB_rev = new byte[] { (byte)0xfe, (byte)0xca, (byte)0xaf, (byte)0xde }; + public static final int testIntMSB_rev = 0xfecaafde; + public static final String[] testStringsMSB_rev = new String[] { "11111110", "11001010", "10101111", "11011110" }; + public static final String testStringMSB_rev = testStringsMSB_rev[0]+testStringsMSB_rev[1]+testStringsMSB_rev[2]+testStringsMSB_rev[3]; + + // + // LSB -> MSB over whole data + // + public static final byte[] testBytesLSB = new byte[] { (byte)0x7f, (byte)0x53, (byte)0xf5, (byte)0x7b }; + public static final int testIntLSB = 0x7f53f57b; + public static final String[] testStringsLSB = new String[] { "01111111", "01010011", "11110101", "01111011" }; + public static final String testStringLSB = testStringsLSB[0]+testStringsLSB[1]+testStringsLSB[2]+testStringsLSB[3]; + + // + // LSB -> MSB, reverse bit-order over each byte of testBytesMSB + // + public static final byte[] testBytesLSB_revByte = new byte[] { (byte)0x7b, (byte)0xf5, (byte)0x53, (byte)0x7f }; + public static final int testIntLSB_revByte = 0x7bf5537f; + public static final String[] testStringsLSB_revByte = new String[] { "01111011", "11110101", "01010011", "01111111" }; + public static final String testStringLSB_revByte = testStringsLSB_revByte[0]+testStringsLSB_revByte[1]+testStringsLSB_revByte[2]+testStringsLSB_revByte[3]; + + public static final void dumpData(final String prefix, final byte[] data, final int offset, final int len) { + for(int i=0; i<len; ) { + System.err.printf("%s: %03d: ", prefix, i); + for(int j=0; j<8 && i<len; j++, i++) { + final int v = 0xFF & data[offset+i]; + System.err.printf(toHexBinaryString(v, 8)+", "); + } + System.err.println(""); + } + } + public static final void dumpData(final String prefix, final ByteBuffer data, final int offset, final int len) { + for(int i=0; i<len; ) { + System.err.printf("%s: %03d: ", prefix, i); + for(int j=0; j<8 && i<len; j++, i++) { + final int v = 0xFF & data.get(offset+i); + System.err.printf(toHexBinaryString(v, 8)+", "); + } + System.err.println(""); + } + } + + public static int getOneBitCount(final String pattern) { + int c=0; + for(int i=0; i<pattern.length(); i++) { + if( '1' == pattern.charAt(i) ) { + c++; + } + } + return c; + } + public static long toLong(final String bitPattern) { + return Long.valueOf(bitPattern, 2).longValue(); + } + public static int toInteger(final String bitPattern) { + final long res = Long.valueOf(bitPattern, 2).longValue(); + if( res > UNSIGNED_INT_MAX_VALUE ) { + throw new NumberFormatException("Exceeds "+toHexString(UNSIGNED_INT_MAX_VALUE)+": "+toHexString(res)+" - source "+bitPattern); + } + return (int)res; + } + + public static String toHexString(final int v) { + return "0x"+Integer.toHexString(v); + } + public static String toHexString(final long v) { + return "0x"+Long.toHexString(v); + } + public static final String strZeroPadding= "0000000000000000000000000000000000000000000000000000000000000000"; // 64 + public static String toBinaryString(final int v, final int bitCount) { + if( 0 == bitCount ) { + return ""; + } + final int mask = (int) ( ( 1L << bitCount ) - 1L ); + final String s0 = Integer.toBinaryString( mask & v ); + return strZeroPadding.substring(0, bitCount-s0.length())+s0; + } + public static String toBinaryString(final long v, final int bitCount) { + if( 0 == bitCount ) { + return ""; + } + final long mask = ( 1L << bitCount ) - 1L; + final String s0 = Long.toBinaryString( mask & v ); + return strZeroPadding.substring(0, bitCount-s0.length())+s0; + } + public static String toHexBinaryString(final long v, final int bitCount) { + final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4; + return String.format((Locale)null, "[%0"+nibbles+"X, %s]", v, toBinaryString(v, bitCount)); + } + public static String toHexBinaryString(final int v, final int bitCount) { + final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4; + return String.format((Locale)null, "[%0"+nibbles+"X, %s]", v, toBinaryString(v, bitCount)); + } + public static String toHexBinaryString(final short v, final int bitCount) { + final int nibbles = 0 == bitCount ? 2 : ( bitCount + 3 ) / 4; + return String.format((Locale)null, "[%0"+nibbles+"X, %s]", v, toBinaryString(v, bitCount)); + } +} diff --git a/test/java/org/jau/util/TestBitfield00.java b/test/java/org/jau/util/TestBitfield00.java new file mode 100644 index 0000000..b0dbac6 --- /dev/null +++ b/test/java/org/jau/util/TestBitfield00.java @@ -0,0 +1,428 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2015 Gothel Software e.K. + * Copyright (c) 2015 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import java.io.IOException; + +import org.junit.Test; +import org.jau.junit.util.JunitTracer; +import org.junit.Assert; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test basic bitfield operations for {@link Bitfield} + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitfield00 extends JunitTracer { + + @Test + public void test01_BitCount32_One() { + final String[] pyramid32bit_one = BitDemoData.pyramid32bit_one; + for(int i=0; i<pyramid32bit_one.length; i++) { + final int val0 = 1 << i; + final int oneBitCountI = Integer.bitCount(val0); + final String pattern0 = pyramid32bit_one[i]; + final int val1 = BitDemoData.toInteger(pattern0); + final String pattern1 = BitDemoData.toBinaryString(val0, 32); + final int oneBitCount0 = BitDemoData.getOneBitCount(pattern0); + final int oneBitCount1 = BitMath.bitCount(val0); + final String msg = String.format("Round %02d: 0x%08x %s, c %d / %d%n : 0x%08x %s, c %d%n", + i, val0, pattern0, oneBitCount0, oneBitCountI, val1, pattern1, oneBitCount1); + + Assert.assertEquals(msg, val0, val1); + Assert.assertEquals(msg, pattern0, pattern1); + + Assert.assertEquals(msg, oneBitCount0, oneBitCountI); + Assert.assertEquals(msg, oneBitCount0, oneBitCount1); + } + } + + @SuppressWarnings("unused") + @Test + public void test02_BitCount32_Samples() { + final long MAX = BitDemoData.UNSIGNED_INT_MAX_VALUE; + final long MAX_minus = MAX-0x1FF; + final long MAX_half = MAX/2; + final long MAX_half_minus = MAX_half-0x1FF; + final long MAX_half_plus = MAX_half+0x1FF; + + if( false ) { + for(long l=0; l<=MAX; l++) { + test_BitCount32_Samples(l); + } + } + for(long l=0; l>=0x1FF; l++) { + test_BitCount32_Samples(l); + } + for(long l=MAX_half_minus; l<=MAX_half_plus; l++) { + test_BitCount32_Samples(l); + } + for(long l=MAX_minus; l<=MAX; l++) { + test_BitCount32_Samples(l); + } + } + static void test_BitCount32_Samples(final long l) { + final int oneBitCountL = Long.bitCount(l); + final int val0 = (int)l; + final int oneBitCountI = Integer.bitCount(val0); + final int oneBitCount1 = BitMath.bitCount(val0); + final String msg = String.format("Round 0x%08x, c %d / %d / %d", val0, + oneBitCountL, oneBitCountI, oneBitCount1); + Assert.assertEquals(msg, oneBitCountI, oneBitCountL); + Assert.assertEquals(msg, oneBitCountI, oneBitCount1); + } + + static int[] testDataOneBit = new int[] { + 0,0, 1,1, 2,1, 3,2, 4,1, 5,2, 6,2, 7,3, + 8,1, 9,2, 10,2, 11,3, 12,2, 13,3, 14,3, 15,4, 16,1, 17,2, + 0x3F,6, 0x40,1, 0x41,2, 0x7f,7, 0x80,1, 0x81,2, 0xfe,7, 0xff,8, + 0x4000,1, 0x4001,2, 0x7000,3, 0x7fff,15, + 0x0FFFFFF0,24, + 0x55555555,16, + 0x7F53F57B,23, 0xFEA7EAF6,23, /* << 1 */ + 0x80000000, 1, + 0xAAAAAAAA,16, + 0xC0C0C0C0, 8, + 0xFF000000, 8, + 0xFFFFFFFF,32 + }; + @Test + public void test03_BitCount32_Data() { + for(int i = 0; i<testDataOneBit.length; i+=2) { + test_BitCount32_Data(testDataOneBit[i], testDataOneBit[i+1]); + } + } + static void test_BitCount32_Data(final int i, final int expOneBits) { + final int oneBitCountI = Integer.bitCount(i); + final int oneBitCount1 = BitMath.bitCount(i); + final String msg = String.format("Round 0x%08x, c %d / %d", i, + oneBitCountI, oneBitCount1); + Assert.assertEquals(msg, oneBitCount1, expOneBits); + Assert.assertEquals(msg, oneBitCountI, oneBitCount1); + } + + @Test + public void test10_Setup() { + final int bitSize32 = 32; + final int bitSize128 = 128; + final Bitfield bf1 = Bitfield.Factory.create(bitSize32); + Assert.assertEquals(bitSize32, bf1.size()); + Assert.assertTrue(bf1 instanceof jau.util.Int32Bitfield); + final Bitfield bf2 = Bitfield.Factory.create(bitSize128); + Assert.assertEquals(bitSize128, bf2.size()); + Assert.assertTrue(bf2 instanceof jau.util.Int32ArrayBitfield); + + // verify no bit is set + Assert.assertEquals(0, bf1.bitCount()); + Assert.assertEquals(0, bf2.bitCount()); + + bf1.clearField(true); + bf2.clearField(true); + Assert.assertEquals(bf1.size(), bf1.bitCount()); + Assert.assertEquals(bf2.size(), bf2.bitCount()); + + bf1.clearField(false); + bf2.clearField(false); + Assert.assertEquals(0, bf1.bitCount()); + Assert.assertEquals(0, bf2.bitCount()); + } + static class TestDataBF { + final int bitSize; + final int val; + final String pattern; + public TestDataBF(final int bitSize, final int value, final String pattern) { + this.bitSize = bitSize; + this.val = value; + this.pattern = pattern; + } + } + static TestDataBF[] testDataBF32Bit = { + new TestDataBF(32, BitDemoData.testIntMSB, BitDemoData.testStringMSB), + new TestDataBF(32, BitDemoData.testIntMSB_rev, BitDemoData.testStringMSB_rev), + new TestDataBF(32, BitDemoData.testIntLSB, BitDemoData.testStringLSB), + new TestDataBF(32, BitDemoData.testIntLSB_revByte, BitDemoData.testStringLSB_revByte), + + // H->L : 0x04030201: 00000100 00000011 00000010 00000001 + new TestDataBF(32, 0x04030201, "00000100000000110000001000000001"), + + // H->L : 0xAFFECAFE: 10101111 11111110 11001010 11111110 + new TestDataBF(32, 0xAFFECAFE, "10101111111111101100101011111110"), + // H->L : 0xDEADBEEF: 11011110 10101101 10111110 11101111 + new TestDataBF(32, 0xDEADBEEF, "11011110101011011011111011101111") + }; + static TestDataBF[] testDataBF16Bit = { + // H->L : 0x0201: 00000100 00000011 00000010 00000001 + new TestDataBF(16, 0x0201, "0000001000000001"), + // H->L : 0x0403: 00000100 00000011 + new TestDataBF(16, 0x0403, "0000010000000011"), + + // H->L : 0xAFFE: 10101111 11111110 + new TestDataBF(16, 0xAFFE, "1010111111111110"), + // H->L : 0xCAFE: 11001010 11111110 + new TestDataBF(16, 0xCAFE, "1100101011111110"), + + // H->L : 0xDEADBEEF: 11011110 10101101 10111110 11101111 + new TestDataBF(16, 0xDEAD, "1101111010101101"), + new TestDataBF(16, 0xBEEF, "1011111011101111") + }; + static TestDataBF[] testDataBF3Bit = { + new TestDataBF(3, 0x01, "001"), + new TestDataBF(3, 0x02, "010"), + new TestDataBF(3, 0x05, "101") + }; + + @Test + public void test20_ValidateTestData() { + for(int i=0; i<testDataBF32Bit.length; i++) { + test_ValidateTestData( testDataBF32Bit[i] ); + } + for(int i=0; i<testDataBF16Bit.length; i++) { + test_ValidateTestData( testDataBF16Bit[i] ); + } + for(int i=0; i<testDataBF3Bit.length; i++) { + test_ValidateTestData( testDataBF3Bit[i] ); + } + } + static void test_ValidateTestData(final TestDataBF d) { + final int oneBitCount0 = BitMath.bitCount(d.val); + final int oneBitCount1 = BitDemoData.getOneBitCount(d.pattern); + Assert.assertEquals(oneBitCount1, oneBitCount0); + final String pattern0 = BitDemoData.toBinaryString(d.val, d.bitSize); + Assert.assertEquals(d.pattern, pattern0); + final int val1 = BitDemoData.toInteger(d.pattern); + Assert.assertEquals(d.val, val1); + Assert.assertEquals(d.bitSize, pattern0.length()); + } + + static void assertEquals(final Bitfield bf, final int bf_off, final int v, final String pattern, final int oneBitCount) { + final int len = pattern.length(); + for(int i=0; i<len; i++) { + final boolean exp0 = 0 != ( v & ( 1 << i ) ); + final boolean exp1 = '1' == pattern.charAt(len-1-i); + final boolean has = bf.get(i+bf_off); + final String msg = String.format("Pos %04d: Value 0x%08x / %s, c %d", i, v, pattern, oneBitCount); + Assert.assertEquals(msg, exp0, has); + Assert.assertEquals(msg, exp1, has); + } + } + + @Test + public void test21_Aligned32bit() { + for(int i=0; i<testDataBF32Bit.length; i++) { + test_Aligned32bit( testDataBF32Bit[i] ); + } + for(int i=0; i<testDataBF16Bit.length; i++) { + test_Aligned32bit( testDataBF16Bit[i] ); + } + for(int i=0; i<testDataBF3Bit.length; i++) { + test_Aligned32bit( testDataBF3Bit[i] ); + } + } + static int get32BitStorageSize(final int bits) { + final int units = Math.max(1, ( bits + 31 ) >>> 5); + return units << 5; + } + static void test_Aligned32bit(final TestDataBF d) { + final int oneBitCount = BitMath.bitCount(d.val); + + final Bitfield bf1 = Bitfield.Factory.create(d.bitSize); + Assert.assertEquals(get32BitStorageSize(d.bitSize), bf1.size()); + final Bitfield bf2 = Bitfield.Factory.create(d.bitSize+128); + Assert.assertEquals(get32BitStorageSize(d.bitSize+128), bf2.size()); + + bf1.put32( 0, d.bitSize, d.val); + Assert.assertEquals(d.val, bf1.get32( 0, d.bitSize)); + Assert.assertEquals(oneBitCount, bf1.bitCount()); + assertEquals(bf1, 0, d.val, d.pattern, oneBitCount); + + bf2.put32( 0, d.bitSize, d.val); + Assert.assertEquals(d.val, bf2.get32( 0, d.bitSize)); + Assert.assertEquals(oneBitCount*1, bf2.bitCount()); + assertEquals(bf2, 0, d.val, d.pattern, oneBitCount); + bf2.put32(64, d.bitSize, d.val); + Assert.assertEquals(d.val, bf2.get32(64, d.bitSize)); + Assert.assertEquals(oneBitCount*2, bf2.bitCount()); + assertEquals(bf2, 64, d.val, d.pattern, oneBitCount); + + Assert.assertEquals(d.val, bf2.copy32(0, 96, d.bitSize)); + Assert.assertEquals(d.val, bf2.get32(96, d.bitSize)); + Assert.assertEquals(oneBitCount*3, bf2.bitCount()); + assertEquals(bf2, 96, d.val, d.pattern, oneBitCount); + } + + @Test + public void test21_Unaligned() { + for(int i=0; i<testDataBF32Bit.length; i++) { + test_Unaligned(testDataBF32Bit[i]); + } + for(int i=0; i<testDataBF16Bit.length; i++) { + test_Unaligned(testDataBF16Bit[i]); + } + for(int i=0; i<testDataBF3Bit.length; i++) { + test_Unaligned( testDataBF3Bit[i] ); + } + } + static void test_Unaligned(final TestDataBF d) { + final Bitfield bf1 = Bitfield.Factory.create(d.bitSize); + final Bitfield bf2 = Bitfield.Factory.create(d.bitSize+128); + Assert.assertEquals(get32BitStorageSize(d.bitSize), bf1.size()); + Assert.assertEquals(get32BitStorageSize(d.bitSize+128), bf2.size()); + test_Unaligned( d, bf1 ); + test_Unaligned( d, bf2 ); + } + static void test_Unaligned(final TestDataBF d, final Bitfield bf) { + final int maxBitpos = bf.size()-d.bitSize; + for(int i=0; i<=maxBitpos; i++) { + bf.clearField(false); + test_Unaligned(d, bf, i); + } + } + static void test_Unaligned(final TestDataBF d, final Bitfield bf, final int lowBitnum) { + final int maxBitpos = bf.size()-d.bitSize; + final int oneBitCount = BitMath.bitCount(d.val); + + final String msg = String.format("Value 0x%08x / %s, l %d/%d, c %d, lbPos %d -> %d", + d.val, d.pattern, d.bitSize, bf.size(), oneBitCount, lowBitnum, maxBitpos); + + // + // via put32 + // + bf.put32( lowBitnum, d.bitSize, d.val); + for(int i=0; i<d.bitSize; i++) { + Assert.assertEquals(msg+", bitpos "+i, 0 != ( d.val & ( 1 << i ) ), bf.get(lowBitnum+i)); + } + Assert.assertEquals(msg, d.val, bf.get32( lowBitnum, d.bitSize)); + Assert.assertEquals(msg, oneBitCount, bf.bitCount()); + assertEquals(bf, lowBitnum, d.val, d.pattern, oneBitCount); + + // + // via copy32 + // + if( lowBitnum < maxBitpos ) { + // copy bits 1 forward + // clear trailing orig bit + Assert.assertEquals(msg, d.val, bf.copy32(lowBitnum, lowBitnum+1, d.bitSize)); + bf.clear(lowBitnum); + Assert.assertEquals(msg, d.val, bf.get32( lowBitnum+1, d.bitSize)); + Assert.assertEquals(msg, oneBitCount, bf.bitCount()); + assertEquals(bf, lowBitnum+1, d.val, d.pattern, oneBitCount); + } + + // test put() return value (previous value) + bf.clearField(false); + Assert.assertEquals(msg+", bitpos "+0, false, bf.put(lowBitnum+0, true)); + Assert.assertEquals(msg+", bitpos "+0, true, bf.put(lowBitnum+0, false)); + + // + // via put + // + for(int i=0; i<d.bitSize; i++) { + Assert.assertEquals(msg+", bitpos "+i, false, bf.put(lowBitnum+i, 0 != ( d.val & ( 1 << i ) ))); + } + Assert.assertEquals(msg, d.val, bf.get32(lowBitnum, d.bitSize)); + for(int i=0; i<d.bitSize; i++) { + Assert.assertEquals(msg+", bitpos "+i, 0 != ( d.val & ( 1 << i ) ), bf.get(lowBitnum+i)); + } + Assert.assertEquals(msg, oneBitCount, bf.bitCount()); + assertEquals(bf, lowBitnum, d.val, d.pattern, oneBitCount); + + // + // via copy + // + if( lowBitnum < maxBitpos ) { + // copy bits 1 forward + // clear trailing orig bit + for(int i=d.bitSize-1; i>=0; i--) { + Assert.assertEquals(msg+", bitpos "+i, 0 != ( d.val & ( 1 << i ) ), + bf.copy(lowBitnum+i, lowBitnum+1+i) ); + } + bf.clear(lowBitnum); + Assert.assertEquals(msg, d.val, bf.get32( lowBitnum+1, d.bitSize)); + for(int i=0; i<d.bitSize; i++) { + Assert.assertEquals(msg+", bitpos "+i, 0 != ( d.val & ( 1 << i ) ), bf.get(lowBitnum+1+i)); + } + Assert.assertEquals(msg, oneBitCount, bf.bitCount()); + assertEquals(bf, lowBitnum+1, d.val, d.pattern, oneBitCount); + } + + // + // via set/clear + // + bf.clearField(false); + for(int i=0; i<d.bitSize; i++) { + if( 0 != ( d.val & ( 1 << i ) ) ) { + bf.set(lowBitnum+i); + } else { + bf.clear(lowBitnum+i); + } + } + Assert.assertEquals(msg, d.val, bf.get32(lowBitnum, d.bitSize)); + for(int i=0; i<d.bitSize; i++) { + Assert.assertEquals(msg+", bitpos "+i, 0 != ( d.val & ( 1 << i ) ), bf.get(lowBitnum+i)); + } + Assert.assertEquals(msg, oneBitCount, bf.bitCount()); + assertEquals(bf, lowBitnum, d.val, d.pattern, oneBitCount); + + // + // Validate 'other bits' put32/get32 + // + bf.clearField(false); + bf.put32( lowBitnum, d.bitSize, d.val); + checkOtherBits(d, bf, lowBitnum, msg, 0); + + bf.clearField(true); + bf.put32( lowBitnum, d.bitSize, d.val); + checkOtherBits(d, bf, lowBitnum, msg, BitMath.UNSIGNED_INT_MAX_VALUE); + } + + static void checkOtherBits(final TestDataBF d, final Bitfield bf, final int lowBitnum, final String msg, final int expBits) { + final int highBitnum = lowBitnum + d.bitSize - 1; + // System.err.println(msg+": [0"+".."+"("+lowBitnum+".."+highBitnum+").."+(bf.size()-1)+"]"); + for(int i=0; i<lowBitnum; i+=32) { + final int len = Math.min(32, lowBitnum-i); + final int val = bf.get32(i, len); + final int exp = expBits & BitMath.getBitMask(len); + // System.err.println(" <"+i+".."+(i+len-1)+">, exp "+BitDemoData.toHexString(exp)); + Assert.assertEquals(msg+", bitpos "+i, exp, val); + } + for(int i=highBitnum+1; i<bf.size(); i+=32) { + final int len = Math.min(32, bf.size() - i); + final int val = bf.get32(i, len); + final int exp = expBits & BitMath.getBitMask(len); + // System.err.println(" <"+i+".."+(i+len-1)+">, exp "+BitDemoData.toHexString(exp)); + Assert.assertEquals(msg+", bitpos "+i, exp, val); + } + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestBitfield00.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/util/TestBitstream00.java b/test/java/org/jau/util/TestBitstream00.java new file mode 100644 index 0000000..e618151 --- /dev/null +++ b/test/java/org/jau/util/TestBitstream00.java @@ -0,0 +1,265 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.LongBuffer; + +import org.jau.io.Bitstream; +import org.jau.junit.util.SingletonJunitCase; +import org.jau.lang.NioUtil; +import org.jau.sys.PlatformProps; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import static org.jau.util.BitDemoData.*; + +/** + * Test basic bit operations for {@link Bitstream} + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream00 extends SingletonJunitCase { + + @Test + public void test00ShowByteOrder() { + final int i_ff = 0xff; + final byte b_ff = (byte)i_ff; + System.err.println("i_ff "+i_ff+", "+toHexBinaryString(i_ff, 8)); + System.err.println("b_ff "+b_ff+", "+toHexBinaryString(0xff & b_ff, 8)); + + System.err.println("Platform.LITTLE_ENDIAN: "+PlatformProps.LITTLE_ENDIAN); + showOrderImpl(null); + showOrderImpl(ByteOrder.BIG_ENDIAN); + showOrderImpl(ByteOrder.LITTLE_ENDIAN); + + dumpData("tstMSB.whole", testBytesMSB, 0, testBytesMSB.length); + dumpData("tstLSB.pbyte", testBytesLSB_revByte, 0, testBytesLSB_revByte.length); + dumpData("tstLSB.whole", testBytesLSB, 0, testBytesLSB.length); + } + void showOrderImpl(final ByteOrder byteOrder) { + final ByteBuffer bb_long = ByteBuffer.allocate(NioUtil.SIZEOF_LONG); + if( null != byteOrder ) { + bb_long.order(byteOrder); + } + System.err.println("Order: "+byteOrder+" -> "+bb_long.order()); + final LongBuffer lb = bb_long.asLongBuffer(); + lb.put(0, 0x0807060504030201L); + dumpData("long."+byteOrder, bb_long, 0, bb_long.capacity()); + + final ByteBuffer bb_int = ByteBuffer.allocate(NioUtil.SIZEOF_INT); + if( null != byteOrder ) { + bb_int.order(byteOrder); + } + final IntBuffer ib = bb_int.asIntBuffer(); + ib.put(0, 0x04030201); + dumpData("long."+byteOrder, bb_int, 0, bb_int.capacity()); + + dumpData("tstMSB.whole", testBytesMSB, 0, testBytesMSB.length); + dumpData("tstLSB.pbyte", testBytesLSB_revByte, 0, testBytesLSB_revByte.length); + dumpData("tstLSB.whole", testBytesLSB, 0, testBytesLSB.length); + } + + @Test + public void test01Uint32Conversion() { + testUInt32Conversion(1, 1); + testUInt32Conversion(-2, -1); + testUInt32Conversion(Integer.MAX_VALUE, Integer.MAX_VALUE); + testUInt32Conversion(0xffff0000, -1); + testUInt32Conversion(0xffffffff, -1); + } + void testUInt32Conversion(final int int32, final int expUInt32Int) { + final String int32_hStr = toHexString(int32); + final long l = Bitstream.toUInt32Long(int32); + final String l_hStr = toHexString(l); + final int i = Bitstream.toUInt32Int(int32); + final String i_hStr = toHexString(i); + System.err.printf("int32_t %012d %10s -> (long) %012d %10s, (int) %012d %10s%n", int32, int32_hStr, l, l_hStr, i, i_hStr); + Assert.assertEquals(int32_hStr, l_hStr); + Assert.assertEquals(expUInt32Int, i); + } + + @Test + public void test02ShiftSigned() { + shiftSigned(0xA0000000); // negative w/ '1010' top-nibble + shiftSigned(-1); + } + void shiftSigned(final int i0) { + System.err.printf("i0 %012d, %s%n", i0, toHexBinaryString(i0, 32)); + { + int im = i0; + for(int i=0; i<32; i++) { + final int bitA = ( 0 != ( i0 & ( 1 << i ) ) ) ? 1 : 0; + final int bitB = im & 0x01; + System.err.printf("[%02d]: bit[%d, %d], im %012d, %s%n", i, bitA, bitB, im, toHexBinaryString(im, 32)); + im = im >>> 1; + } + } + } + + @Test + public void test10ReadWrite_13() throws UnsupportedOperationException, IllegalStateException, IOException { + // H->L : 00000011 00000010 00000001 000000110000001000000001 + // H->L rev: 10000000 01000000 11000000 100000000100000011000000 + // + // L->H : 00000001 00000010 00000011 000000010000001000000011 + // L->H rev: 11000000 01000000 10000000 110000000100000010000000 + test10ReadWrite1_31Impl(8, 8, 8, 0x030201, "000000110000001000000001"); + + // H->L: 00011 000010 00001 0001100001000001 + // L->H: 10000 010000 11000 1000001000011000 + test10ReadWrite1_31Impl(5, 6, 5, 0x1841, "0001100001000001"); + } + void test10ReadWrite1_31Impl(final int c1, final int c2, final int c3, final int v, final String vStrHigh2LowExp) + throws UnsupportedOperationException, IllegalStateException, IOException + { + // final Bitstream<ByteBuffer> source = new Bitstream<ByteBuffer>(); + final int bitCount = c1+c2+c3; + final int byteCount = ( bitCount + 7 ) / 8; + final String vStrHigh2Low0 = Bitstream.toBinString(true, v, bitCount); + System.err.printf("test10ReadWrite31 bits %d:%d:%d = %d = %d bytes%n", + c1, c2, c3, bitCount, byteCount); + System.err.printf("test10ReadWrite31 %s%n", Bitstream.toHexBinString(true, v, bitCount)); + System.err.printf("test10ReadWrite31 %s%n", Bitstream.toHexBinString(false, v, bitCount)); + Assert.assertEquals(vStrHigh2LowExp, vStrHigh2Low0); + + final ByteBuffer bbRead = ByteBuffer.allocate(byteCount); + for(int i=0; i<byteCount; i++) { + final int b = ( v >>> 8*i ) & 0xff; + bbRead.put(i, (byte) b); + System.err.printf("testBytes[%d]: %s%n", i, Bitstream.toHexBinString(true, b, 8)); + } + final Bitstream.ByteBufferStream bbsRead = new Bitstream.ByteBufferStream(bbRead); + final Bitstream<ByteBuffer> bsRead = new Bitstream<ByteBuffer>(bbsRead, false /* outputMode */); + + String vStrHigh2Low1C1 = ""; + String vStrHigh2Low1C2 = ""; + String vStrHigh2Low1C3 = ""; + String vStrHigh2Low1 = ""; + { + bsRead.mark(byteCount); + System.err.println("readBit (msbFirst false): "); + int b; + int i=0; + String vStrHigh2Low1T = ""; // OK for LSB, MSB segmented + while( Bitstream.EOS != ( b = bsRead.readBit(false /* msbFirst */) ) ) { + vStrHigh2Low1T = b + vStrHigh2Low1T; + if(i < c1) { + vStrHigh2Low1C1 = b + vStrHigh2Low1C1; + } else if(i < c1+c2) { + vStrHigh2Low1C2 = b + vStrHigh2Low1C2; + } else { + vStrHigh2Low1C3 = b + vStrHigh2Low1C3; + } + i++; + } + vStrHigh2Low1 = vStrHigh2Low1C3 + vStrHigh2Low1C2 + vStrHigh2Low1C1; + System.err.printf("readBit.1 %s, 0x%s%n", vStrHigh2Low1C1, Integer.toHexString(Integer.valueOf(vStrHigh2Low1C1, 2))); + System.err.printf("readBit.2 %s, 0x%s%n", vStrHigh2Low1C2, Integer.toHexString(Integer.valueOf(vStrHigh2Low1C2, 2))); + System.err.printf("readBit.3 %s, 0x%s%n", vStrHigh2Low1C3, Integer.toHexString(Integer.valueOf(vStrHigh2Low1C3, 2))); + System.err.printf("readBit.T %s, ok %b%n%n", vStrHigh2Low1T, vStrHigh2LowExp.equals(vStrHigh2Low1T)); + System.err.printf("readBit.X %s, ok %b%n%n", vStrHigh2Low1, vStrHigh2LowExp.equals(vStrHigh2Low1)); + bsRead.reset(); + } + + { + String vStrHigh2Low3T = ""; // OK for LSB, MSB segmented + System.err.println("readBits32: "); + final int b = bsRead.readBits31(bitCount); + vStrHigh2Low3T = Bitstream.toBinString(true, b, bitCount); + System.err.printf("readBits31.T %s, ok %b, %s%n%n", vStrHigh2Low3T, vStrHigh2LowExp.equals(vStrHigh2Low3T), Bitstream.toHexBinString(true, b, bitCount)); + bsRead.reset(); + } + + String vStrHigh2Low2 = ""; + { + System.err.println("readBits32: "); + final int bC1 = bsRead.readBits31(c1); + System.err.printf("readBits31.1 %s%n", Bitstream.toHexBinString(true, bC1, c1)); + final int bC2 = bsRead.readBits31(c2); + System.err.printf("readBits31.2 %s%n", Bitstream.toHexBinString(true, bC2, c2)); + final int bC3 = bsRead.readBits31(c3); + System.err.printf("readBits31.3 %s%n", Bitstream.toHexBinString(true, bC3, c3)); + final int b = bC3 << (c1+c2) | bC2 << c1 | bC1; + vStrHigh2Low2 = Bitstream.toBinString(true, b, bitCount); + System.err.printf("readBits31.X %s, ok %b, %s%n%n", vStrHigh2Low2, vStrHigh2LowExp.equals(vStrHigh2Low2), Bitstream.toHexBinString(true, b, bitCount)); + bsRead.reset(); + } + + Assert.assertEquals(vStrHigh2LowExp, vStrHigh2Low1); + Assert.assertEquals(vStrHigh2LowExp, vStrHigh2Low2); + + boolean ok = true; + { + final ByteBuffer bbWrite = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsWrite = new Bitstream.ByteBufferStream(bbWrite); + final Bitstream<ByteBuffer> bsWrite = new Bitstream<ByteBuffer>(bbsWrite, true /* outputMode */); + { + int b; + while( Bitstream.EOS != ( b = bsRead.readBit(false)) ) { + bsWrite.writeBit(false, b); + } + } + bsRead.reset(); + for(int i=0; i<byteCount; i++) { + final int bR = bbWrite.get(i); + final int bW = bbWrite.get(i); + System.err.printf("readWriteBit [%d]: read %s, write %s, ok %b%n", + i, Bitstream.toHexBinString(true, bR, 8), Bitstream.toHexBinString(true, bW, 8), bR==bW); + ok = ok && bR==bW; + } + Assert.assertTrue(ok); + } + { + final ByteBuffer bbWrite = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsWrite = new Bitstream.ByteBufferStream(bbWrite); + final Bitstream<ByteBuffer> bsWrite = new Bitstream<ByteBuffer>(bbsWrite, true /* outputMode */); + { + bsWrite.writeBits31(bitCount, bsRead.readBits31(bitCount)); + } + bsRead.reset(); + for(int i=0; i<byteCount; i++) { + final int bR = bbWrite.get(i); + final int bW = bbWrite.get(i); + System.err.printf("readWriteBits31[%d]: read %s, write %s, ok %b%n", + i, Bitstream.toHexBinString(true, bR, 8), Bitstream.toHexBinString(true, bW, 8), bR==bW); + ok = ok && bR==bW; + } + Assert.assertTrue(ok); + } + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestBitstream00.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/util/TestBitstream01.java b/test/java/org/jau/util/TestBitstream01.java new file mode 100644 index 0000000..6417dfe --- /dev/null +++ b/test/java/org/jau/util/TestBitstream01.java @@ -0,0 +1,361 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.jau.io.Bitstream; +import org.jau.junit.util.SingletonJunitCase; +import org.junit.Assert; +import org.junit.Test; + +import static org.jau.util.BitDemoData.*; + +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ raw linear and bulk read/write access w/o semantics: + * <ul> + * <li>{@link Bitstream#readBit(boolean)}</li> + * <li>{@link Bitstream#writeBit(boolean, int)}</li> + * <li>{@link Bitstream#mark(int)}</li> + * <li>{@link Bitstream#reset()}</li> + * <li>{@link Bitstream#flush()}</li> + * <li>{@link Bitstream#readBits31(int)}</li> + * <li>{@link Bitstream#writeBits31(int, int)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream01 extends SingletonJunitCase { + + Bitstream<ByteBuffer> getTestStream(final boolean msbFirstData, final boolean msbFirstWrite, + final int preBits, final int skipBits, final int postBits) throws IOException { + final int bitCount = preBits+skipBits+postBits; + final int byteCount = ( bitCount + 7 ) / 8; + final ByteBuffer bbTest = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsTest = new Bitstream.ByteBufferStream(bbTest); + final Bitstream<ByteBuffer> bsTest = new Bitstream<ByteBuffer>(bbsTest, true /* outputMode */); + final String sTest0; + if( msbFirstData ) { + sTest0 = testStringMSB.substring(0, preBits+skipBits+postBits); + } else { + sTest0 = testStringLSB.substring(0, preBits+skipBits+postBits); + } + if( msbFirstData == msbFirstWrite ) { + for(int i=0; i<bitCount; i++) { + final int bit = Integer.valueOf(sTest0.substring(i, i+1)); + bsTest.writeBit(msbFirstWrite, bit); + } + } else { + for(int i=bitCount-1; i >= 0; i--) { + final int bit = Integer.valueOf(sTest0.substring(i, i+1)); + bsTest.writeBit(msbFirstWrite, bit); + } + } + System.err.printf("TestData: msbFirst[data %b, write %b], bits[pre %d, skip %d, post %d = %d]: <%s>%n", + msbFirstData, msbFirstWrite, preBits, skipBits, postBits, bitCount, sTest0); + Assert.assertEquals(preBits+skipBits+postBits, bsTest.position()); + bsTest.setStream(bsTest.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("TestData: ", bsTest.getSubStream(), 0, bsTest.getSubStream().limit()); + return bsTest; + } + + String getTestStreamResultAsString(final boolean msbFirstData, final boolean msbFirstAssemble, + final int preBits, final int skipBits, final int postBits) { + final String pre, post; + if( msbFirstData ) { + if( msbFirstAssemble ) { + pre = testStringMSB.substring(0, preBits); + post = testStringMSB.substring(preBits+skipBits, preBits+skipBits+postBits); + } else { + pre = testStringMSB.substring(postBits+skipBits, preBits+skipBits+postBits); + post = testStringMSB.substring(0, postBits); + } + } else { + if( msbFirstAssemble ) { + pre = testStringLSB.substring(0, preBits); + post = testStringLSB.substring(preBits+skipBits, preBits+skipBits+postBits); + } else { + pre = testStringMSB.substring(postBits+skipBits, preBits+skipBits+postBits); + post = testStringMSB.substring(0, postBits); + } + } + final String r = msbFirstAssemble ? pre + post : post + pre; + System.err.println("ResultExp: <"+pre+"> + <"+post+"> = <"+r+">"); + return r; + } + + @Test + public void test01LinearBitsMSBFirst() throws IOException { + testLinearBitsImpl(true /* msbFirst */); + } + @Test + public void test02LinearBitsLSBFirst() throws IOException { + testLinearBitsImpl(false /* msbFirst */); + } + void testLinearBitsImpl(final boolean msbFirst) throws IOException { + testLinearBitsImpl(msbFirst, 0, 0, 1); + testLinearBitsImpl(msbFirst, 0, 0, 3); + testLinearBitsImpl(msbFirst, 0, 0, 8); + testLinearBitsImpl(msbFirst, 0, 0, 10); + testLinearBitsImpl(msbFirst, 0, 0, 30); + testLinearBitsImpl(msbFirst, 0, 0, 32); + + testLinearBitsImpl(msbFirst, 3, 0, 3); + testLinearBitsImpl(msbFirst, 8, 0, 3); + testLinearBitsImpl(msbFirst, 9, 0, 3); + + testLinearBitsImpl(msbFirst, 0, 1, 1); + testLinearBitsImpl(msbFirst, 0, 1, 3); + testLinearBitsImpl(msbFirst, 0, 2, 8); + testLinearBitsImpl(msbFirst, 0, 8, 10); + testLinearBitsImpl(msbFirst, 0, 12, 20); + testLinearBitsImpl(msbFirst, 0, 23, 9); + + testLinearBitsImpl(msbFirst, 1, 1, 1); + testLinearBitsImpl(msbFirst, 2, 1, 3); + testLinearBitsImpl(msbFirst, 7, 2, 8); + testLinearBitsImpl(msbFirst, 8, 8, 8); + testLinearBitsImpl(msbFirst, 15, 12, 5); + testLinearBitsImpl(msbFirst, 16, 11, 5); + } + + String readBits(final boolean msbFirst, final Bitstream<?> copy, final Bitstream<?> input, final int preCount, final int count) throws IOException { + final StringBuilder sbRead = new StringBuilder(); + int i = 0; + while( i < count ) { + final int bit = input.readBit(msbFirst); + if( Bitstream.EOS == bit ) { + System.err.printf(" EOS"); + break; + } else { + sbRead.append( ( 0 != bit ) ? '1' : '0' ); + i++; + Assert.assertEquals(i+preCount, input.position()); + if( null != copy ) { + copy.writeBit(msbFirst, bit); + Assert.assertEquals(i+preCount, copy.position()); + } + } + } + Assert.assertEquals(i+preCount, input.position()); + if( null != copy ) { + Assert.assertEquals(i+preCount, copy.position()); + } + return sbRead.toString(); + } + + void testLinearBitsImpl(final boolean msbFirst, final int preBits, final int skipBits, final int postBits) throws IOException { + final int totalBits = preBits+skipBits+postBits; + System.err.println("XXX TestLinearBits: msbFirst "+msbFirst+", preBits "+preBits+", skipBits "+skipBits+", postBits "+postBits+", totalBits "+totalBits); + + // prepare bitstream + System.err.println("Prepare bitstream"); + final Bitstream<ByteBuffer> bsTest = getTestStream(msbFirst, msbFirst, preBits, skipBits, postBits); + final String sTest = getTestStreamResultAsString(msbFirst, true, preBits, skipBits, postBits); + + // init copy-bitstream + final int byteCount = ( totalBits + 7 ) / 8; + final ByteBuffer bbCopy = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsCopy = new Bitstream.ByteBufferStream(bbCopy); + final Bitstream<ByteBuffer> bsCopy = new Bitstream<ByteBuffer>(bbsCopy, true /* outputMode */); + + // read-bitstream .. and copy bits while reading + System.err.println("Reading bitstream: "+bsTest); + { + final String sReadPre = readBits(msbFirst, bsCopy, bsTest, 0, preBits); + { + final int skippedBits = (int) bsTest.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + { + final int skippedBits = (int) bsCopy.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + final String sReadPost = readBits(msbFirst, bsCopy, bsTest, preBits+skipBits, postBits); + final String sRead = sReadPre + sReadPost; + System.err.println("Read.Test: <"+sReadPre+"> + <"+sReadPost+"> = <"+sRead+">"); + Assert.assertEquals(sTest, sRead); + Assert.assertEquals(totalBits, bsTest.position()); + Assert.assertEquals(totalBits, bsCopy.position()); + } + + // read copy .. + bsCopy.setStream(bsCopy.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("Copy", bbCopy, 0, bbCopy.limit()); + System.err.println("Reading copy-bitstream: "+bsCopy); + bsCopy.mark(0); // mark at beginning + Assert.assertEquals(0, bsCopy.position()); + { + final String sReadPre1 = readBits(msbFirst, null, bsCopy, 0, preBits); + { + final int skippedBits = (int) bsCopy.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + final String sReadPost1 = readBits(msbFirst, null, bsCopy, preBits+skipBits, postBits); + final String sRead1 = sReadPre1 + sReadPost1; + System.err.println("Read.Copy.1: <"+sReadPre1+"> + <"+sReadPost1+"> = <"+sRead1+">"); + Assert.assertEquals(sTest, sRead1); + + bsCopy.reset(); + final String sReadPre2 = readBits(msbFirst, null, bsCopy, 0, preBits); + Assert.assertEquals(sReadPre1, sReadPre2); + { + final int skippedBits = (int) bsCopy.skip(skipBits); + Assert.assertEquals(skipBits, skippedBits); + } + final String sReadPost2 = readBits(msbFirst, null, bsCopy, preBits+skipBits, postBits); + Assert.assertEquals(sReadPost1, sReadPost2); + final String sRead2 = sReadPre2 + sReadPost2; + System.err.println("Read.Copy.2: <"+sReadPre2+"> + <"+sReadPost2+"> = <"+sRead2+">"); + Assert.assertEquals(sTest, sRead2); + Assert.assertEquals(totalBits, bsCopy.position()); + } + } + + @Test + public void test03BulkBits() throws IOException { + testBulkBitsImpl(0, 0, 1); + testBulkBitsImpl(0, 0, 3); + testBulkBitsImpl(0, 0, 8); + testBulkBitsImpl(0, 0, 10); + testBulkBitsImpl(0, 0, 30); + testBulkBitsImpl(0, 0, 31); + + testBulkBitsImpl(3, 0, 3); + testBulkBitsImpl(8, 0, 3); + testBulkBitsImpl(9, 0, 3); + testBulkBitsImpl(5, 0, 6); + testBulkBitsImpl(5, 0, 8); + + testBulkBitsImpl(0, 1, 1); + testBulkBitsImpl(3, 6, 4); + + testBulkBitsImpl(0, 1, 3); + testBulkBitsImpl(0, 2, 8); + testBulkBitsImpl(0, 8, 10); + testBulkBitsImpl(0, 12, 20); + testBulkBitsImpl(0, 23, 9); + testBulkBitsImpl(0, 1, 31); + + testBulkBitsImpl(1, 1, 1); + testBulkBitsImpl(2, 1, 3); + testBulkBitsImpl(7, 2, 8); + testBulkBitsImpl(8, 8, 8); + testBulkBitsImpl(15, 12, 5); + testBulkBitsImpl(16, 11, 5); + testBulkBitsImpl(5, 6, 5); + testBulkBitsImpl(5, 6, 8); + } + + void testBulkBitsImpl(final int preBits, final int skipBits, final int postBits) throws IOException { + final int totalBits = preBits+skipBits+postBits; + System.err.println("XXX TestBulkBits: preBits "+preBits+", skipBits "+skipBits+", postBits "+postBits+", totalBits "+totalBits); + + // prepare bitstream + System.err.println("Prepare bitstream"); + final Bitstream<ByteBuffer> bsTest = getTestStream(true, false, preBits, skipBits, postBits); + final String sTest = getTestStreamResultAsString(true, false, preBits, skipBits, postBits); + + // init copy-bitstream + final int byteCount = ( totalBits + 7 ) / 8; + final ByteBuffer bbCopy = ByteBuffer.allocate(byteCount); + final Bitstream.ByteBufferStream bbsCopy = new Bitstream.ByteBufferStream(bbCopy); + final Bitstream<ByteBuffer> bsCopy = new Bitstream<ByteBuffer>(bbsCopy, true /* outputMode */); + + // read-bitstream .. and copy bits while reading + System.err.println("Reading bitstream: "+bsTest); + { + final int readBitsPre = bsTest.readBits31(preBits); + Assert.assertEquals(readBitsPre, bsCopy.writeBits31(preBits, readBitsPre)); + + final int skippedReadBits = (int) bsTest.skip(skipBits); + final int skippedBitsCopy = (int) bsCopy.skip(skipBits); + + final int readBitsPost = bsTest.readBits31(postBits); + Assert.assertEquals(readBitsPost, bsCopy.writeBits31(postBits, readBitsPost)); + final String sReadPreLo = toBinaryString(readBitsPre, preBits); + final String sReadPostHi = toBinaryString(readBitsPost, postBits); + final String sRead = sReadPostHi + sReadPreLo; + System.err.println("Read.Test: <"+sReadPreLo+"> + <"+sReadPostHi+"> = <"+sRead+">"); + + Assert.assertEquals(skipBits, skippedReadBits); + Assert.assertEquals(sTest, sRead); + Assert.assertEquals(totalBits, bsTest.position()); + Assert.assertEquals(skipBits, skippedBitsCopy); + } + + // read copy .. + bsCopy.setStream(bsCopy.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("Copy", bbCopy, 0, bbCopy.limit()); + System.err.println("Reading copy-bitstream: "+bsCopy); + Assert.assertEquals(0, bsCopy.position()); + { + final int copyBitsPre = bsCopy.readBits31(preBits); + + final int skippedCopyBits = (int) bsCopy.skip(skipBits); + + final int copyBitsPost = bsCopy.readBits31(postBits); + final String sCopyPreLo = toBinaryString(copyBitsPre, preBits); + final String sCopyPostHi = toBinaryString(copyBitsPost, postBits); + final String sCopy = sCopyPostHi + sCopyPreLo; + System.err.println("Copy.Test: <"+sCopyPreLo+"> + <"+sCopyPostHi+"> = <"+sCopy+">"); + + Assert.assertEquals(skipBits, skippedCopyBits); + Assert.assertEquals(sTest, sCopy); + Assert.assertEquals(totalBits, bsCopy.position()); + } + } + + @Test + public void test05ErrorHandling() throws IOException { + // prepare bitstream + final Bitstream<ByteBuffer> bsTest = getTestStream(false, false, 0, 0, 0); + System.err.println("01a: "+bsTest); + bsTest.close(); + System.err.println("01b: "+bsTest); + + try { + bsTest.readBit(false); + } catch (final Exception e) { + Assert.assertNotNull(e); + } + try { + bsTest.writeBit(false, 1); + } catch (final Exception e) { + Assert.assertNotNull(e); + } + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestBitstream01.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/util/TestBitstream02.java b/test/java/org/jau/util/TestBitstream02.java new file mode 100644 index 0000000..b84052c --- /dev/null +++ b/test/java/org/jau/util/TestBitstream02.java @@ -0,0 +1,131 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import static org.jau.util.BitDemoData.toBinaryString; +import static org.jau.util.BitDemoData.toHexBinaryString; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.jau.io.Bitstream; +import org.jau.junit.util.SingletonJunitCase; +import org.jau.lang.NioUtil; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ int8 read/write access w/ semantics + * as well as with aligned and unaligned access. + * <ul> + * <li>{@link Bitstream#readInt8(boolean, boolean)}</li> + * <li>{@link Bitstream#writeInt8(boolean, boolean, byte)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream02 extends SingletonJunitCase { + + @Test + public void test01Int8BitsAligned() throws IOException { + test01Int8BitsAlignedImpl((byte)0); + test01Int8BitsAlignedImpl((byte)1); + test01Int8BitsAlignedImpl((byte)7); + test01Int8BitsAlignedImpl(Byte.MIN_VALUE); + test01Int8BitsAlignedImpl(Byte.MAX_VALUE); + test01Int8BitsAlignedImpl((byte)0xff); + } + void test01Int8BitsAlignedImpl(final byte val8) throws IOException { + // Test with buffer defined value + final ByteBuffer bb = ByteBuffer.allocate(NioUtil.SIZEOF_BYTE); + System.err.println("XXX Test01Int8BitsAligned: value "+val8+", "+toHexBinaryString(val8, 8)); + bb.put(0, val8); + + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, false /* outputMode */); + { + final byte r8 = (byte) bs.readUInt8(); + System.err.println("Read8.1 "+r8+", "+toHexBinaryString(r8, 8)); + Assert.assertEquals(val8, r8); + } + + // Test with written bitstream value + bs.setStream(bs.getSubStream(), true /* outputMode */); + bs.writeInt8(val8); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + { + final byte r8 = (byte) bs.readUInt8(); + System.err.println("Read8.2 "+r8+", "+toHexBinaryString(r8, 8)); + Assert.assertEquals(val8, r8); + } + } + + @Test + public void test02Int8BitsUnaligned() throws IOException { + test02Int8BitsUnalignedImpl(0); + test02Int8BitsUnalignedImpl(1); + test02Int8BitsUnalignedImpl(7); + test02Int8BitsUnalignedImpl(8); + test02Int8BitsUnalignedImpl(15); + test02Int8BitsUnalignedImpl(24); + test02Int8BitsUnalignedImpl(25); + } + void test02Int8BitsUnalignedImpl(final int preBits) throws IOException { + test02Int8BitsUnalignedImpl(preBits, (byte)0); + test02Int8BitsUnalignedImpl(preBits, (byte)1); + test02Int8BitsUnalignedImpl(preBits, (byte)7); + test02Int8BitsUnalignedImpl(preBits, Byte.MIN_VALUE); + test02Int8BitsUnalignedImpl(preBits, Byte.MAX_VALUE); + test02Int8BitsUnalignedImpl(preBits, (byte)0xff); + } + void test02Int8BitsUnalignedImpl(final int preBits, final byte val8) throws IOException { + final int preBytes = ( preBits + 7 ) >>> 3; + final int byteCount = preBytes + NioUtil.SIZEOF_BYTE; + final ByteBuffer bb = ByteBuffer.allocate(byteCount); + System.err.println("XXX Test02Int8BitsUnaligned: preBits "+preBits+", value "+val8+", "+toHexBinaryString(val8, 8)); + + // Test with written bitstream value + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, true /* outputMode */); + bs.writeBits31(preBits, 0); + bs.writeInt8(val8); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + + final int rPre = (short) bs.readBits31(preBits); + final byte r8 = (byte) bs.readUInt8(); + System.err.println("ReadPre "+rPre+", "+toBinaryString(rPre, preBits)); + System.err.println("Read8 "+r8+", "+toHexBinaryString(r8, 8)); + Assert.assertEquals(val8, r8); + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestBitstream02.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/util/TestBitstream03.java b/test/java/org/jau/util/TestBitstream03.java new file mode 100644 index 0000000..e14d2de --- /dev/null +++ b/test/java/org/jau/util/TestBitstream03.java @@ -0,0 +1,156 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import static org.jau.util.BitDemoData.dumpData; +import static org.jau.util.BitDemoData.toBinaryString; +import static org.jau.util.BitDemoData.toHexBinaryString; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.jau.io.Bitstream; +import org.jau.junit.util.SingletonJunitCase; +import org.jau.lang.NioUtil; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ int16 read/write access w/ semantics + * as well as with aligned and unaligned access. + * <ul> + * <li>{@link Bitstream#readUInt16(boolean)}</li> + * <li>{@link Bitstream#writeInt16(boolean, short)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream03 extends SingletonJunitCase { + + @Test + public void test01Int16BitsAligned() throws IOException { + test01Int16BitsImpl(null); + test01Int16BitsImpl(ByteOrder.BIG_ENDIAN); + test01Int16BitsImpl(ByteOrder.LITTLE_ENDIAN); + } + void test01Int16BitsImpl(final ByteOrder byteOrder) throws IOException { + test01Int16BitsAlignedImpl(byteOrder, (short)0); + test01Int16BitsAlignedImpl(byteOrder, (short)1); + test01Int16BitsAlignedImpl(byteOrder, (short)7); + test01Int16BitsAlignedImpl(byteOrder, (short)0x0fff); + test01Int16BitsAlignedImpl(byteOrder, Short.MIN_VALUE); + test01Int16BitsAlignedImpl(byteOrder, Short.MAX_VALUE); + test01Int16BitsAlignedImpl(byteOrder, (short)0xffff); + } + void test01Int16BitsAlignedImpl(final ByteOrder byteOrder, final short val16) throws IOException { + // Test with buffer defined value + final ByteBuffer bb = ByteBuffer.allocate(NioUtil.SIZEOF_SHORT); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + System.err.println("XXX Test01Int16BitsAligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), value "+val16+", "+toHexBinaryString(val16, 16)); + bb.putShort(0, val16); + dumpData("TestData.1: ", bb, 0, 2); + + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, false /* outputMode */); + { + final short r16 = (short) bs.readUInt16(bigEndian); + System.err.println("Read16.1 "+r16+", "+toHexBinaryString(r16, 16)); + Assert.assertEquals(val16, r16); + } + + // Test with written bitstream value + bs.setStream(bs.getSubStream(), true /* outputMode */); + bs.writeInt16(bigEndian, val16); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("TestData.2: ", bb, 0, 2); + { + final short r16 = (short) bs.readUInt16(bigEndian); + System.err.println("Read16.2 "+r16+", "+toHexBinaryString(r16, 16)); + Assert.assertEquals(val16, r16); + } + } + + @Test + public void test02Int16BitsUnaligned() throws IOException { + test02Int16BitsUnalignedImpl(null); + test02Int16BitsUnalignedImpl(ByteOrder.BIG_ENDIAN); + test02Int16BitsUnalignedImpl(ByteOrder.LITTLE_ENDIAN); + } + void test02Int16BitsUnalignedImpl(final ByteOrder byteOrder) throws IOException { + test02Int16BitsUnalignedImpl(byteOrder, 0); + test02Int16BitsUnalignedImpl(byteOrder, 1); + test02Int16BitsUnalignedImpl(byteOrder, 7); + test02Int16BitsUnalignedImpl(byteOrder, 8); + test02Int16BitsUnalignedImpl(byteOrder, 15); + test02Int16BitsUnalignedImpl(byteOrder, 24); + test02Int16BitsUnalignedImpl(byteOrder, 25); + } + void test02Int16BitsUnalignedImpl(final ByteOrder byteOrder, final int preBits) throws IOException { + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)0); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)1); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)7); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)0x0fff); + test02Int16BitsUnalignedImpl(byteOrder, preBits, Short.MIN_VALUE); + test02Int16BitsUnalignedImpl(byteOrder, preBits, Short.MAX_VALUE); + test02Int16BitsUnalignedImpl(byteOrder, preBits, (short)0xffff); + } + void test02Int16BitsUnalignedImpl(final ByteOrder byteOrder, final int preBits, final short val16) throws IOException { + final int preBytes = ( preBits + 7 ) >>> 3; + final int byteCount = preBytes + NioUtil.SIZEOF_SHORT; + final ByteBuffer bb = ByteBuffer.allocate(byteCount); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + System.err.println("XXX Test02Int16BitsUnaligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), preBits "+preBits+", value "+val16+", "+toHexBinaryString(val16, 16)); + + // Test with written bitstream value + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, true /* outputMode */); + bs.writeBits31(preBits, 0); + bs.writeInt16(bigEndian, val16); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("TestData.1: ", bb, 0, byteCount); + + final int rPre = (short) bs.readBits31(preBits); + final short r16 = (short) bs.readUInt16(bigEndian); + System.err.println("ReadPre "+rPre+", "+toBinaryString(rPre, preBits)); + System.err.println("Read16 "+r16+", "+toHexBinaryString(r16, 16)); + Assert.assertEquals(val16, r16); + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestBitstream03.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/util/TestBitstream04.java b/test/java/org/jau/util/TestBitstream04.java new file mode 100644 index 0000000..e6f130f --- /dev/null +++ b/test/java/org/jau/util/TestBitstream04.java @@ -0,0 +1,181 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2014 Gothel Software e.K. + * Copyright (c) 2014 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util; + +import static org.jau.util.BitDemoData.dumpData; +import static org.jau.util.BitDemoData.toBinaryString; +import static org.jau.util.BitDemoData.toHexBinaryString; +import static org.jau.util.BitDemoData.toHexString; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.jau.io.Bitstream; +import org.jau.junit.util.SingletonJunitCase; +import org.jau.lang.NioUtil; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * Test {@link Bitstream} w/ int32 read/write access w/ semantics + * as well as with aligned and unaligned access. + * <ul> + * <li>{@link Bitstream#readUInt32(boolean)}</li> + * <li>{@link Bitstream#writeInt32(boolean, int)}</li> + * </ul> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestBitstream04 extends SingletonJunitCase { + + @Test + public void test01Int32BitsAligned() throws IOException { + test01Int32BitsImpl(null); + test01Int32BitsImpl(ByteOrder.BIG_ENDIAN); + test01Int32BitsImpl(ByteOrder.LITTLE_ENDIAN); + } + void test01Int32BitsImpl(final ByteOrder byteOrder) throws IOException { + test01Int32BitsAlignedImpl(byteOrder, 0, 0); + test01Int32BitsAlignedImpl(byteOrder, 1, 1); + test01Int32BitsAlignedImpl(byteOrder, -1, -1); + test01Int32BitsAlignedImpl(byteOrder, 7, 7); + test01Int32BitsAlignedImpl(byteOrder, 0x0fffffff, 0x0fffffff); + test01Int32BitsAlignedImpl(byteOrder, Integer.MIN_VALUE, -1); + test01Int32BitsAlignedImpl(byteOrder, Integer.MAX_VALUE, Integer.MAX_VALUE); + test01Int32BitsAlignedImpl(byteOrder, 0xffffffff, -1); + } + void test01Int32BitsAlignedImpl(final ByteOrder byteOrder, final int val32, final int expUInt32Int) throws IOException { + // Test with buffer defined value + final ByteBuffer bb = ByteBuffer.allocate(NioUtil.SIZEOF_INT); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + final String val32_hs = toHexString(val32); + System.err.println("XXX Test01Int32BitsAligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), value "+val32+", "+toHexBinaryString(val32, 32)); + System.err.println("XXX Test01Int32BitsAligned: "+val32+", "+val32_hs); + + bb.putInt(0, val32); + dumpData("TestData.1: ", bb, 0, 4); + + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, false /* outputMode */); + { + final long uint32_l = bs.readUInt32(bigEndian); + final int int32_l = (int)uint32_l; + final String uint32_l_hs = toHexString(uint32_l); + final int uint32_i = Bitstream.uint32LongToInt(uint32_l); + System.err.printf("Read32.1 uint32_l %012d, %10s; int32_l %012d %10s; uint32_i %012d %10s%n", + uint32_l, uint32_l_hs, int32_l, toHexString(int32_l), uint32_i, toHexString(uint32_i)); + Assert.assertEquals(val32_hs, uint32_l_hs); + Assert.assertEquals(val32, int32_l); + Assert.assertEquals(expUInt32Int, uint32_i); + } + + // Test with written bitstream value + bs.setStream(bs.getSubStream(), true /* outputMode */); + bs.writeInt32(bigEndian, val32); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + dumpData("TestData.2: ", bb, 0, 4); + { + final long uint32_l = bs.readUInt32(bigEndian); + final int int32_l = (int)uint32_l; + final String uint32_l_hs = toHexString(uint32_l); + final int uint32_i = Bitstream.uint32LongToInt(uint32_l); + System.err.printf("Read32.2 uint32_l %012d, %10s; int32_l %012d %10s; uint32_i %012d %10s%n", + uint32_l, uint32_l_hs, int32_l, toHexString(int32_l), uint32_i, toHexString(uint32_i)); + Assert.assertEquals(val32_hs, uint32_l_hs); + Assert.assertEquals(val32, int32_l); + Assert.assertEquals(expUInt32Int, uint32_i); + } + } + + @Test + public void test02Int32BitsUnaligned() throws IOException { + test02Int32BitsUnalignedImpl(null); + test02Int32BitsUnalignedImpl(ByteOrder.BIG_ENDIAN); + test02Int32BitsUnalignedImpl(ByteOrder.LITTLE_ENDIAN); + } + void test02Int32BitsUnalignedImpl(final ByteOrder byteOrder) throws IOException { + test02Int32BitsUnalignedImpl(byteOrder, 0); + test02Int32BitsUnalignedImpl(byteOrder, 1); + test02Int32BitsUnalignedImpl(byteOrder, 7); + test02Int32BitsUnalignedImpl(byteOrder, 8); + test02Int32BitsUnalignedImpl(byteOrder, 15); + test02Int32BitsUnalignedImpl(byteOrder, 24); + test02Int32BitsUnalignedImpl(byteOrder, 25); + } + void test02Int32BitsUnalignedImpl(final ByteOrder byteOrder, final int preBits) throws IOException { + test02Int32BitsUnalignedImpl(byteOrder, preBits, 0, 0); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 1, 1); + test02Int32BitsUnalignedImpl(byteOrder, preBits, -1, -1); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 7, 7); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 0x0fffffff, 0x0fffffff); + test02Int32BitsUnalignedImpl(byteOrder, preBits, Integer.MIN_VALUE, -1); + test02Int32BitsUnalignedImpl(byteOrder, preBits, Integer.MAX_VALUE, Integer.MAX_VALUE); + test02Int32BitsUnalignedImpl(byteOrder, preBits, 0xffffffff, -1); + } + void test02Int32BitsUnalignedImpl(final ByteOrder byteOrder, final int preBits, final int val32, final int expUInt32Int) throws IOException { + final int preBytes = ( preBits + 7 ) >>> 3; + final int byteCount = preBytes + NioUtil.SIZEOF_INT; + final ByteBuffer bb = ByteBuffer.allocate(byteCount); + if( null != byteOrder ) { + bb.order(byteOrder); + } + final boolean bigEndian = ByteOrder.BIG_ENDIAN == bb.order(); + final String val32_hs = toHexString(val32); + System.err.println("XXX Test02Int32BitsUnaligned: byteOrder "+byteOrder+" (bigEndian "+bigEndian+"), preBits "+preBits+", value "+val32+", "+toHexBinaryString(val32, 32)); + System.err.println("XXX Test02Int32BitsUnaligned: "+val32+", "+val32_hs); + + // Test with written bitstream value + final Bitstream.ByteBufferStream bbs = new Bitstream.ByteBufferStream(bb); + final Bitstream<ByteBuffer> bs = new Bitstream<ByteBuffer>(bbs, true /* outputMode */); + bs.writeBits31(preBits, 0); + bs.writeInt32(bigEndian, val32); + bs.setStream(bs.getSubStream(), false /* outputMode */); // switch to input-mode, implies flush() + + final int rPre = bs.readBits31(preBits); + final long uint32_l = bs.readUInt32(bigEndian); + final int int32_l = (int)uint32_l; + final String uint32_l_hs = toHexString(uint32_l); + final int uint32_i = Bitstream.uint32LongToInt(uint32_l); + System.err.println("ReadPre "+rPre+", "+toBinaryString(rPre, preBits)); + System.err.printf("Read32 uint32_l %012d, %10s; int32_l %012d %10s; uint32_i %012d %10s%n", + uint32_l, uint32_l_hs, int32_l, toHexString(int32_l), uint32_i, toHexString(uint32_i)); + Assert.assertEquals(val32_hs, uint32_l_hs); + Assert.assertEquals(val32, int32_l); + Assert.assertEquals(expUInt32Int, uint32_i); + } + + public static void main(final String args[]) throws IOException { + final String tstname = TestBitstream04.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + +} diff --git a/test/java/org/jau/util/parallel/locks/Lock.java b/test/java/org/jau/util/parallel/locks/Lock.java new file mode 100644 index 0000000..a2798ab --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/Lock.java @@ -0,0 +1,82 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel.locks; + +import org.jau.sys.Debug; + +/** + * Specifying a thread blocking lock implementation + */ +public interface Lock { + + /** Enable via the property <code>jogamp.debug.Lock</code> */ + public static final boolean DEBUG = Debug.debug("Lock"); + + /** Enable via the property <code>jogamp.debug.Lock.TraceLock</code> */ + public static final boolean TRACE_LOCK = Debug.isPropertyDefined("jogamp.debug.Lock.TraceLock", true); + + /** The default {@link #TIMEOUT} value, of {@value} ms */ + public static final long DEFAULT_TIMEOUT = 5000; // 5s default timeout + + /** + * The <code>TIMEOUT</code> for {@link #lock()} in ms, + * defaults to {@link #DEFAULT_TIMEOUT}. + * <p> + * It can be overridden via the system property <code>jogamp.common.utils.locks.Lock.timeout</code>. + * </p> + */ + public static final long TIMEOUT = Debug.getLongProperty("jogamp.common.utils.locks.Lock.timeout", true, DEFAULT_TIMEOUT); + + /** + * Blocking until the lock is acquired by this Thread or {@link #TIMEOUT} is reached. + * + * @throws RuntimeException in case of {@link #TIMEOUT} + */ + void lock() throws RuntimeException; + + /** + * Blocking until the lock is acquired by this Thread or <code>maxwait</code> in ms is reached. + * + * @param timeout Maximum time in ms to wait to acquire the lock. If this value is zero, + * the call returns immediately either without being able + * to acquire the lock, or with acquiring the lock directly while ignoring any scheduling order. + * @return true if the lock has been acquired within <code>maxwait</code>, otherwise false + * + * @throws InterruptedException + */ + boolean tryLock(long timeout) throws InterruptedException; + + /** + * Release the lock. + * + * @throws RuntimeException in case the lock is not acquired by this thread. + */ + void unlock() throws RuntimeException; + + /** Query if locked */ + boolean isLocked(); +} diff --git a/test/java/org/jau/util/parallel/locks/LockFactory.java b/test/java/org/jau/util/parallel/locks/LockFactory.java new file mode 100644 index 0000000..7c3d491 --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/LockFactory.java @@ -0,0 +1,64 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.util.parallel.locks; + +import jau.util.parallel.locks.RecursiveLockImpl01CompleteFair; +import jau.util.parallel.locks.RecursiveLockImpl01Unfairish; +import jau.util.parallel.locks.RecursiveThreadGroupLockImpl01Unfairish; + +public class LockFactory { + + public enum ImplType { + Int01(0), Int02ThreadGroup(2); + + public final int id; + + ImplType(final int id){ + this.id = id; + } + } + + /** default is ImplType.Int01, unfair'ish (fastest w/ least deviation) */ + public static RecursiveLock createRecursiveLock() { + return new RecursiveLockImpl01Unfairish(); + } + + /** default is ImplType.Int02ThreadGroup, unfair'ish (fastest w/ least deviation) */ + public static RecursiveThreadGroupLock createRecursiveThreadGroupLock() { + return new RecursiveThreadGroupLockImpl01Unfairish(); + } + + public static RecursiveLock createRecursiveLock(final ImplType t, final boolean fair) { + switch(t) { + case Int01: + return fair ? new RecursiveLockImpl01CompleteFair() : new RecursiveLockImpl01Unfairish(); + case Int02ThreadGroup: + return new RecursiveThreadGroupLockImpl01Unfairish(); + } + throw new InternalError("XXX"); + } + +} diff --git a/test/java/org/jau/util/parallel/locks/RecursiveLock.java b/test/java/org/jau/util/parallel/locks/RecursiveLock.java new file mode 100644 index 0000000..b1be1f4 --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/RecursiveLock.java @@ -0,0 +1,45 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel.locks; + +/** + * Reentrance capable locking toolkit. + */ +public interface RecursiveLock extends ThreadLock { + /** + * Return the number of locks issued to this lock by the same thread. + * <ul> + * <li>A hold count of 0 identifies this lock as unlocked.</li> + * <li>A hold count of 1 identifies this lock as locked.</li> + * <li>A hold count of > 1 identifies this lock as recursively lock.</li> + * </ul> + */ + int getHoldCount(); + + int getQueueLength(); +} + diff --git a/test/java/org/jau/util/parallel/locks/RecursiveThreadGroupLock.java b/test/java/org/jau/util/parallel/locks/RecursiveThreadGroupLock.java new file mode 100644 index 0000000..238c188 --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/RecursiveThreadGroupLock.java @@ -0,0 +1,137 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package org.jau.util.parallel.locks; + +/** + * Reentrance capable locking toolkit, supporting multiple threads as owner. + * <p> + * See use case description at {@link #addOwner(Thread)}. + * </p> + */ +public interface RecursiveThreadGroupLock extends RecursiveLock { + /** + * Returns true if the current thread is the original lock owner, ie. + * successfully claimed this lock the first time, ie. {@link #getHoldCount()} == 1. + */ + boolean isOriginalOwner(); + + /** + * Returns true if the passed thread is the original lock owner, ie. + * successfully claimed this lock the first time, ie. {@link #getHoldCount()} == 1. + */ + boolean isOriginalOwner(Thread thread); + + /** + * Add a thread to the list of additional lock owners, which enables them to recursively claim this lock. + * <p> + * The caller must hold this lock and be the original lock owner, see {@link #isOriginalOwner()}. + * </p> + * <p> + * If the original owner releases this lock via {@link #unlock()} + * all additional lock owners are released as well. + * This ensures consistency of spawn off additional lock owner threads and it's release. + * </p> + * Use case: + * <pre> + * Thread2 thread2 = new Thread2(); + * + * Thread1 { + * + * // Claim this lock and become the original lock owner. + * lock.lock(); + * + * try { + * + * // Allow Thread2 to claim the lock, ie. make thread2 an additional lock owner + * addOwner(thread2); + * + * // Start thread2 + * thread2.start(); + * + * // Wait until thread2 has finished requiring this lock, but keep thread2 running + * while(!thread2.waitForResult()) sleep(); + * + * // Optional: Only if sure that this thread doesn't hold the lock anymore, + * // otherwise just release the lock via unlock(). + * removeOwner(thread2); + * + * } finally { + * + * // Release this lock and remove all additional lock owners. + * // Implicit wait until thread2 gets off the lock. + * lock.unlock(); + * + * } + * + * }.start(); + * </pre> + * + * @param t the thread to be added to the list of additional owning threads + * @throws RuntimeException if the current thread does not hold the lock. + * @throws IllegalArgumentException if the passed thread is the lock owner or already added. + * + * @see #removeOwner(Thread) + * @see #unlock() + * @see #lock() + */ + void addOwner(Thread t) throws RuntimeException, IllegalArgumentException; + + /** + * Remove a thread from the list of additional lock owner threads. + * <p> + * The caller must hold this lock and be the original lock owner, see {@link #isOriginalOwner()}. + * </p> + * <p> + * Only use this method if sure that the thread doesn't hold the lock anymore. + * </p> + * + * @param t the thread to be removed from the list of additional owning threads + * @throws RuntimeException if the current thread does not hold the lock. + * @throws IllegalArgumentException if the passed thread is not added by {@link #addOwner(Thread)} + */ + void removeOwner(Thread t) throws RuntimeException, IllegalArgumentException; + + /** + * <p> + * Wait's until all additional owners released this lock before releasing it. + * </p> + * + * {@inheritDoc} + */ + @Override + void unlock() throws RuntimeException; + + /** + * <p> + * Wait's until all additional owners released this lock before releasing it. + * </p> + * + * {@inheritDoc} + */ + @Override + void unlock(Runnable taskAfterUnlockBeforeNotify); + +} diff --git a/test/java/org/jau/util/parallel/locks/SingletonInstance.java b/test/java/org/jau/util/parallel/locks/SingletonInstance.java new file mode 100644 index 0000000..495d68c --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/SingletonInstance.java @@ -0,0 +1,149 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2011 Gothel Software e.K. + * Copyright (c) 2011 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel.locks; + +import java.io.File; + +import jau.util.parallel.locks.SingletonInstanceFileLock; +import jau.util.parallel.locks.SingletonInstanceServerSocket; + +public abstract class SingletonInstance implements Lock { + + protected static final boolean DEBUG = true; + + public static SingletonInstance createFileLock(final long poll_ms, final String lockFileBasename) { + return new SingletonInstanceFileLock(poll_ms, lockFileBasename); + } + + public static SingletonInstance createFileLock(final long poll_ms, final File lockFile) { + return new SingletonInstanceFileLock(poll_ms, lockFile); + } + + /** + * A user shall use <b>ephemeral ports</b>: + * <ul> + * <li>IANA suggests 49152 to 65535 as "dynamic and/or private ports".</li> + * <li>Many GNU/Linux kernels use 32768 to 61000.</li> + * <li>FreeBSD >= 4.6 uses the IANA port range.</li> + * <li>FreeBSD < 4.6 and BSD use ports 1024 through 4999.</li> + * <li>Microsoft Windows operating systems through Server 2003 use the range 1025 to 5000</li> + * <li>Windows Vista, Windows 7, and Server 2008 use the IANA range.</li> + * </ul> + * @param pollPeriod + * @param portNumber to be used for this single instance server socket. + */ + public static SingletonInstance createServerSocket(final long poll_ms, final int portNumber) { + return new SingletonInstanceServerSocket(poll_ms, portNumber); + } + + protected SingletonInstance(final long poll_ms) { + this.poll_ms = Math.max(10, poll_ms); + } + + public final long getPollPeriod() { return poll_ms; } + public abstract String getName(); + @Override + public final String toString() { return getName(); } + + @Override + public synchronized void lock() throws RuntimeException { + try { + do { + if(tryLock(TIMEOUT)) { + return; + } + } while ( true ) ; + } catch ( final RuntimeException ie ) { + throw new RuntimeException(ie); + } + } + + @Override + public synchronized boolean tryLock(long maxwait) throws RuntimeException { + if(locked) { + return true; + } + final long t0 = System.currentTimeMillis(); + int i=0; + try { + do { + final long t1 = System.currentTimeMillis(); + locked = tryLockImpl(); + if(locked) { + if( DEBUG ) { + final long t2 = System.currentTimeMillis(); + System.err.println(infoPrefix(t2)+" +++ "+getName()+" - Locked within "+(t2-t0)+" ms, "+(i+1)+" attempts"); + } + return true; + } + if( DEBUG && 0==i ) { + System.err.println(infoPrefix(System.currentTimeMillis())+" III "+getName()+" - Wait for lock"); + } + Thread.sleep(poll_ms); + maxwait -= System.currentTimeMillis()-t1; + i++; + } while ( 0 < maxwait ) ; + } catch ( final InterruptedException ie ) { + final long t2 = System.currentTimeMillis(); + throw new RuntimeException(infoPrefix(t2)+" EEE (1) "+getName()+" - couldn't get lock within "+(t2-t0)+" ms, "+i+" attempts", ie); + } + if( DEBUG ) { + final long t2 = System.currentTimeMillis(); + System.err.println(infoPrefix(t2)+" +++ EEE (2) "+getName()+" - couldn't get lock within "+(t2-t0)+" ms, "+i+" attempts"); + } + return false; + } + protected abstract boolean tryLockImpl(); + + @Override + public void unlock() throws RuntimeException { + final long t0 = System.currentTimeMillis(); + if(locked) { + locked = !unlockImpl(); + if( DEBUG ) { + final long t2 = System.currentTimeMillis(); + System.err.println(infoPrefix(t2)+" --- "+getName()+" - Unlock "+ ( locked ? "failed" : "ok" ) + " within "+(t2-t0)+" ms"); + } + } + } + protected abstract boolean unlockImpl(); + + @Override + public synchronized boolean isLocked() { + return locked; + } + + protected String infoPrefix(final long currentMillis) { + return "SLOCK [T "+Thread.currentThread().getName()+" @ "+currentMillis+" ms"; + } + protected String infoPrefix() { + return infoPrefix(System.currentTimeMillis()); + } + + private final long poll_ms; + private boolean locked = false; +} diff --git a/test/java/org/jau/util/parallel/locks/ThreadLock.java b/test/java/org/jau/util/parallel/locks/ThreadLock.java new file mode 100644 index 0000000..6e6de7e --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/ThreadLock.java @@ -0,0 +1,57 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2021 Gothel Software e.K. + * Copyright (c) 2010 Gothel Software e.K. + * Copyright (c) 2010 JogAmp Community. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package org.jau.util.parallel.locks; + +/** + * Extending the {@link Lock} features with convenient functionality. + */ +public interface ThreadLock extends Lock { + + /** Query whether the lock is hold by the a thread other than the current thread. */ + boolean isLockedByOtherThread(); + + /** Query whether the lock is hold by the given thread. */ + boolean isOwner(Thread thread); + + /** + * @return the Thread owning this lock if locked, otherwise null + */ + Thread getOwner(); + + /** + * @throws RuntimeException if current thread does not hold the lock + */ + void validateLocked() throws RuntimeException; + + /** + * Execute the {@link Runnable Runnable taskAfterUnlockBeforeNotify} while holding the exclusive lock. + * <p> + * Then release the lock. + * </p> + */ + void unlock(Runnable taskAfterUnlockBeforeNotify); +} diff --git a/test/java/org/jau/util/parallel/locks/package.html b/test/java/org/jau/util/parallel/locks/package.html new file mode 100644 index 0000000..c50570d --- /dev/null +++ b/test/java/org/jau/util/parallel/locks/package.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <title>Jaulib Parallel & Concurrency Utilities</title> +</head> + <body> +<h4><i>Jaulib</i> Parallel & Concurrency Utilities</h4> +</body> +</html> |