aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2022-07-04 22:55:44 +0200
committerSven Gothel <[email protected]>2022-07-04 22:55:44 +0200
commit2ba71f91fd852fd609943a2625be26e1c67c8507 (patch)
treec09e6856074b98b6a9ac100544feb7535ff8ab4a
parentcc241a1d4db040160ccd83ee23cb876787c52d5f (diff)
Complete C++ jau::fs -> java org.jau.fs mapping incl. full test_fileutils01.cpp -> TestFileUtils01.java
-rw-r--r--CHANGES.md3
-rw-r--r--include/jau/jni/helper_jni.hpp26
-rw-r--r--java_jni/jni/jau/jau_fs_FileUtil.cxx292
-rw-r--r--java_jni/org/jau/fs/CopyOptions.java24
-rw-r--r--java_jni/org/jau/fs/DirItem.java95
-rw-r--r--java_jni/org/jau/fs/FMode.java147
-rw-r--r--java_jni/org/jau/fs/FileStats.java385
-rw-r--r--java_jni/org/jau/fs/FileUtil.java175
-rw-r--r--java_jni/org/jau/fs/TraverseEvent.java87
-rw-r--r--java_jni/org/jau/fs/TraverseOptions.java22
-rw-r--r--test/java/jau/test/fs/FileUtilBaseTest.java322
-rw-r--r--test/java/jau/test/fs/TestFileUtils01.java1244
-rw-r--r--test/java/jau/test/nio/TestByteStream01.java1
13 files changed, 2805 insertions, 18 deletions
diff --git a/CHANGES.md b/CHANGES.md
index e10fa6b..e1d895d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -14,7 +14,8 @@
- `copy()` with `copy_options` -> `CopyOptions`
- `remove()` with `traverse_options` -> `TraverseOptions`
- `mount_image()` and `umount()`
- - TODO: Partially copy `test_fileutils01.cpp` and `testsudo_fileutils02.cpp` to java.
+ - Copied `test_fileutils01.cpp` to java, passed.
+ - TODO Copy `testsudo_fileutils02.cpp` to java.
* Have `jau.pkg.PlatformRuntime` load tool library `jaulib` as well, resolving dependencies for self-testing.
* Add java mapping of `jau::io::ByteInStream` for file, URL and feed for general use
- `org.jau.nio.ByteInStream`, `org.jau.nio.ByteInStream_File`, `org.jau.nio.ByteInStream_URL`, `org.jau.nio.ByteInStream_Feed`
diff --git a/include/jau/jni/helper_jni.hpp b/include/jau/jni/helper_jni.hpp
index 7d8b839..65b0db6 100644
--- a/include/jau/jni/helper_jni.hpp
+++ b/include/jau/jni/helper_jni.hpp
@@ -759,6 +759,32 @@ namespace jau::jni {
return result;
}
+ template <typename T, typename U>
+ jobject convert_vector_to_jarraylist(JNIEnv *env, T& array, std::function<jobject(JNIEnv*, const U&)> ctor)
+ {
+ unsigned int array_size = array.size();
+
+ jmethodID arraylist_add;
+ jobject result = get_new_arraylist(env, array_size, &arraylist_add);
+
+ if (array_size == 0)
+ {
+ return result;
+ }
+
+ for (unsigned int i = 0; i < array_size; ++i)
+ {
+ jobject object = ctor(env, array[i] /* const U& */);
+ if (!object)
+ {
+ throw jau::RuntimeException("Cannot create instance of class", E_FILE_LINE);
+ }
+ env->CallBooleanMethod(result, arraylist_add, object);
+ java_exception_check_and_throw(env, E_FILE_LINE);
+ }
+ return result;
+ }
+
/**@}*/
diff --git a/java_jni/jni/jau/jau_fs_FileUtil.cxx b/java_jni/jni/jau/jau_fs_FileUtil.cxx
index 74e1b0c..6cb3eed 100644
--- a/java_jni/jni/jau/jau_fs_FileUtil.cxx
+++ b/java_jni/jni/jau/jau_fs_FileUtil.cxx
@@ -22,7 +22,10 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
+#include "org_jau_fs_FMode.h"
+#include "org_jau_fs_FileStats.h"
#include "org_jau_fs_FileUtil.h"
+#include "org_jau_fs_DirItem.h"
#include <jau/debug.hpp>
@@ -30,6 +33,295 @@
#include "jau/file_util.hpp"
+extern "C" {
+ #include <sys/stat.h>
+}
+
+//
+// FMode
+//
+
+jstring Java_org_jau_fs_FMode_to_1string(JNIEnv *env, jclass cls, jint mask, jboolean show_rwx) {
+ (void)cls;
+ try {
+ const std::string s = jau::fs::to_string(static_cast<jau::fs::fmode_t>(mask), JNI_TRUE == show_rwx);
+ return jau::jni::from_string_to_jstring(env, s);
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+//
+// FileStats
+//
+
+jlong Java_org_jau_fs_FileStats_ctorImpl(JNIEnv *env, jclass clazz, jstring jpath) {
+ (void)clazz;
+ try {
+ if( nullptr == jpath ) {
+ throw jau::IllegalArgumentException("path null", E_FILE_LINE);
+ }
+ std::string path = jau::jni::from_jstring_to_string(env, jpath);
+
+ // new instance
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> ref( new jau::fs::file_stats( path ) );
+
+ return ref.release_to_jlong();
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return (jlong) (intptr_t) nullptr;
+}
+
+jlong Java_org_jau_fs_FileStats_ctorLinkTargetImpl(JNIEnv *env, jclass clazz, jlong nativeInstance) {
+ (void)clazz;
+ try {
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> sref(nativeInstance, true /* throw_on_nullptr */); // hold copy until done
+
+ const std::shared_ptr<jau::fs::file_stats>& lt = sref->link_target();
+ if( nullptr != lt ) {
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> ref( lt );
+ return ref.release_to_jlong();
+ }
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return (jlong) (intptr_t) nullptr;
+}
+
+void Java_org_jau_fs_FileStats_dtorImpl(JNIEnv *env, jclass clazz, jlong nativeInstance) {
+ (void)clazz;
+ try {
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> sref(nativeInstance, false /* throw_on_nullptr */); // hold copy until done
+ if( nullptr != sref.pointer() ) {
+ std::shared_ptr<jau::fs::file_stats>* sref_ptr = jau::jni::castInstance<jau::fs::file_stats>(nativeInstance);
+ delete sref_ptr;
+ }
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+}
+
+jintArray Java_org_jau_fs_FileStats_getInt5FieldsFModeUidGidErrno(JNIEnv *env, jclass clazz, jlong nativeInstance) {
+ (void)clazz;
+ try {
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> sref(nativeInstance, true /* throw_on_nullptr */); // hold copy until done
+
+ const size_t array_size = 5;
+ const jint values[] = { (jint)sref->fields(), (jint)sref->mode(), (jint)sref->uid(), (jint)sref->gid(), (jint)sref->errno_res() };
+ jintArray jres = env->NewIntArray((jsize)array_size);
+ if (nullptr == jres) {
+ throw jau::InternalError("Cannot create instance of jintArray", E_FILE_LINE);
+ }
+ env->SetIntArrayRegion(jres, 0, (jsize)array_size, values);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ return jres;
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+jobjectArray Java_org_jau_fs_DirItem_getString2DirItem(JNIEnv *env, jclass clazz, jstring jpath) {
+ (void)clazz;
+ try {
+ const std::string path = jau::jni::from_jstring_to_string(env, jpath);
+
+ const size_t array_size = 2;
+ jclass strclz = jau::jni::search_class(env, "java/lang/String");
+ jobjectArray jres = env->NewObjectArray(array_size, strclz, nullptr);
+ if (nullptr == jres) {
+ throw jau::InternalError("Cannot create instance of jobjectArray", E_FILE_LINE);
+ }
+ jau::fs::dir_item di(path);
+ {
+ jstring jstr = jau::jni::from_string_to_jstring(env, di.dirname());
+ env->SetObjectArrayElement(jres, 0, jstr);
+ env->DeleteLocalRef(jstr);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ }
+ {
+ jstring jstr = jau::jni::from_string_to_jstring(env, di.basename());
+ env->SetObjectArrayElement(jres, 1, jstr);
+ env->DeleteLocalRef(jstr);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ }
+ return jres;
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+jobjectArray Java_org_jau_fs_FileStats_getString3DirItemLinkTargetPath(JNIEnv *env, jclass clazz, jlong nativeInstance) {
+ (void)clazz;
+ try {
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> sref(nativeInstance, true /* throw_on_nullptr */); // hold copy until done
+
+ const size_t array_size = 3;
+ jclass strclz = jau::jni::search_class(env, "java/lang/String");
+ jobjectArray jres = env->NewObjectArray(array_size, strclz, nullptr);
+ if (nullptr == jres) {
+ throw jau::InternalError("Cannot create instance of jobjectArray", E_FILE_LINE);
+ }
+ {
+ jstring jstr = jau::jni::from_string_to_jstring(env, sref->item().dirname());
+ env->SetObjectArrayElement(jres, 0, jstr);
+ env->DeleteLocalRef(jstr);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ }
+ {
+ jstring jstr = jau::jni::from_string_to_jstring(env, sref->item().basename());
+ env->SetObjectArrayElement(jres, 1, jstr);
+ env->DeleteLocalRef(jstr);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ }
+ {
+ const std::shared_ptr<std::string>& str_ref = sref->link_target_path();
+ jstring jstr = nullptr != str_ref ? jau::jni::from_string_to_jstring(env, *str_ref) : nullptr;
+ env->SetObjectArrayElement(jres, 2, jstr);
+ if( nullptr != jstr ) {
+ env->DeleteLocalRef(jstr);
+ }
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ }
+ return jres;
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+jlongArray Java_org_jau_fs_FileStats_getLong9SizeTimes(JNIEnv *env, jclass clazz, jlong nativeInstance) {
+ (void)clazz;
+ try {
+ jau::jni::shared_ptr_ref<jau::fs::file_stats> sref(nativeInstance, true /* throw_on_nullptr */); // hold copy until done
+
+ const size_t array_size = 9;
+ const jlong values[] = { (jlong)sref->size(),
+ (jlong)sref->btime().tv_sec, (jlong)sref->btime().tv_nsec,
+ (jlong)sref->atime().tv_sec, (jlong)sref->atime().tv_nsec,
+ (jlong)sref->ctime().tv_sec, (jlong)sref->ctime().tv_nsec,
+ (jlong)sref->mtime().tv_sec, (jlong)sref->mtime().tv_nsec };
+ jlongArray jres = env->NewLongArray((jsize)array_size);
+ if (nullptr == jres) {
+ throw jau::InternalError("Cannot create instance of jlongArray", E_FILE_LINE);
+ }
+ env->SetLongArrayRegion(jres, 0, (jsize)array_size, values);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ return jres;
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+//
+// FileUtil
+//
+
+jstring Java_org_jau_fs_FileUtil_get_1cwd(JNIEnv *env, jclass cls) {
+ (void)cls;
+ try {
+ const std::string cwd = jau::fs::get_cwd();
+ return jau::jni::from_string_to_jstring(env, cwd);
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+jstring Java_org_jau_fs_FileUtil_dirname(JNIEnv *env, jclass cls, jstring jpath) {
+ (void)cls;
+ try {
+ const std::string path = jau::jni::from_jstring_to_string(env, jpath);
+ const std::string dir = jau::fs::dirname(path);
+ return jau::jni::from_string_to_jstring(env, dir);
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+jstring Java_org_jau_fs_FileUtil_basename(JNIEnv *env, jclass cls, jstring jpath) {
+ (void)cls;
+ try {
+ const std::string path = jau::jni::from_jstring_to_string(env, jpath);
+ const std::string bname = jau::fs::basename(path);
+ return jau::jni::from_string_to_jstring(env, bname);
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
+jboolean Java_org_jau_fs_FileUtil_mkdirImpl(JNIEnv *env, jclass cls, jstring jpath, jint jmode) {
+ (void)cls;
+ try {
+ const std::string path = jau::jni::from_jstring_to_string(env, jpath);
+ const jau::fs::fmode_t mode = static_cast<jau::fs::fmode_t>(jmode);
+ return jau::fs::mkdir(path, mode) ? JNI_TRUE : JNI_FALSE;
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return JNI_FALSE;
+}
+
+constexpr const long MY_UTIME_NOW = ((1l << 30) - 1l);
+
+jboolean Java_org_jau_fs_FileUtil_touchImpl(JNIEnv *env, jclass cls, jstring jpath,
+ jlong atime_s, jlong atime_ns,
+ jlong mtime_s, jlong mtime_ns,
+ jint jmode)
+{
+ (void)cls;
+ try {
+ const std::string path = jau::jni::from_jstring_to_string(env, jpath);
+ const jau::fs::fmode_t mode = static_cast<jau::fs::fmode_t>(jmode);
+ if( MY_UTIME_NOW == atime_ns || MY_UTIME_NOW == mtime_ns ) {
+ return jau::fs::touch(path, mode) ? JNI_TRUE : JNI_FALSE;
+ } else {
+ const jau::fraction_timespec atime((int64_t)atime_s, (int64_t)atime_ns);
+ const jau::fraction_timespec mtime((int64_t)mtime_s, (int64_t)mtime_ns);
+ return jau::fs::touch(path, atime, mtime, mode) ? JNI_TRUE : JNI_FALSE;
+ }
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return JNI_FALSE;
+}
+
+jobject Java_org_jau_fs_FileUtil_get_1dir_1content(JNIEnv *env, jclass cls, jstring jpath) {
+ (void)cls;
+ try {
+ const std::string path = jau::jni::from_jstring_to_string(env, jpath);
+ std::vector<jau::fs::dir_item> content;
+ const jau::fs::consume_dir_item cs = jau::bindCaptureRefFunc<void, std::vector<jau::fs::dir_item>, const jau::fs::dir_item&>(&content,
+ ( void(*)(std::vector<jau::fs::dir_item>*, const jau::fs::dir_item&) ) /* help template type deduction of function-ptr */
+ ( [](std::vector<jau::fs::dir_item>* receiver, const jau::fs::dir_item& item) -> void { receiver->push_back( item ); } )
+ );
+ if( get_dir_content(path, cs) ) {
+ static const std::string _dirItemClassName("org/jau/fs/DirItem");
+ static const std::string _dirItemClazzCtorArgs("(Ljava/lang/String;Ljava/lang/String;)V");
+ jclass dirItemClazz = jau::jni::search_class(env, _dirItemClassName.c_str());
+ jmethodID dirItemClazzCtor = jau::jni::search_method(env, dirItemClazz, "<init>", _dirItemClazzCtorArgs.c_str(), false);
+ std::function<jobject(JNIEnv*, const jau::fs::dir_item&)> ctor_diritem =
+ [&](JNIEnv *env_, const jau::fs::dir_item& di)->jobject {
+ jstring dname = jau::jni::from_string_to_jstring(env_, di.dirname());
+ jstring bname = jau::jni::from_string_to_jstring(env_, di.basename());
+ jobject jdirItem = env->NewObject(dirItemClazz, dirItemClazzCtor, dname, bname);
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ return jdirItem;
+ };
+ return jau::jni::convert_vector_to_jarraylist<std::vector<jau::fs::dir_item>, jau::fs::dir_item>(env, content, ctor_diritem);
+ }
+ } catch(...) {
+ rethrow_and_raise_java_exception_jau(env);
+ }
+ return nullptr;
+}
+
jboolean Java_org_jau_fs_FileUtil_remove_1impl(JNIEnv *env, jclass cls, jstring jpath, jshort jtopts) {
(void)cls;
try {
diff --git a/java_jni/org/jau/fs/CopyOptions.java b/java_jni/org/jau/fs/CopyOptions.java
index 2cfd09c..bc732c6 100644
--- a/java_jni/org/jau/fs/CopyOptions.java
+++ b/java_jni/org/jau/fs/CopyOptions.java
@@ -28,14 +28,12 @@ package org.jau.fs;
*
* By default, the fmode_t POSIX protection mode bits are preserved
* while using the caller's uid and gid as well as current timestamps. <br />
- * Use copy_options::preserve_all to preserve uid and gid if allowed from the caller and access- and modification-timestamps.
+ * Use {@link CopyOptions.Bit#preserve_all} to preserve uid and gid if allowed from the caller and access- and modification-timestamps.
*
- * This `enum class` type fulfills `C++ named requirements: BitmaskType`.
- *
- * @see copy()
+ * @see FileUtil#copy(String, String, CopyOptions)
*/
public class CopyOptions {
- public enum Option {
+ public enum Bit {
/** No option set */
none ( (short) 0 ),
@@ -64,7 +62,7 @@ public class CopyOptions {
/** Enable verbosity mode, show error messages on stderr. */
verbose ( (short)( 1 << 15 ) );
- Option(final short v) { value = v; }
+ Bit(final short v) { value = v; }
public final short value;
}
@@ -77,14 +75,14 @@ public class CopyOptions {
mask = 0;
}
- public boolean isSet(final Option bit) { return bit.value == ( mask & bit.value ); }
- public void set(final Option bit) { mask = (short) ( mask | bit.value ); }
+ public boolean isSet(final Bit bit) { return bit.value == ( mask & bit.value ); }
+ public void set(final Bit bit) { mask = (short) ( mask | bit.value ); }
@Override
public String toString() {
int count = 0;
final StringBuilder out = new StringBuilder();
- for (final Option dt : Option.values()) {
+ for (final Bit dt : Bit.values()) {
if( isSet(dt) ) {
if( 0 < count ) { out.append(", "); }
out.append(dt.name()); count++;
@@ -96,4 +94,12 @@ public class CopyOptions {
}
return out.toString();
}
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ return (other instanceof CopyOptions) &&
+ this.mask == ((CopyOptions)other).mask;
+ }
}
diff --git a/java_jni/org/jau/fs/DirItem.java b/java_jni/org/jau/fs/DirItem.java
new file mode 100644
index 0000000..48ab9a8
--- /dev/null
+++ b/java_jni/org/jau/fs/DirItem.java
@@ -0,0 +1,95 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 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.fs;
+
+/**
+ * Representing a directory item split into {@link #dirname()} and {@link #basename()}.
+ */
+public class DirItem {
+ static private final String _slash = "/";
+ static private final String _dot = ".";
+
+ private final String dirname_;
+ private final String basename_;
+
+ /* pp */ DirItem(final String dirname, final String basename) {
+ dirname_ = dirname;
+ basename_ = basename;
+ }
+
+ /** Empty item w/ `.` set for both, dirname and basename */
+ public DirItem() {
+ dirname_ = _dot;
+ basename_ = _dot;
+ }
+
+ /**
+ * Create a dir_item where path is split into dirname and basename after `.` and `..` has been reduced.
+ *
+ * @param path_ the raw path
+ */
+ public DirItem(final String path) {
+ final String[] s2 = getString2DirItem(path);
+ dirname_ = s2[0];
+ basename_ = s2[1];
+ }
+ private static native String[/*3*/] getString2DirItem(final String s);
+
+ /** Returns the dirname, shall not be empty and denotes `.` for current working director. */
+ public String dirname() { return dirname_; }
+
+ /** Return the basename, shall not be empty nor contain a dirname. */
+ public String basename() { return basename_; }
+
+ /**
+ * Returns a full unix path representation combining dirname() and basename().
+ */
+ public String path() {
+ if( _dot.equals( dirname_ ) ) {
+ return basename_;
+ }
+ if( _dot.equals( basename_ ) ) {
+ return dirname_;
+ }
+ if( _slash.equals( dirname_ ) ) {
+ return dirname_ + basename_;
+ }
+ return dirname_ + _slash + basename_;
+ }
+
+ @Override
+ public String toString() {
+ return "['"+dirname()+"', '"+basename()+"']";
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ return (other instanceof DirItem) &&
+ this.dirname_.equals( ((DirItem)other).dirname_ ) &&
+ this.basename_.equals( ((DirItem)other).basename_ );
+ }
+}
diff --git a/java_jni/org/jau/fs/FMode.java b/java_jni/org/jau/fs/FMode.java
new file mode 100644
index 0000000..4fb8bda
--- /dev/null
+++ b/java_jni/org/jau/fs/FMode.java
@@ -0,0 +1,147 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 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.fs;
+
+/**
+ * Generic file type and POSIX protection mode bits as used in file_stats, touch(), mkdir() etc.
+ *
+ * The POSIX protection mode bits reside in the lower 16-bits and are bit-wise POSIX compliant
+ * while the file type bits reside in the upper 16-bits and are platform agnostic.
+ *
+ * This `enum class` type fulfills `C++ named requirements: BitmaskType`.
+ *
+ * @see FileStats
+ * @see FileStats#mode()
+ */
+public class FMode {
+ public enum Bit {
+ /** No mode bit set */
+ none ( 0 ),
+
+ /** Protection bit: POSIX S_ISUID */
+ set_uid ( 04000 ),
+ /** Protection bit: POSIX S_ISGID */
+ set_gid ( 02000 ),
+ /** Protection bit: POSIX S_ISVTX */
+ sticky ( 01000 ),
+ /** Protection bit: POSIX S_ISUID | S_ISGID | S_ISVTX */
+ ugs_set ( 07000 ),
+
+ /** Protection bit: POSIX S_IRUSR */
+ read_usr ( 00400 ),
+ /** Protection bit: POSIX S_IWUSR */
+ write_usr ( 00200 ),
+ /** Protection bit: POSIX S_IXUSR */
+ exec_usr ( 00100 ),
+ /** Protection bit: POSIX S_IRWXU */
+ rwx_usr ( 00700 ),
+
+ /** Protection bit: POSIX S_IRGRP */
+ read_grp ( 00040 ),
+ /** Protection bit: POSIX S_IWGRP */
+ write_grp ( 00020 ),
+ /** Protection bit: POSIX S_IXGRP */
+ exec_grp ( 00010 ),
+ /** Protection bit: POSIX S_IRWXG */
+ rwx_grp ( 00070 ),
+
+ /** Protection bit: POSIX S_IROTH */
+ read_oth ( 00004 ),
+ /** Protection bit: POSIX S_IWOTH */
+ write_oth ( 00002 ),
+ /** Protection bit: POSIX S_IXOTH */
+ exec_oth ( 00001 ),
+ /** Protection bit: POSIX S_IRWXO */
+ rwx_oth ( 00007 ),
+
+ /** Protection bit: POSIX S_IRWXU | S_IRWXG | S_IRWXO or rwx_usr | rwx_grp | rwx_oth */
+ rwx_all ( 00777 ),
+
+ /** Default directory protection bit: Safe default: POSIX S_IRWXU | S_IRGRP | S_IXGRP or rwx_usr | read_grp | exec_grp */
+ def_dir_prot ( 00750 ),
+
+ /** Default file protection bit: Safe default: POSIX S_IRUSR | S_IWUSR | S_IRGRP or read_usr | write_usr | read_grp */
+ def_file_prot ( 00640 ),
+
+ /** 12 bit protection bit mask 07777 for rwx_all | set_uid | set_gid | sticky . */
+ protection_mask ( 0b111111111111 ),
+
+ /** Type: Entity is a directory ), might be in combination with link. */
+ dir ( 0b000010000000000000000 ),
+ /** Type: Entity is a file ), might be in combination with link. */
+ file ( 0b000100000000000000000 ),
+ /** Type: Entity is a symbolic link ), might be in combination with file or dir. */
+ link ( 0b001000000000000000000 ),
+ /** Type: Entity gives no access to user ), exclusive bit. */
+ no_access ( 0b010000000000000000000 ),
+ /** Type: Entity does not exist ), exclusive bit. */
+ not_existing ( 0b100000000000000000000 ),
+ /** Type mask for dir | file | link | no_access | not_existing. */
+ type_mask ( 0b111110000000000000000 );
+
+ Bit(final int v) { value = v; }
+ public final int value;
+ }
+ public int mask;
+
+ public FMode(final int v) {
+ mask = v;
+ }
+ public FMode() {
+ mask = 0;
+ }
+
+ public boolean isSet(final Bit bit) { return bit.value == ( mask & bit.value ); }
+ public void set(final Bit bit) { mask = (short) ( mask | bit.value ); }
+ public FMode mask(final int bits) {
+ final int r = mask & bits;
+ if( r == mask ) { return this; }
+ else { return new FMode(r); }
+ }
+
+ private static native String to_string(final int mask, final boolean show_rwx);
+
+ @Override
+ public String toString() {
+ return to_string(mask, false);
+ }
+ public String toString(final boolean show_rwx) {
+ return to_string(mask, show_rwx);
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ return (other instanceof FMode) &&
+ this.mask == ((FMode)other).mask;
+ }
+
+ /** Default directory protection bit: Safe default: POSIX S_IRWXU | S_IRGRP | S_IXGRP or rwx_usr | read_grp | exec_grp */
+ public static final FMode def_dir = new FMode(FMode.Bit.def_dir_prot.value);
+
+ /** Default file protection bit: Safe default: POSIX S_IRUSR | S_IWUSR | S_IRGRP or read_usr | write_usr | read_grp */
+ public static final FMode def_file = new FMode(FMode.Bit.def_file_prot.value);
+}
diff --git a/java_jni/org/jau/fs/FileStats.java b/java_jni/org/jau/fs/FileStats.java
new file mode 100644
index 0000000..d581ac0
--- /dev/null
+++ b/java_jni/org/jau/fs/FileStats.java
@@ -0,0 +1,385 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 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.fs;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+
+/**
+ * Platform agnostic representation of POSIX ::lstat() and ::stat()
+ * for a given pathname.
+ *
+ * Implementation follows the symbolic link, i.e. first opens
+ * the given pathname with ::lstat() and if identifying as a symbolic link
+ * opens it via ::stat() to retrieve the actual properties like size, time and ownership.
+ *
+ * On `GNU/Linux` implementation uses ::statx().
+ */
+public class FileStats {
+ public static class Field {
+ public enum Type {
+ /** No mode bit set */
+ none ( 0 ),
+ /** File type mode bits */
+ type ( 0b0000000000000001 ),
+ /** POSIX file protection mode bits */
+ mode ( 0b0000000000000010 ),
+ nlink ( 0b0000000000000100 ),
+ uid ( 0b0000000000001000 ),
+ gid ( 0b0000000000010000 ),
+ atime ( 0b0000000000100000 ),
+ mtime ( 0b0000000001000000 ),
+ ctime ( 0b0000000010000000 ),
+ ino ( 0b0000000100000000 ),
+ size ( 0b0000001000000000 ),
+ blocks ( 0b0000010000000000 ),
+ btime ( 0b0000100000000000 );
+
+ Type(final int v) { value = v; }
+ public final int value;
+ }
+ public int mask;
+
+ public Field(final int v) {
+ mask = v;
+ }
+ public Field() {
+ mask = 0;
+ }
+
+ public boolean isSet(final Type bit) { return bit.value == ( mask & bit.value ); }
+ public void set(final Type bit) { mask = (short) ( mask | bit.value ); }
+
+ @Override
+ public String toString() {
+ int count = 0;
+ final StringBuilder out = new StringBuilder();
+ for (final Type dt : Type.values()) {
+ if( isSet(dt) ) {
+ if( 0 < count ) { out.append(", "); }
+ out.append(dt.name()); count++;
+ }
+ }
+ if( 1 < count ) {
+ out.insert(0, "[");
+ out.append("]");
+ }
+ return out.toString();
+ }
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ return (other instanceof Field) &&
+ this.mask == ((Field)other).mask;
+ }
+ }
+
+ private final DirItem item_;
+ private final String link_target_path_; // stored link-target path this symbolic-link points to if is_link(), otherwise nullptr.
+
+ private final Field has_fields_;
+ private final FMode mode_;
+ private final int uid_;
+ private final int gid_;
+ private final int errno_res_;
+
+ private final long size_;
+ // final ZonedDateTime utc = btime_.atZone(ZoneOffset.UTC); String s = utc.toString();
+ private final Instant btime_; // Birth or creation time
+ private final Instant atime_; // Last access
+ private final Instant ctime_; // Last meta-status change
+ private final Instant mtime_; // Last modification
+
+ private FileStats link_target_; // link-target this symbolic-link points to if is_link(), otherwise nullptr.
+
+ /** Instantiate an empty file_stats with fmode_t::not_existing set. */
+ public FileStats() {
+ item_ = new DirItem();
+ link_target_path_ = null;
+
+ has_fields_ = new Field();
+ mode_ = new FMode();
+ mode_.set(FMode.Bit.not_existing);
+ uid_ = 0;
+ gid_ = 0;
+ errno_res_ = 0;
+
+ size_ = 0;
+ btime_ = Instant.ofEpochSecond(0, 0);
+ atime_ = btime_;
+ ctime_ = btime_;
+ mtime_ = btime_;
+
+ link_target_ = null;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ if( other instanceof FileStats ) {
+ final FileStats o = (FileStats)other;
+ return item_.equals( o.item_ ) &&
+ has_fields_.equals(o.has_fields_) &&
+ mode_.equals(o.mode_) &&
+ uid_ == o.uid_ && gid_ == o.gid_ &&
+ errno_res_ == o.errno_res_ &&
+ size_ == o.size_ &&
+ btime_.equals( o.btime_ ) &&
+ atime_.equals( o.atime_ ) &&
+ ctime_.equals( o.ctime_ ) &&
+ mtime_.equals( o.mtime_ ) &&
+ ( !is_link() ||
+ ( link_target_path_.equals(o.link_target_path_) &&
+ link_target_.equals(o.link_target_)
+ )
+ );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Instantiates a file_stats for the given `path`.
+ *
+ * The dir_item will be constructed without parent_dir
+ * @param path the path to produce stats for
+ */
+ public FileStats(final String path) {
+ this( ctorImpl(path) );
+ }
+ private FileStats(final long h) {
+ {
+ final String[/*3*/] s3 = getString3DirItemLinkTargetPath(h);
+ item_ = new DirItem(s3[0], s3[1]);
+ link_target_path_ = s3[2];
+ }
+ {
+ final int[/*3*/] i5 = getInt5FieldsFModeUidGidErrno(h);
+ has_fields_ = new Field(i5[0]);
+ mode_ = new FMode(i5[1]);
+ uid_ = i5[2];
+ gid_ = i5[3];
+ errno_res_ = i5[4];
+ }
+ {
+ final long[/*1+ 2*4*/] l3 = getLong9SizeTimes(h);
+ size_ = l3[0];
+ btime_ = Instant.ofEpochSecond(l3[1+0*2+0], l3[1+0*2+1]);
+ atime_ = Instant.ofEpochSecond(l3[1+1*2+0], l3[1+1*2+1]);
+ ctime_ = Instant.ofEpochSecond(l3[1+2*2+0], l3[1+2*2+1]);
+ mtime_ = Instant.ofEpochSecond(l3[1+3*2+0], l3[1+3*2+1]);
+ }
+ {
+ final long lth = ctorLinkTargetImpl(h);
+ if( lth == 0 ) {
+ link_target_ = null;
+ } else {
+ link_target_ = new FileStats(lth);
+ }
+ }
+ dtorImpl(h);
+ }
+ private static native long ctorImpl(final String path);
+ private static native void dtorImpl(final long h);
+ private static native int[/*4*/] getInt5FieldsFModeUidGidErrno(final long h);
+ private static native String[/*3*/] getString3DirItemLinkTargetPath(final long h);
+ private static native long[/*1+ 2*4*/] getLong9SizeTimes(final long h);
+ private static native long ctorLinkTargetImpl(final long h);
+
+ /**
+ * Returns the dir_item.
+ *
+ * In case this instance is created by following a symbolic link instance,
+ * it represents the resolved path relative to the used symbolic link's dirname.
+ *
+ * @see is_link()
+ * @see path()
+ */
+ public DirItem item() { return item_; }
+
+ /**
+ * Returns the unix path representation.
+ *
+ * In case this instance is created by following a symbolic link instance,
+ * it represents the resolved path relative to the used symbolic link's dirname.
+ *
+ * @see is_link()
+ * @see item()
+ */
+ public String path() { return item_.path(); }
+
+ /**
+ * Returns the stored link-target path this symbolic-link points to if instance is a symbolic-link, otherwise nullptr.
+ *
+ * @see is_link()
+ * @see link_target()
+ * @see final_target()
+ */
+ public String link_target_path() { return link_target_path_; }
+
+ /**
+ * Returns the link-target this symbolic-link points to if instance is a symbolic-link, otherwise nullptr.
+ *
+ * nullptr is also returned for an erroneous symbolic-links, i.e. non-existing link-targets or recursive loop-errors.
+ *
+ * @see is_link()
+ * @see link_target_path()
+ * @see final_target()
+ */
+ public FileStats link_target() { return link_target_; }
+
+ /**
+ * Returns the final target element, either a pointer to this instance if not a symbolic-link
+ * or the final link-target a symbolic-link (chain) points to.
+ *
+ * @param link_count optional size_t pointer to store the number of symbolic links leading to the final target, excluding the final instance. 0 indicates no symbolic-link;
+ *
+ * @see is_link()
+ * @see link_target_path()
+ * @see link_target()
+ */
+ public FileStats final_target(final long link_count[]) {
+ long count = 0;
+ FileStats fs0 = this;
+ FileStats fs1 = fs0.link_target();
+ while( null != fs1 ) {
+ ++count;
+ fs0 = fs1;
+ fs1 = fs0.link_target();
+ }
+ if( null != link_count && link_count.length > 0 ) {
+ link_count[0] = count;
+ }
+ return fs0;
+ }
+
+ /** Returns true if the given field_t fields were retrieved, otherwise false. */
+ public boolean has(final Field.Type bit) { return has_fields_.isSet(bit); }
+
+ /** Returns the retrieved field_t fields. */
+ public Field fields() { return has_fields_; }
+
+ /** Returns the FMode, file type and mode. */
+ public FMode mode() { return mode_; }
+
+ /** Returns the POSIX protection bit portion of fmode_t, i.e. mode() & {@link FMode.Bit#protection_mask}. */
+ public FMode prot_mode() { return mode_.mask(FMode.Bit.protection_mask.value); }
+
+ /** Returns the user id, owning the element. */
+ public int uid() { return uid_; }
+
+ /** Returns the group id, owning the element. */
+ public int gid() { return gid_; }
+
+ /**
+ * Returns the size in bytes of this element if is_file(), otherwise zero.
+ *
+ * If the element also is_link(), the linked target size is returned.
+ */
+ public long size() { return size_; }
+
+ /** Returns the birth time of this element since Unix Epoch, i.e. its creation time. */
+ public Instant btime() { return btime_; }
+ /** Returns the last access time of this element since Unix Epoch. */
+ public Instant atime() { return atime_; }
+ /** Returns the last status change time of this element since Unix Epoch. */
+ public Instant ctime() { return ctime_; }
+ /** Returns the last modification time of this element since Unix Epoch. */
+ public Instant mtime() { return mtime_; }
+
+ /** Returns the `errno` value occurred to produce this instance, or zero for no error. */
+ public int errno_res() { return errno_res_; }
+
+ /** Returns true if no error occurred */
+ public boolean ok() { return 0 == errno_res_; }
+
+ /** Returns true if entity is a file, might be in combination with is_link(). */
+ public boolean is_file() { return mode_.isSet( FMode.Bit.file ); }
+
+ /** Returns true if entity is a directory, might be in combination with is_link(). */
+ public boolean is_dir() { return mode_.isSet( FMode.Bit.dir ); }
+
+ /** Returns true if entity is a symbolic link, might be in combination with is_file() or is_dir(). */
+ public boolean is_link() { return mode_.isSet( FMode.Bit.link ); }
+
+ /** Returns true if entity gives no access to user, exclusive bit. */
+ public boolean has_access() { return !mode_.isSet( FMode.Bit.no_access); }
+
+ /** Returns true if entity does not exist, exclusive bit. */
+ public boolean exists() { return !mode_.isSet( FMode.Bit.not_existing ); }
+
+ @Override
+ public String toString() {
+ final String stored_path, link_detail;
+ {
+ if( null != link_target_path_ ) {
+ stored_path = " [-> "+link_target_path_+"]";
+ } else {
+ stored_path = new String();
+ }
+ final long link_count[] = { 0 };
+ final FileStats final_target_ = final_target(link_count);
+ if( 0 < link_count[0] ) {
+ link_detail = " -(" + link_count[0] + ")-> '" + final_target_.path() + "'";
+ } else {
+ link_detail = new String();
+ }
+ }
+ final StringBuilder res = new StringBuilder( "file_stats[");
+ res.append(mode_)
+ .append(", '"+item_.path()+"'"+stored_path+link_detail );
+ if( 0 == errno_res_ ) {
+ if( has( Field.Type.uid ) ) {
+ res.append( ", uid " ).append( uid_ );
+ }
+ if( has( Field.Type.gid ) ) {
+ res.append( ", gid " ).append( gid_ );
+ }
+ if( has( Field.Type.size ) ) {
+ res.append( ", size " ).append( String.format("%,d", size_ ) );
+ }
+ if( has( Field.Type.btime ) ) {
+ res.append( ", btime " ).append( btime_.atZone(ZoneOffset.UTC) );
+ }
+ if( has( Field.Type.atime ) ) {
+ res.append( ", atime " ).append( atime_.atZone(ZoneOffset.UTC) );
+ }
+ if( has( Field.Type.ctime ) ) {
+ res.append( ", ctime " ).append( ctime_.atZone(ZoneOffset.UTC) );
+ }
+ if( has( Field.Type.mtime ) ) {
+ res.append( ", mtime " ).append( mtime_.atZone(ZoneOffset.UTC) );
+ }
+ // res.append( ", fields ").append( jau::fs::to_string( has_fields_ ) );
+ } else {
+ res.append( ", errno " ).append( errno_res_ );
+ }
+ res.append("]");
+ return res.toString();
+ }
+}
diff --git a/java_jni/org/jau/fs/FileUtil.java b/java_jni/org/jau/fs/FileUtil.java
index 06feb80..799220b 100644
--- a/java_jni/org/jau/fs/FileUtil.java
+++ b/java_jni/org/jau/fs/FileUtil.java
@@ -23,11 +23,186 @@
*/
package org.jau.fs;
+import java.time.Instant;
+import java.util.List;
+
/**
* Native file types and functionality.
*/
public final class FileUtil {
/**
+ * Return the current working directory or empty on failure.
+ */
+ public static native String get_cwd();
+
+ /**
+ * Return stripped last component from given path separated by `/`, excluding the trailing separator `/`.
+ *
+ * If no directory separator `/` is contained, return `.`.
+ *
+ * If only the root path `/` is given, return `/`.
+ *
+ * @param path given path
+ * @return leading directory name w/o slash or `.`
+ */
+ public static native String dirname(final String path);
+
+ /**
+ * Return stripped leading directory components from given path separated by `/`.
+ *
+ * If only the root path `/` is given, return `/`.
+ *
+ * @param path given path
+ * @return last non-slash component or `.`
+ */
+ public static native String basename(final String path);
+
+ /**
+ * Create directory
+ * @param path full path to new directory
+ * @param mode fmode_t POSIX protection bits used, defaults to {@link FMode#def_dir}
+ * @param verbose defaults to false
+ * @return true if successful, otherwise false
+ */
+ public static boolean mkdir(final String path, final FMode mode) {
+ return mkdirImpl(path, mode.mask);
+ }
+ private static native boolean mkdirImpl(final String path, final int mode);
+
+ /**
+ * See {@link #mkdir(String, FMode)} using {@link FMode#def_dir}
+ */
+ public static boolean mkdir(final String path) {
+ return mkdirImpl(path, FMode.def_dir.mask);
+ }
+
+ /**
+ * Touch the file with given atime and mtime and create file if not existing yet.
+ * @param path full path to file
+ * @param atime new access time
+ * @param mtime new modification time
+ * @param mode fmode_t POSIX protection bits used, defaults to {@link FMode#def_file}
+ * @param verbose defaults to false
+ * @return true if successful, otherwise false
+ */
+ public static boolean touch(final String path, final Instant atime, final Instant mtime,
+ final FMode mode) {
+ return touchImpl(path,
+ atime.getEpochSecond(), atime.getNano(),
+ mtime.getEpochSecond(), mtime.getNano(),
+ mode.mask);
+ }
+ private static native boolean touchImpl(final String path,
+ long atime_s, long atime_ns,
+ long mtime_s, long mtime_ns,
+ int mode);
+
+ public static final long UTIME_NOW = ((1l << 30) - 1l);
+
+ /**
+ * Touch the file with current time and create file if not existing yet.
+ * @param path full path to file
+ * @param mode fmode_t POSIX protection bits used, defaults to {@link FMode#def_file}
+ * @param verbose defaults to false
+ * @return true if successful, otherwise false
+ */
+ public static boolean touch(final String path, final FMode mode) {
+ return touchImpl(path, 0, UTIME_NOW, 0, UTIME_NOW, mode.mask);
+ }
+
+ /**
+ * Returns a list of directory elements excluding `.` and `..` for the given path, non recursive.
+ *
+ * @param path path to directory
+ * @return list of DirItem if given path exists, is directory and is readable, otherwise null
+ */
+ public static native List<DirItem> get_dir_content(final String path);
+
+ /**
+ * Path visitor for {@link FileUtil#visit(FileStats, TraverseOptions, PathVisitor)}
+ */
+ public static interface PathVisitor {
+ boolean visit(TraverseEvent tevt, final FileStats item_stats);
+ }
+
+ /**
+ * Visit element(s) of a given path, see traverse_options for detailed settings.
+ *
+ * All elements of type fmode_t::file, fmode_t::dir and fmode_t::no_access or fmode_t::not_existing
+ * will be visited by the given path_visitor `visitor`.
+ *
+ * Processing ends if the `visitor returns `false`.
+ *
+ * @param path the starting path
+ * @param topts given traverse_options for this operation
+ * @param visitor path_visitor function `bool visitor(const file_stats& item_stats)`.
+ * @return true if all visitor invocations returned true, otherwise false
+ */
+ public static boolean visit(final String path, final TraverseOptions topts, final PathVisitor visitor) {
+ return visit(new FileStats(path), topts, visitor);
+ }
+
+ /**
+ * Visit element(s) of a given path, see traverse_options for detailed settings.
+ *
+ * All elements of type fmode_t::file, fmode_t::dir and fmode_t::no_access or fmode_t::not_existing
+ * will be visited by the given path_visitor `visitor`.
+ *
+ * Processing ends if the `visitor returns `false`.
+ *
+ * @param item_stats pre-fetched file_stats for a given dir_item, used for efficiency
+ * @param topts given traverse_options for this operation
+ * @param visitor path_visitor function `bool visitor(const file_stats& item_stats)`.
+ * @return true if all visitor invocations returned true, otherwise false
+ */
+ public static boolean visit(final FileStats item_stats, final TraverseOptions topts, final PathVisitor visitor) {
+ if( item_stats.is_dir() ) {
+ if( item_stats.is_link() && !topts.isSet(TraverseOptions.Bit.follow_symlinks) ) {
+ return visitor.visit( TraverseEvent.dir_symlink, item_stats );
+ }
+ if( !topts.isSet(TraverseOptions.Bit.recursive) ) {
+ return visitor.visit( TraverseEvent.dir_non_recursive, item_stats );
+ }
+ if( topts.isSet(TraverseOptions.Bit.dir_entry) ) {
+ if( !visitor.visit( TraverseEvent.dir_entry, item_stats ) ) {
+ return false;
+ }
+ }
+ final List<DirItem> content = get_dir_content(item_stats.path());
+ if( null != content && content.size() > 0 ) {
+ for (final DirItem element : content) {
+ final FileStats element_stats = new FileStats( element.path() );
+ if( element_stats.is_dir() ) { // an OK dir
+ if( element_stats.is_link() && !topts.isSet(TraverseOptions.Bit.follow_symlinks) ) {
+ if( !visitor.visit( TraverseEvent.dir_symlink, element_stats ) ) {
+ return false;
+ }
+ } else if( !visit(element_stats, topts, visitor) ) { // recursive
+ return false;
+ }
+ } else if( !visitor.visit( element_stats.is_file() && element_stats.is_link() ? TraverseEvent.file_symlink :
+ ( element_stats.is_file() ? TraverseEvent.file :
+ ( element_stats.is_link() ? TraverseEvent.symlink : TraverseEvent.none ) ),
+ element_stats ) )
+ {
+ return false;
+ }
+ }
+ }
+ }
+ if( item_stats.is_dir() && topts.isSet(TraverseOptions.Bit.dir_exit) ) {
+ return visitor.visit( TraverseEvent.dir_exit, item_stats );
+ } else if( item_stats.is_file() || !item_stats.ok() ) { // file or error-alike
+ return visitor.visit( item_stats.is_file() && item_stats.is_link() ? TraverseEvent.file_symlink :
+ ( item_stats.is_file() ? TraverseEvent.file :
+ ( item_stats.is_link() ? TraverseEvent.symlink : TraverseEvent.none ) ),
+ item_stats );
+ } else {
+ return true;
+ }
+ }
+
+ /**
* Remove the given path. If path represents a director, `recursive` must be set to true.
*
* The given traverse_options `options` are handled as follows:
diff --git a/java_jni/org/jau/fs/TraverseEvent.java b/java_jni/org/jau/fs/TraverseEvent.java
new file mode 100644
index 0000000..bb50bbb
--- /dev/null
+++ b/java_jni/org/jau/fs/TraverseEvent.java
@@ -0,0 +1,87 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 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.fs;
+
+/**
+ * Filesystem traverse event used to call path_visitor for path elements from visit().
+ *
+ * This `enum class` type fulfills `C++ named requirements: BitmaskType`.
+ *
+ * @see FileUtil.PathVisitor
+ * @see FileUtil#visit(FileStats, TraverseOptions, org.jau.fs.FileUtil.PathVisitor)
+ */
+public enum TraverseEvent {
+ /** No value, neither file, symlink nor dir_entry or dir_exit. Implying an error state in file_stat, e.g. !file_stats::has_access(). */
+ none ( (short) 0 ),
+
+ /**
+ * Visiting a symbolic-link, either to a file or a non-existing entity. Not followed symbolic-links to a directory is expressed via dir_symlink.
+ *
+ * In case of a symbolic-link to an existing file, file is also set, i.e. file_symlink.
+ */
+ symlink ( (short) (1 << 0) ),
+
+ /** Visiting a file, may be in conjunction with symlink, i.e. file_symlink */
+ file ( (short) (1 << 1) ),
+
+ /** Visiting a symlink to a file, i.e. symlink | file */
+ file_symlink ( (short) ( 1 << 0 /* symmlink */ | 1 << 1 /* file */ ) ),
+
+ /**
+ * Visiting a directory on entry, see traverse_options::dir_entry.
+ *
+ * If a directory is visited non-recursive, i.e. traverse_options::recursive not set,
+ * dir_entry and dir_exit are set, see dir_non_recursive.
+ *
+ * If a directory is a symbolic link which is not followed, i.e. traverse_options::follow_symlinks not set,
+ * dir_symlink is used instead.
+ */
+ dir_entry ( (short) (1 << 2) ),
+
+ /**
+ * Visiting a directory on exit, see traverse_options::dir_exit.
+ *
+ * If a directory is visited non-recursive, i.e. traverse_options::recursive not set,
+ * dir_entry and dir_exit are set, see dir_non_recursive.
+ *
+ * If a directory is a symbolic link which is not followed, i.e. traverse_options::follow_symlinks not set,
+ * dir_symlink is used instead.
+ */
+ dir_exit ( (short) (1 << 3) ),
+
+ /**
+ * Visiting a symbolic-link to a directory which is not followed, i.e. traverse_options::follow_symlinks not set.
+ */
+ dir_symlink ( (short) (1 << 4) ),
+
+ /**
+ * Visiting a directory non-recursive, i.e. traverse_options::recursive not set.
+ *
+ * Value is a bit-mask of dir_entry | dir_exit
+ */
+ dir_non_recursive ( (short) ( 1 << 2 /* dir_entry */ | 1 << 3 /* dir_exit */ ) );
+
+ TraverseEvent(final short v) { value = v; }
+ public final short value;
+}
diff --git a/java_jni/org/jau/fs/TraverseOptions.java b/java_jni/org/jau/fs/TraverseOptions.java
index 9e67a8b..59c6f65 100644
--- a/java_jni/org/jau/fs/TraverseOptions.java
+++ b/java_jni/org/jau/fs/TraverseOptions.java
@@ -28,11 +28,11 @@ package org.jau.fs;
*
* This `enum class` type fulfills `C++ named requirements: BitmaskType`.
*
- * @see visit()
- * @see remove()
+ * @see FileUtil#visit(FileStats, TraverseOptions, org.jau.fs.FileUtil.PathVisitor)
+ * @see FileUtil#remove(String, TraverseOptions)
*/
public class TraverseOptions {
- public enum Option {
+ public enum Bit {
/** No option set */
none ( (short) 0 ),
@@ -51,7 +51,7 @@ public class TraverseOptions {
/** Enable verbosity mode, potentially used by a path_visitor implementation like remove(). */
verbose ( (short) ( 1 << 15 ) );
- Option(final short v) { value = v; }
+ Bit(final short v) { value = v; }
public final short value;
}
public short mask;
@@ -63,14 +63,14 @@ public class TraverseOptions {
mask = 0;
}
- public boolean isSet(final Option bit) { return bit.value == ( mask & bit.value ); }
- public void set(final Option bit) { mask = (short) ( mask | bit.value ); }
+ public boolean isSet(final Bit bit) { return bit.value == ( mask & bit.value ); }
+ public void set(final Bit bit) { mask = (short) ( mask | bit.value ); }
@Override
public String toString() {
int count = 0;
final StringBuilder out = new StringBuilder();
- for (final Option dt : Option.values()) {
+ for (final Bit dt : Bit.values()) {
if( isSet(dt) ) {
if( 0 < count ) { out.append(", "); }
out.append(dt.name()); count++;
@@ -83,4 +83,12 @@ public class TraverseOptions {
return out.toString();
}
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ return (other instanceof TraverseOptions) &&
+ this.mask == ((TraverseOptions)other).mask;
+ }
}
diff --git a/test/java/jau/test/fs/FileUtilBaseTest.java b/test/java/jau/test/fs/FileUtilBaseTest.java
new file mode 100644
index 0000000..e7698ad
--- /dev/null
+++ b/test/java/jau/test/fs/FileUtilBaseTest.java
@@ -0,0 +1,322 @@
+/*
+ * 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 jau.test.fs;
+
+import org.jau.fs.CopyOptions;
+import org.jau.fs.FileStats;
+import org.jau.fs.FileUtil;
+import org.jau.fs.TraverseEvent;
+import org.jau.fs.TraverseOptions;
+import org.jau.io.PrintUtil;
+import org.junit.Assert;
+
+import jau.test.junit.util.JunitTracer;
+
+public class FileUtilBaseTest extends JunitTracer {
+ public static final String root = "test_data";
+ // normal location with jaulib as sole project
+ public static final String project_root1 = "../../../test_data";
+ // submodule location with jaulib directly hosted below main project
+ public static final String project_root2 = "../../../../jaulib/test_data";
+ // external filesystem to test ...
+ public static final String project_root_ext = "/mnt/ssd0/data/test_data";
+
+ public static final TraverseOptions topts_none = new TraverseOptions();
+ public static final TraverseOptions topts_rec = new TraverseOptions(TraverseOptions.Bit.recursive.value);
+
+ public static class VisitorStats {
+ public TraverseOptions topts;
+ public int total_real;
+ public int total_sym_links_existing;
+ public int total_sym_links_not_existing;
+ public int total_no_access;
+ public int total_not_existing;
+ public long total_file_bytes;
+ public int files_real;
+ public int files_sym_link;
+ public int dirs_real;
+ public int dirs_sym_link;
+
+ public VisitorStats(final TraverseOptions topts_) {
+ topts = topts_;
+ total_real = 0;
+ total_sym_links_existing = 0;
+ total_sym_links_not_existing = 0;
+ total_no_access = 0;
+ total_not_existing = 0;
+ total_file_bytes = 0;
+ files_real = 0;
+ files_sym_link = 0;
+ dirs_real = 0;
+ dirs_sym_link = 0;
+ }
+
+ public void add(final FileStats element_stats) {
+ if( element_stats.is_link() ) {
+ if( element_stats.exists() ) {
+ total_sym_links_existing++;
+ } else {
+ total_sym_links_not_existing++;
+ }
+ } else {
+ total_real++;
+ }
+ if( !element_stats.has_access() ) {
+ total_no_access++;
+ }
+ if( !element_stats.exists() ) {
+ total_not_existing++;
+ }
+ if( element_stats.is_file() ) {
+ if( element_stats.is_link() ) {
+ files_sym_link++;
+ if( topts.isSet(TraverseOptions.Bit.follow_symlinks) ) {
+ total_file_bytes += element_stats.size();
+ }
+ } else {
+ files_real++;
+ total_file_bytes += element_stats.size();
+ }
+ } else if( element_stats.is_dir() ) {
+ if( element_stats.is_link() ) {
+ dirs_sym_link++;
+ } else {
+ dirs_real++;
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if( this == other ) {
+ return true;
+ }
+ if( !( other instanceof VisitorStats ) ) {
+ return false;
+ }
+ final VisitorStats o = (VisitorStats)other;
+ return total_file_bytes == o.total_file_bytes &&
+ total_real == o.total_real &&
+ total_sym_links_existing == o.total_sym_links_existing &&
+ total_sym_links_not_existing == o.total_sym_links_not_existing &&
+ total_no_access == o.total_no_access &&
+ total_not_existing == o.total_not_existing &&
+ files_real == o.files_real &&
+ files_sym_link == o.files_sym_link &&
+ dirs_real == o.dirs_real &&
+ dirs_sym_link == o.dirs_sym_link;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder res = new StringBuilder();
+ res.append( "- traverse_options ").append(topts).append("\n");
+ res.append( "- total_real ").append(total_real).append("\n");
+ res.append( "- total_sym_links_existing ").append(total_sym_links_existing).append("\n");
+ res.append( "- total_sym_links_not_existing ").append(total_sym_links_not_existing).append("\n");
+ res.append( "- total_no_access ").append(total_no_access).append("\n");
+ res.append( "- total_not_existing ").append(total_not_existing).append("\n");
+ res.append( "- total_file_bytes ").append(String.format("%,d", total_file_bytes)).append("\n");
+ res.append( "- files_real ").append(files_real).append("\n");
+ res.append( "- files_sym_link ").append(files_sym_link).append("\n");
+ res.append( "- dirs_real ").append(dirs_real).append("\n");
+ res.append( "- dirs_sym_link ").append(dirs_sym_link).append("\n");
+ return res.toString();
+ }
+ }
+
+ public static class PathStatsVisitor implements FileUtil.PathVisitor {
+ private final VisitorStats stats;
+
+ public PathStatsVisitor(final VisitorStats stats_) {
+ stats = stats_;
+ }
+
+ @Override
+ public boolean visit(final TraverseEvent tevt, final FileStats item_stats) {
+ // PrintUtil.fprintf_td(System.err, "add: item_stats "+item_stats+", tevt "+tevt+"\n");
+ stats.add(item_stats);
+ return true;
+ }
+ }
+
+ static class source_visitor_params {
+ public String title;
+ public String source_folder_path;
+ public FileStats dest;
+ public source_visitor_params(final String t, final String sfp, final FileStats d) {
+ title = t;
+ source_folder_path = sfp;
+ dest = d;
+ }
+ };
+
+ static class dest_visitor_params {
+ public String title;
+ public String source_folder_path;
+ public String dest_folder_path;
+ public String source_basename;
+ public FileStats stats;
+ public boolean match;
+ public dest_visitor_params(final String t, final String sfp, final String dfp, final String sb, final FileStats s) {
+ title = t;
+ source_folder_path = sfp;
+ dest_folder_path = dfp;
+ source_basename = sb;
+ stats = s;
+ match = false;
+ }
+ };
+
+ public void testxx_copy_r_p(final String title, final FileStats source, final int source_added_dead_links, final String dest) {
+ Assert.assertTrue( true == source.exists() );
+
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.recursive);
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.sync);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ FileUtil.remove(dest, topts_rec);
+
+ Assert.assertTrue( true == FileUtil.copy(source.path(), dest, copts) );
+ }
+ final FileStats dest_stats = new FileStats(dest);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_dir() );
+
+ {
+ final TraverseOptions topts = new TraverseOptions();
+ topts.set(TraverseOptions.Bit.recursive);
+ topts.set(TraverseOptions.Bit.dir_entry);
+
+ final VisitorStats stats = new VisitorStats(topts);
+ final VisitorStats stats_copy = new VisitorStats(topts);
+
+ final PathStatsVisitor pv_orig = new PathStatsVisitor(stats);
+ final PathStatsVisitor pv_copy = new PathStatsVisitor(stats_copy);
+
+ Assert.assertTrue( true == FileUtil.visit(source, topts, pv_orig) );
+ Assert.assertTrue( true == FileUtil.visit(dest_stats, topts, pv_copy) );
+
+ PrintUtil.fprintf_td(System.err, "%s: copy %s, traverse %s\n", title, copts, topts);
+ PrintUtil.fprintf_td(System.err, "%s: source visitor stats\n%s\n", title, stats);
+ PrintUtil.fprintf_td(System.err, "%s: destination visitor stats\n%s\n", title, stats_copy);
+
+ Assert.assertTrue( 7 == stats.total_real );
+ Assert.assertTrue( 10 - source_added_dead_links == stats.total_sym_links_existing );
+ Assert.assertTrue( 4 + source_added_dead_links == stats.total_sym_links_not_existing );
+ Assert.assertTrue( 0 == stats.total_no_access );
+ Assert.assertTrue( 4 + source_added_dead_links == stats.total_not_existing );
+ Assert.assertTrue( 60 == stats.total_file_bytes );
+ Assert.assertTrue( 4 == stats.files_real );
+ Assert.assertTrue( 9 - source_added_dead_links == stats.files_sym_link );
+ Assert.assertTrue( 3 == stats.dirs_real );
+ Assert.assertTrue( 1 == stats.dirs_sym_link );
+
+ Assert.assertTrue( 7 == stats_copy.total_real );
+ Assert.assertTrue( 9 == stats_copy.total_sym_links_existing );
+ Assert.assertTrue( 5 == stats_copy.total_sym_links_not_existing ); // symlink ../README.txt + 4 dead_link*
+ Assert.assertTrue( 0 == stats_copy.total_no_access );
+ Assert.assertTrue( 5 == stats_copy.total_not_existing ); // symlink ../README.txt + 4 dead_link*
+ Assert.assertTrue( 60 == stats_copy.total_file_bytes );
+ Assert.assertTrue( 4 == stats_copy.files_real );
+ Assert.assertTrue( 8 == stats_copy.files_sym_link );
+ Assert.assertTrue( 3 == stats_copy.dirs_real );
+ Assert.assertTrue( 1 == stats_copy.dirs_sym_link );
+ }
+ {
+ // compare each file in detail O(n*n)
+ final TraverseOptions topts = new TraverseOptions();
+ topts.set(TraverseOptions.Bit.recursive);
+ topts.set(TraverseOptions.Bit.dir_entry);
+
+ final source_visitor_params svp = new source_visitor_params(title, source.path(), dest_stats);
+ final FileUtil.PathVisitor pv1 = new FileUtil.PathVisitor() {
+ @Override
+ public boolean visit(final TraverseEvent tevt1, final FileStats element_stats1) {
+ final dest_visitor_params dvp = new dest_visitor_params(svp.title, svp.source_folder_path, svp.dest.path(), FileUtil.basename(element_stats1.path() ), element_stats1);
+ final FileUtil.PathVisitor pv2 = new FileUtil.PathVisitor() {
+ @Override
+ public boolean visit(final TraverseEvent tevt2, final FileStats element_stats2) {
+ final String path2 = element_stats2.path();
+ final String basename2 = FileUtil.basename( path2 );
+ final String source_folder_basename = FileUtil.basename( dvp.source_folder_path );
+ if( basename2.equals( dvp.source_basename ) ||
+ ( source_folder_basename.equals( dvp.source_basename ) && dvp.dest_folder_path.equals( path2 ) )
+ )
+ {
+ boolean attr_equal, bit_equal;
+ if( "README_slink08_relext.txt".equals(basename2) || 0 == basename2.indexOf("dead_link") ) {
+ // symlink to ../README.txt not existent on target
+ // dead_link* files intentionally not existant
+ attr_equal = element_stats2.is_link() &&
+ !element_stats2.exists();
+
+ bit_equal = true; // pretend
+ } else {
+ attr_equal =
+ element_stats2.mode().equals( dvp.stats.mode() ) &&
+ element_stats2.atime().equals( dvp.stats.atime() ) &&
+ element_stats2.mtime().equals( dvp.stats.mtime() ) &&
+ element_stats2.uid() == dvp.stats.uid() &&
+ element_stats2.gid() == dvp.stats.gid() &&
+ element_stats2.size() == dvp.stats.size();
+
+ if( dvp.stats.is_file() ) {
+ bit_equal = FileUtil.compare(dvp.stats.path(), element_stats2.path(), true);
+ } else {
+ bit_equal = true; // pretend
+ }
+ }
+ dvp.match = attr_equal && bit_equal;
+ PrintUtil.fprintf_td(System.err, "%s.check: '%s', match [attr %b, bit %b -> %b]\n\t source %s\n\t dest__ %s\n\n",
+ dvp.title, basename2, attr_equal, bit_equal, dvp.match,
+ dvp.stats,
+ element_stats2);
+ return false; // done
+ } else {
+ return true; // continue search
+ }
+ } };
+ if( FileUtil.visit(svp.dest, topts, pv2) ) {
+ PrintUtil.fprintf_td(System.err, "%s.check: '%s', not found!\n\t source %s\n\n",
+ svp.title, dvp.source_basename, element_stats1);
+ return false; // not found, abort
+ } else {
+ // found
+ if( dvp.match ) {
+ return true; // found and matching, continue
+ } else {
+ return false; // found not matching, abort
+ }
+ }
+ } };
+ Assert.assertTrue( true == FileUtil.visit(source, topts, pv1) );
+ }
+ Assert.assertTrue( true == FileUtil.remove(dest, topts_rec) );
+ }
+
+}
diff --git a/test/java/jau/test/fs/TestFileUtils01.java b/test/java/jau/test/fs/TestFileUtils01.java
new file mode 100644
index 0000000..0958695
--- /dev/null
+++ b/test/java/jau/test/fs/TestFileUtils01.java
@@ -0,0 +1,1244 @@
+/*
+ * 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 jau.test.fs;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+
+import org.jau.fs.CopyOptions;
+import org.jau.fs.DirItem;
+import org.jau.fs.FMode;
+import org.jau.fs.FileStats;
+import org.jau.fs.FileUtil;
+import org.jau.fs.TraverseOptions;
+import org.jau.io.PrintUtil;
+import org.junit.Assert;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import jau.pkg.PlatformRuntime;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestFileUtils01 extends FileUtilBaseTest {
+ static final boolean DEBUG = false;
+
+ @Test(timeout = 10000)
+ public final void test01_cwd() {
+ PlatformRuntime.checkInitialized();
+ final String cwd = FileUtil.get_cwd();
+ PrintUtil.println(System.err, "test01_cwd: cwd "+cwd);
+ Assert.assertTrue( 0 < cwd.length() );
+ final int idx = cwd.indexOf("/jaulib/");
+ Assert.assertTrue( 0 < idx );
+ Assert.assertTrue( idx < cwd.length() );
+ Assert.assertTrue( idx > 0 );
+ }
+
+ /**
+ *
+ */
+ @Test(timeout = 10000)
+ public final void test02_dirname() {
+ PlatformRuntime.checkInitialized();
+ {
+ final String pathname0 = "/";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "/" ) );
+ }
+ {
+ {
+ final String pathname0 = "lala.txt";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "." ) );
+ }
+ {
+ final String pathname0 = "lala";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "." ) );
+ }
+ {
+ final String pathname0 = "lala/";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "." ) );
+ }
+ }
+ {
+ final String pathname0 = "/lala.txt";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "/" ) );
+ }
+ {
+ final String pathname0 = "blabla/jaulib/test/sub.txt";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "blabla/jaulib/test" ) );
+ }
+ {
+ final String pathname0 = "blabla/jaulib/test/sub";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "blabla/jaulib/test" ) );
+ }
+ {
+ final String pathname0 = "blabla/jaulib/test/";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "blabla/jaulib" ) );
+ }
+ {
+ final String pathname0 = "blabla/jaulib/test";
+ final String pathname1 = FileUtil.dirname(pathname0);
+ PrintUtil.println(System.err, "test02_dirname: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "blabla/jaulib" ) );
+ }
+ }
+
+ @Test(timeout = 10000)
+ public final void test03_basename() {
+ PlatformRuntime.checkInitialized();
+ {
+ final String pathname0 = "/";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "/" ) );
+ }
+ {
+ {
+ final String pathname0 = "lala.txt";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "lala.txt" ) );
+ }
+ {
+ final String pathname0 = "lala";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "lala" ) );
+ }
+ {
+ final String pathname0 = "lala/";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "lala" ) );
+ }
+ }
+ {
+ final String pathname0 = "/lala.txt";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "lala.txt" ) );
+ }
+ {
+ final String pathname0 = "blabla/jaulib/test/sub.txt";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "sub.txt" ) );
+ }
+
+ {
+ final String pathname0 = "blabla/jaulib/test/";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "test" ) );
+ }
+
+ {
+ final String pathname0 = "blabla/jaulib/test";
+ final String pathname1 = FileUtil.basename(pathname0);
+ PrintUtil.println(System.err, "test03_basename: cwd "+pathname0+" -> "+pathname1);
+ Assert.assertTrue( 0 < pathname1.length() );
+ Assert.assertTrue( pathname1.equals( "test" ) );
+ }
+ }
+
+ @Test(timeout = 10000)
+ public final void test04_dir_item() {
+ PlatformRuntime.checkInitialized();
+ {
+ final String dirname_ = "";
+ final DirItem di = new DirItem(dirname_);
+ PrintUtil.println(System.err, "test04_dir_item: 01 '"+dirname_+"' -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( ".".equals( di.basename() ) );
+ Assert.assertTrue( ".".equals( di.path() ) );
+ }
+ {
+ final String dirname_ =".";
+ final DirItem di = new DirItem(dirname_);
+ PrintUtil.println(System.err, "test04_dir_item: 02 '"+dirname_+"' -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( ".".equals( di.basename() ) );
+ Assert.assertTrue( ".".equals( di.path() ) );
+ }
+ {
+ final String dirname_ ="/";
+ final DirItem di = new DirItem(dirname_);
+ PrintUtil.println(System.err, "test04_dir_item: 03 '"+dirname_+"' -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/".equals( di.dirname() ) );
+ Assert.assertTrue( ".".equals( di.basename() ) );
+ Assert.assertTrue( "/".equals( di.path() ) );
+ }
+
+ {
+ final String path1_ = "lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 10 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "lala/";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 11 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "lala".equals( di.path() ) );
+ }
+
+ {
+ final String path1_ = "/lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 12 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/lala".equals( di.path() ) );
+ }
+
+ {
+ final String path1_ = "dir0/lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 20 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/lala/";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 21 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "/dir0/lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 22 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "/dir0/lala/";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 23 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/dir0/lala".equals( di.path() ) );
+ }
+
+
+ {
+ final String path1_ = "/dir0/../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 30 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 31 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "../../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 32 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "../..".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "../../lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "./../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 33 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "..".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "../lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/../../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 34 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "..".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "../lala".equals( di.path() ) );
+ }
+
+ {
+ final String path1_ = "dir0/dir1/../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 40 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "/dir0/dir1/../lala/";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 41 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/dir1/../bbb/ccc/../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 42 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0/bbb".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/bbb/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/dir1/bbb/../../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 43 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/dir1/bbb/../../../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 44 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/dir1/bbb/../../../../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 45 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "..".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "../lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/dir1/bbb/../../lala/..";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 46 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( ".".equals( di.dirname() ) );
+ Assert.assertTrue( "dir0".equals( di.basename() ) );
+ Assert.assertTrue( "dir0".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/./dir1/./bbb/../.././lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 50 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "dir0/./dir1/./bbb/../.././lala/.";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 51 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "./dir0/./dir1/./bbb/../.././lala/.";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 51 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "dir0/lala".equals( di.path() ) );
+ }
+ {
+ final String path1_ = "/./dir0/./dir1/./bbb/../.././lala/.";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 52 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/dir0".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/dir0/lala".equals( di.path() ) );
+ }
+
+ {
+ final String path1_ = "../../test_data/file_01_slink09R1.txt";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 60 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "../../test_data".equals( di.dirname() ) );
+ Assert.assertTrue( "file_01_slink09R1.txt".equals( di.basename() ) );
+ Assert.assertTrue( "../../test_data/file_01_slink09R1.txt".equals( di.path() ) );
+ }
+
+ {
+ // Error
+ final String path1_ = "/../lala";
+ final DirItem di = new DirItem(path1_);
+ PrintUtil.println(System.err, "test04_dir_item: 99 '"+path1_+" -> "+di.toString()+" -> '"+di.path()+"'\n");
+ Assert.assertTrue( "/..".equals( di.dirname() ) );
+ Assert.assertTrue( "lala".equals( di.basename() ) );
+ Assert.assertTrue( "/../lala".equals( di.path() ) );
+ }
+ }
+
+ @Test(timeout = 10000)
+ public final void test05_file_stat() {
+ PlatformRuntime.checkInitialized();
+ PrintUtil.println(System.err, "test05_file_stat\n");
+
+ {
+ final FileStats stats = new FileStats(project_root_ext+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 01: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 01: fields %s\n", stats.fields());
+ if( stats.exists() ) {
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( stats.is_file() );
+ Assert.assertTrue( !stats.is_link() );
+ Assert.assertTrue( 15 == stats.size() );
+ }
+ }
+
+ FileStats proot_stats = new FileStats(project_root1);
+ if( !proot_stats.exists() ) {
+ proot_stats = new FileStats(project_root2);
+ }
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 11: %s\n", proot_stats);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 11: fields %s\n", proot_stats.fields());
+ Assert.assertTrue( true == proot_stats.exists() );
+ Assert.assertTrue( true == proot_stats.is_dir() );
+
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 12: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 12: fields %s\n", stats.fields());
+ Assert.assertTrue( stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( stats.is_file() );
+ Assert.assertTrue( !stats.is_link() );
+ Assert.assertTrue( 15 == stats.size() );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 12: final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 0 == link_count[0] );
+ Assert.assertTrue( final_target.equals( stats ) );
+
+ {
+ final FileStats stats2 = new FileStats(proot_stats.path()+"/file_01.txt");
+ Assert.assertTrue( stats2.exists() );
+ Assert.assertTrue( stats2.has_access() );
+ Assert.assertTrue( !stats2.is_dir() );
+ Assert.assertTrue( stats2.is_file() );
+ Assert.assertTrue( !stats2.is_link() );
+ Assert.assertTrue( 15 == stats2.size() );
+ Assert.assertEquals( stats, stats2 );
+ }
+ {
+ final FileStats stats2 = new FileStats(proot_stats.path()+"/dir_01/file_02.txt");
+ Assert.assertTrue( stats2.exists() );
+ Assert.assertTrue( stats2.has_access() );
+ Assert.assertTrue( !stats2.is_dir() );
+ Assert.assertTrue( stats2.is_file() );
+ Assert.assertTrue( !stats2.is_link() );
+ Assert.assertNotEquals( stats, stats2 );
+ }
+ }
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/dir_01");
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 13: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 13: fields %s\n", stats.fields());
+ Assert.assertTrue( stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( stats.is_dir() );
+ Assert.assertTrue( !stats.is_file() );
+ Assert.assertTrue( !stats.is_link() );
+ Assert.assertTrue( 0 == stats.size() );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 13: final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 0 == link_count[0] );
+ Assert.assertTrue( final_target.equals( stats ) );
+ }
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/does_not_exist");
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 14: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 14: fields %s\n", stats.fields());
+ Assert.assertTrue( !stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( !stats.is_file() );
+ Assert.assertTrue( !stats.is_link() );
+ Assert.assertTrue( 0 == stats.size() );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "test05_file_stat: 14: final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 0 == link_count[0] );
+ Assert.assertTrue( final_target.equals( stats ) );
+ }
+ }
+
+ @Test(timeout = 10000)
+ public final void test06_file_stat_symlinks() {
+ PlatformRuntime.checkInitialized();
+ PrintUtil.println(System.err, "test06_file_stat_symlinks\n");
+
+ FileStats proot_stats = new FileStats(project_root1);
+ if( !proot_stats.exists() ) {
+ proot_stats = new FileStats(project_root2);
+ }
+ Assert.assertTrue( true == proot_stats.exists() );
+ Assert.assertTrue( true == proot_stats.is_dir() );
+
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/file_01_slink01.txt");
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 13: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 13: fields %s\n", stats.fields());
+ Assert.assertTrue( stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( stats.is_file() );
+ Assert.assertTrue( stats.is_link() );
+ Assert.assertTrue( 15 == stats.size() );
+ Assert.assertTrue( null != stats.link_target_path() );
+ Assert.assertTrue( "file_01.txt".equals( stats.link_target_path() ) );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "- final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 1 == link_count[0] );
+ Assert.assertTrue( final_target != stats );
+ Assert.assertTrue( (proot_stats.path()+"/file_01.txt").equals( final_target.path() ) );
+
+ Assert.assertTrue( null != stats.link_target() );
+ final FileStats link_target = stats.link_target();
+ Assert.assertTrue( null != link_target );
+ PrintUtil.fprintf_td(System.err, "- link_target %s\n", link_target);
+ Assert.assertTrue( final_target.equals( link_target ) );
+ Assert.assertTrue( !link_target.is_dir() );
+ Assert.assertTrue( link_target.is_file() );
+ Assert.assertTrue( !link_target.is_link() );
+ Assert.assertTrue( null == link_target.link_target_path() );
+ Assert.assertTrue( null == link_target.link_target() );
+ }
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/fstab_slink07_absolute");
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 14: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 14: fields %s\n", stats.fields());
+ Assert.assertTrue( stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( stats.is_file() );
+ Assert.assertTrue( stats.is_link() );
+ Assert.assertTrue( 20 < stats.size() ); // greater than basename
+ Assert.assertTrue( null != stats.link_target_path() );
+ Assert.assertTrue( "/etc/fstab".equals( stats.link_target_path() ) );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "- final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 1 == link_count[0] );
+ Assert.assertTrue( final_target != stats );
+ Assert.assertTrue( "/etc/fstab".equals( final_target.path() ) );
+
+ Assert.assertTrue( null != stats.link_target() );
+ final FileStats link_target = stats.link_target();
+ Assert.assertTrue( null != link_target );
+ PrintUtil.fprintf_td(System.err, "- link_target %s\n", link_target);
+ Assert.assertTrue( final_target.equals( link_target ) );
+ Assert.assertTrue( !link_target.is_dir() );
+ Assert.assertTrue( link_target.is_file() );
+ Assert.assertTrue( !link_target.is_link() );
+ Assert.assertTrue( null == link_target.link_target_path() );
+ Assert.assertTrue( null == link_target.link_target() );
+ }
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/file_01_slink10R2.txt"); // -> file_01_slink09R1.txt -> file_01_slink01.txt -> file_01.txt
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 20: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 20: fields %s\n", stats.fields());
+ Assert.assertTrue( stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( stats.is_file() );
+ Assert.assertTrue( stats.is_link() );
+ Assert.assertTrue( 15 == stats.size() );
+ Assert.assertTrue( null != stats.link_target_path() );
+ Assert.assertTrue( "file_01_slink09R1.txt".equals( stats.link_target_path() ) );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "- final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 3 == link_count[0] );
+ Assert.assertTrue( final_target != stats );
+ Assert.assertTrue( (proot_stats.path()+"/file_01.txt").equals( final_target.path() ) );
+
+ Assert.assertTrue( null != stats.link_target() );
+ final FileStats link_target1 = stats.link_target();
+ PrintUtil.fprintf_td(System.err, "- link_target1 %s\n", link_target1);
+ Assert.assertTrue( final_target != link_target1 );
+ Assert.assertTrue( (proot_stats.path()+"/file_01_slink09R1.txt").equals( link_target1.path() ) );
+ Assert.assertTrue( 15 == link_target1.size() );
+ Assert.assertTrue( !link_target1.is_dir() );
+ Assert.assertTrue( link_target1.is_file() );
+ Assert.assertTrue( link_target1.is_link() );
+ Assert.assertTrue( null != link_target1.link_target_path() );
+ Assert.assertTrue( "file_01_slink01.txt".equals( link_target1.link_target_path() ) );
+ {
+ final FileStats link_target2 = link_target1.link_target();
+ Assert.assertTrue( null != link_target2 );
+ PrintUtil.fprintf_td(System.err, " - link_target2 %s\n", link_target2);
+ Assert.assertTrue( final_target != link_target2 );
+ Assert.assertTrue( link_target1 != link_target2 );
+ Assert.assertTrue( (proot_stats.path()+"/file_01_slink01.txt").equals( link_target2.path() ) );
+ Assert.assertTrue( 15 == link_target2.size() );
+ Assert.assertTrue( !link_target2.is_dir() );
+ Assert.assertTrue( link_target2.is_file() );
+ Assert.assertTrue( link_target2.is_link() );
+ Assert.assertTrue( null != link_target2.link_target_path() );
+ Assert.assertTrue( "file_01.txt".equals( link_target2.link_target_path() ) );
+
+ final FileStats link_target3 = link_target2.link_target();
+ Assert.assertTrue( null != link_target3 );
+ PrintUtil.fprintf_td(System.err, " - link_target3 %s\n", link_target3);
+ Assert.assertTrue( final_target.equals( link_target3 ) );
+ Assert.assertTrue( link_target1 != link_target3 );
+ Assert.assertTrue( link_target2 != link_target3 );
+ Assert.assertTrue( 15 == link_target3.size() );
+ Assert.assertTrue( !link_target3.is_dir() );
+ Assert.assertTrue( link_target3.is_file() );
+ Assert.assertTrue( !link_target3.is_link() );
+ Assert.assertTrue( null == link_target3.link_target_path() );
+ Assert.assertTrue( null == link_target3.link_target() );
+ }
+ }
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/dead_link23"); // -> not_existing_file
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 30: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 30: fields %s\n", stats.fields());
+ Assert.assertTrue( !stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( !stats.is_file() );
+ Assert.assertTrue( stats.is_link() );
+ Assert.assertTrue( 0 == stats.size() );
+ Assert.assertTrue( null != stats.link_target_path() );
+ Assert.assertTrue( "not_existing_file".equals( stats.link_target_path() ) );
+ Assert.assertTrue( null == stats.link_target() );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "- final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 0 == link_count[0] );
+ Assert.assertTrue( final_target.equals( stats ) );
+ }
+ {
+ final FileStats stats = new FileStats(proot_stats.path()+"/dead_link22"); // LOOP: dead_link22 -> dead_link21 -> dead_link20 -> dead_link22 ...
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 31: %s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test06_file_stat_symlinks: 31: fields %s\n", stats.fields());
+ Assert.assertTrue( !stats.exists() );
+ Assert.assertTrue( stats.has_access() );
+ Assert.assertTrue( !stats.is_dir() );
+ Assert.assertTrue( !stats.is_file() );
+ Assert.assertTrue( stats.is_link() );
+ Assert.assertTrue( 0 == stats.size() );
+ Assert.assertTrue( null != stats.link_target_path() );
+ Assert.assertTrue( "dead_link21".equals( stats.link_target_path() ) );
+ Assert.assertTrue( null == stats.link_target() );
+
+ final long link_count[] = { 0 };
+ final FileStats final_target = stats.final_target(link_count);
+ PrintUtil.fprintf_td(System.err, "- final_target (%d link count): %s\n", link_count[0], final_target);
+ Assert.assertTrue( 0 == link_count[0] );
+ Assert.assertTrue( final_target.equals( stats ) );
+ }
+ }
+
+ @Test(timeout = 10000)
+ public final void test10_mkdir() {
+ PlatformRuntime.checkInitialized();
+ PrintUtil.println(System.err, "test10_mkdir\n");
+
+ FileUtil.remove(root, topts_rec); // start fresh
+ {
+ final FileStats root_stats = new FileStats(root);
+ PrintUtil.println(System.err, "root_stats.pre: "+root_stats);
+ Assert.assertTrue( !root_stats.exists() );
+ Assert.assertTrue( root_stats.has_access() );
+ Assert.assertTrue( !root_stats.is_dir() );
+ Assert.assertTrue( !root_stats.is_file() );
+ Assert.assertTrue( !root_stats.is_link() );
+ }
+ final FMode mode_def_dir = new FMode(FMode.Bit.def_dir_prot.value);
+ Assert.assertTrue( true == FileUtil.mkdir(root, mode_def_dir) );
+ {
+ final FileStats root_stats = new FileStats(root);
+ PrintUtil.println(System.err, "root_stats.post: "+root_stats);
+ Assert.assertTrue( root_stats.exists() );
+ Assert.assertTrue( root_stats.has_access() );
+ Assert.assertTrue( root_stats.is_dir() );
+ Assert.assertTrue( !root_stats.is_file() );
+ Assert.assertTrue( !root_stats.is_link() );
+ }
+ Assert.assertTrue( false == FileUtil.remove(root, topts_none) );
+ Assert.assertTrue( true == FileUtil.remove(root, topts_rec) );
+ }
+
+ @Test(timeout = 10000)
+ public void test11_touch() {
+ PlatformRuntime.checkInitialized();
+ PrintUtil.println(System.err, "test11_touch\n");
+ final String file_01 = root+"/data01.txt";
+ final String file_02 = root+"/data02.txt";
+ Assert.assertTrue( true == FileUtil.mkdir(root, FMode.def_dir) );
+ {
+ final FileStats root_stats = new FileStats(root);
+ PrintUtil.println(System.err, "root_stats1.post: "+root_stats);
+ Assert.assertTrue( root_stats.exists() );
+ Assert.assertTrue( root_stats.has_access() );
+ Assert.assertTrue( root_stats.is_dir() );
+ Assert.assertTrue( !root_stats.is_file() );
+ Assert.assertTrue( !root_stats.is_link() );
+ }
+
+ Assert.assertTrue( true == FileUtil.touch(file_01, FMode.def_dir) );
+ {
+ final Instant now = Instant.now();
+ final FileStats file_stats = new FileStats(file_01);
+ PrintUtil.println(System.err, "file_stats2.post: "+file_stats);
+ final Instant btime = file_stats.btime();
+ final Instant atime = file_stats.atime();
+ final long atime_td_ms = atime.until(now, ChronoUnit.MILLIS);
+ final Instant mtime = file_stats.mtime();
+ final long mtime_td_ms = mtime.until(now, ChronoUnit.MILLIS);
+ PrintUtil.println(System.err, "now: "+now.atZone(ZoneOffset.UTC));
+ PrintUtil.println(System.err, "btime: "+btime.atZone(ZoneOffset.UTC));
+ PrintUtil.println(System.err, "atime: "+atime.atZone(ZoneOffset.UTC)+", td_now "+atime_td_ms+"ms");
+ PrintUtil.println(System.err, "mtime: "+mtime.atZone(ZoneOffset.UTC)+", td_now "+mtime_td_ms+"ms");
+ Assert.assertTrue( file_stats.exists() );
+ Assert.assertTrue( file_stats.has_access() );
+ Assert.assertTrue( !file_stats.is_dir() );
+ Assert.assertTrue( file_stats.is_file() );
+ Assert.assertTrue( !file_stats.is_link() );
+ if( file_stats.has( FileStats.Field.Type.atime ) ) {
+ Assert.assertTrue( 1000 >= atime_td_ms );
+ }
+ if( file_stats.has( FileStats.Field.Type.mtime ) ) {
+ Assert.assertTrue( 1000 >= mtime_td_ms );
+ }
+ }
+
+ Assert.assertTrue( true == FileUtil.touch(file_02, FMode.def_dir ) );
+ {
+ final Instant now = Instant.now();
+ final FileStats file_stats_pre = new FileStats(file_02);
+ final Instant btime_pre = file_stats_pre.btime();
+ final Instant atime_pre = file_stats_pre.atime();
+ final long atime_td_ms = atime_pre.until(now, ChronoUnit.MILLIS);
+ final Instant mtime_pre = file_stats_pre.mtime();
+ final long mtime_td_ms = mtime_pre.until(now, ChronoUnit.MILLIS);
+ PrintUtil.println(System.err, "now : "+now.atZone(ZoneOffset.UTC));
+ PrintUtil.println(System.err, "btime.pre: "+btime_pre.atZone(ZoneOffset.UTC));
+ PrintUtil.println(System.err, "atime.pre: "+atime_pre.atZone(ZoneOffset.UTC)+", td_now "+atime_td_ms+"ms");
+ PrintUtil.println(System.err, "mtime.pre: "+mtime_pre.atZone(ZoneOffset.UTC)+", td_now "+mtime_td_ms+"ms");
+ if( file_stats_pre.has( FileStats.Field.Type.atime ) ) {
+ Assert.assertTrue( 1000 >= atime_td_ms );
+ }
+ if( file_stats_pre.has( FileStats.Field.Type.mtime ) ) {
+ Assert.assertTrue( 1000 >= mtime_td_ms );
+ }
+
+ // final jau::fraction_timespec ts_20200101( 1577836800_s + 0_h); // 2020-01-01 00:00:00
+ final Instant ts_20200101 = Instant.ofEpochSecond(1577836800); // 2020-01-01 00:00:00
+ final Instant atime_set = ts_20200101.plus( 1, ChronoUnit.DAYS).plus(10, ChronoUnit.HOURS);
+ final Instant mtime_set = ts_20200101.plus( 31, ChronoUnit.DAYS).plus(10, ChronoUnit.HOURS);
+ PrintUtil.println(System.err, "atime.set: "+atime_set.atZone(ZoneOffset.UTC)+", "+atime_set.atZone(ZoneOffset.UTC));
+ PrintUtil.println(System.err, "mtime.set: "+mtime_set.atZone(ZoneOffset.UTC)+", "+mtime_set.atZone(ZoneOffset.UTC));
+ Assert.assertTrue( true == FileUtil.touch(file_02, atime_set, mtime_set, FMode.def_file) );
+
+ final FileStats file_stats_post = new FileStats(file_02);
+ final Instant atime_post = file_stats_post.atime();
+ final Instant mtime_post = file_stats_post.mtime();
+ PrintUtil.println(System.err, "atime.post: "+atime_post.atZone(ZoneOffset.UTC)+", "+atime_post.atZone(ZoneOffset.UTC));
+ PrintUtil.println(System.err, "mtime.post: "+mtime_post.atZone(ZoneOffset.UTC)+", "+mtime_post.atZone(ZoneOffset.UTC));
+ PrintUtil.fprintf_td(System.err, "test11_touch: 03: %s\n", file_stats_post);
+ {
+ Assert.assertTrue( file_stats_post.exists() );
+ Assert.assertTrue( file_stats_post.has_access() );
+ Assert.assertTrue( !file_stats_post.is_dir() );
+ Assert.assertTrue( file_stats_post.is_file() );
+ Assert.assertTrue( !file_stats_post.is_link() );
+ if( file_stats_post.has( FileStats.Field.Type.atime ) ) {
+ Assert.assertTrue( atime_set.equals( file_stats_post.atime() ) );
+ }
+ if( file_stats_post.has( FileStats.Field.Type.mtime ) ) {
+ Assert.assertTrue( mtime_set.equals( file_stats_post.mtime() ) );
+ }
+ }
+ }
+
+ Assert.assertTrue( true == FileUtil.remove(root, topts_rec) );
+ }
+
+ @Test(timeout = 10000)
+ public void test20_visit() {
+ PlatformRuntime.checkInitialized();
+ PrintUtil.println(System.err, "test20_visit\n");
+
+ final String sub_dir1 = root+"/sub1";
+ final String sub_dir2 = root+"/sub2";
+ final String sub_dir3 = root+"/sub1/sub3";
+
+ Assert.assertTrue( true == FileUtil.mkdir(root, FMode.def_dir) );
+ Assert.assertTrue( true == FileUtil.touch(root+"/data01.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.touch(root+"/data02.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.mkdir(sub_dir1, FMode.def_dir) );
+ Assert.assertTrue( true == FileUtil.mkdir(sub_dir2, FMode.def_dir) );
+ Assert.assertTrue( true == FileUtil.mkdir(sub_dir3, FMode.def_dir) );
+ Assert.assertTrue( true == FileUtil.touch(sub_dir1+"/data03.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.touch(sub_dir1+"/data04.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.touch(sub_dir2+"/data05.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.touch(sub_dir2+"/data06.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.touch(sub_dir3+"/data07.txt", FMode.def_file) );
+ Assert.assertTrue( true == FileUtil.touch(sub_dir3+"/data08.txt", FMode.def_file) );
+
+ final TraverseOptions topts_R_FSL_PDL = new TraverseOptions();
+ topts_R_FSL_PDL.set(TraverseOptions.Bit.recursive);
+ topts_R_FSL_PDL.set(TraverseOptions.Bit.follow_symlinks);
+ topts_R_FSL_PDL.set(TraverseOptions.Bit.dir_exit);
+ final VisitorStats stats_R_FSL_PDL = new VisitorStats(topts_R_FSL_PDL);
+ {
+ final PathStatsVisitor pv = new PathStatsVisitor(stats_R_FSL_PDL);
+ Assert.assertTrue( true == FileUtil.visit(root, topts_R_FSL_PDL, pv) );
+ PrintUtil.fprintf_td(System.err, "test20_visit[R, FSL, PDL]: %s\n%s\n", topts_R_FSL_PDL, stats_R_FSL_PDL);
+ Assert.assertTrue( 12 == stats_R_FSL_PDL.total_real );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.total_sym_links_existing );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.total_sym_links_not_existing );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.total_no_access );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.total_not_existing );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.total_file_bytes );
+ Assert.assertTrue( 8 == stats_R_FSL_PDL.files_real );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.files_sym_link );
+ Assert.assertTrue( 4 == stats_R_FSL_PDL.dirs_real );
+ Assert.assertTrue( 0 == stats_R_FSL_PDL.dirs_sym_link );
+ }
+ final TraverseOptions topts_R_FSL = new TraverseOptions();
+ topts_R_FSL.set(TraverseOptions.Bit.recursive);
+ topts_R_FSL.set(TraverseOptions.Bit.follow_symlinks);
+ topts_R_FSL.set(TraverseOptions.Bit.dir_entry);
+ final VisitorStats stats_R_FSL = new VisitorStats(topts_R_FSL);
+ {
+ final PathStatsVisitor pv = new PathStatsVisitor(stats_R_FSL);
+ Assert.assertTrue( true == FileUtil.visit(root, topts_R_FSL, pv) );
+ PrintUtil.fprintf_td(System.err, "test20_visit[R, FSL]: %s\n%s\n", topts_R_FSL, stats_R_FSL);
+ Assert.assertTrue( stats_R_FSL_PDL.equals( stats_R_FSL ) );
+ }
+ Assert.assertTrue( true == FileUtil.remove(root, topts_rec) );
+ }
+
+ @Test(timeout = 10000)
+ public void test22_visit_symlinks() {
+ PlatformRuntime.checkInitialized();
+ PrintUtil.println(System.err, "test22_visit_symlinks\n");
+
+ FileStats proot_stats = new FileStats(project_root1);
+ if( !proot_stats.exists() ) {
+ proot_stats = new FileStats(project_root2);
+ }
+ Assert.assertTrue( true == proot_stats.exists() );
+ Assert.assertTrue( true == proot_stats.is_dir() );
+
+ {
+ final TraverseOptions topts = new TraverseOptions();
+ topts.set(TraverseOptions.Bit.recursive);
+ topts.set(TraverseOptions.Bit.dir_exit);
+ final VisitorStats stats = new VisitorStats(topts);
+
+ final PathStatsVisitor pv = new PathStatsVisitor(stats);
+ Assert.assertTrue( true == FileUtil.visit(proot_stats, topts, pv) );
+ PrintUtil.fprintf_td(System.err, "test22_visit[R]: %s\n%s\n", topts, stats);
+ Assert.assertTrue( 7 == stats.total_real );
+ Assert.assertTrue( 10 == stats.total_sym_links_existing );
+ Assert.assertTrue( 4 == stats.total_sym_links_not_existing );
+ Assert.assertTrue( 0 == stats.total_no_access );
+ Assert.assertTrue( 4 == stats.total_not_existing );
+ Assert.assertTrue( 60 == stats.total_file_bytes );
+ Assert.assertTrue( 4 == stats.files_real );
+ Assert.assertTrue( 9 == stats.files_sym_link );
+ Assert.assertTrue( 3 == stats.dirs_real );
+ Assert.assertTrue( 1 == stats.dirs_sym_link );
+ }
+
+ {
+ final TraverseOptions topts = new TraverseOptions();
+ topts.set(TraverseOptions.Bit.recursive);
+ topts.set(TraverseOptions.Bit.dir_entry);
+ topts.set(TraverseOptions.Bit.follow_symlinks);
+ final VisitorStats stats = new VisitorStats(topts);
+
+ final PathStatsVisitor pv = new PathStatsVisitor(stats);
+ Assert.assertTrue( true == FileUtil.visit(proot_stats, topts, pv) );
+ PrintUtil.fprintf_td(System.err, "test22_visit[R, FSL]: %s\n%s\n", topts, stats);
+ Assert.assertTrue( 9 == stats.total_real );
+ Assert.assertTrue( 11 == stats.total_sym_links_existing );
+ Assert.assertTrue( 4 == stats.total_sym_links_not_existing );
+ Assert.assertTrue( 0 == stats.total_no_access );
+ Assert.assertTrue( 4 == stats.total_not_existing );
+ Assert.assertTrue( 60 < stats.total_file_bytes ); // some followed symlink files are of unknown size, e.g. /etc/fstab
+ Assert.assertTrue( 6 == stats.files_real );
+ Assert.assertTrue( 10 == stats.files_sym_link );
+ Assert.assertTrue( 3 == stats.dirs_real );
+ Assert.assertTrue( 1 == stats.dirs_sym_link );
+ }
+ }
+
+ @Test(timeout = 10000)
+ public void test30_copy_file2dir() {
+ PrintUtil.println(System.err, "test30_copy_file2dir\n");
+
+ FileStats root_orig_stats = new FileStats(project_root1);
+ if( !root_orig_stats.exists() ) {
+ root_orig_stats = new FileStats(project_root2);
+ }
+ Assert.assertTrue( true == root_orig_stats.exists() );
+ Assert.assertTrue( true == root_orig_stats.is_dir() );
+
+ final String root_copy = root+"_copy_test30";
+ {
+ // Fresh target folder
+ FileUtil.remove(root_copy, topts_rec);
+
+ Assert.assertTrue( true == FileUtil.mkdir(root_copy, FMode.def_dir) );
+ {
+ final FileStats stats = new FileStats(root_copy);
+ Assert.assertTrue( true == stats.exists() );
+ Assert.assertTrue( true == stats.ok() );
+ Assert.assertTrue( true == stats.is_dir() );
+ }
+ }
+ final FileStats source1_stats = new FileStats(root_orig_stats.path()+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: source1: %s\n", source1_stats);
+ {
+ Assert.assertTrue( true == source1_stats.exists() );
+ Assert.assertTrue( true == source1_stats.ok() );
+ Assert.assertTrue( true == source1_stats.is_file() );
+ }
+ {
+ // Copy file to folder
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: 01: dest.pre: %s\n", dest_stats);
+ Assert.assertTrue( false == dest_stats.exists() );
+ }
+ Assert.assertTrue( true == FileUtil.copy(source1_stats.path(), root_copy, copts) );
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: 01: dest.post: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ Assert.assertTrue( source1_stats.size() == dest_stats.size() );
+ Assert.assertTrue( source1_stats.mode().equals( dest_stats.mode() ) );
+ }
+ }
+ {
+ // Error: already exists of 'Copy file to folder'
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: 02: dest.pre: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ }
+ Assert.assertTrue( false == FileUtil.copy(source1_stats.path(), root_copy, copts) );
+ }
+ {
+ // Overwrite copy file to folder
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.overwrite);
+ copts.set(CopyOptions.Bit.verbose);
+
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: 03: source: %s\n", source1_stats);
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: 03: dest.pre: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ Assert.assertTrue( source1_stats.size() == dest_stats.size() );
+ Assert.assertTrue( source1_stats.mode().equals( dest_stats.mode() ) );
+ }
+ Assert.assertTrue( true == FileUtil.copy(source1_stats.path(), root_copy, copts) );
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test30_copy_file2dir: 03: dest.post: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ Assert.assertTrue( source1_stats.size() == dest_stats.size() );
+ Assert.assertTrue( source1_stats.mode().equals( dest_stats.mode() ) );
+ }
+ }
+ Assert.assertTrue( true == FileUtil.remove(root_copy, topts_rec) );
+ }
+
+ @Test(timeout = 10000)
+ public void test31_copy_file2file() {
+ PrintUtil.println(System.err, "test31_copy_file2file\n");
+
+ FileStats root_orig_stats = new FileStats(project_root1);
+ if( !root_orig_stats.exists() ) {
+ root_orig_stats = new FileStats(project_root2);
+ }
+ Assert.assertTrue( true == root_orig_stats.exists() );
+ Assert.assertTrue( true == root_orig_stats.is_dir() );
+
+ final String root_copy = root+"_copy_test31";
+ {
+ // Fresh target folder
+ FileUtil.remove(root_copy, topts_rec);
+
+ Assert.assertTrue( true == FileUtil.mkdir(root_copy, FMode.def_dir) );
+ {
+ final FileStats stats = new FileStats(root_copy);
+ Assert.assertTrue( true == stats.exists() );
+ Assert.assertTrue( true == stats.ok() );
+ Assert.assertTrue( true == stats.is_dir() );
+ }
+ }
+ final FileStats source1_stats = new FileStats(root_orig_stats.path()+"/file_01.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: source1: %s\n", source1_stats);
+ {
+ Assert.assertTrue( true == source1_stats.exists() );
+ Assert.assertTrue( true == source1_stats.ok() );
+ Assert.assertTrue( true == source1_stats.is_file() );
+ }
+ final FileStats source2_stats = new FileStats(root_orig_stats.path()+"/README_slink08_relext.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: source2: %s\n", source2_stats);
+ {
+ Assert.assertTrue( true == source2_stats.exists() );
+ Assert.assertTrue( true == source2_stats.ok() );
+ Assert.assertTrue( true == source2_stats.is_file() );
+ Assert.assertTrue( true == source2_stats.is_link() );
+ }
+ {
+ // Copy file to new file-name
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_10.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: 10: dest.pre: %s\n", dest_stats);
+ Assert.assertTrue( false == dest_stats.exists() );
+ }
+ Assert.assertTrue( true == FileUtil.copy(source1_stats.path(), root_copy+"/file_10.txt", copts) );
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_10.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: 10: dest.post: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ Assert.assertTrue( source1_stats.size() == dest_stats.size() );
+ Assert.assertTrue( source1_stats.mode().equals( dest_stats.mode() ) );
+ }
+ }
+ {
+ // Error: already exists of 'Copy file to file'
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_10.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: 11: dest.pre: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ }
+ Assert.assertTrue( false == FileUtil.copy(source1_stats.path(), root_copy+"/file_10.txt", copts) );
+ }
+ {
+ // Overwrite copy file to file
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.overwrite);
+ copts.set(CopyOptions.Bit.follow_symlinks);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_10.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: 12: dest.pre: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ Assert.assertTrue( source1_stats.size() == dest_stats.size() );
+ Assert.assertTrue( source1_stats.mode().equals( dest_stats.mode() ) );
+ }
+ Assert.assertTrue( true == FileUtil.copy(source2_stats.path(), root_copy+"/file_10.txt", copts) );
+ {
+ final FileStats dest_stats = new FileStats(root_copy+"/file_10.txt");
+ PrintUtil.fprintf_td(System.err, "test31_copy_file2file: 12: dest.post: %s\n", dest_stats);
+ Assert.assertTrue( true == dest_stats.exists() );
+ Assert.assertTrue( true == dest_stats.ok() );
+ Assert.assertTrue( true == dest_stats.is_file() );
+ Assert.assertTrue( false == dest_stats.is_link() );
+ Assert.assertTrue( source2_stats.size() == dest_stats.size() );
+ Assert.assertTrue( source2_stats.link_target().prot_mode().equals( dest_stats.prot_mode() ) );
+ }
+ }
+ Assert.assertTrue( true == FileUtil.remove(root_copy, topts_rec) );
+ }
+
+ @Test(timeout = 10000)
+ public void test40_copy_ext_r_p() {
+ PrintUtil.println(System.err, "test40_copy_ext_r_p\n");
+
+ FileStats root_orig_stats = new FileStats(project_root1);
+ if( !root_orig_stats.exists() ) {
+ root_orig_stats = new FileStats(project_root2);
+ }
+ Assert.assertTrue( true == root_orig_stats.exists() );
+ Assert.assertTrue( true == root_orig_stats.is_dir() );
+
+ final String root_copy = root+"_copy_test40";
+ testxx_copy_r_p("test40_copy_ext_r_p", root_orig_stats, 0 /* source_added_dead_links */, root_copy);
+ }
+
+ @Test(timeout = 10000)
+ public void test41_copy_ext_r_p_fsl() {
+ PrintUtil.println(System.err, "test41_copy_ext_r_p_fsl\n");
+
+ FileStats root_orig_stats = new FileStats(project_root1);
+ if( !root_orig_stats.exists() ) {
+ root_orig_stats = new FileStats(project_root2);
+ }
+ Assert.assertTrue( true == root_orig_stats.exists() );
+ Assert.assertTrue( true == root_orig_stats.is_dir() );
+
+ final String root_copy = root+"_copy_test41";
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.recursive);
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.follow_symlinks);
+ copts.set(CopyOptions.Bit.ignore_symlink_errors);
+ copts.set(CopyOptions.Bit.verbose);
+ {
+ FileUtil.remove(root_copy, topts_rec);
+
+ Assert.assertTrue( true == FileUtil.copy(root_orig_stats.path(), root_copy, copts) );
+ }
+ final FileStats root_copy_stats = new FileStats(root_copy);
+ Assert.assertTrue( true == root_copy_stats.exists() );
+ Assert.assertTrue( true == root_copy_stats.ok() );
+ Assert.assertTrue( true == root_copy_stats.is_dir() );
+
+ {
+ final TraverseOptions topts_orig = new TraverseOptions();
+ topts_orig.set(TraverseOptions.Bit.recursive);
+ topts_orig.set(TraverseOptions.Bit.dir_entry);
+ topts_orig.set(TraverseOptions.Bit.follow_symlinks);
+
+ final TraverseOptions topts_copy = new TraverseOptions();
+ topts_copy.set(TraverseOptions.Bit.recursive);
+ topts_copy.set(TraverseOptions.Bit.dir_entry);
+
+ final VisitorStats stats = new VisitorStats(topts_orig);
+ final VisitorStats stats_copy = new VisitorStats(topts_copy);
+
+ final PathStatsVisitor pv_orig = new PathStatsVisitor(stats);
+ final PathStatsVisitor pv_copy = new PathStatsVisitor(stats_copy);
+
+ Assert.assertTrue( true == FileUtil.visit(root_orig_stats, topts_orig, pv_orig) );
+ Assert.assertTrue( true == FileUtil.visit(root_copy_stats, topts_copy, pv_copy) );
+
+ PrintUtil.fprintf_td(System.err, "test41_copy_ext_r_p_fsl: copy %s, traverse_orig %s, traverse_copy %s\n", copts, topts_orig, topts_copy);
+
+ PrintUtil.fprintf_td(System.err, "test41_copy_ext_r_p_fsl: source visitor stats\n%s\n", stats);
+ PrintUtil.fprintf_td(System.err, "test41_copy_ext_r_p_fsl: destination visitor stats\n%s\n", stats_copy);
+
+ Assert.assertTrue( 9 == stats.total_real );
+ Assert.assertTrue( 11 == stats.total_sym_links_existing );
+ Assert.assertTrue( 4 == stats.total_sym_links_not_existing );
+ Assert.assertTrue( 0 == stats.total_no_access );
+ Assert.assertTrue( 4 == stats.total_not_existing );
+ Assert.assertTrue( 60 < stats.total_file_bytes ); // some followed symlink files are of unknown size, e.g. /etc/fstab
+ Assert.assertTrue( 6 == stats.files_real );
+ Assert.assertTrue( 10 == stats.files_sym_link );
+ Assert.assertTrue( 3 == stats.dirs_real );
+ Assert.assertTrue( 1 == stats.dirs_sym_link );
+
+ Assert.assertTrue( 20 == stats_copy.total_real );
+ Assert.assertTrue( 0 == stats_copy.total_sym_links_existing );
+ Assert.assertTrue( 0 == stats_copy.total_sym_links_not_existing );
+ Assert.assertTrue( 0 == stats_copy.total_no_access );
+ Assert.assertTrue( 0 == stats_copy.total_not_existing );
+ Assert.assertTrue( 60 < stats_copy.total_file_bytes ); // some followed symlink files are of unknown size, e.g. /etc/fstab
+ Assert.assertTrue( 16 == stats_copy.files_real );
+ Assert.assertTrue( 0 == stats_copy.files_sym_link );
+ Assert.assertTrue( 4 == stats_copy.dirs_real );
+ Assert.assertTrue( 0 == stats_copy.dirs_sym_link );
+ }
+ Assert.assertTrue( true == FileUtil.remove(root_copy, topts_rec) );
+ }
+
+} \ No newline at end of file
diff --git a/test/java/jau/test/nio/TestByteStream01.java b/test/java/jau/test/nio/TestByteStream01.java
index 1c69f0f..bd1b4c2 100644
--- a/test/java/jau/test/nio/TestByteStream01.java
+++ b/test/java/jau/test/nio/TestByteStream01.java
@@ -29,7 +29,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;