add existing source
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Wed, 5 Jan 2022 17:47:28 +0000 (18:47 +0100)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Wed, 5 Jan 2022 17:47:28 +0000 (18:47 +0100)
54 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
application/Fsb.c [new file with mode: 0644]
application/Fsb.h [new file with mode: 0644]
application/FsbP.h [new file with mode: 0644]
application/Makefile [new file with mode: 0644]
application/main.c [new file with mode: 0644]
application/main.h [new file with mode: 0644]
application/player.c [new file with mode: 0644]
application/player.h [new file with mode: 0644]
application/window.c [new file with mode: 0644]
application/window.h [new file with mode: 0644]
configure [new file with mode: 0755]
make/Makefile.mk [new file with mode: 0644]
make/clang.mk [new file with mode: 0644]
make/configure.vm [new file with mode: 0644]
make/gcc.mk [new file with mode: 0644]
make/mingw.mk [new file with mode: 0644]
make/osx.mk [new file with mode: 0644]
make/package_unix.sh [new file with mode: 0755]
make/project.xml [new file with mode: 0644]
make/suncc.mk [new file with mode: 0644]
make/toolchain.sh [new file with mode: 0644]
make/windows.mk [new file with mode: 0644]
ucx/Makefile [new file with mode: 0644]
ucx/README [new file with mode: 0644]
ucx/allocator.c [new file with mode: 0644]
ucx/array.c [new file with mode: 0644]
ucx/avl.c [new file with mode: 0644]
ucx/buffer.c [new file with mode: 0644]
ucx/list.c [new file with mode: 0644]
ucx/logging.c [new file with mode: 0644]
ucx/map.c [new file with mode: 0644]
ucx/mempool.c [new file with mode: 0644]
ucx/properties.c [new file with mode: 0644]
ucx/stack.c [new file with mode: 0644]
ucx/string.c [new file with mode: 0644]
ucx/test.c [new file with mode: 0644]
ucx/ucx.c [new file with mode: 0644]
ucx/ucx/allocator.h [new file with mode: 0644]
ucx/ucx/array.h [new file with mode: 0644]
ucx/ucx/avl.h [new file with mode: 0644]
ucx/ucx/buffer.h [new file with mode: 0644]
ucx/ucx/list.h [new file with mode: 0644]
ucx/ucx/logging.h [new file with mode: 0644]
ucx/ucx/map.h [new file with mode: 0644]
ucx/ucx/mempool.h [new file with mode: 0644]
ucx/ucx/properties.h [new file with mode: 0644]
ucx/ucx/stack.h [new file with mode: 0644]
ucx/ucx/string.h [new file with mode: 0644]
ucx/ucx/test.h [new file with mode: 0644]
ucx/ucx/ucx.h [new file with mode: 0644]
ucx/ucx/utils.h [new file with mode: 0644]
ucx/utils.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5a5d528
--- /dev/null
@@ -0,0 +1,2 @@
+build
+config.mk
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..3cd3594
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,38 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2022 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+all: config.mk
+       $(MAKE) -f make/Makefile.mk
+
+config.mk:
+       ./configure
+
+clean: FORCE
+       rm -fR build/*
+
+FORCE:
diff --git a/application/Fsb.c b/application/Fsb.c
new file mode 100644 (file)
index 0000000..42cd412
--- /dev/null
@@ -0,0 +1,3106 @@
+/*
+ * Copyright 2021 Olaf Wintermann
+ *
+ * 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.
+ */
+
+//define FSB_ENABLE_DETAIL
+
+#include "Fsb.h"
+#include "FsbP.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include <Xm/XmAll.h>
+#include <Xm/DropDown.h>
+
+#ifdef FSB_ENABLE_DETAIL
+#include <XmL/Grid.h>
+#endif
+
+#define WIDGET_SPACING 5
+#define WINDOW_SPACING 8
+
+#define BUTTON_EXTRA_SPACE 4
+
+#define DATE_FORMAT_SAME_YEAR  "%b %d %H:%M"
+#define DATE_FORMAT_OTHER_YEAR "%b %d  %Y"
+
+#define KB_SUFFIX "KiB"
+#define MB_SUFFIX "MiB"
+#define GB_SUFFIX "GiB"
+#define TB_SUFFIX "TiB"
+
+#define FSB_ERROR_TITLE          "Error"
+#define FSB_ERROR_CHAR           "Character '/' is not allowed in file names"
+#define FSB_ERROR_RENAME         "Cannot rename file: %s"
+#define FSB_ERROR_DELETE         "Cannot delete file: %s"
+#define FSB_ERROR_CREATE_FOLDER  "Cannot create folder: %s"
+#define FSB_ERROR_OPEN_DIR       "Cannot open directory: %s"
+
+#define FSB_DETAIL_HEADINGS "Name|Size|Last Modified"
+
+static void fsb_class_init(void);
+static void fsb_class_part_init (WidgetClass wc);
+static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args);
+static void fsb_resize(Widget widget);
+static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes);
+static void fsb_destroy(Widget widget);
+static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
+static Boolean fsb_acceptfocus(Widget widget, Time *time);
+
+static void fsb_insert_child(Widget child);
+
+static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb);
+
+static int FSBGlobFilter(const char *a, const char *b);
+
+static void FSBUpdateTitle(Widget w);
+
+static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
+
+static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg);
+
+static void FSBRename(XnFileSelectionBox fsb, const char *path);
+static void FSBDelete(XnFileSelectionBox fsb, const char *path);
+
+static void FSBSelectItem(XnFileSelectionBox fsb, const char *item);
+
+static char* set_selected_path(XnFileSelectionBox data, XmString item);
+
+static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd);
+
+static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback);
+
+static void FileListUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData);
+static void FileListSelect(Widget fsb, Widget view, const char *item);
+static void FileListCleanup(Widget fsb, Widget view, void *userData);
+static void FileListDestroy(Widget fsb, Widget view, void *userData);
+
+static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb);
+static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb);
+
+static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count);
+
+#ifdef FSB_ENABLE_DETAIL
+static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData);
+static void FileListDetailSelect(Widget fsb, Widget view, const char *item);
+static void FileListDetailCleanup(Widget fsb, Widget view, void *userData);
+static void FileListDetailDestroy(Widget fsb, Widget view, void *userData);
+static void FileListDetailAdjustColWidth(Widget grid);
+static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth);
+#endif
+
+static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u);
+
+static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u);
+
+static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value);
+
+static void CreateUI(XnFileSelectionBox w);
+static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc createProc, void *userData, Boolean useDirList);
+static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex);
+static void SelectView(XnFileSelectionBox f, int view);
+
+static char* FSBDialogTitle(Widget w);
+
+static FSBViewWidgets CreateListView(Widget fsb, ArgList args, int n, void *userData);
+static FSBViewWidgets CreateDetailView(Widget fsb, ArgList args, int n, void *userData);
+
+static const char* GetHomeDir(void);
+
+static char* ConcatPath(const char *parent, const char *name);
+static char* FileName(char *path);
+static char* ParentPath(const char *path);
+//static int   CheckFileName(const char *name);
+
+static int filedialog_update_dir(XnFileSelectionBox data, const char *path);
+static void filedialog_cleanup_filedata(XnFileSelectionBox data);
+
+static void pathbar_resize(Widget w, PathBar *p, XtPointer d);
+
+static XtResource resources[] = {
+    {XmNokCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.okCallback), XmRCallback, NULL},
+    {XmNcancelCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.cancelCallback), XmRCallback, NULL},
+    {XnNwidgetSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.widgetSpacing), XmRImmediate, (XtPointer)WIDGET_SPACING},
+    {XnNwindowSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.windowSpacing), XmRImmediate, (XtPointer)WINDOW_SPACING},
+    {XnNfsbType, XnCfsbType, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.type), XmRImmediate, (XtPointer)FILEDIALOG_OPEN},
+    {XnNshowHidden, XnCshowHidden, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHidden), XmRImmediate, (XtPointer)False},
+    {XnNshowHiddenButton, XnCshowHiddenButton, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHiddenButton), XmRImmediate, (XtPointer)True},
+    {XnNshowViewMenu, XnCshowViewMenu, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showViewMenu), XmRImmediate, (XtPointer)False},
+    {XnNselectedView, XnCselectedView, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.selectedview), XmRImmediate, (XtPointer)0},
+    
+    {XnNdirectory, XnCdirectory, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.currentPath), XmRString, NULL},
+    {XnNselectedPath, XnCselectedPath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.selectedPath), XmRString, NULL},
+    {XnNhomePath, XnChomePath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.homePath), XmRString, NULL},
+    
+    {XnNfilter,XnCfilter,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.filterStr), XmRString, "*"},
+    {XnNfilterFunc,XnCfilterFunc,XmRFunction,sizeof(FSBFilterFunc),XtOffset(XnFileSelectionBox, fsb.filterFunc), XmRFunction, NULL},
+    
+    {XnNlabelListView,XnClabelListView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelListView), XmRString, "List"},
+    {XnNlabelDetailView,XnClabelDetailView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDetailView), XmRString, "Detail"},
+    {XnNlabelOpenFileTitle,XnClabelOpenFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpenFileTitle), XmRString, "Open File"},
+    {XnNlabelSaveFileTitle,XnClabelSaveFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSaveFileTitle), XmRString, "Save File"},
+    {XnNlabelDirUp,XnClabelDirUp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirUp), XmRString, "Dir Up"},
+    {XnNlabelHome,XnClabelHome,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHome), XmRString, "Home"},
+    {XnNlabelNewFolder,XnClabelNewFolder,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFolder), XmRString, "New Folder"},
+    {XnNlabelFilterButton,XnClabelFilterButton,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFilterButton), XmRString, "Filter"},
+    {XnNlabelShowHiddenFiles,XnClabelShowHiddenFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelShowHiddenFiles), XmRString, "Show hiden files"},
+    {XnNlabelDirectories,XnClabelDirectories,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectories), XmRString, "Directories"},
+    {XnNlabelFiles,XnClabelFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFiles), XmRString, "Files"},
+    {XnNlabelRename,XnClabelRename,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelRename), XmRString, "Rename"},
+    {XnNlabelDelete,XnClabelDelete,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDelete), XmRString, "Delete"},
+    {XnNlabelOpen,XnClabelOpen,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpen), XmRString, "Open"},
+    {XnNlabelSave,XnClabelSave,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSave), XmRString, "Save"},
+    {XnNlabelOk,XnClabelOk,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOk), XmRString, "OK"},
+    {XnNlabelCancel,XnClabelCancel,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelCancel), XmRString, "Cancel"},
+    {XnNlabelHelp,XnClabelHelp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHelp), XmRString, "Help"},
+    {XnNlabelFileName,XnClabelFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFileName), XmRString, "New File Name"},
+    {XnNlabelDirectoryName,XnClabelDirectoryName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectoryName), XmRString, "Directory name:"},
+    {XnNlabelNewFileName,XnClabelNewFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFileName), XmRString, "New file name:"},
+    {XnNlabelDeleteFile,XnClabelDeleteFile,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDeleteFile), XmRString, "Delete file '%s'?"},
+    {XnNdetailHeadings,XnCdetailHeadings,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.detailHeadings), XmRString,FSB_DETAIL_HEADINGS},
+    {XnNdateFormatSameYear,XnCdateFormatSameYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatSameYear), XmRString,DATE_FORMAT_SAME_YEAR},
+    {XnNdateFormatOtherYear,XnNdateFormatOtherYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatOtherYear), XmRString,DATE_FORMAT_OTHER_YEAR},
+    {XnNsuffixBytes,XnCsuffixBytes,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixBytes), XmRString,"bytes"},
+    {XnNsuffixKB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixKB), XmRString,KB_SUFFIX},
+    {XnNsuffixMB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixMB), XmRString,MB_SUFFIX},
+    {XnNsuffixGB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixGB), XmRString,GB_SUFFIX},
+    {XnNsuffixTB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixTB), XmRString,TB_SUFFIX},
+    
+    {XnNerrorTitle,XnCerrorTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorTitle), XmRString,FSB_ERROR_TITLE},
+    {XnNerrorIllegalChar,XnCerrorIllegalChar,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorIllegalChar), XmRString,FSB_ERROR_CHAR},
+    {XnNerrorRename,XnCerrorRename,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorRename), XmRString,FSB_ERROR_RENAME},
+    {XnNerrorCreateFolder,XnCerrorCreateFolder,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorFolder), XmRString,FSB_ERROR_CREATE_FOLDER},
+    {XnNerrorDelete,XnCerrorDelete,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorDelete), XmRString,FSB_ERROR_DELETE},
+    {XnNerrorOpenDir,XnCerrorOpenDir,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorOpenDir), XmRString,FSB_ERROR_OPEN_DIR}
+};
+
+static XtActionsRec actionslist[] = {
+  {"focusIn", FocusInAP},
+  {"NULL", NULL}
+};
+
+
+static char defaultTranslations[] = "<FocusIn>:                        focusIn()";
+
+static XtResource constraints[] = {};
+
+FSBClassRec fsbWidgetClassRec = {
+    // Core Class
+    {
+        (WidgetClass)&xmFormClassRec,
+        "XnFSB",                         // class_name
+        sizeof(FSBRec),                  // widget_size
+        fsb_class_init,                  // class_initialize
+        fsb_class_part_init,             // class_part_initialize
+        FALSE,                           // class_inited
+        fsb_init,                        // initialize
+        NULL,                            // initialize_hook
+        fsb_realize,                     // realize
+        actionslist,                     // actions
+        XtNumber(actionslist),           // num_actions
+        resources,                       // resources
+        XtNumber(resources),             // num_resources
+        NULLQUARK,                       // xrm_class
+        True,                            // compress_motion
+        True,                            // compress_exposure
+        True,                            // compress_enterleave
+        False,                           // visible_interest
+        fsb_destroy,                     // destroy
+        fsb_resize,                      // resize
+        XtInheritExpose,                 // expose
+        fsb_set_values,                  // set_values
+        NULL,                            // set_values_hook
+        XtInheritSetValuesAlmost,        // set_values_almost
+        NULL,                            // get_values_hook
+        fsb_acceptfocus,                 // accept_focus
+        XtVersion,                       // version
+        NULL,                            // callback_offsets
+        defaultTranslations,             // tm_table
+        XtInheritQueryGeometry,          // query_geometry
+        XtInheritDisplayAccelerator,     // display_accelerator
+        NULL,                            // extension
+    },
+    // Composite Class
+    {
+        XtInheritGeometryManager, // geometry_manager 
+        XtInheritChangeManaged,   // change_managed
+        fsb_insert_child,         // insert_child 
+        XtInheritDeleteChild,     // delete_child  
+        NULL,                     // extension   
+    },
+    // Constraint Class
+    {
+        constraints,                 // resources
+        XtNumber(constraints),       // num_resources   
+        sizeof(XmFormConstraintRec), // constraint_size  
+        NULL,                        // initialize 
+        NULL,                        // destroy
+        NULL,                        // set_value
+        NULL,                        // extension 
+    },
+    // XmManager Class
+    {
+        XtInheritTranslations, // translations
+        NULL, // syn_resources
+        0,    // num_syn_resources
+        NULL, // syn_constraint_resources
+        0,    // num_syn_constraint_resources
+        XmInheritParentProcess, // parent_process
+        NULL  // extension
+    },
+    // XmBulletinBoard
+    {
+        FALSE,
+        NULL,
+        XmInheritFocusMovedProc,
+        NULL
+    },
+    // XmForm Class
+    {
+        NULL
+    },
+    // FSB Class
+    {
+        0
+    }
+};
+
+WidgetClass xnFsbWidgetClass = (WidgetClass)&fsbWidgetClassRec;
+
+
+static void fsb_class_init(void) {
+
+}
+
+static void fsb_class_part_init (WidgetClass wc) {
+    FSBClassRec *fsbClass = (FSBClassRec*)wc;
+    XmFormClassRec *formClass = (XmFormClassRec*)xmFormWidgetClass;
+    
+    fsbClass->constraint_class.initialize = formClass->constraint_class.initialize;
+    fsbClass->constraint_class.set_values = formClass->constraint_class.set_values;
+}
+
+
+#define STRDUP_RES(a) if(a) a = strdup(a)
+#define XMS_STRDUP_RES(a) if(a) a = XmStringCopy(a)
+
+static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) {
+    XnFileSelectionBox fsb = (XnFileSelectionBox)neww;
+    (xmFormClassRec.core_class.initialize)(request, neww, args, num_args);
+    
+    fsb->fsb.disable_set_values = 0;
+    
+    STRDUP_RES(fsb->fsb.homePath);
+    STRDUP_RES(fsb->fsb.selectedPath);
+    STRDUP_RES(fsb->fsb.currentPath);
+    STRDUP_RES(fsb->fsb.filterStr);
+    STRDUP_RES(fsb->fsb.labelListView);
+    STRDUP_RES(fsb->fsb.labelDetailView);
+    STRDUP_RES(fsb->fsb.labelOpenFileTitle);
+    STRDUP_RES(fsb->fsb.labelSaveFileTitle);
+    XMS_STRDUP_RES(fsb->fsb.labelDirUp);
+    XMS_STRDUP_RES(fsb->fsb.labelHome);
+    XMS_STRDUP_RES(fsb->fsb.labelNewFolder);
+    XMS_STRDUP_RES(fsb->fsb.labelFilterButton);
+    XMS_STRDUP_RES(fsb->fsb.labelShowHiddenFiles);
+    XMS_STRDUP_RES(fsb->fsb.labelDirectories);
+    XMS_STRDUP_RES(fsb->fsb.labelFiles);
+    XMS_STRDUP_RES(fsb->fsb.labelRename);
+    XMS_STRDUP_RES(fsb->fsb.labelDelete);
+    XMS_STRDUP_RES(fsb->fsb.labelOpen);
+    XMS_STRDUP_RES(fsb->fsb.labelSave);
+    XMS_STRDUP_RES(fsb->fsb.labelCancel);
+    XMS_STRDUP_RES(fsb->fsb.labelHelp);
+    XMS_STRDUP_RES(fsb->fsb.labelFileName);
+    XMS_STRDUP_RES(fsb->fsb.labelDirectoryName);
+    XMS_STRDUP_RES(fsb->fsb.labelNewFileName);
+    STRDUP_RES(fsb->fsb.labelDeleteFile);
+    STRDUP_RES(fsb->fsb.detailHeadings);
+    STRDUP_RES(fsb->fsb.dateFormatSameYear);
+    STRDUP_RES(fsb->fsb.dateFormatOtherYear);
+    STRDUP_RES(fsb->fsb.suffixBytes);
+    STRDUP_RES(fsb->fsb.suffixKB);
+    STRDUP_RES(fsb->fsb.suffixMB);
+    STRDUP_RES(fsb->fsb.suffixGB);
+    STRDUP_RES(fsb->fsb.suffixTB);
+    STRDUP_RES(fsb->fsb.errorTitle);
+    STRDUP_RES(fsb->fsb.errorIllegalChar);
+    STRDUP_RES(fsb->fsb.errorRename);
+    STRDUP_RES(fsb->fsb.errorFolder);
+    STRDUP_RES(fsb->fsb.errorDelete);
+    STRDUP_RES(fsb->fsb.errorOpenDir);
+    
+    CreateUI((XnFileSelectionBox)fsb);
+    
+    XtAddCallback(neww, XmNmapCallback, fsb_mapcb, NULL);
+}
+
+#define STR_FREE(a) if(a) free(a)
+#define XMSTR_FREE(a) if(a) XmStringFree(a)
+
+static void fsb_destroy(Widget widget) {
+    XnFileSelectionBox w = (XnFileSelectionBox)widget;
+    
+    // destroy all views
+    for(int i=0;i<w->fsb.numviews;i++) {
+        FSBView v = w->fsb.view[i];
+        v.destroy(widget, v.widget, v.userData);
+    }
+    
+    STR_FREE(w->fsb.homePath);
+    
+    // free filelists
+    filedialog_cleanup_filedata(w);
+    STR_FREE(w->fsb.currentPath);
+    STR_FREE(w->fsb.selectedPath);
+    STR_FREE(w->fsb.filterStr);
+    
+    PathBarDestroy(w->fsb.pathBar);
+    
+    // free strings
+    STR_FREE(w->fsb.labelListView);
+    STR_FREE(w->fsb.labelDetailView);
+    STR_FREE(w->fsb.labelOpenFileTitle);
+    STR_FREE(w->fsb.labelSaveFileTitle);
+    
+    XMSTR_FREE(w->fsb.labelDirUp);
+    XMSTR_FREE(w->fsb.labelHome);
+    XMSTR_FREE(w->fsb.labelNewFolder);
+    XMSTR_FREE(w->fsb.labelFilterButton);
+    XMSTR_FREE(w->fsb.labelShowHiddenFiles);
+    XMSTR_FREE(w->fsb.labelDirectories);
+    XMSTR_FREE(w->fsb.labelFiles);
+    XMSTR_FREE(w->fsb.labelRename);
+    XMSTR_FREE(w->fsb.labelDelete);
+    XMSTR_FREE(w->fsb.labelOpen);
+    XMSTR_FREE(w->fsb.labelSave);
+    XMSTR_FREE(w->fsb.labelCancel);
+    XMSTR_FREE(w->fsb.labelHelp);
+    XMSTR_FREE(w->fsb.labelFileName);
+    XMSTR_FREE(w->fsb.labelDirectoryName);
+    XMSTR_FREE(w->fsb.labelNewFileName);
+    STR_FREE(w->fsb.labelDeleteFile);
+    STR_FREE(w->fsb.detailHeadings);
+    
+    STR_FREE(w->fsb.dateFormatSameYear);
+    STR_FREE(w->fsb.dateFormatOtherYear);
+    STR_FREE(w->fsb.suffixBytes);
+    STR_FREE(w->fsb.suffixKB);
+    STR_FREE(w->fsb.suffixMB);
+    STR_FREE(w->fsb.suffixGB);
+    STR_FREE(w->fsb.suffixTB);
+    
+    STR_FREE(w->fsb.errorTitle);
+    STR_FREE(w->fsb.errorIllegalChar);
+    STR_FREE(w->fsb.errorRename);
+    STR_FREE(w->fsb.errorFolder);
+    STR_FREE(w->fsb.errorDelete);
+    STR_FREE(w->fsb.errorOpenDir);
+}
+
+static void fsb_resize(Widget widget) {
+    XnFileSelectionBox w = (XnFileSelectionBox)widget;
+    (xmFormClassRec.core_class.resize)(widget);
+    
+#ifdef FSB_ENABLE_DETAIL
+    if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) {
+        FileListDetailAdjustColWidth(w->fsb.grid);
+    }
+#endif
+}
+
+static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) {
+    XnFileSelectionBox w = (XnFileSelectionBox)widget;   
+    (xmFormClassRec.core_class.realize)(widget, mask, attributes);
+    
+    FSBView view = w->fsb.view[w->fsb.selectedview];
+    XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT);
+    
+#ifdef FSB_ENABLE_DETAIL
+    if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) {
+        FileListDetailAdjustColWidth(w->fsb.grid);
+    }
+#endif
+}
+
+static void FSBUpdateTitle(Widget w) {
+    if(XtParent(w)->core.widget_class == xmDialogShellWidgetClass) {
+        char *title = FSBDialogTitle(w);
+        XtVaSetValues(XtParent(w), XmNtitle, title, NULL);
+    }
+}
+
+static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
+    Boolean r = False;
+
+    XnFileSelectionBox o = (XnFileSelectionBox)old;
+    XnFileSelectionBox n = (XnFileSelectionBox)neww;
+    
+    int setOkBtnLabel = 0;
+    int ismanaged = XtIsManaged(neww);
+    Dimension width, height;
+    if(!ismanaged) {
+        width = n->core.width;
+        height = n->core.height;
+        if(n->fsb.pathBar) {
+            n->fsb.pathBar->disableResize = True;
+        }
+    }
+    
+    if(o->fsb.selectedview != n->fsb.selectedview) {
+        int selectedview = n->fsb.selectedview;
+        n->fsb.selectedview = o->fsb.selectedview;
+        SelectView(n, selectedview);
+    }
+    
+    char *updateDir = NULL;
+    int selectItem = 0;
+    if(o->fsb.selectedPath != n->fsb.selectedPath) {
+        STR_FREE(o->fsb.selectedPath);
+        STRDUP_RES(n->fsb.selectedPath);
+        XmTextFieldSetString(n->fsb.name, FileName(n->fsb.selectedPath));
+        // also update current directory
+        updateDir = ParentPath(n->fsb.selectedPath);
+        selectItem = 1;
+    }
+    if(o->fsb.currentPath != n->fsb.currentPath) {
+        STR_FREE(o->fsb.currentPath);
+        updateDir = strdup(n->fsb.currentPath);
+        n->fsb.currentPath = NULL;
+    }
+    
+    if(o->fsb.filterStr != n->fsb.filterStr) {
+        STR_FREE(o->fsb.filterStr);
+        STRDUP_RES(n->fsb.filterStr);
+        XmTextFieldSetString(XmDropDownGetText(n->fsb.filter), n->fsb.filterStr);
+        if(!updateDir) {
+            filedialog_update_dir(n, NULL);
+        }
+    }
+    
+    if(updateDir) {
+        filedialog_update_dir(n, updateDir);
+        PathBarSetPath(n->fsb.pathBar, updateDir);
+        free(updateDir);
+    }
+    
+    if(o->fsb.type != n->fsb.type) {
+        if(n->fsb.type == FILEDIALOG_OPEN) {
+            XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.separator, NULL);
+            XtUnmanageChild(n->fsb.name);
+            XtUnmanageChild(n->fsb.nameLabel);
+        } else {
+            XtManageChild(n->fsb.name);
+            XtManageChild(n->fsb.nameLabel);
+            XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.nameLabel, NULL);
+        }
+        FSBUpdateTitle(neww);
+        setOkBtnLabel = 1;
+    }
+      
+    // label strings
+    int updateTitle = 0;
+    if(o->fsb.labelListView != n->fsb.labelListView) {
+        STR_FREE(o->fsb.labelListView);
+        STRDUP_RES(n->fsb.labelListView);
+        XmString label = XmStringCreateLocalized(n->fsb.labelListView);
+        XtVaSetValues(n->fsb.viewSelectorList, XmNlabelString, label, NULL);
+        XmStringFree(label);
+    }
+    if(o->fsb.labelDetailView != n->fsb.labelDetailView) {
+        STR_FREE(o->fsb.labelDetailView);
+        STRDUP_RES(n->fsb.labelDetailView);
+        XmString label = XmStringCreateLocalized(n->fsb.labelDetailView);
+        XtVaSetValues(n->fsb.viewSelectorDetail, XmNlabelString, label, NULL);
+        if(n->fsb.detailToggleButton) {
+            XtVaSetValues(n->fsb.detailToggleButton, XmNlabelString, label, NULL);
+        }
+        XmStringFree(label);
+    }
+    if(o->fsb.labelOpenFileTitle != n->fsb.labelOpenFileTitle) {
+        STR_FREE(o->fsb.labelOpenFileTitle);
+        STRDUP_RES(n->fsb.labelOpenFileTitle);
+        updateTitle = 1;
+    }
+    if(o->fsb.labelSaveFileTitle != n->fsb.labelSaveFileTitle) {
+        STR_FREE(o->fsb.labelSaveFileTitle);
+        STRDUP_RES(n->fsb.labelSaveFileTitle);
+        updateTitle = 1;
+    }
+    
+    if(o->fsb.labelDirUp != n->fsb.labelDirUp) {
+        XMSTR_FREE(o->fsb.labelDirUp);
+        XMS_STRDUP_RES(n->fsb.labelDirUp);
+        XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelDirUp, NULL);
+    }
+    if(o->fsb.labelHome != n->fsb.labelHome) {
+        XMSTR_FREE(o->fsb.labelHome);
+        XMS_STRDUP_RES(n->fsb.labelHome);
+        XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelHome, NULL);
+    }
+    if(o->fsb.labelNewFolder != n->fsb.labelNewFolder) {
+        XMSTR_FREE(o->fsb.labelNewFolder);
+        XMS_STRDUP_RES(n->fsb.labelNewFolder);
+        XtVaSetValues(n->fsb.newFolder, XmNlabelString, n->fsb.labelNewFolder, NULL);
+    }
+    if(o->fsb.labelFilterButton != n->fsb.labelFilterButton) {
+        XMSTR_FREE(o->fsb.labelFilterButton);
+        XMS_STRDUP_RES(n->fsb.labelFilterButton);
+        XtVaSetValues(n->fsb.filterButton, XmNlabelString, n->fsb.labelFilterButton, NULL);
+    }
+    if(o->fsb.labelShowHiddenFiles != n->fsb.labelShowHiddenFiles) {
+        XMSTR_FREE(o->fsb.labelShowHiddenFiles);
+        XMS_STRDUP_RES(n->fsb.labelShowHiddenFiles);
+        XtVaSetValues(n->fsb.showHiddenButtonW, XmNlabelString, n->fsb.labelShowHiddenFiles, NULL);
+    }
+    if(o->fsb.labelDirectories != n->fsb.labelDirectories) {
+        XMSTR_FREE(o->fsb.labelDirectories);
+        XMS_STRDUP_RES(n->fsb.labelDirectories);
+        XtVaSetValues(n->fsb.lsDirLabel, XmNlabelString, n->fsb.labelDirectories, NULL);
+    }
+    if(o->fsb.labelFiles != n->fsb.labelFiles) {
+        XMSTR_FREE(o->fsb.labelFiles);
+        XMS_STRDUP_RES(n->fsb.labelFiles);
+        XtVaSetValues(n->fsb.lsFileLabel, XmNlabelString, n->fsb.labelFiles, NULL);
+    }
+    int recreateContextMenu = 0;
+    if(o->fsb.labelRename != n->fsb.labelRename) {
+        XMSTR_FREE(o->fsb.labelRename);
+        XMS_STRDUP_RES(n->fsb.labelRename);
+        recreateContextMenu = 1;
+    }
+    if(o->fsb.labelDelete != n->fsb.labelDelete) {
+        XMSTR_FREE(o->fsb.labelDelete);
+        XMS_STRDUP_RES(n->fsb.labelDelete);
+        recreateContextMenu = 1;
+    }
+
+    if(o->fsb.labelOpen != n->fsb.labelOpen) {
+        XMSTR_FREE(o->fsb.labelOpen);
+        XMS_STRDUP_RES(n->fsb.labelOpen);
+        setOkBtnLabel = 1;
+    }
+    if(o->fsb.labelSave != n->fsb.labelSave) {
+        XMSTR_FREE(o->fsb.labelSave);
+        XMS_STRDUP_RES(n->fsb.labelSave);
+        setOkBtnLabel = 1;
+    }
+    if(o->fsb.labelCancel != n->fsb.labelCancel) {
+        XMSTR_FREE(o->fsb.labelCancel);
+        XMS_STRDUP_RES(n->fsb.labelCancel);
+        XtVaSetValues(n->fsb.cancelBtn, XmNlabelString, n->fsb.labelCancel, NULL);
+    }
+    if(o->fsb.labelHelp != n->fsb.labelHelp) {
+        XMSTR_FREE(o->fsb.labelHelp);
+        XMS_STRDUP_RES(n->fsb.labelHelp);
+        XtVaSetValues(n->fsb.helpBtn, XmNlabelString, n->fsb.labelHelp, NULL);
+    }
+    if(o->fsb.labelFileName != n->fsb.labelFileName) {
+        XMSTR_FREE(o->fsb.labelFileName);
+        XMS_STRDUP_RES(n->fsb.labelFileName);
+        XtVaSetValues(n->fsb.nameLabel, XmNlabelString, n->fsb.labelFileName, NULL);
+    }
+    if(o->fsb.labelDirectoryName != n->fsb.labelDirectoryName) {
+        XMSTR_FREE(o->fsb.labelDirectoryName);
+        XMS_STRDUP_RES(n->fsb.labelDirectoryName);
+    }
+    if(o->fsb.labelNewFileName != n->fsb.labelNewFileName) {
+        XMSTR_FREE(o->fsb.labelNewFileName);
+        XMS_STRDUP_RES(n->fsb.labelNewFileName);
+    }
+    
+    if(o->fsb.labelDeleteFile != n->fsb.labelDeleteFile) {
+        STR_FREE(o->fsb.labelDeleteFile);
+        STRDUP_RES(n->fsb.labelDeleteFile);
+    }
+#ifdef FSB_ENABLE_DETAIL
+    if(o->fsb.detailHeadings != n->fsb.detailHeadings) {
+        STR_FREE(o->fsb.detailHeadings);
+        STRDUP_RES(n->fsb.detailHeadings);
+        XtVaSetValues(n->fsb.grid, XmNsimpleHeadings, n->fsb.detailHeadings, NULL);
+    }
+#endif
+    if(o->fsb.dateFormatSameYear != n->fsb.dateFormatSameYear) {
+        STR_FREE(o->fsb.dateFormatSameYear);
+        STRDUP_RES(n->fsb.dateFormatSameYear);
+    }
+    if(o->fsb.dateFormatOtherYear != n->fsb.dateFormatOtherYear) {
+        STR_FREE(o->fsb.dateFormatOtherYear);
+        STRDUP_RES(n->fsb.dateFormatOtherYear);
+    }
+    if(o->fsb.suffixBytes != n->fsb.suffixBytes) {
+        STR_FREE(o->fsb.suffixBytes);
+        STRDUP_RES(n->fsb.suffixBytes);
+    }
+    if(o->fsb.suffixMB != n->fsb.suffixMB) {
+        STR_FREE(o->fsb.suffixMB);
+        STRDUP_RES(n->fsb.suffixMB);
+    }
+    if(o->fsb.suffixGB != n->fsb.suffixGB) {
+        STR_FREE(o->fsb.suffixGB);
+        STRDUP_RES(n->fsb.suffixGB);
+    }
+    if(o->fsb.suffixTB != n->fsb.suffixTB) {
+        STR_FREE(o->fsb.suffixTB);
+        STRDUP_RES(n->fsb.suffixTB);
+    }
+    if(o->fsb.errorTitle != n->fsb.errorTitle) {
+        STR_FREE(o->fsb.errorTitle);
+        STRDUP_RES(n->fsb.errorTitle);
+    }
+    if(o->fsb.errorIllegalChar != n->fsb.errorIllegalChar) {
+        STR_FREE(o->fsb.errorIllegalChar);
+        STRDUP_RES(n->fsb.errorIllegalChar);
+    }
+    if(o->fsb.errorRename != n->fsb.errorRename) {
+        STR_FREE(o->fsb.errorRename);
+        STRDUP_RES(n->fsb.errorRename);
+    }
+    if(o->fsb.errorFolder != n->fsb.errorFolder) {
+        STR_FREE(o->fsb.errorFolder);
+        STRDUP_RES(n->fsb.errorFolder);
+    }
+    if(o->fsb.errorDelete != n->fsb.errorDelete) {
+        STR_FREE(o->fsb.errorDelete);
+        STRDUP_RES(n->fsb.errorDelete);
+    }
+    if(o->fsb.errorOpenDir != n->fsb.errorOpenDir) {
+        STR_FREE(o->fsb.errorOpenDir);
+        STRDUP_RES(n->fsb.errorOpenDir);
+    }
+    
+    if(updateTitle) {
+        FSBUpdateTitle(neww);
+    }
+    if(recreateContextMenu) {
+        XtDestroyWidget(n->fsb.listContextMenu);
+        XtDestroyWidget(n->fsb.gridContextMenu);
+        n->fsb.listContextMenu = CreateContextMenu(n, n->fsb.filelist, FileContextMenuCB);
+        n->fsb.gridContextMenu = CreateContextMenu(n, n->fsb.grid, FileContextMenuCB);
+    }
+    if(setOkBtnLabel) {
+        XtVaSetValues(n->fsb.okBtn, XmNlabelString, n->fsb.type == FILEDIALOG_OPEN ? n->fsb.labelOpen : n->fsb.labelSave, NULL);
+    }
+    
+    if(!ismanaged && !n->fsb.disable_set_values) {
+        n->fsb.disable_set_values = 1;
+        XtVaSetValues(neww, XmNwidth, width, XmNheight, height, NULL);
+        n->fsb.disable_set_values = 0;
+        
+        if(n->fsb.pathBar)
+            n->fsb.pathBar->disableResize = False;
+    }
+    
+    if(selectItem) {
+        if(ismanaged) {
+            FSBSelectItem(n, FileName(n->fsb.selectedPath));
+        }
+    }
+     
+    Boolean fr = (xmFormClassRec.core_class.set_values)(old, request, neww, args, num_args);
+    return fr ? fr : r;
+}
+
+static void fsb_insert_child(Widget child) {
+    XnFileSelectionBox p = (XnFileSelectionBox)XtParent(child);
+    (xmFormClassRec.composite_class.insert_child)(child);
+    
+    if(!p->fsb.gui_created) {
+        return;
+    }
+    
+    // custom child widget insert
+    XtVaSetValues(child,
+            XmNbottomAttachment, XmATTACH_WIDGET,
+            XmNbottomWidget, p->fsb.bottom_widget,
+            XmNbottomOffset, p->fsb.widgetSpacing,
+            XmNleftAttachment, XmATTACH_FORM,
+            XmNleftOffset, p->fsb.windowSpacing,
+            XmNrightAttachment, XmATTACH_FORM,
+            XmNrightAttachment, XmATTACH_FORM,
+            XmNrightOffset, p->fsb.windowSpacing,
+            NULL);
+    
+    
+    XtVaSetValues(p->fsb.listform,
+            XmNbottomWidget, child,
+            XmNbottomOffset, 0,
+            NULL);
+    
+    p->fsb.workarea = child;
+}
+
+Boolean fsb_acceptfocus(Widget widget, Time *time) {
+    return 0;
+}
+
+static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb) {
+    XnFileSelectionBox w = (XnFileSelectionBox)widget;
+    pathbar_resize(w->fsb.pathBar->widget, w->fsb.pathBar, NULL);
+    
+    if(w->fsb.type == FILEDIALOG_OPEN) {
+        FSBView view = w->fsb.view[w->fsb.selectedview];
+        XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT);
+    } else {
+        XmProcessTraversal(w->fsb.name, XmTRAVERSE_CURRENT);
+    }
+    
+    
+    if(w->fsb.selectedPath) {
+        FSBSelectItem(w, FileName(w->fsb.selectedPath));
+    }
+}
+
+static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
+    
+}
+
+static int apply_filter(XnFileSelectionBox w, const char *pattern, const char *string) {
+    if(!pattern) return 0;
+    
+    FSBFilterFunc func = w->fsb.filterFunc ? w->fsb.filterFunc : FSBGlobFilter;
+    return func(pattern, string);
+}
+
+static int FSBGlobFilter(const char *a, const char *b) {
+    return fnmatch(a, b, 0);
+}
+
+
+static void errCB(Widget w, XtPointer d, XtPointer cbs) {
+    XtDestroyWidget(w);
+}
+
+static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg) {
+    Arg args[16];
+    int n = 0;
+    
+    XmString titleStr = XmStringCreateLocalized((char*)title);
+    XmString msg = XmStringCreateLocalized((char*)errmsg);
+    
+    XtSetArg(args[n], XmNdialogTitle, titleStr); n++;
+    XtSetArg(args[n], XmNselectionLabelString, msg); n++;
+    XtSetArg(args[n], XmNokLabelString, w->fsb.labelOk); n++;
+    XtSetArg(args[n], XmNcancelLabelString, w->fsb.labelCancel); n++;
+    
+    Widget dialog = XmCreatePromptDialog ((Widget)w, "NewFolderPrompt", args, n);
+    
+    Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
+    XtUnmanageChild(help);
+    Widget cancel = XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON);
+    XtUnmanageChild(cancel);
+    Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
+    XtUnmanageChild(text);
+    
+    XtAddCallback(dialog, XmNokCallback, errCB, NULL);
+    
+    XtManageChild(dialog);
+    
+    XmStringFree(titleStr);
+    XmStringFree(msg);
+}
+
+static void rename_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) {
+    XnFileSelectionBox fsb = NULL;
+    XtVaGetValues(w, XmNuserData, &fsb, NULL);
+    
+    char *fileName = NULL;
+    XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &fileName);
+    
+    // make sure the new file name doesn't contain a path separator
+    if(strchr(fileName, '/')) {
+        ErrDialog(fsb, fsb->fsb.errorTitle, fsb->fsb.errorIllegalChar);
+        XtFree(fileName);
+        return;
+    }
+    
+    char *parentPath = ParentPath(path);
+    char *newPath = ConcatPath(parentPath, fileName);
+    
+    if(rename(path, newPath)) {
+        char errmsg[256];
+        snprintf(errmsg, 256, fsb->fsb.errorRename, strerror(errno));
+        ErrDialog(fsb, fsb->fsb.errorTitle, errmsg);
+    } else {
+        filedialog_update_dir(fsb, parentPath);
+    }
+    
+    free(parentPath);
+    free(newPath);
+    XtFree(fileName);
+    XtDestroyWidget(XtParent(w));
+}
+
+static void selectionbox_cancel(Widget w, XtPointer data, XtPointer d) {
+    XtDestroyWidget(XtParent(w));
+}
+
+static void FSBRename(XnFileSelectionBox fsb, const char *path) {
+    Arg args[16];
+    int n = 0;
+    Widget w = (Widget)fsb;
+    
+    char *name = FileName((char*)path);
+    
+    XmString filename = XmStringCreateLocalized(name);
+    XtSetArg(args[n], XmNselectionLabelString,fsb->fsb.labelNewFileName); n++;
+    XtSetArg(args[n], XmNtextString, filename); n++;
+    XtSetArg(args[n], XmNuserData, fsb); n++;
+    XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelRename); n++;
+    XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++;
+    XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++;
+    Widget dialog = XmCreatePromptDialog (w, "RenameFilePrompt", args, n);
+    
+    Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
+    XtUnmanageChild(help);
+    
+    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)rename_file_cb, (char*)path);
+    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL);
+    
+    XmStringFree(filename);
+    XtManageChild(dialog);
+}
+
+static void delete_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) {
+    XnFileSelectionBox fsb = NULL;
+    XtVaGetValues(w, XmNuserData, &fsb, NULL);
+    
+    if(unlink(path)) {
+        char errmsg[256];
+        snprintf(errmsg, 256, fsb->fsb.errorDelete, strerror(errno));
+        ErrDialog(fsb, fsb->fsb.errorTitle, errmsg);
+    } else {
+        char *parentPath = ParentPath(path);
+        filedialog_update_dir(fsb, parentPath);
+        free(parentPath);
+    }
+    
+    XtDestroyWidget(XtParent(w));
+}
+
+static void FSBDelete(XnFileSelectionBox fsb, const char *path) {
+    Arg args[16];
+    int n = 0;
+    Widget w = (Widget)fsb;
+    
+    char *name = FileName((char*)path);
+    size_t len = strlen(name);
+    size_t msglen = len + strlen(fsb->fsb.labelDeleteFile) + 4;
+    char *msg = malloc(msglen);
+    snprintf(msg, msglen, fsb->fsb.labelDeleteFile, name);
+    
+    XmString prompt = XmStringCreateLocalized(msg);
+    XtSetArg(args[n], XmNselectionLabelString, prompt); n++;
+    XtSetArg(args[n], XmNuserData, fsb); n++;
+    XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelDelete); n++;
+    XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++;
+    XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++;
+    Widget dialog = XmCreatePromptDialog (w, "DeleteFilePrompt", args, n);
+    
+    Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
+    XtUnmanageChild(help);
+    Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
+    XtUnmanageChild(text);
+    
+    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)delete_file_cb, (char*)path);
+    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL);
+    
+    free(msg);
+    XmStringFree(prompt);
+    XtManageChild(dialog);
+}
+
+static void FSBSelectItem(XnFileSelectionBox fsb, const char *item) {
+    FSBView view = fsb->fsb.view[fsb->fsb.selectedview];
+    if(view.select) {
+        view.select((Widget)fsb, view.widget, item);
+    }
+}
+
+static char* set_selected_path(XnFileSelectionBox data, XmString item)
+{
+    char *name = NULL;
+    XmStringGetLtoR(item, XmFONTLIST_DEFAULT_TAG, &name);
+    if(!name) {
+        return NULL;
+    }
+    char *path = ConcatPath(data->fsb.currentPath, name);
+    XtFree(name);
+    
+    if(data->fsb.selectedPath) {
+        free(data->fsb.selectedPath);
+    }
+    data->fsb.selectedPath = path;
+    
+    return path;
+}
+
+// item0: rename
+// item1: delete
+static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd) {
+    intptr_t i = (intptr_t)index;
+    Widget parent = XtParent(item);
+    XnFileSelectionBox fsb = NULL;
+    XtVaGetValues(parent, XmNuserData, &fsb, NULL);
+    
+    const char *path = fsb->fsb.selectedPath;
+    if(path) {
+        if(i == 0) {
+            FSBRename(fsb, path);
+        } else if(i == 1) {
+            FSBDelete(fsb, path);
+        }
+    }
+}
+
+static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback) {
+    return XmVaCreateSimplePopupMenu(
+            parent, "popup", callback, XmNpopupEnabled, XmPOPUP_AUTOMATIC,
+            XmNuserData, fsb,
+            XmVaPUSHBUTTON, fsb->fsb.labelRename, 'R', NULL, NULL,
+            XmVaPUSHBUTTON, fsb->fsb.labelDelete, 'D', NULL, NULL,
+            NULL);
+}
+
+
+static void FileListUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) {
+    XnFileSelectionBox data = userData;
+    FileListWidgetAdd(data, data->fsb.filelist, data->fsb.showHidden, filter, files, filecount);
+}
+
+static void FileListSelect(Widget fsb, Widget view, const char *item) {
+    XnFileSelectionBox w = (XnFileSelectionBox)fsb;
+    
+    int numItems = 0;
+    XmStringTable items = NULL;
+    XtVaGetValues(w->fsb.filelist, XmNitemCount, &numItems, XmNitems, &items, NULL);
+    
+    for(int i=0;i<numItems;i++) {
+        char *str = NULL;
+        XmStringGetLtoR(items[i], XmFONTLIST_DEFAULT_TAG, &str);
+        if(!strcmp(str, item)) {
+            XmListSelectPos(w->fsb.filelist, i+1, False);
+            break;
+        }
+        XtFree(str);
+    }
+}
+
+static void FileListCleanup(Widget fsb, Widget view, void *userData) {
+    XnFileSelectionBox data = userData;
+    XmListDeleteAllItems(data->fsb.filelist);
+}
+
+static void FileListDestroy(Widget fsb, Widget view, void *userData) {
+    // unused
+}
+
+static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
+{
+    char *path = set_selected_path(data, cb->item);
+    if(path) {
+        data->fsb.end = True;
+        data->fsb.status = FILEDIALOG_OK;
+        data->fsb.selIsDir = False;
+        FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath);
+    }
+}
+
+static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
+{
+    if(data->fsb.type == FILEDIALOG_SAVE) {
+        char *name = NULL;
+        XmStringGetLtoR(cb->item, XmFONTLIST_DEFAULT_TAG, &name);
+        XmTextFieldSetString(data->fsb.name, name);
+        XtFree(name);
+    } else {
+        char *path = set_selected_path(data, cb->item);
+        if(path) {
+            data->fsb.selIsDir = False;
+        }
+    }
+}
+
+
+static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count)
+{   
+    if(count > 0) {
+        XmStringTable items = calloc(count, sizeof(XmString));
+        int i = 0;
+        
+        for(int j=0;j<count;j++) {
+            FileElm *e = &ls[j];
+            
+            char *name = FileName(e->path);
+            if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) {
+                continue;
+            }
+            
+            items[i] = XmStringCreateLocalized(name);
+            i++;
+        }
+        XmListAddItems(w, items, i, 0);
+        for(i=0;i<count;i++) {
+            XmStringFree(items[i]);
+        }
+        free(items);
+    }
+}
+
+#ifdef FSB_ENABLE_DETAIL
+static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) {
+    XnFileSelectionBox data = userData;
+    FileListDetailAdd(data, data->fsb.grid, data->fsb.showHidden, filter, files, filecount, maxnamelen);
+}
+#endif
+
+/*
+ * create file size string with kb/mb/gb/tb suffix
+ */
+static char* size_str(XnFileSelectionBox fsb, FileElm *f) {
+    char *str = malloc(16);
+    uint64_t size = f->size;
+    
+    if(f->isDirectory) {
+        str[0] = '\0';
+    } else if(size < 0x400) {
+        snprintf(str, 16, "%d %s", (int)size, fsb->fsb.suffixBytes);
+    } else if(size < 0x100000) {
+        float s = (float)size/0x400;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x2800 && diff != 0) {
+            // size < 10 KiB
+            snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixKB);
+        } else {
+            snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixKB);
+        }
+    } else if(size < 0x40000000) {
+        float s = (float)size/0x100000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0xa00000 && diff != 0) {
+            // size < 10 MiB
+            snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixMB);
+        } else {
+            snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixMB);
+        }
+    } else if(size < 0x1000000000ULL) {
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 GiB
+            snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixGB);
+        } else {
+            snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixGB);
+        }
+    } else {
+        size /= 1024;
+        float s = (float)size/0x40000000;
+        int diff = (s*100 - (int)s*100);
+        if(diff > 90) {
+            diff = 0;
+            s += 0.10f;
+        }
+        if(size < 0x280000000 && diff != 0) {
+            // size < 10 TiB
+            snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixTB);
+        } else {
+            snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixTB);
+        }
+    }
+    return str;
+}
+
+static char* date_str(XnFileSelectionBox fsb, time_t tm) {
+    struct tm t;
+    struct tm n;
+    time_t now = time(NULL);
+    
+    localtime_r(&tm, &t);
+    localtime_r(&now, &n);
+    
+    char *str = malloc(24);
+    if(t.tm_year == n.tm_year) {
+        strftime(str, 24, fsb->fsb.dateFormatSameYear, &t);
+    } else {
+        strftime(str, 24, fsb->fsb.dateFormatOtherYear, &t);
+    }
+    return str;
+}
+
+#ifdef FSB_ENABLE_DETAIL
+static void FileListDetailAdjustColWidth(Widget grid) {
+    XmLGridColumn column0 = XmLGridGetColumn(grid, XmCONTENT, 0);
+    XmLGridColumn column1 = XmLGridGetColumn(grid, XmCONTENT, 1);
+    XmLGridColumn column2 = XmLGridGetColumn(grid, XmCONTENT, 2);
+    
+    Dimension col0Width = XmLGridColumnWidthInPixels(column0);
+    Dimension col1Width = XmLGridColumnWidthInPixels(column1);
+    Dimension col2Width = XmLGridColumnWidthInPixels(column2);
+    
+    Dimension totalWidth = col0Width + col1Width + col2Width;
+    
+    Dimension gridWidth = 0;
+    Dimension gridShadow = 0;
+    XtVaGetValues(grid, XmNwidth, &gridWidth, XmNshadowThickness, &gridShadow, NULL);
+    
+    Dimension widthDiff = gridWidth - totalWidth - gridShadow - gridShadow;
+    
+    if(gridWidth > totalWidth) {
+            XtVaSetValues(grid,
+            XmNcolumnRangeStart, 0,
+            XmNcolumnRangeEnd, 0,
+            XmNcolumnWidth, col0Width + widthDiff - XmLGridVSBWidth(grid) - 2,
+            XmNcolumnSizePolicy, XmCONSTANT,
+            NULL);
+    }
+}
+
+static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth)
+{
+    XmLGridAddRows(grid, XmCONTENT, 1, count);
+    
+    int row = 0;
+    for(int i=0;i<count;i++) {
+        FileElm *e = &ls[i];
+        
+        char *name = FileName(e->path);
+        if((!showHidden && name[0] == '.') || (!e->isDirectory && apply_filter(fsb, filter, name))) {
+            continue;
+        }
+        
+        // name
+        XmString str = XmStringCreateLocalized(name);
+        XtVaSetValues(grid,
+                XmNcolumn, 0, 
+                XmNrow, row,
+                XmNcellString, str, NULL);
+        XmStringFree(str);
+        // size
+        char *szbuf = size_str(fsb, e);
+        str = XmStringCreateLocalized(szbuf);
+        XtVaSetValues(grid,
+                XmNcolumn, 1, 
+                XmNrow, row,
+                XmNcellString, str, NULL);
+        free(szbuf);
+        XmStringFree(str);
+        // date
+        char *datebuf = date_str(fsb, e->lastModified);
+        str = XmStringCreateLocalized(datebuf);
+        XtVaSetValues(grid,
+                XmNcolumn, 2, 
+                XmNrow, row,
+                XmNcellString, str, NULL);
+        free(datebuf);
+        XmStringFree(str);
+        
+        XtVaSetValues(grid, XmNrow, row, XmNrowUserData, e, NULL);
+        row++;
+    }
+    
+    // remove unused rows
+    if(count > row) {
+        XmLGridDeleteRows(grid, XmCONTENT, row, count-row);
+    }
+    
+    if(maxWidth < 16) {
+        maxWidth = 16;
+    }
+    
+    XtVaSetValues(grid,
+        XmNcolumnRangeStart, 0,
+        XmNcolumnRangeEnd, 0,
+        XmNcolumnWidth, maxWidth,
+        XmNcellAlignment, XmALIGNMENT_LEFT,
+        XmNcolumnSizePolicy, XmVARIABLE,
+        NULL);
+    XtVaSetValues(grid,
+        XmNcolumnRangeStart, 1,
+        XmNcolumnRangeEnd, 1,
+        XmNcolumnWidth, 9,
+        XmNcellAlignment, XmALIGNMENT_LEFT,
+        XmNcolumnSizePolicy, XmVARIABLE,
+        NULL);
+    XtVaSetValues(grid,
+        XmNcolumnRangeStart, 2,
+        XmNcolumnRangeEnd, 2,
+        XmNcolumnWidth, 16,
+        XmNcellAlignment, XmALIGNMENT_RIGHT,
+        XmNcolumnSizePolicy, XmVARIABLE,
+        NULL);
+    
+    FileListDetailAdjustColWidth(grid);
+}
+
+static void FileListDetailSelect(Widget fsb, Widget view, const char *item) {
+    XnFileSelectionBox w = (XnFileSelectionBox)fsb;
+    
+    int numRows = 0;
+    XtVaGetValues(w->fsb.grid, XmNrows, &numRows, NULL);
+    
+    XmLGridColumn col = XmLGridGetColumn(w->fsb.grid, XmCONTENT, 0);
+    for(int i=0;i<numRows;i++) {
+        XmLGridRow row = XmLGridGetRow(w->fsb.grid, XmCONTENT, i);
+        FileElm *elm = NULL;
+        XtVaGetValues(w->fsb.grid, XmNrowPtr, row, XmNcolumnPtr, col, XmNrowUserData, &elm, NULL);
+        if(elm) {
+            if(!strcmp(item, FileName(elm->path))) {
+                XmLGridSelectRow(w->fsb.grid, i, False);
+                XmLGridFocusAndShowRow(w->fsb.grid, i+1);
+                break;
+            }
+        }
+    }
+}
+
+static void FileListDetailCleanup(Widget fsb, Widget view, void *userData) {
+    XnFileSelectionBox data = userData;
+    // cleanup grid
+    Cardinal rows = 0;
+    XtVaGetValues(data->fsb.grid, XmNrows, &rows, NULL);
+    XmLGridDeleteRows(data->fsb.grid, XmCONTENT, 0, rows);
+}
+
+static void FileListDetailDestroy(Widget fsb, Widget view, void *userData) {
+    // unused
+}
+#endif
+
+static void create_folder(Widget w, XnFileSelectionBox data, XmSelectionBoxCallbackStruct *cbs) {
+    char *fileName = NULL;
+    XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &fileName);
+    
+    char *newFolder = ConcatPath(data->fsb.currentPath ? data->fsb.currentPath : "", fileName);
+    if(mkdir(newFolder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
+        char errmsg[256];
+        snprintf(errmsg, 256, data->fsb.errorFolder, strerror(errno));
+        ErrDialog(data, data->fsb.errorTitle, errmsg);
+    } else {
+        char *p = strdup(data->fsb.currentPath);
+        filedialog_update_dir(data, p);
+        free(p);
+    }
+    free(newFolder);
+    
+    XtDestroyWidget(XtParent(w));
+}
+
+static void new_folder_cancel(Widget w, XnFileSelectionBox data, XtPointer d) {
+    XtDestroyWidget(XtParent(w));
+}
+
+static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u)
+{
+    Arg args[16];
+    int n = 0;
+    
+    XtSetArg(args[n], XmNdialogTitle, data->fsb.labelNewFolder); n++;
+    XtSetArg (args[n], XmNselectionLabelString, data->fsb.labelDirectoryName); n++;
+    XtSetArg(args[n], XmNokLabelString, data->fsb.labelOk); n++;
+    XtSetArg(args[n], XmNcancelLabelString, data->fsb.labelCancel); n++;
+    Widget dialog = XmCreatePromptDialog (w, "NewFolderPrompt", args, n);
+    
+    Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
+    XtUnmanageChild(help);
+    
+    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)create_folder, data);
+    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)new_folder_cancel, data);
+    
+    XtManageChild(dialog);
+    
+}
+
+static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u) {
+    const char *homePath = data->fsb.homePath ? data->fsb.homePath : GetHomeDir();
+    filedialog_update_dir(data, homePath);
+    PathBarSetPath(data->fsb.pathBar, homePath);
+}
+
+
+/*
+ * file_cmp_field
+ * 0: compare path
+ * 1: compare size
+ * 2: compare mtime
+ */
+static int file_cmp_field = 0;
+
+/*
+ * 1 or -1
+ */
+static int file_cmp_order = 1;
+
+static int filecmp(const void *f1, const void *f2)
+{
+    const FileElm *file1 = f1;
+    const FileElm *file2 = f2;
+    if(file1->isDirectory != file2->isDirectory) {
+        return file1->isDirectory < file2->isDirectory;
+    }
+    
+    int cmp_field = file_cmp_field;
+    int cmp_order = file_cmp_order;
+    if(file1->isDirectory) {
+        cmp_field = 0;
+        cmp_order = 1;
+    }
+    
+    int ret = 0;
+    switch(cmp_field) {
+        case 0: {
+            ret = strcmp(FileName(file1->path), FileName(file2->path));
+            break;
+        }
+        case 1: {
+            if(file1->size < file2->size) {
+                ret = -1;
+            } else if(file1->size == file2->size) {
+                ret = 0;
+            } else {
+                ret = 1;
+            }
+            break;
+        }
+        case 2: {
+            if(file1->lastModified < file2->lastModified) {
+                ret = -1;
+            } else if(file1->lastModified == file2->lastModified) {
+                ret = 0;
+            } else {
+                ret = 1;
+            }
+            break;
+        }
+    }
+    
+    return ret * cmp_order;
+}
+
+
+static void free_files(FileElm *ls, int count)
+{
+    for(int i=0;i<count;i++) {
+        if(ls[i].path) {
+            free(ls[i].path);
+        }
+    }
+    free(ls);
+}
+
+static void filedialog_cleanup_filedata(XnFileSelectionBox data)
+{
+    free_files(data->fsb.dirs, data->fsb.dircount);
+    free_files(data->fsb.files, data->fsb.filecount);
+    data->fsb.dirs = NULL;
+    data->fsb.files = NULL;
+    data->fsb.dircount = 0;
+    data->fsb.filecount = 0;
+    data->fsb.maxnamelen = 0;
+}
+
+#define FILE_ARRAY_SIZE 1024
+
+static void file_array_add(FileElm **files, int *alloc, int *count, FileElm elm) {
+    int c = *count;
+    int a = *alloc;
+    if(c >= a) {
+        a *= 2;
+        FileElm *newarray = realloc(*files, sizeof(FileElm) * a);
+        
+        *files = newarray;
+        *alloc = a;
+    }
+    
+    (*files)[c] = elm;
+    c++;
+    *count = c;
+}
+
+static int filedialog_update_dir(XnFileSelectionBox data, const char *path)
+{
+    DIR *dir = NULL;
+    if(path) {
+        // try to check first, if we can open the path
+        dir = opendir(path);
+        if(!dir) {
+            char errmsg[256];
+            snprintf(errmsg, 256, data->fsb.errorOpenDir, strerror(errno));
+            
+            ErrDialog(data, data->fsb.errorTitle, errmsg);
+            return 1;
+        }
+    }
+    
+    FSBView view = data->fsb.view[data->fsb.selectedview];
+    view.cleanup((Widget)data, view.widget, view.userData);
+      
+    if(view.useDirList) {
+        XmListDeleteAllItems(data->fsb.dirlist);
+    }
+    
+    /* read dir and insert items */
+    if(path) {
+        int dircount = 0; 
+        int filecount = 0;
+        size_t maxNameLen = 0;
+        
+        FileElm *dirs = calloc(sizeof(FileElm), FILE_ARRAY_SIZE);
+        FileElm *files = calloc(sizeof(FileElm), FILE_ARRAY_SIZE);
+        int dirs_alloc = FILE_ARRAY_SIZE;
+        int files_alloc = FILE_ARRAY_SIZE;
+        
+        filedialog_cleanup_filedata(data);
+    
+        /* dir reading complete - set the path textfield */  
+        XmTextFieldSetString(data->fsb.path, (char*)path);
+        char *oldPath = data->fsb.currentPath;
+        data->fsb.currentPath = strdup(path);
+        if(oldPath) {
+            free(oldPath);
+        }
+        path = data->fsb.currentPath;
+
+        struct dirent *ent;
+        while((ent = readdir(dir)) != NULL) {
+            if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
+                continue;
+            }
+
+            char *entpath = ConcatPath(path, ent->d_name);
+
+            struct stat s;
+            if(stat(entpath, &s)) {
+                free(entpath);
+                continue;
+            }
+
+            FileElm new_entry;
+            new_entry.path = entpath;
+            new_entry.isDirectory = S_ISDIR(s.st_mode);
+            new_entry.size = (uint64_t)s.st_size;
+            new_entry.lastModified = s.st_mtime;
+
+            size_t nameLen = strlen(ent->d_name);
+            if(nameLen > maxNameLen) {
+                maxNameLen = nameLen;
+            }
+
+            if(new_entry.isDirectory) {
+                file_array_add(&dirs, &dirs_alloc, &dircount, new_entry);
+            } else {
+                file_array_add(&files, &files_alloc, &filecount, new_entry);
+            }
+        }
+        closedir(dir);
+        
+        data->fsb.dirs = dirs;
+        data->fsb.files = files;
+        data->fsb.dircount = dircount;
+        data->fsb.filecount = filecount;
+        data->fsb.maxnamelen = maxNameLen;
+        
+        // sort file arrays
+        qsort(dirs, dircount, sizeof(FileElm), filecmp);
+        qsort(files, filecount, sizeof(FileElm), filecmp);
+    }
+    
+    Widget filterTF = XmDropDownGetText(data->fsb.filter);
+    char *filter = XmTextFieldGetString(filterTF);
+    char *filterStr = filter;
+    if(!filter || strlen(filter) == 0) {
+        filterStr = "*";
+    }
+    
+    if(view.useDirList) {
+        FileListWidgetAdd(data, data->fsb.dirlist, data->fsb.showHidden, NULL, data->fsb.dirs, data->fsb.dircount);
+        view.update(
+                (Widget)data,
+                view.widget,
+                NULL,
+                0,
+                data->fsb.files,
+                data->fsb.filecount,
+                filterStr,
+                data->fsb.maxnamelen,
+                view.userData);
+    } else {
+        view.update(
+                (Widget)data,
+                view.widget,
+                data->fsb.dirs,
+                data->fsb.dircount,
+                data->fsb.files,
+                data->fsb.filecount,
+                filterStr,
+                data->fsb.maxnamelen,
+                view.userData);
+    }
+    
+    if(filter) {
+        XtFree(filter);
+    }
+    
+    return 0;
+}
+
+
+static void dirlist_activate(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
+{
+    char *path = set_selected_path(data, cb->item);
+    if(path) {
+        if(!filedialog_update_dir(data, path)) {
+            PathBarSetPath(data->fsb.pathBar, path);
+            data->fsb.selIsDir = TRUE;
+        } 
+    }    
+}
+
+static void dirlist_select(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
+{
+    char *path = set_selected_path(data, cb->item);
+    if(path) {
+        data->fsb.selIsDir = TRUE;
+    }
+}
+
+static void filedialog_enable_detailview(Widget w, XnFileSelectionBox data, XmToggleButtonCallbackStruct *tb) {
+    SelectView(data, tb->set); // 0: list, 1: detail
+}
+
+
+static void filedialog_setshowhidden(
+        Widget w,
+        XnFileSelectionBox data,
+        XmToggleButtonCallbackStruct *tb)
+{
+    data->fsb.showHidden = tb->set;
+    filedialog_update_dir(data, NULL);
+}
+
+static void filedialog_filter(Widget w, XnFileSelectionBox data, XtPointer c)
+{
+    filedialog_update_dir(data, NULL);
+}
+
+static void filedialog_update_filter(Widget w, XnFileSelectionBox data, XtPointer c)
+{
+    filedialog_update_dir(data, NULL);
+    
+}
+
+static void filedialog_goup(Widget w, XnFileSelectionBox data, XtPointer d)
+{
+    char *newPath = ParentPath(data->fsb.currentPath);
+    filedialog_update_dir(data, newPath);
+    PathBarSetPath(data->fsb.pathBar, newPath);
+    free(newPath);
+}
+
+static void filedialog_ok(Widget w, XnFileSelectionBox data, XtPointer d)
+{
+    if(data->fsb.type == FILEDIALOG_SAVE) {
+        char *newName = XmTextFieldGetString(data->fsb.name);
+        if(newName) {
+            if(strchr(newName, '/')) {
+                ErrDialog(data, data->fsb.errorTitle, data->fsb.errorIllegalChar);
+                XtFree(newName);
+                return;
+            }
+            
+            if(strlen(newName) > 0) {
+                char *selPath = ConcatPath(data->fsb.currentPath, newName);
+                if(data->fsb.selectedPath) free(data->fsb.selectedPath);
+                data->fsb.selectedPath = selPath;
+            }
+            XtFree(newName);
+            
+            data->fsb.selIsDir = False;
+        }
+    }
+    
+    if(data->fsb.selectedPath) {
+        if(!data->fsb.selIsDir) {
+            data->fsb.status = FILEDIALOG_OK;
+            data->fsb.end = True;
+            FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath);
+        }
+    }
+}
+
+static void filedialog_cancel(Widget w, XnFileSelectionBox data, XtPointer d)
+{
+    data->fsb.end = 1;
+    data->fsb.status = FILEDIALOG_CANCEL;
+    FileSelectionCallback(data, data->fsb.cancelCallback, XmCR_CANCEL, data->fsb.currentPath);
+}
+
+static void filedialog_help(Widget w, XnFileSelectionBox data, XtPointer d)
+{
+    FileSelectionCallback(data, data->manager.help_callback, XmCR_HELP, data->fsb.currentPath);
+}
+
+static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value) {
+    XmFileSelectionBoxCallbackStruct cbs;
+    memset(&cbs, 0, sizeof(XmFileSelectionBoxCallbackStruct));
+    
+    char *dir = fsb->fsb.currentPath;
+    size_t dirlen = dir ? strlen(dir) : 0;
+    if(dir && dirlen > 0) {
+        char *dir2 = NULL;
+        if(dir[dirlen-1] != '/') {
+            // add a trailing / to the dir string 
+            dir2 = malloc(dirlen+2);
+            memcpy(dir2, dir, dirlen);
+            dir2[dirlen] = '/';
+            dir2[dirlen+1] = '\0';
+            dirlen++;
+            dir = dir2;
+        }
+        cbs.dir = XmStringCreateLocalized(dir);
+        cbs.dir_length = dirlen;
+        if(dir2) {
+            free(dir2);
+        }
+    } else {
+        cbs.dir = XmStringCreateLocalized("");
+        cbs.dir_length = 0;
+    }
+    cbs.reason = reason;
+    
+    cbs.value = XmStringCreateLocalized((char*)value);
+    cbs.length = strlen(value);
+    
+    XtCallCallbackList((Widget)fsb, cb, (XtPointer)&cbs);
+    
+    XmStringFree(cbs.dir);
+    XmStringFree(cbs.value);
+}
+
+static void CreateUI(XnFileSelectionBox w) { 
+    Arg args[32];
+    int n = 0;
+    XmString str;
+       
+    int widget_spacing = w->fsb.widgetSpacing;
+    int window_spacing = w->fsb.windowSpacing;
+    
+    Widget form = (Widget)w;
+    int type = w->fsb.type;
+    
+    XtVaSetValues((Widget)w, XmNautoUnmanage, False, NULL);
+     
+    /* upper part of the gui */
+    
+    n = 0;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelDirUp); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNresizable, True); n++;
+    XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++;
+    w->fsb.dirUp = XmCreatePushButton(form, "DirUp", args, n);
+    XtManageChild(w->fsb.dirUp);
+    XtAddCallback(w->fsb.dirUp, XmNactivateCallback,
+                 (XtCallbackProc)filedialog_goup, w);
+    
+    // View Option Menu
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNshadowThickness, 0); n++;
+    Widget viewframe = XmCreateForm(form, "vframe", args, n);
+    XtManageChild(viewframe);
+
+    w->fsb.viewMenu = XmCreatePulldownMenu(viewframe, "menu", NULL, 0);
+    
+    Widget view;
+    if(w->fsb.showViewMenu) {
+        n = 0;
+        XtSetArg(args[n], XmNsubMenuId, w->fsb.viewMenu); n++;
+        XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNmarginHeight, 0); n++;
+        XtSetArg(args[n], XmNmarginWidth, 0); n++;
+        view = XmCreateOptionMenu(viewframe, "option_menu", args, n);
+        XtManageChild(view);
+        w->fsb.viewOption = view;
+        w->fsb.detailToggleButton = NULL;
+    } else {
+        n = 0;
+        str = XmStringCreateLocalized(w->fsb.labelDetailView);
+        XtSetArg(args[n], XmNlabelString, str); n++;
+        XtSetArg(args[n], XmNfillOnSelect, True); n++;
+        XtSetArg(args[n], XmNindicatorOn, False); n++;
+        XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+        if(w->fsb.selectedview == 1) {
+            XtSetArg(args[n], XmNset, 1); n++;
+        }
+        w->fsb.detailToggleButton = XmCreateToggleButton(viewframe, "ToggleDetailView", args, n);
+        XtManageChild(w->fsb.detailToggleButton);
+        view = w->fsb.detailToggleButton;
+        XmStringFree(str);
+        
+        XtAddCallback(
+            w->fsb.detailToggleButton,
+            XmNvalueChangedCallback,
+            (XtCallbackProc)filedialog_enable_detailview,
+            w);
+        
+        w->fsb.viewOption = NULL;
+    }    
+
+    n = 0;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightWidget, view); n++;
+    XtSetArg(args[n], XmNmarginHeight, 0); n++;
+    XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelNewFolder); n++;
+    w->fsb.newFolder = XmCreatePushButton(viewframe, "NewFolder", args, n);
+    XtManageChild(w->fsb.newFolder);
+    XtAddCallback(
+            w->fsb.newFolder,
+            XmNactivateCallback,
+            (XtCallbackProc)FSBNewFolder,
+            w);
+    
+
+    n = 0;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelHome); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNrightWidget, w->fsb.newFolder); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    w->fsb.home = XmCreatePushButton(viewframe, "Home", args, n);
+    XtManageChild(w->fsb.home);
+    XtAddCallback(
+            w->fsb.home,
+            XmNactivateCallback,
+            (XtCallbackProc)FSBHome,
+            w);
+    
+    // match visual appearance of detailToggleButton with the other buttons
+    if(w->fsb.detailToggleButton) {
+        Dimension highlight, shadow;
+        XtVaGetValues(w->fsb.newFolder, XmNshadowThickness, &shadow, XmNhighlightThickness, &highlight, NULL);
+        XtVaSetValues(w->fsb.detailToggleButton, XmNshadowThickness, shadow, XmNhighlightThickness, highlight, NULL);
+    }
+    
+    // pathbar
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNleftWidget, w->fsb.dirUp); n++;
+    XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNrightWidget, viewframe); n++;
+    XtSetArg(args[n], XmNrightOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); n++;
+    Widget pathBarFrame = XmCreateFrame(form, "pathbar_frame", args, n);
+    XtManageChild(pathBarFrame);
+    w->fsb.pathBar = CreatePathBar(pathBarFrame, args, 0);
+    w->fsb.pathBar->updateDir = (updatedir_callback)filedialog_update_dir;
+    w->fsb.pathBar->updateDirData = w;
+    XtManageChild(w->fsb.pathBar->widget);
+    w->fsb.path = XmCreateTextField(form, "textfield", args, 0);
+    
+    XtVaSetValues(w->fsb.dirUp, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL);
+    if(!w->fsb.showViewMenu) {
+        XtVaSetValues(viewframe, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL);
+    }
+    
+    n = 0;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNtopWidget, pathBarFrame); n++;
+    XtSetArg(args[n], XmNtopOffset, 2*widget_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
+    w->fsb.filterForm = XmCreateForm(form, "filterform", args, n);
+    XtManageChild(w->fsb.filterForm);
+    
+    n = 0;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelDirectories); n++;
+    w->fsb.lsDirLabel = XmCreateLabel(w->fsb.filterForm, "labelDirs", args, n);
+    XtManageChild(w->fsb.lsDirLabel);
+    
+    n = 0;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
+    XtSetArg(args[n], XmNleftPosition, 35); n++;
+    XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++;
+    w->fsb.lsFileLabel = XmCreateLabel(w->fsb.filterForm, "labelFiles", args, n);
+    XtManageChild(w->fsb.lsFileLabel);
+      
+    if(w->fsb.showHiddenButton) {
+        n = 0;
+        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNlabelString, w->fsb.labelShowHiddenFiles); n++;
+        XtSetArg(args[n], XmNset, w->fsb.showHidden); n++;
+        w->fsb.showHiddenButtonW = XmCreateToggleButton(w->fsb.filterForm, "showHidden", args, n);
+        XtManageChild(w->fsb.showHiddenButtonW);
+        XtAddCallback(w->fsb.showHiddenButtonW, XmNvalueChangedCallback,
+                     (XtCallbackProc)filedialog_setshowhidden, w);
+    }
+    
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelFilterButton); n++;
+    if(w->fsb.showHiddenButton) {
+        XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
+        XtSetArg(args[n], XmNrightWidget, w->fsb.showHiddenButtonW); n++;
+    } else {
+        XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    }
+    w->fsb.filterButton = XmCreatePushButton(w->fsb.filterForm, "filedialog_filter", args, n);
+    XtManageChild(w->fsb.filterButton);
+    XtAddCallback(w->fsb.filterButton, XmNactivateCallback,
+                 (XtCallbackProc)filedialog_filter, w);
+    
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNleftWidget, w->fsb.lsFileLabel); n++;
+    XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNrightWidget, w->fsb.filterButton); n++;
+    XtSetArg(args[n], XmNrightOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNshowLabel, False); n++;
+    XtSetArg(args[n], XmNuseTextField, True); n++;
+    XtSetArg(args[n], XmNverify, False); n++;
+    w->fsb.filter = XmCreateDropDown(w->fsb.filterForm, "filedialog_filter_textfield", args, n);
+    XtManageChild(w->fsb.filter);
+    XmTextFieldSetString(XmDropDownGetText(w->fsb.filter), w->fsb.filterStr);
+    XtAddCallback(XmDropDownGetText(w->fsb.filter), XmNactivateCallback,
+                 (XtCallbackProc)filedialog_filter, w);
+    XtAddCallback(w->fsb.filter, XmNupdateTextCallback,
+                 (XtCallbackProc)filedialog_update_filter, w);
+    Widget filterList = XmDropDownGetList(w->fsb.filter);
+    str = XmStringCreateSimple("*");
+    XmListAddItem(filterList, str, 0);
+    XmStringFree(str);
+        
+    /* lower part */
+    n = 0;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNtopOffset, widget_spacing * 2); n++;
+    Widget buttons = XmCreateForm(form, "buttons", args, n);
+    XtManageChild(buttons);
+    
+    n = 0;
+    str = type == FILEDIALOG_OPEN ? w->fsb.labelOpen : w->fsb.labelSave;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNlabelString, str); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
+    XtSetArg(args[n], XmNrightPosition, 14); n++;
+    w->fsb.okBtn = XmCreatePushButton(buttons, "filedialog_open", args, n);
+    XtManageChild(w->fsb.okBtn);
+    XmStringFree(str);
+    XtAddCallback(w->fsb.okBtn, XmNactivateCallback,
+                 (XtCallbackProc)filedialog_ok, w);
+    
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelHelp); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
+    XtSetArg(args[n], XmNleftPosition, 86); n++;
+    w->fsb.helpBtn = XmCreatePushButton(buttons, "filedialog_help", args, n);
+    XtManageChild(w->fsb.helpBtn);
+    XtAddCallback(w->fsb.helpBtn, XmNactivateCallback,
+                 (XtCallbackProc)filedialog_help, w);
+    
+    n = 0;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
+    XtSetArg(args[n], XmNleftPosition, 43); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
+    XtSetArg(args[n], XmNrightPosition, 57); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelCancel); n++;
+    w->fsb.cancelBtn = XmCreatePushButton(buttons, "filedialog_cancel", args, n);
+    XtManageChild(w->fsb.cancelBtn);
+    XtAddCallback(w->fsb.cancelBtn, XmNactivateCallback,
+                 (XtCallbackProc)filedialog_cancel, w);
+    
+    n = 0;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNbottomWidget, buttons); n++;
+    XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftOffset, 1); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightOffset, 1); n++;
+    w->fsb.separator = XmCreateSeparator(form, "ofd_separator", args, n);
+    XtManageChild(w->fsb.separator);
+    
+    Widget bottomWidget = w->fsb.separator;
+    
+    n = 0;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNbottomWidget, w->fsb.separator); n++;
+    XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
+    w->fsb.name = XmCreateTextField(form, "textfield", args, n);
+    XtAddCallback(w->fsb.name, XmNactivateCallback,
+             (XtCallbackProc)filedialog_ok, w);
+
+    n = 0;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNbottomWidget, w->fsb.name); n++;
+    XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelFileName); n++;
+    w->fsb.nameLabel = XmCreateLabel(form, "label", args, n);
+        
+    if(type == FILEDIALOG_SAVE) {
+        bottomWidget = w->fsb.nameLabel;
+        XtManageChild(w->fsb.name);
+        XtManageChild(w->fsb.nameLabel);
+    }
+    w->fsb.bottom_widget = bottomWidget;
+    
+    
+    // middle 
+    // form for dir/file lists
+    n = 0;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNtopWidget, w->fsb.filterForm); n++;
+    XtSetArg(args[n], XmNtopOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNbottomWidget, bottomWidget); n++;
+    XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
+    XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNwidth, 580); n++;
+    XtSetArg(args[n], XmNheight, 400); n++;
+    w->fsb.listform = XmCreateForm(form, "fds_listform", args, n); 
+    
+    // dir/file lists
+    
+    n = 0;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopWidget, w->fsb.lsDirLabel); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
+    XtSetArg(args[n], XmNrightPosition, 35); n++;
+    w->fsb.dirlist = XmCreateScrolledList(w->fsb.listform, "dirlist", args, n);
+    Dimension width, height;
+    XtMakeResizeRequest(w->fsb.dirlist, 150, 200, &width, &height);
+    XtManageChild(w->fsb.dirlist);
+    
+    XtAddCallback(
+            w->fsb.dirlist,
+            XmNdefaultActionCallback,
+            (XtCallbackProc)dirlist_activate,
+            w); 
+    XtAddCallback(
+            w->fsb.dirlist,
+            XmNbrowseSelectionCallback,
+            (XtCallbackProc)dirlist_select,
+            w);
+    
+    // FileList
+    XnFileSelectionBoxAddView(
+            (Widget)w,
+            w->fsb.labelListView,
+            CreateListView,
+            FileListUpdate,
+            FileListSelect,
+            FileListCleanup,
+            FileListDestroy,
+            True,
+            w);
+    
+    // Detail FileList
+#ifdef FSB_ENABLE_DETAIL
+    XnFileSelectionBoxAddView(
+            (Widget)w,
+            w->fsb.labelDetailView,
+            CreateDetailView,
+            FileListDetailUpdate,
+            FileListDetailSelect,
+            FileListDetailCleanup,
+            FileListDetailDestroy,
+            True,
+            w);
+#endif
+       
+    /*
+    n = 0;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++;
+    XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
+    //XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
+    //XtSetArg(args[n], XmNbottomWidget, w->fsb.filelist); n++;
+    XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++;
+    w->fsb.lsFileLabel = XmCreateLabel(w->fsb.listform, "label", args, n);
+    XtManageChild(w->fsb.lsFileLabel);
+    */
+    
+    XtManageChild(w->fsb.listform);
+    
+    int selview = w->fsb.selectedview;
+    if(selview < 2) {
+        XtManageChild(w->fsb.view[selview].widget);
+    } else {
+        w->fsb.selectedview = 0;
+    }
+    
+    
+    if(w->fsb.selectedPath) {
+        char *parentPath = ParentPath(w->fsb.selectedPath);
+        filedialog_update_dir(w, parentPath);
+        PathBarSetPath(w->fsb.pathBar, parentPath);
+        free(parentPath);
+        
+        if(w->fsb.type == FILEDIALOG_SAVE) {
+            XmTextFieldSetString(w->fsb.name, FileName(w->fsb.selectedPath));
+        }
+    } else {
+        char cwd[PATH_MAX];
+        const char *currentPath = w->fsb.currentPath;
+        if(!currentPath) {
+            if(getcwd(cwd, PATH_MAX)) {
+                currentPath = cwd;
+            } else {
+                currentPath = GetHomeDir();
+            }
+        }
+        
+        filedialog_update_dir(w, currentPath);
+        PathBarSetPath(w->fsb.pathBar, w->fsb.currentPath);
+    }
+
+
+    w->fsb.selectedview = selview;
+         
+    XtVaSetValues((Widget)w, XmNcancelButton, w->fsb.cancelBtn, NULL);
+    
+    w->fsb.gui_created = 1;
+}
+
+static char* FSBDialogTitle(Widget widget) {
+    XnFileSelectionBox w = (XnFileSelectionBox)widget;
+    if(w->fsb.type == FILEDIALOG_OPEN) {
+        return w->fsb.labelOpenFileTitle;
+    } else {
+        return w->fsb.labelSaveFileTitle;
+    }
+}
+
+static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc create, void *userData, Boolean useDirList) {
+    Arg args[64];
+    int n = 0;
+    
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    if(useDirList) {
+        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
+        XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++;
+        XtSetArg(args[n], XmNleftOffset, w->fsb.widgetSpacing); n++;
+    } else {
+        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+        XtSetArg(args[n], XmNtopOffset, w->fsb.widgetSpacing); n++;
+        XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    }
+    
+    return create(w->fsb.listform, args, n, userData);
+}
+
+
+typedef struct FSBViewSelection {
+    XnFileSelectionBox fsb;
+    int index;
+} FSBViewSelection;
+
+static void SelectView(XnFileSelectionBox f, int view) {
+    FSBView current = f->fsb.view[f->fsb.selectedview];
+    FSBView newview = f->fsb.view[view];
+    
+    XtUnmanageChild(current.widget);
+    if(newview.useDirList != current.useDirList) {
+        if(current.useDirList) {
+            XtUnmanageChild(f->fsb.listform);
+        } else {
+            XtManageChild(f->fsb.listform);
+        }
+    }
+    
+    current.cleanup((Widget)f, current.widget, current.userData);
+    XtManageChild(newview.widget);
+    
+    f->fsb.selectedview = view;
+    
+    filedialog_update_dir(f, NULL);
+    XmProcessTraversal(newview.focus, XmTRAVERSE_CURRENT);
+}
+
+static void SelectViewCallback(Widget w, FSBViewSelection *data, XtPointer u) {
+    SelectView(data->fsb, data->index);
+}
+
+static void SelectViewItemDestroy(Widget w, FSBViewSelection *data, XtPointer u) {
+    free(data);
+}
+
+static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex) {
+    Arg args[4];
+    int n = 0;
+    
+    XmString label = XmStringCreateLocalized((char*)name);
+    
+    XtSetArg(args[n], XmNlabelString, label); n++;
+    XtSetArg(args[1], XmNpositionIndex, w->fsb.selectedview == w->fsb.numviews ? 0 : w->fsb.numviews+1); n++;
+    Widget item = XmCreatePushButton(w->fsb.viewMenu, "menuitem", args, n);
+    
+    if(viewIndex == 0) {
+        w->fsb.viewSelectorList = item;
+    } else if(viewIndex == 1) {
+        w->fsb.viewSelectorDetail = item;
+    }
+   
+    XtManageChild(item);
+    XmStringFree(label);
+    
+    FSBViewSelection *data = malloc(sizeof(FSBViewSelection));
+    data->fsb = w;
+    data->index = viewIndex;
+    
+    XtAddCallback(
+            item,
+            XmNactivateCallback,
+            (XtCallbackProc)SelectViewCallback,
+            data);
+    XtAddCallback(
+            item,
+            XmNdestroyCallback,
+            (XtCallbackProc)SelectViewItemDestroy,
+            data);
+}
+
+static FSBViewWidgets CreateListView(Widget parent, ArgList args, int n, void *userData) {
+    XnFileSelectionBox fsb = (XnFileSelectionBox)userData;
+    
+    XtSetArg(args[n], XmNshadowThickness, 0); n++;
+    Widget frame = XmCreateFrame(parent, "filelistframe", args, n);
+    
+    fsb->fsb.filelist = XmCreateScrolledList(frame, "filelist", NULL, 0);
+    XtManageChild(fsb->fsb.filelist);
+    
+    XtAddCallback(
+            fsb->fsb.filelist,
+            XmNdefaultActionCallback,
+            (XtCallbackProc)FileListActivateCB,
+            userData); 
+    XtAddCallback(
+            fsb->fsb.filelist,
+            XmNbrowseSelectionCallback,
+            (XtCallbackProc)FileListSelectCB,
+            userData);
+    
+    fsb->fsb.listContextMenu = CreateContextMenu(fsb, fsb->fsb.filelist, FileContextMenuCB);
+    
+    FSBViewWidgets widgets;
+    widgets.view = frame;
+    widgets.focus = fsb->fsb.filelist;
+    return widgets;
+}
+
+#ifdef FSB_ENABLE_DETAIL
+static void set_path_from_row(XnFileSelectionBox data, int row) {
+    FileElm *elm = NULL;
+    XmLGridRow rowPtr = XmLGridGetRow(data->fsb.grid, XmCONTENT, row);
+    XtVaGetValues(data->fsb.grid, XmNrowPtr, rowPtr, XmNrowUserData, &elm, NULL);
+    if(!elm) {
+        fprintf(stderr, "error: no row data\n");
+        return;
+    }
+    
+    char *path = strdup(elm->path);
+    
+    data->fsb.selIsDir = False;
+    if(data->fsb.type == FILEDIALOG_SAVE) {
+        XmTextFieldSetString(data->fsb.name, FileName(path));
+    }
+    
+    if(data->fsb.selectedPath) {
+        free(data->fsb.selectedPath);
+    }
+    data->fsb.selectedPath = path;
+}
+
+static void grid_select(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) {
+    set_path_from_row(data, cb->row);
+}
+
+static void grid_activate(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) {
+    set_path_from_row(data, cb->row);
+    data->fsb.end = True;
+    data->fsb.status = FILEDIALOG_OK;
+    
+    FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath);
+}
+static void grid_key_pressed(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) {
+    char chars[16];
+    KeySym keysym;
+    int nchars;
+    
+    nchars = XLookupString(&cb->event->xkey, chars, 15, &keysym, NULL);
+    
+    if(nchars == 0) return;
+
+    // if data->showHidden is 0, data->files contains more items than the grid
+    // this means SelectedRow might not be the correct index for data->files
+    // we have to count files manually and increase 'row', if the file
+    // is actually displayed in the grid
+    int row = 0;
+    int selectedRow = XmLGridGetSelectedRow(w);
+    
+    int match = -1;
+    
+    for(int i=0;i<data->fsb.filecount;i++) {
+        const char *name = FileName(data->fsb.files[i].path);
+        if(!data->fsb.showHidden && name[0] == '.') continue;
+        
+        size_t namelen = strlen(name);
+        
+        size_t cmplen = namelen < nchars ? namelen : nchars;
+        if(!memcmp(name, chars, cmplen)) {
+            if(row <= selectedRow) {
+                if(match == -1) {
+                    match = row;
+                }
+            } else {
+                match = row;
+                break;
+            }
+        }
+        
+        row++;
+    }
+    
+    if(match > -1) {
+        XmLGridSelectRow(w, match, True);
+        XmLGridFocusAndShowRow(w, match+1);
+    } else {
+        XBell(XtDisplay(w), 0);
+    }
+}
+
+static void grid_header_clicked(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { 
+    int new_cmp_field = 0;
+    switch(cb->column) {
+        case 0: {
+            new_cmp_field = 0;            
+            break;
+        }
+        case 1: {
+            new_cmp_field = 1;
+            break;
+        }
+        case 2: {
+            new_cmp_field = 2;
+            break;
+        }
+    }
+    
+    if(new_cmp_field == file_cmp_field) {
+        file_cmp_order = -file_cmp_order; // revert sort order
+    } else {
+        file_cmp_field = new_cmp_field; // change file cmp order to new field
+        file_cmp_order = 1;
+    }
+    
+    int sort_type = file_cmp_order == 1 ? XmSORT_ASCENDING : XmSORT_DESCENDING;
+    XmLGridSetSort(data->fsb.grid, file_cmp_field, sort_type);
+    
+    qsort(data->fsb.files, data->fsb.filecount, sizeof(FileElm), filecmp);
+    
+    // refresh widget
+    filedialog_update_dir(data, NULL);
+} 
+
+static FSBViewWidgets CreateDetailView(Widget parent, ArgList args, int n, void *userData) {
+    XnFileSelectionBox w = userData;
+    
+    XtSetArg(args[n], XmNshadowThickness, 0); n++;
+    Widget gridcontainer = XmCreateFrame(parent, "gridcontainer", args, n);
+    XtManageChild(gridcontainer);
+    
+    n = 0;
+    XtSetArg(args[n], XmNcolumns, 3); n++;
+    XtSetArg(args[n], XmNheadingColumns, 0); n++;
+    XtSetArg(args[n], XmNheadingRows, 1); n++;
+    XtSetArg(args[n], XmNallowColumnResize, 1); n++;
+    XtSetArg(args[n], XmNsimpleHeadings, w->fsb.detailHeadings); n++;
+    XtSetArg(args[n], XmNhorizontalSizePolicy, XmCONSTANT); n++;
+    
+    w->fsb.grid = XmLCreateGrid(gridcontainer, "grid", args, n);
+    XmLGridSetIgnoreModifyVerify(w->fsb.grid, True);
+    XtManageChild(w->fsb.grid);
+    
+    XtVaSetValues(
+            w->fsb.grid,
+            XmNcellDefaults, True,
+            XtVaTypedArg, XmNblankBackground, XmRString, "white", 6,
+            XtVaTypedArg, XmNcellBackground, XmRString, "white", 6,
+            NULL);
+    
+    XtAddCallback(w->fsb.grid, XmNselectCallback, (XtCallbackProc)grid_select, w);
+    XtAddCallback(w->fsb.grid, XmNactivateCallback, (XtCallbackProc)grid_activate, w);
+    XtAddCallback(w->fsb.grid, XmNheaderClickCallback, (XtCallbackProc)grid_header_clicked, w);
+    XtAddCallback(w->fsb.grid, XmNgridKeyPressedCallback, (XtCallbackProc)grid_key_pressed, w);
+    
+    // context menu
+    w->fsb.gridContextMenu = CreateContextMenu(w, w->fsb.grid, FileContextMenuCB);
+    
+    FSBViewWidgets widgets;
+    widgets.view = gridcontainer;
+    widgets.focus = w->fsb.grid;
+    return widgets;
+}
+#endif
+
+
+/* ------------------------------ Path Utils  ------------------------------ */
+
+const char* GetHomeDir(void) {
+    char *home = getenv("HOME");
+    if(!home) {
+        home = getenv("USERPROFILE");
+        if(!home) {
+            home = "/";
+        }
+    }
+    return home;
+}
+
+static char* ConcatPath(const char *parent, const char *name)
+{ 
+    size_t parentlen = strlen(parent);
+    size_t namelen = strlen(name);
+    
+    size_t pathlen = parentlen + namelen + 2;
+    char *path = malloc(pathlen);
+    
+    memcpy(path, parent, parentlen);
+    if(parentlen > 0 && parent[parentlen-1] != '/') {
+        path[parentlen] = '/';
+        parentlen++;
+    }
+    if(name[0] == '/') {
+        name++;
+        namelen--;
+    }
+    memcpy(path+parentlen, name, namelen);
+    path[parentlen+namelen] = '\0';
+    return path;
+}
+
+static char* FileName(char *path) {
+    int si = 0;
+    int osi = 0;
+    int i = 0;
+    int p = 0;
+    char c;
+    while((c = path[i]) != 0) {
+        if(c == '/') {
+            osi = si;
+            si = i;
+            p = 1;
+        }
+        i++;
+    }
+    
+    char *name = path + si + p;
+    if(name[0] == 0) {
+        name = path + osi + p;
+        if(name[0] == 0) {
+            return path;
+        }
+    }
+    
+    return name;
+}
+
+static char* ParentPath(const char *path) {
+    char *name = FileName((char*)path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    if(parentlen == 0) {
+        parentlen++;
+    }
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+// unused at the moment, maybe reactivate if more illegal characters
+// are defined
+/*
+static int CheckFileName(const char *fileName) {
+    size_t len = strlen(fileName);
+    for(int i=0;i<len;i++) {
+        if(fileName[i] == '/') {
+            return 0;
+        }
+    }
+    return 1;
+}
+*/
+
+/* ------------------------------ PathBar  ------------------------------ */
+
+static void pathbar_resize(Widget w, PathBar *p, XtPointer d)
+{
+    if(p->disableResize) return;
+    
+    Dimension width, height;
+    XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL);
+    
+    Dimension xoff;
+    XtVaGetValues(p->down, XmNwidth, &xoff, NULL);
+    
+    Dimension *segW = calloc(p->numSegments, sizeof(Dimension));
+    
+    Dimension maxHeight = 0;
+    
+    /* get width/height from all widgets */
+    Dimension pathWidth = 0;
+    for(int i=0;i<p->numSegments;i++) {
+        Dimension segWidth;
+        Dimension segHeight;
+        XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL);
+        segW[i] = segWidth;
+        pathWidth += segWidth;
+        if(segHeight > maxHeight) {
+            maxHeight = segHeight;
+        }
+    }
+    Dimension tfHeight;
+    XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL);
+    if(tfHeight > maxHeight) {
+        maxHeight = tfHeight;
+    }
+    
+    Boolean arrows = False;
+    if(pathWidth + xoff + 10 > width) {
+        arrows = True;
+        //pathWidth += p->lw + p->rw;
+    }
+    
+    /* calc max visible widgets */
+    int start = 0;
+    if(arrows) {
+        Dimension vis = p->lw+p->rw;
+        for(int i=p->numSegments;i>0;i--) {
+            Dimension segWidth = segW[i-1];
+            if(vis + segWidth + xoff + 10 > width) {
+                start = i;
+                arrows = True;
+                break;
+            }
+            vis += segWidth;
+        }
+    } else {
+        p->shift = 0;
+    }
+    
+    int leftShift = 0;
+    if(p->shift < 0) {
+        if(start + p->shift < 0) {
+            leftShift = start;
+            start = 0;
+            p->shift = -leftShift;
+        } else {
+            leftShift = -p->shift; /* negative shift */
+            start += p->shift;
+        }
+    }
+    
+    int x = 0;
+    if(arrows) {
+        XtManageChild(p->left);
+        XtManageChild(p->right);
+        x += p->lw;
+    } else {
+        XtUnmanageChild(p->left);
+        XtUnmanageChild(p->right);
+    }
+    
+    for(int i=0;i<p->numSegments;i++) {
+        if(i >= start && i < p->numSegments - leftShift && !p->input) {
+            XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL);
+            x += segW[i];
+            XtManageChild(p->pathSegments[i]);
+        } else {
+            XtUnmanageChild(p->pathSegments[i]);
+        }
+    }
+    
+    if(arrows) {
+        XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL);
+        XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL);
+    }
+    XtVaSetValues(p->down, XmNx, width-xoff, XmNheight, maxHeight, NULL);
+    free(segW);
+    
+    Dimension rw, rh;
+    XtMakeResizeRequest(w, width, maxHeight, &rw, &rh);
+    XtVaSetValues(p->textfield, XmNwidth, rw-xoff, XmNheight, rh, NULL);
+}
+
+static void pathbar_input(Widget w, PathBar *p, XtPointer c)
+{
+    XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
+    XEvent *xevent = cbs->event;
+    
+    if (cbs->reason == XmCR_INPUT) {
+        if (xevent->xany.type == ButtonPress) {
+            XtUnmanageChild(p->left);
+            XtUnmanageChild(p->right);
+            
+            XtManageChild(p->textfield);
+            p->input = 1;
+            
+            XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT);
+            
+            pathbar_resize(p->widget, p, NULL);
+        }
+    }
+}
+
+static void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c)
+{
+    p->input = False;
+    XtUnmanageChild(p->textfield);
+    pathbar_resize(p->widget, p, NULL);
+}
+
+static void pathbar_pathinput(Widget w, PathBar *pb, XtPointer d)
+{
+    char *newpath = XmTextFieldGetString(pb->textfield);
+    if(newpath) {
+        if(newpath[0] == '~') {
+            char *p = newpath+1;
+            char *cp = ConcatPath(GetHomeDir(), p);
+            XtFree(newpath);
+            newpath = cp;
+        } else if(newpath[0] != '/') {
+            char cwd[PATH_MAX];
+            if(!getcwd(cwd, sizeof(cwd))) {
+                cwd[0] = '/';
+                cwd[1] = '\0';
+            }
+            char *cp = ConcatPath(cwd, newpath);
+            XtFree(newpath);
+            newpath = cp;
+        }
+        
+        /* update path */
+        if(pb->updateDir) {
+            if(!pb->updateDir(pb->updateDirData, newpath)) {
+                PathBarSetPath(pb, newpath);
+            }
+        } else {
+            PathBarSetPath(pb, newpath);
+        }
+        XtFree(newpath);
+        
+        /* hide textfield and show path as buttons */
+        XtUnmanageChild(pb->textfield);
+        pathbar_resize(pb->widget, pb, NULL);
+    }
+}
+
+static void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) {
+    p->shift--;
+    pathbar_resize(p->widget, p, NULL);
+}
+
+static void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) {
+    if(p->shift < 0) {
+        p->shift++;
+    }
+    pathbar_resize(p->widget, p, NULL);
+}
+
+static void pathbar_list_select(Widget w, PathBar *p, XmListCallbackStruct *cb) {
+    char *value = NULL;
+    XmStringGetLtoR(cb->item, XmSTRING_DEFAULT_CHARSET, &value);
+    p->updateDir(p->updateDirData, value);
+    PathBarSetPath(p, value);
+    free(value);
+}
+
+static void pathbar_popup(Widget w, PathBar *p, XtPointer d) {
+    Widget parent = XtParent(w);
+    Display *dp = XtDisplay(parent);
+    Window root = XDefaultRootWindow(dp);
+    
+    int x, y;
+    Window child;
+    XTranslateCoordinates(dp, XtWindow(parent), root, 0, 0, &x, &y, &child);
+
+    XtManageChild(p->list);
+    XtPopupSpringLoaded(p->popup);
+    XtVaSetValues(p->popup, XmNx, x, XmNy, y + parent->core.height, XmNwidth, parent->core.width, XmNheight, 200, NULL);
+    
+    XmProcessTraversal(p->list, XmTRAVERSE_CURRENT);
+}
+
+static void popupEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
+    PathBar *bar = data;
+    
+    Window w1 = bar->hs ? XtWindow(bar->hs) : 0;
+    Window w2 = bar->vs ? XtWindow(bar->vs) : 0;
+    
+    if(event->type == ButtonPress) {
+        if(event->xbutton.window != 0 && (event->xbutton.window == w1 || event->xbutton.window == w2)) {
+            bar->popupScrollEvent = 1;
+        } else {
+            bar->popupScrollEvent = 0;
+        }
+    } else if(event->type == ButtonRelease) {
+        if(bar->popupScrollEvent) {
+            *dispatch = False;
+        }
+        bar->popupScrollEvent = 0;
+    } else if(event->type == KeyReleaseMask) {
+        int keycode = event->xkey.keycode;
+        if(keycode == 36 || keycode == 9) {
+            XtUnmapWidget(bar->popup);
+        }
+    }
+}
+
+static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
+    PathBar *pb = data;
+    if(event->type == KeyReleaseMask) {
+        if(event->xkey.keycode == 9) {
+            XtUnmanageChild(pb->textfield);
+            pathbar_resize(pb->widget, pb, NULL);
+            *dispatch = False;
+        }
+    }
+}
+
+
+PathBar* CreatePathBar(Widget parent, ArgList args, int n)
+{
+    PathBar *bar = malloc(sizeof(PathBar));
+    bar->path = NULL;
+    bar->updateDir = NULL;
+    bar->updateDirData = NULL;
+    bar->disableResize = False;
+    
+    bar->shift = 0;
+    
+    XtSetArg(args[n], XmNmarginWidth, 0); n++;
+    XtSetArg(args[n], XmNmarginHeight, 0); n++;
+    bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n);
+    XtAddCallback(
+            bar->widget,
+            XmNresizeCallback,
+            (XtCallbackProc)pathbar_resize,
+            bar);
+    XtAddCallback(
+            bar->widget,
+            XmNinputCallback,
+            (XtCallbackProc)pathbar_input,
+            bar);
+    
+    n = 0;
+    XtSetArg(args[n], XmNownerEvents, True), n++;
+    XtSetArg(args[n], XmNgrabStyle, GrabModeSync), n++;
+    bar->popup = XmCreateGrabShell(bar->widget, "pbpopup", args, n);
+    bar->list = XmCreateScrolledList(bar->popup, "pblist", NULL, 0);
+    XtAddEventHandler(bar->popup, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask, FALSE, popupEH, bar);
+    bar->popupScrollEvent = 0;
+    
+    XtAddCallback(
+            bar->list,
+            XmNdefaultActionCallback,
+            (XtCallbackProc)pathbar_list_select,
+            bar); 
+    XtAddCallback(
+            bar->list,
+            XmNbrowseSelectionCallback,
+            (XtCallbackProc)pathbar_list_select,
+            bar);
+    
+    bar->vs = NULL;
+    bar->hs = NULL;
+    XtVaGetValues(XtParent(bar->list), XmNhorizontalScrollBar, &bar->hs, XmNverticalScrollBar, &bar->vs, NULL);
+    
+    Arg a[4];
+    XtSetArg(a[0], XmNshadowThickness, 0);
+    XtSetArg(a[1], XmNx, 0);
+    XtSetArg(a[2], XmNy, 0);
+    bar->textfield = XmCreateTextField(bar->widget, "pbtext", a, 3);
+    bar->input = 0;
+    XtAddCallback(
+            bar->textfield,
+            XmNlosingFocusCallback,
+            (XtCallbackProc)pathbar_losingfocus,
+            bar);
+    XtAddCallback(bar->textfield, XmNactivateCallback,
+                 (XtCallbackProc)pathbar_pathinput, bar);
+    XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar);
+    
+    XtSetArg(a[0], XmNarrowDirection, XmARROW_DOWN);
+    bar->down = XmCreateArrowButton(bar->widget, "pbbutton", a, 1);
+    XtManageChild(bar->down);
+    XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT);
+    bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1);
+    XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT);
+    bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1);
+    XtAddCallback(
+                bar->down,
+                XmNactivateCallback,
+                (XtCallbackProc)pathbar_popup,
+                bar);
+    XtAddCallback(
+                bar->left,
+                XmNactivateCallback,
+                (XtCallbackProc)pathbar_shift_left,
+                bar);
+    XtAddCallback(
+                bar->right,
+                XmNactivateCallback,
+                (XtCallbackProc)pathbar_shift_right,
+                bar);
+    
+    Pixel bg;
+    XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL);
+    XtVaSetValues(bar->widget, XmNbackground, bg, NULL);
+    
+    //XtManageChild(bar->left);
+    //XtManageChild(bar->right);
+    
+    XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL);
+    XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL);
+    
+    bar->segmentAlloc = 16;
+    bar->numSegments = 0;
+    bar->pathSegments = calloc(16, sizeof(Widget));
+    
+    bar->selection = 0;
+    
+    return bar;
+}
+
+static void PathBarChangeDir(Widget w, PathBar *bar, XtPointer unused) {
+    XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False);
+    
+    for(int i=0;i<bar->numSegments;i++) {  
+        if(bar->pathSegments[i] == w) {
+            bar->selection = i;
+            XmToggleButtonSetState(w, True, False);
+            break;
+        }
+    }
+    
+    int plen = strlen(bar->path);
+    int countSeg = 0;
+    for(int i=0;i<=plen;i++) {
+        char c = bar->path[i];
+        if(c == '/' || c == '\0') {
+            if(countSeg == bar->selection) {
+                char *dir = malloc(i+2);
+                memcpy(dir, bar->path, i+1);
+                dir[i+1] = '\0';
+                if(bar->updateDir) {
+                    bar->updateDir(bar->updateDirData, dir);
+                }
+                free(dir);
+            }
+            countSeg++;
+        }
+    }
+}
+
+void PathBarSetPath(PathBar *bar, const char *path) {
+    if(bar->path) {
+        free(bar->path);
+    }
+    bar->path = strdup(path);
+     
+    for(int i=0;i<bar->numSegments;i++) {
+        XtDestroyWidget(bar->pathSegments[i]);
+    }
+    XtUnmanageChild(bar->textfield);
+    //XtManageChild(bar->left);
+    //XtManageChild(bar->right);
+    bar->input = False;
+    
+    Arg args[4];
+    XmString str;
+    
+    bar->numSegments = 0;
+    
+    int i=0;
+    if(path[0] == '/') {
+        str = XmStringCreateLocalized("/");
+        XtSetArg(args[0], XmNlabelString, str);
+        XtSetArg(args[1], XmNfillOnSelect, True);
+        XtSetArg(args[2], XmNindicatorOn, False);
+        bar->pathSegments[0] = XmCreateToggleButton(
+                bar->widget, "pbbutton", args, 3);
+        XtAddCallback(
+                bar->pathSegments[0],
+                XmNvalueChangedCallback,
+                (XtCallbackProc)PathBarChangeDir,
+                bar);
+        XmStringFree(str);
+        bar->numSegments++;
+        i++;
+    }
+    
+    int len = strlen(path);
+    int begin = i;
+    for(;i<=len;i++) {
+        char c = path[i];
+        if((c == '/' || c == '\0') && i > begin) {
+            char *segStr = malloc(i - begin + 1);
+            memcpy(segStr, path+begin, i-begin);
+            segStr[i-begin] = '\0';
+            begin = i+1;
+            
+            str = XmStringCreateLocalized(segStr);
+            free(segStr);
+            XtSetArg(args[0], XmNlabelString, str);
+            XtSetArg(args[1], XmNfillOnSelect, True);
+            XtSetArg(args[2], XmNindicatorOn, False);
+            Widget button = XmCreateToggleButton(bar->widget, "pbbutton", args, 3);
+            XtAddCallback(
+                    button,
+                    XmNvalueChangedCallback,
+                    (XtCallbackProc)PathBarChangeDir,
+                    bar);
+            XmStringFree(str);
+            
+            if(bar->numSegments >= bar->segmentAlloc) {
+                bar->segmentAlloc += 8;
+                bar->pathSegments = realloc(bar->pathSegments, bar->segmentAlloc * sizeof(Widget));
+            }
+            
+            bar->pathSegments[bar->numSegments++] = button;
+        }
+    }
+    
+    bar->selection = bar->numSegments-1;
+    XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False);
+    
+    XmTextFieldSetString(bar->textfield, (char*)path);
+    XmTextFieldSetInsertionPosition(bar->textfield, XmTextFieldGetLastPosition(bar->textfield));
+    
+    pathbar_resize(bar->widget, bar, NULL);
+}
+
+void PathBarSetDirList(PathBar *bar, const char **dirlist, size_t nelm) {
+    XmStringTable items = calloc(nelm, sizeof(XmString));
+    for(int i=0;i<nelm;i++) {
+        items[i] = XmStringCreateLocalized((char*)dirlist[i]);
+    }
+    XmListDeleteAllItems(bar->list);
+    XmListAddItems(bar->list, items, nelm, 0);
+    XmListSelectPos(bar->list, 1, False);
+    for(int i=0;i<nelm;i++) {
+        XmStringFree(items[i]);
+    }
+    free(items);
+}
+
+void PathBarDestroy(PathBar *bar) {
+    if(bar->path) {
+        free(bar->path);
+    }
+    free(bar->pathSegments);
+    free(bar);
+}
+
+/* ------------------------------ public API ------------------------------ */
+
+Widget XnCreateFileSelectionDialog(
+        Widget parent,
+        String name,
+        ArgList arglist,
+        Cardinal argcount)
+{
+    Widget dialog = XmCreateDialogShell(parent, "FileDialog", NULL, 0);
+    Widget fsb = XnCreateFileSelectionBox(dialog, name, arglist, argcount);
+    char *title = FSBDialogTitle(fsb);
+    XtVaSetValues(dialog, XmNtitle, title, NULL);
+    return fsb;
+}
+
+Widget XnCreateFileSelectionBox(
+        Widget parent,
+        String name,
+        ArgList arglist,
+        Cardinal argcount)
+{
+    Widget fsb = XtCreateWidget(name, xnFsbWidgetClass, parent, arglist, argcount);
+    return fsb;
+}
+
+void XnFileSelectionBoxAddView(
+        Widget fsb,
+        const char *name,
+        FSBViewCreateProc create,
+        FSBViewUpdateProc update,
+        FSBViewSelectProc select,
+        FSBViewCleanupProc cleanup,
+        FSBViewDestroyProc destroy,
+        Boolean useDirList,
+        void *userData)
+{
+    XnFileSelectionBox f = (XnFileSelectionBox)fsb;
+    if(f->fsb.numviews >= FSB_MAX_VIEWS) {
+        fprintf(stderr, "XnFileSelectionBox: too many views\n");
+        return;
+    }
+    
+    FSBView view;
+    view.update = update;
+    view.select = select;
+    view.cleanup = cleanup;
+    view.destroy = destroy;
+    view.useDirList = useDirList;
+    view.userData = userData;
+    
+    FSBViewWidgets widgets = CreateView(f, create, userData, useDirList);
+    view.widget = widgets.view;
+    view.focus = widgets.focus;
+    
+    AddViewMenuItem(f, name, f->fsb.numviews);
+    
+    f->fsb.view[f->fsb.numviews++] = view;
+}
+
+void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm) {
+    XnFileSelectionBox f = (XnFileSelectionBox)fsb;
+    PathBarSetDirList(f->fsb.pathBar, dirlist, nelm);
+}
+
+Widget XnFileSelectionBoxWorkArea(Widget fsb) {
+    XnFileSelectionBox f = (XnFileSelectionBox)fsb;
+    return f->fsb.workarea;
+}
+
+Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child) {
+    XnFileSelectionBox w = (XnFileSelectionBox)fsb;
+    switch(child) {
+        case XnFSB_DIR_UP_BUTTON: return w->fsb.dirUp;
+        case XnFSB_HOME_BUTTON: return w->fsb.home;
+        case XnFSB_NEW_FOLDER_BUTTON: return w->fsb.newFolder;
+        case XnFSB_DETAIL_TOGGLE_BUTTON: return w->fsb.detailToggleButton;
+        case XnFSB_VIEW_OPTION_BUTTON: return w->fsb.viewOption;
+        case XnFSB_FILTER_DROPDOWN: return w->fsb.filter;
+        case XnFSB_FILTER_BUTTON: return w->fsb.filterButton;
+        case XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON: return w->fsb.showHiddenButtonW;
+        case XnFSB_DIRECTORIES_LABEL: return w->fsb.lsDirLabel;
+        case XnFSB_FILES_LABEL: return w->fsb.lsFileLabel;
+        case XnFSB_DIRLIST: return w->fsb.dirlist;
+        case XnFSB_FILELIST: return w->fsb.filelist;
+        case XnFSB_GRID: return w->fsb.grid;
+        case XnFSB_OK_BUTTON: return w->fsb.okBtn;
+        case XnFSB_CANCEL_BUTTON: return w->fsb.cancelBtn;
+        case XnFSB_HELP_BUTTON: return w->fsb.helpBtn;
+    }
+    return NULL;
+}
+
+void XnFileSelectionBoxDeleteFilters(Widget fsb) {
+    XnFileSelectionBox w = (XnFileSelectionBox)fsb;
+    Widget filterList = XmDropDownGetList(w->fsb.filter);
+    XmListDeleteAllItems(filterList);
+}
+
+void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter) {
+    XnFileSelectionBox w = (XnFileSelectionBox)fsb;
+    Widget filterList = XmDropDownGetList(w->fsb.filter);
+    
+    XmString str = XmStringCreateSimple((char*)filter);
+    XmListAddItem(filterList, str, 0);
+    XmStringFree(str);
+}
diff --git a/application/Fsb.h b/application/Fsb.h
new file mode 100644 (file)
index 0000000..b79891b
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2021 Olaf Wintermann
+ *
+ * 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.
+ */
+
+#ifndef FSB_H
+#define FSB_H
+
+#include <X11/Intrinsic.h>
+#include <Xm/PrimitiveP.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern WidgetClass xnFsbWidgetClass;
+
+#define FILEDIALOG_OPEN 1
+#define FILEDIALOG_SAVE 2
+    
+#define FILEDIALOG_OK 1
+#define FILEDIALOG_CANCEL 2
+
+#define XnNwidgetSpacing         "fsbWidgetSpacing"
+#define XnNwindowSpacing         "fsbWindowSpacing"
+
+#define XnNfsbType               "fsbType"
+#define XnCfsbType               "fsbType"
+
+#define XnNshowHidden            "showHidden"
+#define XnCshowHidden            "showHidden"
+#define XnNshowHiddenButton      "showHiddenButton"
+#define XnCshowHiddenButton      "showHiddenButton"
+
+#define XnNshowViewMenu          "showViewMenu"
+#define XnCshowViewMenu          "showViewMenu"
+
+#define XnNselectedView          "fsbSelectedView"
+#define XnCselectedView          "fsbSelectedView"
+
+#define XnNdirectory             "directory"
+#define XnCdirectory             "directory"
+#define XnNselectedPath          "selectedPath"
+#define XnCselectedPath          "selectedPath"
+#define XnNhomePath              "homePath"
+#define XnChomePath              "homePath"
+
+#define XnNfilter                "filter"
+#define XnCfilter                "filter"
+
+#define XnNfilterFunc            "filterFunc"
+#define XnCfilterFunc            "filterFunc"
+
+#define XnNlabelListView         "labelListView"
+#define XnClabelListView         "labelListView"
+#define XnNlabelDetailView       "labelDetailView"
+#define XnClabelDetailView       "labelDetailView"
+#define XnNlabelOpenFileTitle    "labelOpenFileTitle"
+#define XnClabelOpenFileTitle    "labelOpenFileTitle"
+#define XnNlabelSaveFileTitle    "labelSaveFileTitle"
+#define XnClabelSaveFileTitle    "labelSaveFileTitel"
+#define XnNlabelDirUp            "labelDirUp"
+#define XnClabelDirUp            "labelDirUp"
+#define XnNlabelHome             "labelHome"
+#define XnClabelHome             "labelHome"
+#define XnNlabelNewFolder        "labelNewFolder"
+#define XnClabelNewFolder        "labelNewFolder"
+#define XnNlabelFilter           "labelFilter"
+#define XnClabelFilter           "labelFilter"
+#define XnNlabelFilterButton     "labelFilterButton"
+#define XnClabelFilterButton     "labelFilterButton"
+#define XnNlabelShowHiddenFiles  "labelShowHiddenFiles"
+#define XnClabelShowHiddenFiles  "labelShowHiddenFiles"
+#define XnNlabelDirectories      "labelDirectories"
+#define XnClabelDirectories      "labelDirectories"
+#define XnNlabelFiles            "labelFiles"
+#define XnClabelFiles            "labelFiles"
+#define XnNlabelRename           "labelRename"
+#define XnClabelRename           "labelRename"
+#define XnNlabelDelete           "labelDelete"
+#define XnClabelDelete           "labelDelete"
+#define XnNlabelOpen             "labelOpen"
+#define XnClabelOpen             "labelOpen"
+#define XnNlabelSave             "labelSave"
+#define XnClabelSave             "labelSave"
+#define XnNlabelOk               "labelOk"
+#define XnClabelOk               "labelOk"
+#define XnNlabelCancel           "labelCancel"
+#define XnClabelCancel           "labelCancel"
+#define XnNlabelHelp             "labelHelp"
+#define XnClabelHelp             "labelHelp"
+#define XnNlabelFileName         "labelFileName"
+#define XnClabelFileName         "labelFileName"
+#define XnNlabelDirectoryName    "labelDirectoryName"
+#define XnClabelDirectoryName    "labelDirectoryName"
+#define XnNlabelNewFileName      "labelNewFileName"
+#define XnClabelNewFileName      "labelNewFileName"
+#define XnNlabelDeleteFile       "labelDeleteFile"
+#define XnClabelDeleteFile       "labelDeleteFile"
+#define XnNdetailHeadings        "detailHeadings"
+#define XnCdetailHeadings        "detailHeadings"
+#define XnNdateFormatSameYear    "dateFormatSameYear"
+#define XnCdateFormatSameYear    "dateFormatSameYear"
+#define XnNdateFormatOtherYear   "dateFormatOtherYear"
+#define XnCdateFormatOtherYear   "dateFormatOtherYear"
+#define XnNsuffixBytes           "suffixBytes"
+#define XnCsuffixBytes           "suffixBytes"
+#define XnNsuffixKB              "suffixKB"
+#define XnCsuffixKB              "suffixKB"
+#define XnNsuffixMB              "suffixMB"
+#define XnCsuffixMB              "suffixMB"
+#define XnNsuffixGB              "suffixGB"
+#define XnCsuffixGB              "suffixGB"
+#define XnNsuffixTB              "suffixTB"
+#define XnCsuffixTB              "suffixTB"
+#define XnNerrorTitle            "errorTitle"
+#define XnCerrorTitle            "errorTitle"
+#define XnNerrorIllegalChar      "errorIllegalChar"
+#define XnCerrorIllegalChar      "errorIllegalChar"
+#define XnNerrorRename           "errorRename"
+#define XnCerrorRename           "errorRename"
+#define XnNerrorCreateFolder     "errorCreateFolder"
+#define XnCerrorCreateFolder     "errorCreateFolder"
+#define XnNerrorDelete           "errorDelete"
+#define XnCerrorDelete           "errorDelete"
+#define XnNerrorOpenDir          "errorOpenDir"
+#define XnCerrorOpenDir          "errorOpenDir"
+
+/*
+ * int FSBFilterFunc(const char *pattern, const char *string)
+ * 
+ * Checks checks whether the string matches the pattern
+ * 
+ * Return
+ *   zero if the string matches the pattern
+ *   non-zero if there is no match
+ */
+typedef int(*FSBFilterFunc)(const char*, const char*);
+
+typedef int(*updatedir_callback)(void*,char*);
+
+typedef struct PathBar {  
+    Widget widget;
+    Widget textfield;
+    
+    Widget down;
+    Widget left;
+    Widget right;
+    Dimension lw;
+    Dimension rw;
+    
+    Widget popup;
+    Widget list;
+    
+    int shift;
+    
+    Widget *pathSegments;
+    size_t numSegments;
+    size_t segmentAlloc;
+    
+    char *path;
+    int selection;
+    Boolean input;
+    
+    Widget hs;
+    Widget vs;
+    int popupScrollEvent;
+    
+    updatedir_callback updateDir;
+    void *updateDirData;
+    
+    Boolean disableResize;
+} PathBar;
+
+typedef struct FileElm FileElm;
+struct FileElm {
+    char *path;
+    int isDirectory;
+    unsigned long long size;
+    time_t lastModified;
+};
+
+typedef struct {
+    Widget view;
+    Widget focus;
+} FSBViewWidgets;
+
+enum XnFSBChild {
+    XnFSB_DIR_UP_BUTTON = 0,
+    XnFSB_HOME_BUTTON,
+    XnFSB_NEW_FOLDER_BUTTON,
+    XnFSB_DETAIL_TOGGLE_BUTTON,
+    XnFSB_VIEW_OPTION_BUTTON,
+    XnFSB_FILTER_DROPDOWN,
+    XnFSB_FILTER_BUTTON,
+    XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON,
+    XnFSB_DIRECTORIES_LABEL,
+    XnFSB_FILES_LABEL,
+    XnFSB_DIRLIST,
+    XnFSB_FILELIST,
+    XnFSB_GRID,
+    XnFSB_OK_BUTTON,
+    XnFSB_CANCEL_BUTTON,
+    XnFSB_HELP_BUTTON
+};
+
+typedef FSBViewWidgets(*FSBViewCreateProc)(Widget parent, ArgList args, int n, void *userData);
+typedef void(*FSBViewUpdateProc)(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData);
+typedef void(*FSBViewSelectProc)(Widget fsb, Widget view, const char *item);
+typedef void(*FSBViewCleanupProc)(Widget fsb, Widget view, void *userData);
+typedef void(*FSBViewDestroyProc)(Widget fsb, Widget view, void *userData);
+
+PathBar* CreatePathBar(Widget parent, ArgList args, int n);
+void PathBarSetPath(PathBar *bar, const char *path);
+void PathBarSetDirList(PathBar *bar, const char **dirlist, size_t nelm);
+void PathBarDestroy(PathBar *bar);
+
+Widget XnCreateFileSelectionDialog(
+        Widget parent,
+        String name,
+        ArgList arglist,
+        Cardinal argcount);
+
+Widget XnCreateFileSelectionBox(
+        Widget parent,
+        String name,
+        ArgList arglist,
+        Cardinal argcount);
+
+void XnFileSelectionBoxAddView(
+        Widget fsb,
+        const char *name,
+        FSBViewCreateProc create,
+        FSBViewUpdateProc update,
+        FSBViewSelectProc select,
+        FSBViewCleanupProc cleanup,
+        FSBViewDestroyProc destroy,
+        Boolean useDirList,
+        void *userData);
+
+void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm);
+
+Widget XnFileSelectionBoxWorkArea(Widget fsb);
+
+Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child);
+
+void XnFileSelectionBoxDeleteFilters(Widget fsb);
+
+void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FSB_H */
diff --git a/application/FsbP.h b/application/FsbP.h
new file mode 100644 (file)
index 0000000..71c70b7
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2021 Olaf Wintermann
+ *
+ * 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.
+ */
+
+#ifndef FSBP_H
+#define FSBP_H
+
+#include <X11/CoreP.h>
+#include <Xm/XmP.h>
+#include <Xm/PrimitiveP.h>
+#include <Xm/ManagerP.h>
+#include <Xm/FormP.h>
+
+#include "Fsb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FSB_MAX_VIEWS 8
+    
+
+typedef struct FSBView FSBView;
+struct FSBView {
+    Widget widget;
+    Widget focus;
+    FSBViewUpdateProc  update;
+    FSBViewSelectProc  select;
+    FSBViewCleanupProc cleanup;
+    FSBViewDestroyProc destroy;
+    void *userData;
+    Boolean useDirList;
+};
+
+    
+typedef struct FSBClassPart {
+    int unused;
+} FSBClassPart;
+
+typedef struct FSBClassRec {
+    CoreClassPart             core_class;
+    CompositeClassPart        composite_class;
+    ConstraintClassPart       constraint_class;
+    XmManagerClassPart        manager_class;
+    XmBulletinBoardClassPart  bulletin_board_class;
+    XmFormClassPart           form_class;
+    FSBClassPart              fsb_class;
+} FSBClassRec;
+
+typedef struct FSBPart {
+    XtCallbackList okCallback;
+    XtCallbackList cancelCallback;
+    
+    Dimension widgetSpacing;
+    Dimension windowSpacing;
+    
+    Boolean showHiddenButton;
+    
+    Widget path;
+    PathBar *pathBar;
+    Widget filter;
+    Widget filterButton;
+    Widget showHiddenButtonW;
+    
+    FSBFilterFunc filterFunc;
+    
+    char *filterStr;
+    
+    Widget dirUp;
+    Widget home;
+    Widget newFolder;
+    
+    Widget viewSelectorList;
+    Widget viewSelectorDetail;
+    
+    Widget viewMenu;
+    Widget viewOption;
+    Widget detailToggleButton;
+    
+    Widget filterForm;
+    Widget lsDirLabel;
+    Widget lsFileLabel;
+    
+    Widget listContextMenu;
+    Widget gridContextMenu;
+    
+    // icon view
+    
+    // dir/file list view
+    Widget listform;
+    Widget dirlist;
+    
+    FSBView view[FSB_MAX_VIEWS];
+    int numviews;
+    int selectedview;
+    
+    Widget filelist;
+    Widget grid;
+    
+    Widget separator;
+    
+    Widget nameLabel;
+    Widget name;
+    
+    Widget bottom_widget;
+    
+    Widget workarea;
+    
+    Widget okBtn;
+    Widget cancelBtn;
+    Widget helpBtn;
+    
+    FileElm *dirs;
+    FileElm *files;
+    int dircount;
+    int filecount;
+    int maxnamelen;
+    
+    char *homePath;
+    
+    char *currentPath;
+    char *selectedPath;
+    int selIsDir;
+    Boolean showHidden;
+    Boolean showViewMenu;
+      
+    int type;
+    
+    int end;
+    int status;
+    
+    int disable_set_values;
+    int gui_created;
+        
+    char *labelListView;
+    char *labelDetailView;
+    char* labelOpenFileTitle;
+    char* labelSaveFileTitle;
+    XmString labelDirUp;
+    XmString labelHome;
+    XmString labelNewFolder;
+    XmString labelFilterButton;
+    XmString labelShowHiddenFiles;
+    XmString labelDirectories;
+    XmString labelFiles;
+    XmString labelRename;
+    XmString labelDelete;
+    XmString labelOpen;
+    XmString labelSave;
+    XmString labelOk;
+    XmString labelCancel;
+    XmString labelHelp;
+    XmString labelFileName;
+    XmString labelDirectoryName;
+    XmString labelNewFileName;
+    char *labelDeleteFile;
+    
+    char *detailHeadings;
+    
+    char *dateFormatSameYear;
+    char *dateFormatOtherYear;
+    char *suffixBytes;
+    char *suffixKB;
+    char *suffixMB;
+    char *suffixGB;
+    char *suffixTB;
+    
+    char *errorTitle;
+    char *errorIllegalChar;
+    char *errorRename;
+    char *errorFolder;
+    char *errorDelete;
+    char *errorOpenDir;
+} FSBPart;
+
+typedef struct FSBRec {
+   CorePart            core;
+   CompositePart        composite;
+   ConstraintPart       constraint;
+   XmManagerPart        manager;
+   XmBulletinBoardPart  bulletin_board;
+   XmFormPart           form;
+   FSBPart              fsb;
+} FSBRec;
+
+typedef struct FSBRec *XnFileSelectionBox;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FSBP_H */
+
diff --git a/application/Makefile b/application/Makefile
new file mode 100644 (file)
index 0000000..ea6cb48
--- /dev/null
@@ -0,0 +1,50 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2022 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ..
+include $(BUILD_ROOT)/config.mk
+
+CFLAGS += -I../ucx -I..
+
+SRC = main.c
+SRC += window.c
+SRC += player.c
+SRC += Fsb.c
+
+OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/application/%.$(OBJ_EXT))
+
+BINTARGET = mediaplayer
+
+all: $(BUILD_ROOT)/build/bin/$(BINTARGET)
+
+$(BUILD_ROOT)/build/bin/$(BINTARGET): $(OBJ)
+       $(LD) -o $(BUILD_ROOT)/build/bin/$(BINTARGET) $(OBJ) -L$(BUILD_ROOT)/build/lib -lucx $(LDFLAGS) $(APP_LDFLAGS)
+
+$(BUILD_ROOT)/build/application/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) $(APP_CFLAGS) -o $@ -c $<
+
diff --git a/application/main.c b/application/main.c
new file mode 100644 (file)
index 0000000..85687c5
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"), 
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in 
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "window.h"
+
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+
+#define APP_NAME "MediaPlayer"
+
+static XtAppContext app;
+static Display *display;
+
+static String fallback[] = {
+        "*renderTable: rt",
+        "*rt*fontType: FONT_IS_XFT",
+        "*rt*fontName: Sans",
+        "*rt*fontSize: 9",
+       NULL
+};
+
+int main(int argc, char** argv) { 
+    // disable stdout buffering, because the netbeans's internal terminal
+    // has a bug on freebsd and doesn't flush the output after a newline
+    setvbuf(stdout, NULL, _IONBF, 0);
+    
+    XtToolkitInitialize();
+    XtSetLanguageProc(NULL, NULL, NULL);
+    app = XtCreateApplicationContext();
+    XtAppSetFallbackResources(app, fallback);
+    
+    display =  XtOpenDisplay(app, NULL, APP_NAME, APP_NAME, NULL, 0, &argc, argv);
+    
+    MainWindow *window = WindowCreate(display);
+    
+    WindowShow(window);
+    XtAppMainLoop(app);
+    
+    return 0;
+}
+
+XtAppContext* GetAppContext(void) {
+    return &app;
+}
+
+void ApplicationExit(void) {
+    XtAppSetExitFlag(app);
+}
diff --git a/application/main.h b/application/main.h
new file mode 100644 (file)
index 0000000..3a8e3e8
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * 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.
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <Xm/XmAll.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+XtAppContext* GetAppContext(void);
+
+void ApplicationExit(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MAIN_H */
+
diff --git a/application/player.c b/application/player.c
new file mode 100644 (file)
index 0000000..213d184
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"), 
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in 
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "player.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <spawn.h>
+#include <sys/wait.h>
+
+#include <pthread.h>
+
+extern char **environ;
+
+#define WID_ARG_BUFSIZE 24
+
+static void* start_player(void *data);
+
+void PlayerOpenFile(MainWindow *win) {
+    pthread_t tid;
+    if(pthread_create(&tid, NULL, start_player, win)) {
+        perror("pthread_create");
+    }
+}
+
+static void* start_player(void *data) {
+    MainWindow *win = data;
+    
+    char *player_bin = "/usr/local/bin/mpv"; // TODO: get bin from settings
+    
+    // -wid parameter value for embedding the player in the player_widget
+    Window wid = XtWindow(win->player_widget);
+    char wid_arg[WID_ARG_BUFSIZE];
+    if(snprintf(wid_arg, WID_ARG_BUFSIZE, "%lu", wid) >= WID_ARG_BUFSIZE) {
+        return NULL;
+    }
+    
+    // create player arg list
+    char *args[32];
+    args[0] = player_bin;
+    args[1] = "-wid";
+    args[2] = wid_arg;
+    args[3] = win->file;
+    args[4] = NULL;
+    
+    // redirect stdin/stdout
+    int pout[2];
+    int pin[2];
+    if(pipe(pout)) {
+        perror("pipe");
+        return NULL;
+    }
+    if(pipe(pin)) {
+        perror("pipe");
+        return NULL;
+    }
+    
+    posix_spawn_file_actions_t actions;
+    posix_spawn_file_actions_init(&actions);
+    
+    posix_spawn_file_actions_adddup2(&actions, pin[0], STDIN_FILENO);
+    posix_spawn_file_actions_adddup2(&actions, pout[1], STDOUT_FILENO);
+    
+    // start player
+    pid_t player_pid;
+    if(posix_spawn(&player_pid, player_bin, &actions, NULL, args, environ)) {
+        perror("posix_spawn");
+        return NULL;
+    }
+    posix_spawn_file_actions_destroy(&actions);
+    
+    Player *player = malloc(sizeof(Player));
+    memset(player, 0, sizeof(Player));
+    
+    player->in = pin[1];
+    player->out = pout[0];
+    close(pin[0]);
+    close(pout[1]);
+    player->process = player_pid;
+    
+    if(win->player) {
+        PlayerDestroy(win->player);
+    }
+    win->player = player;
+    
+    return NULL;
+}
+
+void PlayerDestroy(Player *p) {
+    free(p);
+}
diff --git a/application/player.h b/application/player.h
new file mode 100644 (file)
index 0000000..ebd6ab4
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * 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.
+ */
+
+
+#ifndef PLAYER_H
+#define PLAYER_H
+
+#include "window.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void PlayerOpenFile(MainWindow *win);
+
+void PlayerDestroy(Player *p);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PLAYER_H */
+
diff --git a/application/window.c b/application/window.c
new file mode 100644 (file)
index 0000000..412381f
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"), 
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in 
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "window.h"
+#include "main.h"
+#include "player.h"
+
+#include "Fsb.h"
+
+static MainWindow *main_window;
+
+static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *args, int nargs);
+
+static void FileOpenCB(Widget w, void *udata, void *cdata);
+static void ViewFullscreenCB(Widget w, void *udata, void *cdata);
+
+static void window_close_handler(Widget window, void *udata, void *cdata) {
+    ApplicationExit();
+}
+
+static unsigned int keycodeF;
+
+static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
+    MainWindow *win = data;
+    if(win->fullscreen && event->xkey.keycode == keycodeF) {
+        WindowFullscreen(main_window, FALSE);
+        *dispatch = FALSE;
+    }
+}
+
+MainWindow* WindowCreate(Display *display) {
+    Arg args[32];
+    int n;
+    
+    MainWindow *window = malloc(sizeof(MainWindow));
+    memset(window, 0, sizeof(MainWindow));
+    main_window = window;
+    
+    // toplevel window
+    n = 0;
+    XtSetArg(args[n], XmNtitle, "MediaPlayer"); n++;
+    window->window = XtAppCreateShell(
+            "mediaplayer",
+            "mediaplayer",
+            //applicationShellWidgetClass,
+            vendorShellWidgetClass,
+            display,
+            args,
+            n);
+    
+    
+    Atom wm_delete_window;
+    wm_delete_window = XmInternAtom(
+            display,
+            "WM_DELETE_WINDOW",
+            0);
+    XmAddWMProtocolCallback(
+            window->window,
+            wm_delete_window,
+            window_close_handler,
+            window);
+    
+    
+    n = 0;
+    XtSetArg(args[n], XmNwidth, 360); n++;
+    XtSetArg(args[n], XmNheight, 220); n++;
+    XtSetArg(args[n], XmNshadowThickness, 0); n++;
+    Widget container = XmCreateForm(window->window, "form", args, n);
+    XtManageChild(container);
+    XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window);
+    
+    n = 0;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
+    WindowCreateMenu(window, container, args, n);
+    
+    n = 0;
+    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
+    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
+    XtSetArg(args[n], XmNtopWidget, window->menubar); n++;
+    XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++;
+    window->player_widget = XmCreateDrawingArea(container, "player", args, n);
+    XtManageChild(window->player_widget);
+    
+    // get F keycode
+    keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F"));
+    
+    return window;
+}
+
+void WindowShow(MainWindow *win) {
+    XtRealizeWidget(win->window);
+}
+
+/*
+ * Creates a XmPushButton menu item
+ */
+static Widget createMenuItem(
+        Widget menu,
+        char *name,
+        char *label,
+        char mnemonic,
+        const char *accelerator,
+        char *accelerator_text,
+        XtCallbackProc callback,
+        void *cbData)
+{
+    Arg args[16];
+    int n = 0;
+    
+    XmString s1 = XmStringCreateSimple(label);
+    XtSetArg(args[n], XmNlabelString, s1); n++;
+    XtSetArg(args[n], XmNmnemonic, mnemonic); n++;
+    XmString at = NULL;
+    if(accelerator && accelerator_text) {
+        at = XmStringCreateSimple(accelerator_text);
+        XtSetArg(args[n], XmNaccelerator, accelerator); n++;
+        XtSetArg(args[n], XmNacceleratorText, at); n++;
+    }
+    
+    Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n);
+    XtManageChild(menuItem);
+    XmStringFree(s1);
+    if(at) XmStringFree(at);
+    
+    if(callback) {
+        XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData);
+    }
+    
+    return menuItem;
+}
+
+static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) {
+    Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs);
+    XtManageChild(menubar);
+    win->menubar = menubar;
+    
+    Arg args[16];
+    int n;
+    
+    // menus
+    XmString s = XmStringCreateSimple("File");
+    Widget fileMenuItem = XtVaCreateManagedWidget(
+            "menuitem",
+            xmCascadeButtonWidgetClass,
+            menubar,
+            XmNlabelString, s,
+            NULL);
+    XmStringFree(s);
+    Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL);
+    
+    s = XmStringCreateSimple("Playback");
+    Widget playMenuItem = XtVaCreateManagedWidget(
+            "menuitem",
+            xmCascadeButtonWidgetClass,
+            menubar,
+            XmNlabelString, s,
+            NULL);
+    XmStringFree(s);
+    Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL);
+    
+    s = XmStringCreateSimple("View");
+    Widget viewMenuItem = XtVaCreateManagedWidget(
+            "menuitem",
+            xmCascadeButtonWidgetClass,
+            menubar,
+            XmNlabelString, s,
+            NULL);
+    XmStringFree(s);
+    Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL);
+    
+    // file menu
+    createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "Ctrl<Key>O", "Ctrl+O", FileOpenCB, NULL);
+    
+    // view menu
+    createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "<Key>F", "F", ViewFullscreenCB, NULL);
+}
+
+void go_fullscreen(Display *dsp, Window win)
+{
+  XEvent xev;
+  Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False);
+  Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False);
+  memset(&xev, 0, sizeof(xev));
+  xev.type = ClientMessage;
+  xev.xclient.window = win;
+  xev.xclient.message_type = wm_state;
+  xev.xclient.format = 32;
+  xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
+  xev.xclient.data.l[1] = fullscreen;
+  xev.xclient.data.l[2] = 0;
+  XSendEvent(dsp, DefaultRootWindow(dsp), False,
+    SubstructureNotifyMask, &xev);
+}
+
+static Atom net_wm_state;
+static Atom net_wm_state_fullscreen;
+static int net_wm_atoms_initialized = 0;
+
+void WindowFullscreen(MainWindow *win, bool enableFullscreen) {
+    Display *dpy = XtDisplay(win->window);
+    
+    // init net_wm_state atoms
+    if(!net_wm_atoms_initialized) {
+        net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
+        net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
+        net_wm_atoms_initialized = 1;
+    }
+    
+    WindowMenubarSetVisible(win, !enableFullscreen);
+    if(enableFullscreen && !win->fullscreen) {
+        XtUnmanageChild(main_window->menubar);
+        main_window->fullscreen = TRUE;
+    } else if(!enableFullscreen && win->fullscreen) {
+        XtManageChild(main_window->menubar);
+        main_window->fullscreen = FALSE;
+    }
+    
+    XEvent ev;
+    memset(&ev, 0, sizeof(XEvent));
+    ev.type = ClientMessage;
+    ev.xclient.window = XtWindow(win->window);
+    ev.xclient.message_type = net_wm_state;
+    ev.xclient.format = 32;
+    ev.xclient.data.l[0] = enableFullscreen ? 1 : 0;
+    ev.xclient.data.l[1] = net_wm_state_fullscreen;
+    ev.xclient.data.l[2] = 0;
+    XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev);
+}
+
+void WindowMenubarSetVisible(MainWindow *win, bool visible) {
+    if(visible) {
+        XtManageChild(main_window->menubar);
+        XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL);
+    } else {
+        XtUnmanageChild(main_window->menubar);
+        XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL);
+    }
+}
+
+
+
+static void filedialog_end(
+        Widget widget,
+        MainWindow *data,
+        XmFileSelectionBoxCallbackStruct *selection)
+{
+    XtUnmanageChild(widget);
+    XtDestroyWidget(widget);
+}
+
+static void filedialog_select(
+        Widget widget,
+        MainWindow *data,
+        XmFileSelectionBoxCallbackStruct *selection)
+{
+    char *value = NULL;
+    if(selection->value) {
+        XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value);
+        if(value) {
+            if(data->file) {
+                XtFree(data->file);
+            }
+            data->file = value;
+            // no need to free the value, because it is stored in MainWindow
+            
+            PlayerOpenFile(data);
+        }
+    }
+    filedialog_end(widget, data, NULL);
+}
+
+
+
+
+static void FileOpenCB(Widget w, void *udata, void *cdata) {
+    MainWindow *win = main_window;
+    Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", NULL, 0);
+    XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win);
+    XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win);
+    XtManageChild(dialog);
+}
+
+static void ViewFullscreenCB(Widget w, void *udata, void *cdata) {
+    if(main_window->fullscreen) {
+        WindowFullscreen(main_window, FALSE);
+    } else {
+        WindowFullscreen(main_window, TRUE);
+    }
+    
+}
diff --git a/application/window.h b/application/window.h
new file mode 100644 (file)
index 0000000..0ac5cc7
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2022 Olaf Wintermann
+ *
+ * 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.
+ */
+
+
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#include <Xm/XmAll.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Player {
+    pid_t process;
+    int in;
+    int out;
+} Player;
+    
+typedef struct MainWindow {
+    Widget window;
+    Widget menubar;
+    Widget player_widget;
+    char *file;
+    Player *player;
+    bool fullscreen;
+    bool mbvisible;
+} MainWindow;
+
+MainWindow* WindowCreate(Display *dp);
+
+void WindowShow(MainWindow *win);
+
+void WindowFullscreen(MainWindow *win, bool enableFullscreen);
+
+void WindowMenubarSetVisible(MainWindow *win, bool visible);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINDOW_H */
+
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..b640363
--- /dev/null
+++ b/configure
@@ -0,0 +1,364 @@
+#!/bin/sh
+
+
+PREFIX=/usr
+EPREFIX=$PREFIX
+
+BINDIR=
+SBINDIR=
+LIBDIR=
+LIBEXECDIR=
+DATADIR=
+SYSCONFDIR=
+SHAREDSTATEDIR=
+LOCALSTATEDIR=
+INCLUDEDIR=
+INFODIR=
+MANDIR=
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+       echo "Cannot create tmp dir"
+       echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+
+# help text
+printhelp()
+{
+       echo "Usage: $0 [OPTIONS]..."
+       cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+
+__EOF__
+}
+
+#
+# parse arguments 
+#
+for ARG in $@
+do
+    case "$ARG" in
+               "--prefix="*)         PREFIX=${ARG#--prefix=} ;;
+               "--exec-prefix="*)    EPREFIX=${ARG#--exec-prefix=} ;;
+               "--bindir="*)         BINDIR=${ARG#----bindir=} ;;
+               "--sbindir="*)        SBINDIR=${ARG#--sbindir=} ;;
+               "--libdir="*)         LIBDIR=${ARG#--libdir=} ;;
+               "--libexecdir="*)     LIBEXECDIR=${ARG#--libexecdir=} ;;
+               "--datadir="*)        DATADIR=${ARG#--datadir=} ;;
+               "--sysconfdir="*)     SYSCONFDIR=${ARG#--sysconfdir=} ;;
+               "--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;;
+               "--localstatedir="*)  LOCALSTATEDIR=${ARG#--localstatedir=} ;;
+               "--includedir="*)     INCLUDEDIR=${ARG#--includedir=} ;;
+               "--infodir="*)        INFODIR=${ARG#--infodir=} ;;
+               "--mandir"*)          MANDIR=${ARG#--mandir} ;;
+               "--help"*) printhelp; exit 1 ;;
+               "-"*) echo "unknown option: $ARG"; exit 1 ;;
+       esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+       BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+       SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+       LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+       LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+       DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+       SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+       SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+       LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+       INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+       INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+       MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+       PLATFORM_NAME=$p
+       break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+
+PREFIX=$PREFIX
+EPREFIX=$EPREFIX
+
+BINDIR=$BINDIR
+SBINDIR=$SBINDIR
+LIBDIR=$LIBDIR
+LIBEXECDIR=$LIBEXECDIR
+DATADIR=$DATADIR
+SYSCONFDIR=$SYSCONFDIR
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+LOCALSTATEDIR=$LOCALSTATEDIR
+INCLUDEDIR=$INCLUDEDIR
+INFODIR=$INFODIR
+MANDIR=$MANDIR
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+dependency_motif()
+{
+    printf "checking for motif... "
+    # dependency motif 
+    while true
+    do
+        CFLAGS="$CFLAGS -DUI_MOTIF"    
+        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
+               echo yes
+        return 0
+    done
+       
+       echo no
+       return 1
+}
+
+DEPENDENCIES_FAILED=
+ERROR=0
+# general dependencies
+CFLAGS=
+LDFLAGS=
+while true
+do
+    if isnotplatform "unix"; then
+        break
+    fi
+    while true
+    do
+        
+               cat >> $TEMP_DIR/make.mk << __EOF__
+OBJ_EXT = o
+LIB_EXT = a
+PACKAGE_SCRIPT = package_unix.sh
+
+__EOF__
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    while true
+    do
+        
+        LDFLAGS="$LDFLAGS -lpthread"    
+        
+        break
+    done
+    
+    break
+done
+while true
+do
+    if isnotplatform "bsd"; then
+        break
+    fi
+    while true
+    do
+        
+        CFLAGS="$CFLAGS -I/usr/local/include"    
+        LDFLAGS="$LDFLAGS -L/usr/local/lib"    
+        
+        break
+    done
+    
+    break
+done
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# OPTION VALUES
+#
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+# Target: app
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+dependency_motif
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED motif "
+       ERROR=1
+fi
+
+# Features
+
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "APP_CFLAGS  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "APP_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "APP_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+if [ $ERROR -ne 0 ]; then
+       echo
+       echo "Error: Unresolved dependencies"
+       echo $DEPENDENCIES_FAILED
+       rm -Rf $TEMP_DIR
+       exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
diff --git a/make/Makefile.mk b/make/Makefile.mk
new file mode 100644 (file)
index 0000000..016534e
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2021 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright notice,
+#      this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+# this makefile is invoked from the build root directory
+
+BUILD_ROOT = ./
+include config.mk
+
+BUILD_DIRS = build/bin build/lib
+BUILD_DIRS += build/ucx
+BUILD_DIRS += build/application
+
+all: $(BUILD_DIRS) ucx application
+       make/$(PACKAGE_SCRIPT)
+
+$(BUILD_DIRS):
+       mkdir -p $@
+
+ucx: $(BUILD_DIRS) FORCE
+       cd ucx; $(MAKE)
+
+application: $(BUILD_DIRS) ucx FORCE
+       cd application; $(MAKE)
+
+FORCE:
+
diff --git a/make/clang.mk b/make/clang.mk
new file mode 100644 (file)
index 0000000..93e8096
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# clang toolchain config
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
diff --git a/make/configure.vm b/make/configure.vm
new file mode 100644 (file)
index 0000000..80068be
--- /dev/null
@@ -0,0 +1,615 @@
+#!/bin/sh
+
+#foreach( $var in $vars )
+#if( $var.exec )
+${var.name}=`${var.value}`
+#else
+${var.name}=${var.value}
+#end
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=/usr
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$PREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=
+#end
+
+OS=`uname -s`
+OS_VERSION=`uname -r`
+
+TEMP_DIR=".tmp-`uname -n`"
+mkdir -p $TEMP_DIR
+if [ $? -ne 0 ]; then
+       echo "Cannot create tmp dir"
+       echo "Abort"
+fi
+touch $TEMP_DIR/options
+touch $TEMP_DIR/features
+
+# features
+#foreach( $feature in $features )
+#if( ${feature.isDefault()} )
+${feature.getVarName()}=on
+#end
+#end
+
+# help text
+printhelp()
+{
+       echo "Usage: $0 [OPTIONS]..."
+       cat << __EOF__
+Installation directories:
+  --prefix=PREFIX         path prefix for architecture-independent files
+                          [/usr]
+  --exec-prefix=EPREFIX   path prefix for architecture-dependent files
+                          [PREFIX]
+
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        system configuration files [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+
+#if( $options.size() > 0 )
+Options:
+#foreach( $opt in $options )
+  --${opt.getArgument()}=${opt.getValuesString()}
+#end
+
+#end
+#if( $features.size() > 0 )
+Optional Features:
+#foreach( $feature in $features )
+#if( $feature.default )
+  --disable-${feature.arg}
+#else
+  --enable-${feature.arg}
+#end
+#end
+
+#end
+__EOF__
+}
+
+#
+# parse arguments 
+#
+#set( $D = '$' )
+for ARG in $@
+do
+    case "$ARG" in
+               "--prefix="*)         PREFIX=${D}{ARG#--prefix=} ;;
+               "--exec-prefix="*)    EPREFIX=${D}{ARG#--exec-prefix=} ;;
+               "--bindir="*)         BINDIR=${D}{ARG#----bindir=} ;;
+               "--sbindir="*)        SBINDIR=${D}{ARG#--sbindir=} ;;
+               "--libdir="*)         LIBDIR=${D}{ARG#--libdir=} ;;
+               "--libexecdir="*)     LIBEXECDIR=${D}{ARG#--libexecdir=} ;;
+               "--datadir="*)        DATADIR=${D}{ARG#--datadir=} ;;
+               "--sysconfdir="*)     SYSCONFDIR=${D}{ARG#--sysconfdir=} ;;
+               "--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;;
+               "--localstatedir="*)  LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;;
+               "--includedir="*)     INCLUDEDIR=${D}{ARG#--includedir=} ;;
+               "--infodir="*)        INFODIR=${D}{ARG#--infodir=} ;;
+               "--mandir"*)          MANDIR=${D}{ARG#--mandir} ;;
+               "--help"*) printhelp; exit 1 ;;
+       #foreach( $opt in $options )
+       "--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;;
+    #end
+       #foreach( $feature in $features )
+               "--enable-${feature.arg}") ${feature.getVarName()}=on ;;
+               "--disable-${feature.arg}") unset ${feature.getVarName()} ;;
+       #end
+               "-"*) echo "unknown option: $ARG"; exit 1 ;;
+       esac
+done
+
+# set dir variables
+if [ -z "$BINDIR" ]; then
+       BINDIR=$EPREFIX/bin
+fi
+if [ -z "$SBINDIR" ]; then
+       SBINDIR=$EPREFIX/sbin
+fi
+if [ -z "$LIBDIR" ]; then
+       LIBDIR=$EPREFIX/lib
+fi
+if [ -z "$LIBEXEC" ]; then
+       LIBEXECDIR=$EPREFIX/libexec
+fi
+if [ -z "$DATADIR" ]; then
+       DATADIR=$PREFIX/share
+fi
+if [ -z "$SYSCONFDIR" ]; then
+       SYSCONFDIR=$PREFIX/etc
+fi
+if [ -z "$SHAREDSTATEDIR" ]; then
+       SHAREDSTATEDIR=$PREFIX/com
+fi
+if [ -z "$LOCALSTATEDIR" ]; then
+       LOCALSTATEDIR=$PREFIX/var
+fi
+if [ -z "$INCLUDEDIR" ]; then
+       INCLUDEDIR=$PREFIX/include
+fi
+if [ -z "$INFODIR" ]; then
+       INFODIR=$PREFIX/info
+fi
+if [ -z "$MANDIR" ]; then
+       MANDIR=$PREFIX/man
+fi
+
+which pkg-config > /dev/null
+if [ $? -eq 0 ]; then
+    PKG_CONFIG=pkg-config
+else
+    PKG_CONFIG=false
+fi
+
+# Simple uname based platform detection
+# $PLATFORM is used for platform dependent dependency selection
+printf "detect platform... "
+if [ $OS = SunOS ]; then
+    PLATFORM="solaris sunos unix svr4"
+fi
+if [ $OS = Linux ]; then
+    PLATFORM="linux unix"
+fi
+if [ $OS = FreeBSD ]; then
+    PLATFORM="freebsd bsd unix"
+fi
+if [ $OS = Darwin ]; then
+    PLATFORM="macos osx bsd unix"
+fi
+echo $OS | grep "MINGW" > /dev/null
+if [ $? -eq 0 ]; then
+    PLATFORM="windows mingw"
+fi
+
+if [ -z "$PLATFORM" ]; then
+    PLATFORM="unix"
+fi
+
+for p in $PLATFORM
+do
+       PLATFORM_NAME=$p
+       break
+done
+echo $PLATFORM_NAME
+
+isplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+isnotplatform()
+{
+    for p in $PLATFORM
+    do
+        if [ $p = $1 ]; then
+            return 1
+        fi
+    done
+    return 0
+}
+
+# generate config.mk and config.h
+cat > $TEMP_DIR/config.mk << __EOF__
+#
+# config.mk generated by configure
+#
+
+# general vars
+#foreach( $var in $vars )
+${var.name}=$${var.name}
+#end
+
+#if ( ! $project.hasVar("PREFIX") )
+PREFIX=$PREFIX
+#end
+#if ( ! $project.hasVar("EPREFIX") )
+EPREFIX=$EPREFIX
+#end
+
+#if ( ! $project.hasVar("BINDIR") )
+BINDIR=$BINDIR
+#end
+#if ( ! $project.hasVar("SBINDIR") )
+SBINDIR=$SBINDIR
+#end
+#if ( ! $project.hasVar("LIBDIR") )
+LIBDIR=$LIBDIR
+#end
+#if ( ! $project.hasVar("LIBEXECDIR") )
+LIBEXECDIR=$LIBEXECDIR
+#end
+#if ( ! $project.hasVar("DATADIR") )
+DATADIR=$DATADIR
+#end
+#if ( ! $project.hasVar("SYSCONFDIR") )
+SYSCONFDIR=$SYSCONFDIR
+#end
+#if ( ! $project.hasVar("SHAREDSTATEDIR") )
+SHAREDSTATEDIR=$SHAREDSTATEDIR
+#end
+#if ( ! $project.hasVar("LOCALSTATEDIR") )
+LOCALSTATEDIR=$LOCALSTATEDIR
+#end
+#if ( ! $project.hasVar("INCLUDEDIR") )
+INCLUDEDIR=$INCLUDEDIR
+#end
+#if ( ! $project.hasVar("INFODIR") )
+INFODIR=$INFODIR
+#end
+#if ( ! $project.hasVar("MANDIR") )
+MANDIR=$MANDIR
+#end
+
+__EOF__
+
+echo > $TEMP_DIR/make.mk
+
+ENV_CFLAGS=$CFLAGS
+ENV_LDFLAGS=$LDFLAGS
+ENV_CXXFLAGS=$CXXFLAGS
+
+# Toolchain detection
+# this will insert make vars to config.mk
+. make/toolchain.sh
+
+# add user specified flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${ENV_CFLAGS}" ]; then
+    echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${ENV_LDFLAGS}" ]; then
+    echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#
+# DEPENDENCIES
+#
+
+#foreach( $dependency in $namedDependencies )
+dependency_${dependency.name}()
+{
+    printf "checking for ${dependency.name}... "
+    #foreach( $sub in $dependency.getSubdependencies() )
+    # dependency $sub.name $sub.getPlatformString()
+    while true
+    do
+       #if( $sub.platform )
+       if isnotplatform "${sub.platform}"; then
+            break
+        fi
+       #end
+               #foreach( $not in $sub.getNotList() )
+               if isplatform "${not}"; then
+            break
+        fi
+               #end
+        #if( $sub.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+               break
+        fi
+        #end
+        #foreach( $pkg in $sub.pkgconfig )
+               $PKG_CONFIG $pkg.getPkgConfigParam()
+        if [ $? -ne 0 ] ; then
+            break
+        fi
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+        #end
+        #foreach( $flags in $sub.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if [ $? -eq 0 ]; then
+            $flags.varName="$$flags.varName `$flags.value`"
+        else
+            break
+        fi
+        #else
+        $flags.varName="$$flags.varName $flags.value"    
+        #end
+        #end
+        #foreach( $test in $sub.tests )
+        $test > /dev/null
+        if [ $? -ne 0 ]; then
+               break
+        fi
+        #end
+               #if ( $sub.make.length() > 0 )
+               cat >> $TEMP_DIR/make.mk << __EOF__
+# Dependency: $dependency.name         
+$sub.make
+__EOF__
+        #end
+               echo yes
+        return 0
+    done
+       
+       #end
+       echo no
+       return 1
+}
+#end
+
+DEPENDENCIES_FAILED=
+ERROR=0
+#if( $dependencies.size() > 0 )
+# general dependencies
+CFLAGS=
+LDFLAGS=
+#foreach( $dependency in $dependencies )
+while true
+do
+       #if( $dependency.platform )
+    if isnotplatform "${dependency.platform}"; then
+        break
+    fi
+    #end
+       #foreach( $not in $dependency.getNotList() )
+    if isplatform "${not}"; then
+        break
+    fi
+       #end
+    while true
+    do
+        #if( $dependency.pkgconfig.size() > 0 )
+        if [ -z "$PKG_CONFIG" ]; then
+            ERROR=1
+            break
+        fi
+        #end
+        #foreach( $pkg in $dependency.pkgconfig )
+        printf "checking for pkg-config package $pkg.getPkgConfigParam()... "
+               $PKG_CONFIG $pkg.getPkgConfigParam()
+        if [ $? -ne 0 ]; then
+            echo no
+            ERROR=1
+            break
+        fi
+        echo yes
+        CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`"
+        LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`"
+        #end
+        
+        #foreach( $flags in $dependency.flags )
+        #if( $flags.exec )
+        $flags.value > /dev/null
+        if [ $? -ne 0 ]; then
+            $flags.varName="$$flags.varName `$flags.value`"
+        else
+            ERROR=1
+            break
+        fi
+        #else
+        $flags.varName="$$flags.varName $flags.value"    
+        #end
+        #end
+               #if ( $dependency.make.length() > 0 )
+               cat >> $TEMP_DIR/make.mk << __EOF__
+$dependency.make
+__EOF__
+        #end
+        
+        break
+    done
+    
+    break
+done
+#end
+
+# add general dependency flags to config.mk
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+#end
+
+#
+# OPTION VALUES
+#
+#foreach( $opt in $options )
+#foreach( $val in $opt.values )
+${val.func}()
+{
+       VERR=0
+       #foreach( $dep in $val.dependencies )
+       dependency_$dep
+       if [ $? -ne 0 ]; then
+               VERR=1
+       fi
+       #end
+       if [ $VERR -ne 0 ]; then
+               return 1
+       fi
+       #foreach( $def in $val.defines )
+               CFLAGS="$CFLAGS ${def.toFlags()}"
+       #end
+       #if( $val.hasMake() )
+       cat >> $TEMP_DIR/make.mk << __EOF__
+$val.make
+__EOF__
+       #end
+       return 0
+}
+#end
+#end
+
+#
+# TARGETS
+#
+CFLAGS=
+CXXFLAGS=
+LDFLAGS=
+
+#foreach( $target in $targets )
+#if ( $target.name )
+# Target: $target.name
+#else
+# Target
+#end
+CFLAGS=
+LDFLAGS=
+CXXFLAGS=
+
+#foreach( $dependency in $target.dependencies )
+dependency_$dependency
+if [ $? -ne 0 ]; then
+       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+       ERROR=1
+fi
+#end
+
+# Features
+#foreach( $feature in $target.features )
+if [ ! -z "$${feature.getVarName()}" ]; then
+#foreach( $dependency in $feature.dependencies )
+       # check dependency
+       dependency_$dependency
+       if [ $? -ne 0 ]; then
+               # "auto" features can fail and are just disabled in this case
+               if [ $${feature.getVarName()} != "auto" ]; then
+                       DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} "
+                       ERROR=1
+               fi
+       fi
+#end
+fi
+#end
+
+#foreach( $opt in $target.options )
+# Option: --${opt.argument}
+if [ -z ${D}${opt.getVarName()} ]; then
+       SAVED_ERROR=$ERROR
+       SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED
+       ERROR=0
+       while true
+       do
+               #foreach( $optdef in $opt.defaults )
+               #if( $optdef.platform )
+               if isplatform "$optdef.platform"; then
+               #end
+               $optdef.func
+               if [ $? -eq 0 ]; then
+                       echo "  ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options
+                       ERROR=0
+                       break
+               fi
+               #if( $optdef.platform )
+               fi
+               #end
+               #end
+               break
+       done
+       if [ $ERROR -ne 0 ]; then
+               SAVED_ERROR=1
+       fi
+       ERROR=$SAVED_ERROR
+       DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED=
+else
+       if false; then
+               false
+       #foreach( $optval in $opt.values )
+       elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then
+               echo "  ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options
+               $optval.func
+               if [ $? -ne 0 ]; then
+                       ERROR=1
+               fi
+       #end
+       fi
+fi
+#end
+
+echo >> $TEMP_DIR/config.mk
+if [ ! -z "${CFLAGS}" ]; then
+    echo "${target.getCFlags()}  += $CFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${CXXFLAGS}" ]; then
+    echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk
+fi
+if [ ! -z "${LDFLAGS}" ]; then
+    echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk
+fi
+
+#end
+if [ $ERROR -ne 0 ]; then
+       echo
+       echo "Error: Unresolved dependencies"
+       echo $DEPENDENCIES_FAILED
+       rm -Rf $TEMP_DIR
+       exit 1
+fi
+
+echo "configure finished"
+echo
+echo "Build Config:"
+echo "  PREFIX:    $PREFIX"
+echo "  TOOLCHAIN: $TOOLCHAIN_NAME"
+#if ( $options.size() > 0 )
+echo "Options:"
+cat $TEMP_DIR/options
+#end
+echo
+cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk
+rm -Rf $TEMP_DIR
+
+
diff --git a/make/gcc.mk b/make/gcc.mk
new file mode 100644 (file)
index 0000000..624bdf1
--- /dev/null
@@ -0,0 +1,10 @@
+#
+# gcc toolchain config
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -fPIC
+SHLIB_LDFLAGS = -shared
+
diff --git a/make/mingw.mk b/make/mingw.mk
new file mode 100644 (file)
index 0000000..340102e
--- /dev/null
@@ -0,0 +1,46 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+MSBUILD = MSBuild.exe
+
+CFLAGS  = -std=gnu99 -c -O2 -m64
+COFLAGS = -o
+LDFLAGS = 
+LOFLAGS = -o
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT = .exe
+
+PACKAGE_SCRIPT = package_windows.sh
\ No newline at end of file
diff --git a/make/osx.mk b/make/osx.mk
new file mode 100644 (file)
index 0000000..0db5e1c
--- /dev/null
@@ -0,0 +1,43 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS  += -std=gnu99 -g -I/usr/include/libxml2
+LDFLAGS += -lxml2 -lz -lpthread -licucore -lm
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = o
+LIB_EXT = a
+APP_EXT =
+
+PACKAGE_SCRIPT = package_osx.sh
diff --git a/make/package_unix.sh b/make/package_unix.sh
new file mode 100755 (executable)
index 0000000..13f4793
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+
diff --git a/make/project.xml b/make/project.xml
new file mode 100644 (file)
index 0000000..9ce2c49
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+       <dependency name="motif">
+               <cflags>-DUI_MOTIF</cflags>
+               <ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+       </dependency>
+       
+       <dependency platform="unix">
+               <make>OBJ_EXT = o</make>
+               <make>LIB_EXT = a</make>
+               <make>PACKAGE_SCRIPT = package_unix.sh</make>
+       </dependency>
+       
+       <dependency>
+               <ldflags>-lpthread</ldflags>
+       </dependency>
+       
+       <dependency platform="bsd">
+               <cflags>-I/usr/local/include</cflags>
+               <ldflags>-L/usr/local/lib</ldflags>
+       </dependency>
+       
+       <target name="app">
+               <dependencies>motif</dependencies>
+       </target>
+</project>
+
diff --git a/make/suncc.mk b/make/suncc.mk
new file mode 100644 (file)
index 0000000..a97fe3c
--- /dev/null
@@ -0,0 +1,10 @@
+#
+# suncc toolchain
+#
+
+CFLAGS = 
+LDFLAGS = 
+
+SHLIB_CFLAGS = -Kpic
+SHLIB_LDFLAGS = -G
+
diff --git a/make/toolchain.sh b/make/toolchain.sh
new file mode 100644 (file)
index 0000000..8e9a529
--- /dev/null
@@ -0,0 +1,181 @@
+#!/bin/sh
+#
+# toolchain detection
+#
+
+C_COMPILERS="cc gcc clang suncc"
+CPP_COMPILERS="CC g++ clang++ sunCC"
+unset CC_ARG_CHECKED
+unset TOOLCHAIN_DETECTION_ERROR
+unset TOOLCHAIN_NAME
+
+check_c_compiler()
+{
+       cat > $TEMP_DIR/test.c << __EOF__
+/* test file */
+#include <stdio.h>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+       printf("clang\n");
+#elif defined(__GNUC__)
+       printf("gcc\n");
+#elif defined(__sun)
+       printf("suncc\n");
+#else
+       printf("unknown\n");
+#endif
+       return 0;
+}
+__EOF__
+       rm -f $TEMP_DIR/checkcc
+       $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null
+       
+       if [ $? -ne 0 ]; then
+               return 1
+       fi
+       return 0
+}
+
+check_cpp_compiler()
+{
+       cat > $TEMP_DIR/test.cpp << __EOF__
+/* test file */
+#include <iostream>
+int main(int argc, char **argv) {
+#if defined(__clang__)
+       std::cout << "clang" << std::endl;
+#elif defined(__GNUC__)
+       std::cout << "gcc" << std::endl;
+#elif defined(__sun)
+       std::cout << "suncc" << std::endl;
+#else
+       std::cout << "unknown" << std::endl;
+#endif
+       return 0;
+}
+__EOF__
+       rm -f $TEMP_DIR/checkcc
+       $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null
+       
+       if [ $? -ne 0 ]; then
+               return 1
+       fi
+       return 0
+}
+
+printf "detect C compiler... "
+
+for COMP in $C_COMPILERS
+do
+       check_c_compiler $COMP
+       if [ $? -ne 0 ]; then
+               if [ ! -z "$CC" ]; then
+                       if [ $COMP = $CC ]; then
+                               echo "$CC is not a working C Compiler"
+                               TOOLCHAIN_DETECTION_ERROR="error"
+                               break
+                       fi
+               fi
+       else
+               TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+               USE_TOOLCHAIN=$TOOLCHAIN_NAME
+               if [ $COMP = "cc" ]; then
+                       # we have found a working compiler, but in case
+                       # the compiler is gcc or clang, we try to use
+                       # these commands and not 'cc'
+                       TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+                       if [ $TOOLCHAIN_NAME = "gcc" ]; then
+                               check_c_compiler "gcc"
+                               if [ $? -eq 0 ]; then
+                                       COMP=gcc
+                                       USE_TOOLCHAIN="gcc"
+                               fi
+                       fi
+                       if [ $TOOLCHAIN_NAME = "clang" ]; then
+                               check_c_compiler "clang"
+                               if [ $? -eq 0 ]; then
+                                       COMP=clang
+                                       USE_TOOLCHAIN="clang"
+                               fi
+                       fi
+               fi
+               
+               TOOLCHAIN_NAME=$USE_TOOLCHAIN
+               TOOLCHAIN_CC=$COMP
+               echo $COMP
+               break
+       fi
+done
+if [ -z $TOOLCHAIN_CC ]; then
+       echo "not found"
+fi
+
+printf "detect C++ compiler... "
+
+for COMP in $CPP_COMPILERS
+do
+       check_cpp_compiler $COMP
+       if [ $? -ne 0 ]; then
+               if [ ! -z "$CXX" ]; then
+                       if [ $COMP = $CXX ]; then
+                               echo "$CC is not a working C++ Compiler"
+                               TOOLCHAIN_DETECTION_ERROR="error"
+                               break
+                       fi
+               fi
+       else
+               if [ $COMP = "CC" ]; then
+                       # we have found a working compiler, but in case
+                       # the compiler is gcc or clang, we try to use
+                       # these commands and not 'cc'
+                       TOOLCHAIN_NAME=`$TEMP_DIR/checkcc`
+                       USE_TOOLCHAIN=$TOOLCHAIN_NAME
+                       if [ $TOOLCHAIN_NAME = "gcc" ]; then
+                               check_cpp_compiler "g++"
+                               if [ $? -eq 0 ]; then
+                                  COMP=g++
+                                  USE_TOOLCHAIN="gcc"
+                               fi
+                       fi
+                       if [ $TOOLCHAIN_NAME = "clang" ]; then
+                               check_cpp_compiler "clang++"
+                               if [ $? -eq 0 ]; then
+                                  COMP=clang++
+                                  USE_TOOLCHAIN="clang"
+                               fi
+                       fi
+               fi
+               
+               TOOLCHAIN_NAME=$USE_TOOLCHAIN
+               TOOLCHAIN_CXX=$COMP
+               echo $COMP
+               break
+       fi
+done
+if [ -z $TOOLCHAIN_CXX ]; then
+       echo "not found"
+fi
+
+TOOLCHAIN_LD=$TOOLCHAIN_CC
+
+if [ -z "$TOOLCHAIN_NAME" ]; then
+       TOOLCHAIN_DETECTION_ERROR="error"
+else
+       cat >> $TEMP_DIR/config.mk << __EOF__
+# toolchain
+__EOF__
+       echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk
+       if [ ! -z "$TOOLCHAIN_CXX" ]; then
+               echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk
+       fi
+       echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk
+       echo >> $TEMP_DIR/config.mk
+       
+       cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1
+       if [ $? -eq 0 ]; then 
+               echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk
+       else
+               echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk
+               echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk
+       fi
+fi
diff --git a/make/windows.mk b/make/windows.mk
new file mode 100644 (file)
index 0000000..2f3bc72
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2011 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+CC = gcc
+LD = gcc
+AR = ar
+RM = rm
+
+CFLAGS  = -std=gnu99
+LDFLAGS = 
+ARFLAGS = -r
+RMFLAGS = -f
+
+OBJ_EXT = obj
+LIB_EXT = lib
+APP_EXT = .exe
+
diff --git a/ucx/Makefile b/ucx/Makefile
new file mode 100644 (file)
index 0000000..2369197
--- /dev/null
@@ -0,0 +1,62 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2013 Olaf Wintermann. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#   1. Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above copyright
+#      notice, this list of conditions and the following disclaimer in the
+#      documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+# list of source files
+SRC  = utils.c
+SRC += list.c
+SRC += map.c
+SRC += avl.c
+SRC += properties.c
+SRC += mempool.c
+SRC += string.c
+SRC += test.c
+SRC += allocator.c
+SRC += logging.c
+SRC += buffer.c
+SRC += stack.c
+SRC += ucx.c
+SRC += array.c
+
+OBJ   = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT))
+
+UCX_LIB = ../build/lib/libucx.$(LIB_EXT)
+
+all: ../build/ucx $(UCX_LIB)
+
+$(UCX_LIB): $(OBJ)
+       $(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ)
+
+../build/ucx:
+       mkdir -p ../build/ucx
+
+../build/ucx/%.$(OBJ_EXT): %.c
+       $(CC) $(CFLAGS) -o $@ -c $<
+
diff --git a/ucx/README b/ucx/README
new file mode 100644 (file)
index 0000000..d0890d0
--- /dev/null
@@ -0,0 +1,4 @@
+UCX is a library for common data structures, algorithms and string functions.
+
+More informations at: https://develop.uap-core.de/ucx/
+
diff --git a/ucx/allocator.c b/ucx/allocator.c
new file mode 100644 (file)
index 0000000..22a5cd5
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/allocator.h"
+
+#include <stdlib.h>
+
+static UcxAllocator default_allocator = {
+    NULL,
+    ucx_default_malloc,
+    ucx_default_calloc,
+    ucx_default_realloc,
+    ucx_default_free
+};
+
+UcxAllocator *ucx_default_allocator() {
+    UcxAllocator *allocator = &default_allocator;
+    return allocator;
+}
+
+void *ucx_default_malloc(void *ignore, size_t n) {
+    return malloc(n);
+}
+
+void *ucx_default_calloc(void *ignore, size_t n, size_t size) {
+    return calloc(n, size);
+}
+
+void *ucx_default_realloc(void *ignore, void *data, size_t n) {
+    return realloc(data, n);
+}
+
+void ucx_default_free(void *ignore, void *data) {
+    free(data);
+}
diff --git a/ucx/array.c b/ucx/array.c
new file mode 100644 (file)
index 0000000..0592fc6
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE /* we want to use qsort_r(), if available */
+#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */
+
+
+#include "ucx/array.h"
+#include "ucx/utils.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifndef UCX_ARRAY_DISABLE_QSORT
+#ifdef __GLIBC__
+#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8)
+#define ucx_array_sort_impl qsort_r
+#endif /* glibc version >= 2.8 */
+#elif /* not  __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__)
+#define ucx_array_sort_impl ucx_qsort_r
+#define USE_UCX_QSORT_R
+#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun)
+#if __STDC_VERSION__ >= 201112L
+#define ucx_array_sort_impl qsort_s
+#endif
+#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */
+#endif /* UCX_ARRAY_DISABLE_QSORT */
+
+#ifndef ucx_array_sort_impl
+#define ucx_array_sort_impl ucx_mergesort
+#endif
+
+static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) {
+    size_t required_capacity = array->capacity;
+    while (reqcap > required_capacity) {
+        if (required_capacity * 2 < required_capacity)
+            return 1;
+        required_capacity <<= 1;
+    }
+    if (ucx_array_reserve(array, required_capacity)) {
+        return 1;
+    }
+    return 0;
+}
+
+int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t elmsize, size_t index, void* data) {
+    
+    if(!alloc || !capacity || !array) {
+        errno = EINVAL;
+        return 1;
+    }
+    
+    size_t newcapacity = *capacity;
+    while(index >= newcapacity) {
+        if(ucx_szmul(newcapacity, 2, &newcapacity)) {
+            errno = EOVERFLOW;
+            return 1;
+        }        
+    }
+
+    size_t memlen, offset;
+    if(ucx_szmul(newcapacity, elmsize, &memlen)) {
+        errno = EOVERFLOW;
+        return 1;
+    }
+    /* we don't need to check index*elmsize - it is smaller than memlen */
+    
+    
+    void* newptr = alrealloc(alloc, *array, memlen);
+    if(newptr == NULL) {
+        errno = ENOMEM; /* we cannot assume that every allocator sets this */
+        return 1;
+    }
+    *array = newptr;
+    *capacity = newcapacity;
+    
+    
+    char* dest = *array;
+    dest += elmsize*index;
+    memcpy(dest, data, elmsize);
+    
+    return 0;
+}
+
+int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t index, void* data) {
+    
+    return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*),
+            index, &data);
+}
+
+UcxArray* ucx_array_new(size_t capacity, size_t elemsize) {
+    return ucx_array_new_a(capacity, elemsize, ucx_default_allocator());
+}
+
+UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
+        UcxAllocator* allocator) {
+    UcxArray* array = almalloc(allocator, sizeof(UcxArray));
+    if(array) {
+        ucx_array_init_a(array, capacity, elemsize, allocator);
+    }
+    return array;
+}
+
+void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) {
+    ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator());
+}
+
+void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
+        UcxAllocator* allocator) {
+    
+    array->allocator = allocator;
+    array->elemsize = elemsize;
+    array->size = 0;
+    array->data = alcalloc(allocator, capacity, elemsize);
+    
+    if (array->data) {
+        array->capacity = capacity;
+    } else {
+        array->capacity = 0;
+    }
+}
+
+int ucx_array_clone(UcxArray* dest, UcxArray const* src) {
+    if (ucx_array_ensurecap(dest, src->capacity)) {
+        return 1;
+    }
+    
+    dest->elemsize = src->elemsize;
+    dest->size = src->size;
+    
+    if (dest->data) {
+        memcpy(dest->data, src->data, src->size*src->elemsize);
+    }
+    
+    return 0;
+}
+
+int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
+        cmp_func cmpfnc, void* data) {
+    
+    if (array1->size != array2->size || array1->elemsize != array2->elemsize) {
+        return 0;
+    } else {
+        if (array1->size == 0)
+            return 1;
+        
+        size_t elemsize;
+        if (cmpfnc == NULL) {
+            cmpfnc = ucx_cmp_mem;
+            elemsize = array1->elemsize;
+            data = &elemsize;
+        }
+        
+        for (size_t i = 0 ; i < array1->size ; i++) {
+            int r = cmpfnc(
+                    ucx_array_at(array1, i),
+                    ucx_array_at(array2, i),
+                    data);
+            if (r != 0)
+                return 0;
+        }
+        return 1;
+    }
+}
+
+void ucx_array_destroy(UcxArray *array) {
+    if(array->data)
+        alfree(array->allocator, array->data);
+    array->data = NULL;
+    array->capacity = array->size = 0;
+}
+
+void ucx_array_free(UcxArray *array) {
+    ucx_array_destroy(array);
+    alfree(array->allocator, array);
+}
+
+int ucx_array_append_from(UcxArray *array, void *data, size_t count) {
+    if (ucx_array_ensurecap(array, array->size + count))
+        return 1;
+    
+    void* dest = ucx_array_at(array, array->size);
+    if (data) {
+        memcpy(dest, data, array->elemsize*count);
+    } else {
+        memset(dest, 0, array->elemsize*count);
+    }
+    array->size += count;
+    
+    return 0;
+}
+
+int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) {
+    if (ucx_array_ensurecap(array, array->size + count))
+        return 1;
+    
+    if (array->size > 0) {
+        void *dest = ucx_array_at(array, count);
+        memmove(dest, array->data, array->elemsize*array->size);
+    }
+    
+    if (data) {
+        memcpy(array->data, data, array->elemsize*count);
+    } else {
+        memset(array->data, 0, array->elemsize*count);
+    }
+    array->size += count;
+        
+    return 0;
+}
+
+int ucx_array_set_from(UcxArray *array, size_t index,
+        void *data, size_t count) {
+    if (ucx_array_ensurecap(array, index + count))
+        return 1;
+    
+    if (index+count > array->size) {
+        array->size = index+count;
+    }
+    
+    void *dest = ucx_array_at(array, index);
+    if (data) {
+        memcpy(dest, data, array->elemsize*count);
+    } else {
+        memset(dest, 0, array->elemsize*count);
+    }
+    
+    return 0;
+}
+
+int ucx_array_concat(UcxArray *array1, const UcxArray *array2) {
+    
+    if (array1->elemsize != array2->elemsize)
+        return 1;
+    
+    size_t capacity = array1->capacity+array2->capacity;
+        
+    if (array1->capacity < capacity) {
+        if (ucx_array_reserve(array1, capacity)) {
+            return 1;
+        }
+    }
+    
+    void* dest = ucx_array_at(array1, array1->size);
+    memcpy(dest, array2->data, array2->size*array2->elemsize);
+    
+    array1->size += array2->size;
+    
+    return 0;
+}
+
+void *ucx_array_at(UcxArray const *array, size_t index) {
+    char* memory = array->data;
+    char* loc = memory + index*array->elemsize;
+    return loc;
+}
+
+size_t ucx_array_find(UcxArray const *array, void *elem,
+        cmp_func cmpfnc, void *data) {
+    
+    size_t elemsize;
+    if (cmpfnc == NULL) {
+        cmpfnc = ucx_cmp_mem;
+        elemsize = array->elemsize;
+        data = &elemsize;
+    }
+
+    if (array->size > 0) {
+        for (size_t i = 0 ; i < array->size ; i++) {
+            void* ptr = ucx_array_at(array, i);
+            if (cmpfnc(ptr, elem, data) == 0) {
+                return i;
+            }
+        }
+        return array->size;
+    } else {
+        return 0;
+    }
+}
+
+int ucx_array_contains(UcxArray const *array, void *elem,
+        cmp_func cmpfnc, void *data) {
+    return ucx_array_find(array, elem, cmpfnc, data) != array->size;
+}
+
+static void ucx_mergesort_merge(void *arrdata,size_t elemsize,
+        cmp_func cmpfnc, void *data,
+        size_t start, size_t mid, size_t end) { 
+    
+    char* array = arrdata;
+    
+    size_t rightstart = mid + 1; 
+  
+    if (cmpfnc(array + mid*elemsize,
+            array + rightstart*elemsize, data) <= 0) {
+        /* already sorted */
+        return;
+    }
+  
+    /* we need memory for one element */
+    void *value = malloc(elemsize);
+    
+    while (start <= mid && rightstart <= end) { 
+        if (cmpfnc(array + start*elemsize,
+                array + rightstart*elemsize, data) <= 0) { 
+            start++; 
+        } else {
+            /* save the value from the right */
+            memcpy(value, array + rightstart*elemsize, elemsize);
+                        
+            /* shift all left elements one element to the right */
+            size_t shiftcount = rightstart-start;
+            void *startptr = array + start*elemsize;
+            void *dest = array + (start+1)*elemsize;
+            memmove(dest, startptr, shiftcount*elemsize);
+            
+            /* bring the first value from the right to the left */
+            memcpy(startptr, value, elemsize);
+  
+            start++; 
+            mid++; 
+            rightstart++; 
+        }
+    }
+    
+    /* free the temporary memory */
+    free(value);
+} 
+  
+static void ucx_mergesort_impl(void *arrdata, size_t elemsize,
+        cmp_func cmpfnc, void *data, size_t l, size_t r) { 
+    if (l < r) {
+        size_t m = l + (r - l) / 2; 
+  
+        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); 
+        ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); 
+        ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r);
+    } 
+}
+
+static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize,
+        cmp_func cmpfnc, void *data) {
+    
+    ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1);
+}
+
+#ifdef USE_UCX_QSORT_R
+struct cmpfnc_swapargs_info {
+    cmp_func func;
+    void *data;
+};
+
+static int cmp_func_swap_args(void *data, const void *x, const void *y) {
+    struct cmpfnc_swapargs_info* info = data;
+    return info->func(x, y, info->data);
+}
+
+static void ucx_qsort_r(void *array, size_t count, size_t elemsize,
+                    cmp_func cmpfnc, void *data) {
+    struct cmpfnc_swapargs_info info;
+    info.func = cmpfnc;
+    info.data = data;
+    qsort_r(array, count, elemsize, &info, cmp_func_swap_args);
+}
+#endif /* USE_UCX_QSORT_R */
+
+void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) {
+    ucx_array_sort_impl(array->data, array->size, array->elemsize,
+            cmpfnc, data);
+}
+
+void ucx_array_remove(UcxArray *array, size_t index) {
+    array->size--;
+    if (index < array->size) {
+        void* dest = ucx_array_at(array, index);
+        void* src = ucx_array_at(array, index+1);
+        memmove(dest, src, (array->size - index)*array->elemsize);
+    }
+}
+
+void ucx_array_remove_fast(UcxArray *array, size_t index) {
+    array->size--;
+    if (index < array->size) {       
+        void* dest = ucx_array_at(array, index);
+        void* src = ucx_array_at(array, array->size);
+        memcpy(dest, src, array->elemsize);
+    }
+}
+
+int ucx_array_shrink(UcxArray* array) {
+    void* newptr = alrealloc(array->allocator, array->data,
+                array->size*array->elemsize);
+    if (newptr) {
+        array->data = newptr;
+        array->capacity = array->size;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+int ucx_array_resize(UcxArray* array, size_t capacity) {
+    if (array->capacity >= capacity) {
+        void* newptr = alrealloc(array->allocator, array->data,
+                capacity*array->elemsize);
+        if (newptr) {
+            array->data = newptr;
+            array->capacity = capacity;
+            if (array->size > array->capacity) {
+                array->size = array->capacity;
+            }
+            return 0;
+        } else {
+            return 1;
+        }
+    } else {
+        return ucx_array_reserve(array, capacity);
+    }
+}
+
+int ucx_array_reserve(UcxArray* array, size_t capacity) {
+    if (array->capacity > capacity) {
+        return 0;
+    } else {
+        void* newptr = alrealloc(array->allocator, array->data,
+                capacity*array->elemsize);
+        if (newptr) {
+            array->data = newptr;
+            array->capacity = capacity;
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+}
+
+int ucx_array_grow(UcxArray* array, size_t count) {
+    return ucx_array_reserve(array, array->size+count);
+}
diff --git a/ucx/avl.c b/ucx/avl.c
new file mode 100644 (file)
index 0000000..7639b56
--- /dev/null
+++ b/ucx/avl.c
@@ -0,0 +1,373 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/avl.h"
+
+#include <limits.h>
+
+#define ptrcast(ptr) ((void*)(ptr))
+#define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree))
+#define alloc_node(al) (UcxAVLNode*) almalloc((al), sizeof(UcxAVLNode))
+
+static void ucx_avl_connect(UcxAVLTree *tree,
+        UcxAVLNode *node, UcxAVLNode *child, intptr_t nullkey) {
+    if (child) {
+        child->parent = node;
+    }
+    // if child is NULL, nullkey decides if left or right pointer is cleared
+    if (tree->cmpfunc(
+        ptrcast(child ? child->key : nullkey),
+        ptrcast(node->key), tree->userdata) > 0) {
+      node->right = child;
+    } else {
+      node->left = child;
+    }
+    size_t lh = node->left ? node->left->height : 0;
+    size_t rh = node->right ? node->right->height : 0;
+    node->height = 1 + (lh > rh ? lh : rh);
+}
+
+#define avlheight(node) ((node) ? (node)->height : 0)
+
+static UcxAVLNode* avl_rotright(UcxAVLTree *tree, UcxAVLNode *l0) {
+    UcxAVLNode *p = l0->parent;
+    UcxAVLNode *l1 = l0->left;
+    if (p) {
+        ucx_avl_connect(tree, p, l1, 0);
+    } else {
+        l1->parent = NULL;
+    }
+    ucx_avl_connect(tree, l0, l1->right, l1->key);
+    ucx_avl_connect(tree, l1, l0, 0);
+    return l1;
+}
+
+static UcxAVLNode* avl_rotleft(UcxAVLTree *tree, UcxAVLNode *l0) {
+    UcxAVLNode *p = l0->parent;
+    UcxAVLNode *l1 = l0->right;
+    if (p) {
+        ucx_avl_connect(tree, p, l1, 0);
+    } else {
+        l1->parent = NULL;
+    }
+    ucx_avl_connect(tree, l0, l1->left, l1->key);
+    ucx_avl_connect(tree, l1, l0, 0);
+    return l1;
+}
+
+static void ucx_avl_balance(UcxAVLTree *tree, UcxAVLNode *n) {
+    int lh = avlheight(n->left);
+    int rh = avlheight(n->right);
+    n->height = 1 + (lh > rh ? lh : rh);
+    
+    if (lh - rh == 2) {
+      UcxAVLNode *c = n->left;
+      if (avlheight(c->right) - avlheight(c->left) == 1) {
+        avl_rotleft(tree, c);
+      }
+      n = avl_rotright(tree, n);
+    } else if (rh - lh == 2) {  
+      UcxAVLNode *c = n->right;
+      if (avlheight(c->left) - avlheight(c->right) == 1) {
+        avl_rotright(tree, c);
+      }
+      n = avl_rotleft(tree, n);
+    }
+
+    if (n->parent) {
+      ucx_avl_balance(tree, n->parent);
+    } else {
+      tree->root = n;
+    }
+}
+
+UcxAVLTree *ucx_avl_new(cmp_func cmpfunc) {
+    return ucx_avl_new_a(cmpfunc, ucx_default_allocator());
+}
+
+UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator) {
+    UcxAVLTree* tree = alloc_tree(allocator);
+    if (tree) {
+        tree->allocator = allocator;
+        tree->cmpfunc = cmpfunc;
+        tree->root = NULL;
+        tree->userdata = NULL;
+    }
+    
+    return tree;
+}
+
+static void ucx_avl_free_node(UcxAllocator *al, UcxAVLNode *node) {
+    if (node) {
+        ucx_avl_free_node(al, node->left);
+        ucx_avl_free_node(al, node->right);
+        alfree(al, node);
+    }
+}
+
+void ucx_avl_free(UcxAVLTree *tree) {
+    UcxAllocator *al = tree->allocator;
+    ucx_avl_free_node(al, tree->root);
+    alfree(al, tree);
+}
+
+static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node,
+        ucx_destructor destr) {
+    if (node) {
+        ucx_avl_free_content_node(al, node->left, destr);
+        ucx_avl_free_content_node(al, node->right, destr);
+        if (destr) {
+            destr(node->value);
+        } else {
+            alfree(al, node->value);
+        }
+    }
+}
+
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) {
+    ucx_avl_free_content_node(tree->allocator, tree->root, destr);
+}
+
+UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) {
+    UcxAVLNode *n = tree->root;
+    int cmpresult;
+    while (n && (cmpresult = tree->cmpfunc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    return n;
+}
+
+void *ucx_avl_get(UcxAVLTree *tree, intptr_t key) {
+    UcxAVLNode *n = ucx_avl_get_node(tree, key);
+    return n ? n->value : NULL;
+}
+
+UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode) {
+    UcxAVLNode *n = tree->root;
+    UcxAVLNode *closest = NULL;
+
+    intmax_t cmpresult;
+    intmax_t closest_dist;
+    closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX;
+    
+    while (n && (cmpresult = dfnc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        if (mode == UCX_AVL_FIND_CLOSEST) {
+            intmax_t dist = cmpresult;
+            if (dist < 0) dist *= -1;
+            if (dist < closest_dist) {
+                closest_dist = dist;
+                closest = n;
+            }
+        } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) {
+            if (cmpresult > closest_dist) {
+                closest_dist = cmpresult;
+                closest = n;
+            }
+        } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) {
+            if (cmpresult < closest_dist) {
+                closest_dist = cmpresult;
+                closest = n;
+            }
+        }
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    return n ? n : closest;
+}
+
+void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode) {
+    UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode);
+    return n ? n->value : NULL;
+}
+
+int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) {
+    return ucx_avl_put_s(tree, key, value, NULL);
+}
+
+int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value,
+        void **oldvalue) {
+    if (tree->root) {
+        UcxAVLNode *n = tree->root;
+        int cmpresult;
+        while ((cmpresult = tree->cmpfunc(
+                ptrcast(key), ptrcast(n->key), tree->userdata))) {
+            UcxAVLNode *m = cmpresult > 0 ? n->right : n->left;
+            if (m) {
+                n = m;
+            } else {
+                break;
+            }
+        }
+
+        if (cmpresult) {
+            UcxAVLNode* e = alloc_node(tree->allocator);
+            if (e) {
+                e->key = key; e->value = value; e->height = 1;
+                e->parent = e->left = e->right = NULL;
+                ucx_avl_connect(tree, n, e, 0);
+                ucx_avl_balance(tree, n);
+                return 0;
+            } else {
+                return 1;
+            }
+        } else {
+            if (oldvalue) {
+                *oldvalue = n->value;
+            }
+            n->value = value;
+            return 0;
+        }
+    } else {
+        tree->root = alloc_node(tree->allocator);
+        if (tree->root) {
+            tree->root->key = key; tree->root->value = value;
+            tree->root->height = 1;
+            tree->root->parent = tree->root->left = tree->root->right = NULL;
+            
+            if (oldvalue) {
+                *oldvalue = NULL;
+            }
+            
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+}
+
+int ucx_avl_remove(UcxAVLTree *tree, intptr_t key) {
+    return ucx_avl_remove_s(tree, key, NULL, NULL);
+}
+    
+int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node) {
+    return ucx_avl_remove_s(tree, node->key, NULL, NULL);
+}
+
+int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
+        intptr_t *oldkey, void **oldvalue) {
+    
+    UcxAVLNode *n = tree->root;
+    int cmpresult;
+    while (n && (cmpresult = tree->cmpfunc(
+            ptrcast(key), ptrcast(n->key), tree->userdata))) {
+        n = cmpresult > 0 ? n->right : n->left;
+    }
+    if (n) {
+        if (oldkey) {
+            *oldkey = n->key;
+        }
+        if (oldvalue) {
+            *oldvalue = n->value;
+        }
+        
+        UcxAVLNode *p = n->parent;
+        if (n->left && n->right) {
+            UcxAVLNode *s = n->right;
+            while (s->left) {
+                s = s->left;
+            }
+            ucx_avl_connect(tree, s->parent, s->right, s->key);
+            n->key = s->key; n->value = s->value;
+            p = s->parent;
+            alfree(tree->allocator, s);
+        } else {
+            if (p) {
+                ucx_avl_connect(tree, p, n->right ? n->right:n->left, n->key);
+            } else {
+                tree->root = n->right ? n->right : n->left;
+                if (tree->root) {
+                    tree->root->parent = NULL;
+                }
+            }
+            alfree(tree->allocator, n);
+        }
+
+        if (p) {
+            ucx_avl_balance(tree, p);
+        }
+        
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+static size_t ucx_avl_countn(UcxAVLNode *node) {
+    if (node) {
+        return 1 + ucx_avl_countn(node->left) + ucx_avl_countn(node->right);
+    } else {
+        return 0;
+    }
+}
+
+size_t ucx_avl_count(UcxAVLTree *tree) {
+    return ucx_avl_countn(tree->root);
+}
+
+UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) {
+    if (node->left) {
+        UcxAVLNode* n = node->left;
+        while (n->right) {
+            n = n->right;
+        }
+        return n;
+    } else {
+        UcxAVLNode* n = node;
+        while (n->parent) {
+            if (n->parent->right == n) {
+                return n->parent;
+            } else {
+                n = n->parent;
+            }
+        }
+        return NULL;
+    }
+}
+
+UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) {
+    if (node->right) {
+        UcxAVLNode* n = node->right;
+        while (n->left) {
+            n = n->left;
+        }
+        return n;
+    } else {
+        UcxAVLNode* n = node;
+        while (n->parent) {
+            if (n->parent->left == n) {
+                return n->parent;
+            } else {
+                n = n->parent;
+            }
+        }
+        return NULL;
+    }
+}
diff --git a/ucx/buffer.c b/ucx/buffer.c
new file mode 100644 (file)
index 0000000..a6a8085
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/buffer.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags) {
+    UcxBuffer *buffer = (UcxBuffer*) malloc(sizeof(UcxBuffer));
+    if (buffer) {
+        buffer->flags = flags;
+        if (!space) {
+            buffer->space = (char*)malloc(capacity);
+            if (!buffer->space) {
+                free(buffer);
+                return NULL;
+            }
+            memset(buffer->space, 0, capacity);
+            buffer->flags |= UCX_BUFFER_AUTOFREE;
+        } else {
+            buffer->space = (char*)space;
+        }
+        buffer->capacity = capacity;
+        buffer->size = 0;
+
+        buffer->pos = 0;
+    }
+
+    return buffer;
+}
+
+void ucx_buffer_free(UcxBuffer *buffer) {
+    if ((buffer->flags & UCX_BUFFER_AUTOFREE) == UCX_BUFFER_AUTOFREE) {
+        free(buffer->space);
+    }
+    free(buffer);
+}
+
+UcxBuffer* ucx_buffer_extract(
+        UcxBuffer *src, size_t start, size_t length, int flags) {
+    if (src->size == 0 || length == 0 ||
+        ((size_t)-1) - start < length || start+length > src->capacity)
+    {
+        return NULL;
+    }
+
+    UcxBuffer *dst = (UcxBuffer*) malloc(sizeof(UcxBuffer));
+    if (dst) {
+        dst->space = (char*)malloc(length);
+        if (!dst->space) {
+            free(dst);
+            return NULL;
+        }
+        dst->capacity = length;
+        dst->size = length;
+        dst->flags = flags | UCX_BUFFER_AUTOFREE;
+        dst->pos = 0;
+        memcpy(dst->space, src->space+start, length);
+    }
+    return dst;
+}
+
+int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence) {
+    size_t npos;
+    switch (whence) {
+    case SEEK_CUR:
+        npos = buffer->pos;
+        break;
+    case SEEK_END:
+        npos = buffer->size;
+        break;
+    case SEEK_SET:
+        npos = 0;
+        break;
+    default:
+        return -1;
+    }
+
+    size_t opos = npos;
+    npos += offset;
+    
+    if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+        return -1;
+    }
+    
+    if (npos >= buffer->size) {
+        return -1;
+    } else {
+        buffer->pos = npos;
+        return 0;
+    }
+
+}
+
+int ucx_buffer_eof(UcxBuffer *buffer) {
+    return buffer->pos >= buffer->size;
+}
+
+int ucx_buffer_extend(UcxBuffer *buffer, size_t len) {
+    size_t newcap = buffer->capacity;
+    
+    if (buffer->capacity + len < buffer->capacity) {
+        return -1;
+    }
+    
+    while (buffer->capacity + len > newcap) {
+        newcap <<= 1;
+        if (newcap < buffer->capacity) {
+            return -1;
+        }
+    }
+    
+    char *newspace = (char*)realloc(buffer->space, newcap);
+    if (newspace) {
+        memset(newspace+buffer->size, 0, newcap-buffer->size);
+        buffer->space = newspace;
+        buffer->capacity = newcap;
+    } else {
+        return -1;
+    }
+    
+    return 0;
+}
+
+size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer) {
+    size_t len;
+    if(ucx_szmul(size, nitems, &len)) {
+        return 0;
+    }
+    size_t required = buffer->pos + len;
+    if (buffer->pos > required) {
+        return 0;
+    }
+    
+    if (required > buffer->capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if (ucx_buffer_extend(buffer, required - buffer->capacity)) {
+                return 0;
+            }
+        } else {
+            len = buffer->capacity - buffer->pos;
+            if (size > 1) {
+                len -= len%size;
+            }
+        }
+    }
+    
+    if (len == 0) {
+        return len;
+    }
+    
+    memcpy(buffer->space + buffer->pos, ptr, len);
+    buffer->pos += len;
+    if(buffer->pos > buffer->size) {
+        buffer->size = buffer->pos;
+    }
+    
+    return len / size;
+}
+
+size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer) {
+    size_t len;
+    if(ucx_szmul(size, nitems, &len)) {
+        return 0;
+    }
+    if (buffer->pos + len > buffer->size) {
+        len = buffer->size - buffer->pos;
+        if (size > 1) len -= len%size;
+    }
+    
+    if (len <= 0) {
+        return len;
+    }
+    
+    memcpy(ptr, buffer->space + buffer->pos, len);
+    buffer->pos += len;
+    
+    return len / size;
+}
+
+int ucx_buffer_putc(UcxBuffer *buffer, int c) {
+    if(buffer->pos >= buffer->capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if(ucx_buffer_extend(buffer, 1)) {
+                return EOF;
+            }
+        } else {
+            return EOF;
+        }
+    }
+    
+    c &= 0xFF;
+    buffer->space[buffer->pos] = (char) c;
+    buffer->pos++;
+    if(buffer->pos > buffer->size) {
+        buffer->size = buffer->pos;
+    }
+    return c;
+}
+
+int ucx_buffer_getc(UcxBuffer *buffer) {
+    if (ucx_buffer_eof(buffer)) {
+        return EOF;
+    } else {
+        int c = ((unsigned char*)buffer->space)[buffer->pos];
+        buffer->pos++;
+        return c;
+    }
+}
+
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) {
+    return ucx_buffer_write((const void*)str, 1, strlen(str), buffer);
+}
+
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) {
+    if (shift >= buffer->size) {
+        buffer->pos = buffer->size = 0;
+    } else {
+        memmove(buffer->space, buffer->space + shift, buffer->size - shift);
+        buffer->size -= shift;
+        
+        if (buffer->pos >= shift) {
+            buffer->pos -= shift;
+        } else {
+            buffer->pos = 0;
+        }
+    }
+    return 0;
+}
+
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) {
+    size_t req_capacity = buffer->size + shift;
+    size_t movebytes;
+    
+    // auto extend buffer, if required and enabled
+    if (buffer->capacity < req_capacity) {
+        if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) {
+            if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) {
+                return 1;
+            }
+            movebytes = buffer->size;
+        } else {
+            movebytes = buffer->capacity - shift;
+        }
+    } else {
+        movebytes = buffer->size;
+    }
+    
+    memmove(buffer->space + shift, buffer->space, movebytes);
+    buffer->size = shift+movebytes;
+    
+    buffer->pos += shift;
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+    
+    return 0;
+}
+
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) {
+    if (shift < 0) {
+        return ucx_buffer_shift_left(buffer, (size_t) (-shift));
+    } else if (shift > 0) {
+        return ucx_buffer_shift_right(buffer, (size_t) shift);
+    } else {
+        return 0;
+    }
+}
diff --git a/ucx/list.c b/ucx/list.c
new file mode 100644 (file)
index 0000000..293592c
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/list.h"
+
+UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) {
+    return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data);
+}
+
+UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l,
+        copy_func fnc, void *data) {
+    UcxList *ret = NULL;
+    while (l) {
+        if (fnc) {
+            ret = ucx_list_append_a(alloc, ret, fnc(l->data, data));
+        } else {
+            ret = ucx_list_append_a(alloc, ret, l->data);
+        }
+        l = l->next;
+    }
+    return ret;
+}
+
+int ucx_list_equals(const UcxList *l1, const UcxList *l2,
+        cmp_func fnc, void* data) {
+    if (l1 == l2) return 1;
+    
+    while (l1 != NULL && l2 != NULL) {
+        if (fnc == NULL) {
+            if (l1->data != l2->data) return 0;
+        } else {
+            if (fnc(l1->data, l2->data, data) != 0) return 0;
+        }
+        l1 = l1->next;
+        l2 = l2->next;
+    }
+    
+    return (l1 == NULL && l2 == NULL);
+}
+
+void ucx_list_free(UcxList *l) {
+    ucx_list_free_a(ucx_default_allocator(), l);
+}
+
+void ucx_list_free_a(UcxAllocator *alloc, UcxList *l) {
+    UcxList *e = l, *f;
+    while (e != NULL) {
+        f = e;
+        e = e->next;
+        alfree(alloc, f);
+    }
+}
+
+void ucx_list_free_content(UcxList* list, ucx_destructor destr) {
+    if (!destr) destr = free;
+    while (list != NULL) {
+        destr(list->data);
+        list = list->next;
+    }
+}
+
+UcxList *ucx_list_append(UcxList *l, void *data)  {
+    return ucx_list_append_a(ucx_default_allocator(), l, data);
+}
+
+UcxList *ucx_list_append_a(UcxAllocator *alloc, UcxList *l, void *data)  {
+    UcxList *nl = (UcxList*) almalloc(alloc, sizeof(UcxList));
+    if (!nl) {
+        return NULL;
+    }
+    
+    nl->data = data;
+    nl->next = NULL;
+    if (l) {
+        UcxList *t = ucx_list_last(l);
+        t->next = nl;
+        nl->prev = t;
+        return l;
+    } else {
+        nl->prev = NULL;
+        return nl;
+    }
+}
+
+UcxList *ucx_list_prepend(UcxList *l, void *data) {
+    return ucx_list_prepend_a(ucx_default_allocator(), l, data);
+}
+
+UcxList *ucx_list_prepend_a(UcxAllocator *alloc, UcxList *l, void *data) {
+    UcxList *nl = ucx_list_append_a(alloc, NULL, data);
+    if (!nl) {
+        return NULL;
+    }
+    l = ucx_list_first(l);
+    
+    if (l) {
+        nl->next = l;
+        l->prev = nl;
+    }
+    return nl;
+}
+
+UcxList *ucx_list_concat(UcxList *l1, UcxList *l2) {
+    if (l1) {
+        UcxList *last = ucx_list_last(l1);
+        last->next = l2;
+        if (l2) {
+            l2->prev = last;
+        }
+        return l1;
+    } else {
+        return l2;
+    }
+}
+
+UcxList *ucx_list_last(const UcxList *l) {
+    if (l == NULL) return NULL;
+    
+    const UcxList *e = l;
+    while (e->next != NULL) {
+        e = e->next;
+    }
+    return (UcxList*)e;
+}
+
+ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem) {
+    ssize_t index = 0;
+    while (list) {
+        if (list == elem) {
+            return index;
+        }
+        list = list->next;
+        index++;
+    }
+    return -1;
+}
+
+UcxList *ucx_list_get(const UcxList *l, size_t index) {
+    if (l == NULL) return NULL;
+
+    const UcxList *e = l;
+    while (e->next && index > 0) {
+        e = e->next;
+        index--;
+    }
+    
+    return (UcxList*)(index == 0 ? e : NULL);
+}
+
+ssize_t ucx_list_find(const UcxList *l, void *elem,
+        cmp_func fnc, void *cmpdata) {
+    ssize_t index = 0;
+    UCX_FOREACH(e, l) {
+        if (fnc) {
+            if (fnc(elem, e->data, cmpdata) == 0) {
+                return index;
+            }
+        } else {
+            if (elem == e->data) {
+                return index;
+            }
+        }
+        index++;
+    }
+    return -1;
+}
+
+int ucx_list_contains(const UcxList *l, void *elem,
+        cmp_func fnc, void *cmpdata) {
+    return ucx_list_find(l, elem, fnc, cmpdata) > -1;
+}
+
+size_t ucx_list_size(const UcxList *l) {
+    if (l == NULL) return 0;
+    
+    const UcxList *e = l;
+    size_t s = 1;
+    while (e->next != NULL) {
+        e = e->next;
+        s++;
+    }
+
+    return s;
+}
+
+static UcxList *ucx_list_sort_merge(size_t length,
+        UcxList* ls, UcxList* le, UcxList* re,
+        cmp_func fnc, void* data) {
+
+    UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length);
+    UcxList *rc, *lc;
+
+    lc = ls; rc = le;
+    size_t n = 0;
+    while (lc && lc != le && rc != re) {
+        if (fnc(lc->data, rc->data, data) <= 0) {
+            sorted[n] = lc;
+            lc = lc->next;
+        } else {
+            sorted[n] = rc;
+            rc = rc->next;
+        }
+        n++;
+    }
+    while (lc && lc != le) {
+        sorted[n] = lc;
+        lc = lc->next;
+        n++;
+    }
+    while (rc && rc != re) {
+        sorted[n] = rc;
+        rc = rc->next;
+        n++;
+    }
+
+    // Update pointer
+    sorted[0]->prev = NULL;
+    for (int i = 0 ; i < length-1 ; i++) {
+        sorted[i]->next = sorted[i+1];
+        sorted[i+1]->prev = sorted[i];
+    }
+    sorted[length-1]->next = NULL;
+
+    UcxList *ret = sorted[0];
+    free(sorted);
+    return ret;
+}
+
+UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) {
+    if (l == NULL) {
+        return NULL;
+    }
+
+    UcxList *lc;
+    size_t ln = 1;
+
+    UcxList *ls = l, *le, *re;
+    
+    // check how many elements are already sorted
+    lc = ls;
+    while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) {
+        lc = lc->next;
+        ln++;
+    }
+    le = lc->next;
+
+    if (le == NULL) {
+        return l; // this list is already sorted :)
+    } else {
+        UcxList *rc;
+        size_t rn = 1;
+        rc = le;
+        // skip already sorted elements
+        while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) {
+            rc = rc->next;
+            rn++;
+        }
+        re = rc->next;
+
+        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
+        UcxList *sorted = ucx_list_sort_merge(ln+rn,
+                ls, le, re,
+                fnc, data);
+        
+        // Something left? Sort it!
+        size_t remainder_length = ucx_list_size(re);
+        if (remainder_length > 0) {
+            UcxList *remainder = ucx_list_sort(re, fnc, data);
+
+            // merge sorted list with (also sorted) remainder
+            l = ucx_list_sort_merge(ln+rn+remainder_length,
+                    sorted, remainder, NULL, fnc, data);
+        } else {
+            // no remainder - we've got our sorted list
+            l = sorted;
+        }
+
+        return l;
+    }
+}
+
+UcxList *ucx_list_first(const UcxList *l) {
+    if (!l) {
+        return NULL;
+    }
+    
+    const UcxList *e = l;
+    while (e->prev) {
+        e = e->prev;
+    }
+    return (UcxList *)e;
+}
+
+UcxList *ucx_list_remove(UcxList *l, UcxList *e) {
+    return ucx_list_remove_a(ucx_default_allocator(), l, e);
+}
+    
+UcxList *ucx_list_remove_a(UcxAllocator *alloc, UcxList *l, UcxList *e) {
+    if (l == e) {
+        l = e->next;
+    }
+    
+    if (e->next) {
+        e->next->prev = e->prev;
+    }
+    
+    if (e->prev) {
+        e->prev->next = e->next;
+    }
+    
+    alfree(alloc, e);
+    return l;
+}
+
+
+static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata,
+        int op) {
+    
+    UcxList *res = NULL;
+    UcxList *cur = NULL;
+    const UcxList *src = left;
+    
+    do {
+        UCX_FOREACH(node, src) {
+            void* elem = node->data;
+            if (
+                (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) ||
+                (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) ||
+                (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) {
+                UcxList *nl = almalloc(allocator, sizeof(UcxList));
+                nl->prev = cur;
+                nl->next = NULL;
+                if (cpfnc) {
+                    nl->data = cpfnc(elem, cpdata);
+                } else {
+                    nl->data = elem;
+                }
+                if (cur != NULL)
+                    cur->next = nl;
+                cur = nl;
+                if (res == NULL)
+                    res = cur;
+            }
+        }
+        if (op == 0 && src == left)
+            src = right;
+        else
+            src = NULL;
+    } while (src != NULL);
+    
+    return res;
+}
+
+UcxList* ucx_list_union(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_union_a(ucx_default_allocator(),
+            left, right, cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_union_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 0);
+}
+
+UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_intersection_a(ucx_default_allocator(), left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 1);
+}
+
+UcxList* ucx_list_difference(UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    return ucx_list_difference_a(ucx_default_allocator(), left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata);
+}
+
+UcxList* ucx_list_difference_a(UcxAllocator *allocator,
+        UcxList const *left, UcxList const *right,
+        cmp_func cmpfnc, void* cmpdata,
+        copy_func cpfnc, void* cpdata) {
+    
+    return ucx_list_setoperation_a(allocator, left, right,
+            cmpfnc, cmpdata, cpfnc, cpdata, 2);
+}
diff --git a/ucx/logging.c b/ucx/logging.c
new file mode 100644 (file)
index 0000000..d6fdce0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/logging.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+
+UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask) {
+    UcxLogger *logger = (UcxLogger*) malloc(sizeof(UcxLogger));
+    if (logger != NULL) {
+        logger->stream = stream;
+        logger->writer = (write_func)fwrite;
+        logger->dateformat = (char*) "%F %T %z ";
+        logger->level = level;
+        logger->mask = mask;
+        logger->levels = ucx_map_new(8);
+        
+        unsigned int l;
+        l = UCX_LOGGER_ERROR;
+        ucx_map_int_put(logger->levels, l, (void*) "[ERROR]");
+        l = UCX_LOGGER_WARN;
+        ucx_map_int_put(logger->levels, l, (void*) "[WARNING]");
+        l = UCX_LOGGER_INFO;
+        ucx_map_int_put(logger->levels, l, (void*) "[INFO]");
+        l = UCX_LOGGER_DEBUG;
+        ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]");
+        l = UCX_LOGGER_TRACE;
+        ucx_map_int_put(logger->levels, l, (void*) "[TRACE]");
+    }
+
+    return logger;
+}
+
+void ucx_logger_free(UcxLogger *logger) {
+    ucx_map_free(logger->levels);
+    free(logger);
+}
+
+// estimated max. message length (documented)
+#define UCX_LOGGER_MSGMAX 4096
+
+void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
+        const unsigned int line, const char *format, ...) {
+    if (level <= logger->level) {
+        char msg[UCX_LOGGER_MSGMAX];
+        const char *text;
+        size_t k = 0;
+        size_t n;
+        
+        if ((logger->mask & UCX_LOGGER_LEVEL) > 0) {
+            text = (const char*) ucx_map_int_get(logger->levels, level);
+            if (!text) {
+                text = "[UNKNOWN]";
+            }
+            n = strlen(text);
+            n = n > 256 ? 256 : n;
+            memcpy(msg+k, text, n);
+            k += n;
+            msg[k++] = ' ';
+        }
+        if ((logger->mask & UCX_LOGGER_TIMESTAMP) > 0) {
+            time_t now = time(NULL);
+            k += strftime(msg+k, 128, logger->dateformat, localtime(&now));
+        }
+        if ((logger->mask & UCX_LOGGER_SOURCE) > 0) {
+            char *fpart = strrchr(file, '/');
+            if (fpart) file = fpart+1;
+            fpart = strrchr(file, '\\');
+            if (fpart) file = fpart+1;
+            n = strlen(file);
+            memcpy(msg+k, file, n);
+            k += n;
+            k += sprintf(msg+k, ":%u ", line);
+        }
+        
+        if (k > 0) {
+            msg[k++] = '-'; msg[k++] = ' ';
+        }
+        
+        va_list args;
+        va_start (args, format);
+        k += vsnprintf(msg+k, UCX_LOGGER_MSGMAX-k-1, format, args);
+        va_end (args);        
+        
+        msg[k++] = '\n';
+        
+        logger->writer(msg, 1, k, logger->stream);
+    }
+}
diff --git a/ucx/map.c b/ucx/map.c
new file mode 100644 (file)
index 0000000..ba7961d
--- /dev/null
+++ b/ucx/map.c
@@ -0,0 +1,402 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/map.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+UcxMap *ucx_map_new(size_t size) {
+    return ucx_map_new_a(NULL, size);
+}
+
+UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size) {
+    if(size == 0) {
+        size = 16;
+    }
+       
+    if(!allocator) {
+        allocator = ucx_default_allocator();
+    }
+    
+    UcxMap *map = (UcxMap*)almalloc(allocator, sizeof(UcxMap));
+    if (!map) {
+        return NULL;
+    }
+    
+    map->allocator = allocator;
+    map->map = (UcxMapElement**)alcalloc(
+            allocator, size, sizeof(UcxMapElement*));
+    if(map->map == NULL) {
+        alfree(allocator, map);
+        return NULL;
+    }
+    map->size = size;
+    map->count = 0;
+
+    return map;
+}
+
+static void ucx_map_free_elmlist_contents(UcxMap *map) {
+    for (size_t n = 0 ; n < map->size ; n++) {
+        UcxMapElement *elem = map->map[n];
+        if (elem != NULL) {
+            do {
+                UcxMapElement *next = elem->next;
+                alfree(map->allocator, elem->key.data);
+                alfree(map->allocator, elem);
+                elem = next;
+            } while (elem != NULL);
+        }
+    }
+}
+
+void ucx_map_free(UcxMap *map) {
+    ucx_map_free_elmlist_contents(map);
+    alfree(map->allocator, map->map);
+    alfree(map->allocator, map);
+}
+
+void ucx_map_free_content(UcxMap *map, ucx_destructor destr) {
+    UcxMapIterator iter = ucx_map_iterator(map);
+    void *val;
+    UCX_MAP_FOREACH(key, val, iter) {
+        if (destr) {
+            destr(val);
+        } else {
+            alfree(map->allocator, val);
+        }
+    }
+}
+
+void ucx_map_clear(UcxMap *map) {
+    if (map->count == 0) {
+        return; // nothing to do
+    }
+    ucx_map_free_elmlist_contents(map);
+    memset(map->map, 0, map->size*sizeof(UcxMapElement*));
+    map->count = 0;
+}
+
+int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) {
+    UcxMapIterator i = ucx_map_iterator(from);
+    void *value;
+    UCX_MAP_FOREACH(key, value, i) {
+        if (ucx_map_put(to, key, fnc ? fnc(value, data) : value)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) {
+    return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data);
+}
+
+UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
+        UcxMap const *map, copy_func fnc, void *data) {
+    size_t bs = (map->count * 5) >> 1;
+    UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size);
+    if (!newmap) {
+        return NULL;
+    }
+    ucx_map_copy(map, newmap, fnc, data);
+    return newmap;
+}
+
+int ucx_map_rehash(UcxMap *map) {
+    size_t load = (map->size * 3) >> 2;
+    if (map->count > load) {
+        UcxMap oldmap;
+        oldmap.map = map->map;
+        oldmap.size = map->size;
+        oldmap.count = map->count;
+        oldmap.allocator = map->allocator;
+        
+        map->size = (map->count * 5) >> 1;
+        map->map = (UcxMapElement**)alcalloc(
+                map->allocator, map->size, sizeof(UcxMapElement*));
+        if (!map->map) {
+            *map = oldmap;
+            return 1;
+        }
+        map->count = 0;
+        ucx_map_copy(&oldmap, map, NULL, NULL);
+        
+        /* free the UcxMapElement list of oldmap */
+        ucx_map_free_elmlist_contents(&oldmap);
+        alfree(map->allocator, oldmap.map);
+    }
+    return 0;
+}
+
+int ucx_map_put(UcxMap *map, UcxKey key, void *data) {
+    UcxAllocator *allocator = map->allocator;
+    
+    if (key.hash == 0) {
+        key.hash = ucx_hash((const char*)key.data, key.len);
+    }
+    
+    struct UcxMapKey mapkey;
+    mapkey.hash = key.hash;
+
+    size_t slot = mapkey.hash%map->size;
+    UcxMapElement *elm = map->map[slot];
+    UcxMapElement *prev = NULL;
+
+    while (elm && elm->key.hash < mapkey.hash) {
+        prev = elm;
+        elm = elm->next;
+    }
+    
+    if (!elm || elm->key.hash != mapkey.hash) {
+        UcxMapElement *e = (UcxMapElement*)almalloc(
+                allocator, sizeof(UcxMapElement));
+        if (!e) {
+            return -1;
+        }
+        e->key.data = NULL;
+        if (prev) {
+            prev->next = e;
+        } else {
+            map->map[slot] = e;
+        }
+        e->next = elm;
+        elm = e;
+    }
+    
+    if (!elm->key.data) {
+        void *kd = almalloc(allocator, key.len);
+        if (!kd) {
+            return -1;
+        }
+        memcpy(kd, key.data, key.len);
+        mapkey.data = kd;
+        mapkey.len = key.len;
+        elm->key = mapkey;
+        map->count++;
+    }
+    elm->data = data;
+
+    return 0;
+}
+
+static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) {
+    if(key.hash == 0) {
+        key.hash = ucx_hash((const char*)key.data, key.len);
+    }
+    
+    size_t slot = key.hash%map->size;
+    UcxMapElement *elm = map->map[slot];
+    UcxMapElement *pelm = NULL;
+    while (elm && elm->key.hash <= key.hash) {
+        if(elm->key.hash == key.hash) {
+            int n = (key.len > elm->key.len) ? elm->key.len : key.len;
+            if (memcmp(elm->key.data, key.data, n) == 0) {
+                void *data = elm->data;
+                if (remove) {
+                    if (pelm) {
+                        pelm->next = elm->next;
+                    } else {
+                        map->map[slot] = elm->next;
+                    }
+                    alfree(map->allocator, elm->key.data);
+                    alfree(map->allocator, elm);
+                    map->count--;
+                }
+
+                return data;
+            }
+        }
+        pelm = elm;
+        elm = pelm->next;
+    }
+
+    return NULL;
+}
+
+void *ucx_map_get(UcxMap const *map, UcxKey key) {
+    return ucx_map_get_and_remove((UcxMap *)map, key, 0);
+}
+
+void *ucx_map_remove(UcxMap *map, UcxKey key) {
+    return ucx_map_get_and_remove(map, key, 1);
+}
+
+UcxKey ucx_key(const void *data, size_t len) {
+    UcxKey key;
+    key.data = data;
+    key.len = len;
+    key.hash = ucx_hash((const char*)data, len);
+    return key;
+}
+
+
+int ucx_hash(const char *data, size_t len) {
+    /* murmur hash 2 */
+
+    int m = 0x5bd1e995;
+    int r = 24;
+
+    int h = 25 ^ len;
+
+    int i = 0;
+    while (len >= 4) {
+        int k = data[i + 0] & 0xFF;
+        k |= (data[i + 1] & 0xFF) << 8;
+        k |= (data[i + 2] & 0xFF) << 16;
+        k |= (data[i + 3] & 0xFF) << 24;
+
+        k *= m;
+        k ^= k >> r;
+        k *= m;
+
+        h *= m;
+        h ^= k;
+
+        i += 4;
+        len -= 4;
+    }
+
+    switch (len) {
+        case 3: h ^= (data[i + 2] & 0xFF) << 16;
+        /* no break */
+        case 2: h ^= (data[i + 1] & 0xFF) << 8;
+        /* no break */
+        case 1: h ^= (data[i + 0] & 0xFF); h *= m;
+        /* no break */
+    }
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    return h;
+}
+
+UcxMapIterator ucx_map_iterator(UcxMap const *map) {
+    UcxMapIterator i;
+    i.map = map;
+    i.cur = NULL;
+    i.index = 0;
+    return i;
+}
+
+int ucx_map_iter_next(UcxMapIterator *i, UcxKey *key, void **elm) {
+    UcxMapElement *e = i->cur;
+    
+    if (e) {
+        e = e->next;
+    } else {
+        e = i->map->map[0];
+    }
+    
+    while (i->index < i->map->size) {
+        if (e) {
+            if (e->data) {
+                i->cur = e;
+                *elm = e->data;
+                key->data = e->key.data;
+                key->hash = e->key.hash;
+                key->len = e->key.len;
+                return 1;
+            }
+
+            e = e->next;
+        } else {
+            i->index++;
+            
+            if (i->index < i->map->size) {
+                e = i->map->map[i->index];
+            }
+        }
+    }
+    
+    return 0;
+}
+
+UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
+                      copy_func cpfnc, void* cpdata) {
+    return ucx_map_union_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_union_a(UcxAllocator *allocator,
+                        const UcxMap *first, const UcxMap *second,
+                        copy_func cpfnc, void* cpdata) {
+    UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata);
+    ucx_map_copy(second, result, cpfnc, cpdata);
+    return result;
+}
+
+UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata) {
+    return ucx_map_intersection_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
+                               const UcxMap *first, const UcxMap *second,
+                               copy_func cpfnc, void* cpdata) {
+    UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ?
+            first->size : second->size);
+
+    UcxMapIterator iter = ucx_map_iterator(first);
+    void* value;
+    UCX_MAP_FOREACH(key, value, iter) {
+        if (ucx_map_get(second, key)) {
+            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
+        }
+    }
+
+    return result;
+}
+
+UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
+                           copy_func cpfnc, void* cpdata) {
+    return ucx_map_difference_a(ucx_default_allocator(),
+            first, second, cpfnc, cpdata);
+}
+
+UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
+                             const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata) {
+
+    UcxMap *result = ucx_map_new_a(allocator, first->size - second->count);
+
+    UcxMapIterator iter = ucx_map_iterator(first);
+    void* value;
+    UCX_MAP_FOREACH(key, value, iter) {
+        if (!ucx_map_get(second, key)) {
+            ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value);
+        }
+    }
+
+    ucx_map_rehash(result);
+    return result;
+}
\ No newline at end of file
diff --git a/ucx/mempool.c b/ucx/mempool.c
new file mode 100644 (file)
index 0000000..beedc31
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/mempool.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#ifdef __cplusplus
+#define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+
+/** Capsule for destructible memory chunks. */
+typedef struct {
+    /** The destructor for the memory chunk. */
+    ucx_destructor destructor;
+    /**
+     * First byte of the memory chunk.
+     * Note, that the address <code>&amp;c</code> is also the address
+     * of the whole memory chunk.
+     */
+    char c;
+} ucx_memchunk;
+
+/** Capsule for data and its destructor. */
+typedef struct {
+    /** The destructor for the data. */
+    ucx_destructor destructor;
+    /** A pointer to the data. */
+    void           *ptr;
+} ucx_regdestr;
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void ucx_mempool_shared_destr(void* ptr) {
+    ucx_regdestr *rd = (ucx_regdestr*)ptr;
+    rd->destructor(rd->ptr);
+}
+
+UcxMempool *ucx_mempool_new(size_t n) {
+    size_t poolsz;
+    if(ucx_szmul(n, sizeof(void*), &poolsz)) {
+        return NULL;
+    }
+    
+    UcxMempool *pool = (UcxMempool*)malloc(sizeof(UcxMempool));
+    if (!pool) {
+        return NULL;
+    }
+    
+    pool->data = (void**) malloc(poolsz);
+    if (pool->data == NULL) {
+        free(pool);
+        return NULL;
+    }
+    
+    pool->ndata = 0;
+    pool->size = n;
+    
+    UcxAllocator *allocator = (UcxAllocator*)malloc(sizeof(UcxAllocator));
+    if(!allocator) {
+        free(pool->data);
+        free(pool);
+        return NULL;
+    }
+    allocator->malloc = (ucx_allocator_malloc)ucx_mempool_malloc;
+    allocator->calloc = (ucx_allocator_calloc)ucx_mempool_calloc;
+    allocator->realloc = (ucx_allocator_realloc)ucx_mempool_realloc;
+    allocator->free = (ucx_allocator_free)ucx_mempool_free;
+    allocator->pool = pool;
+    pool->allocator = allocator;
+    
+    return pool;
+}
+
+int ucx_mempool_chcap(UcxMempool *pool, size_t newcap) {
+    if (newcap < pool->ndata) {
+        return 1;
+    }
+    
+    size_t newcapsz;
+    if(ucx_szmul(newcap, sizeof(void*), &newcapsz)) {
+        return 1;
+    }
+    
+    void **data = (void**) realloc(pool->data, newcapsz);
+    if (data) {
+        pool->data = data; 
+        pool->size = newcap;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+void *ucx_mempool_malloc(UcxMempool *pool, size_t n) {
+    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
+        return NULL;
+    }
+    
+    if (pool->ndata >= pool->size) {
+        size_t newcap = pool->size*2;
+        if (newcap < pool->size || ucx_mempool_chcap(pool, newcap)) {
+            return NULL;
+        }
+    }
+
+    void *p = malloc(sizeof(ucx_destructor) + n);
+    ucx_memchunk *mem = (ucx_memchunk*)p;
+    if (!mem) {
+        return NULL;
+    }
+
+    mem->destructor = NULL;
+    pool->data[pool->ndata] = mem;
+    pool->ndata++;
+
+    return &(mem->c);
+}
+
+void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) {
+    size_t msz;
+    if(ucx_szmul(nelem, elsize, &msz)) {
+        return NULL;
+    }
+    
+    void *ptr = ucx_mempool_malloc(pool, msz);
+    if (!ptr) {
+        return NULL;
+    }
+    memset(ptr, 0, nelem * elsize);
+    return ptr;
+}
+
+void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n) {
+    if(((size_t)-1) - sizeof(ucx_destructor) < n) {
+        return NULL;
+    }
+    
+    char *mem = ((char*)ptr) - sizeof(ucx_destructor);
+    char *newm = (char*) realloc(mem, n + sizeof(ucx_destructor));
+    if (!newm) {
+        return NULL;
+    }
+    if (mem != newm) {
+        for(size_t i=0 ; i < pool->ndata ; i++) {
+            if(pool->data[i] == mem) {
+                pool->data[i] = newm;
+                return newm + sizeof(ucx_destructor);
+            }
+        }
+        fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
+          (intptr_t)ptr, (intptr_t)pool);
+        abort();
+    } else {
+        return newm + sizeof(ucx_destructor);
+    }
+}
+
+void ucx_mempool_free(UcxMempool *pool, void *ptr) {
+    ucx_memchunk *chunk = (ucx_memchunk*)((char*)ptr-sizeof(ucx_destructor));
+    for(size_t i=0 ; i<pool->ndata ; i++) {
+        if(chunk == pool->data[i]) {
+            if(chunk->destructor != NULL) {
+                chunk->destructor(&(chunk->c));
+            }
+            free(chunk);
+            size_t last_index = pool->ndata - 1;
+            if(i != last_index) {
+                pool->data[i] = pool->data[last_index];
+                pool->data[last_index] = NULL;
+            }
+            pool->ndata--;
+            return;
+        }
+    }
+    fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n",
+            (intptr_t)ptr, (intptr_t)pool);
+    abort();
+}
+
+void ucx_mempool_destroy(UcxMempool *pool) {
+    ucx_memchunk *chunk;
+    for(size_t i=0 ; i<pool->ndata ; i++) {
+        chunk = (ucx_memchunk*) pool->data[i];
+        if(chunk) {
+            if(chunk->destructor) {
+                chunk->destructor(&(chunk->c));
+            }
+            free(chunk);
+        }
+    }
+    free(pool->data);
+    free(pool->allocator);
+    free(pool);
+}
+
+void ucx_mempool_set_destr(void *ptr, ucx_destructor func) {
+    *(ucx_destructor*)((char*)ptr-sizeof(ucx_destructor)) = func;
+}
+
+void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr) {
+    ucx_regdestr *rd = (ucx_regdestr*)ucx_mempool_malloc(
+            pool,
+            sizeof(ucx_regdestr));
+    rd->destructor = destr;
+    rd->ptr = ptr;
+    ucx_mempool_set_destr(rd, ucx_mempool_shared_destr);
+}
+
diff --git a/ucx/properties.c b/ucx/properties.c
new file mode 100644 (file)
index 0000000..1cb4de0
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/properties.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxProperties *ucx_properties_new() {
+    UcxProperties *parser = (UcxProperties*)malloc(
+            sizeof(UcxProperties));
+    if(!parser) {
+        return NULL;
+    }
+    
+    parser->buffer = NULL;
+    parser->buflen = 0;
+    parser->pos = 0;
+    parser->tmp = NULL;
+    parser->tmplen = 0;
+    parser->tmpcap = 0;
+    parser->error = 0;
+    parser->delimiter = '=';
+    parser->comment1 = '#';
+    parser->comment2 = 0;
+    parser->comment3 = 0;   
+    
+    return parser;
+}
+
+void ucx_properties_free(UcxProperties *parser) {
+    if(parser->tmp) {
+        free(parser->tmp);
+    }
+    free(parser);
+}
+
+void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) {
+    parser->buffer = buf;
+    parser->buflen = len;
+    parser->pos = 0;
+}
+
+static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) {
+    if(parser->tmpcap - parser->tmplen < len) {
+        size_t newcap = parser->tmpcap + len + 64;
+        parser->tmp = (char*)realloc(parser->tmp, newcap);
+        parser->tmpcap = newcap;
+    }
+    memcpy(parser->tmp + parser->tmplen, buf, len);
+    parser->tmplen += len;
+}
+
+int ucx_properties_next(UcxProperties *parser, sstr_t *name, sstr_t *value)  {   
+    if(parser->tmplen > 0) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        sstr_t str = sstrn(buf, len);
+        sstr_t nl = sstrchr(str, '\n');
+        if(nl.ptr) {
+            size_t newlen = (size_t)(nl.ptr - buf) + 1;
+            parser_tmp_append(parser, buf, newlen);
+            // the tmp buffer contains exactly one line now
+            
+            char *orig_buf = parser->buffer;
+            size_t orig_len = parser->buflen;
+            
+            parser->buffer = parser->tmp;
+            parser->buflen = parser->tmplen;
+            parser->pos = 0;    
+            parser->tmp = NULL;
+            parser->tmpcap = 0;
+            parser->tmplen = 0;
+            // run ucx_properties_next with the tmp buffer as main buffer
+            int ret = ucx_properties_next(parser, name, value);
+            
+            // restore original buffer
+            parser->tmp = parser->buffer;
+            parser->buffer = orig_buf;
+            parser->buflen = orig_len;
+            parser->pos = newlen;
+            
+            /*
+             * if ret == 0 the tmp buffer contained just space or a comment
+             * we parse again with the original buffer to get a name/value
+             * or a new tmp buffer
+             */
+            return ret ? ret : ucx_properties_next(parser, name, value);
+        } else {
+            parser_tmp_append(parser, buf, len);
+            return 0;
+        }
+    } else if(parser->tmp) {
+        free(parser->tmp);
+        parser->tmp = NULL;
+    }
+    
+    char comment1 = parser->comment1;
+    char comment2 = parser->comment2;
+    char comment3 = parser->comment3;
+    char delimiter = parser->delimiter;
+    
+    // get one line and parse it
+    while(parser->pos < parser->buflen) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        
+        /*
+         * First we check if we have at least one line. We also get indices of
+         * delimiter and comment chars
+         */
+        size_t delimiter_index = 0;
+        size_t comment_index = 0;
+        int has_comment = 0;
+
+        size_t i = 0;
+        char c = 0;
+        for(;i<len;i++) {
+            c = buf[i];
+            if(c == comment1 || c == comment2 || c == comment3) {
+                if(comment_index == 0) {
+                    comment_index = i;
+                    has_comment = 1;
+                }
+            } else if(c == delimiter) {
+                if(delimiter_index == 0 && !has_comment) {
+                    delimiter_index = i;
+                }
+            } else if(c == '\n') {
+                break;
+            }
+        }
+
+        if(c != '\n') {
+            // we don't have enough data for a line
+            // store remaining bytes in temporary buffer for next round
+            parser->tmpcap = len + 128;
+            parser->tmp = (char*)malloc(parser->tmpcap);
+            parser->tmplen = len;
+            memcpy(parser->tmp, buf, len);
+            return 0;
+        }
+        
+        sstr_t line = has_comment ? sstrn(buf, comment_index) : sstrn(buf, i);
+        // check line
+        if(delimiter_index == 0) {
+            line = sstrtrim(line);
+            if(line.length != 0) {
+                parser->error = 1;
+            }
+        } else {
+            sstr_t n = sstrn(buf, delimiter_index);
+            sstr_t v = sstrn(
+                    buf + delimiter_index + 1,
+                    line.length - delimiter_index - 1); 
+            n = sstrtrim(n);
+            v = sstrtrim(v);
+            if(n.length != 0 || v.length != 0) {
+                *name = n;
+                *value = v;
+                parser->pos += i + 1;
+                return 1;
+            } else {
+                parser->error = 1;
+            }
+        }
+        
+        parser->pos += i + 1;
+    }
+    
+    return 0;
+}
+
+int ucx_properties2map(UcxProperties *parser, UcxMap *map) {
+    sstr_t name;
+    sstr_t value;
+    while(ucx_properties_next(parser, &name, &value)) {
+        value = sstrdup_a(map->allocator, value);
+        if(!value.ptr) {
+            return 1;
+        }
+        if(ucx_map_sstr_put(map, name, value.ptr)) {
+            alfree(map->allocator, value.ptr);
+            return 1;
+        }
+    }
+    if (parser->error) {
+        return parser->error;
+    } else {
+        return 0;
+    }
+}
+
+// buffer size is documented - change doc, when you change bufsize!
+#define UCX_PROPLOAD_BUFSIZE  1024
+int ucx_properties_load(UcxMap *map, FILE *file) {
+    UcxProperties *parser = ucx_properties_new();
+    if(!(parser && map && file)) {
+        return 1;
+    }
+    
+    int error = 0;
+    size_t r;
+    char buf[UCX_PROPLOAD_BUFSIZE];
+    while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) {
+        ucx_properties_fill(parser, buf, r);
+        error = ucx_properties2map(parser, map);
+        if (error) {
+            break;
+        }
+    }
+    ucx_properties_free(parser);
+    return error;
+}
+
+int ucx_properties_store(UcxMap *map, FILE *file) {
+    UcxMapIterator iter = ucx_map_iterator(map);
+    void *v;
+    sstr_t value;
+    size_t written;
+
+    UCX_MAP_FOREACH(k, v, iter) {
+        value = sstr((char*)v);
+
+        written = 0;
+        written += fwrite(k.data, 1, k.len, file);
+        written += fwrite(" = ", 1, 3, file);
+        written += fwrite(value.ptr, 1, value.length, file);
+        written += fwrite("\n", 1, 1, file);
+
+        if (written != k.len + value.length + 4) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
diff --git a/ucx/stack.c b/ucx/stack.c
new file mode 100644 (file)
index 0000000..467233e
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/stack.h"
+
+#include <string.h>
+
+static size_t ucx_stack_align(size_t n) {
+    int align = n % sizeof(void*);
+    if (align) {
+        n += sizeof(void*) - align;
+    }
+    return n;
+}
+
+void ucx_stack_init(UcxStack *stack, char* space, size_t size) {
+    stack->size = size - size % sizeof(void*);
+    stack->space = space;
+    stack->top = NULL;
+    
+    stack->allocator.pool = stack;
+    stack->allocator.malloc = (ucx_allocator_malloc) ucx_stack_malloc;
+    stack->allocator.calloc = (ucx_allocator_calloc) ucx_stack_calloc;
+    stack->allocator.realloc = (ucx_allocator_realloc) ucx_stack_realloc;
+    stack->allocator.free = (ucx_allocator_free) ucx_stack_free;
+}
+
+void *ucx_stack_malloc(UcxStack *stack, size_t n) {
+
+    if (ucx_stack_avail(stack) < ucx_stack_align(n)) {
+        return NULL;
+    } else {
+        char *prev = stack->top;
+        if (stack->top) {
+            stack->top += ucx_stack_align(ucx_stack_topsize(stack));
+        } else {
+            stack->top = stack->space;
+        }
+        
+        ((struct ucx_stack_metadata*)stack->top)->prev = prev;
+        ((struct ucx_stack_metadata*)stack->top)->size = n;
+        stack->top += sizeof(struct ucx_stack_metadata);
+        
+        return stack->top;
+    }
+}
+
+void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize) {
+    void *mem = ucx_stack_malloc(stack, nelem*elsize);
+    memset(mem, 0, nelem*elsize);
+    return mem;
+}
+
+void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n) {
+    if (ptr == stack->top) {
+        if (stack->size - (stack->top - stack->space) < ucx_stack_align(n)) {
+            return NULL;
+        } else {
+            ((struct ucx_stack_metadata*)stack->top - 1)->size = n;
+            return ptr;
+        }
+    } else {
+        if (ucx_stack_align(((struct ucx_stack_metadata*)ptr - 1)->size) <
+                ucx_stack_align(n)) {
+            void *nptr = ucx_stack_malloc(stack, n);
+            if (nptr) {
+                memcpy(nptr, ptr, n);
+                ucx_stack_free(stack, ptr);
+                
+                return nptr;
+            } else {
+                return NULL;
+            }
+        } else {
+            ((struct ucx_stack_metadata*)ptr - 1)->size = n;
+            return ptr;
+        }
+    }
+}
+
+void ucx_stack_free(UcxStack *stack, void *ptr) {
+    if (ptr == stack->top) {
+        stack->top = ((struct ucx_stack_metadata*) stack->top - 1)->prev;
+    } else {
+        struct ucx_stack_metadata *next = (struct ucx_stack_metadata*)(
+            (char*)ptr +
+            ucx_stack_align(((struct ucx_stack_metadata*) ptr - 1)->size)
+        );
+        next->prev = ((struct ucx_stack_metadata*) ptr - 1)->prev;
+    }
+}
+
+void ucx_stack_popn(UcxStack *stack, void *dest, size_t n) {
+    if (ucx_stack_empty(stack)) {
+        return;
+    }
+    
+    if (dest) {
+        size_t len = ucx_stack_topsize(stack);
+        if (len > n) {
+            len = n;
+        }
+
+        memcpy(dest, stack->top, len);
+    }
+    
+    ucx_stack_free(stack, stack->top);
+}
+
+size_t ucx_stack_avail(UcxStack *stack) {
+    size_t avail = ((stack->top ? (stack->size
+                    - (stack->top - stack->space)
+                    - ucx_stack_align(ucx_stack_topsize(stack)))
+                    : stack->size));
+    
+    if (avail > sizeof(struct ucx_stack_metadata)) {
+        return avail - sizeof(struct ucx_stack_metadata);
+    } else {
+        return 0;
+    }
+}
+
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) {
+    void *space = ucx_stack_malloc(stack, n);
+    if (space) {
+        memcpy(space, data, n);
+    }
+    return space;
+}
+
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data) {
+    
+    // skip the memset by using malloc
+    void *space = ucx_stack_malloc(stack, nelem*elsize);
+    if (space) {
+        memcpy(space, data, nelem*elsize);
+    }
+    return space;
+}
diff --git a/ucx/string.c b/ucx/string.c
new file mode 100644 (file)
index 0000000..5ea54f3
--- /dev/null
@@ -0,0 +1,807 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/string.h"
+
+#include "ucx/allocator.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#ifndef _WIN32
+#include <strings.h> /* for strncasecmp() */
+#endif /* _WIN32 */
+
+sstr_t sstr(char *cstring) {
+    sstr_t string;
+    string.ptr = cstring;
+    string.length = strlen(cstring);
+    return string;
+}
+
+sstr_t sstrn(char *cstring, size_t length) {
+    sstr_t string;
+    string.ptr = cstring;
+    string.length = length;
+    return string;
+}
+
+scstr_t scstr(const char *cstring) {
+    scstr_t string;
+    string.ptr = cstring;
+    string.length = strlen(cstring);
+    return string;
+}
+
+scstr_t scstrn(const char *cstring, size_t length) {
+    scstr_t string;
+    string.ptr = cstring;
+    string.length = length;
+    return string;
+}
+
+
+size_t scstrnlen(size_t n, ...) {
+    if (n == 0) return 0;
+    
+    va_list ap;
+    va_start(ap, n);
+    
+    size_t size = 0;
+
+    for (size_t i = 0 ; i < n ; i++) {
+        scstr_t str = va_arg(ap, scstr_t);
+        if(SIZE_MAX - str.length < size) {
+            size = SIZE_MAX;
+            break;
+        }
+        size += str.length;
+    }
+    va_end(ap);
+
+    return size;
+}
+
+static sstr_t sstrvcat_a(
+        UcxAllocator *a,
+        size_t count,
+        scstr_t s1,
+        va_list ap) {
+    sstr_t str;
+    str.ptr = NULL;
+    str.length = 0;
+    if(count < 2) {
+        return str;
+    }
+    
+    scstr_t s2 = va_arg (ap, scstr_t);
+    
+    if(((size_t)-1) - s1.length < s2.length) {
+        return str;
+    }
+    
+    scstr_t *strings = (scstr_t*) calloc(count, sizeof(scstr_t));
+    if(!strings) {
+        return str;
+    }
+    
+    // get all args and overall length
+    strings[0] = s1;
+    strings[1] = s2;
+    size_t slen = s1.length + s2.length;
+    int error = 0;
+    for (size_t i=2;i<count;i++) {
+        scstr_t s = va_arg (ap, scstr_t);
+        strings[i] = s;
+        if(((size_t)-1) - s.length < slen) {
+            error = 1;
+            break;
+        }
+        slen += s.length;
+    }
+    if(error) {
+        free(strings);
+        return str;
+    }
+    
+    // create new string
+    str.ptr = (char*) almalloc(a, slen + 1);
+    str.length = slen;
+    if(!str.ptr) {
+        free(strings);
+        str.length = 0;
+        return str;
+    }
+    
+    // concatenate strings
+    size_t pos = 0;
+    for (size_t i=0;i<count;i++) {
+        scstr_t s = strings[i];
+        memcpy(str.ptr + pos, s.ptr, s.length);
+        pos += s.length;
+    }
+    
+    str.ptr[str.length] = '\0';
+    
+    free(strings);
+    
+    return str;
+}
+
+sstr_t scstrcat(size_t count, scstr_t s1, ...) {
+    va_list ap;
+    va_start(ap, s1);
+    sstr_t s = sstrvcat_a(ucx_default_allocator(), count, s1, ap);
+    va_end(ap);
+    return s;
+}
+
+sstr_t scstrcat_a(UcxAllocator *a, size_t count, scstr_t s1, ...) {
+    va_list ap;
+    va_start(ap, s1);
+    sstr_t s = sstrvcat_a(a, count, s1, ap);
+    va_end(ap);
+    return s;
+}
+
+static int ucx_substring(
+        size_t str_length,
+        size_t start,
+        size_t length,
+        size_t *newlen,
+        size_t *newpos)
+{
+    *newlen = 0;
+    *newpos = 0;
+    
+    if(start > str_length) {
+        return 0;
+    }
+    
+    if(length > str_length - start) {
+        length = str_length - start;
+    }
+    *newlen = length;
+    *newpos = start;
+    return 1;
+}
+
+sstr_t sstrsubs(sstr_t s, size_t start) {
+    return sstrsubsl (s, start, s.length-start);
+}
+
+sstr_t sstrsubsl(sstr_t s, size_t start, size_t length) {
+    size_t pos;
+    sstr_t ret = { NULL, 0 };
+    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
+        ret.ptr = s.ptr + pos;
+    }
+    return ret;
+}
+
+scstr_t scstrsubs(scstr_t string, size_t start) {
+    return scstrsubsl(string, start, string.length-start);
+}
+
+scstr_t scstrsubsl(scstr_t s, size_t start, size_t length) {
+    size_t pos;
+    scstr_t ret = { NULL, 0 };
+    if(ucx_substring(s.length, start, length, &ret.length, &pos)) {
+        ret.ptr = s.ptr + pos;
+    }
+    return ret;
+}
+
+
+static int ucx_strchr(const char *str, size_t length, int chr, size_t *pos) {
+    for(size_t i=0;i<length;i++) {
+        if(str[i] == chr) {
+            *pos = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int ucx_strrchr(const char *str, size_t length, int chr, size_t *pos) {
+    if(length > 0) {
+        for(size_t i=length ; i>0 ; i--) {
+            if(str[i-1] == chr) {
+                *pos = i-1;
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+sstr_t sstrchr(sstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
+        return sstrsubs(s, pos);
+    }
+    return sstrn(NULL, 0);
+}
+
+sstr_t sstrrchr(sstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
+        return sstrsubs(s, pos);
+    }
+    return sstrn(NULL, 0);
+}
+
+scstr_t scstrchr(scstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strchr(s.ptr, s.length, c, &pos)) {
+        return scstrsubs(s, pos);
+    }
+    return scstrn(NULL, 0);
+}
+
+scstr_t scstrrchr(scstr_t s, int c) {
+    size_t pos = 0;
+    if(ucx_strrchr(s.ptr, s.length, c, &pos)) {
+        return scstrsubs(s, pos);
+    }
+    return scstrn(NULL, 0);
+}
+
+#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \
+    ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index])
+
+#define ptable_w(useheap, ptable, index, src) do {\
+    if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\
+    else ((size_t*)ptable)[index] = src;\
+    } while (0);
+
+
+static const char* ucx_strstr(
+        const char *str,
+        size_t length,
+        const char *match,
+        size_t matchlen,
+        size_t *newlen)
+{
+    *newlen = length;
+    if (matchlen == 0) {
+        return str;
+    }
+    
+    const char *result = NULL;
+    size_t resultlen = 0;
+    
+    /*
+     * IMPORTANT:
+     * our prefix table contains the prefix length PLUS ONE
+     * this is our decision, because we want to use the full range of size_t
+     * the original algorithm needs a (-1) at one single place
+     * and we want to avoid that
+     */
+    
+    /* static prefix table */
+    static uint8_t s_prefix_table[256];
+    
+    /* check pattern length and use appropriate prefix table */
+    /* if the pattern exceeds static prefix table, allocate on the heap */
+    register int useheap = matchlen > 255;
+    register void* ptable = useheap ?
+        calloc(matchlen+1, sizeof(size_t)): s_prefix_table;
+    
+    /* keep counter in registers */
+    register size_t i, j;
+    
+    /* fill prefix table */
+    i = 0; j = 0;
+    ptable_w(useheap, ptable, i, j);
+    while (i < matchlen) {
+        while (j >= 1 && match[j-1] != match[i]) {
+            ptable_r(j, useheap, ptable, j-1);
+        }
+        i++; j++;
+        ptable_w(useheap, ptable, i, j);
+    }
+
+    /* search */
+    i = 0; j = 1;
+    while (i < length) {
+        while (j >= 1 && str[i] != match[j-1]) {
+            ptable_r(j, useheap, ptable, j-1);
+        }
+        i++; j++;
+        if (j-1 == matchlen) {
+            size_t start = i - matchlen;
+            result = str + start;
+            resultlen = length - start;
+            break;
+        }
+    }
+
+    /* if prefix table was allocated on the heap, free it */
+    if (ptable != s_prefix_table) {
+        free(ptable);
+    }
+    
+    *newlen = resultlen;
+    return result;
+}
+
+sstr_t scstrsstr(sstr_t string, scstr_t match) {
+    sstr_t result;
+    
+    size_t reslen;
+    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
+    if(!resstr) {
+        result.ptr = NULL;
+        result.length = 0;
+        return result;
+    }
+    
+    size_t pos = resstr - string.ptr;
+    result.ptr = string.ptr + pos;
+    result.length = reslen;
+    
+    return result;
+}
+
+scstr_t scstrscstr(scstr_t string, scstr_t match) {
+    scstr_t result;
+    
+    size_t reslen;
+    const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen);
+    if(!resstr) {
+        result.ptr = NULL;
+        result.length = 0;
+        return result;
+    }
+    
+    size_t pos = resstr - string.ptr;
+    result.ptr = string.ptr + pos;
+    result.length = reslen;
+    
+    return result;
+}
+
+#undef ptable_r
+#undef ptable_w
+
+sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) {
+    return scstrsplit_a(ucx_default_allocator(), s, d, n);
+}
+
+sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) {
+    if (s.length == 0 || d.length == 0) {
+        *n = -1;
+        return NULL;
+    }
+    
+    /* special cases: delimiter is at least as large as the string */
+    if (d.length >= s.length) {
+        /* exact match */
+        if (sstrcmp(s, d) == 0) {
+            *n = 0;
+            return NULL;
+        } else /* no match possible */ {
+            *n = 1;
+            sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t));
+            if(result) {
+                *result = sstrdup_a(allocator, s);
+            } else {
+                *n = -2;
+            }
+            return result;
+        }
+    }
+    
+    ssize_t nmax = *n;
+    size_t arrlen = 16;
+    sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t));
+
+    if (result) {
+        scstr_t curpos = s;
+        ssize_t j = 1;
+        while (1) {
+            scstr_t match;
+            /* optimize for one byte delimiters */
+            if (d.length == 1) {
+                match = curpos;
+                for (size_t i = 0 ; i < curpos.length ; i++) {
+                    if (curpos.ptr[i] == *(d.ptr)) {
+                        match.ptr = curpos.ptr + i;
+                        break;
+                    }
+                    match.length--;
+                }
+            } else {
+                match = scstrscstr(curpos, d);
+            }
+            if (match.length > 0) {
+                /* is this our last try? */
+                if (nmax == 0 || j < nmax) {
+                    /* copy the current string to the array */
+                    scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr);
+                    result[j-1] = sstrdup_a(allocator, item);
+                    size_t processed = item.length + d.length;
+                    curpos.ptr += processed;
+                    curpos.length -= processed;
+
+                    /* allocate memory for the next string */
+                    j++;
+                    if (j > arrlen) {
+                        arrlen *= 2;
+                        size_t reallocsz;
+                        sstr_t* reallocated = NULL;
+                        if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) {
+                            reallocated = (sstr_t*) alrealloc(
+                                    allocator, result, reallocsz);
+                        }
+                        if (reallocated) {
+                            result = reallocated;
+                        } else {
+                            for (ssize_t i = 0 ; i < j-1 ; i++) {
+                                alfree(allocator, result[i].ptr);
+                            }
+                            alfree(allocator, result);
+                            *n = -2;
+                            return NULL;
+                        }
+                    }
+                } else {
+                    /* nmax reached, copy the _full_ remaining string */
+                    result[j-1] = sstrdup_a(allocator, curpos);
+                    break;
+                }
+            } else {
+                /* no more matches, copy last string */
+                result[j-1] = sstrdup_a(allocator, curpos);
+                break;
+            }
+        }
+        *n = j;
+    } else {
+        *n = -2;
+    }
+
+    return result;
+}
+
+int scstrcmp(scstr_t s1, scstr_t s2) {
+    if (s1.length == s2.length) {
+        return memcmp(s1.ptr, s2.ptr, s1.length);
+    } else if (s1.length > s2.length) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+int scstrcasecmp(scstr_t s1, scstr_t s2) {
+    if (s1.length == s2.length) {
+#ifdef _WIN32
+        return _strnicmp(s1.ptr, s2.ptr, s1.length);
+#else
+        return strncasecmp(s1.ptr, s2.ptr, s1.length);
+#endif
+    } else if (s1.length > s2.length) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+sstr_t scstrdup(scstr_t s) {
+    return sstrdup_a(ucx_default_allocator(), s);
+}
+
+sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) {
+    sstr_t newstring;
+    newstring.ptr = (char*)almalloc(allocator, s.length + 1);
+    if (newstring.ptr) {
+        newstring.length = s.length;
+        newstring.ptr[newstring.length] = 0;
+        
+        memcpy(newstring.ptr, s.ptr, s.length);
+    } else {
+        newstring.length = 0;
+    }
+    
+    return newstring;
+}
+
+
+static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) {
+    const char *newptr = s;
+    size_t length = len;
+    
+    while(length > 0 && isspace(*newptr)) {
+        newptr++;
+        length--;
+    }
+    while(length > 0 && isspace(newptr[length-1])) {
+        length--;
+    }
+    
+    *newlen = length;
+    return newptr - s;
+}
+
+sstr_t sstrtrim(sstr_t string) {
+    sstr_t newstr;
+    newstr.ptr = string.ptr
+                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
+    return newstr;
+}
+
+scstr_t scstrtrim(scstr_t string) {
+    scstr_t newstr;
+    newstr.ptr = string.ptr
+                 + ucx_strtrim(string.ptr, string.length, &newstr.length);
+    return newstr;
+}
+
+int scstrprefix(scstr_t string, scstr_t prefix) {
+    if (string.length == 0) {
+        return prefix.length == 0;
+    }
+    if (prefix.length == 0) {
+        return 1;
+    }
+    
+    if (prefix.length > string.length) {
+        return 0;
+    } else {
+        return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+    }
+}
+
+int scstrsuffix(scstr_t string, scstr_t suffix) {
+    if (string.length == 0) {
+        return suffix.length == 0;
+    }
+    if (suffix.length == 0) {
+        return 1;
+    }
+    
+    if (suffix.length > string.length) {
+        return 0;
+    } else {
+        return memcmp(string.ptr+string.length-suffix.length,
+            suffix.ptr, suffix.length) == 0;
+    }
+}
+
+int scstrcaseprefix(scstr_t string, scstr_t prefix) {
+    if (string.length == 0) {
+        return prefix.length == 0;
+    }
+    if (prefix.length == 0) {
+        return 1;
+    }
+    
+    if (prefix.length > string.length) {
+        return 0;
+    } else {
+        scstr_t subs = scstrsubsl(string, 0, prefix.length);
+        return scstrcasecmp(subs, prefix) == 0;
+    }
+}
+
+int scstrcasesuffix(scstr_t string, scstr_t suffix) {
+    if (string.length == 0) {
+        return suffix.length == 0;
+    }
+    if (suffix.length == 0) {
+        return 1;
+    }
+    
+    if (suffix.length > string.length) {
+        return 0;
+    } else {
+        scstr_t subs = scstrsubs(string, string.length-suffix.length);
+        return scstrcasecmp(subs, suffix) == 0;
+    }
+}
+
+sstr_t scstrlower(scstr_t string) {
+    sstr_t ret = sstrdup(string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = tolower(ret.ptr[i]);
+    }
+    return ret;
+}
+
+sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) {
+    sstr_t ret = sstrdup_a(allocator, string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = tolower(ret.ptr[i]);
+    }
+    return ret;
+}
+
+sstr_t scstrupper(scstr_t string) {
+    sstr_t ret = sstrdup(string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = toupper(ret.ptr[i]);
+    }
+    return ret;
+}
+
+sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) {
+    sstr_t ret = sstrdup_a(allocator, string);
+    for (size_t i = 0; i < ret.length ; i++) {
+        ret.ptr[i] = toupper(ret.ptr[i]);
+    }
+    return ret;
+}
+
+#define REPLACE_INDEX_BUFFER_MAX 100
+
+struct scstrreplace_ibuf {
+    size_t* buf;
+    unsigned int len; /* small indices */
+    struct scstrreplace_ibuf* next;
+};
+
+static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) {
+    while (buf) {
+        struct scstrreplace_ibuf *next = buf->next;
+        free(buf->buf);
+        free(buf);
+        buf = next;
+    }
+}
+
+sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
+                     scstr_t pattern, scstr_t replacement, size_t replmax) {
+
+    if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+        return sstrdup(str);
+
+    /* Compute expected buffer length */
+    size_t ibufmax = str.length / pattern.length;
+    size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
+    if (ibuflen > REPLACE_INDEX_BUFFER_MAX) {
+        ibuflen = REPLACE_INDEX_BUFFER_MAX;
+    }
+
+    /* Allocate first index buffer */
+    struct scstrreplace_ibuf *firstbuf, *curbuf;
+    firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf));
+    if (!firstbuf) return sstrn(NULL, 0);
+    firstbuf->buf = calloc(ibuflen, sizeof(size_t));
+    if (!firstbuf->buf) {
+        free(firstbuf);
+        return sstrn(NULL, 0);
+    }
+
+    /* Search occurrences */
+    scstr_t searchstr = str;
+    size_t found = 0;
+    do {
+        scstr_t match = scstrscstr(searchstr, pattern);
+        if (match.length > 0) {
+            /* Allocate next buffer in chain, if required */
+            if (curbuf->len == ibuflen) {
+                struct scstrreplace_ibuf *nextbuf =
+                        calloc(1, sizeof(struct scstrreplace_ibuf));
+                if (!nextbuf) {
+                    scstrrepl_free_ibuf(firstbuf);
+                    return sstrn(NULL, 0);
+                }
+                nextbuf->buf = calloc(ibuflen, sizeof(size_t));
+                if (!nextbuf->buf) {
+                    free(nextbuf);
+                    scstrrepl_free_ibuf(firstbuf);
+                    return sstrn(NULL, 0);
+                }
+                curbuf->next = nextbuf;
+                curbuf = nextbuf;
+            }
+
+            /* Record match index */
+            found++;
+            size_t idx = match.ptr - str.ptr;
+            curbuf->buf[curbuf->len++] = idx;
+            searchstr.ptr = match.ptr + pattern.length;
+            searchstr.length = str.length - idx - pattern.length;
+        } else {
+            break;
+        }
+    } while (searchstr.length > 0 && found < replmax);
+
+    /* Allocate result string */
+    sstr_t result;
+    {
+        ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
+        size_t rcount = 0;
+        curbuf = firstbuf;
+        do {
+            rcount += curbuf->len;
+            curbuf = curbuf->next;
+        } while (curbuf);
+        result.length = str.length + rcount * adjlen;
+        result.ptr = almalloc(allocator, result.length);
+        if (!result.ptr) {
+            scstrrepl_free_ibuf(firstbuf);
+            return sstrn(NULL, 0);
+        }
+    }
+
+    /* Build result string */
+    curbuf = firstbuf;
+    size_t srcidx = 0;
+    char* destptr = result.ptr;
+    do {
+        for (size_t i = 0; i < curbuf->len; i++) {
+            /* Copy source part up to next match*/
+            size_t idx = curbuf->buf[i];
+            size_t srclen = idx - srcidx;
+            if (srclen > 0) {
+                memcpy(destptr, str.ptr+srcidx, srclen);
+                destptr += srclen;
+                srcidx += srclen;
+            }
+
+            /* Copy the replacement and skip the source pattern */
+            srcidx += pattern.length;
+            memcpy(destptr, replacement.ptr, replacement.length);
+            destptr += replacement.length;
+        }
+        curbuf = curbuf->next;
+    } while (curbuf);
+    memcpy(destptr, str.ptr+srcidx, str.length-srcidx);
+
+    /* Free index buffer */
+    scstrrepl_free_ibuf(firstbuf);
+
+    return result;
+}
+
+sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
+        scstr_t replacement, size_t replmax) {
+    return scstrreplacen_a(ucx_default_allocator(),
+            str, pattern, replacement, replmax);
+}
+
+
+// type adjustment functions
+scstr_t ucx_sc2sc(scstr_t str) {
+    return str;
+}
+scstr_t ucx_ss2sc(sstr_t str) {
+    scstr_t cs;
+    cs.ptr = str.ptr;
+    cs.length = str.length;
+    return cs;
+}
+scstr_t ucx_ss2c_s(scstr_t c) {
+    return c;
+}
diff --git a/ucx/test.c b/ucx/test.c
new file mode 100644 (file)
index 0000000..20b80b4
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/test.h"
+
+UcxTestSuite* ucx_test_suite_new() {
+    UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite));
+    if (suite != NULL) {
+        suite->success = 0;
+        suite->failure = 0;
+        suite->tests = NULL;
+    }
+
+    return suite;
+}
+
+void ucx_test_suite_free(UcxTestSuite* suite) {
+    UcxTestList *l = suite->tests;
+    while (l != NULL) {
+        UcxTestList *e = l;
+        l = l->next;
+        free(e);
+    }
+    free(suite);
+}
+
+int ucx_test_register(UcxTestSuite* suite, UcxTest test) {
+    if (suite->tests) {
+        UcxTestList *newelem = (UcxTestList*) malloc(sizeof(UcxTestList));
+        if (newelem) {
+            newelem->test = test;
+            newelem->next = NULL;
+            
+            UcxTestList *last = suite->tests;
+            while (last->next) {
+                last = last->next;
+            }
+            last->next = newelem;
+            
+            return EXIT_SUCCESS;
+        } else {
+            return EXIT_FAILURE;
+        }
+    } else {
+        suite->tests = (UcxTestList*) malloc(sizeof(UcxTestList));
+        if (suite->tests) {
+            suite->tests->test = test;
+            suite->tests->next = NULL;
+            
+            return EXIT_SUCCESS;
+        } else {
+            return EXIT_FAILURE;
+        }
+    }
+}
+
+void ucx_test_run(UcxTestSuite* suite, FILE* output) {
+    suite->success = 0;
+    suite->failure = 0;
+    for (UcxTestList* elem = suite->tests ; elem ; elem = elem->next) {
+        elem->test(suite, output);
+    }
+    fwrite("\nAll test completed.\n", 1, 21, output);
+    fprintf(output, "  Total:   %u\n  Success: %u\n  Failure: %u\n",
+            suite->success+suite->failure, suite->success, suite->failure);
+}
diff --git a/ucx/ucx.c b/ucx/ucx.c
new file mode 100644 (file)
index 0000000..923330d
--- /dev/null
+++ b/ucx/ucx.c
@@ -0,0 +1,62 @@
+/**
+ * @mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">
+ * https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ * 
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">
+ * https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
+ * <a href="https://develop.uap-core.de/hg/ucx">
+ * https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ * 
+ * <h2>LICENCE</h2>
+ * 
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/ucx.h"
+
+int ucx_szmul_impl(size_t a, size_t b, size_t *result) {
+    if(a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    size_t r = a * b;
+    if(r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
+
diff --git a/ucx/ucx/allocator.h b/ucx/ucx/allocator.h
new file mode 100644 (file)
index 0000000..2159272
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Allocator for custom memory management.
+ * 
+ * A UCX allocator consists of a pointer to the memory area / pool and four
+ * function pointers to memory management functions operating on this memory
+ * area / pool. These functions shall behave equivalent to the standard libc
+ * functions <code>malloc(), calloc(), realloc()</code> and <code>free()</code>.
+ * 
+ * The signature of the memory management functions is based on the signature
+ * of the respective libc function but each of them takes the pointer to the
+ * memory area / pool as first argument.
+ * 
+ * As the pointer to the memory area / pool can be arbitrarily chosen, any data
+ * can be provided to the memory management functions. A UcxMempool is just
+ * one example.
+ * 
+ * @see mempool.h
+ * @see UcxMap
+ * 
+ * @file   allocator.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define        UCX_ALLOCATOR_H
+
+#include "ucx.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A function pointer to the allocators <code>malloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_malloc)(void *pool, size_t n);
+
+/**
+ * A function pointer to the allocators <code>calloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size);
+
+/**
+ * A function pointer to the allocators <code>realloc()</code> function.
+ * @see UcxAllocator
+ */
+typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n);
+
+/**
+ * A function pointer to the allocators <code>free()</code> function.
+ * @see UcxAllocator
+ */
+typedef void(*ucx_allocator_free)(void *pool, void *data);
+
+/**
+ * UCX allocator data structure containing memory management functions.
+ */
+typedef struct {
+    /** Pointer to an area of memory or a complex memory pool.
+     * This pointer will be passed to any memory management function as first
+     * argument.
+     */
+    void *pool;
+    /**
+     * The <code>malloc()</code> function for this allocator.
+     */
+    ucx_allocator_malloc  malloc;
+    /**
+     * The <code>calloc()</code> function for this allocator.
+     */
+    ucx_allocator_calloc  calloc;
+    /**
+     * The <code>realloc()</code> function for this allocator.
+     */
+    ucx_allocator_realloc realloc;
+    /**
+     * The <code>free()</code> function for this allocator.
+     */
+    ucx_allocator_free    free;
+} UcxAllocator;
+
+/**
+ * Returns a pointer to the default allocator.
+ * 
+ * The default allocator contains wrappers to the standard libc memory
+ * management functions. Use this function to get a pointer to a globally
+ * available allocator. You may also define an own UcxAllocator by assigning
+ * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable
+ * to any function that takes a UcxAllocator as argument. Note that using
+ * this function is the recommended way of passing a default allocator, thus
+ * it never runs out of scope.
+ * 
+ * @return a pointer to the default allocator
+ * 
+ * @see UCX_ALLOCATOR_DEFAULT
+ */
+UcxAllocator *ucx_default_allocator();
+
+/**
+ * A wrapper for the standard libc <code>malloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param n argument passed to <code>malloc()</code>
+ * @return return value of <code>malloc()</code>
+ */
+void *ucx_default_malloc(void *ignore, size_t n);
+/**
+ * A wrapper for the standard libc <code>calloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param n argument passed to <code>calloc()</code>
+ * @param size  argument passed to <code>calloc()</code>
+ * @return return value of <code>calloc()</code>
+ */
+void *ucx_default_calloc(void *ignore, size_t n, size_t size);
+/**
+ * A wrapper for the standard libc <code>realloc()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param data argumend passed to <code>realloc()</code>
+ * @param n argument passed to <code>realloc()</code>
+ * @return return value of <code>realloc()</code>
+ */
+void *ucx_default_realloc(void *ignore, void *data, size_t n);
+/**
+ * A wrapper for the standard libc <code>free()</code> function.
+ * @param ignore ignored (may be used by allocators for pooled memory)
+ * @param data argument passed to <code>free()</code>
+ */
+void ucx_default_free(void *ignore, void *data);
+
+/**
+ * Shorthand for calling an allocators malloc function.
+ * @param allocator the allocator to use
+ * @param n size of space to allocate
+ * @return a pointer to the allocated memory area
+ */
+#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n))
+
+/**
+ * Shorthand for calling an allocators calloc function.
+ * @param allocator the allocator to use
+ * @param n the count of elements the space should be allocated for
+ * @param size the size of each element
+ * @return a pointer to the allocated memory area
+ */
+#define alcalloc(allocator, n, size) \
+        ((allocator)->calloc((allocator)->pool, n, size))
+
+/**
+ * Shorthand for calling an allocators realloc function.
+ * @param allocator the allocator to use
+ * @param ptr the pointer to the memory area that shall be reallocated
+ * @param n the new size of the allocated memory area
+ * @return a pointer to the reallocated memory area
+ */
+#define alrealloc(allocator, ptr, n) \
+        ((allocator)->realloc((allocator)->pool, ptr, n))
+
+/**
+ * Shorthand for calling an allocators free function.
+ * @param allocator the allocator to use
+ * @param ptr the pointer to the memory area that shall be freed
+ */
+#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr))
+
+/**
+ * Convenient macro for a default allocator <code>struct</code> definition.
+ */
+#define UCX_ALLOCATOR_DEFAULT {NULL, \
+        ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \
+        ucx_default_free }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_ALLOCATOR_H */
+
diff --git a/ucx/ucx/array.h b/ucx/ucx/array.h
new file mode 100644 (file)
index 0000000..5b02ebf
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Dynamically allocated array implementation.
+ * 
+ * @file   array.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_ARRAY_H
+#define        UCX_ARRAY_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX array type.
+ */
+typedef struct {
+    /**
+     * The current capacity of the array.
+     */
+    size_t capacity;
+    /**
+     * The actual number of elements in the array.
+     */
+    size_t size;
+    /**
+     * The size of an individual element in bytes.
+     */
+    size_t elemsize;
+    /**
+     * A pointer to the data.
+     */
+    void* data;
+    /**
+     * The allocator used for the data.
+     */
+    UcxAllocator* allocator;
+} UcxArray;
+
+/**
+ * Sets an element in an arbitrary user defined array.
+ * The data is copied from the specified data location.
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated and
+ * the possibly new pointer is stored in the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>.
+ *  
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param elmsize the size of each element
+ * @param idx the index of the element to set
+ * @param data a pointer to the element data
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+#define ucx_array_util_set(array, capacity, elmsize, idx, data) \
+    ucx_array_util_set_a(ucx_default_allocator(), (void**)(array), capacity, \
+                         elmsize, idx, data)
+
+/**
+ * Sets an element in an arbitrary user defined array.
+ * The data is copied from the specified data location.
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated
+ * using the specified allocator and the possibly new pointer is stored in
+ * the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>. 
+ * 
+ * @param alloc the allocator that shall be used to reallocate the array
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param elmsize the size of each element
+ * @param idx the index of the element to set
+ * @param data a pointer to the element data
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t elmsize, size_t idx, void* data);
+
+/**
+ * Stores a pointer in an arbitrary user defined array.
+ * The element size of the array must be sizeof(void*).
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated and
+ * the possibly new pointer is stored in the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>.
+ *  
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param idx the index of the element to set
+ * @param ptr the pointer to store
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+#define ucx_array_util_setptr(array, capacity, idx, ptr) \
+    ucx_array_util_setptr_a(ucx_default_allocator(), (void**)(array), \
+                            capacity, idx, ptr)
+
+/**
+ * Stores a pointer in an arbitrary user defined array.
+ * The element size of the array must be sizeof(void*).
+ * 
+ * If the capacity is insufficient, the array is automatically reallocated
+ * using the specified allocator and the possibly new pointer is stored in
+ * the <code>array</code> argument.
+ * 
+ * On reallocation the capacity of the array is doubled until it is sufficient.
+ * The new capacity is stored back to <code>capacity</code>. 
+ * 
+ * @param alloc the allocator that shall be used to reallocate the array
+ * @param array a pointer to location of the array pointer
+ * @param capacity a pointer to the capacity
+ * @param idx the index of the element to set
+ * @param ptr the pointer to store
+ * @return zero on success or non-zero on error (errno will be set)
+ */
+int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity,
+    size_t idx, void* ptr);
+
+
+/**
+ * Creates a new UCX array with the given capacity and element size.
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @return a pointer to a new UCX array structure
+ */
+UcxArray* ucx_array_new(size_t capacity, size_t elemsize);
+
+/**
+ * Creates a new UCX array using the specified allocator.
+ * 
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @param allocator the allocator to use
+ * @return a pointer to new UCX array structure
+ */
+UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize,
+        UcxAllocator* allocator);
+
+/**
+ * Initializes a UCX array structure with the given capacity and element size.
+ * The structure must be uninitialized as the data pointer will be overwritten.
+ * 
+ * @param array the structure to initialize
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ */
+void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize);
+
+/**
+ * Initializes a UCX array structure using the specified allocator.
+ * The structure must be uninitialized as the data pointer will be overwritten.
+ * 
+ * @param array the structure to initialize
+ * @param capacity the initial capacity
+ * @param elemsize the element size
+ * @param allocator the allocator to use
+ */
+void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize,
+        UcxAllocator* allocator);
+
+/**
+ * Creates an shallow copy of an array.
+ * 
+ * This function clones the specified array by using memcpy().
+ * If the destination capacity is insufficient, an automatic reallocation is
+ * attempted.
+ * 
+ * Note: if the destination array is uninitialized, the behavior is undefined.
+ * 
+ * @param dest the array to copy to
+ * @param src the array to copy from
+ * @return zero on success, non-zero on reallocation failure.
+ */
+int ucx_array_clone(UcxArray* dest, UcxArray const* src);
+
+
+/**
+ * Compares two UCX arrays element-wise by using a compare function.
+ *
+ * Elements of the two specified arrays are compared by using the specified
+ * compare function and the additional data. The type and content of this
+ * additional data depends on the cmp_func() used.
+ * 
+ * This function always returns zero, if the element sizes of the arrays do
+ * not match and performs no comparisons in this case.
+ * 
+ * @param array1 the first array
+ * @param array2 the second array
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the two arrays equal element-wise, 0 otherwise
+ */
+int ucx_array_equals(UcxArray const *array1, UcxArray const *array2,
+        cmp_func cmpfnc, void* data);
+
+/**
+ * Destroys the array.
+ * 
+ * The data is freed and both capacity and count are reset to zero.
+ * If the array structure itself has been dynamically allocated, it has to be
+ * freed separately.
+ * 
+ * @param array the array to destroy
+ */
+void ucx_array_destroy(UcxArray *array);
+
+/**
+ * Destroys and frees the array.
+ * 
+ * @param array the array to free
+ */
+void ucx_array_free(UcxArray *array);
+
+/**
+ * Inserts elements at the end of the array.
+ * 
+ * This is an O(1) operation.
+ * The array will automatically grow, if the capacity is exceeded.
+ * If a pointer to data is provided, the data is copied into the array with
+ * memcpy(). Otherwise the new elements are completely zeroed.
+ * 
+ * @param array a pointer the array where to append the data
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, zeroed elements are appended)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_set_from()
+ * @see ucx_array_append()
+ */
+int ucx_array_append_from(UcxArray *array, void *data, size_t count);
+
+
+/**
+ * Inserts elements at the beginning of the array.
+ * 
+ * This is an expensive operation, because the contents must be moved.
+ * If there is no particular reason to prepend data, you should use
+ * ucx_array_append_from() instead.
+ * 
+ * @param array a pointer the array where to prepend the data
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, zeroed elements are inserted)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_append_from()
+ * @see ucx_array_set_from()
+ * @see ucx_array_prepend()
+ */
+int ucx_array_prepend_from(UcxArray *array, void *data, size_t count);
+
+
+/**
+ * Sets elements starting at the specified index.
+ * 
+ * If the any index is out of bounds, the array automatically grows.
+ * The pointer to the data may be NULL, in which case the elements are zeroed. 
+ * 
+ * @param array a pointer the array where to set the data
+ * @param index the index of the element to set
+ * @param data a pointer to the data to insert (may be <code>NULL</code>)
+ * @param count number of elements to copy from data (if data is
+ * <code>NULL</code>, the memory in the array is zeroed)
+ * @return zero on success, non-zero if a reallocation was necessary but failed
+ * @see ucx_array_append_from()
+ * @see ucx_array_set()
+ */
+int ucx_array_set_from(UcxArray *array, size_t index, void *data, size_t count);
+
+/**
+ * Concatenates two arrays.
+ * 
+ * The contents of the second array are appended to the first array in one
+ * single operation. The second array is otherwise left untouched.
+ * 
+ * The first array may grow automatically. If this fails, both arrays remain
+ * unmodified.
+ * 
+ * @param array1 first array
+ * @param array2 second array
+ * @return zero on success, non-zero if reallocation was necessary but failed 
+ * or the element size does not match
+ */
+int ucx_array_concat(UcxArray *array1, const UcxArray *array2);
+
+/**
+ * Returns a pointer to the array element at the specified index.
+ * 
+ * @param array the array to retrieve the element from
+ * @param index index of the element to return
+ * @return a pointer to the element at the specified index or <code>NULL</code>,
+ * if the index is greater than the array size
+ */
+void *ucx_array_at(UcxArray const* array, size_t index);
+
+/**
+ * Returns the index of an element containing the specified data.
+ *
+ * This function uses a cmp_func() to compare the data of each list element
+ * with the specified data. If no cmp_func is provided, memcmp() is used.
+ * 
+ * If the array contains the data more than once, the index of the first
+ * occurrence is returned.
+ * If the array does not contain the data, the size of array is returned.
+ *  
+ * @param array the array where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return the index of the element containing the specified data or the size of
+ * the array, if the data is not found in this array
+ */
+size_t ucx_array_find(UcxArray const *array, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Checks, if an array contains a specific element.
+ * 
+ * An element is found, if ucx_array_find() returns a value less than the size.
+ * 
+ * @param array the array where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the array contains the specified element data
+ * @see ucx_array_find()
+ */
+int ucx_array_contains(UcxArray const *array, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Sorts a UcxArray with the best available sort algorithm.
+ * 
+ * The qsort_r() function is used, if available (glibc, FreeBSD or MacOS).
+ * The order of arguments is automatically adjusted for the FreeBSD and MacOS
+ * version of qsort_r().
+ * 
+ * If qsort_r() is not available, a merge sort algorithm is used, which is
+ * guaranteed to use no more additional memory than for exactly one element.
+ * 
+ * @param array the array to sort
+ * @param cmpfnc the function that shall be used to compare the element data
+ * @param data additional data for the cmp_func() or <code>NULL</code>
+ */
+void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data);
+
+/**
+ * Removes an element from the array.
+ * 
+ * This is in general an expensive operation, because several elements may
+ * be moved. If the order of the elements is not relevant, use
+ * ucx_array_remove_fast() instead.
+ * 
+ * @param array pointer to the array from which the element shall be removed
+ * @param index the index of the element to remove
+ */
+void ucx_array_remove(UcxArray *array, size_t index);
+
+/**
+ * Removes an element from the array.
+ * 
+ * This is an O(1) operation, but does not maintain the order of the elements.
+ * The last element in the array is moved to the location of the removed
+ * element.
+ * 
+ * @param array pointer to the array from which the element shall be removed
+ * @param index the index of the element to remove
+ */
+void ucx_array_remove_fast(UcxArray *array, size_t index);
+
+/**
+ * Shrinks the memory to exactly fit the contents.
+ * 
+ * After this operation, the capacity equals the size.
+ * 
+ * @param array a pointer to the array
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_shrink(UcxArray* array);
+
+/**
+ * Sets the capacity of the array.
+ * 
+ * If the new capacity is smaller than the size of the array, the elements
+ * are removed and the size is adjusted accordingly.
+ * 
+ * @param array a pointer to the array
+ * @param capacity the new capacity
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_resize(UcxArray* array, size_t capacity);
+
+/**
+ * Resizes the array only, if the capacity is insufficient.
+ * 
+ * If the requested capacity is smaller than the current capacity, this
+ * function does nothing.
+ * 
+ * @param array a pointer to the array
+ * @param capacity the guaranteed capacity
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_reserve(UcxArray* array, size_t capacity);
+
+/**
+ * Resizes the capacity, if the specified number of elements would not fit.
+ * 
+ * A call to ucx_array_grow(array, count) is effectively the same as
+ * ucx_array_reserve(array, array->size+count).
+ * 
+ * @param array a pointer to the array
+ * @param count the number of elements that should additionally fit
+ * into the array
+ * @return zero on success, non-zero if reallocation failed
+ */
+int ucx_array_grow(UcxArray* array, size_t count);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_ARRAY_H */
+
diff --git a/ucx/ucx/avl.h b/ucx/ucx/avl.h
new file mode 100644 (file)
index 0000000..8b251a5
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/**
+ * @file avl.h
+ * 
+ * AVL tree implementation.
+ * 
+ * This binary search tree implementation allows average O(1) insertion and
+ * removal of elements (excluding binary search time).
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_AVL_H
+#define UCX_AVL_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX AVL Node type.
+ * 
+ * @see UcxAVLNode
+ */
+typedef struct UcxAVLNode UcxAVLNode;
+
+/**
+ * UCX AVL Node.
+ */
+struct UcxAVLNode {
+    /**
+     * The key for this node.
+     */
+    intptr_t key;
+    /**
+     * Data contained by this node.
+     */
+    void *value;
+    /**
+     * The height of this (sub)-tree.
+     */
+    size_t height;
+    /**
+     * Parent node.
+     */
+    UcxAVLNode *parent;
+    /**
+     * Root node of left subtree.
+     */
+    UcxAVLNode *left;
+    /**
+     * Root node of right subtree.
+     */
+    UcxAVLNode *right;
+};
+
+/**
+ * UCX AVL Tree.
+ */
+typedef struct {
+    /**
+     * The UcxAllocator that shall be used to manage the memory for node data.
+     */
+    UcxAllocator *allocator;
+    /**
+     * Root node of the tree.
+     */
+    UcxAVLNode *root;
+    /**
+     * Compare function that shall be used to compare the UcxAVLNode keys.
+     * @see UcxAVLNode.key
+     */
+    cmp_func cmpfunc;
+    /**
+     * Custom user data.
+     * This data will also be provided to the cmpfunc.
+     */
+    void *userdata;
+} UcxAVLTree;
+
+/**
+ * Initializes a new UcxAVLTree with a default allocator.
+ * 
+ * @param cmpfunc the compare function that shall be used
+ * @return a new UcxAVLTree object
+ * @see ucx_avl_new_a()
+ */
+UcxAVLTree *ucx_avl_new(cmp_func cmpfunc);
+
+/**
+ * Initializes a new UcxAVLTree with the specified allocator.
+ * 
+ * The cmpfunc should be capable of comparing two keys within this AVL tree.
+ * So if you want to use null terminated strings as keys, you could use the
+ * ucx_cmp_str() function here.
+ * 
+ * @param cmpfunc the compare function that shall be used
+ * @param allocator the UcxAllocator that shall be used
+ * @return a new UcxAVLTree object
+ */
+UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator);
+
+/**
+ * Destroys a UcxAVLTree.
+ * 
+ * Note, that the contents are not automatically freed.
+ * Use may use #ucx_avl_free_content() before calling this function.
+ * 
+ * @param tree the tree to destroy
+ * @see ucx_avl_free_content()
+ */
+void ucx_avl_free(UcxAVLTree *tree);
+
+/**
+ * Frees the contents of a UcxAVLTree.
+ * 
+ * This is a convenience function that iterates over the tree and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the tree's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free the entire tree afterwards, as the contents will be invalid.
+ * 
+ * @param tree for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_avl_free()
+ */
+void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr);
+
+/**
+ * Macro for initializing a new UcxAVLTree with the default allocator and a
+ * ucx_cmp_ptr() compare function.
+ * 
+ * @return a new default UcxAVLTree object
+ */
+#define ucx_avl_default_new() \
+    ucx_avl_new_a(ucx_cmp_ptr, ucx_default_allocator())
+
+/**
+ * Gets the node from the tree, that is associated with the specified key.
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return the node (or <code>NULL</code>, if the key is not present)
+ */
+UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * Gets the value from the tree, that is associated with the specified key.
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return the value (or <code>NULL</code>, if the key is not present)
+ */
+void *ucx_avl_get(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * A mode for #ucx_avl_find_node() with the same behavior as
+ * #ucx_avl_get_node().
+ */
+#define UCX_AVL_FIND_EXACT         0
+/**
+ * A mode for #ucx_avl_find_node() finding the node whose key is at least
+ * as large as the specified key.
+ */
+#define UCX_AVL_FIND_LOWER_BOUNDED 1
+/**
+ * A mode for #ucx_avl_find_node() finding the node whose key is at most
+ * as large as the specified key.
+ */
+#define UCX_AVL_FIND_UPPER_BOUNDED 2
+/**
+ * A mode for #ucx_avl_find_node() finding the node with a key that is as close
+ * to the specified key as possible. If the key is present, the behavior is
+ * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
+ * empty trees.
+ */
+#define UCX_AVL_FIND_CLOSEST       3
+
+/**
+ * Finds a node within the tree. The following modes are supported:
+ * <ul>
+ * <li>#UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()</li>
+ * <li>#UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least
+ * as large as the specified key</li>
+ * <li>#UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most
+ * as large as the specified key</li>
+ * <li>#UCX_AVL_FIND_CLOSEST: finds the node with a key that is as close to
+ * the specified key as possible. If the key is present, the behavior is
+ * like #ucx_avl_get_node(). This mode only returns <code>NULL</code> on
+ * empty trees.</li> 
+ * </ul>
+ * 
+ * The distance function provided MUST agree with the compare function of
+ * the AVL tree.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param dfnc the distance function
+ * @param mode the find mode
+ * @return the node (or <code>NULL</code>, if no node can be found)
+ */
+UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode);
+
+/**
+ * Finds a value within the tree.
+ * See #ucx_avl_find_node() for details.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param dfnc the distance function
+ * @param mode the find mode
+ * @return the value (or <code>NULL</code>, if no value can be found)
+ */
+void *ucx_avl_find(UcxAVLTree *tree, intptr_t key,
+        distance_func dfnc, int mode);
+
+/**
+ * Puts a key/value pair into the tree.
+ * 
+ * Attention: use this function only, if a possible old value does not need
+ * to be preserved.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param value the new value
+ * @return zero, if and only if the operation succeeded
+ */
+int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value);
+
+/**
+ * Puts a key/value pair into the tree.
+ * 
+ * This is a secure function which saves the old value to the variable pointed
+ * at by oldvalue.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @param value the new value
+ * @param oldvalue optional: a pointer to the location where a possible old
+ * value shall be stored
+ * @return zero, if and only if the operation succeeded
+ */
+int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue);
+
+/**
+ * Removes a node from the AVL tree.
+ * 
+ * Note: the specified node is logically removed. The tree implementation
+ * decides which memory area is freed. In most cases the here provided node
+ * is freed, so its further use is generally undefined.
+ * 
+ * @param tree the UcxAVLTree
+ * @param node the node to remove
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node);
+
+/**
+ * Removes an element from the AVL tree.
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove(UcxAVLTree *tree, intptr_t key);
+
+/**
+ * Removes an element from the AVL tree.
+ * 
+ * This is a secure function which saves the old key and value data from node
+ * to the variables at the location of oldkey and oldvalue (if specified), so
+ * they can be freed afterwards (if necessary).
+ * 
+ * Note: the returned key in oldkey is possibly not the same as the provided
+ * key for the lookup (in terms of memory location).
+ * 
+ * @param tree the UcxAVLTree
+ * @param key the key of the element to remove
+ * @param oldkey optional: a pointer to the location where the old key shall be
+ * stored
+ * @param oldvalue optional: a pointer to the location where the old value
+ * shall be stored
+ * @return zero, if and only if an element has been removed
+ */
+int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key,
+        intptr_t *oldkey, void **oldvalue);
+
+/**
+ * Counts the nodes in the specified UcxAVLTree.
+ * @param tree the AVL tree
+ * @return the node count
+ */
+size_t ucx_avl_count(UcxAVLTree *tree);
+
+/**
+ * Finds the in-order predecessor of the given node.
+ * @param node an AVL node
+ * @return the in-order predecessor of the given node, or <code>NULL</code> if
+ * the given node is the in-order minimum
+ */
+UcxAVLNode* ucx_avl_pred(UcxAVLNode* node);
+
+/**
+ * Finds the in-order successor of the given node.
+ * @param node an AVL node
+ * @return the in-order successor of the given node, or <code>NULL</code> if
+ * the given node is the in-order maximum
+ */
+UcxAVLNode* ucx_avl_succ(UcxAVLNode* node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_AVL_H */
+
diff --git a/ucx/ucx/buffer.h b/ucx/ucx/buffer.h
new file mode 100644 (file)
index 0000000..25f659f
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file buffer.h
+ * 
+ * Advanced buffer implementation.
+ * 
+ * Instances of UcxBuffer can be used to read from or to write to like one
+ * would do with a stream. This allows the use of ucx_stream_copy() to copy
+ * contents from one buffer to another.
+ * 
+ * Some features for convenient use of the buffer
+ * can be enabled. See the documentation of the macro constants for more
+ * information.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_BUFFER_H
+#define        UCX_BUFFER_H
+
+#include "ucx.h"
+#include <sys/types.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define UCX_BUFFER_DEFAULT      0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents.
+ */
+#define UCX_BUFFER_AUTOFREE     0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define UCX_BUFFER_AUTOEXTEND   0x02
+
+/** UCX Buffer. */
+typedef struct {
+    /** A pointer to the buffer contents. */
+    char *space;
+    /** Current position of the buffer. */
+    size_t pos;
+    /** Current capacity (i.e. maximum size) of the buffer. */
+    size_t capacity;
+    /** Current size of the buffer content. */
+    size_t size;
+    /**
+     * Flag register for buffer features.
+     * @see #UCX_BUFFER_DEFAULT
+     * @see #UCX_BUFFER_AUTOFREE
+     * @see #UCX_BUFFER_AUTOEXTEND
+     */
+    int flags;
+} UcxBuffer;
+
+/**
+ * Creates a new buffer.
+ * 
+ * <b>Note:</b> you may provide <code>NULL</code> as argument for
+ * <code>space</code>. Then this function will allocate the space and enforce
+ * the #UCX_BUFFER_AUTOFREE flag.
+ * 
+ * @param space pointer to the memory area, or <code>NULL</code> to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param flags buffer features (see UcxBuffer.flags)
+ * @return the new buffer
+ */
+UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags);
+
+/**
+ * Destroys a buffer.
+ * 
+ * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer
+ * are also freed.
+ * 
+ * @param buffer the buffer to destroy
+ */
+void ucx_buffer_free(UcxBuffer* buffer);
+
+/**
+ * Creates a new buffer and fills it with extracted content from another buffer.
+ * 
+ * <b>Note:</b> the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer.
+ * 
+ * @param src the source buffer
+ * @param start the start position of extraction
+ * @param length the count of bytes to extract (must not be zero)
+ * @param flags feature mask for the new buffer
+ * @return a new buffer containing the extraction
+ */
+UcxBuffer* ucx_buffer_extract(UcxBuffer *src,
+        size_t start, size_t length, int flags);
+
+/**
+ * A shorthand macro for the full extraction of the buffer.
+ * 
+ * @param src the source buffer
+ * @param flags feature mask for the new buffer
+ * @return a new buffer with the extracted content
+ */
+#define ucx_buffer_clone(src,flags) \
+    ucx_buffer_extract(src, 0, (src)->capacity, flags)
+
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ * 
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ * 
+ * If the offset is negative, the contents are shifted to the left where the
+ * first <code>shift</code> bytes are discarded.
+ * The new size of the buffer is the old size minus
+ * the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ * 
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ * 
+ * <b>Security note:</b> the shifting operation does <em>not</em> erase the
+ * previously occupied memory cells. You can easily do that manually, e.g. by
+ * calling <code>memset(buffer->space, 0, shift)</code> for a right shift or
+ * <code>memset(buffer->size, 0, buffer->capacity-buffer->size)</code>
+ * for a left shift.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset (negative means left shift)
+ * @return 0 on success, non-zero if a required auto-extension fails
+ */
+int ucx_buffer_shift(UcxBuffer* buffer, off_t shift);
+
+/**
+ * Shifts the buffer to the right.
+ * See ucx_buffer_shift() for details.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift);
+
+/**
+ * Shifts the buffer to the left.
+ * 
+ * See ucx_buffer_shift() for details. Note, however, that this method expects
+ * a positive shift offset.
+ * 
+ * Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ * 
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return always zero
+ * @see ucx_buffer_shift()
+ */
+int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift);
+
+
+/**
+ * Moves the position of the buffer.
+ * 
+ * The new position is relative to the <code>whence</code> argument.
+ *
+ * SEEK_SET marks the start of the buffer.
+ * SEEK_CUR marks the current position.
+ * SEEK_END marks the end of the buffer.
+ * 
+ * With an offset of zero, this function sets the buffer position to zero
+ * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position
+ * unchanged (SEEK_CUR).
+ * 
+ * @param buffer
+ * @param offset position offset relative to <code>whence</code>
+ * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END
+ * @return 0 on success, non-zero if the position is invalid
+ *
+ */
+int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ * 
+ * The data is deleted by a zeroing it with call to <code>memset()</code>.
+ * 
+ * @param buffer the buffer to be cleared
+ */
+#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \
+        (buffer)->size = 0; (buffer)->pos = 0;
+
+/**
+ * Tests, if the buffer position has exceeded the buffer capacity.
+ * 
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * available byte of the buffer.
+ */
+int ucx_buffer_eof(UcxBuffer *buffer);
+
+
+/**
+ * Extends the capacity of the buffer.
+ * 
+ * <b>Note:</b> The buffer capacity increased by a power of two. I.e.
+ * the buffer capacity is doubled, as long as it would not hold the current
+ * content plus the additional required bytes.
+ * 
+ * <b>Attention:</b> the argument provided is the number of <i>additional</i>
+ * bytes the buffer shall hold. It is <b>NOT</b> the total number of bytes the
+ * buffer shall hold.
+ * 
+ * @param buffer the buffer to extend
+ * @param additional_bytes the number of additional bytes the buffer shall
+ * <i>at least</i> hold
+ * @return 0 on success or a non-zero value on failure
+ */
+int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes);
+
+/**
+ * Writes data to a UcxBuffer.
+ * 
+ * The position of the buffer is increased by the number of bytes written.
+ * 
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the UcxBuffer to write to
+ * @return the total count of bytes written
+ */
+size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer);
+
+/**
+ * Reads data from a UcxBuffer.
+ * 
+ * The position of the buffer is increased by the number of bytes read.
+ * 
+ * @param ptr a pointer to the memory area where to store the read data
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the UcxBuffer to read from
+ * @return the total number of elements read
+ */
+size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems,
+        UcxBuffer *buffer);
+
+/**
+ * Writes a character to a buffer.
+ * 
+ * The least significant byte of the argument is written to the buffer. If the
+ * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled,
+ * the buffer capacity is extended by ucx_buffer_extend(). If the feature is
+ * disabled or buffer extension fails, <code>EOF</code> is returned.
+ * 
+ * On successful write the position of the buffer is increased.
+ * 
+ * @param buffer the buffer to write to
+ * @param c the character to write as <code>int</code> value
+ * @return the byte that has bean written as <code>int</code> value or
+ * <code>EOF</code> when the end of the stream is reached and automatic
+ * extension is not enabled or not possible
+ */
+int ucx_buffer_putc(UcxBuffer *buffer, int c);
+
+/**
+ * Gets a character from a buffer.
+ * 
+ * The current position of the buffer is increased after a successful read.
+ * 
+ * @param buffer the buffer to read from
+ * @return the character as <code>int</code> value or <code>EOF</code>, if the
+ * end of the buffer is reached
+ */
+int ucx_buffer_getc(UcxBuffer *buffer);
+
+/**
+ * Writes a string to a buffer.
+ * 
+ * @param buffer the buffer
+ * @param str the string
+ * @return the number of bytes written
+ */
+size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str);
+
+/**
+ * Returns the complete buffer content as sstr_t.
+ * @param buffer the buffer
+ * @return the result of <code>sstrn()</code> with the buffer space and size
+ * as arguments
+ */
+#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_BUFFER_H */
+
diff --git a/ucx/ucx/list.h b/ucx/ucx/list.h
new file mode 100644 (file)
index 0000000..2bda6b8
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Doubly linked list implementation.
+ * 
+ * @file   list.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_LIST_H
+#define        UCX_LIST_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Loop statement for UCX lists.
+ * 
+ * The first argument is the name of the iteration variable. The scope of
+ * this variable is limited to the <code>UCX_FOREACH</code> statement.
+ * 
+ * The second argument is a pointer to the list. In most cases this will be the
+ * pointer to the first element of the list, but it may also be an arbitrary
+ * element of the list. The iteration will then start with that element.
+ * 
+ * @param list The first element of the list
+ * @param elem The variable name of the element
+ */
+#define UCX_FOREACH(elem,list) \
+        for (UcxList* elem = (UcxList*) list ; elem != NULL ; elem = elem->next)
+
+/**
+ * UCX list type.
+ * @see UcxList
+ */
+typedef struct UcxList UcxList;
+
+/**
+ * UCX list structure.
+ */
+struct UcxList {
+    /**
+     * List element payload.
+     */
+    void    *data;
+    /**
+     * Pointer to the next list element or <code>NULL</code>, if this is the
+     * last element.
+     */
+    UcxList *next;
+    /**
+     * Pointer to the previous list element or <code>NULL</code>, if this is
+     * the first element.
+     */
+    UcxList *prev;
+};
+
+/**
+ * Creates an element-wise copy of a list.
+ * 
+ * This function clones the specified list by creating new list elements and
+ * copying the data with the specified copy_func(). If no copy_func() is
+ * specified, a shallow copy is created and the new list will reference the
+ * same data as the source list.
+ * 
+ * @param list the list to copy
+ * @param cpyfnc a pointer to the function that shall copy an element (may be
+ * <code>NULL</code>)
+ * @param data additional data for the copy_func()
+ * @return a pointer to the copy
+ */
+UcxList *ucx_list_clone(const UcxList *list, copy_func cpyfnc, void* data);
+
+/**
+ * Creates an element-wise copy of a list using a UcxAllocator.
+ * 
+ * See ucx_list_clone() for details.
+ * 
+ * You might want to pass the allocator via the <code>data</code> parameter,
+ * to access it within the copy function for making deep copies.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list to copy
+ * @param cpyfnc a pointer to the function that shall copy an element (may be
+ * <code>NULL</code>)
+ * @param data additional data for the copy_func()
+ * @return a pointer to the copy
+ * @see ucx_list_clone()
+ */
+UcxList *ucx_list_clone_a(UcxAllocator *allocator, const UcxList *list,
+        copy_func cpyfnc, void* data);
+
+/**
+ * Compares two UCX lists element-wise by using a compare function.
+ * 
+ * Each element of the two specified lists are compared by using the specified
+ * compare function and the additional data. The type and content of this
+ * additional data depends on the cmp_func() used.
+ * 
+ * If the list pointers denote elements within a list, the lists are compared
+ * starting with the denoted elements. Thus any previous elements are not taken
+ * into account. This might be useful to check, if certain list tails match
+ * each other.
+ * 
+ * @param list1 the first list
+ * @param list2 the second list
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the two lists equal element-wise, 0 otherwise
+ */
+int ucx_list_equals(const UcxList *list1, const UcxList *list2,
+        cmp_func cmpfnc, void* data);
+
+/**
+ * Destroys the entire list.
+ * 
+ * The members of the list are not automatically freed, so ensure they are
+ * otherwise referenced or destroyed by ucx_list_free_contents().
+ * Otherwise, a memory leak is likely to occur.
+ * 
+ * <b>Caution:</b> the argument <b>MUST</b> denote an entire list (i.e. a call
+ * to ucx_list_first() on the argument must return the argument itself)
+ * 
+ * @param list the list to free
+ * @see ucx_list_free_contents()
+ */
+void ucx_list_free(UcxList *list);
+
+/**
+ * Destroys the entire list using a UcxAllocator.
+ * 
+ * See ucx_list_free() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list to free
+ * @see ucx_list_free()
+ */
+void ucx_list_free_a(UcxAllocator *allocator, UcxList *list);
+
+/**
+ * Destroys the contents of the specified list by calling the specified
+ * destructor on each of them.
+ * 
+ * Note, that the contents are not usable afterwards and the list should be
+ * destroyed with ucx_list_free().
+ *
+ * If no destructor is specified (<code>NULL</code>), stdlib's free() is used.
+ * 
+ * @param list the list for which the contents shall be freed
+ * @param destr optional destructor function
+ * @see ucx_list_free()
+ */
+void ucx_list_free_content(UcxList* list, ucx_destructor destr);
+
+
+/**
+ * Inserts an element at the end of the list.
+ * 
+ * This is generally an O(n) operation, as the end of the list is retrieved with
+ * ucx_list_last().
+ * 
+ * @param list the list where to append the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
+ * the newly created list otherwise
+ */
+UcxList *ucx_list_append(UcxList *list, void *data);
+
+/**
+ * Inserts an element at the end of the list using a UcxAllocator.
+ * 
+ * See ucx_list_append() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to append the data, or <code>NULL</code> to
+ * create a new list
+ * @param data the data to insert
+ * @return <code>list</code>, if it is not <code>NULL</code> or a pointer to
+ * the newly created list otherwise
+ * @see ucx_list_append()
+ */
+UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data);
+
+
+/**
+ * Inserts an element at the beginning of the list.
+ * 
+ * You <i>should</i> overwrite the old list pointer by calling
+ * <code>mylist = ucx_list_prepend(mylist, mydata);</code>. However, you may
+ * also perform successive calls of ucx_list_prepend() on the same list pointer,
+ * as this function always searchs for the head of the list with
+ * ucx_list_first().
+ * 
+ * @param list the list where to insert the data or <code>NULL</code> to create
+ * a new list
+ * @param data the data to insert
+ * @return a pointer to the new list head
+ */
+UcxList *ucx_list_prepend(UcxList *list, void *data);
+
+/**
+ * Inserts an element at the beginning of the list using a UcxAllocator.
+ * 
+ * See ucx_list_prepend() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list where to insert the data or <code>NULL</code> to create
+ * a new list
+ * @param data the data to insert
+ * @return a pointer to the new list head
+ * @see ucx_list_prepend()
+ */
+UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data);
+
+/**
+ * Concatenates two lists.
+ * 
+ * Either of the two arguments may be <code>NULL</code>.
+ * 
+ * This function modifies the references to the next/previous element of
+ * the last/first element of <code>list1</code>/<code>
+ * list2</code>.
+ * 
+ * @param list1 first list
+ * @param list2 second list
+ * @return if <code>list1</code> is <code>NULL</code>, <code>list2</code> is
+ * returned, otherwise <code>list1</code> is returned
+ */
+UcxList *ucx_list_concat(UcxList *list1, UcxList *list2);
+
+/**
+ * Returns the first element of a list.
+ * 
+ * If the argument is the list pointer, it is directly returned. Otherwise
+ * this function traverses to the first element of the list and returns the
+ * list pointer.
+ * 
+ * @param elem one element of the list
+ * @return the first element of the list, the specified element is a member of
+ */
+UcxList *ucx_list_first(const UcxList *elem);
+
+/**
+ * Returns the last element of a list.
+ * 
+ * If the argument has no successor, it is the last element and therefore
+ * directly returned. Otherwise this function traverses to the last element of
+ * the list and returns it.
+ * 
+ * @param elem one element of the list
+ * @return the last element of the list, the specified element is a member of
+ */
+UcxList *ucx_list_last(const UcxList *elem);
+
+/**
+ * Returns the list element at the specified index.
+ * 
+ * @param list the list to retrieve the element from
+ * @param index index of the element to return
+ * @return the element at the specified index or <code>NULL</code>, if the
+ * index is greater than the list size
+ */
+UcxList *ucx_list_get(const UcxList *list, size_t index);
+
+/**
+ * Returns the index of an element.
+ * 
+ * @param list the list where to search for the element
+ * @param elem the element to find
+ * @return the index of the element or -1 if the list does not contain the
+ * element
+ */
+ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem);
+
+/**
+ * Returns the element count of the list.
+ * 
+ * @param list the list whose elements are counted
+ * @return the element count
+ */
+size_t ucx_list_size(const UcxList *list);
+
+/**
+ * Returns the index of an element containing the specified data.
+ *
+ * This function uses a cmp_func() to compare the data of each list element
+ * with the specified data. If no cmp_func is provided, the pointers are
+ * compared.
+ * 
+ * If the list contains the data more than once, the index of the first
+ * occurrence is returned.
+ *  
+ * @param list the list where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return the index of the element containing the specified data or -1 if the
+ * data is not found in this list
+ */
+ssize_t ucx_list_find(const UcxList *list, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Checks, if a list contains a specific element.
+ * 
+ * An element is found, if ucx_list_find() returns a value greater than -1.
+ * 
+ * @param list the list where to search for the data
+ * @param elem the element data
+ * @param cmpfnc the compare function
+ * @param data additional data for the compare function
+ * @return 1, if and only if the list contains the specified element data
+ * @see ucx_list_find()
+ */
+int ucx_list_contains(const UcxList *list, void *elem,
+    cmp_func cmpfnc, void *data);
+
+/**
+ * Sorts a UcxList with natural merge sort.
+ * 
+ * This function uses O(n) additional temporary memory for merge operations
+ * that is automatically freed after each merge.
+ * 
+ * As the head of the list might change, you <b>MUST</b> call this function
+ * as follows: <code>mylist = ucx_list_sort(mylist, mycmpfnc, mydata);</code>.
+ * 
+ * @param list the list to sort
+ * @param cmpfnc the function that shall be used to compare the element data
+ * @param data additional data for the cmp_func()
+ * @return the sorted list
+ */
+UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data);
+
+/**
+ * Removes an element from the list.
+ * 
+ * If the first element is removed, the list pointer changes. So it is
+ * <i>highly recommended</i> to <i>always</i> update the pointer by calling
+ * <code>mylist = ucx_list_remove(mylist, myelem);</code>.
+ * 
+ * @param list the list from which the element shall be removed
+ * @param element the element to remove
+ * @return returns the updated list pointer or <code>NULL</code>, if the list
+ * is now empty
+ */
+UcxList *ucx_list_remove(UcxList *list, UcxList *element);
+
+/**
+ * Removes an element from the list using a UcxAllocator.
+ * 
+ * See ucx_list_remove() for details.
+ * 
+ * @param allocator the allocator to use
+ * @param list the list from which the element shall be removed
+ * @param element the element to remove
+ * @return returns the updated list pointer or <code>NULL</code>, if the list
+ * @see ucx_list_remove()
+ */
+UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list,
+        UcxList *element);
+
+/**
+ * Returns the union of two lists.
+ * 
+ * The union is a list of unique elements regarding cmpfnc obtained from
+ * both source lists.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the union
+ */
+UcxList* ucx_list_union(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the union of two lists.
+ * 
+ * The union is a list of unique elements regarding cmpfnc obtained from
+ * both source lists.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the union
+ */
+UcxList* ucx_list_union_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two lists.
+ * 
+ * The intersection contains all elements of the left list
+ * (including duplicates) that can be found in the right list.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the intersection
+ */
+UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two lists.
+ * 
+ * The intersection contains all elements of the left list
+ * (including duplicates) that can be found in the right list.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the intersection
+ */
+UcxList* ucx_list_intersection_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two lists.
+ * 
+ * The difference contains all elements of the left list
+ * (including duplicates) that are not equal to any element of the right list.
+ * 
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxList* ucx_list_difference(const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two lists.
+ * 
+ * The difference contains all elements of the left list
+ * (including duplicates) that are not equal to any element of the right list.
+ * 
+ * @param allocator allocates the new list elements
+ * @param left the left source list
+ * @param right the right source list
+ * @param cmpfnc a function to compare elements
+ * @param cmpdata additional data for the compare function
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxList* ucx_list_difference_a(UcxAllocator *allocator,
+    const UcxList *left, const UcxList *right,
+    cmp_func cmpfnc, void* cmpdata,
+    copy_func cpfnc, void* cpdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_LIST_H */
+
diff --git a/ucx/ucx/logging.h b/ucx/ucx/logging.h
new file mode 100644 (file)
index 0000000..32bbbae
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Logging API.
+ * 
+ * @file   logging.h
+ * @author Mike Becker, Olaf Wintermann
+ */
+#ifndef UCX_LOGGING_H
+#define UCX_LOGGING_H
+
+#include "ucx.h"
+#include "map.h"
+#include "string.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* leave enough space for custom log levels */
+
+/** Log level for error messages. */
+#define UCX_LOGGER_ERROR        0x00
+    
+/** Log level for warning messages. */
+#define UCX_LOGGER_WARN         0x10
+
+/** Log level for information messages. */
+#define UCX_LOGGER_INFO         0x20
+
+/** Log level for debug messages. */
+#define UCX_LOGGER_DEBUG        0x30
+
+/** Log level for trace messages. */
+#define UCX_LOGGER_TRACE        0x40
+
+/**
+ * Output flag for the log level. 
+ * If this flag is set, the log message will contain the log level.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_LEVEL        0x01
+
+/**
+ * Output flag for the timestmap.
+ * If this flag is set, the log message will contain the timestmap.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_TIMESTAMP    0x02
+
+/**
+ * Output flag for the source.
+ * If this flag is set, the log message will contain the source file and line
+ * number.
+ * @see UcxLogger.mask
+ */
+#define UCX_LOGGER_SOURCE       0x04
+
+/**
+ * The UCX Logger object.
+ */
+typedef struct {
+    /** The stream this logger writes its messages to.*/
+    void *stream;
+
+    /**
+     * The write function that shall be used.
+     * For standard file or stdout loggers this might be standard fwrite
+     * (default).
+     */
+    write_func writer;
+
+    /**
+     * The date format for timestamp outputs including the delimiter
+     * (default: <code>"%F %T %z "</code>).
+     * @see UCX_LOGGER_TIMESTAMP
+     */
+    char *dateformat;
+
+    /**
+     * The level, this logger operates on.
+     * If a log command is issued, the message will only be logged, if the log
+     * level of the message is less or equal than the log level of the logger.
+     */
+    unsigned int level;
+
+    /**
+     * A configuration mask for automatic output. 
+     * For each flag that is set, the logger automatically outputs some extra
+     * information like the timestamp or the source file and line number.
+     * See the documentation for the flags for details.
+     */
+    unsigned int mask;
+
+    /**
+     * A map of valid log levels for this logger.
+     * 
+     * The keys represent all valid log levels and the values provide string
+     * representations, that are used, if the UCX_LOGGER_LEVEL flag is set.
+     * 
+     * The exact data types are <code>unsigned int</code> for the key and
+     * <code>const char*</code> for the value.
+     * 
+     * @see UCX_LOGGER_LEVEL
+     */
+    UcxMap* levels;
+} UcxLogger;
+
+/**
+ * Creates a new logger.
+ * @param stream the stream, which the logger shall write to
+ * @param level the level on which the logger shall operate
+ * @param mask configuration mask (cf. UcxLogger.mask)
+ * @return a new logger object
+ */
+UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask);
+
+/**
+ * Destroys the logger.
+ * 
+ * The map containing the valid log levels is also automatically destroyed.
+ * 
+ * @param logger the logger to destroy
+ */
+void ucx_logger_free(UcxLogger* logger);
+
+/**
+ * Internal log function - use macros instead.
+ * 
+ * This function uses the <code>format</code> and variadic arguments for a
+ * printf()-style output of the log message.
+ * 
+ * Dependent on the UcxLogger.mask some information is prepended. The complete
+ * format is:
+ * 
+ * <code>[LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message</code>
+ *
+ * The source file name is reduced to the actual file name. This is necessary to
+ * get consistent behavior over different definitions of the __FILE__ macro.
+ *
+ * <b>Attention:</b> the message (including automatically generated information)
+ * is limited to 4096 characters. The level description is limited to
+ * 256 characters and the timestamp string is limited to 128 characters.
+ * 
+ * @param logger the logger to use
+ * @param level the level to log on
+ * @param file information about the source file
+ * @param line information about the source line number
+ * @param format format string
+ * @param ... arguments
+ * @see ucx_logger_log()
+ */
+void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file,
+        const unsigned int line, const char* format, ...);
+
+/**
+ * Registers a custom log level.
+ * @param logger the logger
+ * @param level the log level as unsigned integer
+ * @param name a string literal describing the level
+ */
+#define ucx_logger_register_level(logger, level, name) {\
+        unsigned int l; \
+            l = level; \
+            ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \
+        } while (0);
+
+/**
+ * Logs a message at the specified level.
+ * @param logger the logger to use
+ * @param level the level to log the message on
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_log(logger, level, ...) \
+    ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__)
+
+/**
+ * Shortcut for logging an error message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_error(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__)
+
+/**
+ * Shortcut for logging an information message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_info(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a warning message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_warn(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a debug message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_debug(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__)
+
+/**
+ * Shortcut for logging a trace message.
+ * @param logger the logger to use
+ * @param ... format string and arguments
+ * @see ucx_logger_logf()
+ */
+#define ucx_logger_trace(logger, ...) \
+    ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_LOGGING_H */
diff --git a/ucx/ucx/map.h b/ucx/ucx/map.h
new file mode 100644 (file)
index 0000000..4a9b9a2
--- /dev/null
@@ -0,0 +1,549 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file map.h
+ * 
+ * Hash map implementation.
+ * 
+ * This implementation uses murmur hash 2 and separate chaining with linked
+ * lists.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_MAP_H
+#define        UCX_MAP_H
+
+#include "ucx.h"
+#include "string.h"
+#include "allocator.h"
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Loop statement for UCX maps.
+ * 
+ * The <code>key</code> variable is implicitly defined, but the
+ * <code>value</code> variable must be already declared as type information
+ * cannot be inferred.
+ * 
+ * @param key the variable name for the key
+ * @param value the variable name for the value
+ * @param iter a UcxMapIterator
+ * @see ucx_map_iterator()
+ */
+#define UCX_MAP_FOREACH(key,value,iter) \
+        for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);)
+
+/** Type for the UCX map. @see UcxMap */
+typedef struct UcxMap          UcxMap;
+
+/** Type for a key of a UcxMap. @see UcxKey */
+typedef struct UcxKey          UcxKey;
+
+/** Type for an element of a UcxMap. @see UcxMapElement */
+typedef struct UcxMapElement   UcxMapElement;
+
+/** Type for an iterator over a UcxMap. @see UcxMapIterator */
+typedef struct UcxMapIterator  UcxMapIterator;
+
+/** Structure for the UCX map. */
+struct UcxMap {
+    /** An allocator that is used for the map elements. */
+    UcxAllocator  *allocator;
+    /** The array of map element lists. */
+    UcxMapElement **map;
+    /** The size of the map is the length of the element list array. */
+    size_t        size;
+    /** The count of elements currently stored in this map. */
+    size_t        count;
+};
+
+/** Structure to publicly denote a key of a UcxMap. */
+struct UcxKey {
+    /** The key data. */
+    const void *data;
+    /** The length of the key data. */
+    size_t     len;
+    /** A cache for the hash value of the key data. */
+    int        hash;
+};
+
+/** Internal structure for a key of a UcxMap. */
+struct UcxMapKey {
+    /** The key data. */
+    void    *data;
+    /** The length of the key data. */
+    size_t  len;
+    /** The hash value of the key data. */
+    int     hash;
+};
+
+/** Structure for an element of a UcxMap. */
+struct UcxMapElement {
+    /** The value data. */
+    void              *data;
+    
+    /** A pointer to the next element in the current list. */
+    UcxMapElement     *next;
+    
+    /** The corresponding key. */
+    struct UcxMapKey  key;
+};
+
+/** Structure for an iterator over a UcxMap. */
+struct UcxMapIterator {
+    /** The map to iterate over. */
+    UcxMap const  *map;
+    
+    /** The current map element. */
+    UcxMapElement *cur;
+    
+    /**
+     * The current index of the element list array.
+     * <b>Attention: </b> this is <b>NOT</b> the element index! Do <b>NOT</b>
+     * manually iterate over the map by increasing this index. Use
+     * ucx_map_iter_next().
+     * @see UcxMap.map*/
+    size_t        index;
+};
+
+/**
+ * Creates a new hash map with the specified size.
+ * @param size the size of the hash map
+ * @return a pointer to the new hash map
+ */
+UcxMap *ucx_map_new(size_t size);
+
+/**
+ * Creates a new hash map with the specified size using a UcxAllocator.
+ * @param allocator the allocator to use
+ * @param size the size of the hash map
+ * @return a pointer to the new hash map
+ */
+UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size);
+
+/**
+ * Frees a hash map.
+ * 
+ * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
+ * before calling this function to achieve that.
+ * 
+ * @param map the map to be freed
+ * @see ucx_map_free_content()
+ */
+void ucx_map_free(UcxMap *map);
+
+/**
+ * Frees the contents of a hash map.
+ * 
+ * This is a convenience function that iterates over the map and passes all
+ * values to the specified destructor function.
+ * 
+ * If no destructor is specified (<code>NULL</code>), the free() function of
+ * the map's own allocator is used.
+ * 
+ * You must ensure, that it is valid to pass each value in the map to the same
+ * destructor function.
+ * 
+ * You should free or clear the map afterwards, as the contents will be invalid.
+ * 
+ * @param map for which the contents shall be freed
+ * @param destr optional pointer to a destructor function
+ * @see ucx_map_free()
+ * @see ucx_map_clear()
+ */
+void ucx_map_free_content(UcxMap *map, ucx_destructor destr);
+
+/**
+ * Clears a hash map.
+ * 
+ * <b>Note:</b> the contents are <b>not</b> freed, use ucx_map_free_content()
+ * before calling this function to achieve that.
+ * 
+ * @param map the map to be cleared
+ * @see ucx_map_free_content()
+ */
+void ucx_map_clear(UcxMap *map);
+
+
+/**
+ * Copies contents from a map to another map using a copy function.
+ * 
+ * <b>Note:</b> The destination map does not need to be empty. However, if it
+ * contains data with keys that are also present in the source map, the contents
+ * are overwritten.
+ * 
+ * @param from the source map
+ * @param to the destination map
+ * @param fnc the copy function or <code>NULL</code> if the pointer address
+ * shall be copied
+ * @param data additional data for the copy function
+ * @return 0 on success or a non-zero value on memory allocation errors
+ */
+int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data);
+
+/**
+ * Clones the map and rehashes if necessary.
+ * 
+ * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
+ * This function <i>always</i> ensures a new UcxMap.size of at least
+ * 2.5*UcxMap.count.
+ * 
+ * @param map the map to clone
+ * @param fnc the copy function to use or <code>NULL</code> if the new and
+ * the old map shall share the data pointers
+ * @param data additional data for the copy function
+ * @return the cloned map
+ * @see ucx_map_copy()
+ */
+UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data);
+
+/**
+ * Clones the map and rehashes if necessary.
+ *
+ * <b>Note:</b> In contrast to ucx_map_rehash() the load factor is irrelevant.
+ * This function <i>always</i> ensures a new UcxMap.size of at least
+ * 2.5*UcxMap.count.
+ *
+ * @param allocator the allocator to use for the cloned map
+ * @param map the map to clone
+ * @param fnc the copy function to use or <code>NULL</code> if the new and
+ * the old map shall share the data pointers
+ * @param data additional data for the copy function
+ * @return the cloned map
+ * @see ucx_map_copy()
+ */
+UcxMap *ucx_map_clone_a(UcxAllocator *allocator,
+                        UcxMap const *map, copy_func fnc, void *data);
+
+/**
+ * Increases size of the hash map, if necessary.
+ * 
+ * The load value is 0.75*UcxMap.size. If the element count exceeds the load
+ * value, the map needs to be rehashed. Otherwise no action is performed and
+ * this function simply returns 0.
+ * 
+ * The rehashing process ensures, that the UcxMap.size is at least
+ * 2.5*UcxMap.count. So there is enough room for additional elements without
+ * the need of another soon rehashing.
+ * 
+ * You can use this function to dramatically increase access performance.
+ * 
+ * @param map the map to rehash
+ * @return 1, if a memory allocation error occurred, 0 otherwise
+ */
+int ucx_map_rehash(UcxMap *map);
+
+/**
+ * Puts a key/value-pair into the map.
+ * 
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+int ucx_map_put(UcxMap *map, UcxKey key, void *value);
+
+/**
+ * Retrieves a value by using a key.
+ * 
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+void* ucx_map_get(UcxMap const *map, UcxKey key);
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ * 
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ */
+void* ucx_map_remove(UcxMap *map, UcxKey key);
+
+/**
+ * Shorthand for putting data with a sstr_t key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_sstr_put(map, key, value) \
+    ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value)
+
+/**
+ * Shorthand for putting data with a C string key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_cstr_put(map, key, value) \
+    ucx_map_put(map, ucx_key(key, strlen(key)), (void*)value)
+
+/**
+ * Shorthand for putting data with an integer key into the map.
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ * @see ucx_map_put()
+ */
+#define ucx_map_int_put(map, key, value) \
+    ucx_map_put(map, ucx_key(&key, sizeof(key)), (void*)value)
+
+/**
+ * Shorthand for getting data from the map with a sstr_t key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_sstr_get(map, key) \
+    ucx_map_get(map, ucx_key(key.ptr, key.length))
+
+/**
+ * Shorthand for getting data from the map with a C string key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_cstr_get(map, key) \
+    ucx_map_get(map, ucx_key(key, strlen(key)))
+
+/**
+ * Shorthand for getting data from the map with an integer key.
+ * @param map the map
+ * @param key the key
+ * @return the value
+ * @see ucx_map_get()
+ */
+#define ucx_map_int_get(map, key) \
+    ucx_map_get(map, ucx_key(&key, sizeof(int)))
+
+/**
+ * Shorthand for removing data from the map with a sstr_t key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_sstr_remove(map, key) \
+    ucx_map_remove(map, ucx_key(key.ptr, key.length))
+
+/**
+ * Shorthand for removing data from the map with a C string key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_cstr_remove(map, key) \
+    ucx_map_remove(map, ucx_key(key, strlen(key)))
+
+/**
+ * Shorthand for removing data from the map with an integer key.
+ * @param map the map
+ * @param key the key
+ * @return the removed value
+ * @see ucx_map_remove()
+ */
+#define ucx_map_int_remove(map, key) \
+    ucx_map_remove(map, ucx_key(&key, sizeof(key)))
+
+/**
+ * Creates a UcxKey based on the given data.
+ * 
+ * This function implicitly computes the hash.
+ * 
+ * @param data the data for the key
+ * @param len the length of the data
+ * @return a UcxKey with implicitly computed hash
+ * @see ucx_hash()
+ */
+UcxKey ucx_key(const void *data, size_t len);
+
+/**
+ * Computes a murmur hash-2.
+ * 
+ * @param data the data to hash
+ * @param len the length of the data
+ * @return the murmur hash-2 of the data
+ */
+int ucx_hash(const char *data, size_t len);
+
+/**
+ * Creates an iterator for a map.
+ * 
+ * <b>Note:</b> A UcxMapIterator iterates over all elements in all element
+ * lists successively. Therefore the order highly depends on the key hashes and
+ * may vary under different map sizes. So generally you may <b>NOT</b> rely on
+ * the iteration order.
+ * 
+ * <b>Note:</b> The iterator is <b>NOT</b> initialized. You need to call
+ * ucx_map_iter_next() at least once before accessing any information. However,
+ * it is not recommended to access the fields of a UcxMapIterator directly.
+ * 
+ * @param map the map to create the iterator for
+ * @return an iterator initialized on the first element of the
+ * first element list
+ * @see ucx_map_iter_next()
+ */
+UcxMapIterator ucx_map_iterator(UcxMap const *map);
+
+/**
+ * Proceeds to the next element of the map (if any).
+ * 
+ * Subsequent calls on the same iterator proceed to the next element and
+ * store the key/value-pair into the memory specified as arguments of this
+ * function.
+ * 
+ * If no further elements are found, this function returns zero and leaves the
+ * last found key/value-pair in memory.
+ * 
+ * @param iterator the iterator to use
+ * @param key a pointer to the memory where to store the key
+ * @param value a pointer to the memory where to store the value
+ * @return 1, if another element was found, 0 if all elements has been processed
+ * @see ucx_map_iterator()
+ */
+int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value);
+
+/**
+ * Returns the union of two maps.
+ *
+ * The union is a fresh map which is filled by two successive calls of
+ * ucx_map_copy() on the two input maps.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the union
+ */
+UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second,
+                      copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the union of two maps.
+ *
+ * The union is a fresh map which is filled by two successive calls of
+ * ucx_map_copy() on the two input maps.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the union
+ */
+UcxMap* ucx_map_union_a(UcxAllocator *allocator,
+                        const UcxMap *first, const UcxMap *second,
+                        copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two maps.
+ *
+ * The intersection is defined as a copy of the first map with every element
+ * removed that has no valid key in the second map.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the intersection
+ */
+UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the intersection of two maps.
+ *
+ * The intersection is defined as a copy of the first map with every element
+ * removed that has no valid key in the second map.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new map containing the intersection
+ */
+UcxMap* ucx_map_intersection_a(UcxAllocator *allocator,
+                               const UcxMap *first, const UcxMap *second,
+                               copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two maps.
+ *
+ * The difference contains a copy of all elements of the first map
+ * for which the corresponding keys cannot be found in the second map.
+ *
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second,
+                           copy_func cpfnc, void* cpdata);
+
+/**
+ * Returns the difference of two maps.
+ *
+ * The difference contains a copy of all elements of the first map
+ * for which the corresponding keys cannot be found in the second map.
+ *
+ * @param allocator the allocator that shall be used by the new map
+ * @param first the first source map
+ * @param second the second source map
+ * @param cpfnc a function to copy the elements
+ * @param cpdata additional data for the copy function
+ * @return a new list containing the difference
+ */
+UcxMap* ucx_map_difference_a(UcxAllocator *allocator,
+                             const UcxMap *first, const UcxMap *second,
+                             copy_func cpfnc, void* cpdata);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_MAP_H */
+
diff --git a/ucx/ucx/mempool.h b/ucx/ucx/mempool.h
new file mode 100644 (file)
index 0000000..e4a8ce3
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file mempool.h
+ * 
+ * Memory pool implementation.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define        UCX_MEMPOOL_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UCX mempool structure.
+ */
+typedef struct {
+    /** UcxAllocator based on this pool */
+    UcxAllocator *allocator;
+    
+    /** List of pointers to pooled memory. */
+    void         **data;
+    
+    /** Count of pooled memory items. */
+    size_t       ndata;
+    
+    /** Memory pool size. */
+    size_t       size;
+} UcxMempool;
+
+/** Shorthand for a new default memory pool with a capacity of 16 elements. */
+#define ucx_mempool_new_default() ucx_mempool_new(16)
+
+
+/**
+ * Creates a memory pool with the specified initial size.
+ * 
+ * As the created memory pool automatically grows in size by factor two when
+ * trying to allocate memory on a full pool, it is recommended that you use
+ * a power of two for the initial size.
+ * 
+ * @param n initial pool size (should be a power of two, e.g. 16)
+ * @return a pointer to the new memory pool
+ * @see ucx_mempool_new_default()
+ */
+UcxMempool *ucx_mempool_new(size_t n);
+
+/**
+ * Resizes a memory pool.
+ * 
+ * This function will fail if the new capacity is not sufficient for the
+ * present data.
+ * 
+ * @param pool the pool to resize
+ * @param newcap the new capacity
+ * @return zero on success or non-zero on failure
+ */
+int ucx_mempool_chcap(UcxMempool *pool, size_t newcap);
+
+/**
+ * Allocates pooled memory.
+ * 
+ * @param pool the memory pool
+ * @param n amount of memory to allocate
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_malloc()
+ */
+void *ucx_mempool_malloc(UcxMempool *pool, size_t n);
+/**
+ * Allocates a pooled memory array.
+ * 
+ * The content of the allocated memory is set to zero.
+ * 
+ * @param pool the memory pool
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_calloc()
+ */
+void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize);
+
+/**
+ * Reallocates pooled memory.
+ * 
+ * If the memory to be reallocated is not contained by the specified pool, the
+ * behavior is undefined.
+ * 
+ * @param pool the memory pool
+ * @param ptr a pointer to the memory that shall be reallocated
+ * @param n the new size of the memory
+ * @return a pointer to the new location of the memory
+ * @see ucx_allocator_realloc()
+ */
+void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n);
+
+/**
+ * Frees pooled memory.
+ * 
+ * Before freeing the memory, the specified destructor function (if any)
+ * is called.
+ * 
+ * If you specify memory, that is not pooled by the specified memory pool, the
+ * program will terminate with a call to <code>abort()</code>.
+ * 
+ * @param pool the memory pool
+ * @param ptr a pointer to the memory that shall be freed
+ * @see ucx_mempool_set_destr()
+ */
+void ucx_mempool_free(UcxMempool *pool, void *ptr);
+
+/**
+ * Destroys a memory pool.
+ * 
+ * For each element the destructor function (if any) is called and the element
+ * is freed.
+ * 
+ * Each of the registered destructor function that has no corresponding element
+ * within the pool (namely those registered by ucx_mempool_reg_destr) is
+ * called interleaving with the element destruction, but with guarantee to the
+ * order in which they were registered (FIFO order).
+ * 
+ * 
+ * @param pool the mempool to destroy
+ */
+void ucx_mempool_destroy(UcxMempool *pool);
+
+/**
+ * Sets a destructor function for the specified memory.
+ * 
+ * The destructor is automatically called when the memory is freed or the
+ * pool is destroyed.
+ * A destructor for pooled memory <b>MUST NOT</b> free the memory itself,
+ * as this is done by the pool. Use a destructor to free any resources
+ * managed by the pooled object.
+ * 
+ * The only requirement for the specified memory is, that it <b>MUST</b> be
+ * pooled memory by a UcxMempool or an element-compatible mempool. The pointer
+ * to the destructor function is saved in a reserved area before the actual
+ * memory.
+ * 
+ * @param ptr pooled memory
+ * @param func a pointer to the destructor function
+ * @see ucx_mempool_free()
+ * @see ucx_mempool_destroy()
+ */
+void ucx_mempool_set_destr(void *ptr, ucx_destructor func);
+
+/**
+ * Registers a destructor function for the specified (non-pooled) memory.
+ *
+ * This is useful, if you have memory that has not been allocated by a mempool,
+ * but shall be managed by a mempool.
+ * 
+ * This function creates an entry in the specified mempool and the memory will
+ * therefore (logically) convert to pooled memory.
+ * <b>However, this does not cause the memory to be freed automatically!</b>.
+ * If you want to use this function, make the memory pool free non-pooled
+ * memory, the specified destructor function must call <code>free()</code>
+ * by itself. But keep in mind, that you then MUST NOT use this destructor
+ * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it
+ * would cause a double-free.
+ * 
+ * @param pool the memory pool
+ * @param ptr data the destructor is registered for
+ * @param destr a pointer to the destructor function
+ */
+void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_MEMPOOL_H */
+
diff --git a/ucx/ucx/properties.h b/ucx/ucx/properties.h
new file mode 100644 (file)
index 0000000..819d1e5
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file properties.h
+ * 
+ * Load / store utilities for properties files.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define        UCX_PROPERTIES_H
+
+#include "ucx.h"
+#include "map.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * UcxProperties object for parsing properties data.
+ * Most of the fields are for internal use only. You may configure the
+ * properties parser, e.g. by changing the used delimiter or specifying 
+ * up to three different characters that shall introduce comments.
+ */
+typedef struct {
+    /**
+     * Input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    char   *buffer;
+    
+    /**
+     * Length of the input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    size_t buflen;
+    
+    /**
+     * Current buffer position (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t pos;
+    
+    /**
+     * Internal temporary buffer (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    char   *tmp;
+    
+    /**
+     * Internal temporary buffer length (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmplen;
+    
+    /**
+     * Internal temporary buffer capacity (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmpcap;
+    
+    /**
+     * Parser error code.
+     * This is always 0 on success and a nonzero value on syntax errors.
+     * The value is set by ucx_properties_next().
+     */
+    int    error;
+    
+    /**
+     * The delimiter that shall be used.
+     * This is '=' by default.
+     */
+    char   delimiter;
+    
+    /**
+     * The first comment character.
+     * This is '#' by default.
+     */
+    char   comment1;
+    
+    /**
+     * The second comment character.
+     * This is not set by default.
+     */
+    char   comment2;
+    
+    /**
+     * The third comment character.
+     * This is not set by default.
+     */
+    char   comment3;
+} UcxProperties;
+
+
+/**
+ * Constructs a new UcxProperties object.
+ * @return a pointer to the new UcxProperties object
+ */
+UcxProperties *ucx_properties_new();
+
+/**
+ * Destroys a UcxProperties object.
+ * @param prop the UcxProperties object to destroy
+ */
+void ucx_properties_free(UcxProperties *prop);
+
+/**
+ * Sets the input buffer for the properties parser.
+ * 
+ * After calling this function, you may parse the data by calling
+ * ucx_properties_next() until it returns 0. The function ucx_properties2map()
+ * is a convenience function that reads as much data as possible by using this
+ * function.
+ * 
+ * 
+ * @param prop the UcxProperties object
+ * @param buf a pointer to the new buffer
+ * @param len the payload length of the buffer
+ * @see ucx_properties_next()
+ * @see ucx_properties2map()
+ */
+void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
+
+/**
+ * Retrieves the next key/value-pair.
+ * 
+ * This function returns a nonzero value as long as there are key/value-pairs
+ * found. If no more key/value-pairs are found, you may refill the input buffer
+ * with ucx_properties_fill().
+ * 
+ * <b>Attention:</b> the sstr_t.ptr pointers of the output parameters point to
+ * memory within the input buffer of the parser and will get invalid some time.
+ * If you want long term copies of the key/value-pairs, use sstrdup() after
+ * calling this function.
+ * 
+ * @param prop the UcxProperties object
+ * @param name a pointer to the sstr_t that shall contain the property name
+ * @param value a pointer to the sstr_t that shall contain the property value
+ * @return Nonzero, if a key/value-pair was successfully retrieved
+ * @see ucx_properties_fill()
+ */
+int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value);
+
+/**
+ * Retrieves all available key/value-pairs and puts them into a UcxMap.
+ * 
+ * This is done by successive calls to ucx_properties_next() until no more
+ * key/value-pairs can be retrieved.
+ * 
+ * The memory for the map values is allocated by the map's own allocator.
+ * 
+ * @param prop the UcxProperties object
+ * @param map the target map
+ * @return The UcxProperties.error code (i.e. 0 on success).
+ * @see ucx_properties_fill()
+ * @see UcxMap.allocator
+ */
+int ucx_properties2map(UcxProperties *prop, UcxMap *map);
+
+/**
+ * Loads a properties file to a UcxMap.
+ * 
+ * This is a convenience function that reads data from an input
+ * stream until the end of the stream is reached.
+ * 
+ * @param map the map object to write the key/value-pairs to
+ * @param file the <code>FILE*</code> stream to read from
+ * @return 0 on success, or a non-zero value on error
+ * 
+ * @see ucx_properties_fill()
+ * @see ucx_properties2map()
+ */
+int ucx_properties_load(UcxMap *map, FILE *file);
+
+/**
+ * Stores a UcxMap to a file.
+ * 
+ * The key/value-pairs are written by using the following format:
+ * 
+ * <code>[key] = [value]\\n</code>
+ * 
+ * @param map the map to store
+ * @param file the <code>FILE*</code> stream to write to
+ * @return 0 on success, or a non-zero value on error
+ */
+int ucx_properties_store(UcxMap *map, FILE *file);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_PROPERTIES_H */
+
diff --git a/ucx/ucx/stack.h b/ucx/ucx/stack.h
new file mode 100644 (file)
index 0000000..6fe8a06
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file stack.h
+ * 
+ * Default stack memory allocation implementation.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_STACK_H
+#define        UCX_STACK_H
+
+#include "ucx.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * UCX stack structure.
+ */
+typedef struct {
+    /** UcxAllocator based on this stack */
+    UcxAllocator allocator;
+    
+    /** Stack size. */
+    size_t size;
+    
+    /** Pointer to the bottom of the stack */
+    char *space;
+    
+    /** Pointer to the top of the stack */
+    char *top;
+} UcxStack;
+
+/**
+ * Metadata for each UCX stack element.
+ */
+struct ucx_stack_metadata {
+    /**
+     * Location of the previous element (<code>NULL</code> if this is the first)
+     */
+    char *prev;
+    
+    /** Size of this element */
+    size_t size;
+};
+
+/**
+ * Initializes UcxStack structure with memory.
+ * 
+ * @param stack a pointer to an uninitialized stack structure
+ * @param space the memory area that shall be managed
+ * @param size size of the memory area
+ * @return a new UcxStack structure
+ */
+void ucx_stack_init(UcxStack *stack, char* space, size_t size);
+
+/**
+ * Allocates stack memory.
+ * 
+ * @param stack a pointer to the stack
+ * @param n amount of memory to allocate
+ * @return a pointer to the allocated memory or <code>NULL</code> on stack
+ * overflow
+ * @see ucx_allocator_malloc()
+ */
+void *ucx_stack_malloc(UcxStack *stack, size_t n);
+
+/**
+ * Allocates memory with #ucx_stack_malloc() and copies the specified data if
+ * the allocation was successful.
+ * 
+ * @param stack a pointer to the stack
+ * @param n amount of memory to allocate
+ * @param data a pointer to the data to copy
+ * @return a pointer to the allocated memory
+ * @see ucx_stack_malloc
+ */
+void *ucx_stack_push(UcxStack *stack, size_t n, const void *data);
+
+/**
+ * Allocates an array of stack memory
+ * 
+ * The content of the allocated memory is set to zero.
+ * 
+ * @param stack a pointer to the stack
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @return a pointer to the allocated memory
+ * @see ucx_allocator_calloc()
+ */
+void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize);
+
+/**
+ * Allocates memory with #ucx_stack_calloc() and copies the specified data if
+ * the allocation was successful.
+ * 
+ * @param stack a pointer to the stack
+ * @param nelem amount of elements to allocate
+ * @param elsize amount of memory per element
+ * @param data a pointer to the data
+ * @return a pointer to the allocated memory
+ * @see ucx_stack_calloc
+ */
+void *ucx_stack_pusharr(UcxStack *stack,
+        size_t nelem, size_t elsize, const void *data);
+
+/**
+ * Reallocates memory on the stack.
+ * 
+ * Shrinking memory is always safe. Extending memory can be very expensive. 
+ * 
+ * @param stack the stack
+ * @param ptr a pointer to the memory that shall be reallocated
+ * @param n the new size of the memory
+ * @return a pointer to the new location of the memory
+ * @see ucx_allocator_realloc()
+ */
+void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n);
+
+/**
+ * Frees memory on the stack.
+ * 
+ * Freeing stack memory behaves in a special way.
+ * 
+ * If the element, that should be freed, is the top most element of the stack,
+ * it is removed from the stack. Otherwise it is marked as freed. Marked
+ * elements are removed, when they become the top most elements of the stack.
+ * 
+ * @param stack a pointer to the stack
+ * @param ptr a pointer to the memory that shall be freed
+ */
+void ucx_stack_free(UcxStack *stack, void *ptr);
+
+
+/**
+ * Returns the size of the top most element.
+ * @param stack a pointer to the stack
+ * @return the size of the top most element
+ */
+#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\
+                                  (stack)->top - 1)->size : 0)
+
+/**
+ * Removes the top most element from the stack and copies the content to <code>
+ * dest</code>, if specified.
+ * 
+ * Use #ucx_stack_topsize()# to get the amount of memory that must be available
+ * at the location of <code>dest</code>.
+ * 
+ * @param stack a pointer to the stack
+ * @param dest the location where the contents shall be written to, or <code>
+ * NULL</code>, if the element shall only be removed.
+ * @see ucx_stack_free
+ * @see ucx_stack_popn
+ */
+#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1)
+
+/**
+ * Removes the top most element from the stack and copies the content to <code>
+ * dest</code>.
+ * 
+ * This function copies at most <code>n</code> bytes to the destination, but
+ * the element is always freed as a whole.
+ * If the element was larger than <code>n</code>, the remaining data is lost.
+ * 
+ * @param stack a pointer to the stack
+ * @param dest the location where the contents shall be written to
+ * @param n copies at most n bytes to <code>dest</code>
+ * @see ucx_stack_pop
+ */
+void ucx_stack_popn(UcxStack *stack, void *dest, size_t n);
+
+/**
+ * Returns the remaining available memory on the specified stack.
+ * 
+ * @param stack a pointer to the stack
+ * @return the remaining available memory
+ */
+size_t ucx_stack_avail(UcxStack *stack);
+
+/**
+ * Checks, if the stack is empty.
+ * 
+ * @param stack a pointer to the stack
+ * @return nonzero, if the stack is empty, zero otherwise
+ */
+#define ucx_stack_empty(stack) (!(stack)->top)
+
+/**
+ * Computes a recommended size for the stack memory area. Note, that
+ * reallocations have not been taken into account, so you might need to reserve
+ * twice as much memory to allow many reallocations.
+ * 
+ * @param size the approximate payload
+ * @param elems the approximate count of element allocations
+ * @return a recommended size for the stack space based on the information
+ * provided
+ */
+#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \
+                                    (elems + 1))
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_STACK_H */
+
diff --git a/ucx/ucx/string.h b/ucx/ucx/string.h
new file mode 100644 (file)
index 0000000..90b437a
--- /dev/null
@@ -0,0 +1,1201 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Bounded string implementation.
+ * 
+ * The UCX strings (<code>sstr_t</code>) provide an alternative to C strings.
+ * The main difference to C strings is, that <code>sstr_t</code> does <b>not
+ * need to be <code>NULL</code>-terminated</b>. Instead the length is stored
+ * within the structure.
+ * 
+ * When using <code>sstr_t</code>, developers must be full aware of what type
+ * of string (<code>NULL</code>-terminated) or not) they are using, when 
+ * accessing the <code>char* ptr</code> directly.
+ * 
+ * The UCX string module provides some common string functions, known from
+ * standard libc, working with <code>sstr_t</code>.
+ * 
+ * @file   string.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_STRING_H
+#define        UCX_STRING_H
+
+#include "ucx.h"
+#include "allocator.h"
+#include <stddef.h>
+
+/*
+ * Use this macro to disable the shortcuts if you experience macro collision.
+ */
+#ifndef UCX_NO_SSTR_SHORTCUTS
+/**
+ * Shortcut for a <code>sstr_t struct</code>
+ * or <code>scstr_t struct</code> literal.
+ */
+#define ST(s) { s, sizeof(s)-1 }
+
+/** Shortcut for the conversion of a C string to a <code>sstr_t</code>. */
+#define S(s) sstrn(s, sizeof(s)-1)
+
+/** Shortcut for the conversion of a C string to a <code>scstr_t</code>. */
+#define SC(s) scstrn(s, sizeof(s)-1)
+#endif /* UCX_NO_SSTR_SHORTCUTS */
+
+/*
+ * Use this macro to disable the format macros.
+ */
+#ifndef UCX_NO_SSTR_FORMAT_MACROS
+/** Expands a sstr_t or scstr_t to printf arguments. */
+#define SFMT(s) (int) (s).length, (s).ptr
+
+/** Format specifier for a sstr_t or scstr_t. */
+#define PRIsstr ".*s"
+#endif /* UCX_NO_SSTR_FORMAT_MACROS */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+/**
+ * The UCX string structure.
+ */
+typedef struct {
+   /** A pointer to the string
+    * (<b>not necessarily <code>NULL</code>-terminated</b>) */
+    char *ptr;
+    /** The length of the string */
+    size_t length;
+} sstr_t;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+typedef struct {
+    /** A constant pointer to the immutable string
+     * (<b>not necessarily <code>NULL</code>-terminated</b>) */
+    const char *ptr;
+    /** The length of the string */
+    size_t length;
+} scstr_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#ifdef __cplusplus
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some sstr_t
+ * @return an immutable (scstr_t) version of the provided string.
+ */
+inline scstr_t s2scstr(sstr_t s) {
+    scstr_t c;
+    c.ptr = s.ptr;
+    c.length = s.length;
+    return c;
+}
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * This variant is used, when the string is already immutable and no operation
+ * needs to be performed.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some scstr_t
+ * @return the argument itself
+ */
+inline scstr_t s2scstr(scstr_t str) {
+    return str;
+}
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(s) s2scstr(s)
+#else
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * This variant is used, when the string is already immutable and no operation
+ * needs to be performed.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some scstr_t
+ * @return the argument itself
+ */
+scstr_t ucx_sc2sc(scstr_t str);
+
+/**
+ * One of two type adjustment functions that return an scstr_t.
+ * 
+ * Used <b>internally</b> to convert a UCX string to an immutable UCX string.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @param str some sstr_t
+ * @return an immutable (scstr_t) version of the provided string.
+ */
+scstr_t ucx_ss2sc(sstr_t str);
+
+#if __STDC_VERSION__ >= 201112L
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(str) _Generic(str, sstr_t: ucx_ss2sc, scstr_t: ucx_sc2sc)(str)
+
+#elif defined(__GNUC__) || defined(__clang__)
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return an immutable version of the provided string
+ */
+#define SCSTR(str) __builtin_choose_expr( \
+        __builtin_types_compatible_p(typeof(str), sstr_t), \
+        ucx_ss2sc, \
+        ucx_sc2sc)(str)
+
+#elif defined(__sun)
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return the an immutable version of the provided string
+ */
+#define SCSTR(str) ({typeof(str) ucx_tmp_var_str = str; \
+       scstr_t ucx_tmp_var_c; \
+       ucx_tmp_var_c.ptr = ucx_tmp_var_str.ptr;\
+       ucx_tmp_var_c.length = ucx_tmp_var_str.length;\
+       ucx_tmp_var_c; })
+#else /* no generics and no builtins */
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * 
+ * This <b>internal</b> function (ab)uses the C standard an expects one single
+ * argument which is then implicitly converted to scstr_t without a warning.
+ * 
+ * <b>Do not use this function manually.</b>
+ * 
+ * @return the an immutable version of the provided string
+ */
+scstr_t ucx_ss2c_s();
+
+/**
+ * Converts a UCX string to an immutable UCX string (scstr_t).
+ * @param str some UCX string
+ * @return the an immutable version of the provided string
+ */
+#define SCSTR(str) ucx_ss2c_s(str)
+#endif /* C11 feature test */
+
+#endif /* C++ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Creates a new sstr_t based on a C string.
+ * 
+ * The length is implicitly inferred by using a call to <code>strlen()</code>.
+ *
+ * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use sstrdup() on the return value of this function.
+ * 
+ * If you need to wrap a constant string, use scstr().
+ * 
+ * @param cstring the C string to wrap
+ * @return a new sstr_t containing the C string
+ * 
+ * @see sstrn()
+ */
+sstr_t sstr(char *cstring);
+
+/**
+ * Creates a new sstr_t of the specified length based on a C string.
+ *
+ * <b>Note:</b> the sstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use sstrdup() on the return value of this function.
+ * 
+ * If you need to wrap a constant string, use scstrn().
+ * 
+ * @param cstring  the C string to wrap
+ * @param length   the length of the string
+ * @return a new sstr_t containing the C string
+ * 
+ * @see sstr()
+ * @see S()
+ */
+sstr_t sstrn(char *cstring, size_t length);
+
+/**
+ * Creates a new scstr_t based on a constant C string.
+ * 
+ * The length is implicitly inferred by using a call to <code>strlen()</code>.
+ *
+ * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use scstrdup() on the return value of this function.
+ * 
+ * @param cstring the C string to wrap
+ * @return a new scstr_t containing the C string
+ * 
+ * @see scstrn()
+ */
+scstr_t scstr(const char *cstring);
+
+
+/**
+ * Creates a new scstr_t of the specified length based on a constant C string.
+ *
+ * <b>Note:</b> the scstr_t will share the specified pointer to the C string.
+ * If you do want a copy, use scstrdup() on the return value of this function. * 
+ * 
+ * @param cstring  the C string to wrap
+ * @param length   the length of the string
+ * @return a new scstr_t containing the C string
+ * 
+ * @see scstr()
+ */
+scstr_t scstrn(const char *cstring, size_t length);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ * 
+ * <b>Attention:</b> if the count argument is larger than the count of the
+ * specified strings, the behavior is undefined.
+ *
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the accumulated length of all strings
+ */
+size_t scstrnlen(size_t count, ...);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ * 
+ * <b>Attention:</b> if the count argument is larger than the count of the
+ * specified strings, the behavior is undefined.
+ * 
+ * @param count    the total number of specified strings
+ * @param ...      all strings
+ * @return the cumulated length of all strings
+ */
+#define sstrnlen(count, ...) scstrnlen(count, __VA_ARGS__)
+
+/**
+ * Concatenates two or more strings.
+ * 
+ * The resulting string will be allocated by standard <code>malloc()</code>. 
+ * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ */
+sstr_t scstrcat(size_t count, scstr_t s1, ...);
+
+/**
+ * Concatenates two or more strings.
+ * 
+ * The resulting string will be allocated by standard <code>malloc()</code>. 
+ * So developers <b>MUST</b> pass the sstr_t.ptr to <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ * 
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ */
+#define sstrcat(count, s1, ...) scstrcat(count, SCSTR(s1), __VA_ARGS__)
+
+/**
+ * Concatenates two or more strings using a UcxAllocator.
+ * 
+ * The resulting string must be freed by the allocators <code>free()</code>
+ * implementation.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param alloc   the allocator to use
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ * 
+ * @see scstrcat()
+ */
+sstr_t scstrcat_a(UcxAllocator *alloc, size_t count, scstr_t s1, ...);
+
+/**
+ * Concatenates two or more strings using a UcxAllocator.
+ * 
+ * The resulting string must be freed by the allocators <code>free()</code>
+ * implementation.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated.
+ *
+ * @param alloc   the allocator to use
+ * @param count   the total number of strings to concatenate
+ * @param s1      first string
+ * @param ...     all remaining strings
+ * @return the concatenated string
+ * 
+ * @see sstrcat()
+ */
+#define sstrcat_a(alloc, count, s1, ...) \
+    scstrcat_a(alloc, count, SCSTR(s1), __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use sstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * 
+ * @see sstrsubsl()
+ * @see sstrchr()
+ */
+sstr_t sstrsubs(sstr_t string, size_t start);
+
+/**
+ * Returns a substring with the given length starting at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use sstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * with a maximum length of <code>length</code>
+ * 
+ * @see sstrsubs()
+ * @see sstrchr()
+ */
+sstr_t sstrsubsl(sstr_t string, size_t start, size_t length);
+
+/**
+ * Returns a substring of an immutable string starting at the specified
+ * location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+* input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use scstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * 
+ * @see scstrsubsl()
+ * @see scstrchr()
+ */
+scstr_t scstrsubs(scstr_t string, size_t start);
+
+/**
+ * Returns a substring of an immutable string with a maximum length starting
+ * at the specified location.
+ * 
+ * <b>Attention:</b> the new string references the same memory area as the
+ * input string and is <b>NOT</b> required to be <code>NULL</code>-terminated.
+ * Use scstrdup() to get a copy.
+ * 
+ * @param string input string
+ * @param start  start location of the substring
+ * @param length the maximum length of the substring
+ * @return a substring of <code>string</code> starting at <code>start</code>
+ * with a maximum length of <code>length</code>
+ * 
+ * @see scstrsubs()
+ * @see scstrchr()
+ */
+scstr_t scstrsubsl(scstr_t string, size_t start, size_t length);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of <code>chr</code>
+ * 
+ * @see sstrsubs()
+ */
+sstr_t sstrchr(sstr_t string, int chr);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of <code>chr</code>
+ * 
+ * @see sstrsubs()
+ */
+sstr_t sstrrchr(sstr_t string, int chr);
+
+/**
+ * Returns an immutable substring starting at the location of the first
+ * occurrence of the specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the first location of <code>chr</code>
+ * 
+ * @see scstrsubs()
+ */
+scstr_t scstrchr(scstr_t string, int chr);
+
+/**
+ * Returns an immutable substring starting at the location of the last
+ * occurrence of the specified character.
+ * 
+ * If the string does not contain the character, an empty string is returned.
+ * 
+ * @param string the string where to locate the character
+ * @param chr    the character to locate
+ * @return       a substring starting at the last location of <code>chr</code>
+ * 
+ * @see scstrsubs()
+ */
+scstr_t scstrrchr(scstr_t string, int chr);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+sstr_t scstrsstr(sstr_t string, scstr_t match);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+#define sstrstr(string, match) scstrsstr(string, SCSTR(match))
+
+/**
+ * Returns an immutable substring starting at the location of the
+ * first occurrence of the specified immutable string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+scstr_t scstrscstr(scstr_t string, scstr_t match);
+
+/**
+ * Returns an immutable substring starting at the location of the
+ * first occurrence of the specified immutable string.
+ * 
+ * If the string does not contain the other string, an empty string is returned.
+ * 
+ * If <code>match</code> is an empty string, the complete <code>string</code> is
+ * returned.
+ * 
+ * @param string the string to be scanned
+ * @param match  string containing the sequence of characters to match
+ * @return       a substring starting at the first occurrence of
+ *               <code>match</code>, or an empty string, if the sequence is not
+ *               present in <code>string</code>
+ */
+#define sstrscstr(string, match) scstrscstr(string, SCSTR(match))
+
+/**
+ * Splits a string into parts by using a delimiter string.
+ * 
+ * This function will return <code>NULL</code>, if one of the following happens:
+ * <ul>
+ *   <li>the string length is zero</li>
+ *   <li>the delimeter length is zero</li>
+ *   <li>the string equals the delimeter</li>
+ *   <li>memory allocation fails</li>
+ * </ul>
+ * 
+ * The integer referenced by <code>count</code> is used as input and determines
+ * the maximum size of the resulting array, i.e. the maximum count of splits to
+ * perform + 1.
+ * 
+ * The integer referenced by <code>count</code> is also used as output and is
+ * set to
+ * <ul>
+ *   <li>-2, on memory allocation errors</li>
+ *   <li>-1, if either the string or the delimiter is an empty string</li>
+ *   <li>0, if the string equals the delimiter</li>
+ *   <li>1, if the string does not contain the delimiter</li>
+ *   <li>the count of array items, otherwise</li>
+ * </ul>
+ * 
+ * If the string starts with the delimiter, the first item of the resulting
+ * array will be an empty string.
+ * 
+ * If the string ends with the delimiter and the maximum list size is not
+ * exceeded, the last array item will be an empty string.
+ * In case the list size would be exceeded, the last array item will be the
+ * remaining string after the last split, <i>including</i> the terminating
+ * delimiter.
+ * 
+ * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
+ * items must be manually passed to <code>free()</code>. Use scstrsplit_a() with
+ * an allocator to managed memory, to avoid this.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see scstrsplit_a()
+ */
+sstr_t* scstrsplit(scstr_t string, scstr_t delim, ssize_t *count);
+
+/**
+ * Splits a string into parts by using a delimiter string.
+ * 
+ * This function will return <code>NULL</code>, if one of the following happens:
+ * <ul>
+ *   <li>the string length is zero</li>
+ *   <li>the delimeter length is zero</li>
+ *   <li>the string equals the delimeter</li>
+ *   <li>memory allocation fails</li>
+ * </ul>
+ * 
+ * The integer referenced by <code>count</code> is used as input and determines
+ * the maximum size of the resulting array, i.e. the maximum count of splits to
+ * perform + 1.
+ * 
+ * The integer referenced by <code>count</code> is also used as output and is
+ * set to
+ * <ul>
+ *   <li>-2, on memory allocation errors</li>
+ *   <li>-1, if either the string or the delimiter is an empty string</li>
+ *   <li>0, if the string equals the delimiter</li>
+ *   <li>1, if the string does not contain the delimiter</li>
+ *   <li>the count of array items, otherwise</li>
+ * </ul>
+ * 
+ * If the string starts with the delimiter, the first item of the resulting
+ * array will be an empty string.
+ * 
+ * If the string ends with the delimiter and the maximum list size is not
+ * exceeded, the last array item will be an empty string.
+ * In case the list size would be exceeded, the last array item will be the
+ * remaining string after the last split, <i>including</i> the terminating
+ * delimiter.
+ * 
+ * <b>Attention:</b> The array pointer <b>AND</b> all sstr_t.ptr of the array
+ * items must be manually passed to <code>free()</code>. Use sstrsplit_a() with
+ * an allocator to managed memory, to avoid this.
+ *
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see sstrsplit_a()
+ */
+#define sstrsplit(string, delim, count) \
+    scstrsplit(SCSTR(string), SCSTR(delim), count)
+
+/**
+ * Performing scstrsplit() using a UcxAllocator.
+ * 
+ * <i>Read the description of scstrsplit() for details.</i>
+ * 
+ * The memory for the sstr_t.ptr pointers of the array items and the memory for
+ * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
+ * function.
+ * 
+ * @param allocator the UcxAllocator used for allocating memory
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see scstrsplit()
+ */
+sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim,
+        ssize_t *count);
+
+/**
+ * Performing sstrsplit() using a UcxAllocator.
+ * 
+ * <i>Read the description of sstrsplit() for details.</i>
+ * 
+ * The memory for the sstr_t.ptr pointers of the array items and the memory for
+ * the sstr_t array itself are allocated by using the UcxAllocator.malloc()
+ * function.
+ * 
+ * @param allocator the UcxAllocator used for allocating memory
+ * @param string the string to split
+ * @param delim  the delimiter string
+ * @param count  IN: the maximum size of the resulting array (0 = no limit),
+ *               OUT: the actual size of the array
+ * @return a sstr_t array containing the split strings or
+ * <code>NULL</code> on error
+ * 
+ * @see sstrsplit()
+ */
+#define sstrsplit_a(allocator, string, delim, count) \
+    scstrsplit_a(allocator, SCSTR(string), SCSTR(delim), count)
+
+/**
+ * Compares two UCX strings with standard <code>memcmp()</code>.
+ * 
+ * At first it compares the scstr_t.length attribute of the two strings. The
+ * <code>memcmp()</code> function is called, if and only if the lengths match.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of
+ * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
+ */
+int scstrcmp(scstr_t s1, scstr_t s2);
+
+/**
+ * Compares two UCX strings with standard <code>memcmp()</code>.
+ * 
+ * At first it compares the sstr_t.length attribute of the two strings. The
+ * <code>memcmp()</code> function is called, if and only if the lengths match.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of
+ * <code>memcmp()</code> otherwise (i.e. 0 if the strings match)
+ */
+#define sstrcmp(s1, s2) scstrcmp(SCSTR(s1), SCSTR(s2))
+
+/**
+ * Compares two UCX strings ignoring the case.
+ * 
+ * At first it compares the scstr_t.length attribute of the two strings. If and
+ * only if the lengths match, both strings are compared char by char ignoring
+ * the case.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of the platform
+ * specific string comparison function ignoring the case.
+ */
+int scstrcasecmp(scstr_t s1, scstr_t s2);
+
+/**
+ * Compares two UCX strings ignoring the case.
+ * 
+ * At first it compares the sstr_t.length attribute of the two strings. If and
+ * only if the lengths match, both strings are compared char by char ignoring
+ * the case.
+ * 
+ * @param s1 the first string
+ * @param s2 the second string
+ * @return -1, if the length of s1 is less than the length of s2 or 1, if the 
+ * length of s1 is greater than the length of s2 or the result of the platform
+ * specific string comparison function ignoring the case.
+ */
+#define sstrcasecmp(s1, s2) scstrcasecmp(SCSTR(s1), SCSTR(s2))
+
+/**
+ * Creates a duplicate of the specified string.
+ * 
+ * The new sstr_t will contain a copy allocated by standard
+ * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
+ * <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated and mutable, regardless of the argument.
+ * 
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup_a()
+ */
+sstr_t scstrdup(scstr_t string);
+
+/**
+ * Creates a duplicate of the specified string.
+ * 
+ * The new sstr_t will contain a copy allocated by standard
+ * <code>malloc()</code>. So developers <b>MUST</b> pass the sstr_t.ptr to
+ * <code>free()</code>.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated, regardless of the argument.
+ * 
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see sstrdup_a()
+ */
+#define sstrdup(string) scstrdup(SCSTR(string))
+
+/**
+ * Creates a duplicate of the specified string using a UcxAllocator.
+ * 
+ * The new sstr_t will contain a copy allocated by the allocators
+ * UcxAllocator.malloc() function. So it is implementation depended, whether the
+ * returned sstr_t.ptr pointer must be passed to the allocators
+ * UcxAllocator.free() function manually.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated and mutable, regardless of the argument.
+ * 
+ * @param allocator a valid instance of a UcxAllocator
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup()
+ */
+sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t string);
+
+/**
+ * Creates a duplicate of the specified string using a UcxAllocator.
+ * 
+ * The new sstr_t will contain a copy allocated by the allocators
+ * UcxAllocator.malloc() function. So it is implementation depended, whether the
+ * returned sstr_t.ptr pointer must be passed to the allocators
+ * UcxAllocator.free() function manually.
+ * 
+ * The sstr_t.ptr of the return value will <i>always</i> be <code>NULL</code>-
+ * terminated, regardless of the argument.
+ * 
+ * @param allocator a valid instance of a UcxAllocator
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see scstrdup()
+ */
+#define sstrdup_a(allocator, string) scstrdup_a(allocator, SCSTR(string))
+
+
+/**
+ * Omits leading and trailing spaces.
+ * 
+ * This function returns a new sstr_t containing a trimmed version of the
+ * specified string.
+ * 
+ * <b>Note:</b> the new sstr_t references the same memory, thus you
+ * <b>MUST NOT</b> pass the sstr_t.ptr of the return value to
+ * <code>free()</code>. It is also highly recommended to avoid assignments like
+ * <code>mystr = sstrtrim(mystr);</code> as you lose the reference to the
+ * source string. Assignments of this type are only permitted, if the
+ * sstr_t.ptr of the source string does not need to be freed or if another
+ * reference to the source string exists.
+ * 
+ * @param string the string that shall be trimmed
+ * @return a new sstr_t containing the trimmed string
+ */
+sstr_t sstrtrim(sstr_t string);
+
+/**
+ * Omits leading and trailing spaces.
+ * 
+ * This function returns a new scstr_t containing a trimmed version of the
+ * specified string.
+ * 
+ * <b>Note:</b> the new scstr_t references the same memory, thus you
+ * <b>MUST NOT</b> pass the scstr_t.ptr of the return value to
+ * <code>free()</code>. It is also highly recommended to avoid assignments like
+ * <code>mystr = scstrtrim(mystr);</code> as you lose the reference to the
+ * source string. Assignments of this type are only permitted, if the
+ * scstr_t.ptr of the source string does not need to be freed or if another
+ * reference to the source string exists.
+ * 
+ * @param string the string that shall be trimmed
+ * @return a new scstr_t containing the trimmed string
+ */
+scstr_t scstrtrim(scstr_t string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+int scstrprefix(scstr_t string, scstr_t prefix);
+
+/**
+ * Checks, if a string has a specific prefix.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+#define sstrprefix(string, prefix) scstrprefix(SCSTR(string), SCSTR(prefix))
+
+/**
+ * Checks, if a string has a specific suffix.
+ * 
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+int scstrsuffix(scstr_t string, scstr_t suffix);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+#define sstrsuffix(string, suffix) scstrsuffix(SCSTR(string), SCSTR(suffix))
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+int scstrcaseprefix(scstr_t string, scstr_t prefix);
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ * 
+ * @param string the string to check
+ * @param prefix the prefix the string should have
+ * @return 1, if and only if the string has the specified prefix, 0 otherwise
+ */
+#define sstrcaseprefix(string, prefix) \
+  scstrcaseprefix(SCSTR(string), SCSTR(prefix))
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ * 
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+int scstrcasesuffix(scstr_t string, scstr_t suffix);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @param string the string to check
+ * @param suffix the suffix the string should have
+ * @return 1, if and only if the string has the specified suffix, 0 otherwise
+ */
+#define sstrcasesuffix(string, suffix) \
+  scstrcasesuffix(SCSTR(string), SCSTR(suffix))
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting lower case string
+ * @see scstrdup()
+ */
+sstr_t scstrlower(scstr_t string);
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting lower case string
+ */
+#define sstrlower(string) scstrlower(SCSTR(string))
+
+/**
+ * Returns a lower case version of a string.
+ * 
+  * This function creates a duplicate of the input string, first
+ * (see scstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting lower case string
+ * @see scstrdup_a()
+ */
+sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string);
+
+
+/**
+ * Returns a lower case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting lower case string
+ */
+#define sstrlower_a(allocator, string) scstrlower_a(allocator, SCSTR(string))
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting upper case string
+ * @see scstrdup()
+ */
+sstr_t scstrupper(scstr_t string);
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup()).
+ * 
+ * @param string the input string
+ * @return the resulting upper case string
+ */
+#define sstrupper(string) scstrupper(SCSTR(string))
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see scstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting upper case string
+ * @see scstrdup_a()
+ */
+sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string);
+
+/**
+ * Returns a upper case version of a string.
+ * 
+ * This function creates a duplicate of the input string, first
+ * (see sstrdup_a()).
+ * 
+ * @param allocator the allocator used for duplicating the string
+ * @param string the input string
+ * @return the resulting upper case string
+ */
+#define sstrupper_a(allocator, string) scstrupper_a(allocator, string)
+
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str,
+        scstr_t pattern, scstr_t replacement, size_t replmax);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+sstr_t scstrreplacen(scstr_t str, scstr_t pattern,
+        scstr_t replacement, size_t replmax);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplacen_a(allocator, str, pattern, replacement, replmax) \
+        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
+            SCSTR(replacement), replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplacen(str, pattern, replacement, replmax) \
+        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The resulting string is allocated by the specified allocator. I.e. it
+ * depends on the used allocator, whether the sstr_t.ptr must be freed
+ * manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param allocator the allocator to use
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplace_a(allocator, str, pattern, replacement) \
+        scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \
+            SCSTR(replacement), SIZE_MAX)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most <code>replmax</code> occurrences.
+ *
+ * The sstr_t.ptr of the resulting string must be freed manually.
+ *
+ * If allocation fails, the sstr_t.ptr of the return value is NULL.
+ *
+ * @param str the string where replacements should be applied
+ * @param pattern the pattern to search for
+ * @param replacement the replacement string
+ * @return the resulting string after applying the replacements
+ */
+#define sstrreplace(str, pattern, replacement) \
+        scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_STRING_H */
diff --git a/ucx/ucx/test.h b/ucx/ucx/test.h
new file mode 100644 (file)
index 0000000..b45b1d1
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file: test.h
+ * 
+ * UCX Test Framework.
+ * 
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <pre>
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </pre>
+ *
+ * **** IN SOURCE FILE: ****
+ * <pre>
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with UCX_TEST_ASSERT()
+ * }
+ * 
+ * UCX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #UCX_TEST_BEGIN
+ *   // tests with UCX_TEST_ASSERT() and/or
+ *   // calls with UCX_TEST_CALL_SUBROUTINE() here
+ *   #UCX_TEST_END
+ *   // cleanup of memory here
+ * }
+ * </pre>
+ *
+ * <b>Note:</b> if a test fails, a longjump is performed
+ * back to the #UCX_TEST_BEGIN macro!
+ * 
+ * <b>Attention:</b> Do not call own functions within a test, that use
+ * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE().
+ * 
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define        UCX_TEST_H
+
+#include "ucx.h"
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+/** Type for the UcxTestSuite. */
+typedef struct UcxTestSuite UcxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*UcxTest)(UcxTestSuite*,FILE*);
+
+/** Type for the internal list of test cases. */
+typedef struct UcxTestList UcxTestList;
+
+/** Structure for the internal list of test cases. */
+struct UcxTestList {
+    
+    /** Test case. */
+    UcxTest test;
+    
+    /** Pointer to the next list element. */
+    UcxTestList *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct UcxTestSuite {
+    
+    /** The number of successful tests after the suite has been run. */
+    unsigned int success;
+    
+    /** The number of failed tests after the suite has been run. */
+    unsigned int failure;
+    
+    /**
+     * Internal list of test cases.
+     * Use ucx_test_register() to add tests to this list.
+     */
+    UcxTestList *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @return a new test suite
+ */
+UcxTestSuite* ucx_test_suite_new();
+
+/**
+ * Destroys a test suite.
+ * @param suite the test suite to destroy
+ */
+void ucx_test_suite_free(UcxTestSuite* suite);
+
+/**
+ * Registers a test function with the specified test suite.
+ * 
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @return <code>EXIT_SUCCESS</code> on success or
+ * <code>EXIT_FAILURE</code> on failure
+ */
+int ucx_test_register(UcxTestSuite* suite, UcxTest test);
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param outstream the stream the log shall be written to
+ */
+void ucx_test_run(UcxTestSuite* suite, FILE* outstream);
+
+/**
+ * Macro for a #UcxTest function header.
+ * 
+ * Use this macro to declare and/or define a #UcxTest function.
+ * 
+ * @param name the name of the test function
+ */
+#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_)
+
+/**
+ * Marks the begin of a test.
+ * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>after</b>
+ * #UCX_TEST_BEGIN.
+ * 
+ * @see #UCX_TEST_END
+ */
+#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\
+        fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+        fwrite("... ", 1, 4, _output_);\
+        jmp_buf _env_; \
+        if (!setjmp(_env_)) {
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition the condition to check
+ * @param message the message that shall be printed out on failure
+ */
+#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \
+        fwrite(message".\n", 1, 2+strlen(message), _output_); \
+        _suite_->failure++; \
+        longjmp(_env_, 1);\
+    }
+
+/**
+ * Macro for a test subroutine function header.
+ * 
+ * Use this to declare and/or define a subroutine that can be called by using
+ * UCX_TEST_CALL_SUBROUTINE().
+ * 
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ * 
+ * @see UCX_TEST_CALL_SUBROUTINE()
+ */
+#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\
+        FILE *_output_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ * 
+ * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ * 
+ * <b>Note:</b> You may <b>only</b> call subroutines within a #UCX_TEST_BEGIN-
+ * #UCX_TEST_END-block.
+ * 
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ * 
+ * @see UCX_TEST_SUBROUTINE()
+ */
+#define UCX_TEST_CALL_SUBROUTINE(name,...) \
+        name(_suite_,_output_,_env_,__VA_ARGS__);
+
+/**
+ * Marks the end of a test.
+ * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>before</b>
+ * #UCX_TEST_END.
+ * 
+ * @see #UCX_TEST_BEGIN
+ */
+#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_TEST_H */
+
diff --git a/ucx/ucx/ucx.h b/ucx/ucx/ucx.h
new file mode 100644 (file)
index 0000000..0944def
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * Main UCX Header providing most common definitions.
+ * 
+ * @file   ucx.h
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_H
+#define        UCX_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   2
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   1
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#ifdef _WIN32
+#if !(defined __ssize_t_defined || defined _SSIZE_T_)
+#include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#define __ssize_t_defined
+#define _SSIZE_T_
+#endif /* __ssize_t_defined and _SSIZE_T */
+#else /* !_WIN32 */
+#include <sys/types.h>
+#endif /* _WIN32 */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    
+
+/**
+ * A function pointer to a destructor function.
+ * @see ucx_mempool_setdestr()
+ * @see ucx_mempool_regdestr()
+ */
+typedef void(*ucx_destructor)(void*);
+
+/**
+ * Function pointer to a compare function.
+ * 
+ * The compare function shall take three arguments: the two values that shall be
+ * compared and optional additional data.
+ * The function shall then return -1 if the first argument is less than the
+ * second argument, 1 if the first argument is greater than the second argument
+ * and 0 if both arguments are equal. If the third argument is
+ * <code>NULL</code>, it shall be ignored.
+ */
+typedef int(*cmp_func)(const void*,const void*,void*);
+
+/**
+ * Function pointer to a distance function.
+ * 
+ * The distance function shall take three arguments: the two values for which
+ * the distance shall be computed and optional additional data.
+ * The function shall then return the signed distance as integer value.
+ */
+typedef intmax_t(*distance_func)(const void*,const void*,void*);
+
+/**
+ * Function pointer to a copy function.
+ * 
+ * The copy function shall create a copy of the first argument and may use
+ * additional data provided by the second argument. If the second argument is
+ * <code>NULL</code>, it shall be ignored.
+
+ * <b>Attention:</b> if pointers returned by functions of this type may be
+ * passed to <code>free()</code> depends on the implementation of the
+ * respective <code>copy_func</code>.
+ */
+typedef void*(*copy_func)(const void*,void*);
+
+/**
+ * Function pointer to a write function.
+ * 
+ * The signature of the write function shall be compatible to the signature
+ * of standard <code>fwrite</code>, though it may use arbitrary data types for
+ * source and destination.
+ * 
+ * The arguments shall contain (in ascending order): a pointer to the source,
+ * the length of one element, the element count and a pointer to the
+ * destination.
+ */
+typedef size_t(*write_func)(const void*, size_t, size_t, void*);
+
+/**
+ * Function pointer to a read function.
+ * 
+ * The signature of the read function shall be compatible to the signature
+ * of standard <code>fread</code>, though it may use arbitrary data types for
+ * source and destination.
+ * 
+ * The arguments shall contain (in ascending order): a pointer to the
+ * destination, the length of one element, the element count and a pointer to
+ * the source.
+ */
+typedef size_t(*read_func)(void*, size_t, size_t, void*);
+
+
+
+#if __GNUC__ >= 5 || defined(__clang__)
+#define UCX_MUL_BUILTIN
+
+#if __WORDSIZE == 32
+/**
+ * Alias for <code>__builtin_umul_overflow</code>.
+ * 
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result)
+#else /* __WORDSIZE != 32 */
+/**
+ * Alias for <code>__builtin_umull_overflow</code>.
+ * 
+ * Performs a multiplication of size_t values and checks for overflow.
+ * 
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result)
+#endif /* __WORDSIZE */
+
+#else /* no GNUC or clang bultin */
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+  *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define ucx_szmul(a, b, result) ucx_szmul_impl(a, b, result)
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * This is a custom implementation in case there is no compiler builtin
+ * available.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t where the result should be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+int ucx_szmul_impl(size_t a, size_t b, size_t *result);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_H */
+
diff --git a/ucx/ucx/utils.h b/ucx/ucx/utils.h
new file mode 100644 (file)
index 0000000..642397a
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file utils.h
+ * 
+ * Compare, copy and printf functions.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_UTILS_H
+#define UCX_UTILS_H
+
+#include "ucx.h"
+#include "string.h"
+#include "allocator.h"
+#include <inttypes.h>
+#include <string.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy().
+ */
+#define UCX_STREAM_COPY_BUFSIZE 4096
+
+/**
+ * Copies a string.
+ * @param s the string to copy
+ * @param data omitted
+ * @return a pointer to a copy of s1 that can be passed to free(void*)
+ */
+void *ucx_strcpy(const void *s, void *data);
+
+/**
+ * Copies a memory area.
+ * @param m a pointer to the memory area
+ * @param n a pointer to the size_t containing the size of the memory area
+ * @return a pointer to a copy of the specified memory area that can
+ * be passed to free(void*)
+ */
+void *ucx_memcpy(const void *m, void *n);
+
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if <code>NULL</code> was
+ * provided for <code>buf</code>, this is the size of the buffer that shall be
+ * implicitly created
+ * @param n the maximum number of bytes that shall be copied
+ * @return the total number of bytes copied
+  */
+size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc,
+        char* buf, size_t bufsize, size_t n);
+
+/**
+ * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @return total number of bytes copied
+ * 
+ * @see #UCX_STREAM_COPY_BUFSIZE
+ */
+#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1)
+
+/**
+ * Shorthand for ucx_stream_bncopy using a default copy buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param n maximum number of bytes that shall be copied
+ * @return total number of bytes copied
+ */
+#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        NULL, UCX_STREAM_COPY_BUFSIZE, n)
+
+/**
+ * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer.
+ * 
+ * @param src the source stream
+ * @param dest the destination stream
+ * @param rfnc the read function
+ * @param wfnc the write function
+ * @param buf a pointer to the copy buffer or <code>NULL</code> if a buffer
+ * shall be implicitly created on the heap
+ * @param bufsize the size of the copy buffer - if <code>NULL</code> was
+ * provided for <code>buf</code>, this is the size of the buffer that shall be
+ * implicitly created
+ * @return total number of bytes copied
+ */
+#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\
+        src, dest, (read_func)rfnc, (write_func)wfnc, \
+        buf, bufsize, (size_t)-1)
+
+/**
+ * Wraps the strcmp function.
+ * @param s1 string one
+ * @param s2 string two
+ * @param data omitted
+ * @return the result of strcmp(s1, s2)
+ */
+int ucx_cmp_str(const void *s1, const void *s2, void *data);
+
+/**
+ * Wraps the strncmp function.
+ * @param s1 string one
+ * @param s2 string two
+ * @param n a pointer to the size_t containing the third strncmp parameter
+ * @return the result of strncmp(s1, s2, *n)
+ */
+int ucx_cmp_strn(const void *s1, const void *s2, void *n);
+
+/**
+ * Wraps the sstrcmp function.
+ * @param s1 sstr one
+ * @param s2 sstr two
+ * @param data ignored
+ * @return the result of sstrcmp(s1, s2)
+ */
+int ucx_cmp_sstr(const void *s1, const void *s2, void *data);
+
+/**
+ * Compares two integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_longint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type long long.
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_longlong(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int16_t.
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int16(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int32_t.
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int32(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type int64_t.
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_int64(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned int.
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned long int.
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_ulongint(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type unsigned long long.
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint16_t.
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint16(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint32_t.
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint32(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two integers of type uint64_t.
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @param data omitted
+ * @return -1, if *i1 is less than *i2, 0 if both are equal,
+ * 1 if *i1 is greater than *i2
+ */
+int ucx_cmp_uint64(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int.
+ * @param i1 pointer to integer one
+ * @param i2 pointer to integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long int.
+ * @param i1 pointer to long integer one
+ * @param i2 pointer to long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type long long.
+ * @param i1 pointer to long long one
+ * @param i2 pointer to long long two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int16_t.
+ * @param i1 pointer to int16_t one
+ * @param i2 pointer to int16_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int32_t.
+ * @param i1 pointer to int32_t one
+ * @param i2 pointer to int32_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type int64_t.
+ * @param i1 pointer to int64_t one
+ * @param i2 pointer to int64_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned int.
+ * @param i1 pointer to unsigned integer one
+ * @param i2 pointer to unsigned integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned long int.
+ * @param i1 pointer to unsigned long integer one
+ * @param i2 pointer to unsigned long integer two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type unsigned long long.
+ * @param i1 pointer to unsigned long long one
+ * @param i2 pointer to unsigned long long two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint16_t.
+ * @param i1 pointer to uint16_t one
+ * @param i2 pointer to uint16_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint32_t.
+ * @param i1 pointer to uint32_t one
+ * @param i2 pointer to uint32_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data);
+
+/**
+ * Distance function for integers of type uint64_t.
+ * @param i1 pointer to uint64_t one
+ * @param i2 pointer to uint64_t two
+ * @param data omitted
+ * @return i1 minus i2
+ */
+intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data);
+
+/**
+ * Compares two real numbers of type float.
+ * @param f1 pointer to float one
+ * @param f2 pointer to float two
+ * @param data if provided: a pointer to precision (default: 1e-6f)
+ * @return -1, if *f1 is less than *f2, 0 if both are equal,
+ * 1 if *f1 is greater than *f2
+ */
+
+int ucx_cmp_float(const void *f1, const void *f2, void *data);
+
+/**
+ * Compares two real numbers of type double.
+ * @param d1 pointer to double one
+ * @param d2 pointer to double two
+ * @param data if provided: a pointer to precision (default: 1e-14)
+ * @return -1, if *d1 is less than *d2, 0 if both are equal,
+ * 1 if *d1 is greater than *d2
+ */
+int ucx_cmp_double(const void *d1, const void *d2, void *data);
+
+/**
+ * Compares two pointers.
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param data omitted
+ * @return -1 if ptr1 is less than ptr2, 0 if both are equal,
+ * 1 if ptr1 is greater than ptr2
+ */
+int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data);
+
+/**
+ * Compares two memory areas.
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @param n a pointer to the size_t containing the third parameter for memcmp
+ * @return the result of memcmp(ptr1, ptr2, *n)
+ */
+int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n);
+
+/**
+ * A <code>printf()</code> like function which writes the output to a stream by
+ * using a write_func().
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ */
+int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...);
+
+/**
+ * <code>va_list</code> version of ucx_fprintf().
+ * @param stream the stream the data is written to
+ * @param wfc the write function
+ * @param fmt format string
+ * @param ap argument list
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap);
+
+/**
+ * A <code>printf()</code> like function which allocates space for a sstr_t
+ * the result is written to.
+ * 
+ * <b>Attention</b>: The sstr_t data is allocated with the allocators
+ * ucx_allocator_malloc() function. So it is implementation dependent, if
+ * the returned sstr_t.ptr pointer must be passed to the allocators
+ * ucx_allocator_free() function manually.
+ * 
+ * <b>Note</b>: The sstr_t.ptr of the return value will <i>always</i> be
+ * <code>NULL</code>-terminated.
+ * 
+ * @param allocator the UcxAllocator used for allocating the result sstr_t
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return a sstr_t containing the formatted string
+ */
+sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...);
+
+/**
+ * <code>va_list</code> version of ucx_asprintf().
+ * 
+ * @param allocator the UcxAllocator used for allocating the result sstr_t
+ * @param fmt format string
+ * @param ap argument list
+ * @return a sstr_t containing the formatted string
+ * @see ucx_asprintf()
+ */
+sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap);
+
+/** Shortcut for ucx_asprintf() with default allocator. */
+#define ucx_sprintf(...) \
+    ucx_asprintf(ucx_default_allocator(), __VA_ARGS__)
+
+/**
+ * A <code>printf()</code> like function which writes the output to a
+ * UcxBuffer.
+ * 
+ * @param buffer the buffer the data is written to
+ * @param ... format string and additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \
+        (write_func)ucx_buffer_write, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_UTILS_H */
+
diff --git a/ucx/utils.c b/ucx/utils.c
new file mode 100644 (file)
index 0000000..101c33f
--- /dev/null
@@ -0,0 +1,448 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx/utils.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+
+/* COPY FUCNTIONS */
+void* ucx_strcpy(const void* s, void* data) {
+    const char *str = (const char*) s;
+    size_t n = 1+strlen(str);
+    char *cpy = (char*) malloc(n);
+    memcpy(cpy, str, n);
+    return cpy;
+}
+
+void* ucx_memcpy(const void* m, void* n) {
+    size_t k = *((size_t*)n);
+    void *cpy = malloc(k);
+    memcpy(cpy, m, k);
+    return cpy;
+}
+
+size_t ucx_stream_bncopy(void *src, void *dest, read_func readfnc,
+        write_func writefnc, char* buf, size_t bufsize, size_t n) {
+    if(n == 0 || bufsize == 0) {
+        return 0;
+    }
+    
+    char *lbuf;    
+    size_t ncp = 0;
+    
+    if(buf) {
+        lbuf = buf;
+    } else {
+        lbuf = (char*)malloc(bufsize);
+        if(lbuf == NULL) {
+            return 0;
+        }
+    }
+    
+    size_t r;
+    size_t rn = bufsize > n ? n : bufsize;
+    while((r = readfnc(lbuf, 1, rn, src)) != 0) {
+        r = writefnc(lbuf, 1, r, dest);
+        ncp += r;
+        n -= r;
+        rn = bufsize > n ? n : bufsize;
+        if(r == 0 || n == 0) {
+            break;
+        }
+    }
+    
+    if (lbuf != buf) {
+        free(lbuf);
+    }
+    
+    return ncp;
+}
+
+/* COMPARE FUNCTIONS */
+
+int ucx_cmp_str(const void *s1, const void *s2, void *data) {
+    return strcmp((const char*)s1, (const char*)s2);
+}
+
+int ucx_cmp_strn(const void *s1, const void *s2, void *n) {
+    return strncmp((const char*)s1, (const char*)s2, *((size_t*) n));
+}
+
+int ucx_cmp_sstr(const void *s1, const void *s2, void *data) {
+    sstr_t a = *(const sstr_t*) s1;
+    sstr_t b = *(const sstr_t*) s2;
+    return sstrcmp(a, b);
+}
+
+int ucx_cmp_int(const void *i1, const void *i2, void *data) {
+   int a = *((const int*) i1);
+   int b = *((const int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_longint(const void *i1, const void *i2, void *data) {
+   long int a = *((const long int*) i1);
+   long int b = *((const long int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_longlong(const void *i1, const void *i2, void *data) {
+   long long a = *((const long long*) i1);
+   long long b = *((const long long*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_int16(const void *i1, const void *i2, void *data) {
+   int16_t a = *((const int16_t*) i1);
+   int16_t b = *((const int16_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_int32(const void *i1, const void *i2, void *data) {
+   int32_t a = *((const int32_t*) i1);
+   int32_t b = *((const int32_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_int64(const void *i1, const void *i2, void *data) {
+   int64_t a = *((const int64_t*) i1);
+   int64_t b = *((const int64_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint(const void *i1, const void *i2, void *data) {
+   unsigned int a = *((const unsigned int*) i1);
+   unsigned int b = *((const unsigned int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_ulongint(const void *i1, const void *i2, void *data) {
+   unsigned long int a = *((const unsigned long int*) i1);
+   unsigned long int b = *((const unsigned long int*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) {
+   unsigned long long a = *((const unsigned long long*) i1);
+   unsigned long long b = *((const unsigned long long*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint16(const void *i1, const void *i2, void *data) {
+   uint16_t a = *((const uint16_t*) i1);
+   uint16_t b = *((const uint16_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint32(const void *i1, const void *i2, void *data) {
+   uint32_t a = *((const uint32_t*) i1);
+   uint32_t b = *((const uint32_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_uint64(const void *i1, const void *i2, void *data) {
+   uint64_t a = *((const uint64_t*) i1);
+   uint64_t b = *((const uint64_t*) i2);
+   if (a == b) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int*) i1);
+   intmax_t b = *((const int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long int*) i1);
+   intmax_t b = *((const long int*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const long long*) i1);
+   intmax_t b = *((const long long*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int16_t*) i1);
+   intmax_t b = *((const int16_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int32_t*) i1);
+   intmax_t b = *((const int32_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) {
+   intmax_t a = *((const int64_t*) i1);
+   intmax_t b = *((const int64_t*) i2);
+   return a - b;
+}
+
+intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned int*) i1);
+   uintmax_t b = *((const unsigned int*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned long int*) i1);
+   uintmax_t b = *((const unsigned long int*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const unsigned long long*) i1);
+   uintmax_t b = *((const unsigned long long*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint16_t*) i1);
+   uintmax_t b = *((const uint16_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint32_t*) i1);
+   uintmax_t b = *((const uint32_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) {
+   uintmax_t a = *((const uint64_t*) i1);
+   uintmax_t b = *((const uint64_t*) i2);
+   return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a);
+}
+
+int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) {
+   float a = *((const float*) f1);
+   float b = *((const float*) f2);
+   float e = !epsilon ? 1e-6f : *((float*)epsilon);
+   if (fabsf(a - b) < e) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) {
+   double a = *((const double*) d1);
+   double b = *((const double*) d2);
+   double e = !epsilon ? 1e-14 : *((double*)epsilon);
+   if (fabs(a - b) < e) {
+       return 0;
+   } else {
+       return a < b ? -1 : 1;
+   }
+}
+
+int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) {
+    const intptr_t p1 = (const intptr_t) ptr1;
+    const intptr_t p2 = (const intptr_t) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1  < p2 ? -1 : 1;
+    }
+}
+
+int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) {
+    return memcmp(ptr1, ptr2, *((size_t*)n));
+}
+
+/* PRINTF FUNCTIONS */
+
+#ifdef va_copy
+#define UCX_PRINTF_BUFSIZE 256
+#else
+#pragma message("WARNING: C99 va_copy macro not supported by this platform" \
+                " - limiting ucx_*printf to 2 KiB")
+#define UCX_PRINTF_BUFSIZE 0x800
+#endif
+
+int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...) {
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = ucx_vfprintf(stream, wfc, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap) {
+    char buf[UCX_PRINTF_BUFSIZE];
+#ifdef va_copy
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret < 0) {
+        return ret;
+    } else if (ret < UCX_PRINTF_BUFSIZE) {
+        return (int)wfc(buf, 1, ret, stream);
+    } else {
+        if (ret == INT_MAX) {
+            errno = ENOMEM;
+            return -1;
+        }
+        
+        int len = ret + 1;
+        char *newbuf = (char*)malloc(len);
+        if (!newbuf) {
+            return -1;
+        }
+        
+        ret = vsnprintf(newbuf, len, fmt, ap2);
+        if (ret > 0) {
+            ret = (int)wfc(newbuf, 1, ret, stream);
+        }
+        free(newbuf);
+    }
+    return ret;
+#else
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret < 0) {
+        return ret;
+    } else if (ret < UCX_PRINTF_BUFSIZE) {
+        return (int)wfc(buf, 1, ret, stream);
+    } else {
+        errno = ENOMEM;
+        return -1;
+    }
+#endif
+}
+
+sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...) {
+    va_list ap;
+    sstr_t ret;
+    va_start(ap, fmt);
+    ret = ucx_vasprintf(allocator, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+sstr_t ucx_vasprintf(UcxAllocator *a, const char *fmt, va_list ap) {
+    sstr_t s;
+    s.ptr = NULL;
+    s.length = 0;
+    char buf[UCX_PRINTF_BUFSIZE];
+#ifdef va_copy
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) {
+        s.ptr = (char*)almalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t)ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else if (ret == INT_MAX) {
+        errno = ENOMEM;
+    } else  {
+        int len = ret + 1;
+        s.ptr = (char*)almalloc(a, len);
+        if (s.ptr) {
+            ret = vsnprintf(s.ptr, len, fmt, ap2);
+            if (ret < 0) {
+                free(s.ptr);
+                s.ptr = NULL;
+            } else {
+                s.length = (size_t)ret;
+            }
+        }
+    }
+#else
+    int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap);
+    if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) {
+        s.ptr = (char*)almalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t)ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else {
+        errno = ENOMEM;
+    }
+#endif
+    return s;
+}