diff options
author | Sven Gothel <[email protected]> | 2022-07-04 22:55:44 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2022-07-04 22:55:44 +0200 |
commit | 2ba71f91fd852fd609943a2625be26e1c67c8507 (patch) | |
tree | c09e6856074b98b6a9ac100544feb7535ff8ab4a | |
parent | cc241a1d4db040160ccd83ee23cb876787c52d5f (diff) |
Complete C++ jau::fs -> java org.jau.fs mapping incl. full test_fileutils01.cpp -> TestFileUtils01.java
-rw-r--r-- | CHANGES.md | 3 | ||||
-rw-r--r-- | include/jau/jni/helper_jni.hpp | 26 | ||||
-rw-r--r-- | java_jni/jni/jau/jau_fs_FileUtil.cxx | 292 | ||||
-rw-r--r-- | java_jni/org/jau/fs/CopyOptions.java | 24 | ||||
-rw-r--r-- | java_jni/org/jau/fs/DirItem.java | 95 | ||||
-rw-r--r-- | java_jni/org/jau/fs/FMode.java | 147 | ||||
-rw-r--r-- | java_jni/org/jau/fs/FileStats.java | 385 | ||||
-rw-r--r-- | java_jni/org/jau/fs/FileUtil.java | 175 | ||||
-rw-r--r-- | java_jni/org/jau/fs/TraverseEvent.java | 87 | ||||
-rw-r--r-- | java_jni/org/jau/fs/TraverseOptions.java | 22 | ||||
-rw-r--r-- | test/java/jau/test/fs/FileUtilBaseTest.java | 322 | ||||
-rw-r--r-- | test/java/jau/test/fs/TestFileUtils01.java | 1244 | ||||
-rw-r--r-- | test/java/jau/test/nio/TestByteStream01.java | 1 |
13 files changed, 2805 insertions, 18 deletions
@@ -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; |