summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2021-01-25 01:28:36 +0100
committerSven Gothel <[email protected]>2021-01-25 01:28:36 +0100
commitb371af6d83e0fb94cba02ec6c2837bf58c2eea39 (patch)
tree48999c7e269409ebe389e5d5e72ca141d8b9eea7
parent1ba932716f2d382af56bf8ea69a57666e2c835d6 (diff)
Java import and modularization: jaulib_base, jaulib_jni, jaulib_net, jaulib_pkg (WIP)
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt64
-rw-r--r--Doxyfile.java.in2365
-rw-r--r--java/jni/jau/BasicTypes.cxx61
-rw-r--r--java/org/jau/sys/PlatformToolkit.java587
-rw-r--r--java_base/CMakeLists.txt32
-rw-r--r--java_base/jau/info.txt3
-rw-r--r--java_base/jau/util/Int32ArrayBitfield.java (renamed from java/jau/util/Int32ArrayBitfield.java)0
-rw-r--r--java_base/jau/util/Int32Bitfield.java (renamed from java/jau/util/Int32Bitfield.java)0
-rw-r--r--java_base/jau/util/SyncedBitfield.java (renamed from java/jau/util/SyncedBitfield.java)0
-rw-r--r--java_base/manifest.txt.in30
-rw-r--r--java_base/org/jau/io/Bitstream.java (renamed from java/org/jau/io/Bitstream.java)0
-rw-r--r--java_base/org/jau/io/IOUtil.java1358
-rw-r--r--java_base/org/jau/io/MappedByteBufferInputStream.java927
-rw-r--r--java_base/org/jau/io/MappedByteBufferOutputStream.java349
-rw-r--r--java_base/org/jau/lang/ExceptionUtils.java (renamed from java/org/jau/lang/ExceptionUtils.java)0
-rw-r--r--java_base/org/jau/lang/InterruptSource.java155
-rw-r--r--java_base/org/jau/lang/InterruptedRuntimeException.java76
-rw-r--r--java_base/org/jau/lang/NioUtil.java85
-rw-r--r--java_base/org/jau/lang/ReflectionUtil.java228
-rw-r--r--java_base/org/jau/lang/SourcedInterruptedException.java163
-rw-r--r--java_base/org/jau/lang/StructAccessor.java302
-rw-r--r--java_base/org/jau/lang/UnsafeUtil.java (renamed from java/org/jau/lang/UnsafeUtil.java)12
-rw-r--r--java_base/org/jau/sec/SHASum.java292
-rw-r--r--java_base/org/jau/sec/SecurityUtil.java184
-rw-r--r--java_base/org/jau/sys/AndroidUtil.java90
-rw-r--r--java_base/org/jau/sys/AndroidVersion.java178
-rw-r--r--java_base/org/jau/sys/Debug.java (renamed from java/org/jau/sys/Debug.java)10
-rw-r--r--java_base/org/jau/sys/JNILibrary.java492
-rw-r--r--java_base/org/jau/sys/MachineDataInfo.java393
-rw-r--r--java_base/org/jau/sys/PlatformProps.java453
-rw-r--r--java_base/org/jau/sys/PlatformTypes.java247
-rw-r--r--java_base/org/jau/sys/PropertyAccess.java (renamed from java/org/jau/sys/PropertyAccess.java)3
-rw-r--r--java_base/org/jau/sys/elf/Ehdr_p1.java138
-rw-r--r--java_base/org/jau/sys/elf/Ehdr_p2.java194
-rw-r--r--java_base/org/jau/sys/elf/ElfHeaderPart1.java518
-rw-r--r--java_base/org/jau/sys/elf/ElfHeaderPart2.java377
-rw-r--r--java_base/org/jau/sys/elf/IOUtils.java129
-rw-r--r--java_base/org/jau/sys/elf/Section.java49
-rw-r--r--java_base/org/jau/sys/elf/SectionArmAttributes.java352
-rw-r--r--java_base/org/jau/sys/elf/SectionHeader.java283
-rw-r--r--java_base/org/jau/sys/elf/Shdr.java194
-rw-r--r--java_base/org/jau/util/ArrayHashMap.java (renamed from java/org/jau/util/ArrayHashMap.java)2
-rw-r--r--java_base/org/jau/util/ArrayHashSet.java (renamed from java/org/jau/util/ArrayHashSet.java)2
-rw-r--r--java_base/org/jau/util/BasicAlgos.java (renamed from java/org/jau/util/BasicAlgos.java)0
-rw-r--r--java_base/org/jau/util/BasicTypes.java (renamed from java/org/jau/util/BasicTypes.java)2
-rw-r--r--java_base/org/jau/util/BitMath.java (renamed from java/org/jau/util/BitMath.java)0
-rw-r--r--java_base/org/jau/util/Bitfield.java (renamed from java/org/jau/util/Bitfield.java)0
-rw-r--r--java_base/org/jau/util/Function.java47
-rw-r--r--java_base/org/jau/util/Hash32.java (renamed from java/org/jau/util/Hash32.java)0
-rw-r--r--java_base/org/jau/util/Hash64.java (renamed from java/org/jau/util/Hash64.java)0
-rw-r--r--java_base/org/jau/util/IntMath.java (renamed from java/org/jau/util/IntMath.java)0
-rw-r--r--java_base/org/jau/util/LFRingbuffer.java (renamed from java/org/jau/util/LFRingbuffer.java)0
-rw-r--r--java_base/org/jau/util/Ringbuffer.java (renamed from java/org/jau/util/Ringbuffer.java)0
-rw-r--r--java_base/org/jau/util/ValueConv.java (renamed from java/org/jau/util/ValueConv.java)0
-rw-r--r--java_base/org/jau/util/VersionNumber.java283
-rw-r--r--java_base/org/jau/util/VersionNumberString.java88
-rw-r--r--java_base/org/jau/util/parallel/FunctionTask.java218
-rw-r--r--java_base/org/jau/util/parallel/RunnableExecutor.java48
-rw-r--r--java_base/org/jau/util/parallel/RunnableTask.java162
-rw-r--r--java_base/org/jau/util/parallel/TaskBase.java193
-rw-r--r--java_base/org/jau/util/parallel/package.html9
-rw-r--r--java_jni/CMakeLists.txt21
-rw-r--r--java_jni/jau/sys/MachineDataInfoRuntime.java155
-rw-r--r--java_jni/jau/sys/dl/BionicDynamicLinker32bitImpl.java61
-rw-r--r--java_jni/jau/sys/dl/BionicDynamicLinker64BitImpl.java61
-rw-r--r--java_jni/jau/sys/dl/DynamicLinkerImpl.java215
-rw-r--r--java_jni/jau/sys/dl/MacOSXDynamicLinkerImpl.java57
-rw-r--r--java_jni/jau/sys/dl/PosixDynamicLinkerImpl.java52
-rw-r--r--java_jni/jau/sys/dl/UnixDynamicLinkerImpl.java64
-rw-r--r--java_jni/jau/sys/dl/WindowsDynamicLinkerImpl.java92
-rw-r--r--java_jni/jni/CMakeLists.txt42
-rw-r--r--java_jni/jni/helper_jni.cxx (renamed from java/jni/helper_jni.cxx)0
-rw-r--r--java_jni/jni/jau/Clock.cxx (renamed from java/jni/jau/Environment.cxx)8
-rw-r--r--java_jni/jni/jau/JVM_JNI8.cxx46
-rw-r--r--java_jni/jni/jau/MachineDataInfoRuntime.cxx211
-rw-r--r--java_jni/jni/jau/UnixDynamicLinkerImpl_JNI.cxx138
-rw-r--r--java_jni/jni/jau/WindowsDynamicLinkerImpl_JNI.cxx107
-rw-r--r--java_jni/jni/jni_mem.cxx (renamed from java/jni/jni_mem.cxx)0
-rw-r--r--java_jni/manifest.txt.in30
-rw-r--r--java_jni/org/jau/sys/Clock.java (renamed from java/org/jau/sys/Environment.java)2
-rw-r--r--java_jni/org/jau/sys/dl/DynamicLibraryBundle.java422
-rw-r--r--java_jni/org/jau/sys/dl/DynamicLibraryBundleInfo.java128
-rw-r--r--java_jni/org/jau/sys/dl/DynamicLinker.java113
-rw-r--r--java_jni/org/jau/sys/dl/DynamicLookupHelper.java57
-rw-r--r--java_jni/org/jau/sys/dl/NativeLibrary.java298
-rw-r--r--java_net/CMakeLists.txt20
-rw-r--r--java_net/manifest.txt.in27
-rw-r--r--java_net/org/jau/net/AssetURLConnection.java123
-rw-r--r--java_net/org/jau/net/AssetURLContext.java280
-rw-r--r--java_net/org/jau/net/AssetURLStreamHandler.java60
-rw-r--r--java_net/org/jau/net/GenericURLStreamHandlerFactory.java92
-rw-r--r--java_net/org/jau/net/PiggybackURLConnection.java109
-rw-r--r--java_net/org/jau/net/PiggybackURLContext.java43
-rw-r--r--java_net/org/jau/net/Uri.java2546
-rw-r--r--java_net/org/jau/net/UriQueryProps.java136
-rw-r--r--java_net/org/jau/net/asset/Handler.java63
-rw-r--r--java_pkg/CMakeLists.txt19
-rw-r--r--java_pkg/jni/CMakeLists.txt33
-rw-r--r--java_pkg/jni/jau/JVM_JNI8.cxx46
-rw-r--r--java_pkg/jni/jau/JarUtil.cxx43
-rw-r--r--java_pkg/manifest.txt.in27
-rw-r--r--java_pkg/org/jau/pkg/JNIJarLibrary.java333
-rw-r--r--java_pkg/org/jau/pkg/JarUtil.java740
-rw-r--r--java_pkg/org/jau/pkg/TempJarSHASum.java70
-rw-r--r--java_pkg/org/jau/pkg/cache/TempCacheReg.java39
-rw-r--r--java_pkg/org/jau/pkg/cache/TempFileCache.java562
-rw-r--r--java_pkg/org/jau/pkg/cache/TempJarCache.java502
-rw-r--r--scripts/build.sh14
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--test/java/CMakeLists.txt45
-rw-r--r--test/java/jau-test/info.txt3
-rw-r--r--test/java/jau/info.txt3
-rw-r--r--test/java/jau/util/parallel/locks/LockDebugUtil.java78
-rw-r--r--test/java/jau/util/parallel/locks/RecursiveLockImpl01CompleteFair.java319
-rw-r--r--test/java/jau/util/parallel/locks/RecursiveLockImpl01Unfairish.java318
-rw-r--r--test/java/jau/util/parallel/locks/RecursiveThreadGroupLockImpl01Unfairish.java233
-rw-r--r--test/java/jau/util/parallel/locks/SingletonInstanceFileLock.java134
-rw-r--r--test/java/jau/util/parallel/locks/SingletonInstanceServerSocket.java290
-rw-r--r--test/java/manifest.txt.in24
-rw-r--r--test/java/org/jau/junit/util/JunitTracer.java107
-rw-r--r--test/java/org/jau/junit/util/MiscUtils.java110
-rw-r--r--test/java/org/jau/junit/util/SingletonJunitCase.java88
-rw-r--r--test/java/org/jau/net/AssetURLConnectionBase.java64
-rw-r--r--test/java/org/jau/net/TestAssetURLConnectionRegistered.java89
-rw-r--r--test/java/org/jau/net/TestAssetURLConnectionUnregistered.java62
-rw-r--r--test/java/org/jau/net/TestUri01.java458
-rw-r--r--test/java/org/jau/net/TestUri02Composing.java94
-rw-r--r--test/java/org/jau/net/TestUri03Resolving.java426
-rw-r--r--test/java/org/jau/net/TestUri99LaunchOnReservedCharPathBug908.java166
-rw-r--r--test/java/org/jau/net/TestUriQueryProps.java47
-rw-r--r--test/java/org/jau/net/TestUrisWithAssetHandler.java49
-rw-r--r--test/java/org/jau/net/URIDumpUtil.java98
-rw-r--r--test/java/org/jau/net/data/AssetURLConnectionTest.txt3
-rw-r--r--test/java/org/jau/net/data/RelativeData.txt3
-rw-r--r--test/java/org/jau/net/data2/RelativeData2.txt3
-rw-r--r--test/java/org/jau/sys/elf/TestElfReader01.java185
-rw-r--r--test/java/org/jau/util/BitDemoData.java178
-rw-r--r--test/java/org/jau/util/TestBitfield00.java428
-rw-r--r--test/java/org/jau/util/TestBitstream00.java265
-rw-r--r--test/java/org/jau/util/TestBitstream01.java361
-rw-r--r--test/java/org/jau/util/TestBitstream02.java131
-rw-r--r--test/java/org/jau/util/TestBitstream03.java156
-rw-r--r--test/java/org/jau/util/TestBitstream04.java181
-rw-r--r--test/java/org/jau/util/parallel/locks/Lock.java82
-rw-r--r--test/java/org/jau/util/parallel/locks/LockFactory.java64
-rw-r--r--test/java/org/jau/util/parallel/locks/RecursiveLock.java45
-rw-r--r--test/java/org/jau/util/parallel/locks/RecursiveThreadGroupLock.java137
-rw-r--r--test/java/org/jau/util/parallel/locks/SingletonInstance.java149
-rw-r--r--test/java/org/jau/util/parallel/locks/ThreadLock.java57
-rw-r--r--test/java/org/jau/util/parallel/locks/package.html9
151 files changed, 25797 insertions, 682 deletions
diff --git a/.gitignore b/.gitignore
index a46b74b..4a7c48a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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 &gt; 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 &le; {@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 &gt; {@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 &le; than this section size
+ * @throws IOException if read error occurs
+ * @throws IllegalArgumentException if section offset or size mismatch including size &gt; {@link Integer#MAX_VALUE}
+ * @throws IllegalArgumentException if requested read length is &gt; 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 &amp; Concurrency Utilities</title>
+</head>
+ <body>
+<h4><i>Jaulib</i> Parallel &amp; 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>&amp;</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 &gt; 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 &amp; Concurrency Utilities</title>
+</head>
+ <body>
+<h4><i>Jaulib</i> Parallel &amp; Concurrency Utilities</h4>
+</body>
+</html>