    //@include lib_Class.js
    //@include lib_sol.common.JsonUtils.js

    /**
     * File utilities
     *
     * @author ELO Digital Office GmbH
     *
     * @elojc
     * @eloas
     * @eloix
     */
    sol.define("sol.common.FileUtils", {

      singleton: true,

      bom: "\uFEFF", // ByteOrderMark (BOM);

      /**
       * Clean path parts in a given array
       * @param {Array} pathPartsArr Array of path parts
       * @param {String} replacement Replacement string
       * @return {Array} Clean path parts array
       */
      cleanPathParts: function (pathPartsArr, replacement) {
        var me = this,
            cleanPathPartsArr = [],
            i, cleanPathPart;

        pathPartsArr = pathPartsArr || [];

        for (i = 0; i < pathPartsArr.length; i++) {
          cleanPathPart = me.sanitizeFilename(pathPartsArr[i], replacement);
          cleanPathPartsArr.push(cleanPathPart);
        }

        return cleanPathPartsArr;
      },

      /**
       * Cleans the given file name so that the name contains only valid file name chars.
       * @param {String} fileName File name to clean
       * @param {String} replacement Replacement string
       * @return {String} Sanitized filename
       */
      sanitizeFilename: function (fileName, replacement) {
        var me = this,
            cleanFileName;

        me.logger.enter("sanitizeFilename", arguments);

        replacement = replacement || " ";
        cleanFileName = String(fileName).replace(/[\/\?<>\\:\*\|":]/g, replacement);
        me.logger.exit("sanitizeFilename", cleanFileName);

        return cleanFileName;
      },

      /**
       * Extracts the file name from a file object (without the file extension)
       * @param {java.io.File} file File
       * @return {String} The name of the file
       */
      getName: function (file) {
        var me = this,
            nameWithoutExtension;

        if (!file) {
          return "";
        }

        me.logger.enter("getName", arguments);

        nameWithoutExtension = Packages.org.apache.commons.io.FilenameUtils.removeExtension(file.name) + "";

        me.logger.exit("getName", nameWithoutExtension);

        return nameWithoutExtension;
      },

      /**
       * Extracts the file extension from a file object
       * @param {java.io.File} file File
       * @return {String} The file extension
       */
      getExtension: function (file) {
        var me = this,
            extension;

        if (!file) {
          return "";
        }

        me.logger.enter("getExtension", file.canonicalPath + "");

        extension = Packages.org.apache.commons.io.FilenameUtils.getExtension(file.name) + "";

        me.logger.exit("getExtension", extension + "");

        return extension;
      },

      /**
       * Extracts the file extension from a path
       * @param {String} filePath File path
       * @return {String} File extension
       */
      getExtensionFromPath: function (filePath) {
        var me = this,
            extension;

        if (!filePath) {
          return "";
        }

        me.logger.enter("getExtensionFromPath", filePath);

        extension = Packages.org.apache.commons.io.FilenameUtils.getExtension(filePath) + "";

        me.logger.exit("getExtensionFromPath", extension);

        return extension;
      },

      /**
       * Change the extension of a file.
       * @param {java.io.File} file Source file
       * @param {String} extension New file extension
       * @return {File}
       */
      changeExtension: function (file, extension) {
        var me = this,
            _result,
            _extension = extension.indexOf(".") != 0 ? "." + extension : extension;

        me.logger.enter("changeExtension", arguments);
        _result = new File(me.removeExtension(file.absolutePath) + _extension);
        me.logger.exit("changeExtension", _result + "");
        return _result;
      },

      /**
       * Removes the extension of a file or file path
       * @param {java.io.File|String} file File or file path
       * @return {String} File path without extension
       */
      removeExtension: function (file) {
        var filePath, pathWithoutExtension;

        filePath = (file instanceof java.io.File) ? file.canonicalPath : file;

        pathWithoutExtension = Packages.org.apache.commons.io.FilenameUtils.removeExtension(filePath);
        return pathWithoutExtension;
      },

      /**
       * Returns a timestamp of the current time as string.
       * @param {String} pattern Timestamp pattern
       * @return {String} Timestamp as string
       */
      getTimeStampString: function (pattern) {
        var me = this,
            _result;
        me.logger.enter("getTimeStampString", arguments);
        pattern = pattern || "yyyyMMddHHmmss";
        try {
          _result = String(Packages.org.apache.commons.lang3.time.DateFormatUtils.format(new java.util.Date(), pattern));
        } catch (ex) {
          _result = String(Packages.org.apache.commons.lang.time.DateFormatUtils.format(new java.util.Date(), pattern));
        }
        me.logger.exit("getTimeStampString", _result);
        return _result;
      },

      /**
       * Returns true if the path exists
       * @param {String} path Path
       * @return {Boolean}
       */
      exists: function (path) {
        var me = this,
            _result;
        me.logger.enter("exists", arguments);
        _result = new File(path).exists();
        me.logger.exit("exists", _result);
        return _result;
      },

      /**
       * Returns the path of the parent
       * @param {java.io.File} path Path
       * @return {String}
       */
      getParentPath: function (path) {
        return String(new File(path).parent);
      },

      /**
       * Writes a string into a file
       * @param {String} filePath File path
       * @param {String} content Text content
       * @param {Object} params Parameters
       * @param {Boolean} params.bom Add byte order mark
       * @param {Boolean} [params.encoding=UTF-8] Encoding
       */
      writeStringToFile: function (filePath, content, params) {
        var me = this;

        if (!filePath) {
          throw "File path is empty";
        }
        content = content || "";
        params = params || {};
        params.encoding = params.encoding || "UTF-8";

        content = (params.bom) ? me.bom + content : content;

        Packages.org.apache.commons.io.FileUtils.writeStringToFile(new File(filePath + ""), content, params.encoding);
      },

      /**
       * Writes a string into a file
       * @param {String} path Path
       * @param {String} stringArray String array
       */
      writeStringArrayToFile: function (path, stringArray) {
        var me = this;
        me.logger.enter("writeStringArrayToFile", arguments);
        Packages.org.apache.commons.io.FileUtils.writeStringToFile(new File(path), stringArray.join("\r\n"), "UTF-8");
        me.logger.exit("writeStringArrayToFile");
      },

      /**
       * Writes a configuation into a file
       * @param {String} path Path
       * @param {String} config Configuration object
       */
      writeConfigToFile: function (path, config) {
        var me = this,
            content;
        me.logger.enter("writeConfigToFile", arguments);
        content = sol.common.JsonUtils.stringifyAll(config, { tabStop: 2 });
        me.writeStringToFile(path, content);
        me.logger.exit("writeConfigToFile");
      },

      /**
       * Rename a file
       * @param {String} path Path
       * @param {String} newName New file name
       * @return {java.io.File}
       */
      rename: function (path, newName) {
        var me = this,
            srcFile, dstFile;
        me.logger.enter("rename", arguments);
        srcFile = new File(path);
        dstFile = new File(srcFile.parentFile.absolutePath + File.separator + newName);
        if (dstFile.exists()) {
          dstFile.delete();
        }
        Packages.org.apache.commons.io.FileUtils.moveFile(srcFile, dstFile);
        me.logger.exit("rename", dstFile + "");
        return dstFile;
      },

      /**
       * Deletes a file
       * @param {java.io.File|String} file File or file path
       * @param {Object} params Parameters
       * @param {Boolean} params.quietly Delete quietly
       */
      delete: function (file, params) {
        var me = this;

        params = params || {};
        if (!file) {
          return;
        }

        me.logger.enter("delete", arguments);

        if (!(file instanceof java.io.File)) {
          file = new java.io.File(new java.lang.String(file));
        }

        if (params.quietly) {
          Packages.org.apache.commons.io.FileUtils.deleteQuietly(file);
        } else {
          file["delete"]();
        }
        me.logger.exit("delete");
      },

      /**
       * Deletes files
       * @param {Object} config Config
       * @param {String} config.dirPath Directory path
       * @param {String} config.prefix Prefix
       * @param {String} config.suffix Suffix
       */
      deleteFiles: function (config) {
        var me = this,
            filesIterator;
        me.logger.enter("deleteFiles", arguments);

        if (config.dirPath && config.prefix) {
          filesIterator = Packages.org.apache.commons.io.FileUtils.iterateFiles(new File(config.dirPath),
            new Packages.org.apache.commons.io.filefilter.PrefixFileFilter(config.prefix, Packages.org.apache.commons.io.IOCase.INSENSITIVE), null);
        }
        if (config.dirPath && config.suffix) {
          filesIterator = Packages.org.apache.commons.io.FileUtils.iterateFiles(new File(config.dirPath),
            new Packages.org.apache.commons.io.filefilter.SuffixFileFilter(config.suffix, Packages.org.apache.commons.io.IOCase.INSENSITIVE), null);
        }
        if (!filesIterator) {
          me.logger.exit("deleteFiles");
          return;
        }
        while (filesIterator.hasNext()) {
          Packages.org.apache.commons.io.FileUtils.deleteQuietly(filesIterator.next());
        }
        me.logger.exit("deleteFiles");
      },

      contentTypeExtensions: {
        bmp: "image/bmp",
        ico: "image/x-ico",
        jpg: "image/jpeg",
        png: "image/png"
      },

      /**
       * Loads a file to a FileData structure
       * @param {String} filePath File path
       * @param {String} contentType Content type
       * @return {de.elo.ix.client.FileData}
       */
      loadToFileData: function (filePath, contentType) {
        var me = this,
            fileData;
        me.logger.enter("loadToFileData", arguments);
        fileData = new FileData();
        if (!contentType) {
          contentType = me.contentTypeExtensions[me.getExtensionFromPath(filePath).toLowerCase()];
        }
        fileData.contentType = contentType;
        fileData.data = Packages.org.apache.commons.io.FileUtils.readFileToByteArray(new File(filePath));
        me.logger.exit("loadToFileData", fileData);
        return fileData;
      },

      /**
       * Save file data to file
       * @param {de.elo.ix.client.FileData} fileData
       * @param {String} filePath
       */
      saveFileData: function (fileData, filePath) {
        if (!fileData) {
          throw "fileData is empty";
        }
        if (!filePath) {
          throw "filePath is empty";
        }
        Packages.org.apache.commons.io.FileUtils.writeByteArrayToFile(new File(filePath), fileData.data);
      },

      /**
       * Reads a file into a string
       * @param {String} filePath FilePath
       * @param {Object} params Parameters
       * @param {String} params.encoding Encoding
       * @return {String}
       */
      readFileToString: function (filePath, params) {
        var me = this,
            str;
        me.logger.enter("readFileToString", arguments);
        params = params || {};
        params.encoding = params.encoding || "UTF-8";
        str = String(Packages.org.apache.commons.io.FileUtils.readFileToString(new File(filePath), params.encoding));

        me.logger.exit("readFileToString");
        return str;
      },

      /**
       * Reads a file into an object
       * @param {String} filePath FilePath
       * @param {Object} params Parameters
       * @param {String} params.encoding Encoding
       * @return {String}
       */
      readFileToObject: function (filePath, params) {
        var me = this,
            str, obj;

        str = me.readFileToString(filePath, params);

        obj = JSON.parse(str);

        return obj;
      },

      /**
       * Writes an object into a file
       * @param {Object} obj Object
       * @param {String} filePath File path
       */
      writeObjectToFile: function (obj, filePath) {
        var me = this,
            json;

        json = sol.common.JsonUtils.stringifyAll(obj, { tabStop: 2 });
        me.writeStringToFile(filePath, json);
      },

      /**
       * Reads a file into a string
       * @param {String} filePath FilePath
       * @return {String}
       */
      readConfig: function (filePath) {
        var me = this,
            content, _result;
        me.logger.enter("readConfig", arguments);
        content = me.readFileToString(filePath);
        _result = JSON.parse(content);
        me.logger.exit("readConfig", _result);
        return _result;
      },

      /**
       * Returns the URL of a file
       * @param {String} filePath File path
       * @return {String} URL
       */
      getUrlFromFilePath: function (filePath) {
        var url;

        if (!filePath) {
          throw "File path is empty";
        }
        url = String(new File(filePath).toURI().toURL().toString());
        return url;
      },

      /**
       * Returns the temp directory path
       * @return {String} Temp directory path
       */
      getTempDirPath: function () {
        var tempDirPath;
        tempDirPath = String(java.lang.System.getProperty("java.io.tmpdir"));
        return tempDirPath;
      },

      /**
       * Creates a temporary directory
       * @param {Object} params Parameters
       * @param {String} params.prefix Prefix
       * @return {java.io.File} Temporary directory
       */
      createTempDir: function (params) {
        var me = this,
            tempDirBasePath, timestamp, tempDirPath, tempDir;

        params = params || {};
        params.prefix = params.prefix || "temp";
        tempDirBasePath = me.getTempDirPath();
        timestamp = me.getTimeStampString();
        tempDirPath = tempDirBasePath + File.separator + params.prefix + "_" + timestamp;
        tempDir = new File(tempDirPath);
        Packages.org.apache.commons.io.FileUtils.forceMkdir(tempDir);
        return tempDir;
      },

      /**
       * Makes directories
       * @param {java.io.File} dir Directory
       */
      makeDirectories: function (dir) {
        Packages.org.apache.commons.io.FileUtils.forceMkdir(dir);
      },

      /**
       * Returns the OS user name
       * @return {String} Temp directory path
       */
      getOsUserName: function () {
        var userName;
        userName = String(java.lang.System.getProperty("user.name"));
        return userName;
      },

      /**
       * Returns the temp directory path
       * @return {String} Temp directory path
       */
      getHomeDirPath: function () {
        var homeDirPath;
        homeDirPath = String(java.lang.System.getProperty("user.home"));
        return homeDirPath;
      },

      /**
       * Checks wether the path is writeable
       * @param {String} pathString Path
       * @returns `true` if the path is writeable
       */
      isWritable: function (pathString) {
        var path, isWriteable;

        if (!pathString) {
          throw "Path is missing";
        }
        path = java.nio.file.FileSystems.default.getPath(pathString);
        isWriteable = java.nio.file.Files.isWritable(path);

        return isWriteable;
      },

      /**
       * Copies a file
       * @param {java.io.File} srcFile
       * @param {java.io.File} dstFile
       */
      copyFile: function (srcFile, dstFile) {
        if (!srcFile) {
          throw "Source file is empty";
        }
        if (!dstFile) {
          throw "Destination file is empty";
        }
        Packages.org.apache.commons.io.FileUtils.copyFile(srcFile, dstFile);
      },

      /**
       * Download documents
       *
       * @param {String} objId Folder or object ID
       * @param {String} dstDirPath Destination directory path
       * @param {Object} params Parameters
       * @param {Boolean} [params.makeDstDirs=true] Make directories
       * @param {Boolean} params.cleanDstDir Clean destination directory
       * @param {Boolean} params.includeReferences Include references
       * @return {java.io.File[]} Downloaded files
       */
      downloadDocuments: function (objId, dstDirPath, params) {
        var me = this,
            downloadedFiles = [],
            sord, children, dstDir, findConfig, downloadedFile;

        if (!objId) {
          throw "Folder ID is empty";
        }

        if (!dstDirPath) {
          throw "Destination directory path is empty";
        }

        dstDir = new java.io.File(dstDirPath);

        params = params || {};
        params.makeDstDirs = (params.makeDstDirs == false) ? false : true;

        if (params.makeDstDirs) {
          sol.common.FileUtils.makeDirectories(dstDir);
        }

        if (params.cleanDstDir) {
          sol.common.FileUtils.deleteFiles({ dirPath: dstDirPath });
        }

        try {
          sord = ixConnect.ix().checkoutSord(objId, SordC.mbLean, LockC.NO);

          if (sol.common.SordUtils.isFolder(sord)) {
            findConfig = { includeFolders: false, includeDocuments: true };
            if (params.includeReferences) {
              findConfig.includeReferences = true;
            }
            children = sol.common.RepoUtils.findChildren(sord.id, findConfig);
            if (children) {
              children.forEach(function (child) {
                downloadedFile = me.downloadDocument(child.id, dstDirPath);
                downloadedFiles.push(downloadedFile);
              });
            }
          } else {
            downloadedFile = me.downloadDocument(sord.id, dstDirPath);
            downloadedFiles.push(downloadedFile);
          }
        } catch (ex) {
          me.logger.error(["error reading sord from objId '{0}'", objId], ex);
        }

        return downloadedFiles;
      },

      /**
       * Download file
       * @param {String} objId Object ID
       * @param {String} dstDirPath Destination directory path
       * @return {File} Downloaded document;
       */
      downloadDocument: function (objId, dstDirPath) {
        var me = this,
            editInfo, docVersion, sordName, dstFile;

        editInfo = ixConnect.ix().checkoutDoc(objId + "", "", new EditInfoZ(EditInfoC.mbDocumentMembers, SordC.mbLean), LockC.NO);
        docVersion = editInfo.document.docs[0];
        sordName = me.sanitizeFilename(editInfo.sord.name);
        dstFile = new java.io.File(dstDirPath, java.lang.String.format("%s.%s", sordName, docVersion.ext));
        ixConnect.download(docVersion.url, dstFile);

        return dstFile;
      },

      /**
       * Reads a manifest file
       * @param {String} filePath Manifest file path
       * @return {java.util.Map<Object,Object>} Attributes
       */
      readManifestFile: function (filePath) {
        var manifestFile, attributes;

        manifestFile = new java.util.jar.Manifest(new java.io.FileInputStream(new java.io.File(filePath)));
        attributes = manifestFile.mainAttributes;

        return attributes;
      },

      generateEcdString: function (objId) {
        var ecdString, sordArchive, sord;

        ecdString = "EP\n";

        sordArchive = sol.common.RepoUtils.getSord("1", {
          sordZ: SordC.mbOnlyId
        });
        ecdString += "A" + sordArchive.name + "\n";

        sord = sol.common.RepoUtils.getSord(objId, {
          sordZ: SordC.mbOnlyId
        });
        ecdString += "G" + sord.guid + "\n";
        ecdString += "I" + sord.id + "\n";

        ecdString += "WTOP\n";
        ecdString += "T";

        return ecdString;
      },

      getEcdAsStream: function (objId) {
        var me = this,
            ecdContent;

        ecdContent = me.generateEcdString(objId);
        return new java.io.ByteArrayInputStream(new java.lang.String(ecdContent).getBytes(java.nio.charset.StandardCharsets.UTF_8));
      }
    });

    /**
     * Represents an Ini file
     * @elojc
     * @eloas
     * @eloix
     */
    sol.define("sol.common.IniFile", {

      initialize: function (config) {
        var me = this;
        me.$super("sol.Base", "initialize", [config]);
      },

      /**
       * Parses an INI file
       * @param {String} content Content
       */
      parse: function (content) {
        var me = this,
            lines, i, line, sectionName, sectionArr, equalSignPos, fields;

        if (!content) {
          throw "Content is empty";
        }

        me.sections = [];

        content += "";
        lines = content.split(/\r?\n/);
        for (i = 0; i < lines.length; i++) {
          line = lines[i];
          sectionName = me.getSectionName(line);
          if (sectionName) {
            sectionArr = [sectionName, []];
            me.sections.push(sectionArr);
            continue;
          }
          equalSignPos = line.indexOf("=");
          if (equalSignPos > -1) {
            fields = [line.substring(0, equalSignPos), line.substring(equalSignPos + 1)];
            if (!sectionArr) {
              throw "Section is missing: " + line;
            }
            sectionArr[1].push(fields);
          }
        }
      },

      getSectionName: function (line) {
        var endBracketPos, sectionName;

        if (!line) {
          return;
        }
        if (line.indexOf("[") != 0) {
          return;
        }
        endBracketPos = line.indexOf("]");
        if (endBracketPos > 0) {
          sectionName = line.substring(1, endBracketPos);
          return sectionName;
        }
        return "";
      },

      getValue: function (sectionName, key) {
        var me = this,
            entry, section, i;

        if (typeof key == "undefined") {
          throw "Key name is empty";
        }

        key += "";

        section = me.getSection(sectionName);
        if (!section) {
          return;
        }

        for (i = 0; i < section.length; i++) {
          entry = section[i];
          if (entry[0] == key) {
            return entry[1];
          }
        }

        return "";
      },

      setValue: function (sectionName, key, value) {
        var me = this,
            entry, section, i;

        if (typeof key == "undefined") {
          throw "Key name is empty";
        }

        key += "";

        section = me.getSection(sectionName);
        if (!section) {
          return;
        }

        for (i = 0; i < section.length; i++) {
          entry = section[i];
          if (entry[0] == key) {
            entry[1] = value;
          }
        }
      },

      getSection: function (sectionName) {
        var me = this,
            section, i;

        if (!sectionName) {
          throw "Section name is empty";
        }

        for (i = 0; i < me.sections.length; i++) {
          section = me.sections[i];
          if (section[0] == sectionName) {
            return section[1];
          }
        }
      },

      bom: "\uFEFF", // ByteOrderMark (BOM)

      stringify: function () {
        var me = this,
            lines = [],
            content, sectionArr, sectionName, sectionEntries, entry, i, j;

        for (i = 0; i < me.sections.length; i++) {
          sectionArr = me.sections[i];
          sectionName = sectionArr[0];
          sectionEntries = sectionArr[1];
          lines.push("[" + sectionName + "]");
          for (j = 0; j < sectionEntries.length; j++) {
            entry = sectionEntries[j];
            lines.push(entry[0] + "=" + entry[1]);
          }
        }

        content = me.bom + lines.join("\r\n") + "\r\n";

        return content;
      }
    });